Monday, January 5, 2009

How not to mess up your environments (or learn from my mistakes)

Here is a fun one:
An error has occurred. Try this action again. If the problem
continues, check the Microsoft Dynamics CRM Community for solutions or
contact your organization's Microsoft Dynamics CRM Administrator. Finally,
you can contact Microsoft Support.


Right, lots of info there. Troubleshooting? Let's not even talk about what I tried to do. What happened? Well, I broke it. Good thing this was development environment and not production. I did what everyone tells you not to do, but at least I had the forethought to "TRY" it out first.

I had been working with a development system that was quite old. No one really knew when it was set up, if it was a snapshot of production, when the last time data or configurations had been migrated...all we knew was that it wasn't 100% perfect, but it was good enough. I'd been using it for a couple of weeks, trying some configurations out, code, entities, all sorts of stuff.

Suddenly, we were given a new environment built on virtuals - "Yeah!" yelled the developers. This sounded great. It was a recent snapshot of production (minus data), but it worked (for the most part). Minor inconsistencies (that I wont elaborate on) aside, it was good.

At this point, I needed to move any new developments from the old dev system to the new one. No big deal. This would be a good test for moving changes we'd made to the production system...Here goes. First, back up the customizations from the dev virtual, then we dove in. Made a few changes, added some objects, then pulled 3 or 4 entities from the old dev and imported to the virtual.

Let's publish...and get an error. No biggie...we get those. But no solution to be found.

After searching and looking, importing backups and everything it came down to this...

Creating an object in CRM assigns a GUID that is carried over (within the database) when exporting and importing to new environments. If you create a new object in 1 enviroment that has some 1:N or N:1 or N:N relationship to another, then recreate that object in a new environment EVEN IF YOU DON'T IMPORT THAT OBJECT those N:1, 1:N, N:N relationships reference the GUID...if you import an object that is related to the one you created...you're dead in the water. You can't "un-import", even importing the backup entity doesn't fix the issue.

I may explore the configuration database and see if I can manually remove/update, but I think that will just create other issues. I can't delete those objects in my new dev and try to recreate them with an import because the relationships somehow prevent this and they (the relationships) cannot be removed.

Anyway, this has rambled on longer than I wanted to, but wanted to get this info out as I find it important to know.

Again, really good that we found this out now rather than doing it in production :)

Friday, December 19, 2008

Script behind the Price List Field

I thought this was worth a post. This deals with MS-CRM 4.0.

At some point, the Price List field was removed from the Opportunity form. Once you put it back on, you can't actually select a price list. The fix deals with a script that has to be MANUALLY added back through the object XML file. (Thanks to http://dmcrm.blogspot.com/2008/03/removing-price-list-field-from.html).

Maybe you ran into my problem? After importing the customization, when I try to access an Opportunity object I would get an error. I looked and looked, but couldn't figure it out. I must have edited that XML 12 times to be sure I had it right. Anyway, the simple solution was that you must have the Currency field on the form as well. You don't get any sort of error that points you to this and I don't believe there's anything in the script, but this resolved the issue. Currency is read only, so I don't see any other issues.

That said...more posts to come.

Thursday, August 7, 2008

Cleaning up old CRM Active Directory groups

For anyone who has done multiple re-installations of MSCRM in the same domain and wants to figure out which AD groups can be deleted based on GUID, run this query and look at the GUID in the PrivUserGroup column:

select * from dbo.OrganizationBase

Tuesday, July 29, 2008

TDDing a CRM 4.0 Plugin - Part 7

Well, hopefully that goes over all the basics of TDDing a CRM 4.0 Plugin.

After the first post I stopped doing some refactoring. There are also a LOT of tests cases not hit here. I'm not going to code them all out, but I will at least show you the resulting refactored code:


using System;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
using Microsoft.Crm.Sdk;
using NMock2;
using NMock2.Monitoring;
using NUnit.Framework;

