CrmSvcUtil Entity Generator Extension

Following from my other CrmSvcUtil extensions CrmSvcUtil Attribute Constant Generator Extension and CrmSvcUtil Option Set Enum Generator Extension, I’ve also implemented a (light-weight) extension to the standard entity generator.

Currently, the list of customisations that this extension adds is:

  1. Replaces the standard <summary> tag for all attributes with the new summary in the form: [ISNULL(Attribute.Label, Attribute.LogicalName)]: [Attribute.Description].
  2. Add a System.ComponentModel.Description attribute to each attribute showing the label of the attribute.

The purpose of these extensions is to give visiblity of the label of an attribute – specifically to make our lives easier when coding against attributes whose labels no longer match their logical name.

Download the CrmSvcUtil extension at codeplex: CrmSvcUtil Generate Entities.

To use the extension:

  1. Copy GenerateEntities.dll into the directory containing CrmSvcUtil.exe
  2. Run CrmSvcUtil with the following parameters:
CrmSvcUtil.exe ^
/codecustomization:"GenerateEntities.CodeCustomizationService, GenerateEntities" ^
/url:http://servername/orgname/XRMServices/2011/Organization.svc ^
/out:Attributes.cs ^
Advertisement

Cannot assign ownership of entities to users in CRM 2013

I recently dealt with an issue in a CRM2013 organisation that had been upgraded from CRM3, to CRM4, to CRM2011 and eventually to CRM2013 where users were unable to assign ownership of Accounts and Contacts to users. When accessing the assign functionality through the ribbon or the owner field on the form, the Look for field is disabled and there is no way to assign the record to a user.
Look for control disabled

After investigating several dead-ends, I compared the XML of the account entity from an organisation created in CRM2013 and the one that had been upgraded over the years and discovered the following suspicious sections in the upgraded organisation:

<attribute PhysicalName="OwnerId">
  <Type>owner</Type>
  <Name>ownerid</Name>
  <LogicalName>ownerid</LogicalName>
  <RequiredLevel>systemrequired</RequiredLevel>
  <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid|RequiredForForm</DisplayMask>
  <ImeMode>auto</ImeMode>
  <ValidForReadApi>1</ValidForReadApi>
  <ValidForCreateApi>1</ValidForCreateApi>
  <IsCustomField>0</IsCustomField>
  <IsAuditEnabled>1</IsAuditEnabled>
  <IsSecured>0</IsSecured>
  <IntroducedVersion>5.0.0.0</IntroducedVersion>
  <LookupStyle>single</LookupStyle>
  <LookupTypes>
    <LookupType id="00000000-0000-0000-0000-000000000000">9</LookupType>
  </LookupTypes>
  <displaynames>
    <displayname description="Owner" languagecode="1033" />
  </displaynames>
  <Descriptions>
    <Description description="Enter the user or team who is assigned to manage the record. This field is updated every time the record is assigned to a different user." languagecode="1033" />
  </Descriptions>
</attribute>
<EntityRelationship Name="owner_accounts">
  <EntityRelationshipType>OneToMany</EntityRelationshipType>
  <IntroducedVersion>5.0.0.0</IntroducedVersion>
  <ReferencingEntityName>Account</ReferencingEntityName>
  <ReferencedEntityName>Owner</ReferencedEntityName>
  <CascadeAssign>NoCascade</CascadeAssign>
  <CascadeDelete>NoCascade</CascadeDelete>
  <CascadeReparent>NoCascade</CascadeReparent>
  <CascadeShare>NoCascade</CascadeShare>
  <CascadeUnshare>NoCascade</CascadeUnshare>
  <ReferencingAttributeName>OwnerId</ReferencingAttributeName>
  <RelationshipDescription>
    <Descriptions>
      <Description description="Enter the user or team who is assigned to manage the record. This field is updated every time the record is assigned to a different user." languagecode="1033" />
    </Descriptions>
  </RelationshipDescription>
  <field name="ownerid" requiredlevel="systemrequired" imemode="auto" lookupstyle="single" lookupbrowse="0" lookuptypes="9">
    <IsSecured>0</IsSecured>
    <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid|RequiredForForm</DisplayMask>
    <IsAuditEnabled>1</IsAuditEnabled>
    <displaynames>
      <displayname description="Owner" languagecode="1033" />
    </displaynames>
  </field>
