Linux/Mac下通过Dataverse.Client.Dynamics连接本地Dynamics 365 v9.1
- 适用环境:CRM Dynamics 365 v9.1 本地部署版(on-premise)
- 支持运行操作系统:Linux、Mac OS
微软官方为Dynamics 365 v9.x提供的传统NuGet SDK包仅支持.Net Framework,不兼容.Net Core/5/6版本;目前另有官方预览版NuGet包Microsoft.PowerPlatform.Dataverse.Client.Dynamics,官方文档标注可支持.Net Core/5/6版本。
当前正在开发对接该版本CRM的.Net 6应用,后续将打包发布为Docker镜像。现阶段已通过Dynamics Web API实现通信:创建配置了NTLM认证的HttpClient,连接所用的账号密码存储在系统环境变量中,该方案在Mac OS下运行正常,实现代码如下:
static HttpClient CreateCrmHttpClient(string domain, string crmWebApiUrl, string authType, string crmLogin, string crmPassword, Guid? callerId) { var uri = new Uri(crmWebApiUrl); var credentialsCache = new CredentialCache {{uri, authType, new NetworkCredential(crmLogin, crmPassword, domain)}}; var handler = new HttpClientHandler {Credentials = credentialsCache}; var httpClient = new HttpClient(handler) {BaseAddress = uri, Timeout = new TimeSpan(0, 2, 0)}; httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0"); httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0"); httpClient.DefaultRequestHeaders.Add("Prefer", "odata.include-annotations=\"*\""); if (callerId != null) { httpClient.DefaultRequestHeaders.Add("MSCRMCallerID", callerId.Value.ToString()); } return httpClient; }
调用示例:
string domain = "MyCompany"; string crmWebApiUrl = "https://crm-dev.MyCompany.ru:456/MyCompany/api/data/v9.1/"; string crmLogin = Environment.GetEnvironmentVariable("CrmLogin"); string crmPassword = Environment.GetEnvironmentVariable("CrmPassword"); Guid callerId = Guid.Parse("81CF7EFF-A996-44C2-8710-06E8177586C2"); // J.Smith string authType = "NTLM"; using (var httpClient = CreateCrmHttpClient(domain, crmWebApiUrl, authType, crmLogin, crmPassword, callerId)) { // 执行业务逻辑 }
目前希望改用Microsoft.PowerPlatform.Dataverse.Client.Dynamics包实现与CRM的通信,但遇到连接问题:该包未直接提供NTLM认证的连接能力,尝试多种官方示例给出的连接方式均未成功。需要可在Linux或Mac环境下正常运行的、通过Microsoft.PowerPlatform.Dataverse.Client.Dynamics包连接本地部署CRM Dynamics 365 v9.1的可运行代码示例。
核心实现逻辑:Microsoft.PowerPlatform.Dataverse.Client.Dynamics 1.0.9及以上版本支持自定义HttpClient注入,可以直接复用已经验证跨平台可用的NTLM认证HttpClientHandler配置,不需要依赖SDK内置的Windows专属SSPI认证实现,即可在Linux/Mac环境正常连接。
前置准备
安装版本号不低于1.0.9的Microsoft.PowerPlatform.Dataverse.Client.Dynamics NuGet包,推荐使用1.0.14及以上的预览版本,低版本未开放自定义HttpClient注入接口,无法实现跨平台NTLM认证。
实现代码
using Microsoft.PowerPlatform.Dataverse.Client; using Microsoft.Xrm.Sdk.Query; using System.Net; using System.Net.Http.Headers; static ServiceClient CreateCrmDataverseClient(string domain, string crmOrgRootUrl, string authType, string crmLogin, string crmPassword, Guid? callerId) { var orgUri = new Uri(crmOrgRootUrl); // 复用已验证可用的NTLM认证凭证配置 var credentialsCache = new CredentialCache { {orgUri, authType, new NetworkCredential(crmLogin, crmPassword, domain)} }; var ntlmHandler = new HttpClientHandler { Credentials = credentialsCache // 测试环境如果是自签名SSL证书,可临时放开下一行配置跳过证书验证,生产环境建议部署合法可信证书 // ServerCertificateCustomValidationCallback = (_, _, _, _) => true }; // 初始化Dataverse客户端,注入配置好NTLM认证的HttpClient var serviceClient = new ServiceClient(orgUri, async (_, _) => { return await Task.FromResult(new HttpClient(ntlmHandler) { Timeout = TimeSpan.FromMinutes(2) }); }, disposeHttpClient: true); // 配置通用请求头 serviceClient.HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); serviceClient.HttpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0"); serviceClient.HttpClient.DefaultRequestHeaders.Add("OData-Version", "4.0"); serviceClient.HttpClient.DefaultRequestHeaders.Add("Prefer", "odata.include-annotations=\"*\""); // 配置模拟调用用户ID if (callerId != null) { serviceClient.CallerId = callerId.Value; } // 校验连接状态 if (!serviceClient.IsReady) { throw new InvalidOperationException($"CRM连接失败: {serviceClient.LastError}", serviceClient.LastException); } return serviceClient; }
调用示例
string domain = "MyCompany"; // 注意此处传入CRM组织根地址即可,不需要带/api/data/v9.1后缀,SDK会自动拼接Web API路由 string crmOrgRootUrl = "https://crm-dev.MyCompany.ru:456/MyCompany/"; string crmLogin = Environment.GetEnvironmentVariable("CrmLogin"); string crmPassword = Environment.GetEnvironmentVariable("CrmPassword"); Guid callerId = Guid.Parse("81CF7EFF-A996-44C2-8710-06E8177586C2"); // J.Smith string authType = "NTLM"; using (var serviceClient = CreateCrmDataverseClient(domain, crmOrgRootUrl, authType, crmLogin, crmPassword, callerId)) { // 测试连接:查询第一条客户记录 var testQuery = new QueryExpression("account") { ColumnSet = new ColumnSet("name"), TopCount = 1 }; var firstAccount = serviceClient.RetrieveMultiple(testQuery).Entities.FirstOrDefault(); Console.WriteLine($"连接验证成功,第一条客户记录名称:{firstAccount?.GetAttributeValue<string>("name")}"); // 后续执行业务逻辑 }
注意事项
- Linux环境(含Docker容器部署)需要提前安装NTLM认证依赖组件:基于Debian/Ubuntu的基础镜像需要在Dockerfile中执行
apt-get update && apt-get install -y gss-ntlmssp,基于RHEL/CentOS的基础镜像执行yum install -y gss-ntlmssp,否则会抛出NTLM认证不支持的异常。 - 不要使用SDK内置的连接字符串配置NTLM认证,内置NTLM实现依赖Windows系统的SSPI接口,跨平台环境无法正常运行,必须走自定义HttpClient注入的方式。
- 传入ServiceClient的地址必须是CRM组织根地址,不要带Web API版本路由后缀,否则会出现404错误。
内容的提问来源于stack exchange,提问作者Andrey Bushman

