根據MediatR的Contract Messages自動生成Minimal WebApi介面

小莊 發表於 2022-11-24

大家好,我是失業在家,正在找工作的博主Jerry。今天給大家介紹一個能大大減少ASP.Net Minimal WebApi編碼量的方法。

我們一般會把微服務的VO和DTO封裝成訊息類,並作為WebApi的Request和Response引數進行網路傳遞。

如果使用MediatR,我們封裝的訊息類就要實現 MediatR Contract 介面 IRequest<> 或者INotification,  例如我的程式碼如下:

namespace MediatRApplication.CategoryCRUD
{
    public class CreateCategory : IRequest<CreateCategoryResult>
    {
        public string Message { get; set; }
    }
    public class CreateCategoryResult
    {
        public string Message { get; set; }
    }

    public class ReadCategory : IRequest<ReadCategoryResult>
    {
        public string Message { get; set; }
    }
    public class ReadCategoryResult
    {
        public string Message { get; set; }
    }

    public class UpdateCategory : IRequest<UpdateCategoryResult>
    {
        public string Message { get; set; }
    }
    public class UpdateCategoryResult
    {
        public string Message { get; set; }
    }

    public class DeleteCategory : IRequest
    {
        public string Message { get; set; }
    }
}

如上程式碼是對Category業務實體進行CRUD操作封裝的DTO訊息類,每個訊息類都實現了MediatR的IRequest介面。有了訊息類,就可以對每個訊息類編寫處理器(Handler),以實現業務功能。

有了訊息類,就需要為每個訊息類建立WebApi介面,以實現訊息的Request和Response。WebAPI介面中沒有業務邏輯,只需要呼叫MediatR的Send方法將訊息類傳送給Handler即可。

但是,由於訊息類比較多,一個一個建立WebApi介面是一件費時費力,並且容易出錯的事情。作為一個架構師,是無法忍受程式設計師們幹這些出力不討好的事情的。

所以,為了專案,為了大家的Work Life Banlance, 我把建立WebApi這件事情減少成了一行程式碼。是的,你沒看錯,就是隻要一行程式碼:

app.MapMediatorWebAPIs(typeof(CreateCategory).Assembly);

只要在ASP.Net Minimal API 專案的Progam檔案中加入這一行程式碼,就可以把指定程式集中所有實現了IRequest<>和INotification的訊息類自動生成WebAPI介面。

根據MediatR的Contract Messages自動生成Minimal WebApi介面

看起來很神奇,其實也不神奇。主要就是兩個字:反射。還有泛型。

簡單來說,就是在指定程式集中,透過反射查詢那些類實現了IRequest<>或者INotification,然後在透過對泛型對映WebAPI方法的反射呼叫,為每個訊息類生成WebApi介面。

Let me show you the code:

using MediatR;
using MediatRApplication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Reflection;
using System.Xml.Linq;

namespace MediatRWebAPI
{
    public static class MediatorWebAPIExtensions
    {
        /// <summary>
        /// 擴充套件方法,為所有MediatR Contract 訊息類建立WebAPI介面
        /// </summary>
        /// <param name="app"></param>
        /// <param name="assemblies">Contract 訊息類所在程式集</param>
        /// <returns></returns>
        public static IEndpointRouteBuilder MapMediatorWebAPIs(this IEndpointRouteBuilder app, params Assembly[] assemblies)
        {
            //為所有實現了IRequest<>的訊息類建立WebAPI介面
            Type genericRequestType = typeof(IRequest<>);
            var sendMethodInfo = typeof(MediatorWebAPIExtensions).GetMethod("MapMediatorSendApi", BindingFlags.NonPublic | BindingFlags.Static);
            foreach (var assembly in assemblies)
            {
                //獲取該程式集中所有實現了IRequest<>的訊息類型別
                var requestTypes = assembly.GetTypes().Where(type => !type.IsInterface && type.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == genericRequestType));
                foreach (var requestType in requestTypes)
                {
                    //獲取IRequest<>中尖括號中的泛型引數型別。
                    var responseType = requestType.GetInterfaces().First(t => t.IsGenericType && t.GetGenericTypeDefinition() == genericRequestType).GetGenericArguments().First();
                    //反射呼叫泛型對映WebApi方法
                    var genericMethod = sendMethodInfo.MakeGenericMethod(requestType, responseType);
                    genericMethod.Invoke(null, new object[] { app, requestType.Name });
                }

            }
            //為所有實現了INotification的訊息類建立WebAAPI介面
            Type genericNotificationType = typeof(INotification);
            var publishMethodInfo = typeof(MediatorWebAPIExtensions).GetMethod("MapMediatorPublishApi", BindingFlags.NonPublic | BindingFlags.Static);
            foreach (var assembly in assemblies)
            {
                //獲取該程式集中所有實現了INotification的訊息類型別
                var requestTypes = assembly.GetTypes().Where(type => !type.IsInterface && genericNotificationType.IsAssignableFrom(type));
                foreach (var requestType in requestTypes)
                {
                    //反射呼叫泛型對映WebApi方法
                    var genericMethod = publishMethodInfo.MakeGenericMethod(requestType);
                    genericMethod.Invoke(null, new object[] { app, requestType.Name });
                }

            }

