使用自定义数据源配置虚拟实体

我是微软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列设定为快速查找列,搜索也是正常。

原文地址:https://www.cnblogs.com/luoyong0201/p/Dynamics_365_Develop_Virtual_Entity_with_Custom_Data_Provider.html