Wednesday, May 4, 2011

How to: Add an Attribute to an Entity Using Jscript or .NET Using the Microsoft Dynamics CRM 2011 SDK

The other day I showed you how to create a new entity using the SDK here:
http://mileyja.blogspot.com/2011/05/how-to-create-custom-entity-using.html

Now, here is a quick demo on how to utilize the CreateAttribute message of the CRM 2011 Organization service in both C# and Jscript.   This illustration shows you how to add a custom attribute to an entity using SOAP (JScript) or C# (.NET) with the CreateAttribute message.
First in C#:

 int _languageCode = 1033;

         // Create a string attribute
         StringAttributeMetadata stringAttribute = new StringAttributeMetadata
         {
             // Set base properties
             SchemaName = "new_string",
             DisplayName = new Label("Sample String", _languageCode),
             RequiredLevel = new AttributeRequiredLevelManagedProperty(AttributeRequiredLevel.None),
             Description = new Label("String Attribute", _languageCode),
             // Set extended properties
             MaxLength = 100
         };


         // Create the request.
         CreateAttributeRequest createAttributeRequest = new CreateAttributeRequest
         {
             EntityName = Contact.EntityLogicalName,
             Attribute = stringAttribute
         };

         // Execute the request.
         service.Execute(createAttributeRequest);

This is in part taken from an MSDN article which also illustrates how to perform this addition in .NET using all kinds of different attribute types.  It is really a good article for how to do this in .NET.
http://msdn.microsoft.com/en-us/library/microsoft.xrm.sdk.messages.createattributerequest.aspx

Now here is the Jscript nicely formatted by the CRM 2011 SOAP formatter. Available at: http://crm2011soap.codeplex.com/

Now in Jscript:

function runme()
{
    SDK.SAMPLES.CreateAttributeRequest();
}

