In their seminal book Design Patterns the authors, collectively known as the Gang of Four, write about one of the most often used patterns in object-oriented design known as the Factory Pattern. It turns out that when working with the Microsoft CRM API from Microsoft .NET the Factory pattern can come in handy.
In order to manipulate data in various CRM entities a developer must instantiate and configure individual objects such as CRMAccount, CRMContact, and CRMLead. It also is often the case that a particular application need only work with one or two of these objects. Further, each object requires that its Url and Credentials properties be set before any of its methods can be invoked.
To handle all of this I created a CRMConfigFactory that combines the Factory Pattern with a configuration section handler. The class reads a custom configuration section such as the one shown here.
<mscrmconfig>
<server>http://crmapp.crm.sbl.com/mscrmservices/</server>
<proxyassembly>Microsoft.Crm.Platform.Proxy,
Version=1.2.3297.0, Culture = neutral,
PublicKeyToken=31bf3856ad364e35</proxyassembly>
<entities>
<entity type="Microsoft.Crm.Platform.Proxy.BizUser"
name="BizUser" srf="BizUser.srf">
<entity type="Microsoft.Crm.Platform.Proxy.BizPrivilege"
name="BizPrivilege" srf="BizPrivilege.srf">
<entity type="Microsoft.Crm.Platform.Proxy.CRMActivity"
name="Activity" srf="CRMActivity.srf">
<entity type="Microsoft.Crm.Platform.Proxy.CRMQuery"
name="Query" srf="CRMQuery.srf">
<entity type="Microsoft.Crm.Platform.Proxy.CRMQueue"
name="Queue" srf="CRMQueue.srf">
</entities>
</mscrmconfig>
As you can see, in this project only five of the CRM entities will be utilized as identified in the entities element, each of which contains an attribute that points to the .srf file that represents the endpoint on the CRM server used to process requests. The server element points to the Microsoft CRM server while the proxy assembly element points to the assembly from which the CRM entities will be loaded.
The CRMConfigFactory class then implements the IConfigurationSectionHandler interface along with the Create method. The Create method is called by the CLR and passes into it the configuration section. As shown here, the method reads the entries from the section and populates an ArrayList of custom CRMEntity structures with the information about the entities.
Public Function Create(ByVal parent As Object, _
ByVal context As Object, ByVal section As XmlNode) _
As Object Implements IConfigurationSectionHandler.Create
' Get the server URL
Dim n As XmlNode
n = section.SelectSingleNode("//server")
_server = n.FirstChild.Value
' Get the proxy assembly
_proxyAssembly = section.SelectSingleNode("proxyassembly").FirstChild.Value
' Get the list of supported entities
Dim entities As XmlNodeList = section.SelectNodes("//entity")
Dim e As XmlNode
For Each e In entities
' Add each entity to the hashtable
Dim ce As New CRMEntity
ce.TypeName = e.Attributes("type").Value
ce.Name = e.Attributes("name").Value
ce.Srf = e.Attributes("srf").Value
_entities.Add(ce.Name, ce)
Next
End Function
The CRMEntity structure simply contains Name, TypeName, and Srf fields to hold the values read from the configuration file.
To implement the Factory Pattern the class then exposes a set of overloaded shared methods called CreateEntity whose signature are shown below:
Public Overloads Shared Function CreateEntity( _
ByVal entityName As String) As SoapHttpClientProtocol
Public Overloads Shared Function CreateEntity( _
ByVal entityType As CRMEntityTypes) As SoapHttpClientProtocol
Public Overloads Shared Function CreateEntity( _
ByVal entityName As String, _
ByVal cred As NetworkCredential) As SoapHttpClientProtocol
Public Overloads Shared Function CreateEntity( _
ByVal entityType As CRMEntityTypes, _
ByVal cred As NetworkCredential) As SoapHttpClientProtocol
This style of Factory method is known as a parameterized factory because the first argument specifies the type of object to create. Here the object is specified using either a string or a value from the custom enumerated type CRMEntityTypes.
Public Enum CRMEntityTypes
BizUser
BizPrivilege
Activity
Query
Queue
End Enum
Providing both a string and enumerated type overload is a good practice so that a user of the class can simply add a new entry to the configuration file without modifying
The second argument of the third and fourth signatures represents the set of credentials used when accessing the CRM server while in the first two signatures the System.Net.CredentialCache.DefaultCredentials object is passed representing the currently logged in user.
Finally, the actual work of creating the object is left to a private method called by each of the CreateEntity methods. Here the CRMEntity object is found by accessing the ArrayList (_entities) using the string value passed in to the method. This works since when a CRMEntityTypes value is passed into the public method it is passed into _createEntity by calling the ToString method. Once the type to create has been found the instantiation occurs using the CreateInstanceAndUnwrap method of the AppDomain object from within the current application domain (AppDomain.CurrentDomain).
Notice that the CRM assembly and type name are passed in. Because, being web service calls, all the CRM classes are derived from SoapHttpClientProtocol, the resulting objects is cast and then the Url and Credentials properties set before returning the created object to the caller.
Private Shared Function _createEntity(ByVal typeKey As String, _
ByVal cred As ICredentials) As SoapHttpClientProtocol
Dim c As CRMEntity
c = CType(_entities(typeKey), CRMEntity)
If c.TypeName Is Nothing Then
Throw New CRMException("CRMEntity '" & typeKey & _
"' does not exist in the configuration file")
End If
Dim o As SoapHttpClientProtocol = CType( _
AppDomain.CurrentDomain.CreateInstanceAndUnwrap( _
_proxyAssembly, c.TypeName), SoapHttpClientProtocol)
o.Url = _server & "\" & c.Srf
o.Credentials = cred
Return o
End Function
In this way a client can simply do any of the following:
Dim o As Object = CRMConfigFactory.CreateEntity(CRMEntityTypes.BizUser)
Dim a As CRMActivity = CType(CRMConfigFactory.CreateEntity("Activity"),_
CRMActivity)
Dim o As Object = CRMConfigFactory.CreateEntity(CRMEntityTypes.Query,cred)