</EntityRelationship>

The two sections above filter the lookup types of the ownerid field to 9 (Team) and completely exclude 8 (User). After updating the sections above in each entity I was experiencing the problem with to match the same sections from the organisation that was created in CRM2013 (see below) and re-importing, the Look for control now contains both User and Team and allows selection of either.

<attribute PhysicalName="OwnerId">
  <Type>owner</Type>
  <Name>ownerid</Name>
  <LogicalName>ownerid</LogicalName>
  <RequiredLevel>systemrequired</RequiredLevel>
  <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid|RequiredForForm</DisplayMask>
  <ImeMode>auto</ImeMode>
  <ValidForReadApi>1</ValidForReadApi>
  <ValidForCreateApi>1</ValidForCreateApi>
  <IsCustomField>0</IsCustomField>
  <IsAuditEnabled>1</IsAuditEnabled>
  <IsSecured>0</IsSecured>
  <IntroducedVersion>5.0.0.0</IntroducedVersion>
  <LookupStyle>single</LookupStyle>
  <LookupTypes>
    <LookupType id="00000000-0000-0000-0000-000000000000">8</LookupType>
    <LookupType id="00000000-0000-0000-0000-000000000000">9</LookupType>
  </LookupTypes>
  <displaynames>
    <displayname description="Owner" languagecode="1033" />
  </displaynames>
  <Descriptions>
    <Description description="Enter the user or team who is assigned to manage the record. This field is updated every time the record is assigned to a different user." languagecode="1033" />
  </Descriptions>
</attribute>
<EntityRelationship Name="owner_accounts">
  <EntityRelationshipType>OneToMany</EntityRelationshipType>
  <IntroducedVersion>5.0.0.0</IntroducedVersion>
  <ReferencingEntityName>Account</ReferencingEntityName>
  <ReferencedEntityName>Owner</ReferencedEntityName>
  <CascadeAssign>NoCascade</CascadeAssign>
  <CascadeDelete>NoCascade</CascadeDelete>
  <CascadeReparent>NoCascade</CascadeReparent>
  <CascadeShare>NoCascade</CascadeShare>
  <CascadeUnshare>NoCascade</CascadeUnshare>
  <ReferencingAttributeName>OwnerId</ReferencingAttributeName>
  <RelationshipDescription>
    <Descriptions>
      <Description description="Enter the user or team who is assigned to manage the record. This field is updated every time the record is assigned to a different user." languagecode="1033" />
    </Descriptions>
  </RelationshipDescription>
  <field name="ownerid" requiredlevel="systemrequired" imemode="auto" lookupstyle="single" lookupbrowse="0" lookuptypes="8, 9">
    <IsSecured>0</IsSecured>
    <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid|RequiredForForm</DisplayMask>
    <IsAuditEnabled>1</IsAuditEnabled>
    <displaynames>
      <displayname description="Owner" languagecode="1033" />
    </displaynames>
  </field>
</EntityRelationship>

So, to summarise the fix for the issue:

  1. Create a solution with the entities that do not allow assignment to users.
  2. Export the solution.
  3. Extract the solution.
  4. Open customizations.xml.
  5. For each entity, replace the sections in Example 1.1 and Example 1.2 with the sections from Example 2.1 and Example 2.2 respectively.
  6. Zip the customisation files up again.
  7. Import the customisation zip file.
  8. Publish the customisations.
  9. Sit back and enjoy a Look for control that allows selection of users again:

Look for fixed

Missing CRM Explorer in Visual Studio 2012

Some people have reported that opening a CRM project in Visual Studio 2012 that has not been connected to a CRM organisation causes the CRM Explorer window not to load when the project is loaded as well as there being no option to show it from the VIEW menu and no option to Connect To Dynamics CRM Server in the TOOLS menu. There seem to be several causes of this behavior, the most common of which being the failure to set the CRM organisation when creating the project.

