Archive for February, 2012

CRM 2011: Custom Reports & the rsProcessingAborted Error

I created a custom report for CRM 2011 in BIDS months ago for a client, but was recently notified that it had stopped working after some significant changes were made to their environment. Out-of-the-box reports would display just fine, but the one I wrote displayed a vague “The report cannot be displayed. (rsProcessingAborted)” message and there was nothing useful in the Event Viewer to shed more light on the problem.

After confirming that the data source and credentials my report was using were still valid, I started poking around and discovered that other custom reports, even those created through the CRM 2011 Report Wizard, were failing with the same error. I tried a few solutions that I found online, but the one that fixed it is covered in When CRM 2011 Custom Reports Don’t Work.

Turns out all I needed to do was look up which account was being used for the CRM Application account by checking the CRMAppPool in IIS, then add an SPN for it with “setspn –a HTTP/NETBIOSNAME DOMAINSERVICEACCOUNT”. Such a quick and easy solution for something I was beginning to worry would end up being a real headache to fix!

Tags: , , ,


Deactivating an Option Set Value in MS CRM

MS CRM has grown considerably from it’s early days. Customizing and developing against the older versions certainly had it’s share of pain and I really enjoy working with MS CRM 2011. For all the features they’ve added, though, there’s one still missing that I’m surprised isn’t baked into the product and that is the ability to retire an option set value. By retiring a value, I want a value in an option set to no longer be selectable, but, if any records have that existing value, I want it to keep that value. Currently, if you remove an option set value, any records that have that value will NOT be updated but the value that they had before will no longer link to an option set value.

As an example, open the Account entity and find the address1_shippingmethodcode field. If you look at the option set you’ll see it contains a list of values. For our example, we want to disable the value “Full Load” because it’s no longer a valid shipping option for new customers or customers that haven’t already chosen this shipping method. In this case it should be hidden. Any existing customers that have already selected this method may keep this shipping method, so if they have that value already, we want it to show up in the form when it’s opened. My coworker Rod McBride made a great suggestion as he was reviewing my post, but is NOT implemented below, and that is adding effective dates for an option set value. It wouldn’t be hard to add – simply add the start and date fields to the entity, add those fields to the oData query and then use those fields when determining to deactivate the picklist option. I’ll leave that as an exercise for you!

To do this:

  1. Create an account and on that account set the address1_shippingmethodcode to “Full Load” and save the account. You’ll need this to test later.
  2. Create a second account and on this account, set the address1_shippingmethodcode to a value other than “Full Load”. You’ll also need this to test later.
  3. Create a new entity (Disabled Picklist Items) with four attributes (in addition to the default name): new_entityname (Single Line of Text), new_entitytypecode (Whole Number), new_attributename (Single Line of Text), new_disabledattributevalue (Whole Number).
  4. Modify the form of the newly created entity (Disabled Picklist Items) and add the four fields to that form.
  5. Publish your changes. You now have a form that you can use to add records that indicate which option set value for a given entity/attribute combination that should be retired.
  6. Using the form you just created, add a new record of the type Disabled Picklist Items. Give it a name of “Account Shipping Method”. Enter the entity name as “account”, set the entitytypecode to 1 (1=account), set the Attribute Name as  ”address1_shippingmethodcode” and set the Disabled Attribute Value of 6 (6 is the option set value for “Full Load”).
  7. Next, open the account form in design mode. On the form properties add two libraries: one for jQuery (I add jQuery often because it’s quite nifty; yes, I just used the word “nifty”. Deal with it. :) ) and one that will contain the jscript that will do the actual work (client side) of removing the option set value that’s disabled if it’s not the selected value. As you can see, I have added new_jquery1.6.4.js (use whatever version you like) and a second called “new_DisablePicklistItem”. Set the form load to call a function named “DisablePicklistItems”.
  8. The code for the new_DisablePicklistItem script should look like the code below (see the bottom of the post). I put lots of comments in to explain what it’s doing. Most of the code is your typical MS CRM web service stuff. All that’s unique is the part that constructs the oData query and the part that actually processes the results.
  9. When you’re done, publish your changes.
  10. Open the account you created in the first bullet point. If you’ve gotten everything right, you should see your shipping method on address1 is “Full Load”.
  11. Now go to the account you created in the second bullet point. You should not see the value “Full Load” in the list. Same if you attempt to create a new account.

