Dynamics 365通过Action调用Graph API获取用户经理信息

我是微软Dynamics 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面的微软最有价值专家(Microsoft MVP),欢迎关注我的微信公众号 MSFTDynamics365erLuoYong ,回复408或者20200510可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!

通过 https://aad.portal.azure.com/ 可以查看和设置用户信息(包括经理),如下图所示:

 

 用户的经理并未同步到Dynamics 365中,有时候需要获取。首先说一下通过Power Automate是可以方便的获取的。使用Office 365 User这个Connector下面的 Get Manager (V2)来获取。

  

可以获取指定用户的mnager,当然也可以使用表达式填入触发者的邮箱来获取当前用户的manager。

 

 运行下,返回结果示例如下,我用红框标出来最重要的信息,Manager的姓名和UPN。

 

 但是Power Automate只能是异步的,若是用HTTP Trigger来作为flow的触发器,默认情况下是没有安全保护措施的,当然可以通过Azure API Management来保护它,可以参考 Call, trigger, or nest logic apps by using HTTPS endpoints in Azure Logic Apps 。Sandro Pereira 的博文 How to Expose Microsoft Flow thru Azure API Management 提供了更加详细的具体做法说明。

今天的博文讲解的是通过Grahp API来获取信息,先找到怎么调用才能拿到指定用户的Manager,通过 Graph Explorer 我们看到有一个【my manager】的示例,可以看到调用方法和返回结果,但是这个是获取当前登录用户的,我们需要的是获取指定用户的,点击这个【my manager】旁边的文档图标。

 

 会打开 List manager 的文档,可以看到调用需要的权限和调用的方式。

 再回到 Graph Explorer 界面,我修改GET的URL改成 https://graph.microsoft.com/v1.0/users/colinlin@CRM435740.onmicrosoft.com/manager 后执行就可以获取到colinlin@CRM435740.onmicrosoft.com这个用户的manager信息了。当然,我如果不需要这么多信息,只需要显示名称和邮箱,请求的querystring加上$select表达式即可,这个和Dynamics 365的Web API是很相似,甚至几乎一样的,示例如下: https://graph.microsoft.com/v1.0/users/colinlin@CRM435740.onmicrosoft.com/manager?$select=displayName,mail 。

 

当然获取组织中任何人的经理信息(User.Read.All)需要租户管理员的授权,授权后需要重新登录下。

 

 代码中如何获取?这里首先需要注册个App,参考官方文档 Register an application with the Microsoft identity platform 。方法类似我前面的博文: Dynamics 365 Online通过OAuth 2 Client Credential授权(Server-to-Server Authentication)后调用Web API ,所以这里我不解释太多,主要做一下截图。

登录 https://portal.azure.com/ 后,点击左侧导航栏的 Azure Active Directory ,然后选择【App registrations】 > 【New registration】。

 

 我这里的设置如下,然后点击 【Register】 按钮。

 

 当然要为这个app添加权限,点击左侧的【API permissions】,然后再点击【Add a permission】。】

 

 选择【Microsoft Graph】。

 

 然后再选择【Application permissions】,选择     User.Read.All 这个权限后点击【Add permissions】按钮完成权限的添加.

 

 因为这个权限是Admin consent required (该列的值为Yes,意思是需要管理员同意)的,所以需要点击【Grant admin consent for Contoso】按钮完成授权。

  

 完成授权后还需要设置认证信息,点击左侧的【Certificates & secrets】,然后点击 【New client secret】按钮。

 

输入Secret的说明后,选择合适的过期时间,点击【Add】按钮。

 

 生成后记得要好好的保存下来,这个类似密码很重要,离开当前页面后不能再次获取到这个seceret。

 

 

当然还有Overview中的

 

 可以通过Postman来做尝试调用,请参考:Use Postman with the Microsoft Graph API 。我这里简述下,

导入Collection后,切换到Microsoft Graph environment后点击图标更新Variable的值。

 

 然后点击左侧Collections下面的 MicrosoftGraph > Application > Get App-only Access Token ,点击【Send】按钮,这样就会获取到access token。然后就可以执行其他操作了,比如MicrosoftGraph > Application >  Users > Get Users。

 