            return app;
        }


        /// <summary>
        /// 為實現了IRequest<>的訊息類為對映為WebAPI介面,根據訊息類名稱生成對應的CRUDD Http Method。
        /// </summary>
        /// <typeparam name="TRequest"></typeparam>
        /// <typeparam name="TResponse"></typeparam>
        /// <param name="app"></param>
        /// <param name="requestTypeName"></param>
        internal static void MapMediatorSendApi<TRequest, TResponse>(IEndpointRouteBuilder app, string requestTypeName) where TRequest : IRequest<TResponse>
        {
            if (requestTypeName.StartsWith("Create")) //Http Post
            {
                var uri = new Uri(requestTypeName.Replace("Create", ""), UriKind.Relative);
                app.MapPost(uri.ToString(), async ([FromServices] IMediator mediator, [FromBody] TRequest request) =>
                {
                    TResponse response = await mediator.Send(request);
                    return Results.Created(uri, response);
                }).WithName(requestTypeName).WithOpenApi();
            }
            else if (requestTypeName.StartsWith("Read")) //Http Get
            {
                var uri = new Uri(requestTypeName.Replace("Read", ""), UriKind.Relative);
                app.MapGet(uri.ToString(), async ([FromServices] IMediator mediator, [FromBody] TRequest request) =>
                {
                    TResponse response = await mediator.Send(request);
                    return Results.Ok(response);
                }).WithName(requestTypeName).WithOpenApi();
            }
            else if (requestTypeName.StartsWith("Update")) //Http Put
            {
                var uri = new Uri(requestTypeName.Replace("Update", ""), UriKind.Relative);
                app.MapPut(uri.ToString(), async ([FromServices] IMediator mediator, [FromBody] TRequest request) =>
                {
                    TResponse response = await mediator.Send(request);
                    return Results.Ok(response);
                }).WithName(requestTypeName).WithOpenApi();
            }
            else if (requestTypeName.StartsWith("Delete")) //Http Delete
            {
                var uri = new Uri(requestTypeName.Replace("Delete", ""), UriKind.Relative);
                app.MapDelete(uri.ToString(), async ([FromServices] IMediator mediator, [FromBody] TRequest request) =>
                {
                    TResponse response = await mediator.Send(request);
                    return Results.NoContent();
                }).WithName(requestTypeName).WithOpenApi();
            }
            else  //如不匹配則生成MediatR Send WebAPI介面
            {
                app.MapPost("/mediatr/send/" + requestTypeName, async ([FromServices] IMediator mediator, [FromBody] TRequest request) =>
                {
                    TResponse response = await mediator.Send(request);
                    return Results.Ok(response);
                }).WithName(requestTypeName).WithOpenApi();
            }
        }

        /// <summary>
        /// 為實現了INotification的訊息類對映WebAPI介面。
        /// </summary>
        /// <typeparam name="TNotification"></typeparam>
        /// <param name="app"></param>
        /// <param name="requestTypeName"></param>
        internal static void MapMediatorPublishApi<TNotification>(IEndpointRouteBuilder app, string requestTypeName) where TNotification : INotification
        {
            app.MapPost("/mediatr/publish/" + requestTypeName, async ([FromServices] IMediator mediator, [FromBody] TNotification notification) =>
            {
                await mediator.Publish(notification);
                return Results.Ok();
            }).WithName(requestTypeName).WithOpenApi();
        }
    }
}

如上就是實現這個功能的所有程式碼,為了讓大家看明白,我加了很多註釋。如果哪位小夥伴還不明白就在下面留言。這些程式碼最難的地方就是對於泛型介面的處理。

我的示例專案如下,程式碼已經上傳到了GitHub :iamxiaozhuang/MediatRWebAPI (github.com)  大家隨便用。

根據MediatR的Contract Messages自動生成Minimal WebApi介面