Friday, February 12, 2010

How to send sms from Microsoft Dynamics CRM

In order to be able to send sms from Microsoft Dynamics CRM we must have an account with a sms provider. For my example I used an account with http api. This means that I need to send http request to the provider in order to send sms. For my solution I will create a workflow that handles the sms sending. The workflow will be triggered by campaign activities, and the sms will be send to each account in Marketing List (of accounts) of campaign activity. The mobile phone is placed on a custom field in account, named new_mobile.
First of all I we create a new project in Visual Studio 2008 of type CrmWorkflowActivity. Rename the workflow file to SendSMSWorkflow.cs.

using System;
using System.IO;
using System.Net;
using System.Web;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using System.Security.Principal;
// Microsoft Dynamics CRM namespaces
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Workflow;
using Microsoft.Crm.Workflow.Activities;
namespace CustomWorkflowActivity
{
[CrmWorkflowActivity("Send SMS", "My Company")]
public class SendSMSWorkflow : SequenceActivity
{
private int smsToSend;
private int smsSent;
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
try
{
// Get access to the Microsoft Dynamics CRM Web service proxy.
IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
IWorkflowContext context = contextService.Context;
ICrmService crmService = context.CreateCrmService();
...
}
catch (System.Web.Services.Protocols.SoapException ex)
{
throw new InvalidPluginExecutionException(
String.Format("An error occurred in the {0} plug-in.",
this.GetType().ToString()),
ex);
}
return ActivityExecutionStatus.Closed;
}
}
}
We will declare some input variables witch are needed for the specific sms provider
public static DependencyProperty UserProperty =
DependencyProperty.Register("User", typeof(System.String), typeof(SendSMSWorkflow));
///
/// This is the username of the sms provider account
///
[CrmInput("User")]
public String User
{
get
{
return (String)base.GetValue(UserProperty);
}
set
{
base.SetValue(UserProperty, value);
}
}
public static DependencyProperty PasswordProperty =
DependencyProperty.Register("Password", typeof(System.String), typeof(SendSMSWorkflow));
///
/// This is the password of the sms provider account
///
[CrmInput("Password")]
public String Password
{
get
{
return (String)base.GetValue(PasswordProperty);
}
set
{
base.SetValue(PasswordProperty, value);
}
}
public static DependencyProperty SenderProperty =
DependencyProperty.Register("Sender", typeof(System.String), typeof(SendSMSWorkflow));
///
/// This is the sender that will be displayed in sms
///
[CrmInput("Sender")]
public String Sender
{
get
{
return (String)base.GetValue(SenderProperty);
}
set
{
base.SetValue(SenderProperty, value);
}
}
public static DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(System.String), typeof(SendSMSWorkflow));
///
/// This is the text of the sms
///
[CrmInput("Text")]
public String Text
{
get
{
return (String)base.GetValue(TextProperty);
}
set
{
base.SetValue(TextProperty, value);
}
}
public static DependencyProperty ProxyProperty =
DependencyProperty.Register("Proxy", typeof(System.String), typeof(SendSMSWorkflow));
///
/// Proxy to be used (It's necessary where proxy is used to access internet)
///
[CrmInput("Proxy")]
public String Proxy
{
get
{
return (String)base.GetValue(ProxyProperty);
}
set
{
base.SetValue(ProxyProperty, value);
}
}
public static DependencyProperty DemoProperty =
DependencyProperty.Register("Demo", typeof(CrmBoolean), typeof(SendSMSWorkflow));
///
/// I use this so I can set the workflow to demo state, and not send
/// actual sms.
///
[CrmInput("Demo")]
[CrmDefault("false")]
public CrmBoolean Demo
{
get
{
return (CrmBoolean)base.GetValue(DemoProperty);
}
set
{
base.SetValue(DemoProperty, value);
}
}

Next I will declare some output parameters that I will update them with status data.

