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.

=-}

TDDing a CRM 4.0 Plugin - Part 3

In Part 2 I showed you how to TDD the reading of the PostImage entity images from the plugin context. This part will go over how to mock the call to the webservice.

To review, at this point we have gotten the DynamicEntity object out of the plugin context. Now we need to do something useful with it. We will hand it off to a custom built webservice (this is not the CRM webservice) for external processing. There are many different ways to handle an integration like this. I'd prefer something a bit more fail-safe like having a Biztalk server receive the message and process it. In our case though, it couldn't matter less.

So let's start with our test case to make sure our webservice method is called. The first question I always have when starting a new test case is whether to create a new one, or expand on an old one. In this case, we're adding to the basic "happy path" of the plugin. We could create a new test case but it would largely contain all the same code as before. Moreover, as soon as I added in the additional code, I'd have to add the test code (like the Expect calls) to the first test case. In the end, I'd end up with two identical test cases. I don't want to get into the debates here as they're off-track from the discussion at hand. For simplicity, I'm going to add to the old test case.

I need to add a new "Expect" call to the test case to make sure the webservice is getting called. Since the intent of the test is changing, let's rename the method to:


public void Execute_HappyPathExecutesAsExpected()


I've already mocked the object out (IWebServiceWrapper webService) so no need to add additional code there. So all there is to do is add the new expect clause:


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

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

// new code
Expect.Once.On(webService).Method("UpdateAccount").With(account);
// end new code

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

}


If I run the test, I can see it fails because our method called "UpdateAccount" doesn't exist yet. We should resolve this error first because the test case fails in validating the test case code, not because the production code isn't written yet. So, add the UpdateAccount method to the IWebServiceWrapper interface and the implementing class (so the project compiles):


public class MyWebService : IWebServiceWrapper
{
public void UpdateAccount(DynamicEntity account)
{

}
}

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


Good, now when we run the test we see that not all of our expectations were met. Perfect! Now that we know our test is valid, we can write the code that makes the test pass:


public void Execute(IPluginExecutionContext context)
{
DynamicEntity account = (DynamicEntity) context.PostEntityImages["postImage"];
webService.UpdateAccount(account);
}


And when we run out test we can see it now passes!

We'll skip the refactoring this time around as there isn't much to do other then just throwing the new Expect call into it's own method.

Stay tuned for Part 4, where I'll show you how to simulate calls to the CRM WebService.

=-}

MSCRM 4.0 Roadmap

Microsoft released a whitepaper which is essentially a roadmap for MSCRM 4.0 into 2009.

https://mbs.microsoft.com/downloads/customer/documentation/whitepapers/MD_CRM_StatementOfDirection_July08_FINAL.pdf

Some highlights include details around Mobility, Accelerators, BizTalk Integration and SharePoint web parts. Many of these will be coming out in the second half of 2008.

One interesting point is Microsoft's release updates for CRM Online which will include search engine keyword research and bidding as well as campaign management dashboards. Between these enhancements and the forthcoming accelerators it will put MS in a much better competitive position against salesforce.com although far from the huge library in App Exchange.

CRM Mobile Express for 4.0

I'm sure everyone has seen CRM Mobile Express for MSCRM v.3.0 which was released in August of 2006. Unfortunately there is no mobile version yet for MSCRM v.4.0 but I have seen a lot of discussion around whether Mobile Express will run with v.4.0. The good news is...yes! Since the v.3.0 web-services still exist in v.4.0 everything works as it did in v.3.0.

There is only one small tweak you have to make to the Sitemap.xml code that is given in the Mobile Express documentation. Wherever you see "/MobileClient/HomePage.aspx" make sure you put "../.." in front of it (e.g. "../../MobileClient/HomePage.aspx"). The reason for this is the way the CRM application is referenced due to multi-tenancy. The SiteMap for v.4.0 needs to crawl up to the top-level virtual directory to access the web-service.

You also have to put the Microsoft.Crm.Webservice.dll in the GAC on the CRM server.

You can download CRM Mobile Express here: http://www.codeplex.com/crmmobileexpress

If anyone has had less than favorable results with CRM Mobile Express using CRM v.4.0, let us know.

Monday, July 28, 2008

CRM 4.0 Solution Center

Microsoft has released a support site that lists some of the top issues, how-to and readme files. The site is far from a wealth of information but it is a great start:

http://support.microsoft.com/ph/12976

