This sample is designed to help developers who are using CrmSvcUtil from the Microsoft Dynamics 365 SDK extend the code generation process.
The CrmSvcUtil.exe tool provided by the Dynamics 365 SDK allows developers to customize the process of the code generation. Out of the box, the code generation has some limitations:
The CloudSmith.Cds.CrmSvcUtil package contains an assembly with public types, which can bu used to inject logic into the CrmSvcUtil.exe execution pipeline. Microsoft describes this process under Create extensions for the code generation tool.
1) Create the project where your generated code should be placed 1) Install and configure the Microsoft.CrmSdk.CoreTools package, which includes CrmSvcUtil.exe and it’s required dependencies.
Install-Package Microsoft.CrmSdk.CoreTools
1) Add the CloudSmith.Cds.CrmSvcUtil package to you project:
Install-Package CloudSmith.Cds.CrmSvcUtil
As a result of the installation the assembly CloudSmith.Cds.CrmSvcUtil.dll will be copied to the
\bin\coretols
folder Note: Alternatively, you can manually deploy CloudSmith.Cds.CrmSvcUtil.dll to the folder containing CrmSvcUtil.exe.
1) Inject/configure the services you want to use to modify the generated code.
It is possible to run each individual service extension stand-alone. For better results, we recommend using the Composite services listed below. The composite services are designed to run more than one underlying service and consolidate the results.
Parameter / Key | CrmSvcUtil Service Interface | Extension Class | Configuration Section |
---|---|---|---|
codecustomization | ICustomizeCodeDomService | CloudSmith.Dynamics365.CrmSvcUtil.CompositeCustomizationService | Generation |
codewriterfilter | ICodeWriterFilterService | CloudSmith.Dynamics365.CrmSvcUtil.CompositeFilterService | Filtering |
codewritermessagefilter | ICodeWriterMessageFilterService | CloudSmith.Dynamics365.CrmSvcUtil.CompositeFilterService | Filtering |
metadataproviderservice | IMetadataProviderService | TBD | TBD |
codegenerationservice | ICodeGenerationService | TBD | TBD |
namingservice | INamingService | CloudSmith.Dynamics365.CrmSvcUtil.CompositeNamingService | Naming |
You may specify these types on the CrmSvcUtil command line, or in the CrmSvcUtil.exe.config file inside the appSettings element as shown below.
<appSettings>
<add key="codewriterfilter" value="CloudSmith.Cds.CrmSvcUtil.CompositeFilterService, CloudSmith.Cds.CrmSvcUtil" />
<add key="codewritermessagefilter" value="CloudSmith.Cds.CrmSvcUtil.CompositeFilterService, CloudSmith.Cds.CrmSvcUtil" />
<add key="codecustomization" value="CloudSmith.Cds.CrmSvcUtil.CompositeCustomizationService, CloudSmith.Cds.CrmSvcUtil" />
<add key="namingservice" value="CloudSmith.Cds.CrmSvcUtil.CompositeNamingService, CloudSmith.Cds.CrmSvcUtil" />
</appSettings>
.\CrmSvcUtil.exe
/url:{url}
/username:{username}
/password:{password}
/namespace:{namespace}
/out:{filename}
/codewriterfilter:"CloudSmith.Cds.CrmSvcUtil.CompositeFilterService, CloudSmith.Cds.CrmSvcUtil"
/codewritermessagefilter:"CloudSmith.Cds.CrmSvcUtil.CompositeFilterService, CloudSmith.Cds.CrmSvcUtil"
/codecustomization:"CloudSmith.Cds.CrmSvcUtil.CompositeCustomizationService, CloudSmith.Cds.CrmSvcUtil"
/namingservice:"CloudSmith.Cds.CrmSvcUtil.CompositeNamingService, CloudSmith.Cds.CrmSvcUtil"
It is possible to configure the individual services via the appSettings Section in the CrmSvcUtil.exe.config file, instead of using the composite services provided in the example above. Below is a list of packaged services and the order in which rules are evaluated.
It is not necessarily to configure the individual services in most scenarios, using the composite services above will automatically execute each individual service below.
Order | Service | Type | Purpose |
---|---|---|---|
0 | (default) | ICodeWriterFilterService | CrmSvcUtil built in ICodeWriterFilterService |
1 | CloudSmith.Dynamics365.CrmSvcUtil.Filter.RegExFilterService | ICodeWriterFilterService | Filter entities or attributes generated via RegEx |
2 | CloudSmith.Dynamics365.CrmSvcUtil.Filter.SolutionFilterService | ICodeWriterFilterService | Filter entities or attributes based on their existance in a Solution |
3 | CloudSmith.Dynamics365.CrmSvcUtil.Filter.ExactMatchFilterService | ICodeWriterFilterService | Filter entities or attributes based on their match to a whitelist/blacklist |
4 | CloudSmith.Dynamics365.CrmSvcUtil.Filter.CustomEntitiesFilterService | ICodeWriterFilterService | Filter customizations based on their “IsCustomized” flag in Dynamics |
0 | (default) | INamingService | CrmSvcUtil built in INamingService |
1 | CloudSmith.Dynamics365.CrmSvcUtil.Naming.PublisherNameService | INamingService | Rename all items, optionally removing publisher prefixes |
2 | CloudSmith.Dynamics365.CrmSvcUtil.Naming.MapNameService | INamingService | Rename attributes and entities, using a specific mapping scheme |
0 | (default) | ICustomizeCodeDomService | CrmSvcUtil built in ICustomizeCodeDomService |
1 | CloudSmith.Dynamics365.CrmSvcUtil.Generation.OptionSetEnumCustomizationService | ICustomizeCodeDomService | Refactors OptionSet values to use Enum types instead of OptionSetValue |
2 | CloudSmith.Dynamics365.CrmSvcUtil.Generation.ImportResolverCustomizationService | ICustomizeCodeDomService | Applies “using” or “Import” statements to each file generated |
3 | CloudSmith.Dynamics365.CrmSvcUtil.Generation.FileSplitCustomizationService | ICustomizeCodeDomService | Slices generation tasks into separate for version control |
1) Configure the extensions and run CrmSvcUtil.exe.
The CrmSvcUtil extensions use a .NET ConfigurationSection to locate its configuration information. All configuration values have a default value which reflects the default behavior of the CrmSvcUtil.exe tool, or can optionally disable some features.
To start configuring the CrmSvcUtil extensions you have to add a new section in the CrmSvcUtil.exe.config file as demonstrated below.
<configSections>
<section name="ServiceExtensions" type="CloudSmith.Cds.CrmSvcUtil.Configuration.ServiceExtensionsConfigurationSection, CloudSmith.Cds.CrmSvcUtil" />
</configSections>
Inside the ServiceExtensions element you may input configuration values for each individual service. This example shows a completed configuration section that uses all the services.
<ServiceExtensions>
<Filtering>
<Whitelist filter="Exclusive">
<Entities>
<add entity="account" />
<add entity="contact" />
</Entities>
<Attributes>
<add entity="*" attribute="name" />
</Attributes>
<OptionSets>
<add entity="*" optionSet="addresstype" />
</OptionSets>
<Filters>
<entity expression=".*" ignoreCase="true" />
<attribute entity="*" expression=".*" ignoreCase="true" />
</Filters>
<Solutions>
<add solution="MyDynamicsSolution" />
</Solutions>
<!-- Valid values are Default, CustomOnly, and UncustomizedOnly -->
<Customizations strategy="Default" />
</Whitelist>
<Blacklist>
<Entities>
<!-- wont be blacklisted as it's on the whitelist -->
<add entity="contact" />
</Entities>
<Attributes>
<add entity="*" attribute="description" />
</Attributes>
</Blacklist>
</Filtering>
<Naming>
<!-- configuration that goes here only applies for the NamingService -->
<PublisherRules>
<add name="cs_" action="remove" />
</PublisherRules>
<MappingRules>
<add from="sample_entity" to="MyEntity" type="entity"/>
<add from="sample_entity.foo" to="Bar" type="attribute"/>
</MappingRules>
</Naming>
<CodeGeneration path=".\Output">
<Behaviors>
<add name="TranslateOptionSetsAsEnums" />
<add name="ImportNamespaces" arguments="System,System.ComponentModel,System.Runtime.Serialization,System.CodeDom.Compiler,Microsoft.Xrm.Sdk" />
</Behaviors>
<Files>
<!-- options for type include: Entities, AttributeConstants, OptionSets, Requests -->
<!-- options for generate include: OneFilePerEntity, OneFilePerItem, SingleFile -->
<!-- format string is only 1 parameter: (name; formatted as filename safe) -->
<!-- paths are relative to the code generation path above, or output switch, or current folder -->
<add filename="Entities\{0}.cs" type="Entities" generate="OneFilePerEntity" />
<add filename="Entities\{0}.attributes.cs" type="AttributeConstants" generate="OneFilePerEntity" />
<add filename="Entities\{0}.optionsets.cs" type="OptionSets" generate="OneFilePerEntity" />
<add filename="Requests.cs" type="Requests" generate="SingleFile" />
</Files>
</CodeGeneration>
</ServiceExtensions>
The Filtering element allows you to configure the beavior of the filtering services using a Whitelist and a Blacklist.
Whitelisting has 2 modes - Inclusive and Exclusive.
The Entities, Attributes, OptionSets and Solutions elements inside the Filtering element are collection elements. Collection elements can be layered in many configuration files, allowing a composite set of items to be procesed from different configurations. The elements (tags) that can go inside collections include:
Element | Purpose |
---|---|
add | Adds an element to the collection. Each item is keyed |
clear | Clears the contents of the collection, regardless of how it was populated. |
remove | Removes an element from the collection. |
The Entities element allows you to configure specific entiites for whitelisting or blacklisting. They are specified using these configuration attributes:
Attribute | Purpose |
---|---|
entity | The logical name of the entity (e.g. account) |
The following example shows how to add an entity to a WhiteList in configuration:
<Whitelist filter="Exclusive">
<Entities>
<add entity="account" />
</Entities>
</Whitelist>
The Attributes element allows you to configure specific attributes (per entity or globally) for whitelisting or blacklisting. They are specified using these configuration attributes:
Attribute | Purpose |
---|---|
entity | The logical name of the entity (use “*” to specify all entities) |
attribute | The logical name of the attribute (e.g. description) |
The following example shows how to add the attribute “name” to a WhiteList for all entities in configuration:
<Whitelist filter="Exclusive">
<Attributes>
<add entity="*" attribute="name" />
</Attributes>
</Whitelist>
The OptionSets element allows you to configure specific option sets (per entity or globally) for whitelisting or blacklisting. They are specified using these configuration attributes:
Attribute | Purpose |
---|---|
entity | The logical name of the entity for local option sets, (omit for global sets) |
optionSet | The logical name of the option set (e.g. description) |
The following example shows how to add the attribute “name” to a WhiteList for all entities in configuration:
<Whitelist filter="Exclusive">
<OptionSets>
<add entity="*" optionSet="addresstype" />
</OptionSets>
</Whitelist>
The Filters element allows you to add custom filters for entities, attributes or OptionSets by using regular expressions.
By default the Filters element contains the expression “
.*
”, which means that all items are part of the output.
The Filters element works differently than other configuration sets. You add filters cumulatively using specified element names. The following elements are supported:
Element | Purpose |
---|---|
entity | Defines a regular expression to use against an entity’s logical name |
attribute | Defines a regular expression to use against an attribute’s logical name |
optionSet | Defines a regular expression to use against an option set’s logical name |
The following attributes apply to each element.
Attribute | Purpose |
---|---|
expression | The regular expression to match against |
ignoreCase | true/false value indicating if case-sensitivity should be ignored |
Attribute | Purpose |
---|---|
entity | Defines which entiy to use when matching. Use “*” for all |
expression | The regular expression to match against |
ignoreCase | true/false value indicating if case-sensitivity should be ignored |
Attribute | Purpose |
---|---|
expression | The regular expression to match against |
ignoreCase | true/false value indicating if case-sensitivity should be ignored |
Here is an example of regular expression filters applied to entities and attributes.
<Whitelist filter="Exclusive">
<Filters>
<entity expression=".*" ignoreCase="true" />
<attribute entity="*" expression=".*" ignoreCase="true" />
</Filters>
</Whitelist>
The Solutions element allows you to configure which entities and attributes should be part of the generated code based on their inclusion in one or more solutions. Items will be included or excluded based on their presence in a solution. Here is an example of the Solutions element in a whitelist.
<Whitelist filter="Exclusive">
<Solutions>
<!-- configuration that goes here only applies for the SolutionFilterService -->
<add solution="MyDynamicsSolution" />
</Solutions>
</Whitelist>
The Customization element allows you to influence if you would like custom entities included or not. It is a single element with just one attribute, strategy.
The following values are defined for the “strategy” attribute.
Attribute value | Purpose |
---|---|
Default | Allows both customized and non-customized items to be generated |
CustomOnly | Generates only custom items. All out of the box items are skipped |
UncustomizedOnly | Generates only uncustomized items. All non out of the box items are skipped |
Below is an example of using this filter in a whitelist.
<Whitelist filter="Exclusive">
<!-- Valid values are Default, CustomOnly, and UncustomizedOnly -->
<Customizations strategy="Default" />
</Whitelist>
Naming rules allow you to change the behavior of how items are named when they are generated. The following naming rules are available.
Rule | Purpose |
---|---|
PublisherRules | Rules that can remove publisher prefixes from generated items |
MappingRules | Rules that can map types to alternative names |
The PublisherRules element configures how publisher prefixed names shoudld be handled.
Action | Description |
---|---|
Remove | Removes the prefix from the Member Name |
<Naming>
<PublisherRules>
<!-- remove the beedev_ prefix from the entities -->
<add name="cs_" action="Remove"/>
</PublisherRules>
</Naming>
The above configuration reults in the following code:
/// <summary>
///
/// </summary>
[System.Runtime.Serialization.DataContractAttribute()]
[Microsoft.Xrm.Sdk.Client.EntityLogicalNameAttribute("cs_sampleentity")]
[System.CodeDom.Compiler.GeneratedCodeAttribute("CrmSvcUtil", "8.2.1.8676")]
public partial class SampleEntity : Microsoft.Xrm.Sdk.Entity, System.ComponentModel.INotifyPropertyChanging, System.ComponentModel.INotifyPropertyChanged
{
/// <summary>
/// Default Constructor.
/// </summary>
public SampleEntity() :
base(EntityLogicalName)
{
}
public const string EntityLogicalName = "cs_sampleentity";
// ...
/// <summary>
/// The name of the custom entity.
/// </summary>
[Microsoft.Xrm.Sdk.AttributeLogicalNameAttribute("cs_name")]
public string Name
{
get
{
return this.GetAttributeValue<string>("cs_name");
}
set
{
this.OnPropertyChanging("Name");
this.SetAttributeValue("cs_name", value);
this.OnPropertyChanged("Name");
}
}
}
The MappingRules section allows a detailed control for the naming of special CRM Element like Entities or Attributes.
<Naming>
<MappingRules>
<add from="sample_entity" to="MyEntity" type="entity"/>
<add from="sample_entity.foo" to="Bar" type="attribute"/>
</MappingRules>
</Naming>
It is possible to influence how the actual output files are generated in CrmSvcUtil.exe using options in the Generation element. The Generation element can be configured in these ways:
Name | Purpose |
---|---|
outputPath | The root path to use when generating files. |
language | The language to use when generating files. |
Behaviors | A collection of behaviors to enable when generating files. |
Files | A collection of output files and strategies for outputting each item |
The outputPath attribute can remain empty (or missing), a relative path, or an absolute path. The following table lists the expected outcome of each value.
Value | Outcome |
---|---|
empty | The current working directory where CrmSvcUtil.exe is executed will be used. |
relative path | The relative path will be resolved from the current working directory |
absolute path | The absolute path will be resolved as provided |
The language attribute can remain empty (and will assume C# of whatever commandline variable was supplied), or contain one of these supported output types.
Value | Outcome |
---|---|
empty | The “language” argument will be used, otherwise CSharp |
CSharp | All types will be generated in Microsoft C# |
VisualBasic | All types will be generated in Microsoft Visual Basic |
The behaviors element allows you to inject certain behaviors into the code generation process. The following is a list of behaviors that currently ship with these extensions.
Behavior | Arguments | Outcome |
---|---|---|
TranslateOptionSetsAsEnums | (none) | Turns option set int values into Enums automatically. |
ImportNamespaces | comma delimited list of using statements | Adds and resolves namespaces automatically to the top of each generated file. |
The following example shows how to setup code generation with additional behaviors enabled.
<CodeGeneration path=".\Output" language="CSharp">
<Behaviors>
<add name="TranslateOptionSetsAsEnums" />
<add name="ImportNamespaces" arguments="System,System.ComponentModel,System.Runtime.Serialization,System.CodeDom.Compiler,Microsoft.Xrm.Sdk" />
</Behaviors>
</CodeGeneration>
You may generate up to 6 sets of files using the Files collection. Each type of file hsa various strategies available for generation.
Type | File contains | Applicable Strategies |
---|---|---|
Entities | Entity class defiinitions | OneFilePerType, OneFilePerItem, SingleFile |
OptionSets | Enum definitions for local option sets | OneFilePerType, OneFilePerItem, SingleFile |
Attributes | Static field (const) definitions for attribute names | OneFilePerType, SingleFile |
Requests | Class definitions for requests and requests generated from SDK message pairs | OneFilePerType, SingleFile |
Responses | Class definitions for responses | OneFilePerType, SingleFile |
ServiceContext | Class definition for the Service Context | SingleFile |
The following example shows how to setup code generation with unique files for each entity, but otherwise single files per type.
<CodeGeneration path=".\Output" language="CSharp">
<Files>
<!-- options for type include: Entities, AttributeConstants, OptionSets, Requests, Responses, ServiceContext -->
<!-- options for generate include: OneFilePerItem, OneFilePerType, SingleFile -->
<!-- format string is up to 3 parameters: (root filename (no suffix); type; name; formatted as filename safe) -->
<!-- paths are relative to the code generation path above, or output switch, or current folder -->
<add filename="{0}.{1}.{2}.cs" type="Entities" generate="OneFilePerItem" />
<add filename="{0}.{1}.cs" type="AttributeConstants" generate="OneFilePerType" />
<add filename="{0}.{1}.cs" type="OptionSets" generate="OneFilePerType" />
<add filename="{0}.{1}.cs" type="Requests" generate="OneFilePerType" />
</Files>
</CodeGeneration>
This work is largely derivitive. CloudSmith would like to recognize and thank the contributors and underlying projects that represent this extension’s foundation.
Project | Author | License |
---|---|---|
NZ.CrmSvcUtil | Peter Geil | None |
CRMSvcUtilExtensions | Sebastian Holager | MIT |
Xrm.Tooling | Joachim Jauß | None |