public static DependencyProperty SmsResultProperty =
DependencyProperty.Register("SmsResult", typeof(System.String), typeof(SendSMSWorkflow));
///
/// Result of sms send workflow. It returns "OK" if everything is ok.
///
[CrmOutput("SmsResult")]
public String SmsResult
{
get
{
return (String)base.GetValue(SmsResultProperty);
}
set
{
base.SetValue(SmsResultProperty, value);
}
}
public static DependencyProperty SmsSentProperty =
DependencyProperty.Register("SmsSent", typeof(CrmNumber), typeof(SendSMSWorkflow));
///
/// Sms that actually sent
///
[CrmOutput("SmsSent")]
public CrmNumber SmsSent
{
get
{
return (CrmNumber)base.GetValue(SmsSentProperty);
}
set
{
base.SetValue(SmsSentProperty, value);
}
}
public static DependencyProperty SmsToSendProperty =
DependencyProperty.Register("SmsToSend", typeof(CrmNumber), typeof(SendSMSWorkflow));
///
/// Sms supposed to be sent
///
[CrmOutput("SmsToSend")]
public CrmNumber SmsToSend
{
get
{
return (CrmNumber)base.GetValue(SmsToSendProperty);
}
set
{
base.SetValue(SmsToSendProperty, value);
}
}

In Workflow execute function first I will retrieve the marketing lists of campaign activity. And for each List I’ll send sms to all members

try
{
// Get access to the Microsoft Dynamics CRM Web service proxy.
IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
IWorkflowContext context = contextService.Context;
ICrmService crmService = context.CreateCrmService();
// We run only for Campaign Activity
if (context.PrimaryEntityName == EntityName.campaignactivity.ToString())
{
smsSent = 0;
smsToSend = 0;
TargetRetrieveDynamic targetRetrieve = new TargetRetrieveDynamic();
RetrieveRequest retrieverequest = new RetrieveRequest();
retrieverequest.ColumnSet = new AllColumns();
retrieverequest.ReturnDynamicEntities = true;
retrieverequest.Target = targetRetrieve;
targetRetrieve.EntityName = EntityName.campaignactivity.ToString();
targetRetrieve.EntityId = context.PrimaryEntityId;
RetrieveResponse retrieved = (RetrieveResponse)crmService.Execute(retrieverequest);
DynamicEntity entity = (DynamicEntity)retrieved.BusinessEntity;
if (entity != null)
{
// Find all activity lists
LinkEntity le = new LinkEntity();
le.LinkFromEntityName = EntityName.list.ToString();
le.LinkFromAttributeName = "listid";
le.LinkToEntityName = EntityName.campaignactivityitem.ToString();
le.LinkToAttributeName = "itemid";
LinkEntity le2 = new LinkEntity();
le2.LinkFromAttributeName = "campaignactivityid";
le2.LinkFromEntityName = EntityName.campaignactivityitem.ToString();
le2.LinkToAttributeName = "activityid";
le2.LinkToEntityName = EntityName.campaignactivity.ToString();
// Create the condition to test the user ID.
ConditionExpression ce = new ConditionExpression();
ce.AttributeName = "activityid";
ce.Operator = ConditionOperator.Equal;
ce.Values = new object[] { context.PrimaryEntityId };
// Add the condition to the link entity.
le2.LinkCriteria = new FilterExpression();
le2.LinkCriteria.Conditions.Add(ce);
le.LinkEntities.Add(le2);
// Put everything together in an expression.
QueryExpression qryExpression = new QueryExpression();
qryExpression.ColumnSet = new AllColumns();
qryExpression.EntityName = EntityName.list.ToString();
qryExpression.LinkEntities.Add(le);
// Return all records.
qryExpression.Distinct = true;
// Execute the query.
BusinessEntityCollection entityResultSet = crmService.RetrieveMultiple(qryExpression);
foreach (list crmList in entityResultSet.BusinessEntities)
{
SendToMembers(crmService, crmList);
}
}
}
SmsResult = "OK";
SmsSent = new CrmNumber(smsSent);
SmsToSend = new CrmNumber(smsToSend);
}
catch (System.Web.Services.Protocols.SoapException ex)
{
throw new InvalidPluginExecutionException(
String.Format("An error occurred in the {0} plug-in.",
this.GetType().ToString()),
ex);
}
return ActivityExecutionStatus.Closed;