Some helpful links on this page that I commonly run into are:

How to change the Web site port after Dynamics CRM 4.0 is installed
Internet facing deployment scenarios
Installing Dynamics CRM 4.0 with the minimum required permissions

Thursday, July 17, 2008

TDDing a CRM 4.0 Plugin

To anyone reading, I just noticed that my screenshots of code are not clickable and are entirely too small. I will look into a better way of showing you code and update the posts. Sorry for the inconvenience.

UPDATE: I've done a quick change to scale up some of the pics, but they look pretty bad still. I'll see what I can do to get real code in there instead of just pics.

UPDATE 2: OK, I updated Part 2 of the TDD with better source code. If you like, please comment and I'll go back and update Part 1 as well.

=-{

Change Business Unit Permission

Ah, the CRM security model. Nothing is what it seems. I spent about an hour today trying to figure out what controlled the "Change Business Unit" permission and from what I can tell it is not the Business Management tab of a security role that controls this, but rather I had to give full permission on the Service Management tab for the following entities - Calendar, My Work Hours and Service as well as Search Availability and Browse Availability.

The security model continues to amaze me at times.




Wednesday, July 16, 2008

TDDing a CRM 4.0 Plugin - Part 2

In Part 1, I gave a quick introduction into the world of TDD. Nothing I did was specific to CRM. During this post, I'll talk more about specific issues with CRM Plugin development.


Let's start by taking a look at the existing code from Part 1:

public class DemoPlugin { }


Notice here that we haven't setup our plugin to implement the IPlugin interface that is required in a CRM plugin. Since we hadn't tested anything around this we hadn't written the production code yet.



Let's update our class to implement the IPlugin interface:


   
public void Execute(IPluginExecutionContext context)
{
throw new NotImplementedException();
}



We also need to start testing this code. But, what does this code do!? If you recall from Part 1, this code will fire when an account is created or updated and call a webservice, passing vital information to that webservice. We're considering vital information the account's Name, GUID and Account Number. For the sake of this example we don't care what the webservice does with this information since we're mocking out that interface.

So there are a few vital processes here:
  • Retrieve account information from the EntityImage passed in through the plugin context
  • Call the webservice with specific information
So now we need to start with writing out test case. The first thing we'll do is mock out the plugin context and tell NMock that we'll retrieve the EntityImage information from it.

I'm going to start here in skipping past a lot of the detail code analysis and things specific to TDD. I want to focus more on details regarding TDD with CRM Plugins. There are tons of good references out there and just a quick google search for them will provide tons of good examples.

One thing I will say is that I tend to setup my test cases in a specific format. The top portion sets up any mock objects. The next session sets up all expectations in order in which I expect them to occur in code. Next is the method call that I'm testing and then finally any assertions or, in this case, just verifying that all my expectations were met.

This is my test case expecting that the entity image will be read from the plugin context:


[Test]
public void Execute_EntityImageIsReadFromContext()
{
Mockery mock = new Mockery();
IWebServiceWrapper webService = mock.NewMock();
IPluginExecutionContext context = mock.NewMock();

DemoPlugin plugin = GetDependencyInjectedPlugin();

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

PropertyBag postEntityImages = new PropertyBag();
postEntityImages["postImage"] = account;

Expect.Once.On(context).GetProperty("PostEntityImages").Will(Return.Value(postEntityImages));

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

}




I don't want to dwell into the details here because it's specific to the NMock and NUnit frameworks. But let's cover a few bigger points

First is I create a simple DynamicEntity object that will represent the account record as it's passed into the plugin context. Notice here that I'm putting values in. This will be part of the "happy path" tests that will be written. Later on I'll show you how to test for non-happy-paths.

Next, I create a PropertyBag object which will hold the account object created above.

Finally, I tell the NMock framework (that has mocked out the IPluginExecutionContext object) that I Expect the PostEntityImages property to be read. When it is, I want to return the PropertyBag object I created above.

I'll skip the boring screenshot showing that my test doesn't pass because all the invocations weren't happening.

No, if I just implement one line of code I get a passing test:



public void Execute(IPluginExecutionContext context)
{
DynamicEntity account = (DynamicEntity) context.PostEntityImages["postImage"];
}



So that sure seemed like a lot of work in writing that test case just for just one line of code. I beg you not to look at it like this even though such an observation is inevitable. This is not about writing less code, this is about writing better code and more verifiable code. Later on we may change our code and not realize we accidentally got rid of this one line. Our unit test will show us quickly that we have whereas we'd otherwise build the assembly, register the plugin, go into CRM and update an account before we got an error and a little upfront code in a unit test will save you lots in the end. If I didn't believe this I would be spending all this time blogging about it.


Let's continue, shall we?


We've got a passing test, but first I want to refactor some things. As I mentioned before, refactoring is important in TDD. Not only for your production code but refactoring your test cases is immensely important in having easily maintainable test cases. You'll find yourself often wondering why you're writing test cases when you could just write the code and "be done" and having unmanageable test cases will only exacerbate that.


Refactoring is as much an art as anything. I do not pretend that I'm skillful at it but after some simple refactorings I get the following test cases (I have no need to refactor my plugin yet, except for maybe putting it in a proper file but I'll do that later):



