根据MediatR的Contract Messages自动生成Minimal WebApi接口
阅读原文时间:2023年07月10日阅读:3

大家好,我是失业在家,正在找工作的博主Jerry。今天给大家介绍一个能大大减少ASP.Net Minimal WebApi编码量的方法。

我们一般会把微服务的VO和DTO封装成消息类,并作为WebApi的Request和Response参数进行网络传递。

如果使用MediatR,我们封装的消息类就要实现 MediatR Contract 接口 IRequest<> 或者INotification,  例如我的代码如下:

namespace MediatRApplication.CategoryCRUD
{
public class CreateCategory : IRequest
{
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接口。

看起来很神奇,其实也不神奇。主要就是两个字:反射。还有泛型。

简单来说,就是在指定程序集中,通过反射查找那些类实现了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
{
///

/// 扩展方法,为所有MediatR Contract 消息类创建WebAPI接口 ///
///
/// Contract 消息类所在程序集
///
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)  大家随便用。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章