One thing I’ve noticed is that there is a bit of a delay the first time you open an account and the web service is accessed which is why I put a call to disable the control in question before the values are removed. Subsequent calls are much faster.

There you have it! You now have the ability to retire an option set value so that it will show up for those records that already have the value selected, but accounts that don’t (and new accounts) can’t select the retired option.

Here’s what the list looks like before:

Here’s what it looks like after – note that the option “Full Load” is missing:

function DisablePicklistItems(context)
{
 // Disable the control in question. In theory, you should read
 // what controls are disabled and dynamically disable them.
 // The Disable Picklist Items records you create in MS CRM
 // have the attributes you need to figure out what controls to disable.
 // Since I have a lot of work to do, I'm hard-coding to the attribute we're
 // discussing here.
 Xrm.Page.ui.controls.get("address1_shippingmethodcode").setDisabled(true);

 // The line below constructs and oData query that gets all the
 // DisabledPicklistItems records for the entity that we are on.
 var query = '/XRMServices/2011/organizationData.svc/new_disabledpicklistitemSet?$select=new_AttributeName,new_disabledattributevalue&$filter=new_EntityTypeCode eq ' + context.getContext().getQueryStringParameters().etc;

// Call a method that actually calls the web service.
 getEntityItems(query, "disabledItems");
}

// This function just calls the oData web service. It also
// specifies the callback function that gets called when
// the state of the request changes.
function getEntityItems(oDataQuery, queryName) {
 try {
 var serverUrl;
 serverUrl = Xrm.Page.context.getServerUrl() + oDataQuery;
 var request = new XMLHttpRequest();
 request.open("GET", serverUrl, true);
 request.setRequestHeader("Accept", "application/json");
 request.setRequestHeader("Content-Type", "application/json; charset=utf-8");
 // When the request state changes, call the function "requestComplete".
 request.onreadystatechange = function () {
 requestComplete(request, queryName);
 }
 request.send();
 } catch (e) {
 // You probably want to do something other than an alert here.
 alert(e.Description);
 }
}

// The query name parameter is passed so that this function can be used by
// multiple functions that make CRM web service calls.
function requestComplete(request, queryName) {
 if (request.readyState == 4 && request.status == 200) {
 // Everything was OK, so get the request response and assign it to a variable.
 var json = $.parseJSON(request.responseText);
 if ( (json != undefined) && (json.d != undefined) && (json.d.results != undefined) && (json.d.results[0] != null) ) {
 // If you had more than one query, you would add additional else-ifs.
 // Might even make sense to use a switch statement. Might make even more
 // sense to have each if (or switch) call a completely separate function
 // instead having the logic inline. :) 
 if (queryName == 'disabledItems') {
 // This is the heart of the process. We've gotten data
 // from the server so we need to loop through each value.
 // (The $ notation is jQuery which is beyond the scope of this post.)
 $.each(json, function(index, value) {
 // For each returned disabled attribute record from the web service,
 // get the attribute name and attribute value.
 var attribute = value.results[0].new_AttributeName;
 var attributeValue = value.results[0].new_disabledattributevalue;

 // Get the current value from the page of the attribute in question.
 var currentValue = Xrm.Page.data.entity.attributes.get(attribute).getValue();

 // If the current value on the page is not equal to the currently iterated
 // disabled value, then remove the option from the option set.
 if (currentValue != attributeValue)
 {
 Xrm.Page.getControl(attribute).removeOption(attributeValue);
 }

 });
 }
 }
 // Re-enable the control. Again, this is hard coded for speed but you should
 // probably do this dynamically.
 Xrm.Page.ui.controls.get("address1_shippingmethodcode").setDisabled(false);
 }
}

Tags: , , ,


Table To Enum Using T4 Template

Most developers have used enums at some point in their development process. They are basically strongly typed compile-time constants and allow you to assign a symbolic name to an integer value, removing the use of magic numbers, which leads to more maintainable code. Most projects tend to have several enums that mirror database lookup tables and these values tend to get out of sync over time and can be tedious to maintain.

In a previous project, I created enums dynamically from a database table using a number of classes in the System.Reflection.Emit namespace and use a separate project to generated an assembly that housed the enums. Though not overly complex, it involved a number of extra steps and I wanted to refactor. I finally got around to refactoring using the Text Template Transformation Toolkit (T4).