[TestFixture]
public class PluginTestCases
{
private Mockery mock;
private IWebServiceWrapper webService;
private IPluginExecutionContext context;

[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();
}

[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);
}

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

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

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

}

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;
}
}



All I did here was rearrange some code to make it more readable. I can verify this by running all my tests again and seeing that they still all pass. A good refactoring tool like Resharper will greatly help in refactoring and make you much more likely to do it.

A few points here that can be refactored/improved upon:

  • The key value for the property bag "postImage" should probably be pulled into either a configurable option or a const value. Configurable is nice but it's probably something you just need in documentation for the implementer of the plugin so that when they register it in the CRM system they know what images to setup the plugin to receive.
  • Regarding deployment (this isn't TDD specific, just a point I like to follow), when developing your plugin you will likely be testing against CRM directly (remember, TDD is a development tool, not a testing tool). You'll have to use one of the registration tools. It's a good idea to export your registration settings into an XML file and store that in your project for reference. This can also be included easily then in a deployment package (msi file or otherwise) to ease deployment on the implementer.

That's it for this part. Stay tuned for part 3 where I show the testing of the call to the webservice.


=-}

Monday, July 14, 2008

TDDing a CRM 4.0 Plugin - Part 1

I was recently introduced to the wonderful world of TDD and I'll never turn back. I've spent less time with headaches, bugs and waiting for an IISReset to complete.

However, when I got back into the world of CRM I was surprised to find little about how to TDD a CRM Plugin.

If you are unfamiliar with TDD, you can find a useful primer over at wikipedia. I'll cover the basics of TDD here in the first post and then cover CRM specifics in later posts.


For this example I will be using NUnit as the testing framework and NMock for mocking interfaces. You may also see some traces of Resharper. While by no means required to do proper TDD development you'll find your productivity greatly increased by using this tool (and no, they aren't paying me to say that. But if they wanted to I'd gladly accept their money).

We'll be writing a plugin that is probably over simplistic. Upon account creation or updation, it will call a webservice. Integration with other systems is probably the thing I am asked most to write so this should cover a pretty common scenario.

Let's get started.

We'll begin with the basic NUnit test fixture and create a simple test case. Our dependency for CRM is handled by the PluginContext object that is passed in to us during runtime. But, we've also got a dependency on the webservice we'll be calling to. We need to make sure that our dependency is created correctly. This makes for a tightly coupled plugin but from my experience, that's OK. I doubt I'd take this approach in a fullblown application as a loosely coupled system usually makes for an easier system to maintain. However, for the sake of this example, I'm comfortable with it. The test case and test fixture end up like this:



Notice how I've created just enough code for this to compile and for the test to run. Resharper helps out immensly here as it recognizes what's missing and can create basic classes and interfaces with types inferred from method parameters and such. I've got the interface for my webservice, a simple instance of it, and my plugin class itself.

I run the unit test now and...

And good, I get my red/failed test. By reviewing the failure I can see that my unit test is correctly failing because what I expected (an instance of my MyWebService class) was null. Perfect. Now we'll write the implementation code to make the unit test pass: And then re-run the unit test: And voila! We have our test passing so we know our dependencies will be setup correctly.

However, when I unit test I want these to be "unit" tests and not integration tests. Therefore, I need to be able to inject my dependency. This allows me to ensure that calls to my webservice are happening correctly without actually standing up a webservice to receive those calls.

So, I'll write my unit test. Again, I've written just enough code to let it all compile but nothing to actually make the test pass. I run it and can see it correctly failing:

I'll go ahead and write the code to make it pass: And the passing test case: Now, a very important part of TDD is refactoring. Refactoring is important as it helps create reusable and maintainable code.

Right now this code has a fatal flaw... if I later write code in one constructor, I have to write it in both constrcutors. Writing code twice is the first indication that you need to refactor. So, let's rewrite the constructors a bit to see if we can remove this duplication. By making my default constructor call my dependency injected constructor I can ensure that any code I need to add to my constructor only needs to be added to the dependency injected constructor:


Now I can rerun my two tests and see they still pass so I know my code still has the same end effect:

At this point I should also refactor my unit tests, but there is little if anything I could do here so I'll skip it. Do not refactor your code and tests at the same time. I also need to refactor my classes and interfaces into correct file structures but I won't cover that here (again, Resharper or another refactoring tool can help immensly with this).

There is a lot of discipline involved with TDD. It may seem like a lot of work upfront but from my experience with it that work upfront will save you more than enough work on the backend and makes it worth it. Making sure your tests fail first, being thorough in your test cases and effective refactoring will make for successful TDD development. TDD is a developing ideology and not a testing one. The name is misleading. By requiring the coder to write the unit tests first it makes him/her throughly decide on the input an output of a procedure without getting involved in the implementation details. By no means should unit testing be done in lieu of integration, system and user testing. If anyone thinks that unit testing is enough, remind them of the Mars Polar Lander (which likely failed because while the individual systems were unit tested the whole was not properly system tested).

Now, this was only a basic introduction into the world of TDD. I suggest you read some more on your own. Stay tuned for more posts that dive into specifics regarding TDD with the CRM API.

=-}

Great MS-CRM site

In case you don't know about this site, it is a great way to stay plugged in to the Microsoft Dynamics CRM community with helpful news links, articles about ISV solutions and case studies.

http://msdynamicsworld.com/page/dynamics-crm-resource-center

Friday, July 11, 2008

Someone forgot to update the default About box.

This isn't CRM related, but I couldn't help but post it cause I laughed when I saw it.

I was experimenting with the Visual Studio Add-in wizard. Step 5 was creating info for the About Box. Check out the default:

=-}

Pre-Filter Lookup

I ran across some code today that will pre-filter a lookup field based on a selection in another lookup field.

Put this in the onChange of the first lookup:

document.filterLookup(crmForm.all.customerid, crmForm.all.contactid);

Put this in the onLoad of the form:

// Filtered lookup
document.filterLookup = function (source, target) {
if (IsNull(source) IsNull(target)) {
return;
}
var name = IsNull(source.DataValue) ? '' : source.DataValue[0].name;
target.additionalparams = 'search=' + name;
}


For a more in-depth method of achieving this, visit this blog: http://jianwang.blogspot.com/2008/05/mysterious-crm-lookup-ii.html

Thursday, July 10, 2008

A Better CRM Library

As you've seen from my last few blog posts here I've been working around a few of the frustrating design directions of the CRM SDK team.

I'm excited to announce that I've created a CodePlex project so that all of this information can be shared. You can find it here:

http://www.codeplex.com/ABetterCRM

A few disclaimers though:
  • This is still in development and testing. Use what you like but make sure nothing is put into a production environment without thorough testing on your side.

Ok, just one disclaimer...

This is licensed by the Apache 2.0 license. So, read it thoroughly if you are unfamiliar with it.

Any suggestions, bugs, comments, or fan-mail should be directed through the CodePlex project and everything (including criticism) is greatly appreciated.

Thanks!

=-}

CRM Webservices and .Net 3.5

Now, I haven't done a lot of experimenting on this but I ran into a bit of a pain today.

I typically stick to doing development against the provided .dlls and not by adding web services. It poses it's own problems but also has it's advantages (for example, you don't actually have to have a CRM instance going to do CRM development. Nice!).

I find that the MetadataService in the SDK dll's is sorely lacking (and in many ways completely non-functional) so I had to add it in as a webservice. However, with VS 2008 and .Net 3.5, the services weren't adding in correctly. For example, I didn't get a MetadataService proxy class. Kinda important, you'd think...

Thank God for the "Advanced" button and being able to add a .Net 2.0 style web service in. Problem solved!

=-}

CRM Properties and Values: More About "Equals"

As you can see from a previous post, I'm not real happy with the DynamicEntity class and what I consider an improper implementations of the Equals method on it. I attempted to fix this.
If you take a close look at the code I provided there was a second little trick in there I didn't touch on. I'm touching it now.

