Thursday, September 16, 2010

ICrmService vs CrmService

If you have written a plugin for Dynamics CRM, you have probably had to create an instance of the CrmService class.  The template for writing plugins for CRM includes the following code:


 // Create a Microsoft Dynamics CRM Web service proxy.
 // TODO Uncomment or comment out the appropriate statement.

 // For a plug-in running in the child pipeline, use this statement.
 //CrmService crmService = CreateCrmService(context, true);

 // For a plug-in running in the parent pipeline, use this statement.
 ICrmService crmService = context.CreateCrmService(true);

The first thing that should strike the developer as odd is that for the child pipeline a CrmService is used and for the parent pipeline a ICrmService interface is used.  I made the unfortunate assumption that CrmService could be cast to ICrmService and used a single ICrmService instance in my code instead, since naturally CrmService must implement ICrmService right?

Of course making such assumptions always get you into trouble.  Perhaps the Microsoft CRM team was asleep when covering object oriented design or perhaps they never read Head First Design Patterns by Freeman & Freeman (possibly one of the best books on software engineering ever written).  We will never know why ICrmService wasn't implemented in their CrmService class, but a runtime error is generated when trying to make the cast. 

Even though Microsoft chose to ignore the principles of good object oriented design, that isn't an excuse for us to ignore them.  Oddly, the CrmService implements every method from ICrmService with the same signature with the exception of one method, Execute.  Fortunately design patterns gives us an answer (is there anything design patterns can't do?) through the adapter pattern.  Its fairly straightforward to create a wrapper for CrmService that will expose it through an ICrmService interface.  The following code demonstrates:


/// <summary>
/// Implement wrapper for CrmService to expose it through a ICrmService interface
/// </summary>
public class CrmServiceWrapper:ICrmService
{
    private CrmService _service;

    public CrmServiceWrapper(CrmService service)
    {
        _service = service;
    }

    public Guid Create(BusinessEntity entity)
    {
        if (_service!=null)
        {
            return _service.Create(entity);
        }
        return Guid.Empty;
    }

    public void Delete(string entityName, Guid id)
    {
        if (_service != null)
        {
            _service.Delete(entityName,id);
        }           
    }

    /// <summary>
    /// Execute, this is the only method that is different from the interface,
    /// so we should check the parameter to insure that the passed object
    /// is actually a Request
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    public object Execute(object request)
    {
        if (_service != null)
        {
            if (!(request is Request))
            {
                throw new ArgumentException("Object passed must be a Request object.", "request");
            }
            return _service.Execute((Request)request);
        }
        return null;
    }

    public string Fetch(string fetchXml)
    {
        if (_service != null)
        {
            return _service.Fetch(fetchXml);
        }
        return null;
    }

    public BusinessEntity Retrieve(string entityName, Guid id, Microsoft.Crm.Sdk.Query.ColumnSetBase columnSet)
    {
        if (_service != null)
        {
            return _service.Retrieve(entityName, id, columnSet);
        }
        return null;
    }

    public BusinessEntityCollection RetrieveMultiple(Microsoft.Crm.Sdk.Query.QueryBase query)
    {
        if (_service != null)
        {
            return _service.RetrieveMultiple(query);
        }
        return null;
    }

    public void Update(BusinessEntity entity)
    {
        if (_service != null)
        {
            _service.Update(entity);
        }
    }

    public void Dispose()
    {
        if (_service != null)
        {
            _service.Dispose();
        }
    }
}

By using this class any code written for your plugins can safely be written against the ICrmService interface (as it should be) without you having to worry about whether your code is being called from the parent or child pipeline.  Hopefully Microsoft will include this in a future version so that the wrapper isn't necessary.

2 comments:

  1. Hi John.

    I'm trying to use your class in my custom Plug-In. When I press to Build Solution, I'm getting some casting errors in your code:

    - Value of type 'Microsoft.Crm.Sdk.BusinessEntity' cannot be converted to 'CrmService.BusinessEntity'

    - Value of type 'Microsoft.Crm.Sdk.Query.ColumnSetBase' cannot be converted to 'CrmService.ColumnSetBase'

    - Value of type 'Microsoft.Crm.Sdk.Query.QueryBase' cannot be converted to 'CrmService.QueryBase'

    Can you help me please?

    ReplyDelete
  2. Sorry I took so long to respond to your comment. Are you using the SDK or are you using a web service reference? It is recommended that you use the SDK with plugins rather than the web service reference.

    Based on the errors you are receiving, I'd guess that you are using a web service reference.

    ReplyDelete