获取access token的方法可以参考 Get access without a user ,文档比较详细,我就不解释了。

然后我新建一个自定义工作流活动来获取指定用户的经理信息,我使用的代码如下,关于使用自定义工作流活动请参考官方文档:Workflow extensions

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
using System;
using System.Activities;
using Microsoft.Xrm.Sdk.Query;
using System.Net.Http;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;

namespace DemoCodeActivities
{
    public class GetUserManagerActivity : CodeActivity
    {
        [Input("User")]
        [ReferenceTarget("systemuser")]
        public InArgument<EntityReference> User { get; set; }

        [Input("User's Email")]
        public InArgument<string> UserEmail { get; set; }

        [Output("Manager's Name")]
        public OutArgument<string> ManagerName { get; set; }

        [Output("Manager's Email")]
        public OutArgument<string> ManagerEmail { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            ITracingService tracingService = context.GetExtension<ITracingService>();
            tracingService.Trace($"Enter {this.GetType()} on {DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss:ms")}");
            IWorkflowContext workflowContext = context.GetExtension<IWorkflowContext>();
            IOrganizationServiceFactory serviceFactory = context.GetExtension<IOrganizationServiceFactory>();
            IOrganizationService service = serviceFactory.CreateOrganizationService(workflowContext.UserId);
            try
            {
                string userEmail = UserEmail.Get(context);
                if (string.IsNullOrEmpty(userEmail))
                {
                    var user = User.Get(context);
                    userEmail = service.Retrieve("systemuser", user.Id, new ColumnSet("internalemailaddress")).GetAttributeValue<string>("internalemailaddress");
                }
                if (string.IsNullOrEmpty(userEmail))
                {
                    throw new InvalidPluginExecutionException("EmailAddr is empty!");
                }
                tracingService.Trace($"UserEmail = {userEmail} on {DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss:ms")}");
                const string clientId = "21cd9cdf-b67c-453d-a1fa-d497a3b5ff61";
                const string tenantId = "9870b0d6-32be-4bbc-9cd7-01d0eed5e8a2";
                const string clientSecret = @"tJYl78_Brrc-1XrT3~Rp_5.FUS_-n8r-4D";
                const string scope = @"https%3A%2F%2Fgraph.microsoft.com%2F.default";
                string tokenUrl = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";
                AuthResponse authRepsonse = null;
                using (HttpClient httpClient = new HttpClient())
                {
                    var authRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
                    var requestBody = new StringContent($"client_id={clientId}&scope={scope}&client_secret={clientSecret}&grant_type=client_credentials");
                    requestBody.Headers.ContentType.MediaType = "application/x-www-form-urlencoded";
                    authRequest.Content = requestBody;
                    var authResponseStr = httpClient.SendAsync(authRequest).Result.Content.ReadAsStringAsync().Result;
                    tracingService.Trace($"authResponseStr = {authResponseStr} on {DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss:ms")}");
                    using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(authResponseStr)))
                    {
                        DataContractJsonSerializer deseralizer = new DataContractJsonSerializer(typeof(AuthResponse));
                        authRepsonse = (AuthResponse)deseralizer.ReadObject(ms);
                    }
                    if(authRepsonse != null && !string.IsNullOrEmpty(authRepsonse.access_token))
                    {
                        var request = new HttpRequestMessage(HttpMethod.Get, $"https://graph.microsoft.com/v1.0/users/{userEmail}/manager?$select=displayName,mail");
                        request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authRepsonse.access_token);
                        var getUserResponseStr = httpClient.SendAsync(request).Result.Content.ReadAsStringAsync().Result;
                        tracingService.Trace($"getUserResponseStr = {getUserResponseStr} on {DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss:ms")}");
                        using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(getUserResponseStr)))
                        {
                            DataContractJsonSerializer deseralizer = new DataContractJsonSerializer(typeof(GetUserResponse));
                            var getUserResponse = (GetUserResponse)deseralizer.ReadObject(ms);
                            ManagerName.Set(context, getUserResponse.displayName);
                            ManagerEmail.Set(context, getUserResponse.mail);
                        }
                    }
                    else
                    {
                        throw new InvalidPluginExecutionException($"Get access token error!" + authResponseStr);
                    }
                }
                tracingService.Trace($"Leave {this.GetType()} on {DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss:ms")}");
            }
            catch (Exception ex)
            {
                tracingService.Trace($"{this.GetType()} encountered unexpected exception: {ex.Message + ex.StackTrace} on {DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss:ms")}");
                throw new InvalidPluginExecutionException(ex.Message + ex.StackTrace);
            }
        }
    }

    public class AuthResponse
    {
        public string access_token { get; set; }
        public int expires_in { get; set; }
        public int ext_expires_in { get; set; }
        public string token_type { get; set; }
    }

    public class GetUserResponse
    {
        public string displayName { get; set; }

        public string mail { get; set; }
    }
}

 

