使用自定义数据源配置虚拟实体
Posted luoyong0201
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用自定义数据源配置虚拟实体相关的知识,希望对你有一定的参考价值。
我是微软Dynamics 365 & Power Platform方面的工程师/顾问罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面的微软最有价值专家(Microsoft MVP),欢迎关注我的微信公众号 MSFTDynamics365erLuoYong ,回复431或者20201221可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!
前面的博文 介绍并配置Dynamics 365中的虚拟实体Virtual Entity 讲了通过配置的方法来使用虚拟实体,今天我们来讲需要编码才能使用的虚拟实体。主要参考的官方文档包括 Custom virtual entity data providers 和 Sample: Generic virtual entity data provider plug-in 。
这首先需要创建自定义数据提供方(custom data provider),分为两种自定义数据提供方,分别是通用的自定义数据提供方(Generic)和特定的自定义数据提供方(Targeted),我们今天演示下通用的自定义数据提供方来为虚拟实体提供数据。
和创建插件程序集差不多,需要创建一个框架为.NET Framework 4.6.2 的 Class Library (.NET Framework) 项目。我这里将自动生成Class1.cs文件删除。
首先通过Nuget添加对 Microsoft.CrmSdk.Data 的引用。
安装时候会自动安装Microsoft.CrmSdk.CoreAssemblies ,安装完成后界面如下。
像开发插件一样,需要为该程序集添加签名。
然后一般至少需要添加两个类,分别为RetrieveMutiple 和 Retrieve消息准备,我这里使用的示例代码如下。值得注意的是虚拟实体的主键,比如我这里是 ly_testvirtualentityid 一定要赋值,否则会出错。
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Data.Exceptions;
using Microsoft.Xrm.Sdk.Extensions;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
namespace LuoYongCustomDataProvider
{
public class RetrieveMultiple : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService orgSvc = factory.CreateOrganizationService(context.UserId);
try
{
QueryExpression query = context.InputParameterOrDefault<QueryExpression>("Query");
var filter = query.Criteria;
//下面的代码可以用来查看查询使用的QueryExpression转换后的FetchXml
QueryExpressionToFetchXmlRequest req = new QueryExpressionToFetchXmlRequest();
req.Query = query;
QueryExpressionToFetchXmlResponse resp = (QueryExpressionToFetchXmlResponse)orgSvc.Execute(req);
tracingService.Trace($"Query expression to fetchxml={resp.FetchXml}");
var transferedFetchXml = resp.FetchXml.Replace("ly_testvirtualentityid", "accountid");
transferedFetchXml = transferedFetchXml.Replace("ly_testvirtualentity","account");
transferedFetchXml = transferedFetchXml.Replace("ly_name", "name");
transferedFetchXml = transferedFetchXml.Replace("ly_createdon", "createdon");
transferedFetchXml = transferedFetchXml.Replace("ly_website", "websiteurl");
tracingService.Trace($"Transfered Fetchxml={transferedFetchXml}");
EntityCollection results = new EntityCollection();
var queryResults = ExecuteQuery(tracingService, transferedFetchXml).Result;
queryResults.ForEach(t =>
{
results.Entities.Add(new Entity("ly_testvirtualentity") {
Id = t.accountid,
Attributes = {
["ly_testvirtualentityid"] = t.accountid,
["ly_name"] = t.name,
["ly_createdon"] = Convert.ToDateTime(t.createdon).ToUniversalTime(),
["ly_website"] = t.websiteurl
}
});
});
tracingService.Trace($"results.count = {results.Entities.Count}");
context.OutputParameters["BusinessEntityCollection"] = results;
}
catch (Exception e)
{
tracingService.Trace($"{e.Message} {e.StackTrace}");
if (e.InnerException != null)
tracingService.Trace($"{e.InnerException.Message} {e.InnerException.StackTrace}");
throw new InvalidPluginExecutionException(e.Message);
}
}
private static async Task<List<Result>> ExecuteQuery(ITracingService tracer, string fetchXml)
{
if (string.IsNullOrEmpty(fetchXml))
{
fetchXml = @"<fetch version=\'1.0\' mapping=\'logical\' distinct=\'false\'><entity name=\'account\'><attribute name=\'name\' /><attribute name=\'accountid\' /><attribute name=\'websiteurl\' /><attribute name=\'createdon\' /><order attribute=\'name\' descending=\'false\' /><filter type=\'and\'><condition attribute=\'statecode\' operator=\'eq\' value=\'0\' /></filter></entity></fetch>";
}
System.Collections.Generic.List<KeyValuePair<string, string>> vals = new System.Collections.Generic.List<KeyValuePair<string, string>>();
vals.Add(new KeyValuePair<string, string>("client_id", @"bd41df00-ae2b-4d94-aa12-18fb231c0b51"));
vals.Add(new KeyValuePair<string, string>("resource", @"https://logicalinventorycenter.crm5.dynamics.com/"));
vals.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
vals.Add(new KeyValuePair<string, string>("client_secret", @"8Es-j~RZG~-w.1Q7D_546r5IWZIq9XkqIp"));
string tokenUrl = string.Format("https://login.windows.net/{0}/oauth2/token", @"4a54995a-f092-4738-806e-c8427bdc39fb");
using (HttpClient httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
HttpContent content = new FormUrlEncodedContent(vals);
HttpResponseMessage hrm = httpClient.PostAsync(tokenUrl, content).Result;
if (hrm.IsSuccessStatusCode)
{
string data = await hrm.Content.ReadAsStringAsync();
//tracer.Trace($"Get token response = {data}");
var authenticationResponse = Utility.DeserializeDictionary(data);
var request = new HttpRequestMessage(HttpMethod.Get, $"https://logicalinventorycenter.crm5.dynamics.com/api/data/v9.1/accounts?fetchXml={fetchXml}");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authenticationResponse["access_token"]);
request.Headers.Add("OData-MaxVersion", "4.0");
request.Headers.Add("OData-Version", "4.0");
var queryResponseStr = httpClient.SendAsync(request).Result.Content.ReadAsStringAsync().Result;
//tracer.Trace($"Execute query result = {queryResponseStr}");
return ((QueryResult)Utility.DeserializeObject(queryResponseStr, typeof(QueryResult))).value;
}
else
{
throw new InvalidPluginExecutionException("Get token error!");
}
}
}
}
}
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Data.Exceptions;
using Microsoft.Xrm.Sdk.Extensions;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
namespace LuoYongCustomDataProvider
{
public class Retrieve : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService orgSvc = factory.CreateOrganizationService(context.UserId);
try
{
EntityReference target = (EntityReference)context.InputParameters["Target"];
tracingService.Trace($"Target: {target.Id.ToString()}");
var queryResults = ExecuteQueryById(tracingService, target.Id).Result;
var newEntity = new Entity("ly_virtualaccount");
newEntity.Id = queryResults.accountid;
newEntity["ly_testvirtualentityid"] = queryResults.accountid;
newEntity["ly_name"] = queryResults.name;
newEntity["ly_createdon"] = Convert.ToDateTime(queryResults.createdon).ToUniversalTime();
newEntity["ly_website"] = queryResults.websiteurl;
context.OutputParameters["BusinessEntity"] = newEntity;
}
catch (Exception e)
{
tracingService.Trace($"{e.Message} {e.StackTrace}");
if (e.InnerException != null)
tracingService.Trace($"{e.InnerException.Message} {e.InnerException.StackTrace}");
throw new InvalidPluginExecutionException(e.Message);
}
}
private static async Task<Result> ExecuteQueryById(ITracingService tracingService, Guid Id)
{
System.Collections.Generic.List<KeyValuePair<string, string>> vals = new System.Collections.Generic.List<KeyValuePair<string, string>>();
vals.Add(new KeyValuePair<string, string>("client_id", @"bd41df00-ae2b-4d94-aa12-18fb231c0b51"));
vals.Add(new KeyValuePair<string, string>("resource", @"https://logicalinventorycenter.crm5.dynamics.com/"));
vals.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
vals.Add(new KeyValuePair<string, string>("client_secret", @"8Es-j~RZG~-w.1Q7D_546r5IWZIq9XkqIp"));
string tokenUrl = string.Format("https://login.windows.net/{0}/oauth2/token", @"4a54995a-f092-4738-806e-c8427bdc39fb");
using (HttpClient httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
HttpContent content = new FormUrlEncodedContent(vals);
HttpResponseMessage hrm = httpClient.PostAsync(tokenUrl, content).Result;
if (hrm.IsSuccessStatusCode)
{
string data = await hrm.Content.ReadAsStringAsync();
tracingService.Trace($"Get token response = {data}");
var authenticationResponse = Utility.DeserializeDictionary(data);
var request = new HttpRequestMessage(HttpMethod.Get, $"https://logicalinventorycenter.crm5.dynamics.com/api/data/v9.1/accounts({Id})?$select=name,websiteurl,createdon");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authenticationResponse["access_token"]);
request.Headers.Add("OData-MaxVersion", "4.0");
request.Headers.Add("OData-Version", "4.0");
var queryResponseStr = httpClient.SendAsync(request).Result.Content.ReadAsStringAsync().Result;
tracingService.Trace($"Execute query result = {queryResponseStr}");
return ((Result)Utility.DeserializeObject(queryResponseStr, typeof(Result)));
}
else
{
throw new InvalidPluginExecutionException("Get token error!");
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
namespace LuoYongCustomDataProvider
{
public class Utility
{
public static Dictionary<string, string> DeserializeDictionary(string josnStr)
{
Dictionary<string, string> returnVal = null;
if (!string.IsNullOrEmpty(josnStr))
{
DataContractJsonSerializerSettings serializerSettings = new DataContractJsonSerializerSettings
{
UseSimpleDictionaryFormat = true
};
using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(josnStr)))
{
DataContractJsonSerializer deseralizer = new DataContractJsonSerializer(typeof(Dictionary<string, string>), serializerSettings);
returnVal = (Dictionary<string, string>)deseralizer.ReadObject(ms);
}
}
return returnVal;
}
public static Object DeserializeObject(string josnStr, Type type)
{
Object returnVal = null;
if (!string.IsNullOrEmpty(josnStr))
{
DataContractJsonSerializerSettings serializerSettings = new DataContractJsonSerializerSettings
{
UseSimpleDictionaryFormat = true
};
using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(josnStr)))
{
DataContractJsonSerializer deseralizer = new DataContractJsonSerializer(type);
returnVal = deseralizer.ReadObject(ms);
}
}
return returnVal;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LuoYongCustomDataProvider
{
public class Result
{
public Guid accountid { get; set; }
public string name { get; set; }
public string createdon { get; set; }
public string websiteurl { get; set; }
}
public class QueryResult
{
public List<Result> value { get; set; }
}
}
再需要将刚才的程序集使用插件注册工具的 Register > Register New Assembly 注册下。
然后需要注册一个新的Data Provider, 还是使用插件注册工具 Register > Register New Data Provider .
我的设置如下,注意Data Source Entity选择Create New Data Source ,Assembly选择我刚才注册,Retrieve和RetrieveMultiple分别选择前面创建的Class。
这时候会弹出新窗口,我的设置如下,然后点击 Create 按钮。
创建成功后在Register New Data Provider界面点击 Register 按钮。
创建完了界面如下所示:
还没完,需要在Dynamics 365中导航到 Advanced Settings > Settings > Administration > Virtual Entity Datasources ,新建一个Data Source,选择我们前面步骤创建的Data Provider,点击OK。
输入Name,并保存记录。
然后就是新建的虚拟实体,选择我们新建的Data Source,保存好后发布我们去看效果。
如果有错误,可以开启插件日志,看看具体错误内容。我这里看到效果如下:
我为这个虚拟实体启用了快速搜索,比如将name列设定为快速查找列,搜索也是正常。
以上是关于使用自定义数据源配置虚拟实体的主要内容,如果未能解决你的问题,请参考以下文章