The problem seems to be that there is a missing section in the visual studio solution file. To fix the issue, follow the process below:

  1. Close the Visual Studio project
  2. Open the Visual Studio Solution file in a text editor such as notepad.
  3. Find the Global section and insert the highlighted section below at the beginning of the section

    Global
        GlobalSection(CRMSolutionProperties) = preSolution
            SolutionIsBoundToCRM = True
        EndGlobalSection
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
            Debug|Any CPU = Debug|Any CPU
            Debug|Mixed Platforms = Debug|Mixed Platforms
            Debug|x86 = Debug|x86
            Release|Any CPU = Release|Any CPU
            Release|Mixed Platforms = Release|Mixed Platforms
            Release|x86 = Release|x86
        EndGlobalSection
    EndGlobal
        
  4. Save the file.
  5. Open the Visual Studio project.
  6. If all has gone to plan, the Connect To Dynamics CRM Server dialog will now pop up and ask you to connect to your CRM Organisation.

Mimic multi-type lookup in CRM 2011

Certain CRM entities have lookup fields that allow more than one type (regardingobjectid on all activity types, customerid on case, etc). Unfortunately, we are not able to create custom fields allowing multiple types but we can mimic the user interface on the form using the following approach. To highlight the approach, I am going to create a custom Credit Note entity (new_creditnote) with a Customer field that can point to an Account or a Contact.

Step 1: Create N:1 relationships to each of the possible entity types.
Mimic custom multi-type lookup - Step 1

Step 2: Add all fields to the form. Give them all the same label.
Mimic custom multi-type lookup - Step 2

Step 3: Add code to the on load event of the form to override the available lookup types of the first of the multi-type controls.

var control = document.getElementById("new_accountid"); // Get the first control
control.setAttribute("lookuptypes", "1,2"); // Set the available lookup types
control.setAttribute("lookuptypenames", "account:1:Account,contact:2:Contact"); // Set the available lookup type names

Step 4: Add an on change event to each of the controls

Xrm.Page.data.entity.attributes.get("new_accountid").addOnChange(customer_onchange);
Xrm.Page.data.entity.attributes.get("new_contactid").addOnChange(customer_onchange);
function customer_onchange() {
  if (Xrm.Page.data.entity.attributes.get("new_accountid").getValue() != null){
    switch (Xrm.Page.data.entity.attributes.get("new_accountid").getValue()[0].type){
      case "1": // Account
        // Already the correct type
        break;
      case "2": // Contact
        Xrm.Page.data.entity.attributes.get("new_contactid").setValue(Xrm.Page.data.entity.attributes.get("new_accountid").getValue()); // Set the value of the new_contactid field to the value of the new_accountid field
        Xrm.Page.data.entity.attributes.get("new_accountid").setValue(null); // Set the value of the new_accountid field to null
        break;
    }
  }

  Xrm.Page.ui.controls.get("new_accountid").setVisible(Xrm.Page.data.entity.attributes.get("new_accountid").getValue() != null || Xrm.Page.data.entity.attributes.get("new_contactid").getValue() == null); // Set the visibility of the first control to visible if the selected customer is a contact or if all the other lookups are null
  Xrm.Page.ui.controls.get("new_contactid").setVisible(Xrm.Page.data.entity.attributes.get("new_contactid").getValue() != null); // Set the visibility of the other control to visible if the lookup's value is not null
}

Step 5: Add code to the onload event of the form to call the customer_onchange function to set the visibility of the customer controls.

customer_onchange();

And there you have it – multiple lookups that to the user, behave like a single multi-type lookup.

Example form showing a contact selected through the account lookup control.

Example form showing a contact selected through the account lookup control.

Unfortunately, this approach does not handle displaying only one field in lists. This could probably be achieved with a sneaky PostRetrieveMultipe plugin but I would not advise it.

Example list showing 2 credit notes. One has an account customer and the other has a contact customer.

Example list showing 2 credit notes. One has an account customer and the other has a contact customer.

The approach explained here is the bare minimum to get this kind of interface.