namespace TDD_with_CRM_Plugin
{
[TestFixture]
public class PluginTestCases
{
#region Setup/Teardown

[SetUp] // executes at the beginning of each test case. Executed only once per TextFixture
public void Setup()
{
mock = new Mockery();
webService = mock.NewMock();
context = mock.NewMock();
crmService = mock.NewMock();
correlationID = Guid.NewGuid();
}

#endregion

private Mockery mock;
private IWebServiceWrapper webService;
private IPluginExecutionContext context;
private Guid correlationID;
private string resultingUrl = "http://someurl";
private ICrmService crmService;

private void SetupExpectationThatCreateCrmServiceIsCalled()
{
Expect.Once.On(context).Method("CreateCrmService").With(true).Will(Return.Value(crmService));
}

private void SetupExpectationThatCRMWebServiceWillBeCalledWithUpdatedDynamicEntity(
DynamicEntity dynamicEntityWithURL)
{
Expect.Once.On(crmService).Method("Update").With(new DynamicEntityMatcher(dynamicEntityWithURL)).Will(
new PluginRecursionAction(context));
}

private DynamicEntity CreateAndSetupExpectationsThatDynamicEntityIsUpdatedWithURLFromWebService(
DynamicEntity account)
{
DynamicEntity dynamicEntityWithURL = CloneDynamicEntity(account);
dynamicEntityWithURL["new_sharepointurl"] = resultingUrl;
return dynamicEntityWithURL;
}

private void SetupExpectationThatCustomWebServiceIsCalledWithAccount(DynamicEntity account)
{
Expect.Once.On(webService).Method("UpdateAccount").With(account).Will(Return.Value(resultingUrl));
}

private void SetupExpectationsThatCorrelationIDWillBeRead()
{
Expect.Once.On(context).GetProperty("CorrelationId").Will(Return.Value(correlationID));
}

public DynamicEntity GetSampleAccount()
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof (DynamicEntity));
return
(DynamicEntity)
xmlSerializer.Deserialize(new StreamReader(@"Sample Files\account.sample.xml").BaseStream);
}

private DynamicEntity CloneDynamicEntity(DynamicEntity account)
{
if (account == null)
throw new ArgumentNullException("account", "The supplied DynamicEntity cannot be null.");
if (String.IsNullOrEmpty(account.Name))
throw new ArgumentOutOfRangeException("account",
"The name of the DynamicEntity can not be null or blank.");
XmlSerializer xmlSerializer = new XmlSerializer(typeof (DynamicEntity));
MemoryStream memoryStream = new MemoryStream();
xmlSerializer.Serialize(memoryStream, account);
memoryStream.Seek(0, SeekOrigin.Begin);
DynamicEntity clonedDynamicEntity = (DynamicEntity) xmlSerializer.Deserialize(memoryStream);

return clonedDynamicEntity;
}

private DemoPlugin GetDependencyInjectedPlugin()
{
return new DemoPlugin(webService);
}

private void SetupExpectationsThatPostEntityImagePropertyBagIsRead(PropertyBag postEntityImages)
{
Expect.Once.On(context).GetProperty("PostEntityImages").Will(Return.Value(postEntityImages));
}

private PropertyBag CreatePropertyBagWithAccount(DynamicEntity account)
{
PropertyBag postEntityImages = new PropertyBag();
postEntityImages["postImage"] = account;
return postEntityImages;
}

private DynamicEntity CreateSampleAccountDEWithPopulatedValues()
{
// the account entity that fired the update
DynamicEntity account = new DynamicEntity("account");
account["accountid"] = new Key(Guid.NewGuid());
account["accountnumber"] = "someAccountNumber";
account["name"] = "accountName";
return account;
}

[Test]
public void Execute_HappyPathExecutesAsExpected()
{
DemoPlugin plugin = GetDependencyInjectedPlugin();

SetupExpectationsThatCorrelationIDWillBeRead();

DynamicEntity account = CreateSampleAccountDEWithPopulatedValues();
PropertyBag postEntityImages = CreatePropertyBagWithAccount(account);

SetupExpectationsThatPostEntityImagePropertyBagIsRead(postEntityImages);
SetupExpectationThatCustomWebServiceIsCalledWithAccount(account);
SetupExpectationThatCreateCrmServiceIsCalled();

DynamicEntity dynamicEntityWithURL =
CreateAndSetupExpectationsThatDynamicEntityIsUpdatedWithURLFromWebService(account);

SetupExpectationsThatCorrelationIDWillBeRead();
SetupExpectationThatCRMWebServiceWillBeCalledWithUpdatedDynamicEntity(dynamicEntityWithURL);

plugin.Execute(context);
mock.VerifyAllExpectationsHaveBeenMet();
}