Assume this:
CrmBoolean leftSide = new CrmBoolean(true);
CrmBoolean rightSide = new CrmBoolean(true);
Assert.IsTrue(leftSide == rightSide);
Assert.IsTrue(leftSide.Equals(rightSide));

Can you guess what will happen with those assertions?! Yup, they fail.

GRRR! Why?!

Now, admittedly this is a little grayer of a subject. Conceptually speaking there is no way to tell that, from a business logic standpoint, that those should equal. Why do I say that?

Well, what if leftSide is actually a CrmBoolean from the account object and rightSide is a CrmBoolean from the contact record. Now, even though they are both true they don't carry the same meaning.

Ok, ok, I'll admit, that's pretty flimsy. I'm just trying to give MS the benefit of the doubt here on why they didn't do a data comparison here.

In the previous post the code has to compare these types of objects to see if they're equal. It does it by using reflection on all public properties. This code was originally part of a general CRM Utility class I'm working on but I refactored it into the object itself. But that's neither here nor there.

Let's assume for a moment (I love assumptions) that we actually want these properties to reflect a more realistic "Equals" operation.

We'll start by creating a new object that inherits from CrmBoolean:

public class CRMBoolean : Microsoft.Crm.Sdk.CrmBoolean { }


Just like before, we'll override the Equals method (and some related methods) so that we do a data comparison and not just a pointer comparison:



public class CRMBoolean
{
public CRMBoolean(bool value)
:
base(value)
{
}
public CRMBoolean()
{
}
public override
bool
Equals(object obj)
{
if (obj == null)
return false;
if
(obj.GetType() != typeof (CrmBoolean) && obj.GetType() != typeof
(CRMBoolean))
return false;
if (GetType() == typeof
(string))
return
this == obj;
PropertyInfo[] properties =
GetType().GetProperties(BindingFlags.Instance
BindingFlags.Public);
foreach
(PropertyInfo property in
properties)
{
object leftValue =
property.GetValue(this,
null);
object rightValue = property.GetValue(obj,
null);
if (leftValue
== null && rightValue != null)
return
false;
if (leftValue !=
null &&
!leftValue.Equals(rightValue))
return
false;
}
return
true;
}
public bool Equals(CRMBoolean
obj)
{
return
base.Equals(obj);
}
public override int
GetHashCode()
{
return
base.GetHashCode();
}
public static bool
operator ==(CRMBoolean
left,
CRMBoolean right)
{
return
Equals(left, right);
}
public
static bool operator ==(CRMBoolean
left,
CrmBoolean right)
{
return
Equals(left,
right);
}
public static bool operator !=(CRMBoolean
left,
CrmBoolean right)
{
return !(left == right);
}
public
static bool operator !=(CRMBoolean left,
CRMBoolean right)
{
return
!Equals(left, right);
}
}
However, here we have a slightly unfun task ahead of us. Namely, we have to override EVERY "CrmSomething" class in the library.

I'm not going to cover how to do this, but I'm intending to include these types of objects in the future library I'm working on (wow, two posts now mentioning this, I sure better produce something!). I'll likely generate them.

Once all the various "value" type objects in the CRM libraries are overridden with this "better" Equals method, then I'll no longer need to do that heavy-lifting comparison in the DynamicEntity's Equals method, which is always a bonus.

=-}

P.S. Still not caring too much about the formatting of the code. Sorry. Complain and I might fix it.

CRM DynamicEntity Limitations - Part 1

DynamicEntities in CRM are either a love or hate relationship. Either you love the flexibility of using them within code (no need to do WebService proxy updates as configuration changes are made in CRM) or you hate them for their lack of usability.

What do I mean by their lack of usability? Namely this:

Microsoft.Crm.Sdk.DynamicEntity leftSide = new
Microsoft.Crm.Sdk.DynamicEntity("account");

Microsoft.Crm.Sdk.DynamicEntity rightSide = new
Microsoft.Crm.Sdk.DynamicEntity("account");

leftSide.Properties.Add(new StringProperty("name", "Fabrikam"));

rightSide.Properties.Add(new StringProperty("name", "Fabrikam"));

Assert.IsTrue(leftSide == rightSide); // this will fail every time

Assert.IsTrue(leftSide.Equals(rightSide)); // this will fail every time
too

