In the previous post I demonstrated an implementation of the Customization Aware Web Resource approach using SOAP to retrieve metadata. In this post, I’ll demonstrate a similar implementation, this time using Web API.
You can download an unmanaged solution containing the working sample code presented in this post. Note that this is a different solution version from the one made available in the previous post.
Why should you use Web API instead of SOAP? Here are some of the reasons which importance rise as your implementation scales up:
- Reducing network traffic: compared to SOAP, REST requests are lighter and require less network resources
- Reduce maintenance efforts: REST based code require considerably less code than SOAP based code. Less code require less maintenance
- Robustness: even when using a library like Sdk.Soap.js or SDK.MetaData.js to abstract SOAP plumbing, you depend on an external library you have to drag along, maintain, minify etc. With Web API, you can simply use native XMLHttpRequest object to submit requests. Less moving parts means a more robust solution
- You would prefer code with an attitude:

Bits & Bytes
Few details to note regarding Web API metadata queries:
- The target table used is EntityDefinitions
- Each entity and attribute has a unique metadata id stored in a property named MetadataId
- Retrieving metadata using attribute logical name will only return general properties (which all attribute has: display name, requirement level, description etc.)
- Retrieving type specific attributes (OptionSet options, number values range, text format etc.) require a query involving the attribute MetadataId value, sometimes with a proper type cast. It means you have to know each attribute MetadataId in advance if you wish to perform only one query per attribute. Fortunately, entity and attribute MetadataId values are static between Organizations, so once you found the required MetadataId value, you can safely use it as a constant in your queries, even when importing code via Solution into other Organizations.
Here are some sample queries and responses:
The following query require entity and attribute logical name but will return only general characteristics for the specified attribute:
…/EntityDefinitions?$select=MetadataId&$filter=SchemaName eq ‘Lead‘&$expand=Attributes($select=LogicalName,SchemaName,Description,RequiredLevel,DisplayName;$filter=LogicalName eq ‘firstname‘)
The response contains the Lead entity MetadataId, the firstname attribute MetadataId and the requested general attributes:
{
"@odata.context":"https://thekk.crm.dynamics.com/api/data/v8.0/$metadata#EntityDefinitions(MetadataId,Attributes,Attributes(LogicalName,SchemaName,Description,RequiredLevel,DisplayName))","value":[
{
"MetadataId":"dc6574cb-92ce-446c-a5d6-885a75107d52","Attributes@odata.context":"https://thekk.crm.dynamics.com/api/data/v8.0/$metadata#EntityDefinitions(dc6574cb-92ce-446c-a5d6-885a75107d52)/Attributes(LogicalName,SchemaName,Description,RequiredLevel,DisplayName)","Attributes":[
{
"@odata.type":"#Microsoft.Dynamics.CRM.StringAttributeMetadata","LogicalName":"firstname","SchemaName":"FirstName","Description":{
"LocalizedLabels":[
{
"Label":"Type the first name of the primary contact for the lead to make sure the prospect is addressed correctly in sales calls, email, and marketing campaigns.","LanguageCode":1033,"IsManaged":true,"MetadataId":"8c4901bf-2241-db11-898a-0007e9e17ebd","HasChanged":null
}
],"UserLocalizedLabel":{
"Label":"Type the first name of the primary contact for the lead to make sure the prospect is addressed correctly in sales calls, email, and marketing campaigns.","LanguageCode":1033,"IsManaged":true,"MetadataId":"8c4901bf-2241-db11-898a-0007e9e17ebd","HasChanged":null
}
},"RequiredLevel":{
"Value":"Recommended","CanBeChanged":true,"ManagedPropertyLogicalName":"canmodifyrequirementlevelsettings"
},"DisplayName":{
"LocalizedLabels":[
{
"Label":"First Name","LanguageCode":1033,"IsManaged":true,"MetadataId":"8b4901bf-2241-db11-898a-0007e9e17ebd","HasChanged":null
}
],"UserLocalizedLabel":{
"Label":"First Name","LanguageCode":1033,"IsManaged":true,"MetadataId":"8b4901bf-2241-db11-898a-0007e9e17ebd","HasChanged":null
}
},"MetadataId":"4ed9d959-5425-468c-bf1c-98a8b125ab31"
}
]
}
]
}
The following query uses the entity and attribute MetadataId values to retrieve type specific characteristics
…/EntityDefinitions(dc6574cb-92ce-446c-a5d6-885a75107d52)/Attributes(4ed9d959-5425-468c-bf1c-98a8b125ab31)
The response contains the text format which was can not be retrieved by the previous query :
{
"@odata.context":https://thekk.crm.dynamics.com/api/data/v8.0/$metadata#EntityDefinitions(dc6574cb-92ce-446c-a5d6-885a75107d52)/Attributes/$entity,
"@odata.type":"#Microsoft.Dynamics.CRM.StringAttributeMetadata","Format":"Text","FormatName":{
"Value":"Text"
},"ImeMode":"Active","MaxLength":50,"YomiOf":null,"IsLocalizable":false,"FormulaDefinition":null,"SourceTypeMask":0,"AttributeOf":null,"AttributeType":"String","AttributeTypeName":{
"Value":"StringType"
},"ColumnNumber":19,"Description":{
"LocalizedLabels":[
{
"Label":"Type the first name of the primary contact for the lead to make sure the prospect is addressed correctly in sales calls, email, and marketing campaigns.","LanguageCode":1033,"IsManaged":true,"MetadataId":"8c4901bf-2241-db11-898a-0007e9e17ebd","HasChanged":null
}
],"UserLocalizedLabel":{
"Label":"Type the first name of the primary contact for the lead to make sure the prospect is addressed correctly in sales calls, email, and marketing campaigns.","LanguageCode":1033,"IsManaged":true,"MetadataId":"8c4901bf-2241-db11-898a-0007e9e17ebd","HasChanged":null
}
},"DisplayName":{
"LocalizedLabels":[
{
"Label":"First Name","LanguageCode":1033,"IsManaged":true,"MetadataId":"8b4901bf-2241-db11-898a-0007e9e17ebd","HasChanged":null
}
],"UserLocalizedLabel":{
"Label":"First Name","LanguageCode":1033,"IsManaged":true,"MetadataId":"8b4901bf-2241-db11-898a-0007e9e17ebd","HasChanged":null
}
},"DeprecatedVersion":null,"IntroducedVersion":"5.0.0.0","EntityLogicalName":"lead","IsAuditEnabled":{
"Value":true,"CanBeChanged":true,"ManagedPropertyLogicalName":"canmodifyauditsettings"
},"IsCustomAttribute":false,"IsPrimaryId":false,"IsPrimaryName":false,"IsValidForCreate":true,"IsValidForRead":true,"IsValidForUpdate":true,"CanBeSecuredForRead":false,"CanBeSecuredForCreate":false,"CanBeSecuredForUpdate":false,"IsSecured":false,"IsRetrievable":false,"IsFilterable":false,"IsSearchable":true,"IsManaged":true,"IsGlobalFilterEnabled":{
"Value":false,"CanBeChanged":true,"ManagedPropertyLogicalName":"canmodifyglobalfiltersettings"
},"IsSortableEnabled":{
"Value":false,"CanBeChanged":true,"ManagedPropertyLogicalName":"canmodifyissortablesettings"
},"LinkedAttributeId":null,"LogicalName":"firstname","IsCustomizable":{
"Value":true,"CanBeChanged":false,"ManagedPropertyLogicalName":"iscustomizable"
},"IsRenameable":{
"Value":true,"CanBeChanged":false,"ManagedPropertyLogicalName":"isrenameable"
},"IsValidForAdvancedFind":{
"Value":true,"CanBeChanged":true,"ManagedPropertyLogicalName":"canmodifysearchsettings"
},"RequiredLevel":{
"Value":"Recommended","CanBeChanged":true,"ManagedPropertyLogicalName":"canmodifyrequirementlevelsettings"
},"CanModifyAdditionalSettings":{
"Value":true,"CanBeChanged":false,"ManagedPropertyLogicalName":"canmodifyadditionalsettings"
},"SchemaName":"FirstName","IsLogical":false,"InheritsFrom":null,"SourceType":0,"MetadataId":"4ed9d959-5425-468c-bf1c-98a8b125ab31","HasChanged":null
}
The only exception is related to OptionSet control options collection which require an expanded query along with a proper type cast:
…/v8.0/EntityDefinitions(dc6574cb-92ce-446c-a5d6-885a75107d52)/Attributes(49afb6d2-986f-4ccc-b07d-a38de518b8d1)/Microsoft.Dynamics.CRM.PicklistAttributeMetadata?$expand=OptionSet
As shown in the HTML below (contained in dyn_CreateLeadWebAPI.htm), I made some design changes to support both Logical name and metadataId.
Adding just the logicalname attribute to any input control will cause the code to perform two queries to retrieve full metadata details. Adding the optional metadataid attribute will perform one query. Not adding any of the attributes will cause the code to ignore the control.

The two interesting functions here are retrieveMetadataByLogicalName and retrieveMetadataByMetadataId which actually query metadata using Web API.
Note how simple it is to construct a query, submit it and parse the response.


Conclusion
With minor effort, the sample code in the previous or current post can be turned into a JS library which will quietly apply the CAWR approach to your Web Resources just by referencing it.
Setting HTML controls properties to match MSCRM attribute logical name or metadataid will enable the code to automatically retrieve and apply metadata.
If you have any ideas or suggestions regarding the CAWR approach, share them here.