[Test]
public void New_DefaultDependencyIsCreatedCorrectly()
{
DemoPlugin plugin = new DemoPlugin();
Assert.IsInstanceOfType(typeof (MyWebService), plugin.webService);
}

[Test]
public void New_DependencyInjectionTakes()
{
DemoPlugin plugin = new DemoPlugin(webService);
Assert.AreEqual(webService, plugin.webService);
}
}

public class DynamicEntityMatcher : Matcher
{
private readonly DynamicEntity leftSide;

public DynamicEntityMatcher(DynamicEntity leftSide)
{
this.leftSide = leftSide;
}

public override bool Matches(object o)
{
if (!(o is DynamicEntity))
return false;

DynamicEntity rightSide = (DynamicEntity) o;
if (leftSide.Name != rightSide.Name)
return false;

foreach (Property property in leftSide.Properties)
{
if (!(rightSide.Properties.Contains(property.Name)))
return false;
if (!(PropertiesAreEqual(leftSide[property.Name], rightSide[property.Name])))
return false;
}
return true;
}

private bool PropertiesAreEqual(object leftSideProperty,
object rightSideProperty)
{
if (leftSideProperty == null && rightSideProperty == null)
return true;
if (leftSideProperty == null)
return false;

if (leftSideProperty.GetType() != rightSideProperty.GetType())
return false;
if (leftSideProperty.GetType() == typeof (string))
return (string) leftSideProperty == (string) rightSideProperty;

PropertyInfo[] properties =
leftSideProperty.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo property in properties)
{
object leftValue = property.GetValue(leftSideProperty, null);
object rightValue = property.GetValue(rightSideProperty, null);
if (leftValue == null && rightValue != null)
return false;
if (leftValue != null && !leftValue.Equals(rightValue))
return false;
}
return true;
}

public override void DescribeTo(TextWriter writer)
{
writer.Write("DynamicEntities are equal");
}
}

public class PluginRecursionAction : IAction
{
private readonly IPluginExecutionContext context;

public PluginRecursionAction(IPluginExecutionContext context)
{
this.context = context;
}

#region IAction Members

public void Invoke(Invocation invocation)
{
new DemoPlugin().Execute(context);
}

public void DescribeTo(TextWriter writer)
{
writer.Write("Plugin will recurse");
}

#endregion
}
}


And the actual plugin class, refactored into it's own file:


using System;
using Microsoft.Crm.Sdk;

namespace TDD_with_CRM_Plugin
{
public class DemoPlugin : IPlugin
{
private static Guid correlationID;
internal IWebServiceWrapper webService;

public DemoPlugin() : this(new MyWebService())
{
}

public DemoPlugin(IWebServiceWrapper webService)
{
this.webService = webService;
}

#region IPlugin Members

public void Execute(IPluginExecutionContext context)
{
if (IsRecursiveCall(context))
return;
DynamicEntity account = (DynamicEntity) context.PostEntityImages["postImage"];
string resultingUrl = webService.UpdateAccount(account);
ICrmService crmService = context.CreateCrmService(true);
account["new_sharepointurl"] = resultingUrl;
crmService.Update(account);
ClearCorrelationID();
}

#endregion

private void ClearCorrelationID()
{
correlationID = Guid.Empty;
}

private bool IsRecursiveCall(IPluginExecutionContext context)
{
if (correlationID == Guid.Empty)
correlationID = context.CorrelationId;
else if (correlationID == context.CorrelationId)
return true;
return false;
}
}
}


And the IWebServiceWrapper interface and implementation put into their own class. Notice here that we never tested this class. We don't care about it's functionality with these tests. We just care that it's called.


using Microsoft.Crm.Sdk;

namespace TDD_with_CRM_Plugin
{

public interface IWebServiceWrapper
{
string UpdateAccount(DynamicEntity account);
}

public class MyWebService : IWebServiceWrapper
{
#region IWebServiceWrapper Members

public string UpdateAccount(DynamicEntity account)
{
return "";
}

#endregion
}
}