if (typeof (SDK) == "undefined")
   { SDK = { __namespace: true }; }
       //This will establish a more unique namespace for functions in this library. This will reduce the 
       // potential for functions to be overwritten due to a duplicate name when the library is loaded.
       SDK.SAMPLES = {
           _getServerUrl: function () {
               ///<summary>
               /// Returns the URL for the SOAP endpoint using the context information available in the form
               /// or HTML Web resource.
               ///</summary>
               var OrgServicePath = "/XRMServices/2011/Organization.svc/web";
               var serverUrl = "";
               if (typeof GetGlobalContext == "function") {
                   var context = GetGlobalContext();
                   serverUrl = context.getServerUrl();
               }
               else {
                   if (typeof Xrm.Page.context == "object") {
                         serverUrl = Xrm.Page.context.getServerUrl();
                   }
                   else
                   { throw new Error("Unable to access the server URL"); }
                   }
                  if (serverUrl.match(/\/$/)) {
                       serverUrl = serverUrl.substring(0, serverUrl.length - 1);
                   } 
                   return serverUrl + OrgServicePath;
               }, 
           CreateAttributeRequest: function () {
               var requestMain = ""
               requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
               requestMain += "  <s:Body>";
               requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
               requestMain += "      <request i:type=\"a:CreateAttributeRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\">";
               requestMain += "        <a:Parameters xmlns:b=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <b:key>Attribute</b:key>";
               requestMain += "            <b:value i:type=\"c:StringAttributeMetadata\" xmlns:c=\"http://schemas.microsoft.com/xrm/2011/Metadata\">";
               requestMain += "              <c:MetadataId i:nil=\"true\" />";
               requestMain += "              <c:AttributeOf i:nil=\"true\" />";
               requestMain += "              <c:AttributeType>String</c:AttributeType>";
               requestMain += "              <c:CanBeSecuredForCreate i:nil=\"true\" />";
               requestMain += "              <c:CanBeSecuredForRead i:nil=\"true\" />";
               requestMain += "              <c:CanBeSecuredForUpdate i:nil=\"true\" />";
               requestMain += "              <c:CanModifyAdditionalSettings i:nil=\"true\" />";
               requestMain += "              <c:ColumnNumber i:nil=\"true\" />";
               requestMain += "              <c:DeprecatedVersion i:nil=\"true\" />";
               requestMain += "              <c:Description>";
               requestMain += "                <a:LocalizedLabels>";
               requestMain += "                  <a:LocalizedLabel>";
               requestMain += "                    <a:IsManaged i:nil=\"true\" />";
               requestMain += "                    <a:Label>String Attribute</a:Label>";
               requestMain += "                    <a:LanguageCode>1033</a:LanguageCode>";
               requestMain += "                  </a:LocalizedLabel>";
               requestMain += "                </a:LocalizedLabels>";
               requestMain += "                <a:UserLocalizedLabel i:nil=\"true\" />";
               requestMain += "              </c:Description>";
               requestMain += "              <c:DisplayName>";
               requestMain += "                <a:LocalizedLabels>";
               requestMain += "                  <a:LocalizedLabel>";
               requestMain += "                    <a:IsManaged i:nil=\"true\" />";
               requestMain += "                    <a:Label>Sample String</a:Label>";
               requestMain += "                    <a:LanguageCode>1033</a:LanguageCode>";
               requestMain += "                  </a:LocalizedLabel>";
               requestMain += "                </a:LocalizedLabels>";
               requestMain += "                <a:UserLocalizedLabel i:nil=\"true\" />";
               requestMain += "              </c:DisplayName>";
               requestMain += "              <c:EntityLogicalName i:nil=\"true\" />";
               requestMain += "              <c:IsAuditEnabled i:nil=\"true\" />";
               requestMain += "              <c:IsCustomAttribute i:nil=\"true\" />";
               requestMain += "              <c:IsCustomizable i:nil=\"true\" />";
               requestMain += "              <c:IsManaged i:nil=\"true\" />";
               requestMain += "              <c:IsPrimaryId i:nil=\"true\" />";
               requestMain += "              <c:IsPrimaryName i:nil=\"true\" />";
               requestMain += "              <c:IsRenameable i:nil=\"true\" />";
               requestMain += "              <c:IsSecured i:nil=\"true\" />";
               requestMain += "              <c:IsValidForAdvancedFind i:nil=\"true\" />";
               requestMain += "              <c:IsValidForCreate i:nil=\"true\" />";
               requestMain += "              <c:IsValidForRead i:nil=\"true\" />";
               requestMain += "              <c:IsValidForUpdate i:nil=\"true\" />";
               requestMain += "              <c:LinkedAttributeId i:nil=\"true\" />";
               requestMain += "              <c:LogicalName i:nil=\"true\" />";
               requestMain += "              <c:RequiredLevel>";
               requestMain += "                <a:CanBeChanged>true</a:CanBeChanged>";
               requestMain += "                <a:ManagedPropertyLogicalName i:nil=\"true\" />";
               requestMain += "                <a:Value>None</a:Value>";
               requestMain += "              </c:RequiredLevel>";
               requestMain += "              <c:SchemaName>new_string</c:SchemaName>";
               requestMain += "              <c:Format i:nil=\"true\" />";
               requestMain += "              <c:ImeMode i:nil=\"true\" />";
               requestMain += "              <c:MaxLength>100</c:MaxLength>";
               requestMain += "              <c:YomiOf i:nil=\"true\" />";
               requestMain += "            </b:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <b:key>EntityName</b:key>";
               requestMain += "            <b:value i:type=\"c:string\" xmlns:c=\"http://www.w3.org/2001/XMLSchema\">contact</b:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "        </a:Parameters>";
               requestMain += "        <a:RequestId i:nil=\"true\" />";
               requestMain += "        <a:RequestName>CreateAttribute</a:RequestName>";
               requestMain += "      </request>";
               requestMain += "    </Execute>";
               requestMain += "  </s:Body>";
               requestMain += "</s:Envelope>";
               var req = new XMLHttpRequest();
               req.open("POST", SDK.SAMPLES._getServerUrl(), true)
               // Responses will return XML. It isn't possible to return JSON.
               req.setRequestHeader("Accept", "application/xml, text/xml, */*");
               req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
               req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
               var successCallback = null;
               var errorCallback = null;
               req.onreadystatechange = function () { SDK.SAMPLES.CreateAttributeResponse(req, successCallback, errorCallback); };
               req.send(requestMain);
           },
       CreateAttributeResponse: function (req, successCallback, errorCallback) {
               ///<summary>
               /// Recieves the assign response
               ///</summary>
               ///<param name="req" Type="XMLHttpRequest">
               /// The XMLHttpRequest response
               ///</param>
               ///<param name="successCallback" Type="Function">
               /// The function to perform when an successfult response is returned.
               /// For this message no data is returned so a success callback is not really necessary.
               ///</param>
               ///<param name="errorCallback" Type="Function">
               /// The function to perform when an error is returned.
               /// This function accepts a JScript error returned by the _getError function
               ///</param>
               if (req.readyState == 4) {
               if (req.status == 200) {
               if (successCallback != null)
               { successCallback(); }
               }
               else {
                   errorCallback(SDK.SAMPLES._getError(req.responseXML));
               }
           }
       },
       _getError: function (faultXml) {
           ///<summary>
           /// Parses the WCF fault returned in the event of an error.
           ///</summary>
           ///<param name="faultXml" Type="XML">
           /// The responseXML property of the XMLHttpRequest response.
           ///</param>
           var errorMessage = "Unknown Error (Unable to parse the fault)";
           if (typeof faultXml == "object") {
               try {
                   var bodyNode = faultXml.firstChild.firstChild;
                   //Retrieve the fault node
                   for (var i = 0; i < bodyNode.childNodes.length; i++) {
                       var node = bodyNode.childNodes[i];
                       //NOTE: This comparison does not handle the case where the XML namespace changes
                       if ("s:Fault" == node.nodeName) {
                       for (var j = 0; j < node.childNodes.length; j++) {
                           var faultStringNode = node.childNodes[j];
                           if ("faultstring" == faultStringNode.nodeName) {
                               errorMessage = faultStringNode.text;
                               break;
                           }
                       }
                       break;
                   }
               }
           }
           catch (e) { };
        }
        return new Error(errorMessage);
     },
 __namespace: true
};




