Sunday, May 31, 2009

Writing to batch files with workflow

A quite common customer request is the ability to write information to batchfiles for later processing. Integration with print systems are a typical task that usually is done with batch files. Other scenarios might be the necessity to transfer some customer information outside the system to external processing, such as event management and follow-up (If you opt to not use the excellent accelerator) Larger clients often print out hundreds or thousands of identical letters to their clients, and it's quite possible to manage that from MS CRM

Print systems usually use some sort of XML file and/or markup to generate the letter and the information, and you need to adjust the workflow to fit the layout of your print file in order to get this to work. But the concept of writing to a batch file is quite straight forward. This example will write the email address for a contact to a xml file for processing by other systems.

Fire up Visual Studio, create a new class library and add references to all the usual stuff. when you're done, create a class called WriteToEmailAddress. Remember to set the [PersistOnClose]-attribute as we want to make an additional try if this fails. We need to properties, one to hold the contact and one for the filepath.

[CrmWorkflowActivity("Write E-mail address To Batch File" )]
[PersistOnClose]
public class WriteEmailAddressToBatchFile : Activity
{

public static DependencyProperty ContactProperty = DependencyProperty.Register("Contact", typeof(Lookup), typeof(WriteEmailAddressToBatchFile));
public static DependencyProperty FilepathProperty = DependencyProperty.Register("Filepath", typeof(string), typeof(WriteEmailAddressToBatchFile));
//Define the properties
[CrmInput("Recipient")]
[ValidationOption(ValidationOption.Required)]
[CrmReferenceTarget("contact")]
public Lookup Contact
{
get
{
return (Lookup)base.GetValue(ContactProperty);
}
set
{
//Validate the argument
if (value == null || (value.IsNullSpecified && value.IsNull))
{
throw new ArgumentNullException("Contact Lookup cannot be null or have IsNullSpecified = true", "");
}
else if (value.type != null && value.type != "contact")
{
throw new ArgumentNullException("Contact Lookup must be a contact entity", "");
}
else if (value.Value == Guid.Empty)
{
throw new ArgumentException("Contact Lookup must contain a valid Guid", "");
}

base.SetValue(ContactProperty, value);
}
}

public string Filepath
{
get
{
return base.GetValue(FilepathProperty).ToString();
}
set
{
base.SetValue(FilepathProperty, value);
}
}

What you need to do now is to do a query in the workflow's execute method in order to retrieve the fields you need to write to the file, and then write it. After this we'll add a couple of method to increase the robustness of the code, such as generation of an xml file if it doesn't exist and some error handling procedure and you're almost there.


protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
// Get the context service.
IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
IWorkflowContext context = contextService.Context;

// Use the context service to create an instance of CrmService.
ICrmService crmService = context.CreateCrmService(true);
try
{
XmlDocument xmldoc = WriteToFile.LoadFile(this.Filepath, FileType.PrintFile);
// Create a new XML-element
XmlElement newemail = xmldoc.CreateElement("Email");
// Attribute for ID
XmlAttribute emailid = xmldoc.CreateAttribute("ID");

// Generates a new Guid
emailid.Value = Guid.NewGuid().ToString();

//attach the attribute to the parent element
newemail.SetAttributeNode(emailid);

// New subelements

XmlElement emailaddress = xmldoc.CreateElement("EmailAddress");
XmlElement date = xmldoc.CreateElement("Date");

// Assigns values to the subelements
string _contactid = "";
if (this.Contact != null)
{
_contactid = this.Contact.Value.ToString();
}
else
{
_contactid = "00000000-0000-0000-0000-000000000000";
}
//retrieves values based on GUID
BusinessEntityCollection bec = new BusinessEntityCollection();
QueryExpression query = new QueryExpression();
query.EntityName = "contact";

ColumnSet columns = new ColumnSet();

columns.Attributes.Add("emailaddress1");
query.ColumnSet = columns;

query.Criteria = new FilterExpression();
query.Criteria.FilterOperator = LogicalOperator.And;

ConditionExpression condition1 = new ConditionExpression();
condition1.AttributeName = "contactid";
condition1.Operator = ConditionOperator.Equal;
condition1.Values = new object[] { _contactid };

query.Criteria.Conditions.Add(condition1);
bec = crmService.RetrieveMultiple(query);

contact con = new contact();

con.emailaddress1 = "";


if (bec.BusinessEntities.Count > 0)
{
con = (contact)bec.BusinessEntities[0];
}



emailaddress.InnerText = con.emailaddress1;
con = null;

date.InnerText = System.DateTime.Now.Date.ToLongDateString();

// attach the child elements to the parent

newemail.AppendChild(emailaddress);
newemail.AppendChild(date);

// Adds the new element to the end of the xml file
xmldoc.DocumentElement.InsertAfter(newemail, xmldoc.DocumentElement.LastChild);
//Saves the file
WriteToFile.SaveFile(xmldoc, this.Filepath);
xmldoc = null;
}

catch (Exception ex)
{
//Handle errors here;
}
return base.Execute(executionContext);
}
}

The last thing we need to do is to add methods to handle xml file loading and saving. If the xml file doesn't exists it will be created.

public class WriteToFile
{

public static XmlDocument LoadFile(string path)
{
XmlDocument xmldoc = new XmlDocument();
try
{
if (!File.Exists(path))//if the file doesnt exists we'll create it at the specified location.
{
xmldoc = CreateXmlFile(path);
}
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
xmldoc.Load(fs);
}
}

catch (Exception ex)
{
//Handle errors here;
}
return xmldoc;
}
private static XmlDocument CreateXmlFile(string path)
{

XmlDocument xmldoc = new XmlDocument();
try
{
//let's add the XML declaration section
XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration, "", "");
xmldoc.AppendChild(xmlnode);
//let's add the root element
string rootElementName = "EmailRecipient";

XmlElement xmlelem = xmldoc.CreateElement("", rootElementName, "");
xmldoc.AppendChild(xmlelem);

xmldoc.Save(path);
System.Diagnostics.EventLog appLog = new System.Diagnostics.EventLog();
appLog.Source = "MS CRM Custom Workflow Activity";
System.Text.StringBuilder sb = new StringBuilder();
sb.AppendLine("Message: A new Batch File was created");

appLog.WriteEntry(sb.ToString(), System.Diagnostics.EventLogEntryType.Information);
}

catch (Exception ex)
{
//Handle errors here;
}
return xmldoc;
}
public static void SaveFile(XmlDocument xmldoc, string path)
{
try
{
using (FileStream fsxml = new FileStream(path, FileMode.Truncate, FileAccess.Write, FileShare.ReadWrite))
{
xmldoc.Save(fsxml);
}
}

catch (Exception ex)
{
//Handle errors here;
}
}


}

That's it! Build the project, register it with the pluginregistration tool, and your workflow is ready to use. You might tailor it so it's executed each time a letter is created for instance, and the contents and address information is written instead

Another thing: Remember that the workflow needs succifient rights on the file it's supposed to write to. Make sure that the Network Service account has sufficient rights to write to that file!

No comments:

Post a Comment