Microsoft Dynamics CRM 3.0: Accessing Selected Records in a Grid

 

Peter Hecke
Microsoft Corporation

January 2007

Applies to:
   Microsoft Dynamics CRM 3.0

Requirements

Microsoft Dynamics CRM 3.0
Microsoft Visual Studio 2005

Summary: Learn how to determine programmatically which entity records a user has selected in the Microsoft Dynamics CRM 3.0 Web application grid. Develop custom code to perform a desired operation on each entity selected in the grid.

In addition to showing you how to obtain the list of selected grid items, this article demonstrates how to develop application code which makes use of the Microsoft CRM 3.0 SDK and runs under the .NET Framework 2.0. Up until now, Microsoft CRM 3.0 SDK applications were developed to run under the .NET Framework 1.x only. (11 printed pages)

Download Microsoft C# and Microsoft VB.NET versions of the sample code for this article: GridSelectedItems.exe

Contents

Introduction
Installation and Setup
Application Design
Additional Information

Introduction

This article describes a simple ASP.NET sample application that uses the Microsoft CRM 3.0 SDK to extend the capabilities of Microsoft CRM 3.0 programmatically. The sample application demonstrates how to determine which accounts have been selected in the Microsoft CRM 3.0 Web application grid and use that information to update the business data of the selected accounts. While the sample application is designed to work with the Accounts grid, the programming technique demonstrated in this article is applicable to any entity type that is listed in the Microsoft CRM 3.0 Web application grid.

Figure 1 shows the Accounts grid of the Microsoft CRM 3.0 Web application in which a custom Postal Code Resolver button has been added to the grid toolbar. If you click that button, the sample Web application code executes. The sample Web application obtains the postal-code attribute value from each selected account and compares this value against a list of known postal codes. If a match is found, the city and state attributes of the selected account are set to the appropriate value, based on the postal code.

Click here for larger image

Figure 1. The Microsoft CRM 3.0 Web application grid (Click on the picture for a larger image)

The sample application that accompanies this article is limited in that it tests only for two known postal codes. A more sophisticated method would be to access a postal-code lookup Web service to obtain the city and state associated with all known postal codes.

Installation and Setup

The following procedures describe installation and setup of the sample Web application on a Microsoft CRM server. Microsoft Visual Studio 2005 should be installed on the server in order to build the sample application. If you have never run Visual Studio 2005 on the server, run Visual Studio 2005 once from your logon account, so that a folder named "Visual Studio 2005" exists in your My Documents folder.

To publish the sample software, you must be logged on to the Microsoft CRM server using an account that is a member of the Administrators group. To customize Microsoft CRM, your logon account must be assigned the System Customizer or System Administrator security role in Microsoft CRM.

Installing and Setting Up the Solution

  1. Download the GridSelectedItems.exe file by clicking on the link found in the "Summary" section of this article.

  2. Run the GridSelectedItems.exe self-extracting program by clicking on it in Windows Explorer. When prompted, extract the files to any folder. A folder named "gridselection" is created.

  3. In the gridselection folder are two subfolders named "websites" and "projects." In the websites folder, copy or move the gridselecteditems (C#) and gridselecteditemsvb (VB.NET) folders to your My Documents\Visual Studio 2005\WebSites folder on the Microsoft CRM 3.0 server. In the projects folder, copy or move the gridselecteditems folder to your My Documents\Visual Studio 2005\Projects folder on the Microsoft CRM 3.0 server.

  4. In Windows Explorer, navigate to the Visual Studio 2005\Projects\gridselecteditems folder and double-click the gridselecteditems.sln file to launch Visual Studio 2005. The C# and VB.NET versions of the sample ASP.NET application are loaded and displayed in Solution Explorer.

  5. The sample application code is configured to work with a Microsoft CRM installation that uses the standard non-default TCP port of 5555. If Microsoft CRM can be started in Microsoft Internet Explorer using a Web address of https://localhost:5555/loader.aspx, skip to step 13. If not, continue with step 6.

  6. In Solution Explorer, right-click the App_WebReferences folder, click Delete in the menu, and then click OK in the dialog box.

  7. In Solution Explorer, right-click the project path name …\GridSelectedItems\, and click Add Web Reference in the menu.

  8. In the Add Web Reference dialog box, enter the CrmService Web service URL for your server and click Go. The format of the URL is http://<servername:port>/MSCRMServices/2006/CrmService.asmx. You can omit the TCP port, if your server is using port 80.

  9. After the CrmService Web service is found, enter a Web reference name of CrmSdk in the text box and click Add Reference.

  10. Edit the project's web.config file by double-clicking its name in the Solution Explorer window.

  11. Locate the following appSettings configuration line:

    <add key="CrmSdk.CrmService" 
    value="https://localhost:5555/MSCRMServices/2006/CrmService.asmx"/>
    
  12. Change the server name and TCP port to the one that was used in step 8. Save the web.config file by clicking the Save web.config menu item in the File menu.

  13. Right-click the project path name …\GridSelectedItems\, and click Build Web Site. The Web project should compile with no errors.

    Note   In the C# version of the code, if you get a build error of "The type or namespace name 'CrmSdk' could not be found," try changing line 22 of the MultiplePostalCodeResolver.aspx.cs file from using CrmSdk to using crmsdk.

  14. Right-click the project path name …\GridSelectedItems\, and click Publish Web Site.

  15. In the dialog box, enter a target location of https://localhost/SelectedItems and click OK.

Configuring Application Execution for .NET 2.0

The sample ASP.NET Web application is installed on your Microsoft CRM server at https://localhost/SelectedItems. Now, you must configure the Microsoft Internet Information Services (IIS) Web server to run the sample ASP.NET application using the .NET Framework version 2.0.

  1. In the Control Panel, click Administrative Tools, and then click Internet Information Services (IIS) Manager.
  2. In the left tree view, expand the local computer node, the Web Sites node, and then the Default Web Site node.
  3. Right-click the SelectedItems virtual directory, and click Properties in the menu.
  4. In the dialog box, click the ASP.NET tab.
  5. In the combo box showing the ASP.NET version, click the down arrow and select the 2.0.x value.
  6. Click Apply and then OK.

Configuring the Application Pool

Because Microsoft CRM 3.0 runs under .NET Framework version 1.1 and this article's code sample runs under .NET Framework version 2.0, additional configuration is necessary in order to avoid a run-time IIS error. Perform the instructions that follow to create a new application pool, and assign the sample application to that pool.

To disable IIS 5.0 isolation mode in IIS 6.0

  1. In the IIS Manager, expand the local computer by clicking the plus sign.
  2. Right-click the Web Sites folder and click Properties.
  3. On the Service tab, clear the Run Web service in IIS 5.0 isolation mode check box.

To create a pool designation in IIS 6.0

  1. In the IIS Manager, expand the local computer by clicking the plus sign.
  2. Right-click the Application Pools folder, point to New, and then click Application Pool. The Add New Application Pool dialog box appears.
  3. Enter the new pool designation (for example, SampleAppPool) in the Application pool text box, and then click OK.

To assign a pool designation to the sample ASP.NET application in IIS 6.0

  1. In the IIS Manager, expand the local computer by clicking the plus sign, expand Web Sites, and then expand Default Web Site.
  2. Right-click the sample application's virtual directory, named SelectedItems, and then click Properties. The SelectedItems Properties dialog box appears.
  3. On the Directory tab, select the new pool designation that you previously created from the Application Pool list.
  4. Click OK to dismiss the dialog box.

An article that describes this in more detail can be found at How to: Configure ASP.NET Applications for an ASP.NET Version, in the .NET Framework Developer's Guide.

Integrating the Web Page with Microsoft CRM

With the custom Web page in place on your Microsoft CRM 3.0 server, you now must add a custom toolbar button to the Accounts grid of the Microsoft CRM Web application. When the user clicks the button, Microsoft CRM will open the sample Web page.

Three steps are required to integrate the custom toolbar button:

  • Modify the Microsoft CRM Web configuration file to enable ISV integration.
  • Back up any existing Microsoft CRM customizations.
  • Load the sample's customization XML file.

Modifying the Web Configuration File

To enable Microsoft CRM 3.0 customizations, you must modify the C:\Program Files\Microsoft CRM\CRMWeb\web.config file on the Microsoft CRM 3.0 server. In the <appSettings> configuration section of the file, locate the following line:

<add key="ISVIntegration" value="None"/>

Make sure that the ISVIntegration value is set to either All or Web—for example:

<add key="ISVIntegration" value="Web"/>

A value of All specifies that integration is turned on for both Microsoft Office Outlook and Web clients, and a value of Web specifies that integration is turned on for only the Web client. For more information, see Editing the ISV.Config in the Microsoft CRM 3.0 SDK.

Save the web.config file, if you made any changes. In a Command Prompt window, type iisreset to restart IIS, and then close the Command Prompt window.

Backing Up Existing Customizations

To save your current Microsoft CRM customizations

  1. Start the Microsoft CRM 3.0 Web application.
  2. In the Navigation Pane, click Settings, click Customization, and then click Export Customizations.
  3. Select ISV.Config in the list
  4. On the Actions toolbar, click More Actions, and then click Export Selected Customizations.
  5. Click OK to close the dialog box.
  6. In the File Download dialog box, click Save and indicate where you want to save the customizations file. You might want to rename the file to something like saved_customizations.xml.
  7. Click the Save button, and then click the Close button after the file has been saved.

The saved copy of the current customizations can be used to restore Microsoft CRM to its prior configuration, after testing of the sample Web application has been completed. To restore the original customizations, import the saved customizations file.

Adding a Custom Button to the Microsoft CRM Grid View

The customization.xml file included with the sample code adds a custom Postal Code Resolver button to the menu bar of the Microsoft CRM 3.0 Web application grid view. When the button is clicked by the user, the sample Web page is loaded and displayed.

The following configuration code is contained in the XML customizations file that is provided with this article.

<IsvConfig>
<![CDATA[<configuration version="3.0.0000.0">
  <Root />
  <Entities>
    <Entity name="account">
      <Grid>
        <MenuBar>
          <Buttons>
            <Button Title="Postal Code Resolver"              ToolTip="Resolve the postal codes for the selected records"              Url="https://localhost/SelectedItems/                   MultiplePostalCodeResolver.aspx"              WinParams="dialogHeight: 135px; dialogWidth: 300px"              PassParams="1"              WinMode="1" />
          </Buttons>
        </MenuBar>
      </Grid>
    </Entity>
  </Entities>
</configuration>]]>
</IsvConfig>

The configuration data describing customizations to Microsoft CRM is contained in a CDATA section of the XML code. Within that section is a hierarchy of XML tags that describe where in the Microsoft CRM user interface the customization applies and for what particular entity. The highlighted XML code defines the button. In the IsvConfig XML hierarchy, the custom button is part of a group of buttons that is located in a menu bar of the grid for an account entity.

If you do not have any existing customizations in your Microsoft CRM installation, you can just load the customization file provided by performing the steps described later under "Loading the Sample Customizations File."

When merging the Postal Code Resolver button's XML definition code into existing customizations, the goal is to add the button into your exported XML customizations, so that the hierarchy of tags remains the same as shown in the preceding code sample. Some of the tags might already exist in your XML file, and some might not. If any tags do not exist, they must be added. For example, if the <Entity name="account"> tag exists in your XML file, but the <Grid> tag does not exist before the next closing </Entity> tag, copy every line of XML sample code from the <Grid> tag through the </Grid> tag, and place it immediately below the existing <Entity name="account"> tag. If the <Entity name="account">, <Grid>, and <MenuBar> tags exist, but the <Buttons> tag does not, copy every line of XML code from <Buttons> through </Buttons>, and insert it between the <MenuBar> and </MenuBar> tags.

To merge the new custom button into your existing Microsoft CRM customizations

  1. Make a copy of your saved customizations file that you created in the previous section. You are to modify this copy.
  2. Edit the copied XML file in Visual Studio 2005 or some other preferred XML editor.
  3. Locate the IsvConfig tag and CDATA sections in the file.
  4. Make the necessary XML code changes, as shown in the examples described earlier.
  5. Save the file.
  6. Follow the steps in the next section, "Loading the Sample Customizations File," except in step 3 select your modified XML file instead of the customizations.xml file that is part of the article's download.

For more information about customizing Microsoft CRM using ISV.Config, see Customizing Using ISV.Config in the Microsoft CRM 3.0 SDK.

Loading the Sample Customizations File

To load the sample customizations file and completely replace any existing Microsoft CRM customizations that you have

  1. In the Navigation Pane of Microsoft CRM 3.0, click Settings, click Customization, and then click Import Customizations.
  2. Click Browse.
  3. In the dialog box, locate and select the customizations.xml file, which can be found at Visual Studio 2005\WebSites\gridselecteditems.
  4. Click Upload on the Import Customizations page.
  5. Click Import All Customizations, and then click OK.
  6. Click Workplace, and then click Accounts in the Navigation Pane. There should be a Postal Code Resolver toolbar button in the Accounts grid.

Testing the Solution

To run the sample Web application

  1. Start Microsoft CRM 3.0, if it is not already running.
  2. In the Navigation Pane, click Accounts.
  3. Create one or more new accounts, and assign them a postal code of either 21401 or 98008.
  4. In the Accounts grid, select one or more of the new accounts that were created in step 3.
  5. Click the Postal Code Resolver toolbar button.
  6. After a short delay, a small Web page opens and displays the text "Resolving Postal Codes." After the window disappears, the selected accounts with postal codes of either 21401 or 98008 will have their city and state set to Bellevue, Washington, or Annapolis, Maryland. Click the Refresh Grid rotating arrows on the top-right corner of the grid to refresh the list.

When you are done testing the sample Web application, you can use the Internet Information Services (IIS) Manager to delete the SelectedItems Web site under the Default Web Site node. Import your backup customizations file into Microsoft CRM to restore the previous customization settings.

Application Design

The sample Web application is based on a single ASP.NET Web page. The presentation part of the page contains JScript code, which obtains the IDs of all entities that are selected in the Microsoft CRM Web application grid. The JScript code then passes the IDs to the code-behind page for processing. The JScript code also retrieves any error messages that might have been generated by the code-behind page, and displays them in an alert dialog box.

Note   Version 3.0.5 of the Microsoft CRM 3.0 SDK documentation contains a new section titled Finding the ID from ASP.NET Code that describes how to obtain the selected entity-instance IDs in JScript code.

The code-behind page uses the ID of each account to read the attributes from Microsoft CRM, obtain the postal-code attribute value, and set the city and state attribute values according to the postal code.

A custom Postal Code Resolver button is added to the toolbar of the account grid view. When the user selects the button, the sample Web page is loaded and displayed while the logic in the code-behind page runs. The user does not interact with the sample Web page.

The Custom Toolbar Button

The customizations.xml file included with the sample code contains the definition of the custom toolbar button. The XML configuration code for the button is as follows:

<Button Title="Postal Code Resolver "
    ToolTip="Resolve the postal codes for the selected records"
    Url=https://localhost/SelectedItems/MultiplePostalCodeResolver.aspx
    WinParams="dialogHeight: 135px; dialogWidth: 300px" PassParams="1"
    WinMode="1"/></Buttons>

The following table identifies the attributes used in the XML configuration code.

Attribute Description
Title Specifies the name that appears on the button.
Tooltip Specifies the ToolTip that appears for the button.
Url Specifies the URL to be opened when the button is clicked.
WinParams Specifies the parameters to be passed to the window. The format of this parameter is different, depending on the value of the WinMode parameter. In the example code, the dialog-box width and height in pixels are specified.
PassParams Specifies whether the entity type and entity-instance ID parameters are to be passed to the URL. A value of 1 indicates true.
WinMode Specifies the window mode.

0 = Window [default]

1 = Modal Dialog Box

2 = Modeless Dialog Box

A custom Web application can use the PassParams attribute to access the entity type and entity-instance ID information by referring to the System.Web.HttpRequest object's QueryString property or through JScript code of a Web page in the dialogArguments property of the window object.

For more information about the configuration format for a button, see the Button topic in the Microsoft CRM 3.0 SDK.

The Web Page

The HTML code in the Web page displays to the user a "Resolving Postal Codes…" text message.

There are two JScript functions used in the Web page: window.onload and UpdateAccounts.

window.onload

When the user clicks the custom toolbar button, a new dialog-box window is opened that causes the window's onload event to fire. The window.onload event-handler function is executed as a result of that event. That function first obtains the list of entity-instance IDs from the window object's dialogArguments property, which was populated by Microsoft CRM. If there is at least one entity-instance ID in dialogArguments, the UpdateAccounts function is invoked after a 100 millisecond delay. The delay allows the Web page to render properly.

function window.onload()
{
   // window.dialogArguments contains an array of the IDs for the
   // selected records.
   var sSelectedRows = window.dialogArguments;

   // If sSelectedRows is empty, do not execute the update.
   if (sSelectedRows == "" || sSelectedRows.length == 0)
   {
      alert("You must select records in order to use this feature.");
      window.close();
   }
   else
   {
       // Wait 100 milliseconds and then execute the UpdateAccounts
       // method.
      window.setTimeout(UpdateAccounts, 100);
   }
}

UpdateAccounts

The UpdateAccounts function is responsible for generating a list of account IDs for the selected entities, and transmitting the list to the Web page. The function performs the following operations:

  • Creates an XML-formatted string containing the selected account IDs bounded by a <record> tag
  • Uses an XMLHTTP object to issue an HTTP POST request to the same Web page, this time passing as arguments a state and the XML string of entity-instance IDs
  • Waits for an XML HTTP response from the code-behind page
  • Displays a message to the user if the response contains an error message

The Microsoft Core XML Services (MSXML) is used to send an XML HTTP request and retrieve a response. The Microsoft XMLDOM (XML Document Object Model) is used to load the XML response and search for an error message bounded by an <error> tag.

function UpdateAccounts()
{
    // Create an XML string of entity-instance IDs bounded by <record> 
tags.
    sSelectedRecords = "<records>" + window.dialogArguments.toString() +
                       "</records>";

    // Do an Http POST to invoke the ASP.NET Web page. The sState argument
    // is set to 'Update'.
    var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    xmlhttp.open("POST",
       "https://localhost/SelectedItems/MultiplePostalCodeResolver.aspx
       ?sState=Update", false);
    // Send the string of entity-instance IDs.
    xmlhttp.send(sSelectedRecords);

    // Create an XML document in which the response message will be
    // loaded.
    var oResultXmlDoc = new ActiveXObject("Microsoft.XMLDOM");
    oResultXmlDoc.async = false;

    // Extract any XML message returned in the Http response and load
    // it into the XML document. An <eom /> tag is used to indicated
    // the end of the response message from the code-behind page.
    var eom = xmlhttp.responseText.indexOf("<eom />");
    var xmlMessage = xmlhttp.responseText.substring(0,eom);
    oResultXmlDoc.loadXML(xmlMessage);

    // If errors were returned in the Http response, display them.
    var oNodeError = oResultXmlDoc.selectSingleNode("error");
    if(oNodeError != null)
    {
        alert(oNodeError.text);
    } 

    window.close();   
}

The Code-Behind Page

The code-behind page contains the business logic that compares a selected entity's postal code against a list of known codes, and sets the city and state attribute values of the entity if a match is found. The code first defines a constant string that indicates an update condition.

private const string UPDATE = "Update";

This constant is used in the Page_Load method as a state flag to indicate that entity-instance ID information is included in the HTTP request.

Page_Load

In this sample, the Web page is loaded at least twice. The first time is when the user clicks the custom toolbar button. In this case, Request.QueryString does not contain an sState parameter, so the Page_Load method performs no processing and simply returns. However, the Web page onload event handler does execute. The event handler calls the UpdateAccounts JScript function, which issues an HTTP POST to the Web page, causing the page to load again. During this second page load, a state parameter is passed in the HTTP request, followed by an XML list of entity-instance IDs bounded by <record></record> tags.

When the code-behind page executes, a check of the QueryString property is performed. If the state parameter is found, the Page_Load method does the following:

  • Clears the Response object and sets its content type.
  • Creates a proxy object to the Microsoft CRM 3.0 Web service.
  • Gets the selected records list from the Request object.
  • For each record in the list, the method:
    • Converts the entity-instance ID into a GUID, in order to access the information in Microsoft CRM 3.0.
    • Retrieves needed attributes of the entity instance from Microsoft CRM 3.0.
    • Compares the postal-code attribute against a list of known postal codes.
    • Sets the city and state attributes to predefined values, if a match is found.
  • Returns an XML-formatted error message bounded by <error></error> tags, if an exception occurred.
protected void Page_Load(object sender, EventArgs e)
{
   // If the state of the page is an update, update the record.
   // Otherwise, the page is loading.
   if (string.Compare(Request.QueryString["sState"], UPDATE) == 0)
   {
       Response.ContentType = "text/xml";
       Response.Clear();

       try
       {
           // Create a proxy to the Microsoft CRM 3.0 Web service using
           // the default network credentials.
           CrmService crmService = new CrmService();
           crmService.Credentials = 
               System.Net.CredentialCache.DefaultCredentials;

           // Get the URL for the Microsoft CRM 3.0 server from the
           // web.config file appSettings.
           AppSettingsReader appSettings = new AppSettingsReader();
           crmService.URL = appSettings.GetValue("CrmSdk.CrmService", 
                              Type.GetType("System.String")).ToString();

           // Get the list of records that are selected in the grid.
           string[] selectedEntities = GetSelectedRecords();

           for (int i = 0; i < selectedEntities.Length; i++)
           {
               bool update = false;

               // Set a temporary GUID to the ID in the string array.
               System.Guid idGuid = new System.Guid(selectedEntities[i]);

               // Specify the database columns (entity attributes) that
               // are to be retrieved. Retrieve the needed address fields
               // and the account ID in order to later execute the update.
               ColumnSet cols = new ColumnSet();
               cols.Attributes = new string[] { "accountid",
                                            "address1_city",
                                            "address1_postalcode",
                                            "address1_stateorprovince" };

               // Retrieve the account data from Microsoft CRM 3.0.
               account account = (account)crmService.Retrieve(
                                     EntityName.account.ToString(),
                                     idGuid, cols);

               // Compare the postal code of the retrieved account against
               // the postal codes that are to be matched. If the postal
               // codes match, update the account's city and state.
               switch (account.address1_postalcode)
               {
                   case "21401":
                       account.address1_city = "Annapolis";
                       account.address1_stateorprovince = "Maryland";
                       update = true;
                       break;

                   case "98008":
                       account.address1_city = "Bellevue";
                       account.address1_stateorprovince = "Washington";
                       update = true;
                       break;
               }

               // Only update the account if the account's postal code
               // matches the postal code that is being resolved.
               if (update)
               {
                   crmService.Update(account);
               }
           }
       }

       catch (System.Web.Services.Protocols.SoapException ex)
       {
           Response.Write("<error>");
           Response.Write(
                    "An error occurred while accessing Microsoft CRM.");
           Response.Write(ex.Detail.OuterXml.ToString());
           Response.Write("</error>");
       }

       catch (System.Exception ex)
       {
           Response.Write("<error>");
           Response.Write(ex.Message);
           Response.Write("</error>");
       }

       finally
       {
           // Add a tag to identify the end of the message.
           Response.Write("<eom />");
           Response.Flush();
       }
   }
}

GetSelectedRecords

The GetSelectedRecords method obtains an XML string containing the entity-instance IDs bounded by <record> tags from the System.Web.HttpRequest object's input stream. The XML string is then loaded into an XmlDocument object, in order to separate the comma-separated list of IDs from the <record> tags. The ID list is then split into a string array and returned to the caller.

private string[] GetSelectedRecords()
{
   // Read the XML data that is passed in the HttpRequest stream.
   StreamReader sr = new StreamReader(Request.InputStream);
   string recordIds = sr.ReadToEnd();

   // Load the data string into an XML document. The list of comma-
   // separated IDs are obtained from the document element InnerText.
   XmlDocument xmlDoc = new XmlDocument();
   xmlDoc.LoadXml(recordIds);
   string selectedEntities = xmlDoc.DocumentElement.InnerText;

   // If the string is empty, no records have been selected by the user.
   if (selectedEntities == null || selectedEntities.Length == 0)
   {
       return new string[0];
   }

   // Split the string of comma-separated IDs into an array.
   return selectedEntities.Split(new char[] { ',' });
}

Additional Information

For more information about customizing Microsoft CRM 3.0, see the Microsoft CRM 3.0 SDK.