To understand how to parse the response please review my post on using the DOM parser.
Now you can call the runme or the SDK.SAMPLES.CreateAttributeRequest function from your form jscript handler.
Thats all there is to it!

You may also want to publish your custom attribute after creation so you may want to read my article on publishing an entity from jscript.
http://mileyja.blogspot.com/2011/03/publish-entity-in-jscript-in-crm-2011.html


I hope this helps!

6 comments:

  1. Jamie, Can you use this to add attributes to system entities not available through the customizer UI (like the price list for example)?
    Graham, Metisc

    ReplyDelete
  2. I might be misunderstanding you here. Price List is available in the UI customizer in the GUI. You can use this method to add attributes to all customizable entities. I would refer to the metadata browser solution included with the SDK to determine what entities are customizable and which ones aren't.

    ReplyDelete
  3. Hello Jamie,
    I am encountering a very strange error, The _getServerUrl function does not recognize Xrm object in first place that means I can not get the server url hence can not execute any request at all with javascript out of any of these examples. It's quite frustrating as I have simply applied the same script as in example.

    SDK.MetaData={_ServerUrl:
    function(){
    var a,b="Context to retrieve the Server URL is not available.";
    if(typeof GetGlobalContext!="undefined")
    a=GetGlobalContext().getServerUrl();
    else if(typeof Xrm!="undefined")a=Xrm.Page.context.getServerUrl();
    else return new Error(b);
    if(a.match(/\/$/))a=a.substring(0,a.length-1);
    return a+"/XRMServices/2011/Organization.svc/web"
    }

    ReplyDelete
  4. Hi Jamie,
    This example shows creating attribute with a Async request, can we do the same using a Sync request ?

    ReplyDelete
    Replies
    1. Here you go.
      http://mileyja.blogspot.com/2011/07/using-jscript-to-access-soap-web.html

      Delete
    2. Hi Jamie,

      Can you also share the code for updateattributeresuest.

      Thank you in Advance.

      Delete