You'll probably notice rather quickly that there is a lot more code in our test than our actual plugin. Don't be turned off by this. It just means you are thoroughly testing your code. If you make a change to your plugin you can quickly verify it all functions as like you expected without having to deploy and run through a full regression test suite through the CRM UI, which can be significanly more painful than this up-front coding.

I hope this was useful to someone. If you have any questions or comments, please let me know.

=-}

TDDing a CRM 4.0 Plugin - Part 6

One thing that I haven't touched on so far is the testing of a read from the CRM WebService. We've sent it a record to update, but we haven't read from it yet.

There are a few different approaches you can take to this:



  1. Use a plugin that serializes the DynamicEntity in the Image PropertyBags to write to a file. You can then deserialize that file in test cases and use that as your sample objects. The downside to this is that you can only do this for DynamicEntity classes since that's all that is available in the plugins. If you are working with "core" objects, this does you no good.


  2. Create your own sample objects from scratch. This can be very tedious (less so if you're working with core objects, which I don't recommend) and prone to errors as it's likely you'll use the wrong Property type with the wrong attribute (LookupProperty with the "owner" attribute).


  3. Use an Object Builder/Mother to create random objects at runtime. The problem here is that we're talking "random" and that's usually not good unless you want to put significant coding behind making them "smart random". In which case, why not just write the samples from scratch.




The #1 approach is my favorite as it makes for less coding and more realiable objects. However, it means that anytime you want to retrieve objects from the CRM WebService you can't use the standard Retrieve and RetrieveMultiple commands as they return "core" objects by default.

#2 is pretty straight forward and we've already done it before:


DynamicEntity account = new DynamicEntity("account");
account["accountid"] = new Key(Guid.NewGuid());
account["accountnumber"] = "someAccountNumber";
account["name"] = "accountName";


#3, well, I don't even want to get into that. Have fun with it though. =-}

Creating a plugin that will serialize your objects is pretty easy:


public class SerializeEntityPlugin : IPlugin
{
public void Execute(IPluginExecutionContext context)
{
DynamicEntity entity = (DynamicEntity) context.PostEntityImages["PostImage"];
XmlSerializer xmlSerializer = new XmlSerializer(typeof(DynamicEntity));
xmlSerializer.Serialize(new StreamWriter(@"c:\temp\" + GetFilename(entity), false), entity);

}

private string GetFilename(DynamicEntity entity)
{
foreach (Property property in entity.Properties)
{
if (property.GetType() == typeof(KeyProperty))
{
return entity.Name + "--" + ((KeyProperty) property).Value.Value + ".xml";
}
}
string returnValue = entity.Name + "--Unknown Primary Key.xml";
return returnValue;
}
}



Register this against an account and it's update event and then update an account through the UI (make sure the "C:\temp" directory exists). You should get a file called "account--.xml".


Now we've got a serialized DynamicEntity object. We can deserialize this object and use it in our tests. To do that, first copy the resulting file to your solution. Make sure you setup the file to be copied to your output directory so that it's easily available during the runtime of the tests (I would also suggest renaming the file to not include the GUID so that you don't have to worry about it when referencing the filename).


public DynamicEntity GetSampleAccount()
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof (DynamicEntity));
return (DynamicEntity) xmlSerializer.Deserialize(new StreamReader(@"Sample Files\account.sample.xml").BaseStream) ;
}


You could also refactor this out or create a wrapping method to get a variety of different objects for your testing.

In the end, I recommend both techniques (not the 3rd one). You can get a long way with just creating your own objects but if your code depends on all the various CRM value types (Picklist, Lookup, CrmDateTime), then it can be pretty tedious to create the testing objects, whereas this is pretty simple if you don't mind a little plugin deployment.

=-}

TDDing a CRM 4.0 Plugin - Part 5

At the end of Part 4 I mentioned a critical flaw with the current code of our plugin. It will naturally recurse and cause a stack overflow. This is because the plugin that triggers off of the updating of an account also updates that account.

There are a few different ways you can prevent plugin recursion from happening. There are numerous sources that you can google for about how to do this. I'll be using the CorrelationID in the plugin context to catch this.