Where I have used this approach, I actually place each lookup into its own section and then set the visiblity of the section, not the individual control. This is because hiding a control actually leaves the space that the control was in on the form (like the difference between setting the display vs visibility CSS properties). Setting the visiblity of the section makes the control really appear to be one control.

The approach is not limited to 2 entity types as in the example above and can be expanded to cater for any number of different entity types.

See below for the full code listing:

function onload() {
  var control = document.getElementById("new_accountid"); // Get the first control
  control.setAttribute("lookuptypes", "1,2"); // Set the available lookup types
  control.setAttribute("lookuptypenames", "account:1:Account,contact:2:Contact"); // Set the available lookup type names

  customer_onchange();

  Xrm.Page.data.entity.attributes.get("new_accountid").addOnChange(customer_onchange);
  Xrm.Page.data.entity.attributes.get("new_contactid").addOnChange(customer_onchange);
}

function customer_onchange() {
  if (Xrm.Page.data.entity.attributes.get("new_accountid").getValue() != null){
    switch (Xrm.Page.data.entity.attributes.get("new_accountid").getValue()[0].type){
      case "1": // Account
        // Already the correct type
        break;
      case "2": // Contact
        Xrm.Page.data.entity.attributes.get("new_contactid").setValue(Xrm.Page.data.entity.attributes.get("new_accountid").getValue()); // Set the value of the new_contactid field to the value of the new_accountid field
        Xrm.Page.data.entity.attributes.get("new_accountid").setValue(null); // Set the value of the new_accountid field to null
        break;
    }
  }

  Xrm.Page.ui.controls.get("new_accountid").setVisible(Xrm.Page.data.entity.attributes.get("new_accountid").getValue() != null || Xrm.Page.data.entity.attributes.get("new_contactid").getValue() == null); // Set the visibility of the first control to visible if the selected customer is a contact or if all the other lookups are null
  Xrm.Page.ui.controls.get("new_contactid").setVisible(Xrm.Page.data.entity.attributes.get("new_contactid").getValue() != null); // Set the visibility of the other control to visible if the lookup's value is not null
}

CrmSvcUtil Option Set Enum Generator Extension

Following from my recent post, I thought I’d release the option set enumeration generator I have been using as well.

I am aware that several existing CrmSvcUtil extensions as well as the unextended CrmSvcUtil generate option set enumerations but this one has several key advantages:

  1. Enumerations are generated as members of the entity classes, not global options sets. This allows copies of global option sets per attribute that uses the global option set.
  2. Duplicate enumeration options are handled by appending the value of the item, not an arbitrary counter. Appending a counter can actually cause bugs that are incredibly difficult to track down if another duplicate instance is added in between two existing duplicates – effectively changing the key/value pair combination without updating dependent code.
  3. Enumerations are generated and consistently named for Option Set, Boolean, State and Status attribute types. Enumerations are referred to in the form “[Entity Schema Name].[Attribute Logical Name]Values” in all cases.

Download the CrmSvcUtil extension at codeplex: CrmSvcUtil Generate Option Set Enums.

To use the extension:

  1. Copy GenerateOptionSetEnums.dll into the directory containing CrmSvcUtil.exe
  2. Run CrmSvcUtil with the following parameters:
CrmSvcUtil.exe ^
/codecustomization:"GenerateOptionSetEnums.CodeCustomizationService, GenerateOptionSetEnums" ^
/codewriterfilter:"GenerateOptionSetEnums.FilteringService, GenerateOptionSetEnums" ^
/url:http://servername/orgname/XRMServices/2011/Organization.svc ^
/out:Enumerations.cs ^

The baseTypes parameter is optional and can be used to override the base types for entity classes which is needed if you have previously generated the wrapper classes with an extension (eg. Microsoft.Xrm.Client.CodeGeneration.CodeCustomization).

To use the generated option set enumerations:

Contact contact = new Contact();
contact.Attributes[Contact.firstnameAttribute] = "Michael";
contact.Attributes[Contact.lastnameAttribute] = "Palmer";
contact.EducationCode = new OptionSetValue((int)Contact.gendercodeValues.Male);

CrmSvcUtil Attribute Constant Generator Extension

A recent question on the CRM MSDN forum made me realise that as far as I can see, no-one has yet created a CrmSvcUtil extension to generate attribute constants. So I did.

Download the CrmSvcUtil extension at codeplex: CrmSvcUtil Generate Attribute Constants.

To use the extension:

  1. Copy GenerateAttributeConstants.dll into the directory containing CrmSvcUtil.exe
  2. Run CrmSvcUtil with the following parameters:
CrmSvcUtil.exe ^
/codecustomization:&quot;GenerateAttributeConstants.CodeCustomizationService, GenerateAttributeConstants&quot; ^
/codewriterfilter:&quot;GenerateAttributeConstants.FilteringService, GenerateAttributeConstants&quot; ^
/url:http://servername/orgname/XRMServices/2011/Organization.svc ^
/out:Attributes.cs

The baseTypes parameter is optional and can be used to override the base types for entity classes which is needed if you have previously generated the wrapper classes with an extension (eg. Microsoft.Xrm.Client.CodeGeneration.CodeCustomization).

To use the generated attribute constants:

Contact contact = new Contact();
contact.Attributes[Contact.firstnameAttribute] = &quot;Michael&quot;;
contact.Attributes[Contact.lastnameAttribute] = &quot;Palmer&quot;;

CRM 2011 Developer Tools target location for Visual Studio 2010 vs Visual Studio 2012

The path for the targets is different for the Developer Toolkit for Visual Studio 2010 and Visual Studio 2012. This can cause errors when loading the Visual Studio project in the form of the error

The imported project “C:\Program Files (x86)\MSBuild\Microsoft\CRM\Microsoft.CrmDeveloperTools.CrmClient.targets” was not found. Confirm that the path in the declaration is correct, and that the file exists on disk.

or when attempting to deploy the CrmPackage project from Visual Studio 2010 as the error

Error connecting to CRM Server. [A]Microsoft.CrmDeveloperTools.CrmClient.Entities.Solution cannot be cast to [B]Microsoft.CrmDeveloperTools.CrmClient.Entities.Solution. Type A originates from ‘Microsoft.CrmDeveloperTools.CrmClient, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’ in the context ‘LoadFrom’ at location ‘C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft\Dynamics CRM 2011 Developer Tools\1.0\Microsoft.CrmDeveloperTools.CrmClient.dll’. Type B originates from ‘Microsoft.CrmDeveloperTools, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’ in the context ‘LoadFrom’ at location ‘C:\Program Files (x86)\MSBuild\Microsoft\CRM\Microsoft.CrmDeveloperTools.dll’.

or when attempting to deploy the CrmPackage project from Visual Studio 2012 as the error

Error connecting to CRM Server. [A]Microsoft.CrmDeveloperTools.CrmClient.Entities.Solution cannot be cast to [B]Microsoft.CrmDeveloperTools.CrmClient.Entities.Solution. Type A originates from ‘Microsoft.CrmDeveloperTools, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’ in the context ‘LoadFrom’ at location ‘C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\Extensions\Microsoft\Dynamics CRM 2011 Developer Tools\1.0\Microsoft.CrmDeveloperTools.dll’. Type B originates from ‘Microsoft.CrmDeveloperTools.CrmClient, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’ in the context ‘LoadFrom’ at location ‘C:\Program Files (x86)\MSBuild\Microsoft\CRM\Microsoft.CrmDeveloperTools.CrmClient.dll’.

depending on the combination of Visual Studio version, installed Developer Toolkit version and previously installed Developer Toolkit versions.

In order to resolve any of these, make sure that the correct targets are referenced in the CrmPackage.csproj file:

  1. In Visual Studio, right click the CrmPackage project and click Unload Project.
  2. Right click on the CrmPackage project and click edit CrmPackage.csproj.
  3. Find the line starting with “<Import Project="$(MSBuildExtensionsPath32)\Microsoft\CRM\" and replace it with the correct path from the list below.
  4. Save and close the file.
  5. Right click on the CrmPackage project and click Reload Project.
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\CRM\Microsoft.CrmDeveloperTools.CrmClient.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\CRM\Microsoft.CrmDeveloperTools.12.targets" />