Phew! So it's Friday night and for the past week I've been between jobs (i.e, unemployed), so I've finally had the time to create a tool that I've been wanting to build for a while. So here is a new tool for customizing ribbons on Microsoft Dynamics CRM 2011.
It's different from other ribbon editors available because it shows a full real-time preview of what the ribbon will look like in CRM, and it should eliminate the need to spend hours studying the ribbon XML schema in the CRM SDK before trying to add a new button to a ribbon. No more scrolling through pages of XML looking for the ID of the element you want to insert your button into. Just click "New Button", position it where you want, then define the actions and enable rules or display rules. Then click "Save" to save the changes back to CRM.
I'll post more about this project later, but for now you can find it at http://crmvisualribbonedit.codeplex.com/
It's currently in a beta state and still need alot of testing, but please check it out!
-Erik Pool
Erik Pool
XRM Development, etc.
Friday, October 21, 2011
Friday, October 14, 2011
Understanding CRM Ribbon XML - Part 3: Group Templates
This is part 3 in a series of posts on customizing CRM ribbons:
- Part 1: Understanding the content and structure of the ribbon xml
- Part 2: Customizing ribbons by editing the RibbonDiffXml
- Part 2: Customizing ribbons by editing the RibbonDiffXml
- Part 3: Understanding Group Templates (this post)
How do Templates work?
Until now I’ve completely skipped over the templates section of the ribbon xml. This is because Templates can be the most difficult part of ribbon customizations to understand. Templates are used to determine how groups of controls on a ribbon are arranged. The template used will determine the order of the buttons, as well as the size of the buttons. And the template will also determine how the group shrinks when there isn’t enough room on the page for the full ribbon.
For most scenarios (such as adding a button to an existing group) you won’t even need to worry about templates, you can just add the button set it's TemplateAlias attribute to “isv” and you’re done. However, if you are creating a new Group (or a new tab), then it’s worth knowing how Templates work.
First, take a look at the buttons in the UI section. Notice how they each have a TemplateAlias attribute:Also notice how each Group has a Template Attribute:
If we scroll down to the Templates section, we will find the Template that the group is using. Each Template will have multiple Layouts inside it with names like “LargeMedium”, or “LargeSmall”. One of these Layouts will be used to determine the arrangement of the controls inside the Group (we’ll see how CRM actually decides which layout to use in a bit).
Inside of each layout, there are two ways that the arrangement of buttons and controls can be defined...
1. Layout using OverflowSections
The most common way is using OverflowSections. Each OverflowSection will be arranged in the ribbon group horizontally from left to right, and will contain all the buttons from the group that have the specified TemplateAlias. This image shows the "Collaborate" group, which is using the "LargeMedium" layout of the "Flexible2" template:
In the image above, all of the buttons using the "o1" templateAlias are placed before all of the buttons with the "o2" TemplateAlias, regardless of their sequence number. But controls with the same TemplateAlias will be ordered inside the OverflowSection based on their sequence number. In this example there are no buttons currently using the “isv” TemplateAlias, so the isv OverflowSection is not shown at all.
An overflowSection with a Type of “OneRow” will have just a single row of buttons, while a Type of “ThreeRow” will have three rows of buttons stacked on top of eachother. The DisplayMode determines what size the buttons in the OverflowSection will be. There are three sizes - Large, Medium, and Small:
Notice that all of the default templates each have an OverflowSection for the “isv” templateAlias. This is so developers can easily just add a new button to any group without having to worry about templates or layouts. If you just always use the “isv” TemplateAlias then the button will show up at the end of the group.
2. Layout using Sections and Rows
The other way that Layouts can be defined is using Sections and Rows. Sections and Rows allow the template to have much more precision than OverflowSections when defining the arrangement of controls. Each control in the Group is placed into a specific Section and Row. For example, the Activities group on the “Add Existing” tab uses Sections and Rows to get a very specific arrangement of controls:
Tip: Any time you see a control with a TemplateAlias that starts with “c”, it is using Sections and Rows for layout. Any time the TemplateAlias starts with “o”, it is using OverflowSections (c = ConfrolRef, o = overflowSection).
How does CRM decide which layout to use?
Each template has multiple layouts, and each of these layouts defines how the ribbon group will look at different sizes. This is where the Scaling section comes into play. If you look at the scaling section of a Tab (way back up in the UI section of the xml file), you will see that it maps Groups to Layouts:
The MaxSize element defines which layout the group will use when the ribbon is at its full size. If the user shrinks the window size (reducing the amount of horizontal space available for the ribbon), then it will switch to the next Scale listed.
For each Tab, all of the MaxSize and Scale elements for every groups in a tab are all collected together in one single Scaling element, but if we look at only the Scales for the “Collaborate” group (ignoring all the scales for the other groups), we can see how scales change the layout of the group as horizontal size of the ribbon is decreased:
When the Ribbon is full-width, it uses the first MaxSize scale, which points to the “LargeMedium” layout. As the available width shrinks, it moves to the next Scale in the list, which uses the “LargeSmall” layout. Finally, as the width of the ribbon shrinks further, it reaches the 3rd and final Scale, which uses the “Popup” layout. If you open CRM and navigate to Accounts, you can see how the size of the “collaborate” group changes as you adjust the size of the browser window.
The last Layout in a template is usually a special “Popup” layout. The Popup layout tells CRM which layout to use when the group is shrunk down to its smallest size and becomes a popup menu. The LayoutTitle attribute in the element refers to another layout that is used to arrange the content of the popup (in this case, the popup uses the LargeMedium layout).
How to use this information?
So how do we use this information about templates and scales? Well, if you just need to add a button to an existing group it doesn’t really matter. You can just set button’s TemplateAlias to “isv” and you’re done. However, if you are creating a completely new group for the ribbon then you will need to pick a Template and define the MaxSize and Scaling elements for the group. And if none of the default Templates will work for you, then you can even define your own template in the RibbonDiffXml.
To create a new Group, you will need to do the following:
- Add a new Group element to the Tab’s “Groups” collection using a CustomAction.
- Add a new MaxSize element into the Tab’s “Scaling” collection using a CustomAction.
- Optionally, add additional scales to the scaling collection (it is a good idea to at least add a “Popup” scale).
- If you want to specify EnableRules or DisplayRules for the Group, then you will need to create a CommandDefinition for the group, otherwise you can just set the group’s command attribute to “Mscrm.Enabled” (this will make the group always enabled).
- Add a new LocLabel to the LocLabels section for the groups Title attribute.
- If you defined a Popup scale for the group, then the group should have the Image32by32Popup attribute defined (this is the image shown when the group turns into a popup button at the smallest scale).
In Conclusion...
- The arrangement of controls in a ribbon Group is defined by which Template the group is using, and by which TemplateAlias the controls are using.
- Template layouts can be defined using either OverflowSections or Sections and Rows.
- The layout that CRM uses for a group depends on the MaxSize and Scaling elements defined in the Tab, and also the size of the browser window.
- When adding a button to an existing group, you should just use the “isv” TemplateAlias, but when creating new groups you will need to select a Template and define the group’s scaling.
thanks,
~Erik Pool
Wednesday, October 5, 2011
Understanding CRM Ribbon XML - Part 2: updating the ribbon
This is part 2 in a series of posts about the CRM 2011 Ribbon:
How to edit a ribbon
You edit a ribbon by updating the RibbonDiffXml section of the Customizations.xml file. This file is part of the CRM solution zip file that is exported from CRM. Basically, you must create a new solution in CRM that contains the entity who’s ribbon you want to edit. Then you must export the solution and extract the customizations xml file from the exported zip file. See this page for more information on how to export and import ribbons.
Now remember, the CRM solution only contains the changes to the ribbon, so when you view the RibbonDiffXml for a solution exported from CRM, you will not see the full definition of the ribbon. In fact, if no changes to the ribbon have been made yet, it will be almost empty:
One thing that is important to realize is that the different sections of the ribbon are edited differently. To add a new CommandDefinition, EnableRule, or DisplayRule, you can simply insert the new item into the appropriate section inside the RibbonDiffXml:
CustomActions
However, to change anything in the UI section, you need to create CustomActions. Each CustomAction tells CRM to make a change to the UI section of the ribbon. A customAction could tell CRM to insert a new button into the ribbon UI. Or a CustomAction could tell CRM to remove an existing button from the UI. Essentially, each CustomAction either defines a block of XML that should be inserted into the UI section of the ribbon, or indicates which element of the XML should be removed from the ribbon.
Take a look at this example:
This CustomAction tells CRM to insert the XML inside the CommandUIDefinition element into the XML element defined by the Location attribute (highlighted in yellow). In this case the CustomAction is inserting a new Button element into the Mscrm.HomepageGrid.account.MainTab.Collaborate.Controls element.
Notice that the Location attribute has “_children” at the end of it. This is just a convention used by CRM, it means “insert the xml content into the children of the specified element”.
This is what the base ribbon xml looks like after CRM has used the CustomAction to insert the button into the “Mscrm.HomepageGrid.account.MainTab.Collaborate.Controls” element:
Removing Existing Buttons and Controls
If you want to remove an existing element from a ribbon, you can use HideCustomAction. With HideCustomAction, you just specify which element inside the ribbon UI to hide. For example, the following will hide the ExportData button on the account homepage ribbon:
Remember, each solution only defines the actions used to change the base ribbon xml. So you could have one CRM solution that adds a ribbon button, and then another Solution layered on top of it that hides the same button.
A quick note about LocLabels
In the next post I will discuss how the CRM Ribbon uses Templates to determine positioning and size of ribbon controls.
- Part 1 - Content and Structure of the Ribbon.
- Part 2 - Editing the ribbon (this post)
- Part 3 - How Ribbon Templates work (coming soon)
How to edit a ribbon
You edit a ribbon by updating the RibbonDiffXml section of the Customizations.xml file. This file is part of the CRM solution zip file that is exported from CRM. Basically, you must create a new solution in CRM that contains the entity who’s ribbon you want to edit. Then you must export the solution and extract the customizations xml file from the exported zip file. See this page for more information on how to export and import ribbons.
Now remember, the CRM solution only contains the changes to the ribbon, so when you view the RibbonDiffXml for a solution exported from CRM, you will not see the full definition of the ribbon. In fact, if no changes to the ribbon have been made yet, it will be almost empty:
One thing that is important to realize is that the different sections of the ribbon are edited differently. To add a new CommandDefinition, EnableRule, or DisplayRule, you can simply insert the new item into the appropriate section inside the RibbonDiffXml:
However, to change anything in the UI section, you need to create CustomActions. Each CustomAction tells CRM to make a change to the UI section of the ribbon. A customAction could tell CRM to insert a new button into the ribbon UI. Or a CustomAction could tell CRM to remove an existing button from the UI. Essentially, each CustomAction either defines a block of XML that should be inserted into the UI section of the ribbon, or indicates which element of the XML should be removed from the ribbon.
Take a look at this example:
This CustomAction tells CRM to insert the XML inside the CommandUIDefinition element into the XML element defined by the Location attribute (highlighted in yellow). In this case the CustomAction is inserting a new Button element into the Mscrm.HomepageGrid.account.MainTab.Collaborate.Controls element.
Notice that the Location attribute has “_children” at the end of it. This is just a convention used by CRM, it means “insert the xml content into the children of the specified element”.
This is what the base ribbon xml looks like after CRM has used the CustomAction to insert the button into the “Mscrm.HomepageGrid.account.MainTab.Collaborate.Controls” element:
Removing Existing Buttons and Controls
If you want to remove an existing element from a ribbon, you can use HideCustomAction. With HideCustomAction, you just specify which element inside the ribbon UI to hide. For example, the following will hide the ExportData button on the account homepage ribbon:
<HideCustomAction Location="Mscrm.HomepageGrid.quote.MainTab.ExportData"
HideActionId="Sample.HomepageGrid.quote.MainTab.ExportData.HideAction" />Remember, each solution only defines the actions used to change the base ribbon xml. So you could have one CRM solution that adds a ribbon button, and then another Solution layered on top of it that hides the same button.
A quick note about LocLabels
In the RibbonDiffXml from your solution, you will see a RibbonDiffXml section that doesn’t appear in the Base ribbon XML from the resources\exportedribbonxml. This is where you store the localized labels, tooltips, and descriptions for your custom ribbon buttons. Labels are defined in the LocLabels section of the RibbonDiffXml and are referenced by the controls using the “$LocLabels:” syntax:
<Button Id="Sample.account.grid.SendToOtherSystem.Button"
Command="Sample.account.grid.SendToOtherSystem.Command"
LabelText="$LocLabels:Sample.account.SendToOtherSystem.LabelText"
TemplateAlias="o1"
Image32by32="$webresource:sample_/icons/TIcon32x32.png" />
Why don’t LocLabels appear in the base ribbon xml? I suspect it’s because none of the built-in CRM buttons actually use LocLabels, instead they use their own localized resources embedded in the CRM application.
To recap:
- The ribbon XML definition cannot be edited directly, instead you must edit the RibbonDiffXml from the solution customizations.xml file.
- New CommandDefinitions, EnableRules, and DisplayRules can be added directly to the available sections in the RibbonDiffXml.
- New UI elements (i.e, new buttons, groups, or tabs) must be added to the ribbon using CustomActions
- Existing tabs, groups, and buttons can be hidden using HideCustomActions.
In the next post I will discuss how the CRM Ribbon uses Templates to determine positioning and size of ribbon controls.
Saturday, October 1, 2011
Understanding CRM Ribbon XML - Part 1: ribbon content and structure
This is part 1 in a series of posts about the CRM 2011 Ribbon:
- Part 1 - Content and Structure of the Ribbon. (this post)
- Part 2 - Editing the ribbon.
- Part 3 - How Ribbon Templates work (coming soon)
Demystifying the CRM 2011 ribbon xml
- Part 1 - Content and Structure of the Ribbon. (this post)
- Part 2 - Editing the ribbon.
- Part 3 - How Ribbon Templates work (coming soon)
Demystifying the CRM 2011 ribbon xml
The CRM 2011 SDK provides a number of walkthroughs on adding buttons and tabs to the CRM entity ribbons, but they don’t really offer an in-depth explanation of why each step is necessary, or what other options are available. In the next few posts I will attempt to explain fully how the ribbons for CRM 2011 can be customized, and explain in more detail some of the areas that the SDK only touches lightly. In this first post I will cover the basic structure and content of the ribbon XML, and in following posts we'll go over editing the ribbon and how ribbon templates work.
Your CRM solution doesn’t contain the entire ribbon
The first thing you must know about CRM ribbons, is that a CRM solution will not contain the entire definition of a ribbon. The CRM database does contain a base ribbon definition for each entity, but this definition is not directly customizable. When you modify the ribbon, your CRM solution will only store the changes made to the ribbon. This way multiple solutions can each make changes to the same ribbon and they will all get layered on top of each other, without any solutions overwriting each other.
So when making changes to any CRM ribbon you have to keep in mind that you’re not actually editing the ribbon XML definition, you are editing an XML file (ribbondiffxml) that describes the changes made to the ribbon.
Viewing the Full Ribbon Definition
Each ribbon in CRM is defined by an xml file, and the first thing you should do when trying to understand how ribbon customization works is to look at the full ribbon xml for a CRM entity. There are two ways you can get the ribbon xml for an entity:
- Look in the resources\exportedribbonxml folder in the CRM SDK. This folder contains the ribbon xml for all the system entities in CRM.
- Or, you can export the ribbon xml for a specific entity programmatically, using the RetrieveEntityRibbonRequest message. For uncustomized system entities this will give you the same xml that is found in resources\exportedribbonxml.
Contents of the Ribbon XML
As you look through the ribbon xml you will see four main sections: UI, Templates, CommandDefinitons, and RuleDefinitions.
- UI – The UI section contains all the Tabs, Groups, and buttons that show up in the ribbon. This defines what you actually see in the ribbon.
- Templates – Templates define the layout of ribbon groups. The size of buttons and controls on a ribbon, how they are arranged, and how they collapse when the window size shrinks all depends on what templates the ribbon is using.
- CommandDefinitons – CommandDefinitions define what the ribbon buttons do (i.e., what actions are executed when a user clicks the button).
- RuleDefinitions – RuleDefinitions define when a button is displayed and when it is enabled or disabled.
Take a look at the xml inside the UI section and you will see a hierarchy of Tabs, which contain Groups, which then contain Controls (including ribbon buttons). Each of these elements corresponds to a part of the ribbon:
For the most part, the UI section is pretty straightforward, but there are a few things worth pointing out:
- Notice that there are several different types of tabs: HomepageGrid, Form, and SubGrid. These tabs will be difference parts of the CRM application. Read about the different types of tabs here.
- Each button has a sequence number and a TemplateAlias. These are used to derermine the ordering of the buttons and the size of the button. We’ll look at how templates work in the next post.
- Inside each tab, in addition to a collection of Groups you will see a “Scaling” section. Ignore this section for now, we’ll get to it in a bit (essentially, it defines how the groups behave when there isn’t enough room on the screen to display the full ribbon).
Command Definitions
Look at the Buttons in the UI section:
Notice that the buttons each have a “Command” attribute. If we search the XML file for the command, we see that it’s a reference to a CommandDefinition (which is found in the commandDefinitions section, of course):
You can see that in the CommandDefinition, there is an Action, and also a number of DisplayRules and EnableRules. The Action defines what happens when the button is pressed. The Action can be either a Javascript function or a URL. In the example shown above, the action calls the “openObj” JavaScript function. See this page for more information on how to define ribbon actions.
RuleDefinitions
The EnableRules and DisplayRules in the CommandDefinition define which rules are used to determine when the button is enabled or visible. The rules you see inside the CommandDefinition are just pointers, the actual definitions of these rules are defined in the RuleDefinitions section:
There are many different kinds of rules that can be used within each EnableRule or DisplayRule, allowing you to create complex conditions for almost any kind of scenario. In the example shown above, the CreateSelectedEntityPermission rule uses an EntityPrivilegeRule to make sure that the “New Record” button is only enabled when the current user has the Create permission on the selected entity. To understand the full capability of EnableRules and DisplayRules, check out the following SDK articles: EnableRules, DisplayRules.
In Conclusion..
To recap, we have the UI section, which defines the actual visual elements that make up a ribbon. The buttons in the UI section link to a CommandDefinition, which defines what the button does. And the CommandDefinition links to multiple RuleDefinitions, which define when the button is visible or enabled. What about the Templates section? We will get to that in a future post.
In the next post, we'll see how to actually edit the ribbon.
Sunday, August 28, 2011
Error Logging Plug-in exceptions in CRM Online
(Cross-posted from the Avanade Xrm Blog, where I've been posting more recently)
For a while now we've been trying to find some way of logging plug-in exceptions on CRM Online (and I've also seen a number of posts on the CRM Developer forums asking if this is possible).
Logging exceptions that originate from CRM plug-ins presents a number of problems, especially in CRM online. First of all, plug-ins in CRM online are sand-boxed, so you do not have access to the file system or event log. The CRM SDK offers a tracing service that can be used, but this only displays tracing information to the end user. No permanent log of the exception is recorded.
The logical solution is to just write all exception messages to a custom CRM entity (The SDK even mentions this idea). But this is also problematic because synchronous plug-ins are run inside of a database transaction. So if your plug-in runs into an exception the transaction will get rolled back, including rolling back the creation of any Error Log record you created while handling the exception.
Ideally, we want all of the operations of the plug-in to be inside the database transaction, but the creation of the errorlog record to NOT be part of the transaction (so that any changes made by the plug-in are rolled back, but an admin can still read the error message afterwards). This can be achieved in an on-premise CRM installation by just creating a new OrganizationService proxy from scratch, instead of using the one provided by the plugin context. But again, this doesn't work in CRM Online because of the sandbox restrictions (when running in partial trust you cannot make WCF calls or use the libraries included in the CRM SDK for connecting to CRM online).
Fortunately, there is a different way to connect to CRM online while in partial-trust, and the answer comes from this post on Dimaz Pramudya's blog.
Dimaz has created two c# classes; LiveIdTicketAcquirer and CrmOnlineQueryManager, which together can be used to connect to CRM online from within a CRM plug-in. And this new connection will not be part of the database transaction, which means it can be used to write our error log messages back to a CRM entity.
In the following code sample I am using the CrmOnlineQueryManager to log an exception to a custom entity called "new_error":
When I run this plug-in, the thrown exception rolls back the update I was making, and I see the expected error message:
But I also see a log of the exception in my custom "Errors" table:
So now we can do error logging on CRM Online. Of course, this is just a proof of concept. There's a few more steps we would need to take before using this in a production environment..
~Erik Pool
For a while now we've been trying to find some way of logging plug-in exceptions on CRM Online (and I've also seen a number of posts on the CRM Developer forums asking if this is possible).
Logging exceptions that originate from CRM plug-ins presents a number of problems, especially in CRM online. First of all, plug-ins in CRM online are sand-boxed, so you do not have access to the file system or event log. The CRM SDK offers a tracing service that can be used, but this only displays tracing information to the end user. No permanent log of the exception is recorded.
The logical solution is to just write all exception messages to a custom CRM entity (The SDK even mentions this idea). But this is also problematic because synchronous plug-ins are run inside of a database transaction. So if your plug-in runs into an exception the transaction will get rolled back, including rolling back the creation of any Error Log record you created while handling the exception.
Ideally, we want all of the operations of the plug-in to be inside the database transaction, but the creation of the errorlog record to NOT be part of the transaction (so that any changes made by the plug-in are rolled back, but an admin can still read the error message afterwards). This can be achieved in an on-premise CRM installation by just creating a new OrganizationService proxy from scratch, instead of using the one provided by the plugin context. But again, this doesn't work in CRM Online because of the sandbox restrictions (when running in partial trust you cannot make WCF calls or use the libraries included in the CRM SDK for connecting to CRM online).
Fortunately, there is a different way to connect to CRM online while in partial-trust, and the answer comes from this post on Dimaz Pramudya's blog.
This post is over a year old, and only describes connecting to the CRM 4.0 webservicce, but it will still work with CRM 2011 online."There is a sample on how to achieve this using Passport authentication in MSCRM SDK. ... it failed miserably in shared hosting environment which runs Medium or High trust level.
What I wanted to share today is an alternative way to get the Windows Live Id ticket that works virtually on any environment. This method uses the RPS (Relying Party Suites) authentication option for Windows Live."
Dimaz has created two c# classes; LiveIdTicketAcquirer and CrmOnlineQueryManager, which together can be used to connect to CRM online from within a CRM plug-in. And this new connection will not be part of the database transaction, which means it can be used to write our error log messages back to a CRM entity.
In the following code sample I am using the CrmOnlineQueryManager to log an exception to a custom entity called "new_error":
public class TestPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
try
{
throw new Exception("Testing Error logging");
}
catch( Exception ex)
{
CrmOnlineErrorLogger.LogError(ex);
throw;
}
}
}class CrmOnlineErrorLogger
{
const string username = "myLiveId";
const string password = "myPassword";
const string organization = "myOrgName";
public static void LogError(Exception ex)
{
CrmOnlineQueryManager queryManager = new CrmOnlineQueryManager();
var crmService = queryManager.Connect(username, password, organization);DynamicEntity entity = new DynamicEntity();
entity.Name = "new_error";StringProperty exceptionType = new StringProperty() { Name = "new_exceptiontype", Value = ex.GetType().FullName };StringProperty details = new StringProperty() { Name = "new_details", Value = ex.Message + ": " + ex.StackTrace };entity.Properties = new Property[] { exceptionType, details };crmService.Create(entity);
}
}
But I also see a log of the exception in my custom "Errors" table:
So now we can do error logging on CRM Online. Of course, this is just a proof of concept. There's a few more steps we would need to take before using this in a production environment..
- This code still connects to the old CRM 4.0 webservice. Presumably a similar connector could be made that uses the 2011 endpoint.
- There may be performance issues. I've noticed its a bit slow to log errors, but a lot of the slowdown can be reduced by trimming the crm4 webservice proxy class down to just the minimum components needed. Also, the slowdown only occurs when the plug-in actually throws an error, which may be acceptable. Bottom line, this technique still needs to be fully tested.
~Erik Pool
Sunday, March 20, 2011
Filtering generated entities with CrmSvcUtil
In CRM 2011 you can create early-bound entity classes using the code generation utility (CrmSvcUtil.exe) that comes with the SDK. This gives you nice strongly-typed entity classes that you can use with full intellisense in Visual Studio and in linq queries (and many other benefits).
However, the cs file generated from this utility can be over 5 - 10MB in size, which is a lot when you want to include it in a CRM plug-in where you should try to keep your assembly as small as possible.
By default the utility will generate classes for every entity in the CRM organization, but fortunately Microsoft has provided a way to filter which entities are generated.
To filter the entities that are generate, we need to create an extension for the CrmSvcUtil utility. Basically, we have to create a small class library that implements an interface used by the utility. The SDK provides a little bit of info, but not much in the way of examples. So here's what we need to do:
This class implements the ICodeWriterFilterService interface. This interface is used by the class generation utility to determine which entities, attrributes, etc. should actually be generated. The interface is very simple and just has seven methods that are passed metadata info and return a boolean indicating whether or not the metadata should be included in the generated code file.
For now I just want to be able to determine which entities are generated, so in the constructor I read from an XML file (filter.xml) that holds the list of entities to generate and put the list in a Hashset. The format of the xml is this:
Take a look at the methods in the class. In the GenerateEntity method, we can simply check the EntityMetadata parameter against our list of valid entities and return true if it's an entity that we want to generate.
For all of the other methods we want to just do whatever the default implementation of the utility is. Notice how the constructor of the class accepts a defaultService parameter. We can just save a reference to this default service and use it whenever we want to stick with the default behavior. All of the other methods in the class just call the default service.
To use our extension when running the utility, we just have to make sure the compiled DLL and the filter.xml file are in the same folder as CrmSvcUtil.exe, and set the /codewriterfilter command-line argument when running the utility (as described in the SDK):
That's it! You now have a generated sdk.cs file that is only a few hundred kilobytes instead of 5MB.
One final note: There is actually a lot more you can do with extensions to the code generation utility. For example: if you return true in the GenerateOptionSet method, it will actually generated Enums for each CRM picklist (which it doesn't normally do by default).
Also, the source code for this SvcUtilFilter example can be found here. Use at your own risk, no warranties, etc. etc.
-Erik Pool
However, the cs file generated from this utility can be over 5 - 10MB in size, which is a lot when you want to include it in a CRM plug-in where you should try to keep your assembly as small as possible.
By default the utility will generate classes for every entity in the CRM organization, but fortunately Microsoft has provided a way to filter which entities are generated.
To filter the entities that are generate, we need to create an extension for the CrmSvcUtil utility. Basically, we have to create a small class library that implements an interface used by the utility. The SDK provides a little bit of info, but not much in the way of examples. So here's what we need to do:
- Create a new C# class library project in Visual Studio called SvcUtilFilter.
- In the project, add references to the following:
- CrmSvcUtil.exe This exe has the interface we will implement.
- Microsoft.Xrm.Sdk.dll (found in the CRM SDK).
- System.Runtime.Serialization.
- Add the following class to the project:
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Microsoft.Crm.Services.Utility;
using Microsoft.Xrm.Sdk.Metadata;
namespace SvcUtilFilter
{
/// <summary>
/// CodeWriterFilter for CrmSvcUtil that reads list of entities from an xml file to
/// determine whether or not the entity class should be generated.
/// </summary>
public class CodeWriterFilter : ICodeWriterFilterService
{
//list of entity names to generate classes for.
private HashSet<string> _validEntities = new HashSet<string>();
//reference to the default service.
private ICodeWriterFilterService _defaultService = null;
/// <summary>
/// constructor
/// </summary>
/// <param name="defaultService">default implementation</param>
public CodeWriterFilter( ICodeWriterFilterService defaultService )
{
this._defaultService = defaultService;
LoadFilterData();
}
/// <summary>
/// loads the entity filter data from the filter.xml file
/// </summary>
private void LoadFilterData()
{
XElement xml = XElement.Load("filter.xml");
XElement entitiesElement = xml.Element("entities");
foreach (XElement entityElement in entitiesElement.Elements("entity"))
{
_validEntities.Add(entityElement.Value.ToLowerInvariant());
}
}
/// <summary>
/// /Use filter entity list to determine if the entity class should be generated.
/// </summary>
public bool GenerateEntity(EntityMetadata entityMetadata, IServiceProvider services)
{
return (_validEntities.Contains(entityMetadata.LogicalName.ToLowerInvariant()));
}
//All other methods just use default implementation:
public bool GenerateAttribute(AttributeMetadata attributeMetadata, IServiceProvider services)
{
return _defaultService.GenerateAttribute(attributeMetadata, services);
}
public bool GenerateOption(OptionMetadata optionMetadata, IServiceProvider services)
{
return _defaultService.GenerateOption(optionMetadata, services);
}
public bool GenerateOptionSet(OptionSetMetadataBase optionSetMetadata, IServiceProvider services)
{
return _defaultService.GenerateOptionSet(optionSetMetadata, services);
}
public bool GenerateRelationship(RelationshipMetadataBase relationshipMetadata, EntityMetadata otherEntityMetadata, IServiceProvider services)
{
return _defaultService.GenerateRelationship(relationshipMetadata, otherEntityMetadata, services);
}
public bool GenerateServiceContext(IServiceProvider services)
{
return _defaultService.GenerateServiceContext(services);
}
}
}
This class implements the ICodeWriterFilterService interface. This interface is used by the class generation utility to determine which entities, attrributes, etc. should actually be generated. The interface is very simple and just has seven methods that are passed metadata info and return a boolean indicating whether or not the metadata should be included in the generated code file.
For now I just want to be able to determine which entities are generated, so in the constructor I read from an XML file (filter.xml) that holds the list of entities to generate and put the list in a Hashset. The format of the xml is this:
<filter><entities><entity>systemuser</entity><entity>team</entity><entity>role</entity><entity>businessunit</entity></entities></filter>
Take a look at the methods in the class. In the GenerateEntity method, we can simply check the EntityMetadata parameter against our list of valid entities and return true if it's an entity that we want to generate.
For all of the other methods we want to just do whatever the default implementation of the utility is. Notice how the constructor of the class accepts a defaultService parameter. We can just save a reference to this default service and use it whenever we want to stick with the default behavior. All of the other methods in the class just call the default service.
To use our extension when running the utility, we just have to make sure the compiled DLL and the filter.xml file are in the same folder as CrmSvcUtil.exe, and set the /codewriterfilter command-line argument when running the utility (as described in the SDK):
crmsvcutil.exe /url:http://<server>/<org>/XrmServices/2011/Organization.svc /out:sdk.cs /namespace:<namespace> /codewriterfilter:SvcUtilFilter.CodeWriterFilter,SvcUtilFilter
That's it! You now have a generated sdk.cs file that is only a few hundred kilobytes instead of 5MB.
One final note: There is actually a lot more you can do with extensions to the code generation utility. For example: if you return true in the GenerateOptionSet method, it will actually generated Enums for each CRM picklist (which it doesn't normally do by default).
Also, the source code for this SvcUtilFilter example can be found here. Use at your own risk, no warranties, etc. etc.
-Erik Pool
Tuesday, March 1, 2011
Debugging Silverlight web-resources that connect to CRM Online.
Debugging Silverlight web-resources in CRM 2011 can be pretty tedious when you have to constantly re-upload your XAP file to the CRM server and then manually attach the debugger to the IE process. If you run the Silverlight application from your own development machine (using the ASP.NET Development server) you can quickly start debugging by hitting F5 in Visual Studio, but the app won't be able to make any requests to the CRM server because of Silverlight's cross-domain restrictions.
If you have an on-premise CRM server you can get around the cross-domain restrictions by putting a ClientAccessPolicy.xml file in the root of the CRM web application (See Markus Konrad's post on creating the policy file), but CRM online won't let you upload a ClientAccessPolicy, so you have to be a little more clever. One method is to just create a proxy to pass all the requests to the CRM server.
To get web-service requests to go from a Silverlight application w just need a few things:
1. Create an aspx page in the web project that's hosting the Silverlight app that will act as our proxy.
2. Program the proxy page to forward all posted requests to the CRM Online web-service endpoint.
3. Authenticate against CRM Online and attach the LiveId security token to each request.
4. Configure the Silverlight app to point to the proxy page.
Steps 1 and 2 are actually pretty straightforward once you pull out fiddler and figure out what the soap requests work like. Step 3 is the complicated bit but the sample code in the CRM SDK provides an example of authenticating against CRM Online (see sdk\samplecode\cs\wsdlbasedproxies\online). Once you've authenticated and have the security token you can just inject the token right into the header of the SOAP request.
I've built an example application which you can download here. It's just a simple Silverlight application that makes create, retrieve, update, delete, and execute request to CRM Online using the proxy described above.
To try it out just update the constants defined at the top of Proxy.aspx.cs:
Also note the GetOrganizationService method in the Silverlight app. If the current page is running on "localhost" it assumes you're debugging and points the org service url at the proxy.aspx page. This way the same silverlight app works on my dev machine and when it's deployed to CRM Online.
Download the source code here.
-Erik
If you have an on-premise CRM server you can get around the cross-domain restrictions by putting a ClientAccessPolicy.xml file in the root of the CRM web application (See Markus Konrad's post on creating the policy file), but CRM online won't let you upload a ClientAccessPolicy, so you have to be a little more clever. One method is to just create a proxy to pass all the requests to the CRM server.
Creating a proxy for CRM SOAP requests
A quick note: This works for the SOAP Organization service and the example at the end of this post only uses the SOAP service. I'm not sure if a similar proxy would work for the REST endpoint (Given the REST endpoint's limitation I rarely use it anyway).
To get web-service requests to go from a Silverlight application w just need a few things:
1. Create an aspx page in the web project that's hosting the Silverlight app that will act as our proxy.
2. Program the proxy page to forward all posted requests to the CRM Online web-service endpoint.
3. Authenticate against CRM Online and attach the LiveId security token to each request.
4. Configure the Silverlight app to point to the proxy page.
Steps 1 and 2 are actually pretty straightforward once you pull out fiddler and figure out what the soap requests work like. Step 3 is the complicated bit but the sample code in the CRM SDK provides an example of authenticating against CRM Online (see sdk\samplecode\cs\wsdlbasedproxies\online). Once you've authenticated and have the security token you can just inject the token right into the header of the SOAP request.
I've built an example application which you can download here. It's just a simple Silverlight application that makes create, retrieve, update, delete, and execute request to CRM Online using the proxy described above.
To try it out just update the constants defined at the top of Proxy.aspx.cs:
//Update these constants to match your CRM Online accountconst string host = "orgName.api.crm.dynamics.com";const string userName = "erik.pool@example.com";const string password = "Password";
Also note the GetOrganizationService method in the Silverlight app. If the current page is running on "localhost" it assumes you're debugging and points the org service url at the proxy.aspx page. This way the same silverlight app works on my dev machine and when it's deployed to CRM Online.
if (href.Contains("localhost")){Uri baseUrl = new Uri(href, UriKind.Absolute);orgServiceUrl = new Uri(baseUrl, "SoapProxy/Proxy.aspx");}else{orgServiceUrl = new Uri(location.GetProperty("protocol") + "//" + location.GetProperty("host") + "/xrmservices/2011/organization.svc/web");}
Download the source code here.
-Erik
Subscribe to:
Posts (Atom)