文章《Dynamics CRM 2011编程系列(53):客户端实体序列化工具--MagicBox 》分享了一款序列化实体为JSON字符串的工具,该文章仅仅介绍了如何使用它来生成JSON字符串并没有介绍它的实现原理。本文就来描述下该工具的设计思路以及实现原理吧。
在Dynamics CRM 2011系统中,如果我们想在客户端对记录进行CRUD操作,就现在来说有两种方式:其一为调用REST端点,另一为用JS构造与CRUD对应SOAP报文并发送给服务端。这两种方法我之前都介绍过,今天我们要谈的是第一种方法。
Dynamics CRM 2011中的REST端点是ROA概念的解决方案,ROA即为面向资源。ROA指出互联网上的所有资源都可以用一种格式来进行描述,无论是获取它还是对它进行修改,这种格式就是我们都熟悉的---URI,因此OData就此诞生了。我们看几个OData查询URL吧,这些操作中除Read操作是以GET方式请求请求服务器,其他的均为POST方式。MagicBox生成的JSON字符串主要用在Create和Update操作上。:
Create var path="/GH2011/XRMServices/2011/OrganizationData.svc/ContactSet"; Read var query="/GH2011/XRMServices/2011/OrganizationData.svc/AccountSet?$top=5&$orderby=CreatedOn desc"; Update(需要设置请求报头 X-HTTP-Method的值为MERGE) var uptUrl="/GH2011/XRMServices/2011/OrganizationData.svc/OpportunitySet(guid'{366BE04B-47D9-E011-9C38-000C2956A039}')"; Delete(需要设置请求报头 X-HTTP-Method的值为DELETE) var delUrl="/GH2011/XRMServices/2011/OrganizationData.svc/ContactSet(guid'{366BE04B-47D9-E011-9C38-000C2956A039}')";
Click GO button--〉GenerateEntity()--> GenerateSerializationString()
--> GetAttributeMetaData()
-->GetEntityMetadata()
MagicBox中的方法GetEntityMetadata()会根据我们提供的实体名称获取相应的实体元数据,我们在这些元数据中提取我们需要的属性元数据。因为REST端点接受Create/Update提交的JSON字符串是区分大小写的,它是根据属性的SchemaName来进行匹配的。在我们开发的时候我们并不能保证所有的实体的SchemaName都是小写或都是大写,所以很容易出现拼写错误,因此我们很有必要把这个容易出错的环节自动化起来.。
获取实体元数据(只获取当前实体的属性类型的元数据)
private EntityMetadata GetEntityMetadata(string entityName) { if(EntityMetaDataTable.ContainsKey(entityName)==false) { IOrganizationService svc = CommonLibrary.CrmSvcHelper.CreateService(); RetrieveEntityRequest request = new RetrieveEntityRequest(); request.EntityFilters = EntityFilters.Attributes; request.LogicalName = entityName; RetrieveEntityResponse response = svc.Execute(request) as RetrieveEntityResponse; if (response != null && response.EntityMetadata != null) { EntityMetaDataTable.Add(entityName, response.EntityMetadata); } else { return null; } } return EntityMetaDataTable[entityName]; }
光有实体的元数据还是不行的,现在我们还缺少一个对应关系:服务端实体的属性结构和客户端JSON字符串的对应关系。我们可以通过Fiddler抓包来获取这个关系,看官们有兴趣可以自己尝试下,如下是我抓取的对应关系:
{ 'ms_currencytype':{'Value':'1.0000'} --Money Type ,'StateCode':{'Value':'0'} --State Type ,'ms_stringtype':'string type' --string type ,'ms_optionsettype':{'Value':'2'} --picklist type ,'StatusCode':{'Value':'1'} --state reason type ,'ms_floattype':'12.3' --float type ,'OwnerId':{'LogicalName':'systemuser','Id':'f703d401-5a01-e211-90cd-00155db7bf1b'} --owner type ,'ms_decimaltype':'12.5000000000' --decimal type ,'ms_booleantype':'True' --boolean type ,'ms_datetimetype':'4/17/2013 2:34:14 AM' --datetime type ,'ms_lookup':{'LogicalName':'account','Id':'f88a7ff6-74a6-e211-b2be-00155d1ce815'} --lookup type ,'ms_numbertype':'123' --number type }
既然有了这个关系,咱们就可以制作一个解析器了:
private string GenerateSerializationString(Entity entity) { StringBuilder result = new StringBuilder(); string tmp=string.Empty; Microsoft.Xrm.Sdk.AttributeCollection attributes = entity.Attributes; AttributeMetadata attrMetadata=null; result.Append("{"); foreach (var item in attributes) { attrMetadata=GetAttributeMetadata(entity.LogicalName,item.Key); switch (attrMetadata.AttributeType.Value) { case AttributeTypeCode.BigInt: result.Append(string.Format("'{0}':'{1}'", attrMetadata.SchemaName, item.Value)); result.Append(","); break; case AttributeTypeCode.Boolean: result.Append(string.Format("'{0}':'{1}'", attrMetadata.SchemaName, item.Value)); result.Append(","); break; case AttributeTypeCode.CalendarRules: break; case AttributeTypeCode.Customer: break; case AttributeTypeCode.DateTime: result.Append(string.Format("'{0}':'{1}'", attrMetadata.SchemaName, item.Value)); result.Append(","); break; case AttributeTypeCode.Decimal: result.Append(string.Format("'{0}':'{1}'", attrMetadata.SchemaName, item.Value)); result.Append(","); break; case AttributeTypeCode.Double: result.Append(string.Format("'{0}':'{1}'", attrMetadata.SchemaName, item.Value)); result.Append(","); break; case AttributeTypeCode.EntityName: break; case AttributeTypeCode.Integer: result.Append(string.Format("'{0}':'{1}'", attrMetadata.SchemaName, item.Value)); result.Append(","); break; case AttributeTypeCode.Lookup: result.Append(string.Format("'{0}':{{'LogicalName':'{1}','Id':'{2}'}}", attrMetadata.SchemaName, ((EntityReference)item.Value).LogicalName, ((EntityReference)item.Value).Id)); result.Append(","); break; case AttributeTypeCode.ManagedProperty: break; case AttributeTypeCode.Memo: break; case AttributeTypeCode.Money: result.Append(string.Format("'{0}':{{'Value':'{1}'}}", attrMetadata.SchemaName, ((Money)item.Value).Value)); result.Append(","); break; case AttributeTypeCode.Owner: result.Append(string.Format("'{0}':{{'LogicalName':'{1}','Id':'{2}'}}", attrMetadata.SchemaName, ((EntityReference)item.Value).LogicalName, ((EntityReference)item.Value).Id)); result.Append(","); break; case AttributeTypeCode.PartyList: break; case AttributeTypeCode.Picklist: result.Append(string.Format("'{0}':{{'Value':'{1}'}}", attrMetadata.SchemaName, ((OptionSetValue)item.Value).Value)); result.Append(","); break; case AttributeTypeCode.State: result.Append(string.Format("'{0}':{{'Value':'{1}'}}", attrMetadata.SchemaName, ((OptionSetValue)item.Value).Value)); result.Append(","); break; case AttributeTypeCode.Status: result.Append(string.Format("'{0}':{{'Value':'{1}'}}", attrMetadata.SchemaName, ((OptionSetValue)item.Value).Value)); result.Append(","); break; case AttributeTypeCode.String: result.Append(string.Format("'{0}':'{1}'",attrMetadata.SchemaName,item.Value)); result.Append(","); break; case AttributeTypeCode.Uniqueidentifier: result.Append(string.Format("'{0}':'{1}'", attrMetadata.SchemaName, item.Value)); result.Append(","); break; case AttributeTypeCode.Virtual: break; default: break; } } tmp=result.ToString(); return tmp.Substring(0, tmp.Length - 1) + "}";// remove the last , }
现在JSON字符串的处理工作完成了,但我们需要向这个处理器进行相应的输入。在这个版本的MagicBox中,我没打算把它实现的很友好,毕竟它只是给咱们开发人使用的。所以就以填充代码的方式来传递需要解析的实体记录了:
private Entity GenerateEntity() { //Type your codes to retrieve a entity which you want to use. //Start IOrganizationService svc = CommonLibrary.CrmSvcHelper.CreateService(); Guid entityId = Guid.Parse("{204B9D72-20A7-E211-B2BE-00155D1CE815}"); return svc.Retrieve("ms_fighter", entityId, new Microsoft.Xrm.Sdk.Query.ColumnSet(true)); //End }
在方法GenerateSerializationString()中有几个属性是没有被处理的。因为我没有找到客户描述它们的JSON影射关系,各位如果知道它们的映射关系可以联系我。