However, we're more curious about how to test this to make sure it doesn't happen in our production code.

At this point we have to get into specifics of the NMock library. While I'll discuss and show you the specific implementation of the test, I'll go over enough of the concepts that you should be able to use nearly any mocking framework to do the same.

First, let's review the test:


Expect.Once.On(crmService).Method("Update").With(new DynamicEntityMatcher(dynamicEntityWithURL));


Here we're telling the NMock framework that we "expect" that the crmService will be called with a specific instance of the DynamicEntity object (which is checked by our special DynamicEntityMatcher class we created during the last post).

We're going to modify this to tell NMock that when this happens, we expect something else to occur as a result. If we look at the definition of the With method we see it returns an IMatchSyntax interface. One of the methods on that interface is the Will method. The Will method takes an IAction interface. We've used an instance of this interface before:


Expect.Once.On(context).Method("CreateCrmService").With(true).Will(Return.Value(crmService));


Here our IAction is the result of the Return.Value() method and we're saying our method call (CreateCrmService) will return a specific value (crmService). So we just need to create a new IAction:


public class PluginRecursionAction : IAction
{
private readonly IPluginExecutionContext context;

public PluginRecursionAction(IPluginExecutionContext context)
{
this.context = context;
}

public void Invoke(Invocation invocation)
{
new DemoPlugin().Execute(context);
}

public void DescribeTo(TextWriter writer)
{
writer.Write("Plugin will recurse");
}
}


And then update our previous Expect to use this action:


Expect.Once.On(crmService).Method("Update").With(new DynamicEntityMatcher(dynamicEntityWithURL)).Will(new PluginRecursionAction(context));


Now if we run our test case we'll see something we saw a while back:

NMock2.Internal.ExpectationException: unexpected invocation of pluginExecutionContext.PostEntityImages

Knowing our code, this means it's going through and trying to run a second time. Success!

Let's now make our code a little smarter. Since we're going to use the CorrelationID on the context to determine if we're in a recursive call we need to setup the expectation that it will be read from our mock object:


Guid correlationID = Guid.NewGuid();
Expect.Once.On(context).GetProperty("CorrelationId").Will(Return.Value(correlationID));



In fact, we expect that it will be read twice. Once for the initial call and a second time for the result of our custom IAction. So our final code:


Guid correlationID = Guid.NewGuid();
Expect.Once.On(context).GetProperty("CorrelationId").Will(Return.Value(correlationID));
Expect.Once.On(context).GetProperty("CorrelationId").Will(Return.Value(correlationID));



We can run our test again, although it doesn't exactly provide useful information. It's a good habit though... You should see an unexpected call to the PostEntityImages property and two unexecuted expectations for the CorrelationID.

Now it's time to write the code. The basic principle is that I'll have a static variable in my plugin called "correlationID". I'll set this if it's empty to the current context's CorrelationID. If it's not empty, then I'll check to see if it matches the current context's CorrelationID. If it does, then I'm calling myself, if not, then it's something else.

Here is the final code I end up with. Review it at your own pace. Now, this is from a previous plugin I worked with. I just noticed that there is a case where if two updates are occuring at the same time or nearly the same time, it could be problematic. I need to work through some tests for this and see what happens. I would not take this code to be production worthy at this point. Again, I'm teaching TDD with CRM plugins here, not proper CRM plugin development. =-}


public void Execute(IPluginExecutionContext context)
{
if (IsRecursiveCall(context))
return;
DynamicEntity account = (DynamicEntity) context.PostEntityImages["postImage"];
string resultingUrl = webService.UpdateAccount(account);
ICrmService crmService = context.CreateCrmService(true);
account["new_sharepointurl"] = resultingUrl;
crmService.Update(account);
ClearCorrelationID();
}

private void ClearCorrelationID()
{
correlationID = Guid.Empty;
}

private bool IsRecursiveCall(IPluginExecutionContext context)
{
if (correlationID == Guid.Empty)
correlationID = context.CorrelationId;
else if (correlationID == context.CorrelationId)
return true;
return false;
}



