ABP VNext框架中Winform終端的開發和客戶端授權信息的處理


在ABP VNext框架中,即使在它提供的所有案例中,都沒有涉及到Winform程序的案例介紹,不過微服務解決方案中提供了一個控制台的程序供了解其IDS4的調用和處理,由於我開發過很多Winform項目,以前基於ABP框架基礎上開發的《ABP快速開發框架》中就包含了Winform客戶端,因此我對於ABP VNext在Winform上的使用也比較關心,花了不少時間來研究框架的相關的授權和窗體構建處理上,因此整理了該隨筆內容,主要用於介紹ABP VNext框架中Winform終端的開發和客戶端授權信息的處理。

1、ABP VNext框架中Winform終端的開發

不管對於那種終端項目,需要應用ABP VNext模塊的,都需要創建一個模塊類,繼承於AbpModule,然后引入相關的依賴模塊,並配置Servcie信息,如下是Winform項目中的Module類,如下所示。

namespace Winform.TestApp
{
    [DependsOn(
        typeof(MicroBookStoreHttpApiClientModule),
        typeof(AbpHttpClientIdentityModelModule)
        )]
    public class WinformApiClientModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
        }
    }
}

ABP VNext模塊的初始化,根據依賴關系進行相關的初始化,我們在創建Winform項目(基於.net Core開發)的時候,需要在Main函數中創建一個應用接口,如下所示。

 // 使用 AbpApplicationFactory 創建一個應用
 var app = AbpApplicationFactory.Create<WinformApiClientModule>();
 // 初始化應用
 app.Initialize();

這個app接口對象非常重要,需要用它創建一些接口服務,如下所示。

var service = app.ServiceProvider.GetService<IService1>();

不過由於這個app對象需要在整個應用程序的生命周期中都可能會用到,用來構建一些用到的接口對象等,那么我們就需要創建一個靜態類對象用來存儲相關的應用接口信息,需要用到它的時候就可以直接使用了,否則丟掉了就沒法構建接口使用了。

首先我們創建一個用於存儲全局信息類GlobalControl,如下所示。

    /// <summary>
    /// 應用程序全局對象
    /// </summary>
    public class GlobalControl
    {
        public MainForm? MainDialog { get; set; } = null;
        public IAbpApplicationWithInternalServiceProvider? app { get; set; }

        /// <summary>
        /// 創建指定類型窗口實例
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T CreateForm<T>() where T :Form
        {
            if (app == null) return null;
            else
            {
                var form = app.ServiceProvider.GetService<T>();
                return form;
            }
        }

        /// <summary>
        /// 創建服務類的接口實例
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T GetService<T>() where T : class
        {
            if (app == null) return null;
            else
            {
                var service = app.ServiceProvider.GetService<T>();
                return service;
            }
        }

這樣我們在Main方法中創建的時候,構建一個靜態的類對象,用於存儲我們所需要的信息,這樣上面提到的應用接口對象,就可以存儲起來,

    public static class Portal
    {
        /// <summary>
        /// 應用程序的全局靜態對象
        /// </summary>
        public static GlobalControl gc = new GlobalControl();

        /// <summary>
        ///  The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .Enrich.FromLogContext()
                .WriteTo.Console()
                .WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day)
                .CreateLogger();

            // 使用 AbpApplicationFactory 創建一個應用
            var app = AbpApplicationFactory.Create<WinformApiClientModule>();
            // 初始化應用
            app.Initialize();
            gc.app = app;

            Application.SetHighDpiMode(HighDpiMode.SystemAware);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            var form = app.ServiceProvider.GetService<MainForm>();
            gc.MainDialog = form;
            Application.Run(gc.MainDialog);
        }
    }

上面標注紅色的部分就是把這個重要的app存放起來,便於后期的使用。

而我們注意到,我們創建窗體的時候,不是使用

var form = new MainForm();

的方式構建,而是使用接口構建的方式。

 var form = app.ServiceProvider.GetService<MainForm>();

和我們前面提到的方式構建接口是一樣的。

var service = app.ServiceProvider.GetService<IService1>();

這個是為什么呢?因為我們需要通過構造函數注入接口方式,在窗體中引用相關的接口服務。

 由於沒有默認構造函數,因此不能再通過new的方式構建了,需要使用ABP VNext的常規接口解析的方式獲得對應的窗體對象了。

注意:這里窗體需要繼承自 ITransientDependency 接口,這樣才可以通過接口的方式構建,否則是不行的。

如果我們在主窗體或者其他界面事件中調用其他窗口,也是類似,如下調用所示。

        private void button2_Click(object sender, EventArgs e)
        {
            var form2 = Portal.gc.CreateForm<SecondForm>();
            form2.ShowDialog();
        }

這個地方就是用到了靜態對象GlobalControl里面的方法構建,因為里面在程序啟動的時候,已經存儲了app應用接口對象了,可以用它來構建相關的接口或者窗體對象。

當然,這里的SecondForm也是不能使用New的方式構建窗體對象,也需要使用服務構建的標准方式來處理,畢竟它的默認構造函數用於接口的注入處理了。

程序看起來效果如下所示,可以正常打開窗體了。

 

2、Winform客戶端授權信息的處理

 在ABP VNext微服務的解決方案中,有一個控制台調用服務接口的測試項目,如下所示。

它主要就是介紹如何配置IdentityServer4(也叫IDS4)的授權規則來獲得動態客戶端的接口調用服務的。

它的配置是通過appsettings.json中配置好IdentityServer4終端的節點信息,用來在客戶端調用類中進行相關的授權處理(獲得令牌)的,因為我們調用服務接口需要令牌信息,而這些都是封裝在內部里面的。