I find that incredibly annoying. I love the TDD style to coding and not having these two objects do a data style comparison kills me. I have rarely run into a need to do a reference comparison and if so, I'll explicitly call a ReferenceEquals. To me. "Equals" is always a data-comparison. I mean, if you do "test" == "test" you'll get True, right?

Now, I could always create routines that will compare DynamicEntity classes (data wise) during my unit testing, but then every time I have some comparison of objects I have to create a new matcher. I tend to use NMock and NUnit which means I've got two different libraries to work with and about 3-4 different types of matching interfaces I have to implement. Why would I want to do that!?

Now, I could go off on MS for this sorely lacking detail but I'll refrain. That is better left for a well thought-out and tested post going over ALL the pain points! =-}

Here's my solution (which will hopefully make it up on CodePlex at some point as part of a general "Better CRM Library" project.

I first take and create my own DynamicEntity class that inherits from MS's DynamicEntity:

public class DynamicEntity : Microsoft.Crm.Sdk.DynamicEntity {}

Now let's get to some overriding! Now, I wouldn't say this code has been thoroughly unit tested or integration tested, so use at your own risk. It is only meant to point you in the right direction for your own implementations:


public class DynamicEntity : Microsoft.Crm.Sdk.DynamicEntity
{
public
DynamicEntity(string name)
: base(name)
{
}
public
DynamicEntity()
{
}
public override bool Equals(object obj)
{
if
(!(obj is Microsoft.Crm.Sdk.DynamicEntity obj is
DynamicEntity))
{
return false;
}
var rightSide = (DynamicEntity)
obj;
if (Name != rightSide.Name)
return false;
foreach (Property
property in Properties)
{
if
(!rightSide.Properties.Contains(property.Name))
return false;
if
(!PropertiesEqual(Properties[property.Name],
rightSide[property.Name]))
{
return false;
}
}
return
true;
}
public static bool operator ==(DynamicEntity
left,
DynamicEntity right)
{
if (Equals(left, null) &&
Equals(right, null))
{
return true;
}
if (Equals(left,
null))
{
return false;
}
return left.Equals(right);
}
public
static bool operator !=(DynamicEntity left,
DynamicEntity
right)
{
return !(left == right);
}
public bool Equals(DynamicEntity
obj)
{
return Equals(this, obj);
}
public override int
GetHashCode()
{
int runningTotal = 0;
runningTotal +=
Name.GetHashCode();
foreach (Property p in Properties)
runningTotal +=
p.GetHashCode();
return runningTotal;
}
public static bool
PropertiesEqual(object leftSide,
object rightSide)
{
if (leftSide ==
null && rightSide == null)
return true;
if (leftSide ==
null)
return false;
if (leftSide.GetType() !=
rightSide.GetType())
return false;
if (leftSide.GetType() == typeof
(string))
return leftSide == rightSide;
PropertyInfo[] properties =
leftSide.GetType().GetProperties(BindingFlags.Instance
BindingFlags.Public);
foreach (PropertyInfo property in
properties)
{
object leftValue = property.GetValue(leftSide,
null);
object rightValue = property.GetValue(rightSide, null);
if
(leftValue == null && rightValue != null)
return false;
if
(leftValue != null && !leftValue.Equals(rightValue))
return
false;
}
return true;
}
}

I haven't included the unit tests around this and I'm sure they don't have full code coverage anyway. But I want to get this out there.

This will hopefully be only one part of a whole series of these types of posts where I continue to expand and show how to handle some of the frustrating limitations of the CRM API. Let's just hope I've got the bench-time to get all them out.

=-}

P.S. Forgive me for the poor formatting of the code.

Tuesday, July 8, 2008

Enabling Social Networking within CRM Applications

A primer on integrating your CRM application to Social Networking Sites

Over the past 6 months, there has been an increase in the demand for CRM application integration with Social Networking sites. This integration allows organizations to harvest relationships that have been developed by their employees using tools like Linked in and Plaxo. In addition, this integration can be used for marketing purposes by creating corporate social networking sites on MySpace, FaceBook, LinkedIn, Plaxo, etc…

These integrations can have a significant impact on CRM user adoptions by eliminating data entry and making CRM information far more comprehensive. While Oracle and Saleforce.com are leading the way with built in social networking features, most other CRM applications can be configured to incorporate social networking functionality.