然后我再建立一个全局Action如下:

 

 如果知道Dynamics 365用户的Id,我这样调用:

var GetUserManagerActionRequest = function (user, userEmail) {
    this.User = user;
    this.UserEmail = userEmail;
};

GetUserManagerActionRequest.prototype.getMetadata = function () {
    return {
        boundParameter: null,
        parameterTypes: {
            "User": {
                typeName: "mscrm.systemuser",
                structuralProperty: 5
            },
            "UserEmail": {
                typeName: "Edm.String",
                structuralProperty: 1
            }
        },
        operationType: 0,
        operationName: "new_GetUserManagerAction",
    };
};

var user = {
    "systemuserid": "3155b6bd-3985-ea11-a811-000d3a80cfdd"
}

var getUserManagerActionRequest = new GetUserManagerActionRequest(user,"");
Xrm.WebApi.online.execute(getUserManagerActionRequest).then(
    function (result) {
        if (result.ok) {
            result.json().then(
                    function (response) {
                        console.log(response);
                        alert(response.ManagerName + "_" + response.ManagerEmail);
                    });
        }
    },
    function (error) {
        console.log(error);
        var alertStrings = { confirmButtonLabel: "OK", text: "Operation Failed. " + error.message, title: "Error" };
        var alertOptions = { height: 300, width: 400 };
        Xrm.Navigation.openAlertDialog(alertStrings, alertOptions);
    }
);

 

如果知道用户的邮箱,我这样调用:

var GetUserManagerActionRequest = function (user, userEmail) {
    this.User = user;
    this.UserEmail = userEmail;
};

GetUserManagerActionRequest.prototype.getMetadata = function () {
    return {
        boundParameter: null,
        parameterTypes: {
            "User": {
                typeName: "mscrm.systemuser",
                structuralProperty: 5
            },
            "UserEmail": {
                typeName: "Edm.String",
                structuralProperty: 1
            }
        },
        operationType: 0,
        operationName: "new_GetUserManagerAction",
    };
};

var getUserManagerActionRequest = new GetUserManagerActionRequest(null,"admin@CRM435740.onmicrosoft.com");
Xrm.WebApi.online.execute(getUserManagerActionRequest).then(
    function (result) {
        if (result.ok) {
            result.json().then(
                    function (response) {
                        console.log(response);
                        alert(response.ManagerName + "_" + response.ManagerEmail);
                    });
        }
    },
    function (error) {
        console.log(error);
        var alertStrings = { confirmButtonLabel: "OK", text: "Operation Failed. " + error.message, title: "Error" };
        var alertOptions = { height: 300, width: 400 };
        Xrm.Navigation.openAlertDialog(alertStrings, alertOptions);
    }
);

 

你可能会认为secret等写死在代码中,可以做成可以配置的吗?我这里提供另外一个方法,请参考我的博文:在插件中使用Secure Configuration

posted @ 2020-05-10 17:38  微软MVP(15-18)罗勇  阅读(631)  评论(0编辑  收藏  举报