appsettings.json的配置信息如下所示,這個IDS4認證是采用client_credentials方式認證的。

而在構建ABP VNext項目模板的時候,也提供了一個類似控制台的測試項目,如下所示。

 這個里面的appsettings.json是使用用戶名密碼的方式進行認證的,授權方式是密碼方式。

 看到這些信息,你可能注意到了用戶名密碼都在里面。

我在想,如果每次讓用戶使用Winform程序的時候,來修改一下這個appsettings.json,那肯定是不友好的,如果把IDS4信息動態構建,傳入接口使用,是不是就可以不用配置文件了呢?

通過分析ABP VNExt框架的類庫,你可以看到IDS的授權認證處理是在IdentityModelAuthenticationService 接口實現類里面,它通過下面接口獲得通信令牌信息。

public async Task<string> GetAccessTokenAsync(IdentityClientConfiguration configuration)

我們傳入對應的IDS4的配置對象即可獲得接口的令牌信息。

我們通過IIdentityModelAuthenticationService 接口獲得令牌信息,緩存起來可以,但是每次調用的時候,如何設定HttpClient的令牌頭部信息呢,通過分析 IdentityModelAuthenticationService 類的代碼知道,如果我們在appsetting.json配置了IDS4的標准配置,它就可以根據配置信息獲得令牌信息的緩存,並設置到調用的HttpClient里面,如果我們采用剛才說的動態配置對象的傳入獲得token,沒有IDS4配置文件信息它是沒法提取出令牌緩存信息的。

        public async Task<bool> TryAuthenticateAsync(HttpClient client, string identityClientName = null)
        {
            var accessToken = await GetAccessTokenOrNullAsync(identityClientName);
            if (accessToken == null)
            {
                return false;
            }

            SetAccessToken(client, accessToken);
            return true;
        }

那有沒有其他方式可以動態設定令牌信息或者類似的操作呢?

有!我們注意到,IRemoteServiceHttpClientAuthenticator 接口就是用來解決終端授權處理的接口,它的接口定義如下所示。

namespace Volo.Abp.Http.Client.Authentication
{
    public interface IRemoteServiceHttpClientAuthenticator
    {
        Task Authenticate(RemoteServiceHttpClientAuthenticateContext context);
    }
}

我們參考項目Volo.Abp.Http.Client.IdentityModel.Web的思路

 

 這個項目使用了自定義的接口實現類HttpContextIdentityModelRemoteServiceHttpClientAuthenticator,替換默認的IdentityModelRemoteServiceHttpClientAuthenticator類,我們來看看它的具體實現

namespace Volo.Abp.Http.Client.IdentityModel.Web
{
    [Dependency(ReplaceServices = true)]
    public class HttpContextIdentityModelRemoteServiceHttpClientAuthenticator : IdentityModelRemoteServiceHttpClientAuthenticator
    {
        public IHttpContextAccessor HttpContextAccessor { get; set; }

        public HttpContextIdentityModelRemoteServiceHttpClientAuthenticator(
            IIdentityModelAuthenticationService identityModelAuthenticationService)
            : base(identityModelAuthenticationService)
        {
        }

        public override async Task Authenticate(RemoteServiceHttpClientAuthenticateContext context)
        {
            if (context.RemoteService.GetUseCurrentAccessToken() != false)
            {
                var accessToken = await GetAccessTokenFromHttpContextOrNullAsync();
                if (accessToken != null)
                {
                    context.Request.SetBearerToken(accessToken);
                    return;
                }
            }

            await base.Authenticate(context);
        }

        protected virtual async Task<string> GetAccessTokenFromHttpContextOrNullAsync()
        {
            var httpContext = HttpContextAccessor?.HttpContext;
            if (httpContext == null)
            {
                return null;
            }

            return await httpContext.GetTokenAsync("access_token");
        }
    }
}

這里看到,它主要就是從httpContext中獲得access_token的頭部信息,然后通過SetBearerToken的接口設置到對應的HttpRequest請求中去的,也就是先獲得令牌,然后設置請求對象的令牌,從而完成了授權令牌的信息處理。

我們如果是Winform或者控制台,那么調用請求類是HttpClient,我們可以模仿項目 Volo.Abp.Http.Client.IdentityModel.Web 這個方式創建一個項目,然后通過依賴方式來替換默認授權處理接口的實現;也可以通過在本地項目中創建一個IdentityModelRemoteServiceHttpClientAuthenticator的子類來替換默認的,如下所示。

namespace Winform.TestApp
{
    public class MyIdentityModelRemoteServiceHttpClientAuthenticator : IdentityModelRemoteServiceHttpClientAuthenticator
    {

在ABP VNext框架類IdentityModelAuthenticationService中獲得令牌的時候,就會設置獲得的令牌到分布式緩存中,它的鍵是IdentityClientConfiguration對象的鍵值生成的,如下代碼邏輯所示。

 

 那么我們只需要在自定義的 MyIdentityModelRemoteServiceHttpClientAuthenticator 類中根據鍵獲得緩存就可以設置令牌信息了。

通過上面的處理,我們就可以動態根據賬號密碼獲得令牌,並根據配置信息的鍵從緩存中獲得令牌,設置到對應的對象上去,完成了令牌的信息設置,這樣ABP VNext動態客戶端的代理接口類,就可以正常調用獲得數據了。

 

數據記錄展示如下。

這樣,整個測試的例子就完成了多個Winform窗體的生成和調用展示,並通過令牌的處理,完成了客戶端的IDS4授權,可以正常調用動態客戶端的接口類,完美解決了相關的技術點了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM