Showing posts with label Customization. Show all posts
Showing posts with label Customization. Show all posts

Saturday, October 31, 2009

ISV Utilities for Comparing Customizations and Transferring Configuration Data

Inna Agranov


Microsoft Corporation


February 2009


Summary


Learn how to build and use two new powerful tools developed for Microsoft Dynamics CRM. The Customization Comparison Utility lets you compare the customization files between two Microsoft Dynamics CRM systems and the Configuration Data Utility lets you transfer custom configuration data from one Microsoft Dynamics CRM system to another.


Download the Visual Studio 2008 and Visual C# code samples for this article:



The Readme.doc files that are included with the sample code contain information about how to set up and build the sample applications. The user guides contain detailed information about how to use the sample applications and view the results. You can find the Readme.doc files and user guides in the project folder for each utility.


Applies To


Microsoft Dynamics CRM 4.0


Microsoft Visual Studio 2008


Introduction


Microsoft Dynamics CRM is a highly customizable system. Not only you can modify different sections of the product, you can also create new components to address business needs. The Microsoft Dynamics CRM platform offers a robust set of tools, APIs, and documentation that helps you build custom business applications. As the applications built on the Microsoft Dynamics CRM platform become more and more complex, a need for specialized support tools grows. In this article you will learn about two very useful tools that help you analyze the impact of customizations on the system and maintain consistent configuration data across multiple Microsoft Dynamics CRM systems.


Evaluating the Impact of Customizations with the Customization Comparison Utility


To evaluate the impact of customizations, it is helpful to compare customization files between the source and the target systems before you import customizations. The Customization Comparison Utility helps you accomplish this task.


Analyzing Customizations

Often you have to export custom components from one Microsoft Dynamics CRM environment and import them into another, for example, from development into test or production. However, before you import customizations, it is very helpful to assess the impact of customizations on the target system. The system where you import customizations may have been changed since the last installation. You have to consider the extent of the changes and how they may affect the new installation. While some of the changes, such as renaming of the attributes or adding new attributes, are minor, other modifications, such as deletion of entities or changes in the forms may have a significant effect on the system.


Analyzing and understanding the system customizations may result in more successful deployment of a new version of the application. This analysis minimizes the risk of overwriting important customization data in the target system. For example, if only several attribute names have changed, you may be able to do a plain import using the import/export functionality built into Microsoft Dynamics CRM. However, if some key components were deleted, such as entity forms, you may have to merge the customizations with the changes in the target system. Comparing customization files between the two systems helps you determine which approach will result in more successful deployment. This is also very useful when you are diagnosing the problems between two systems. By comparing the customization files, you can often identify possible causes of the existing problems.


Using the Customization Comparison Utility

The Customization Comparison utility lets you easily compare two Microsoft Dynamics CRM customization.xml files. Unlike other XML comparison tools, this utility can read and understand Microsoft Dynamics CRM schema. The results of comparison show the differences in entities, attributes, forms, views, workflows, security roles, entity maps, and relationships. You can use this tool before you import customizations into a system to evaluate the effect they will have on the system.


Use the tool to compare XML customizations files between the source and the target systems. If you use a zipped customization file, make sure that it contains only one customization XML file. The following illustration shows the results of comparison between two customization files. The compared items include entities, roles, workflows, entity maps, and relationships. You can drill down into each item to see more details. From the entities, you can view the changes in attributes, forms, and system views. You can easily see the changes in source and target. It shows the items that are present in the source file and not present in the target file and the items that are present in the target file, but not in the source file.


Dd442453.a7338331-120b-4f29-ab17-1c94b85e014b(en-us,MSDN.10).gif


In addition to reviewing the results of the comparison in the grid, the tool includes a report that you can easily export to Microsoft Office Excel for additional analysis.


For more information about how to use the tool, see the Customization Comparison user's guide included in the download package for this utility.


Transferring Configuration Data with the Configuration Data Utility


When you work with multiple environments, such as development, test, and production, or multiple Microsoft Dynamics CRM organizations, keeping consistent configuration data across all systems can be very important. The Configuration Data Utility helps you achieve this. It lets you export custom configuration data from a source Microsoft Dynamics CRM system and import it to a target Microsoft Dynamics CRM system.


Storing Configuration Data in Custom Entities

In Microsoft Dynamics CRM you often use custom entities to store business information. However, you could also use custom entities to store system configuration data. For example, if an application integrates Microsoft Dynamics CRM with a third-party system, you could create a configuration entity with attributes such as pollingtime, url, and retries to store the configuration data needed for the integration. This is very convenient because the data stored in the configuration entity can be used by the system administrators to configure a new application or update an existing application. To keep the configuration data up to date, you may have to frequently upload the new data, or have an automated task to do it.


Using the Configuration Data Utility gives you a simple and efficient way to transfer custom configuration data from one system to another. One of the main benefits of this utility is that you can import configuration data from multiple custom entities at the same time. While it only imports and exports data for custom entities, the tool can handle useful scenarios, such as importing records that reference other records that are also being imported.


Dd442453.Important(en-us,MSDN.10).gifImportant


For the tool to work correctly, the schema for the source entities and the target entities must be identical.


Dd442453.note(en-us,MSDN.10).gifNote


In more complex cases, use the Microsoft Dynamics CRM data export and import tools or Data Migration Manager to transfer data for custom and system entities.


For more information about these tools, see Microsoft Dynamics CRM online Help.


Using the Configuration Data Utility

Use the Configuration Data Utility to export the source system configuration data and import it into a target system. The tool provides a convenient interface that lets you select the custom entities that contain the configuration data in the source system, save the data into a data file, and then import the records from the data file into a target system.


To run the tool, you must be a system administrator with appropriate privileges to create, read, and update entity instances.


The following illustration shows the entities in the source system that are selected for export.


Dd442453.0b8f0a81-1a17-4d77-9419-1cdc12427223(en-us,MSDN.10).gif


For import, specify the target server where you import the configuration data and the data file that you created during export, as shown in following illustrations.


Dd442453.9436a94c-f84f-4151-8d85-edc09ecaa422(en-us,MSDN.10).gif


Dd442453.5841695f-63ff-4ebb-a3b4-143657a9f759(en-us,MSDN.10).gif


The tool offers a command line version that you can run from a command prompt.


For more information about how to use the tool, see the Configuration Data Utility user's guide included in the download package for this utility.


Additional Information


Download the Microsoft Dynamics CRM 4.0 Software Development Kit (SDK) from the Microsoft Download Center.


For more information about custom entities, see Entity Customization.

Friday, October 30, 2009

SiteMap Privilege Tag

Dynamic Methods Microsoft CRM Blog


At times there are users who need to read data from entities but those same users should never really see the full list of items of that entity in a place where they could take action against any of the items.
For example, an entity called "Locations" exists in CRM. Users will need to have the ability to view these Locations in order to enter a Location on a related object form, perhaps an Account. As an administrator you would like to have the list available to you so that you can add to or modify the list for the users. It could be placed in the Settings area and sometimes that is enough to keep people away from the list. But just to be safe you want to guarantee that users cannot do anything to the list.
This is where SiteMap Privilege tags come in. Within each SubArea tag a Privilege tag can be added. When a Privilege tag is applied, CRM will check on the main page load what privileges the user has to see if that user should be able to see the item. If the user does not have rights, then the item is not shown, if the user does have rights then the item is shown.
So, following our example from above the following could be inside the SiteMap:
<SubArea Id="new_location " Entity="new_location">
<Privilege Entity="new_location" Privilege="Write" />
</SubArea>
By setting the privilege to "Write" only those users that have the write privilege will be able to view the entity from the main CRM page. Multiple privileges may be used as well. Here are the possible values from the SDK:
All
AllowQuickCampaign
Append
AppendTo
Assign
Create
Delete
Read
Share
Write
Here's an example with multiple privileges:
<SubArea Id="new_location " Entity="new_location">
<Privilege Entity="new_location" Privilege="Read,Write,Share" />
</SubArea>
And finally, get creative. Just because the area is for an entity doesn't mean that the privilege has to be for the same entity. Perhaps the Location entity should only show up to users who have rights to write to the Knowledge Base. The following would be completely legit as well:
<SubArea Id="new_location " Entity="new_location">
<Privilege Entity="kbarticle" Privilege="Read,Write" />
</SubArea>
Now your main CRM page can be much more dynamic depending on who you are.
David Fronk
Dynamic Methods Inc.

Friday, December 19, 2008

Invalid Argument error when importing unpublished entities

Posted by David Jennaway at 19:45

If, like I did recently, you export an entity before it has been published, you will get an 'Invalid Argument' error when importing the entity if it has any relationships.

The reason for this is that the relationship attribute will have no display name, which results in the error. The simplest solution would be to go back to the original deployment, publish the entity, then export again. But, if that's not an option, you can fix the problem within the exported xml as follows:

The invalid relationship xml will look like the following:

<field name="pjv_targetid" requiredlevel="required" imemode="auto" lookupstyle="single" lookupbrowse="0">
<displaynames />
</field>


The problem is that the displaynames element is blank. This can be changed to something like the following

<displaynames>
<displayname description="Target" languagecode="1033" />
</displaynames>


The languagecode attribute will need to be set to the correct value for your deployment. Many other elements will have this value set in the customizations.xml file

Wednesday, November 26, 2008

Displaying the Number of Notes on the Notes Tab

Posted by Jim Steger on November 7, 2008


The Microsoft CRM default form layout displays the Notes section on a separate tab. I often hear complaints from users that they don't know if any notes have been added without first clicking that tab. Many people don't realize that just as with any other section, you can move the Notes section to another tab, just as you would any other section. So one approach that many of our users have liked is to place the Notes section on the first tab as shown below.

This works, but I personally find it annoying as the the cursor will 'jump' down to the Notes section, sometimes scrolling past the info on top set of information, and you have less room to see multiple notes. Since I tend to keep the Notes section on its own separate tab, I wanted to find a way to let users know that data exists on that tab prior to clicking it. I created the following script to display the number of notes on the tab label as shown in the screen shot below.

The script I used is shown below and should be added to the entity's form onLoad function. Since this approach is entirely script based, it should also work on CRM Online. The tab where the Notes section exists must be called Notes for the script to work.

var totalNotes = getTotalNotes(crmForm.ObjectId);
setNoteTabName(totalNotes);

function setNoteTabName(count) {
    /* update note tab */
    if (crmForm.FormType != 1) {
        var cells = document.getElementsByTagName("A");

        for (var i = 0; i < cells.length; i++) {
            if (cells[i].innerText == "Notes") {
                if (count > 0) {
                        cells[i].innerText = "Notes (" + count + ")";
                        document.all.crmTabBar.style.width = "auto";
                }
                break;
            }
        }
    }
}

// Helper method to return the total notes associated with an object
function getTotalNotes(objectId) {
        // Define SOAP message
        var xml =
        [
        "<?xml version='1.0' encoding='utf-8'?>",
        "<soap:Envelope xmlns:soap=\"
http://schemas.xmlsoap.org/soap/envelope/\" ",
        "xmlns:xsi=\"
http://www.w3.org/2001/XMLSchema-instance\" ",
        "xmlns:xsd=\"
http://www.w3.org/2001/XMLSchema\">",
        GenerateAuthenticationHeader(),
        "<soap:Body>",
        "<RetrieveMultiple xmlns='
http://schemas.microsoft.com/crm/2007/WebServices'>",
        "<query xmlns:q1='
http://schemas.microsoft.com/crm/2006/Query' ",
        "xsi:type='q1:QueryExpression'>",
        "<q1:EntityName>annotation</q1:EntityName>",
        "<q1:ColumnSet xsi:type=\"q1:ColumnSet\"><q1:Attributes><q1:Attribute>createdon</q1:Attribute></q1:Attributes></q1:ColumnSet>",
        "<q1:Distinct>false</q1:Distinct><q1:Criteria><q1:FilterOperator>And</q1:FilterOperator>",
        "<q1:Conditions><q1:Condition><q1:AttributeName>objectid</q1:AttributeName><q1:Operator>Equal</q1:Operator>",
        "<q1:Values><q1:Value xsi:type=\"xsd:string\">",
        objectId,
        "</q1:Value></q1:Values></q1:Condition></q1:Conditions></q1:Criteria>",
        "</query>",
        "</RetrieveMultiple>",
        "</soap:Body>",
        "</soap:Envelope>"
        ].join("");
        var resultXml = executeSoapRequest("RetrieveMultiple", xml);
        return getMultipleNodeCount(resultXml, "q1:createdon");
}

// Helper method to execute a SOAP request
function executeSoapRequest(action, xml) {
    var actionUrl = "
http://schemas.microsoft.com/crm/2007/WebServices/";
    actionUrl += action;

    var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
    xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
    xmlHttpRequest.setRequestHeader("SOAPAction", actionUrl);
    xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
    xmlHttpRequest.send(xml);

    var resultXml = xmlHttpRequest.responseXML;
    return resultXml;
}

// Helper method to return total # of nodes from XML
function getMultipleNodeCount(tree, el) {
    var e = null;
    e = tree.getElementsByTagName(el);
    return e.length;
}

Naturally, all of the caveats apply...this code is presented as is and may not upgrade with future releases of Microsoft CRM.

Finally, since we are discussing Notes, a hot fix exists if you see that your Notes area just displays the spinning icon as referenced in this KB article:
http://support.microsoft.com/kb/951174

Wednesday, October 15, 2008

Enabling fields and events in bulk edit forms

You may have noticed that in CRM 4.0 the JavaScript events do not fire in Bulk Edit forms. It doesn't seem a big issue, because until today I haven't heard any complaints about it - or I already forgot them. Anyway, there are two things about the Bulk Edit form that you should know:

Any event handling code (OnLoad, OnSave, OnChange) is disabled by default, but you can get them working again.
Any field having an OnChange event handler applied is disabled on the form. You can fix that as well.
Click here to read more

Monday, August 4, 2008

Working with Dynamic Entities in CRM 4.0

Mitch Milam's Microsoft Discussions wrote this

Working with Dynamic entities is a bit different in 4.0 vs. 3.0.  When writing plug-ins and using Microsoft.Crm.Sdk, you have the ability to access the attributes of a Dynamic Entity via a property bag, much like this:

DynamicEntity updateEntity = new DynamicEntity("m3_roundrobin"); updateEntity["m3_lastuserid"] = newOwner.ToString(); updateEntity["m3_teamid"] = teamId.ToString();

However, if you're writing an application that used the standard CRM web service, this methodology is not available to you, so you have to resort to the more code-intensive methods we used in CRM 3.0

Fortunately, if you look deep into the CRM SDK, you'll find a solution to this issue.

After installing the CRM 4.0 SDK, browse to this folder:

\sdk\server\helpers\cs\crmhelpers

and look for the following file:

dynamicentitypartialtype.cs

If you add this file to your Visual Studio project, you will have the same Dynamic Entity property bag capability using the CRM web service as you do when using the Microsoft.Crm.Sdk assembly.

I was pleasantly surprised when I located the code and it has made working with Dynamic Entities much easier.

 

Have a great weekend everyone.

Tuesday, July 22, 2008

Converting HTML E-mail To Plain Text

Posted: Thursday, July 10, 2008 10:34 PM by Simon Hutson

OK, I admit it. I've caught the CRM development bug. What started as a harmless bit of fun working on document library integration between CRM & SharePoint has now developed into an obsession. In this post I will describe how to build a plug-in that examines the body of any e-mail promoted promoted from Outlook or the e-mail router and converts the HTML into plain text.


After a bit of searching, I found a good article which showed how you could use regular expressions to remove unwanted HTML tags leaving just the plain text - Convert HTML to Plain Text. Converting this from C# to VB (my preferred choice of language) and stripping out some of the bits I didn't need, I came up with the following code which forms the basis of this plug-in.



Private Function ConvertHTMLToText(ByVal Source As String) As String
 
    Dim result As String = Source
 
    ' Remove formatting that will prevent regex from running reliably
    ' \r - Matches a carriage return \u000D.
    ' \n - Matches a line feed \u000A.
    ' \f - Matches a form feed \u000C.
    ' For more details see http://msdn.microsoft.com/en-us/library/4edbef7e.aspx
    result = Replace(result, "[\r\n\f]", String.Empty, Text.RegularExpressions.RegexOptions.IgnoreCase)
 
    ' replace the most commonly used special characters:
    result = Replace(result, "&lt;", "<", RegexOptions.IgnoreCase)
    result = Replace(result, "&gt;", ">", RegexOptions.IgnoreCase)
    result = Replace(result, "&nbsp;", " ", RegexOptions.IgnoreCase)
    result = Replace(result, "&quot;", """", RegexOptions.IgnoreCase)
    result = Replace(result, "&amp;", "&", RegexOptions.IgnoreCase)
 
    ' Remove ASCII character code sequences such as &#nn; and &#nnn;
    result = Replace(result, "&#[0-9]{2,3};", String.Empty, RegexOptions.IgnoreCase)
 
    ' Remove all other special characters. More can be added - see the following for more details:
    ' http://www.degraeve.com/reference/specialcharacters.php
    ' http://www.web-source.net/symbols.htm
    result = Replace(result, "&.{2,6};", String.Empty, RegexOptions.IgnoreCase)
 
    ' Remove all attributes and whitespace from the <head> tag
    result = Replace(result, "< *head[^>]*>", "<head>", RegexOptions.IgnoreCase)
    ' Remove all whitespace from the </head> tag
    result = Replace(result, "< */ *head *>", "</head>", RegexOptions.IgnoreCase)
    ' Delete everything between the <head> and </head> tags
    result = Replace(result, "<head>.*</head>", String.Empty, RegexOptions.IgnoreCase)
 
    ' Remove all attributes and whitespace from all <script> tags
    result = Replace(result, "< *script[^>]*>", "<script>", RegexOptions.IgnoreCase)
    ' Remove all whitespace from all </script> tags
    result = Replace(result, "< */ *script *>", "</script>", RegexOptions.IgnoreCase)
    ' Delete everything between all <script> and </script> tags
    result = Replace(result, "<script>.*</script>", String.Empty, RegexOptions.IgnoreCase)
 
    ' Remove all attributes and whitespace from all <style> tags
    result = Replace(result, "< *style[^>]*>", "<style>", RegexOptions.IgnoreCase)
    ' Remove all whitespace from all </style> tags
    result = Replace(result, "< */ *style *>", "</style>", RegexOptions.IgnoreCase)
    ' Delete everything between all <style> and </style> tags
    result = Replace(result, "<style>.*</style>", String.Empty, RegexOptions.IgnoreCase)
 
    ' Insert tabs in place of <td> tags
    result = Replace(result, "< *td[^>]*>", vbTab, RegexOptions.IgnoreCase)
 
    ' Insert single line breaks in place of <br> and <li> tags
    result = Replace(result, "< *br[^>]*>", vbCrLf, RegexOptions.IgnoreCase)
    result = Replace(result, "< *li[^>]*>", vbCrLf, RegexOptions.IgnoreCase)
 
    ' Insert double line breaks in place of <p>, <div> and <tr> tags
    result = Replace(result, "< *div[^>]*>", vbCrLf + vbCrLf, RegexOptions.IgnoreCase)
    result = Replace(result, "< *tr[^>]*>", vbCrLf + vbCrLf, RegexOptions.IgnoreCase)
    result = Replace(result, "< *p[^>]*>", vbCrLf + vbCrLf, RegexOptions.IgnoreCase)
 
    ' Remove all reminaing html tags
    result = Replace(result, "<[^>]*>", String.Empty, RegexOptions.IgnoreCase)
 
    ' Replace repeating spaces with a single space
    result = Replace(result, " +", " ")
 
    ' Remove any trailing spaces and tabs from the end of each line
    result = Replace(result, "[ \t]+\r\n", vbCrLf)
 
    ' Remove any leading whitespace characters
    result = Replace(result, "^[\s]+", String.Empty)
 
    ' Remove any trailing whitespace characters
    result = Replace(result, "[\s]+$", String.Empty)
 
    ' Remove extra line breaks if there are more than two in a row
    result = Replace(result, "\r\n\r\n(\r\n)+", vbCrLf + vbCrLf)
 
    ' Thats it.
    Return result
 
End Function

All that remains is to implement the IPlugin.Execute method. In order to be able to modify the e-mail message before the e-mail activity gets created in the database, I had to figure out which event(s) to intercept. Through a bit of trial and error, I observed that any e-mail promoted from Outlook triggers the "DeliverPromote" event, whereas any incoming e-mail handled by the e-mail router triggers the "DeliverIncoming" event. Interestingly enough, the "Create" event was also called as a child pipeline for these events, but modifying the message here didn't have any effect, even in the pre-processing stage.


Because plug-ins have the potential to introduce significant performance and scalability issues into your environment, it is important to ensure that the code is as efficient as possible. To that end I added additional checks to ensure that the even if registered on multiple events, the main code will only run if the plug-in:



  1. is running on the 'DeliverPromote' or 'DeliverIncoming' messages

  2. is running synchronously

  3. is running against the 'Email' entity

  4. is running in the 'pre-processing' stage of the pipeline

  5. is running in a 'Parent' pipeline


Public Class ConvertHtmlToText
    Implements IPlugin
 
    Public Sub Execute(ByVal context As IPluginExecutionContext) Implements IPlugin.Execute
 
        ' Exit if any of the following conditions are true:
        '  1. plug-in is not running synchronously
        '  2. plug-in is not running against the 'Email' entity
        '  3. plug-in is not running in the 'pre-processing' stage of the pipeline
        '  4. plug-in is not running in a 'Parent' pipeline
        If Not (context.Mode = 0) Or Not (context.PrimaryEntityName = "email") Or Not (context.Stage = 10) Or Not (context.InvocationSource = 0) Then
            Exit Sub
        End If
 
        If (context.MessageName = "DeliverPromote") Or (context.MessageName = "DeliverIncoming") Then
 
            For Each item In context.InputParameters.Properties
 
                If (item.Name = "Body") Then
                    context.InputParameters.Properties.Item("Body") = ConvertHTMLToText(CStr(item.Value))
                End If
 
            Next
 
        End If
 
    End Sub
 
End Class

As always, I have include the source code to my project here. Please do bear in mind that I haven't included any error handling or logging, so it's not production-ready. However, it should provide you with a good head-start.


This posting is provided "AS IS" with no warranties, and confers no rights.

Monday, July 21, 2008

Adding An I_Frame For An N:N Relationship

by Danny Varghese 07.10.08

I've mentioned in a previous post how to add an I_Frame to a related entity: http://crowechizek.com/cs/blogs/crm/archive/2008/03/18/adding-an-i-frame-that-contains-a-view-of-related-entity.aspx.  The steps mentioned work for any entity that has a 1:N or N:1 relationship in both CRM 3.0 and now the new CRM 4.0 (Titan).  As you all know, Titan now has the ability to create N:N relationships!  With any new feature comes some new challenges, but not to worry, you can add an I_Frame for N:N related entities.  Thanks to a user who posted a comment on my blog article, I did a little digging and found that an additional parameter is needed in the url.


The additional parameter is shown in red and has been added to the original code from my other post:

 var urlAct = ""; urlAct =  "areas.aspx?oId=" + crmFormSubmit.crmFormSubmitId.value + "&oType=" + crmFormSubmit.crmFormSubmitObjectType.value + "&security=" + crmFormSubmit.crmFormSubmitSecurity.value +"&tabSet=areaActivityHistory" + "&roleOrd=2";document.getElementById('IFRAME_History').src = urlAct;

Although I don’t have confirmation of what this parameter is, I believe it may stand for "Role Ordinal."  If you look at the definition of the word "ordinal," it means to define the order or succession of something.  This parameter appears to define which side/order of the N:N relationship to display.  In the example above, the value is "2," which represents which side of the relationship you want to view.  So if you create an N:N relationship say from the Account entity, and you're asked to fill in the "Other Entity" section, that would represent the variable "2."  Another way to look at it is, if you're on the Account form, and you want to create an I_Frame pointing to the related entity of the N:N relationship, then the "roleOrd" is 2.  The best way to find the value of this parameter is if you view the source of the page you're on, do a find for "roleOrd," and see what the value is for the related entity.


Either way, I've seen this example work.  If anyone has any comments on this parameter, please do comment.  Thank you!

CRM Custom RSS Feed in less than 30 minutes

by Mitchell Kett 07.11.08


One of the best ways to improve a client's business is to keep users better informed and up-to-date on the information provided by CRM.  A workflow could be created (and maintained) to send out an email to the appropriate parties when a specific event happens (create, update, delete of an entity), but what if we could go one step further and provide the same up-to-date information without emails (and maintaining who gets what) or without the need for a user to look in CRM?  What about using an RSS feed?

 

Thanks to a very useful tutorial provided by Jeff at uberasp.net, creating an RSS feed for CRM can be done in a matter of minutes.  For a very quick crash course in XML and the syntax for RSS, see  http://www.w3schools.com/rss/rss_syntax.asp .

 

Say I'd like to create an RSS Feed for a specific entity in CRM.  Whenever a new record is created for this entity, I want to see it in my RSS Feed.  For this example, I created a custom entity in CRM called "new_rssfeed".  The only attribute I added to new_rssfeed was an ntext field called "new_description" which will contain text describing the new record.  After publishing my new entity type, I opened up Visual Studio 2005 and started a new ASP.Net Web Site.  I renamed the Default.aspx file generated by VS to "RSS_Feed.aspx" and changed the code to the following:

 

//RSS_Feed.aspx

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="RSS_Feed.aspx.cs" Inherits="_Default" EnableViewState="false" %>

<%@ OutputCache Duration="300" VaryByParam="none" %>

 

Yup, that is all you should see in your .aspx file.  No need for any html tags or DOCTYPE declarations.  What will happen is that when a user navigates to the RSS_Feed.aspx file, the Page_Load event will generate a stream of XML code which the web browser will interpret as an RSS feed.  So there is no need for any HTML.

 

Within the code-behind file, RSS_Feed.aspx.cs, I added the following code to generate the XML for the feed within the Page_Load event.  You can use this code as a template for your own feed.

 

//RSS_Feed.aspx.cs

protected void Page_Load(object sender, EventArgs e)

    {

Response.Clear();

Response.ContentType = "text/xml";

XmlTextWriter objX = new XmlTextWriter(Response.OutputStream, Encoding.UTF8);

objX.WriteStartDocument();

objX.WriteStartElement("rss");

objX.WriteAttributeString("version","2.0");

objX.WriteStartElement("channel");

objX.WriteElementString("title", "Practice CRM RSS Feed");

objX.WriteElementString("link","http://localhost:5555/RSS/RSS_Feed.aspx");

objX.WriteElementString("description","Live, up-to-date information coming from CRM!");

objX.WriteElementString("copyright","(c) 2008. All rights reserved.");

objX.WriteElementString("ttl","5");

SqlConnection objConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["crmConnectionString"].ToString());

objConnection.Open();

string sql = "SELECT TOP 10 new_name, new_description, new_rssfeedid, createdon FROM new_rssfeed ORDER BY createdon DESC";

SqlCommand objCommand = new SqlCommand(sql, objConnection);

SqlDataReader objReader = objCommand.ExecuteReader();

while (objReader.Read())

{

objX.WriteStartElement("item");

objX.WriteElementString("title",objReader.GetString(0));

objX.WriteElementString("description",objReader.GetString(1));

objX.WriteElementString("link", "http://localhost:5555/MicrosoftCRM/userdefined/edit.aspx?id=" + objReader["new_rssfeedid"].ToString() + "&etc=10008");

objX.WriteElementString("pubDate", objReader.GetDateTime(3).ToString("R"));

objX.WriteEndElement();

}

objReader.Close();

objConnection.Close();

 

objX.WriteEndElement();

objX.WriteEndElement();

objX.WriteEndDocument();

objX.Flush();

objX.Close();

Response.End();

    }

 

Notice the bolded text within the code.  These are snippets that will differ in your code.  For my RSS feed, I gave it the title of "Practice CRM RSS Feed".  The link element is for the URL used to get to the aspx file.  For my connection to CRM, I simply created a web.config file with a connection string to my CRM DB.  Throw in your own custom SQL Query to grab the necessary info to populate the "title", "description", "link", and "pubDate" for the feed <item> element.  The above code, in a nut shell, will grab the 10 most recently added New_rssfeed elements and format them for the feed.  I built and published the web site project and the last thing to do was configure IIS to make the feed accessible.

 

In IIS, all that I needed to do was create a new virtual directory with the alias "RSS" under the Microsoft CRM web site and point it to the folder with the compiled web code.  It automatically saw the web.config file, so no other adjustments had to be made.  Do an IIS reset and navigate to the aspx page.   You should see a basic page with the feed title and a description of how to subscribe to the feed.  You will also see the feed articles listed below and search options to the right (I used IE7 -- other browsers may render differently or re-direct to an RSS Reader like Google Reader).

 

Example of RSS Feed rendering in IE7 (click to view larger image)

 

Just think of what you could use this for!  You could integrate workflows and plugins with an RSS feed in order to provide up-to-date info on what's happening in CRM to other users (or anyone within the local network).  Inform sales people of new opportunities and leads, give executives updates by the minute as opportunities close and new ones come in.  Create one generic feed and register a plugin for multiple actions which could generate a variety of updates to the feed.  You could even create multiple feeds/aspx files and give users the option of how much/ little they'd like to get updated on.  We could even throw in a couple parameters  like entity type and GUID and we've got an RSS feed for one specific record in CRM. 

 

I merely scratched the surface of RSS (you can add images and other content as well), so be creative and think of how you might be able to use this to keep users (and developers) better informed of what's going on in CRM.