There are basically 4 levels of integration:
  1. Static Linking. By far the easiest to implement but also the most limiting. With static linking you provide a link to the social network sites that are most commonly used by your CRM users. You rely on the smarts of the social network site to do the rest. For instance, you may want to setup a research page in your CRM application and provide links to the search pages on Plaxo, Linked In and Hoovers. This can be accomplished in almost any CRM application with some basic configurations.
  2. Dynamic Linking. Most CRM applications allow you to add iFrames with mashups or dynamic links or to other web pages. The dynamic links will contain fields from the current record to allow you to navigate to the appropriate page on the destination site.
    a) Example of this for MS-CRM & LinkedIn; MS-CRM & Facebook
    b) Example of this for salesforce.com & LinkedIn
  3. Web Services Integration. Develop web services calls to social Networking sites and integrate data directly into the CRM application. This approach is ideal for creating new leads/prospects or establishing relationship maps. If the social networking site provides an API, the code can be written using the API. Otherwise a screen scraping approach can be used and data can be returned based on the current screen design. A screen scraping approach is not ideal as it can break if the social networking site changes screen designs, but for sites without an open API it is the only option.
    a) Example of this for MS-CRM & LinkedIn (Contact us)
    b) Example of this for SalesLogix & LinkedIn
    c) Example of this for salesforce.com & LinkedIn; salesforce.com & FaceBook
  4. MultiSite Integration. You may wish to aggregate data from multiple Social Networking sites. This can be accomplished by creating custom code that integrates with each site or by leveraging a common social networking framework. There are two emerging frameworks fbOpen from Facebook and Open Social from Google.
    a) salesforce.com OpenSocial Strategy
    b) Oracle’s OpenSocial Strategy

Developer/API Resources:

Integrating LinkedIn and MS-CRM



A simple example of using a dynamic URL to pull LinkedIn information into a MS-CRM iframe.



For this example we will create a simple integration with LinkedIn from the MS-CRM account record. This integration will allow CRM users to quickly see all contacts from this account that are in their LinkedIn network. This feature is enhanced significantly if you setup a group for your Organization within LinkedIn, because this will ensure you see all of the relationships across your organization.




1) Create the iFrame
As a system administrator, go to the Settings tab and click Customizations. Select Customize Entities and then click the Account record. Click Forms and Views under the details section. Click the form record. Add a Tab and name it LinkedIn. Add a section and name it LinkedIn. Add an Iframe and name it IFRAME_LinkedIn. Uncheck restrict cross-frame scripting box and check the “automatically expand to use available space” box under the row layout section of the formatting tab. Click ok to save the iframe changes.

2) Add Code
Click the form properties under the common tasks section of the account form. Select OnLoad from the Event List and click edit. Enter the following code:



var CRM_FORM_TYPE_CREATE = 1;
var CRM_FORM_TYPE_UPDATE = 2;
switch (crmForm.FormType)
{
case CRM_FORM_TYPE_UPDATE:
crmForm.all.IFRAME_LinkedIn.src="http://www.linkedin.com/search?search=&company=" + crmForm.all.name.DataValue+ "&currentCompany=currentCompany&searchLocationType=I&countryCode=us&postalCode=" + crmForm.all.address1_postalcode.DataValue+ "&distance=50&sortCriteria=3";
break;
}



Click ok to close Event Detail Properties window. Click ok to close Form Properties window. Click the Save and Close button on the Account form.

3) Publish
Click Publish from the Action menu on the Account Entity form. The change is now available to your application.







Notes
Required Fields
Account Name and Zipcode are required fields for this integration and should be made as required fields in MS-CRM. The Zipcode requirement can be removed by removing the location references in the URL.

Sorting
The lists will be sorted by the default sorting (degrees and Name). This can be changed by changing the sortCriteria value.
1: Number of Connections
2: Degrees and Recommendations
3: Degrees and Name
4: Keyword (CompanyName) relevance

Troubleshooting
If you get a security message when you open the account record or the following message in the LinkedIn iframe “Navigation to the webpage was canceled”, you need to change security settings in Internet Explorer to enable “Display Mixed Content”.

Thursday, July 3, 2008

CRM Email Router Event Log

One of our IT Administrators found out that when the CRM 4.0 Email Router is running it will log an event in the server Application Event log each time an email is filtered. There is a fix for this that can be found at the following URL. I have also copied the specific section text below:

http://support.microsoft.com/kb/907490





