zoukankan      html  css  js  c++  java
  • ASP.NET Core分布式项目实战(Consent 代码重构)--学习笔记

    任务23:Consent 代码重构

    新建一个 Sercices 文件夹,在文件夹下新建一个 ConsentService,专门用于处理 Consent 的逻辑,我们会把 controller 中不是 action 的方法移到 service 中

    先将 ConsentController 私有变量和构造函数搬到 ConsentService 中

    ConsentService

    private readonly IClientStore _clientSotre;
    private readonly IResourceStore _resourceStore;
    private readonly IIdentityServerInteractionService _identityServerInteractionService;
    
    public ConsentService(
        IClientStore clientStore,
        IResourceStore resourceStore,
        IIdentityServerInteractionService identityServerInteractionService)
    {
        _clientSotre = clientStore;
        _resourceStore = resourceStore;
        _identityServerInteractionService = identityServerInteractionService;
    }
    

    接着搬移私有方法,并将 BuildConsentViewModel 修改为 public

    ConsentService

    #region Private Methods
    
    private ConsentViewModel CreateConsentViewModel(AuthorizationRequest request, Client client, Resources resources)
    {
        var vm = new ConsentViewModel();
        vm.ClientName = client.ClientName;
        vm.ClientLogoUrl = client.LogoUri;
        vm.ClientUrl = client.ClientUri;
        vm.RememberConsent = client.AllowRememberConsent;
        vm.IdentityScopes = resources.IdentityResources.Select(i => CreateScopeViewModel(i));
        vm.ResourceScopes = resources.ApiResources.SelectMany(i => i.Scopes).Select(x => CreateScopeViewModel(x));
    
        return vm;
    }
    
    private ScopeViewModel CreateScopeViewModel(IdentityResource identityResource)
    {
        return new ScopeViewModel
        {
            Name = identityResource.Name,
            DisplayName = identityResource.DisplayName,
            Description = identityResource.Description,
            Required = identityResource.Required,
            Checked = identityResource.Required,
            Emphasize = identityResource.Emphasize,
        };
    }
    
    private ScopeViewModel CreateScopeViewModel(Scope scope)
    {
        return new ScopeViewModel
        {
            Name = scope.Name,
            DisplayName = scope.DisplayName,
            Description = scope.Description,
            Required = scope.Required,
            Checked = scope.Required,
            Emphasize = scope.Emphasize,
        };
    }
    
    #endregion
    
    public async Task<ConsentViewModel> BuildConsentViewModel(string returnUrl)
    {
        var request = await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);
        if (request == null)
            return null;
    
        var client = await _clientSotre.FindEnabledClientByIdAsync(request.ClientId);
        var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
    
        var vm = CreateConsentViewModel(request, client, resources);
        vm.ReturnUrl = returnUrl;
        return vm;
    }
    

    接着将 ConsentService 注入到 ConsentController 中并调用 BuildConsentViewModel

    ConsentController

    private readonly ConsentService _consentService;
    public ConsentController(ConsentService consentService)
    {
        _consentService = consentService;
    }
    
    [HttpGet]
    public async Task<IActionResult> Index(string returnUrl)
    {
        var model = await _consentService.BuildConsentViewModel(returnUrl);
    
        if (model == null)
        {
    
        }
    
        return View(model);
    }
    

    接着将 ConsentController 中 post 的逻辑搬到 ConsentService 的一个方法 ProcessConsent 中

    这里不能直接调用 Redirect 所以需要一个新建一个ViewModel 作为返回

    ProcessConsentResult

    public class ProcessConsentResult
    {
        public string RedirectUrl { get; set; }
        public bool IsRedirect => RedirectUrl != null;
    }
    

    ConsentService

    public async Task<ProcessConsentResult> ProcessConsent(InputConsentViewModel viewModel)
    {
        ConsentResponse consentResponse = null;
        var result = new ProcessConsentResult();
    
        if (viewModel.Button == "no")
        {
            consentResponse = ConsentResponse.Denied;
        }
        else if (viewModel.Button == "yes")
        {
            if (viewModel.ScopesConsented != null && viewModel.ScopesConsented.Any())
            {
                consentResponse = new ConsentResponse
                {
                    RememberConsent = viewModel.RememberConsent,
                    ScopesConsented = viewModel.ScopesConsented,
                };
            }
        }
    
        if (consentResponse != null)
        {
            var request = await _identityServerInteractionService.GetAuthorizationContextAsync(viewModel.ReturnUrl);
            await _identityServerInteractionService.GrantConsentAsync(request, consentResponse);
    
            result.RedirectUrl = viewModel.ReturnUrl;
        }
    
        return result;
    }
    

    接着在 ConsentController 的 post 逻辑中调用 ProcessConsent

    ConsentController

    [HttpPost]
    public async Task<IActionResult> Index(InputConsentViewModel viewModel)
    {
        var result = await _consentService.ProcessConsent(viewModel);
        if (result.IsRedirect)
        {
            return Redirect(result.RedirectUrl);
        }
    
        return View(viewModel);
    }
    

    因为在视图层 index 中使用的是 ConsentViewModel,不能直接把 InputConsentViewModel 传过去,因为是无法识别的,所以我们需要在 ConsentService 中转换一下

    首先在 ProcessConsentResult 中添加一个 ConsentViewModel

    ProcessConsentResult

    public class ProcessConsentResult
    {
        public string RedirectUrl { get; set; }
        public bool IsRedirect => RedirectUrl != null;
        public ConsentViewModel viewModel { get; set; }
    }
    

    在什么情况下会返回这个 ViewModel,当 ConsentService 的 ProcessConsent 方法中的 consentResponse 为 null 的时候,在这个时候我们需要给它封装一个 model

    ConsentService

    if (consentResponse != null)
    {
    ...
    }
    {
        var consentViewModel = await BuildConsentViewModel(viewModel.ReturnUrl);
        result.viewModel = consentViewModel;
    }
    

    但是在 BuildConsentViewModel 的时候,ConsentViewModel 的 ScopeViewModel 里面有 Required 和 Checked,如果在填写的时候已经勾选了,我们需要把它的状态带过去,而在 viewModel.ScopesConsented 的时候已经知道勾选了哪些,所以我们需要把 model 传过去

    ConsentService

    var consentViewModel = await BuildConsentViewModel(viewModel.ReturnUrl, viewModel);
    

    改造一下 BuildConsentViewModel,接收一个 InputConsentViewModel,默认为 null,如有它有值,可以知道客户的选中信息,然后传入 CreateConsentViewModel 中

    ConsentService

    public async Task<ConsentViewModel> BuildConsentViewModel(string returnUrl, InputConsentViewModel model = null)
    {
        ...
        var vm = CreateConsentViewModel(request, client, resources, model);
        ...
    }
    

    所以在 CreateConsentViewModel 的时候对 Checked 赋值,或者已经选中的情况下就选中

    ConsentService

    private ScopeViewModel CreateScopeViewModel(IdentityResource identityResource, bool check)
    {
        return new ScopeViewModel
        {
            Name = identityResource.Name,
            DisplayName = identityResource.DisplayName,
            Description = identityResource.Description,
            Required = identityResource.Required,
            Checked = check || identityResource.Required,
            Emphasize = identityResource.Emphasize,
        };
    }
    
    private ScopeViewModel CreateScopeViewModel(Scope scope, bool check)
    {
        return new ScopeViewModel
        {
            Name = scope.Name,
            DisplayName = scope.DisplayName,
            Description = scope.Description,
            Required = scope.Required,
            Checked = check ||scope.Required,
            Emphasize = scope.Emphasize,
        };
    }
    

    接着处理一下 CreateConsentViewModel,传入一个 InputConsentViewModel,然后修改 RememberConsent,以及获取一个 selectedScopes

    ConsentService

    private ConsentViewModel CreateConsentViewModel(AuthorizationRequest request, Client client, Resources resources, InputConsentViewModel model)
    {
        var rememberConsent = model?.RememberConsent ?? true;
        var selectedScopes = model?.ScopesConsented ?? Enumerable.Empty<string>();
    
        var vm = new ConsentViewModel();
        vm.ClientName = client.ClientName;
        vm.ClientLogoUrl = client.LogoUri;
        vm.ClientUrl = client.ClientUri;
        vm.RememberConsent = rememberConsent;
        vm.IdentityScopes = resources.IdentityResources.Select(i => CreateScopeViewModel(i, selectedScopes.Contains(i.Name) || model == null));
        vm.ResourceScopes = resources.ApiResources.SelectMany(i => i.Scopes).Select(x => CreateScopeViewModel(x, selectedScopes.Contains(x.Name) || model == null));
    
        return vm;
    }
    

    这一块就改进完了,接下来就是在不选中的情况下会有提示让我们选择,现在添加一些错误的提示,在 ProcessConsentResult 中添加一些信息

    ProcessConsentResult

    public string ValidationError { get; set; }
    

    赋值 ValidationError

    ConsentService

    else if (viewModel.Button == "yes")
    {
        if (viewModel.ScopesConsented != null && viewModel.ScopesConsented.Any())
        {
            consentResponse = new ConsentResponse
            {
                RememberConsent = viewModel.RememberConsent,
                ScopesConsented = viewModel.ScopesConsented,
            };
        }
    
        result.ValidationError = "请至少选中一个权限";
    }
    

    接着处理一下页面,将信息返回

    ConsentController

    [HttpPost]
    public async Task<IActionResult> Index(InputConsentViewModel viewModel)
    {
        var result = await _consentService.ProcessConsent(viewModel);
        if (result.IsRedirect)
        {
            return Redirect(result.RedirectUrl);
        }
    
        if (!string.IsNullOrEmpty(result.ValidationError))
        {
            ModelState.AddModelError("", result.ValidationError);
        }
    
        return View(result.viewModel);
    }
    

    加入验证信息之后需要修改视图把这块信息显示出来

    Index

    <input type="hidden" asp-for="ReturnUrl"/>
    
        <div class="alert alert-danger">
            <strong>Error""</strong>
            <div asp-validation-summary="All" class="danger"></div>
        </div>
    

    添加依赖注入

    startup

    services.AddScoped<ConsentService>();
    

    启动服务端,启动客户端

    因为默认勾选第一个,无法看到错误信息,所以去掉 disabled 与 hidden 控件,两个选项都不勾选,点击同意就会看到错误信息

    _ScopeListitem

    disabled="@Model.Required"
    
    @if (Model.Required)
    {
        <input type="hidden" name="ScopesConsented" value="@Model.Name"/>
    }
    

    课程链接

    http://video.jessetalk.cn/course/explore

    知识共享许可协议

    本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

    欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

    如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

  • 相关阅读:
    websocket的理解及实例应用
    laravel框架cookie应用到中间件的理解
    PHP多机实现session共享
    mysql中exists的详细说明
    window环境下安装pear
    php 进程管理及操作
    PHP设计模式之职责链模式
    PHP设计模式之备忘录模式
    PHP设计模式之装饰模式
    PHP设计模式之策略模式
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/13034933.html
Copyright © 2011-2022 走看看