Tuesday, August 04, 2009

Custom workflow action which renders and sends a report for Microsoft Dynamics CRM 4.0 with email

Idea of this custom workflow action is following:
1. Give user possibility to run and export report in one action.
2. Insert this exported report as attachment into email.
3. Sent this email to recipient.


I've created a new project. Type of project is 'Class Library', Framework - 3.0.

First step is to add all required references to reporting service, Workflow assemblies and CRM assemblies:

Reporting Services:





Workflow assemblies:



And Microsoft CRM SDK assemblies.

As a result I retrieved such project:



Second step - is to create a custom workflow action which does't have a required code but can be deployed to CRM. So the code of such class:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Crm.Workflow;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;

namespace SendReportAction
{
[CrmWorkflowActivity("Execute and send a report")]
public class SendReport : SequenceActivity
{
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
return ActivityExecutionStatus.Closed;
}
}
}


Next step is to declare properties for worflow action (url of reporting services web service, report to run, recipient of email with report - in my case a systemuser):

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Crm.Workflow;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;

namespace SendReportAction
{
[CrmWorkflowActivity("Execute and send a report")]
public class SendReport : SequenceActivity
{
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
return ActivityExecutionStatus.Closed;
}

public static DependencyProperty ServiceURLProperty = DependencyProperty.Register("ServiceURL", typeof(string), typeof(SendReport));

[CrmInput("ServiceURL")]
public string ServiceURL
{
get
{
return (string)base.GetValue(ServiceURLProperty);
}
set
{
base.SetValue(ServiceURLProperty, value);
}
}

public static DependencyProperty ReportNameProperty = DependencyProperty.Register("ReportName", typeof(string), typeof(SendReport));

[CrmInput("ReportName")]
public string ReportName
{
get
{
return (string)base.GetValue(ReportNameProperty);
}
set
{
base.SetValue(ReportNameProperty, value);
}
}

public static DependencyProperty MailRecipientProperty = DependencyProperty.Register("MailRecipient", typeof(Lookup), typeof(SendReport));

[CrmInput("MailRecipient")]
[CrmReferenceTarget("systemuser")]
public Lookup MailRecipient
{
get
{
return (Lookup)base.GetValue(MailRecipientProperty);
}
set
{
base.SetValue(MailRecipientProperty, value);
}
}
}
}


Next step - is to write code which will create an email:

if (MailRecipient != null && !MailRecipient.IsNull && !MailRecipient.IsNullSpecified)
{
IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
IWorkflowContext workflowContext = contextService.Context;
ICrmService crmservice = workflowContext.CreateCrmService();

email mail = new email();

activityparty fromparty = new activityparty();
fromparty.partyid = new Lookup();
fromparty.partyid.type = EntityName.systemuser.ToString();
fromparty.partyid.Value = workflowContext.UserId;
mail.from = new activityparty[] { fromparty };

activityparty toparty = new activityparty();
toparty.partyid = new Lookup();
toparty.partyid.type = EntityName.systemuser.ToString();
toparty.partyid.Value = MailRecipient.Value;
mail.to = new activityparty[] { toparty };

mail.subject = "Report Subscription";
mail.sender = "crm@example.com";

mail.description = "Report Subscription";

mail.ownerid = new Owner();
mail.ownerid.type = EntityName.systemuser.ToString();
mail.ownerid.Value = workflowContext.UserId;
Guid createdEmailGuid = crmservice.Create(mail);
}


Next step is to execute and export report:

Reporting.SessionHeader sessionheader = null;
byte[] result;
string encoding;
string mimetype;
Reporting.ParameterValue[] parametersUsed = null;
Reporting.Warning[] warnings;
string[] streamids;

BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
binding.Security.Transport.Realm = string.Empty;
EndpointAddress endpoint = new EndpointAddress(ServiceURL);

Reporting.ReportingServiceSoapClient client = new Reporting.ReportingServiceSoapClient(binding, endpoint);
client.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;

client.Render(ref sessionheader, ReportName, "Excel", null,
null, null, null, null, out result, out encoding, out mimetype,
out parametersUsed, out warnings, out streamids);


Next step is to attach retrieved file to the email:

activitymimeattachment attach = new activitymimeattachment();
attach.activityid = new Lookup(EntityName.email.ToString(), createdEmailGuid);
attach.body = System.Convert.ToBase64String(result);
attach.subject =
attach.filename = "Report.xls";
attach.filesize = new CrmNumber(result.Length);
attach.mimetype = @"application/vnd.ms-excel";