T4 is basically Microsoft’s code generation tool that’s built into Visual studio, so I decided to use it to simplify the creation of the various enums I needed. There are dozens of great articles on T4 such as T4 Code Generation – Best Kept Visual Studio Secret, T4 and ASP.NET MVC and Creating your first code generator so I won’t go into detail here, but it’s well worth a few minutes of your time if you aren’t familiar with them.

To start, I created a new Text Template in Visual Studio 2010 and added the assembly and namespace directives that were needed to connect to a SQL database and specified the output file type.

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Collections.Generic" #>

I then added a class code block with the fields and methods that will used by other code blocks in the template.

<#+
private string ServerName;
private string DatabaseName;

private struct Entity {
    public string Name;
    public string TextColumn;
    public string ValueColumn;
}

private List<Entity> entities = new List<Entity>();

private string CreateConnectionString()
{
    var sb = new SqlConnectionStringBuilder();
    sb.DataSource = ServerName;
    sb.InitialCatalog = DatabaseName;
    sb.IntegratedSecurity = true;

	return sb.ConnectionString;
}

private string ParseEntityField(string name)
{
	// Remove the dot, left bracket, right bracket, space
	// and slash characters from the naem.
	var pattern = @"[.[]s/-]";
    var regex = new Regex(pattern, RegexOptions.None);
    return regex.Replace(name, string.Empty);
}

private void CreateEnum(Entity e)
{	
    using(var con = new SqlConnection(CreateConnectionString()))
    {
        var cmd = con.CreateCommand();
		var sql = string.Format("SELECT {0}, {1} FROM {2} ORDER BY {0}", e.TextColumn, e.ValueColumn, e.Name);
        cmd.CommandText = sql;
        con.Open();
        
		using(var rdr = cmd.ExecuteReader())
		{
	        PushIndent("t");
	        while (rdr.Read())
	        {
	            var option = string.Format("{0} = {1},", ParseEntityField(rdr[e.TextColumn].ToString()), rdr[e.ValueColumn].ToString());
	            WriteLine(option);
	        }
	        PopIndent();
        }
    }
}
#>

And finally, a code block to set the connection, create a list of tables to generated enums for and the logic to loop through the tables.

<#
// Connection info
ServerName = "(local)";
DatabaseName = "AdventureWorks2008R2";	

// List of entities to create enums for
Entities.Add(new Entity {Name="Person.AddressType", TextColumn="Name", ValueColumn="AddressTypeID"});
Entities.Add(new Entity {Name="Person.ContactType", TextColumn="Name", ValueColumn="ContactTypeID"});
Entities.Add(new Entity {Name="Person.PhoneNumberType", TextColumn="Name", ValueColumn="PhoneNumberTypeID"});

foreach(var entity in Entities)
{
#>
    public enum <#= ParseEntityField(entity.Name) #>
    {
    <#
        CreateEnum(entity);
    #>
    }

<#
}
#>

Now, to generate the enums simply save the template and a file (csharp in this case) will be created based on the output directive.

    public enum PersonAddressType
    {
    Archive = 6,
    Billing = 1,
    Home = 2,
    MainOffice = 3,
    Primary = 4,
    Shipping = 5,
    }

    public enum PersonContactType
    {
    AccountingManager = 1,
    AssistantSalesAgent = 2,
    AssistantSalesRepresentative = 3,
    CoordinatorForeignMarkets = 4,
    ExportAdministrator = 5,
    InternationalMarketingManager = 6,
    MarketingAssistant = 7,
    MarketingManager = 8,
    MarketingRepresentative = 9,
    OrderAdministrator = 10,
    Owner = 11,
    OwnerMarketingAssistant = 12,
    ProductManager = 13,
    PurchasingAgent = 14,
    PurchasingManager = 15,
    RegionalAccountRepresentative = 16,
    SalesAgent = 17,
    SalesAssociate = 18,
    SalesManager = 19,
    SalesRepresentative = 20,
    }

    public enum PersonPhoneNumberType
    {
    Cell = 1,
    Home = 2,
    Work = 3,
    }

One of the first questions you may have, as I did, when using T4 is how do I access app/web config settings from the template. You cannot do it directly as T4 runs under a different context than that of the project. See T4 templates using own project .config file or Accessing app.config/web.config from T4 template – Take 2 for additional information on using config files.

T4 allows a mixture of text and code blocks and can generate any type of text file such as classes, web pages, stored procedures, etc. These types of boilerplate code are often needed repeatedly and code generation can be used to reduce time and the chance of errors.

Tags: , ,