Now with this code in place and I re-run the test, it passes. I'm reasonably happy at this point (with the caveat already mentioned) that this plugin should not recurse.

Stay tuned for the (possibly last) post in the series regarding how to mock the retrieval of an object from the CRM WebService.

=-}

TDDing a CRM 4.0 Plugin - Part 4

In Part 3, I showed you how to make the simple call to our external webservice. In this part, we'll expand upon that and show you how to make calls to the CRM WebService with information returned from the other webservice.

However, let's add some context to all of this. Let's say we've got a nicely integrated system between CRM and Sharepoint 2007 (MOSS). Our webservice that we've called in Part 3 takes the account information we supplied and determines if a Sharepoint site needs to be provisioned, and if so, creates that site and populates it with some data. Again, for simplicity I'm not considering all the various issues involved with the integration. We're making this plugin very simple. Assume that all the serious logic and code is all in the webservice that we aren't developing here.

This webservice is called and if a site is provisioned it returns a URL that we can store in CRM and provide as a link to the user so they can easily access the Sharepoint site. For our example here we'll assume the site is provisioned and a URL is returned. We now need to update CRM with that information.

First, let's expand our "Happy Path" test to have the Expect clause include a return value from the webservice call:


Expect.Once.On(webService).Method("UpdateAccount").With(account).Will(Return.Value("http://someurl"));


But of course, before this will work at all, we also need to update our method signature in our interface and the implementing class to have a return value:


public class MyWebService : IWebServiceWrapper
{
public string UpdateAccount(DynamicEntity account)
{
return "";
}
}

public interface IWebServiceWrapper
{
string UpdateAccount(DynamicEntity account);
}


At this point we could run our tests again, but there's no point except to see we didn't break the code (from compiling). The test will still pass because all of our assertions (including the "VerifyAllExpectations...") were met.

Now comes the fun part... we need to make sure the test verifies that the CRM webservice is called and sent the correct data.

But wait! We don't have a CRM WebService. Ah, but we do! It's in the plugin context. And, thankfully, it's an interface, so we can mock it easily:


[Test]
public void Execute_HappyPathExecutesAsExpected()
{
DemoPlugin plugin = GetDependencyInjectedPlugin();

DynamicEntity account = CreateSampleAccountDEWithPopulatedValues();
PropertyBag postEntityImages = CreatePropertyBagWithAccount(account);
SetupExpectationsThatPostEntityImagePropertyBagIsRead(postEntityImages);

Expect.Once.On(webService).Method("UpdateAccount").With(account).Will(Return.Value("http://someurl"));

ICrmService crmService = mock.NewMock();
Expect.Once.On(context).Method("CreateCrmService").With(true).Will(Return.Value(crmService));

plugin.Execute(context);
mock.VerifyAllExpectationsHaveBeenMet();

}


We've said that the "context" object will have the CreateCrmService method called with "true" passed in as a parameter and it will return the crmService object we have mocked.

Now comes the somewhat tricky part... we have this DynamicEntity object that represents our account. We want to update that to reflect the new website URL we got back from our custom webservice and then call the CRM webservice with that object. But remember that all of this code we're setting up is irrelevant of the actual execution of the code.

In other words, even though I've laid out my test code to represent what's happening in the production code, this has no meaning in terms of the actual execution. If I setup this DynamicEntity object, setup all the expects, and then later update a property on it to reflect the new URL, the first usages of the DynamicEntity object will have the website url in it. So, I have to create a second instance of the DynamicEntity object that will represent the object with an updated URL.

Let's get some code in front of you and perhaps this will make more sense:


[Test]
public void Execute_HappyPathExecutesAsExpected()
{
DemoPlugin plugin = GetDependencyInjectedPlugin();

DynamicEntity account = CreateSampleAccountDEWithPopulatedValues();
PropertyBag postEntityImages = CreatePropertyBagWithAccount(account);
SetupExpectationsThatPostEntityImagePropertyBagIsRead(postEntityImages);

string resultingUrl = "http://someurl";
Expect.Once.On(webService).Method("UpdateAccount").With(account).Will(Return.Value(resultingUrl));

ICrmService crmService = mock.NewMock();
Expect.Once.On(context).Method("CreateCrmService").With(true).Will(Return.Value(crmService));

DynamicEntity dynamicEntityWithURL = CloneDynamicEntity(account);
dynamicEntityWithURL["new_sharepointUrl"] = resultingUrl;

Expect.Once.On(crmService).Method("Update").With(dynamicEntityWithURL);

plugin.Execute(context);
mock.VerifyAllExpectationsHaveBeenMet();

}


So I've created a clone of the "account" object we used before and set the property on it with the url from our custom webservice. I've cheated here and used a CloneDynamicEntity method I developed for the ABetterCRM library. I then setup the expectation that it will be used in the Update method of our CRM WebService.

Note: I'm assuming here the Account entity in CRM has been expanded to include a string attribute called "new_sharepointUrl"

Run the test, and we get a failed test because not all expectations were called. Excellent. Let's write the production code now:


public void Execute(IPluginExecutionContext context)
{
DynamicEntity account = (DynamicEntity) context.PostEntityImages["postImage"];
string resultingUrl = webService.UpdateAccount(account);
ICrmService crmService = context.CreateCrmService(true);
account["new_sharepointurl"] = resultingUrl;
crmService.Update(account);
}


Excellent. Now, we run our test and... WHAT?! It failed?

"NMock2.Internal.ExpectationException: unexpected invocation of crmService.Update()
Expected:
1 time: crmService.Update(equal to ) [called 0 times]"

That's weird! We can double check out code 100 times and it looks right.

The trick here is how CRM's DynamicEntity object and how NMock work together. NMock will use the object's Equals method by default to determine if objects are equal. However, if you read earlier posts of mine you'll see that two DynamicEntity objects with the same values do not equate. So, how do we get around this?

There are a couple of options here. You could create a new class that inherits from DynamicEntity and properly overrides the Equals method (my preference). However, if you run across this for other objects then you'll have to keep overriding. If you ever run across a sealed class, you'll be stuck. Let's see if we can take advantage of the NMock library.

If we take a look at the NMock With method we see one option for the parameter is a Matcher class. So let's create our own version of this. Forgive me for not going into details here, but this technique will depend largely on the mocking library you use and each one has a different process for doing this. I don't want this to be too specific to NMock so I'll just give you the code for the matcher:


public class DynamicEntityMatcher: Matcher
{
private readonly DynamicEntity leftSide;

public DynamicEntityMatcher(DynamicEntity leftSide)
{
this.leftSide = leftSide;
}

public override bool Matches(object o)
{
if (!(o is DynamicEntity))
return false;

DynamicEntity rightSide = (DynamicEntity) o;
if (leftSide.Name != rightSide.Name)
return false;

foreach (Property property in leftSide.Properties)
{
if (!(rightSide.Properties.Contains(property.Name)))
return false;
if (!(PropertiesAreEqual(leftSide[property.Name], rightSide[property.Name])))
return false;
}
return true;
}

private bool PropertiesAreEqual(object leftSideProperty,
object rightSideProperty)
{
if (leftSideProperty == null && rightSideProperty == null)
return true;
if (leftSideProperty == null)
return false;

if (leftSideProperty.GetType() != rightSideProperty.GetType())
return false;
if (leftSideProperty.GetType() == typeof(string))
return (string)leftSideProperty == (string)rightSideProperty;

PropertyInfo[] properties = leftSideProperty.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo property in properties)
{
object leftValue = property.GetValue(leftSideProperty, null);
object rightValue = property.GetValue(rightSideProperty, null);
if (leftValue == null && rightValue != null)
return false;
if (leftValue != null && !leftValue.Equals(rightValue))
return false;
}
return true;
}

public override void DescribeTo(TextWriter writer)
{
writer.Write("DynamicEntities are equal");
}
}


Without going line by line, it first compares the names of the two DynamicEntities, then it compares each of the properties using reflection.

Now, with using this Matcher we get a passed test! Yay!

However, there is something here that is very wrong and will cause huge problems. Recursion. This plugin will be registered on the post-update event of the account object. We just performed an update of the account object. This plugin will cause itself to fire. If we don't do anything to catch this, we will get a stack overflow very quickly.

I will discuss how to handle this in Part 5.

=-}