Function SendToMembers will send sms to each member

private void SendToMembers(ICrmService crmService, list crmList)
{
if (crmList.membertype == null)
return;
LinkEntity le = new LinkEntity();
QueryExpression qryExpression = new QueryExpression();
// Create the retrieve target.
TargetRetrieveDynamic targetRetrieve = new TargetRetrieveDynamic();
RetrieveRequest retrieverequest = new RetrieveRequest();
retrieverequest.ColumnSet = new AllColumns();
retrieverequest.ReturnDynamicEntities = true;
retrieverequest.Target = targetRetrieve;
switch (crmList.membertype.Value)
{
case 1:
le.LinkFromEntityName = EntityName.account.ToString();
le.LinkFromAttributeName = "accountid";
qryExpression.EntityName = EntityName.account.ToString();
targetRetrieve.EntityName = EntityName.account.ToString();
break;
default:
return;
}
le.LinkToEntityName = EntityName.listmember.ToString();
le.LinkToAttributeName = "entityid";
// Create the condition to test the user ID.
ConditionExpression ce = new ConditionExpression();
ce.AttributeName = "listid";
ce.Operator = ConditionOperator.Equal;
ce.Values = new object[] { crmList.listid.Value };
// Add the condition to the link entity.
le.LinkCriteria = new FilterExpression();
le.LinkCriteria.Conditions.Add(ce);
// Put everything together in an expression.
qryExpression.ColumnSet = new AllColumns();
qryExpression.LinkEntities.Add(le);
// Return all records.
qryExpression.Distinct = true;
BusinessEntityCollection profilesResultSet = crmService.RetrieveMultiple(qryExpression);
DynamicEntity entity;
string mobile;
foreach (BusinessEntity crmEntity in profilesResultSet.BusinessEntities)
{
smsToSend++;
mobile = null;
switch (crmList.membertype.Value)
{
case 1:
account crmAccount = crmEntity as account;
targetRetrieve.EntityId = crmAccount.accountid.Value;
RetrieveResponse retrieved = (RetrieveResponse)crmService.Execute(retrieverequest);
entity = (DynamicEntity)retrieved.BusinessEntity;
// This is custom field
if (entity.Properties.Contains("new_mobile"))
{
mobile = (string)entity.Properties["new_mobile"];
}
break;
default:
return;
}
string strResponse = SendSms(mobile);
if (!string.IsNullOrEmpty(strResponse) && strResponse.ToLower().StartsWith("sent"))
smsSent++;
}
}

Function SendSms makes the actual request to the provider.

private string SendSms(string receiver)
{
if (Demo != null && Demo.Value == true)
return "sent";
string result = null;
// Make some modifications to prepare the phone number for the provider
receiver = PrepareReceiver(receiver);
if (receiver != null)
{
string url = "not set yet";
try
{
url = string.Format(@"http://services.yuboto.com/sms/api/smsc.asp?User={0}&Pass={1}&Action=send&From={2}&To={3}&Text={4}",
HttpUtility.UrlEncode(User),
HttpUtility.UrlEncode(Password),
HttpUtility.UrlEncode(Sender),
HttpUtility.UrlEncode(receiver),
HttpUtility.UrlEncode(Text));
WebRequest request = HttpWebRequest.Create(url);
if (!string.IsNullOrEmpty(Proxy))
{
request.Proxy = new WebProxy(Proxy, true);
}
WebResponse response = request.GetResponse();
StreamReader sresponse = new StreamReader(response.GetResponseStream());
result = sresponse.ReadToEnd();
}
catch (Exception ex)
{
throw new Exception(string.Format("Url '{0}' failed", url), ex);
}
}
return result;
}

Function PrepareReceiver make some modifications to the mobile phone to meet the criteria of the provider

private string PrepareReceiver(string receiver)
{
if (string.IsNullOrEmpty(receiver))
return null;
receiver = receiver.Replace(" ", "");
receiver = receiver.Replace("-", "");
return receiver;
}

This workflow is only for demonstration and its purpose is only to show you the capabilities and the flexibility of Microsoft Dynamics CRM Workflow.