crmservice.Create(attach);


And sent the email:

SendEmailRequest sendrequest = new SendEmailRequest();
sendrequest.EmailId = createdEmailGuid;
sendrequest.TrackingToken = "";
sendrequest.IssueSend = true;

crmservice.Execute(sendrequest);


Full code of this custom workflow action:


using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Crm.Workflow;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using System.ServiceModel;
using System.Security.Principal;

namespace SendReportAction
{
[CrmWorkflowActivity("Execute and send a report")]
public class SendReport : SequenceActivity
{
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
if (MailRecipient != null && !MailRecipient.IsNull && !MailRecipient.IsNullSpecified)
{
IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
IWorkflowContext workflowContext = contextService.Context;
ICrmService crmservice = workflowContext.CreateCrmService();

email mail = new email();

activityparty fromparty = new activityparty();
fromparty.partyid = new Lookup();
fromparty.partyid.type = EntityName.systemuser.ToString();
fromparty.partyid.Value = workflowContext.UserId;
mail.from = new activityparty[] { fromparty };

activityparty toparty = new activityparty();
toparty.partyid = new Lookup();
toparty.partyid.type = EntityName.systemuser.ToString();
toparty.partyid.Value = MailRecipient.Value;
mail.to = new activityparty[] { toparty };

mail.subject = "Report Subscription";
mail.sender = "crm@example.com";

mail.description = "Report Subscription";

mail.ownerid = new Owner();
mail.ownerid.type = EntityName.systemuser.ToString();
mail.ownerid.Value = workflowContext.UserId;
Guid createdEmailGuid = crmservice.Create(mail);

Reporting.SessionHeader sessionheader = null;
byte[] result;
string encoding;
string mimetype;
Reporting.ParameterValue[] parametersUsed = null;
Reporting.Warning[] warnings;
string[] streamids;

BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
binding.Security.Transport.Realm = string.Empty;
EndpointAddress endpoint = new EndpointAddress(ServiceURL);

Reporting.ReportingServiceSoapClient client = new Reporting.ReportingServiceSoapClient(binding, endpoint);
client.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;

client.Render(ref sessionheader, ReportName, "Excel", null,
null, null, null, null, out result, out encoding, out mimetype,
out parametersUsed, out warnings, out streamids);

activitymimeattachment attach = new activitymimeattachment();
attach.activityid = new Lookup(EntityName.email.ToString(), createdEmailGuid);
attach.body = System.Convert.ToBase64String(result);
attach.subject =
attach.filename = "Report.xls";
attach.filesize = new CrmNumber(result.Length);
attach.mimetype = @"application/vnd.ms-excel";

crmservice.Create(attach);

SendEmailRequest sendrequest = new SendEmailRequest();
sendrequest.EmailId = createdEmailGuid;
sendrequest.TrackingToken = "";
sendrequest.IssueSend = true;

crmservice.Execute(sendrequest);
}

return ActivityExecutionStatus.Closed;
}

public static DependencyProperty ServiceURLProperty = DependencyProperty.Register("ServiceURL", typeof(string), typeof(SendReport));

[CrmInput("ServiceURL")]
public string ServiceURL
{
get
{
return (string)base.GetValue(ServiceURLProperty);
}
set
{
base.SetValue(ServiceURLProperty, value);
}
}

public static DependencyProperty ReportNameProperty = DependencyProperty.Register("ReportName", typeof(string), typeof(SendReport));

[CrmInput("ReportName")]
public string ReportName
{
get
{
return (string)base.GetValue(ReportNameProperty);
}
set
{
base.SetValue(ReportNameProperty, value);
}
}

public static DependencyProperty MailRecipientProperty = DependencyProperty.Register("MailRecipient", typeof(Lookup), typeof(SendReport));

[CrmInput("MailRecipient")]
[CrmReferenceTarget("systemuser")]
public Lookup MailRecipient
{
get
{
return (Lookup)base.GetValue(MailRecipientProperty);
}
set
{
base.SetValue(MailRecipientProperty, value);
}
}
}
}


Next step is to build and publish developed custom workflow action:



And now I can use this step in CRM. As an example I'll create on demand workflow which will send 'Contacts' report (this report have no parameters but my custom workflow action can be extended with parameters to be used in report).

This workflow:









Save and publish it and the result of work:













As you see - this works. But there is one trick. To make this workflow action work you have to run Microsoft CRM Asynchronous Service under account which have access to Reports used in the workflow.