Pages

Wednesday, June 22, 2011

Silverlight and CRM 4

[UPDATE:  A demonstration of this code can be found in a follow-up post.]
My presentation this morning at CRMUG’s 10@10of10 went pretty well, I thought.  There weren’t too many attendees, though.  I can’t say I blame anyone for that.  In my experience, very few jump with glee for a chance to look at code.
Since the format was limited to 10 minutes, I obviously had to truncate a great deal of the information I wanted to provide.  It’s funny, because at first I wondered what I’d fill 10 minutes with.  Those who attended will likely visit this space to retrieve what I promised:  the code.  So, without further ado:
The Code <heavenly chorus>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization;
using System.Xml;

namespace CrmSDK
{
public partial class DynamicEntity
{
private Dictionary<string, Property> namedProperties;

[System.Xml.Serialization.XmlIgnore]
public Dictionary<string, Property> NamedProperties
{
get { return namedProperties; }
set
{
namedProperties = value;
this.RaisePropertyChanged("NamedProperties");
}
}

public DynamicEntity()
{
this.PropertyChanged += new PropertyChangedEventHandler(dynamicEntity_PropertyChanged);
}

private void dynamicEntity_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Properties")
{
NamedProperties = convertPropertyArray(this.Properties);
}
}

private static Dictionary<string, Property> convertPropertyArray(Property[] properties)
{
Dictionary<string, Property> propertyDictionary = new Dictionary<string, Property>();

for (int propertyIndex = 0; propertyIndex < properties.Length; propertyIndex++)
propertyDictionary.Add(properties[propertyIndex].Name, properties[propertyIndex]);

return propertyDictionary;
}
}

public class CrmServiceConnectionParams
{
public String Scheme { get; set; }

public String Url { get; set; }

public CrmAuthenticationToken AuthenticationToken { get; set; }

public BasicHttpSecurityMode SecurityMode { get; set; }

private void setSecurityModeFromScheme()
{
switch (Scheme)
{
case "https":
SecurityMode = BasicHttpSecurityMode.Transport;
break;
default:
SecurityMode = BasicHttpSecurityMode.None;
break;
}
}

public CrmServiceConnectionParams(String url, CrmAuthenticationToken token)
{
if (url.Contains("://"))
{
string[] urlSplit = url.Split(new string[] { "://" }, StringSplitOptions.None);

Scheme = urlSplit[0];
Url = urlSplit[1];
}
else
throw new ArgumentException("Failure creating CrmServiceConnectionParams instance. Invalid or missing URL scheme (e.g. 'http://').");

AuthenticationToken = token;

setSecurityModeFromScheme();
}

public CrmServiceConnectionParams(String scheme, String url, CrmAuthenticationToken token)
{
Scheme = scheme;
Url = url;
AuthenticationToken = token;

setSecurityModeFromScheme();
}

public CrmServiceConnectionParams(String scheme, String url, CrmAuthenticationToken token, BasicHttpSecurityMode securityMode)
{
Scheme = scheme;
Url = url;
AuthenticationToken = token;
SecurityMode = securityMode;
}
}