Microsoft Dynamics CRM 4.0 E-mail Router
You can update the Microsoft Dynamics CRM E-mail Router to enable tracing. To do this, you must modify the registry and XML service configurations.To enable verbose operations log level, you must manually update the configuration by updating the registry. To do this, follow these steps:
1. Log on to the server where the Microsoft CRM E-mail Router is installed with local administrator privilege.
2. Click Start, click Run, type regedit, and then click OK.
3. Locate and then click the following registry subkey: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MSCRMEmail
4. Right-click the LogLevel registry entry, click Modify, and then change the value to 3. By default, this value is set to 1.
5. Update the Microsoft.Crm.Tools.EmailAgent.xml configuration file for the E-mail Router Service. To do this, follow these steps:
a. In Windows Explorer, locate the Microsoft.Crm.Tools.EmailAgent.xml file. By default, this file is located in the following folder:
SystemDrive:\Program Files\Microsoft CRM Email\Service
b. Open the file by using Notepad or another text-editing program.
c. In the file, examine the <systemconfiguration> node, and then scroll down the text toward the end of the file to find the following statement:
<loglevel>1</loglevel>
By default, this value is set to 1 (one). Modify this statement so that it reads as follows:
<loglevel>3</loglevel>
d. Save the file.
e. Restart the E-mail Router Service. You will find a new event viewer log view that is named MSCRMEmailLog. It is in this event viewer log view that the events appear.


Tuesday, June 17, 2008

CRM 4.0 Gotchas

After implementing a bunch of CRM 4.0 projects I have compiled a short list of "gotchas" that I always share with people as I am gathering requirements for the project. Some of these were around in v3.0 but were not rectified in v.4.0. If anyone has additional ones that they would like to add in the comments I will update this posting.
  • Service Activities have a max length of 3 days (cannot be changed)
  • Bug in WF/Customization: If you customize an entity, all WFs associated are "soft" unpublished. If you change a WF and re-publish, JS on that entity is disabled.
  • If you restrict Write access to an Account, Opportunities, Contacts, Cases associated to that Account cannot be created. One possible solution is to use Teams.
  • The KB and Queue views cannot be modified (use c360 My Workplace instead)
  • You cannot access related data from the Recipient of a Phone Call activity in workflow.
  • Contacts and Contract Lines cannot be removed from the Case form. They must be hidden with JavaScript.
  • It takes 15mins for a published KB article to be indexed and searchable in the Knowledge Base.
  • You cannot delete a KB template unless all articles using it are deleted.
  • In order for an Opportunity to show in the Pipeline report it must have a revenue value entered.

Wednesday, June 11, 2008

CRM 4.0 Sales Process Workflow

I found a great article today on creating Sales Processes with Workflow in CRM 4.0. Unfortunately in my experience the workflow engine is pretty finicky, especially when it comes to using Stages. A lot of times it hangs on the wait timers.

http://www.bizitpro.com/weblog/entry/microsoft_crm_40_workflow_stage_example_upgraded_from_crm_30/

UPDATE: Microsoft has released a hotfix to fix the wait timer issue:
http://support.microsoft.com/?kbid=951919

Monday, June 2, 2008

Hiding Action Menu items

Well, this is completely unsupported but we had to hide the Activate and Deactivate choices from the Actions menu on a form the other day. I highly suggest downloading the IE Developer Toolbar to accomplish this. It will assist with getting the ids for these two menu choices. Here is the javascript we implemented:

//HIDE DEACTIVATE AND ACTIVATE MENU ITEM if (document.all._MIchangeStatedeactivate100145 != null) { document.all._MIchangeStatedeactivate100145.style.display = "none";} if (document.all._MIchangeStateactivate100146 != null) { document.all._MIchangeStateactivate100146.style.display = "none";}

Wednesday, April 30, 2008

Statera and SmartCatalog partnership

I recently had the pleasure of meeting the guys from SmartCatalog during a co-presentation we did on CRM 4.0. Their product is great and interfaces very nicely with the MS-CRM platform. Read about our partnership from an independent source here:

http://blog.tmcnet.com/telecom-crm/2008/04/18/crm-platform-from-mozes-endeavor-and-statera-amdocs-results-cegedim-de.asp

Thursday, March 20, 2008

Disable all attributes on a form

I found this snippet of code today on a message board to disable all attributes on a form:

for (var i = 0; i <>)
{
crmForm.all[i].Disabled = true;
}

Original post can be found here:
http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.crm.developer&tid=0ddb6aa6-3d3c-4299-835e-94a8bc9509c1