public class CrmServiceInstance
{
private CrmServiceConnectionParams connectionParams;
public CrmServiceConnectionParams ConnectionParams
{
get { return connectionParams; }
set
{
connectionParams = value;
spawnCrmService();
}
}

private CrmServiceSoapClient crmService;
public CrmServiceSoapClient CrmService
{
get { return crmService; }
set
{
crmService = value;
isCrmServiceReady = true;

if (OnCrmServiceReady != null)
OnCrmServiceReady(this, new EventArgs());
}
}

#region OnCrmServiceReady Event

public event EventHandler<EventArgs> OnCrmServiceReady;

private bool isCrmServiceReady;
public bool IsCrmServiceReady
{
get { return isCrmServiceReady; }
}

#endregion

private static CrmServiceSoapClient CreateCrmService(String crmServiceUrl, CrmAuthenticationToken authToken, BasicHttpSecurityMode securityMode)
{
BasicHttpBinding httpBinding = new BasicHttpBinding(securityMode);
httpBinding.MaxReceivedMessageSize = Int32.MaxValue;

EndpointAddress crmEndpoint = new EndpointAddress(crmServiceUrl);

CrmServiceSoapClient crmService = new CrmServiceSoapClient(httpBinding, crmEndpoint);

MessageHeader authTokenHeader = MessageHeader.CreateHeader("CrmAuthenticationToken",
"http://schemas.microsoft.com/crm/2007/WebServices", string.Empty, new CrmAuthenticationTokenSerializer(authToken));

crmService.ChannelFactory.Endpoint.Behaviors.Add(new CrmServiceBehavior(new CrmServiceMessageInspector(authTokenHeader)));

return crmService;
}

private void spawnCrmService()
{
CrmService = CreateCrmService(
ConnectionParams.Scheme + "://" + ConnectionParams.Url,
ConnectionParams.AuthenticationToken,
ConnectionParams.SecurityMode);
}

public CrmServiceInstance()
{
isCrmServiceReady = false;
}

private class CrmServiceMessageInspector : IClientMessageInspector
{
public MessageHeader ServiceHeader;

#region IClientMessageInspector Members

public void AfterReceiveReply(ref Message reply, object correlationState) { }

public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
request.Headers.Add(ServiceHeader);
return null;
}

#endregion 

public CrmServiceMessageInspector(MessageHeader header)
{
ServiceHeader = header;
}
}

private class CrmServiceBehavior : IEndpointBehavior
{
public CrmServiceMessageInspector ServiceInspector;

#region IEndpointBehavior Members

public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(ServiceInspector);
}

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher dispatcher) 
{
throw new NotImplementedException(); // Silverlight does not invoke this method.
}

public void Validate(ServiceEndpoint endpoint) { }

#endregion

public CrmServiceBehavior(CrmServiceMessageInspector inspector)
{
ServiceInspector = inspector;
}
}
}

public class CrmAuthenticationTokenSerializer : XmlObjectSerializer
{
#region CrmAuthenticationTokenSerializer Members

private readonly string authType;
private readonly string organizationName;
private readonly string callerId;
private readonly string crmTicket;

public CrmAuthenticationTokenSerializer(CrmAuthenticationToken authToken)
{
callerId = Guid.Empty.ToString();
authType = authToken.AuthenticationType.ToString();
organizationName = authToken.OrganizationName;
crmTicket = authToken.CrmTicket;
}

#endregion

#region XmlObjectSerializer Members

public override bool IsStartObject(XmlDictionaryReader reader)
{
return true;
}

public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName)
{
return null;
}

public override void WriteEndObject(XmlDictionaryWriter writer)
{
return;
}

public override void WriteObjectContent(XmlDictionaryWriter writer, object graph)
{
string tokenXmlLiteral = String.Empty;

tokenXmlLiteral += "<AuthenticationType xmlns='http://schemas.microsoft.com/crm/2007/CoreTypes'>"
+ authType
+ "</AuthenticationType>";

if (crmTicket != null && crmTicket != String.Empty)
tokenXmlLiteral += "<CrmTicket xmlns='http://schemas.microsoft.com/crm/2007/CoreTypes'>"
+ crmTicket
+ "</CrmTicket>";

tokenXmlLiteral += "<OrganizationName xmlns='http://schemas.microsoft.com/crm/2007/CoreTypes'>"
+ organizationName
+ "</OrganizationName>"
+ "<CallerId xmlns='http://schemas.microsoft.com/crm/2007/CoreTypes'>"
+ callerId
+ "</CallerId>";

writer.WriteRaw(tokenXmlLiteral);
}

public override void WriteStartObject(XmlDictionaryWriter writer, object graph)
{
return;
}

#endregion
}
}
</heavenly chorus>
So, here’s how this works:
  1. Copy the code into a .cs file of your choosing (I use CrmServiceHelpers.cs);
  2. Change the “namespace” declaration to match the declared namespace for your CRM Web Service Reference;
  3. Start using CrmServiceInstance in conjunction with CrmServiceConnectionParams to get the job done.
CrmServiceInstance exposes the member CrmService, which is the interface you’ll use with calling the Web Service API.  Remember, in Silverlight everything must be done asynchronously, so instead of Execute(), you call ExecuteAsync().  To catch the results, you attach a new handler to the event ExecuteCompleted.  You can find examples of this in Humberto Lezama’s Silverlight code.  As I mentioned in the presentation, this code served as the foundation upon which I built the code above.
CrmServiceInstance is designed to accept implementation as a XAML element.  This is not required, of course, but in my implementation I’ve successfully used CrmServiceInstance as a XAML element, declared as an Application resource, to allow connecting to a data-context element through binding as a {StaticResource}.  Why?  Because I like the way it looks.  Conceivably, I could switch the CrmServiceInstance context on-the-fly for a Silverlight control, thereby allowing that control to communicate with a dynamically defined CRM deployment.  Interesting stuff.
CrmServiceConnectionParams is a simple class that abstracts the parameters required to establish a connection with CRM. It takes, at a minimum, the Web Service URL and a CrmAuthenticationToken. Other constructors are provided to accept more parameters for customization, but the basic constructor requires these two. By assigning a new instance of this class to CrmServiceInstance.ConnectionParams, you call the underlying code within CrmServiceInstance to create a new connection model for the CRM deployment you have targeted.  It’s important to note that this must be done in the code-behind for the XAML.  CrmServiceInstance does not implement DependencyObject, and therefore does not suffer bindings to be made to ConnectionParams.
Because CrmServiceInstance can be configured as a XAML element, it provides two other features: an IsCrmServiceReady member, as a boolean indicator of whether the CrmService member is fully constructed; and a OnCrmServiceReady event, to which you can attach handlers for waiting for the service to become ready.  These are not necessary to use if you will not use CrmServiceInstance as a XAML element—because the construction of CrmService itself is synchronously performed.
Speaking of all this XAML power, there was a presentation link I wanted to provide—but forgot to place in the PowerPoint slides—and that’s a presentation by Daniel Roth and Rob Relyea, several years ago, about XAML.  Otherwise, you now have all the materials to complement the presentation I gave.
Cheers!

Wednesday, June 15, 2011

Slowly Getting Back

For those who have been concerned about the well-being of my daughter, please know that things are doing tremendously well.  Her treatment is far from over, but the chemotherapy portion is nearly complete and a recent surgery was successful at removing 90-99% of her existing mass.  Other treatments remain, but we remain very hopeful due to her resilience thus far.

I’ve been working at night on freelance work for George Doubinski, and have been picking up Silverlight as quickly as possible.  One thing that disappointed me was the dearth of information regarding Silverlight development in CRM 4.  Considering the maturity of both product lines, I would have thought them to have been married much sooner than CRM 2011.  To that end, I’ve decided to participate in CRMUG’s 10@10of10 presentation series and bring what I’ve learned to those of us who haven’t quite moved into CRM 2011 yet.  CRMUG membership is required to attend.

I’m still working up the presentation content, but I’m hoping to have a sensible development example for producing Silverlight code that works for both CRM 4 and CRM 2011.  At any rate, I expect to reproduce the contents and code from the presentation upon this blog; so if you can’t attend the Live Meeting, be sure to check back in the following weeks.

Aside from that, I’ve decided to apply some theme changes to the blog to encourage me to post a little more often.  I’m certainly looking forward to producing more CRM 2011 resources (while bringing forward as many of the CRM 4 developments as are suitable).  However, the pace will be crawling for a while.  Some of you may have seen more recent involvements from me in the CRM Development forum.  Indeed, that’s a pretty good indicator of my free time, because that’s where I spend it.