前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >(转载非原创)由ASP.NET Core WebApi添加Swagger报错引发的探究

(转载非原创)由ASP.NET Core WebApi添加Swagger报错引发的探究

作者头像
xlj
修改2021-07-12 09:48:01
1.4K0
修改2021-07-12 09:48:01
举报
文章被收录于专栏:XLJ的技术专栏XLJ的技术专栏

由ASP.NET Core WebApi添加Swagger报错引发的探究

缘起#

    在使用ASP.NET Core进行WebApi项目开发的时候,相信很多人都会使用Swagger作为接口文档呈现工具。相信大家也用过或者了解过Swagger,这里咱们就不过多的介绍了。本篇文章记录一下,笔者在使用ASP.NET Core开发Api的过程中,给接口整合Swagger过程中遇到的一个异常,笔者抱着好奇的心态研究了一下异常的原因,并解决了这个问题。在这个过程中笔者学到了一些新的技能,得到了一些新的知识,便打算记录一下,希望能帮助到更多的人。

示例#

    从项目渊源上说起,笔者所在项目,很多都是从.Net FrameWork的老项目迁移到ASP.NET Core上来的,这其中做了很多兼容的处理,来保证尽量不修改原有的业务代码,这其中就包含了WebApi相关的部分,这里我们用简单的示例描述现有WebApi的Controller的情况,大致写法如下

代码语言:javascript
复制
[Route("api/[controller]/[action]")]
代码语言:javascript
复制
[ApiController]
代码语言:javascript
复制
public class OrderController : ControllerBase
代码语言:javascript
复制
{
代码语言:javascript
复制
    private List<OrderDto> orderDtos = new List<OrderDto>();
代码语言:javascript
复制
    public OrderController()
代码语言:javascript
复制
    {
代码语言:javascript
复制
        orderDtos.Add(new OrderDto { Id = 1,TotalMoney=222,Address="北京市",Addressee="me",From="淘宝",SendAddress="武汉" });
代码语言:javascript
复制
        orderDtos.Add(new OrderDto { Id = 2, TotalMoney = 111, Address = "北京市", Addressee = "yi", From = "京东", SendAddress = "北京" });
代码语言:javascript
复制
        orderDtos.Add(new OrderDto { Id = 3, TotalMoney = 333, Address = "北京市", Addressee = "yi念之间", From = "天猫", SendAddress = "杭州" });
代码语言:javascript
复制
    }
代码语言:javascript
复制
    /// <summary>
代码语言:javascript
复制
    /// 获取订单数据
代码语言:javascript
复制
    /// </summary>
代码语言:javascript
复制
    public OrderDto Get(long id)
代码语言:javascript
复制
    {
代码语言:javascript
复制
        return orderDtos.FirstOrDefault(i => i.Id == id);
代码语言:javascript
复制
    }
代码语言:javascript
复制
    /// <summary>
代码语言:javascript
复制
    /// 添加订单数据
代码语言:javascript
复制
    /// </summary>
代码语言:javascript
复制
    public IActionResult Add(OrderDto orderDto)
代码语言:javascript
复制
    {
代码语言:javascript
复制
        orderDtos.Add(orderDto);
代码语言:javascript
复制
        return Ok();
代码语言:javascript
复制
    }
代码语言:javascript
复制
    /// <summary>
代码语言:javascript
复制
    /// 添加订单数据
代码语言:javascript
复制
    /// </summary>
代码语言:javascript
复制
    public IActionResult Edit(long id, OrderDto orderDto)
代码语言:javascript
复制
    {
代码语言:javascript
复制
        var order = orderDtos.FirstOrDefault(i => i.Id == id);
代码语言:javascript
复制
        if (order == null)
代码语言:javascript
复制
        {
代码语言:javascript
复制
            return NotFound();
代码语言:javascript
复制
        }
代码语言:javascript
复制
        order.Address = orderDto.Address;
代码语言:javascript
复制
        order.From = orderDto.From;
代码语言:javascript
复制
        return Ok();
代码语言:javascript
复制
    }
代码语言:javascript
复制
    /// <summary>
代码语言:javascript
复制
    /// 删除订单数据
代码语言:javascript
复制
    /// </summary>
代码语言:javascript
复制
    public IActionResult Delete(long id)
代码语言:javascript
复制
    {
代码语言:javascript
复制
        var order = orderDtos.FirstOrDefault(i=>i.Id==id);
代码语言:javascript
复制
        if (order == null)
代码语言:javascript
复制
        {
代码语言:javascript
复制
            return NotFound();
代码语言:javascript
复制
        }
代码语言:javascript
复制
        orderDtos.Remove(order);
代码语言:javascript
复制
        return Ok();
代码语言:javascript
复制
    }
代码语言:javascript
复制
}

虽然是笔者写的demo,但是大致是这种形式,而且直接通过ASP.NET Core运行起来也没有任何的问题,调用也不会出现任何异常。当项目开发完成后,给项目添加Swagger,笔者用的是Swashbuckle.AspNetCore这个组件,添加Swagger的方式大致如下,首先是在Startup类的ConfigureServices方法中添加以下代码

代码语言:javascript
复制
services.AddSwaggerGen(c =>
代码语言:javascript
复制
{
代码语言:javascript
复制
    c.SwaggerDoc("v1", new OpenApiInfo
代码语言:javascript
复制
    {
代码语言:javascript
复制
        Title = "OrderApi",
代码语言:javascript
复制
        Description = "订单服务接口"
代码语言:javascript
复制
    });
代码语言:javascript
复制
    var xmlCommentFile = $"{AppContext.BaseDirectory}OrderApi.xml";
代码语言:javascript
复制
    if (File.Exists(xmlCommentFile))
代码语言:javascript
复制
    {
代码语言:javascript
复制
        c.IncludeXmlComments(xmlCommentFile);
代码语言:javascript
复制
    }
代码语言:javascript
复制
});

添加完成之后,在Configure方法中开启Swagger中间件,具体代码如下

代码语言:javascript
复制
app.UseSwagger();
代码语言:javascript
复制
app.UseSwaggerUI(c =>
代码语言:javascript
复制
{
代码语言:javascript
复制
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "OrderApi");
代码语言:javascript
复制
});

添加完成之后,运行起来项目打开Swagger地址http://localhost:5000/swagger结果直接弹出了一个红色浮窗,看样子有异常,打开.Net Core控制台窗口看到了如下异常

代码语言:javascript
复制
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1] An unhandled exception has occurred while executing the request.
代码语言:javascript
复制
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Ambiguous HTTP method for action OrderApi.Controllers.OrderController.Get (OrderApi). 
代码语言:javascript
复制
Actions require an explicit HttpMethod binding for Swagger/OpenAPI 3.0
代码语言:javascript
复制
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
代码语言:javascript
复制
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
代码语言:javascript
复制
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String documentName, String host, String basePath)
代码语言:javascript
复制
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
代码语言:javascript
复制
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

其中核心的关键词汇就是Ambiguous HTTP method for action OrderApi.Controllers.OrderController.Get (OrderApi). Actions require an explicit HttpMethod binding for Swagger/OpenAPI 3.0笔者用尽毕生的英语修为,了解到其大概意思是Swagger/OpenAPI 3.0要求Action上必须绑定HttpMethod相关Attribute,否则就报这一大堆错误。这里的HttpMethod其实就是咱们常用HttpGetHttpPostHttpPutHttpDelete相关的Attribute。 正常逻辑来说那就给每个Action添加HttpMethod呗,但是往往情况就出现在不正常的时候。因为项目是迁移的老项目,先不说私自改了别人代码带来的甩锅问题,公司的WebApi项目很多,这意味着Action很多,如果一个项目一个项目的去找Action添加HttpMethod可是一个不小的工作量,而且开发人员工作繁忙,基本上不会抽出来时间去修改这些的,因为这种只是Swagger不行,但是对于WebApi本身来说这种写法没有任何的问题,也不会报错,只是看起来不规范。那该怎么办呢?

探究源码#

又看了看异常决定从源码入手,通过控制台报出的异常可以看到报错的最初位置是在Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable1 apiDescriptions, SchemaRepository schemaRepository)`那就从这里准备入手了。

Swashbuckle.AspNetCore入手#

在GitHub上找到Swashbuckle.AspNetCore仓库位置,近期GitHub不太稳定,除了梯子貌似也没有很好的办法,多刷新几次将就着用吧,由异常信息可知抛出异常所在的位置SwaggerGenerator类的GenerateOperations方法直接找到源码位置[点击查看源码👈]代码如下

代码语言:javascript
复制
private IDictionary<OperationType, OpenApiOperation> GenerateOperations(IEnumerable<ApiDescription> apiDescriptions,
代码语言:javascript
复制
            SchemaRepository schemaRepository)
代码语言:javascript
复制
{
代码语言:javascript
复制
    //根据HttpMethod分组
代码语言:javascript
复制
    var apiDescriptionsByMethod = apiDescriptions
代码语言:javascript
复制
        .OrderBy(_options.SortKeySelector)
代码语言:javascript
复制
        .GroupBy(apiDesc => apiDesc.HttpMethod);
代码语言:javascript
复制
    var operations = new Dictionary<OperationType, OpenApiOperation>();
代码语言:javascript
复制
    foreach (var group in apiDescriptionsByMethod)
代码语言:javascript
复制
    {
代码语言:javascript
复制
        var httpMethod = group.Key;
代码语言:javascript
复制
        if (httpMethod == null)
代码语言:javascript
复制
            //异常位置在这里
代码语言:javascript
复制
            throw new SwaggerGeneratorException(string.Format(
代码语言:javascript
复制
                "Ambiguous HTTP method for action - {0}. " +
代码语言:javascript
复制
                "Actions require an explicit HttpMethod binding for Swagger/OpenAPI 3.0",
代码语言:javascript
复制
                group.First().ActionDescriptor.DisplayName));
代码语言:javascript
复制
        if (group.Count() > 1 && _options.ConflictingActionsResolver == null)
代码语言:javascript
复制
            throw new SwaggerGeneratorException(string.Format(
代码语言:javascript
复制
                "Conflicting method/path combination \"{0} {1}\" for actions - {2}. " +
代码语言:javascript
复制
                "Actions require a unique method/path combination for Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround",
代码语言:javascript
复制
                httpMethod,
代码语言:javascript
复制
                group.First().RelativePathSansQueryString(),
代码语言:javascript
复制
                string.Join(",", group.Select(apiDesc => apiDesc.ActionDescriptor.DisplayName))));
代码语言:javascript
复制
        var apiDescription = (group.Count() > 1) ? _options.ConflictingActionsResolver(group) : group.Single();
代码语言:javascript
复制
        operations.Add(OperationTypeMap[httpMethod.ToUpper()], GenerateOperation(apiDescription, schemaRepository));
代码语言:javascript
复制
    };
代码语言:javascript
复制
    return operations;
代码语言:javascript
复制
}

httpMethod属性的数据源来自IEnumerable<ApiDescription>集合,顺着调用关系往上找,最后发现ApiDescription来自IApiDescriptionGroupCollectionProvider而它来自于构造函数注入进来的

代码语言:javascript
复制
private readonly IApiDescriptionGroupCollectionProvider _apiDescriptionsProvider;
代码语言:javascript
复制
private readonly ISchemaGenerator _schemaGenerator;
代码语言:javascript
复制
private readonly SwaggerGeneratorOptions _options;
代码语言:javascript
复制
public SwaggerGenerator(
代码语言:javascript
复制
    SwaggerGeneratorOptions options,
代码语言:javascript
复制
    IApiDescriptionGroupCollectionProvider apiDescriptionsProvider,
代码语言:javascript
复制
    ISchemaGenerator schemaGenerator)
代码语言:javascript
复制
{
代码语言:javascript
复制
    _options = options ?? new SwaggerGeneratorOptions();
代码语言:javascript
复制
    _apiDescriptionsProvider = apiDescriptionsProvider;
代码语言:javascript
复制
    _schemaGenerator = schemaGenerator;
代码语言:javascript
复制
}

看名字也知道IApiDescriptionGroupCollectionProvider是专门服务于Api描述相关的,在Swashbuckle.AspNetCore仓库中造了下没发现相关定义,于是用VS找到引用发现定义如下

代码语言:javascript
复制
namespace Microsoft.AspNetCore.Mvc.ApiExplorer
代码语言:javascript
复制
{
代码语言:javascript
复制
    public interface IApiDescriptionGroupCollectionProvider
代码语言:javascript
复制
    {
代码语言:javascript
复制
        ApiDescriptionGroupCollection ApiDescriptionGroups { get; }
代码语言:javascript
复制
    }
代码语言:javascript
复制
}
转战aspnetcore#

看命名空间IApiDescriptionGroupCollectionProvider居然是AspNetCore.Mvc下的,也就是说来自AspNetCore自身,跑到AspNetCore的核心仓库搜索了一下代码找到如下位置代码[点击查看源码👈]

代码语言:javascript
复制
internal static void AddApiExplorerServices(IServiceCollection services)
代码语言:javascript
复制
{
代码语言:javascript
复制
    services.TryAddSingleton<IApiDescriptionGroupCollectionProvider, ApiDescriptionGroupCollectionProvider>();
代码语言:javascript
复制
    services.TryAddEnumerable(
代码语言:javascript
复制
        ServiceDescriptor.Transient<IApiDescriptionProvider, DefaultApiDescriptionProvider>());
代码语言:javascript
复制
}

而AddApiExplorerServices方法是在当前类的AddApiExplorer扩展方法中被调用的

代码语言:javascript
复制
public static IMvcCoreBuilder AddApiExplorer(this IMvcCoreBuilder builder)
代码语言:javascript
复制
{
代码语言:javascript
复制
    AddApiExplorerServices(builder.Services);
代码语言:javascript
复制
    return builder;
代码语言:javascript
复制
}

看到IMvcCoreBuilder接口,我们就应该感觉到这是Mvc的核心接口扩展方法,但是趋于好奇心还是往上找了一下,发现确实是跟着ASP.NET Core土生土长的实现,最终位置如下[点击查看源码👈]

代码语言:javascript
复制
private static IMvcCoreBuilder AddControllersCore(IServiceCollection services)
代码语言:javascript
复制
{
代码语言:javascript
复制
    return services
代码语言:javascript
复制
        .AddMvcCore()
代码语言:javascript
复制
        .AddApiExplorer()
代码语言:javascript
复制
        .AddAuthorization()
代码语言:javascript
复制
        .AddCors()
代码语言:javascript
复制
        .AddDataAnnotations()
代码语言:javascript
复制
        .AddFormatterMappings();
代码语言:javascript
复制
}

微软想的还是比较周到的,居然在ASP.NET Core的核心位置,加入了IApiDescriptionGroupCollectionProvider这种操作,在IApiDescriptionGroupCollectionProvider的示例中包含了当前Api项目有关Controller和Action相关的信息,而Swagger的Doc文档也就是咱们看到的swagger.json正是基于这些数据信息组装而来。

IApiDescriptionGroupCollectionProvider还是比较实用,如果在不知道这个操作存在的情况下,我们获取WebApi的Controller或Action相关的信息,首先想到的就是反射Controller得到这些,如今有了IApiDescriptionGroupCollectionProvider我们可以在IOC容器中直接获取这个接口的实例,获取Controller和Action的信息。

解决问题#

我们找到了问题的根源,可以下手解决问题了,其本质问题是Swagger通过ApiDescription获取Action的HttpMethod信息,但是我们项目由于各种原因,在Action上并没有添加HttpMethod相关的Attribute,所以我们只能从ApiDescription入手,好在我们可以在IOC容器中获取到IApiDescriptionGroupCollectionProvider的实例,从这里入手扩展一个方法,具体实现如下

代码语言:javascript
复制
/// <summary>
代码语言:javascript
复制
/// action没有httpmethod attribute的情况下根据action的开头名称给与默认值
代码语言:javascript
复制
/// </summary>
代码语言:javascript
复制
/// <param name="app">IApplicationBuilder</param>
代码语言:javascript
复制
/// <param name="defaultHttpMethod">默认给定的HttpMethod</param>
代码语言:javascript
复制
public static void AutoHttpMethodIfActionNoBind(this IApplicationBuilder app, string defaultHttpMethod = null)
代码语言:javascript
复制
{
代码语言:javascript
复制
    //从容器中获取IApiDescriptionGroupCollectionProvider实例
代码语言:javascript
复制
    var apiDescriptionGroupCollectionProvider = app.ApplicationServices.GetRequiredService<IApiDescriptionGroupCollectionProvider>();
代码语言:javascript
复制
    var apiDescriptionGroupsItems = apiDescriptionGroupCollectionProvider.ApiDescriptionGroups.Items;
代码语言:javascript
复制
    //遍历ApiDescriptionGroups
代码语言:javascript
复制
    foreach (var apiDescriptionGroup in apiDescriptionGroupsItems)
代码语言:javascript
复制
    {
代码语言:javascript
复制
        foreach (var apiDescription in apiDescriptionGroup.Items)
代码语言:javascript
复制
        {
代码语言:javascript
复制
            if (string.IsNullOrEmpty(apiDescription.HttpMethod))
代码语言:javascript
复制
            {
代码语言:javascript
复制
                //获取Action名称
代码语言:javascript
复制
                var actionName = apiDescription.ActionDescriptor.RouteValues["action"];
代码语言:javascript
复制
                //默认给定POST
代码语言:javascript
复制
                string methodName = defaultHttpMethod ?? "POST";
代码语言:javascript
复制
                //根据Action开头单词给定HttpMethod默认值
代码语言:javascript
复制
                if (actionName.StartsWith("get", StringComparison.OrdinalIgnoreCase))
代码语言:javascript
复制
                {
代码语言:javascript
复制
                    methodName = "GET";
代码语言:javascript
复制
                }
代码语言:javascript
复制
                else if (actionName.StartsWith("put", StringComparison.OrdinalIgnoreCase))
代码语言:javascript
复制
                {
代码语言:javascript
复制
                    methodName = "PUT";
代码语言:javascript
复制
                }
代码语言:javascript
复制
                else if (actionName.StartsWith("delete", StringComparison.OrdinalIgnoreCase))
代码语言:javascript
复制
                {
代码语言:javascript
复制
                    methodName = "DELETE";
代码语言:javascript
复制
                }
代码语言:javascript
复制
                apiDescription.HttpMethod = methodName;
代码语言:javascript
复制
            }
代码语言:javascript
复制
        }
代码语言:javascript
复制
    }
代码语言:javascript
复制
}

写完上面的代码后,抱着试试看的心情,因为不清楚这波操作好不好使,将扩展方法引入到Configure方法中,为了清晰和Swagger中间件放到一起后,效果如下

代码语言:javascript
复制
if (!env.IsProduction())
代码语言:javascript
复制
{
代码语言:javascript
复制
    app.UseSwagger();
代码语言:javascript
复制
    app.UseSwaggerUI(c =>
代码语言:javascript
复制
    {
代码语言:javascript
复制
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "OrderApi");
代码语言:javascript
复制
    });
代码语言:javascript
复制
    //给没有配置httpmethod的action添加默认操作
代码语言:javascript
复制
    app.AutoHttpMethodIfActionNoBind();
代码语言:javascript
复制
}

加完之后重新运行项目,打开swagger地址http://localhost:5000/swagger没有异常,在Swagger上调用了接口试了一下,没有任何问题。这样的话可以做到只添加一个扩展方法就能解决问题,而不需要挨个Action进行添加HttpMethod。如果想需要更智能的判断Action默认的HttpMethod需要如何定位,直接修改AutoHttpMethodIfActionNoBind扩展方法,因为我们WebApi项目的Action大部分调用方式都是HttpPost,所以这里的逻辑我写的比较简单。

后续小插曲#

通过上面的方式解决了Swagger报错之后,在后来无意中翻看Swashbuckle.AspNetCore文档的时候发现了IDocumentFilter这个Swagger过滤器,想着如果能通过过滤器的方式去解决这个问题会更优雅。我们都知道过滤器的作用,而这个过滤器通过看名字我们可以知道他是在生成SwaggerDoc的时候可以对Doc数据进行处理,于是尝试写了一个过滤器,实现如下

代码语言:javascript
复制
public class AutoHttpMethodOperationFitler : IDocumentFilter
代码语言:javascript
复制
{
代码语言:javascript
复制
    private readonly string _defaultHttpMethod;
代码语言:javascript
复制
    public AutoHttpMethodOperationFitler(string defaultHttpMethod = null)
代码语言:javascript
复制
    {
代码语言:javascript
复制
        _defaultHttpMethod = defaultHttpMethod;
代码语言:javascript
复制
    }
代码语言:javascript
复制
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
代码语言:javascript
复制
    {
代码语言:javascript
复制
        //通过DocumentFilterContext上下文可以获取到ApiDescription集合
代码语言:javascript
复制
        foreach (var apiDescription in context.ApiDescriptions)
代码语言:javascript
复制
        {
代码语言:javascript
复制
            //为null说明没有给Action添加HttpMethod
代码语言:javascript
复制
            if (string.IsNullOrEmpty(apiDescription.HttpMethod))
代码语言:javascript
复制
            {
代码语言:javascript
复制
                //这些逻辑是和AutoHttpMethodIfActionNoBind扩展方法保持一致的
代码语言:javascript
复制
                var actionName = apiDescription.ActionDescriptor.RouteValues["action"];
代码语言:javascript
复制
                string methodName = "POST";
代码语言:javascript
复制
                if (actionName.StartsWith("get", StringComparison.OrdinalIgnoreCase))
代码语言:javascript
复制
                {
代码语言:javascript
复制
                    methodName = "GET";
代码语言:javascript
复制
                }
代码语言:javascript
复制
                else if (actionName.StartsWith("put", StringComparison.OrdinalIgnoreCase))
代码语言:javascript
复制
                {
代码语言:javascript
复制
                    methodName = "PUT";
代码语言:javascript
复制
                }
代码语言:javascript
复制
                else if (actionName.StartsWith("delete", StringComparison.OrdinalIgnoreCase))
代码语言:javascript
复制
                {
代码语言:javascript
复制
                    methodName = "DELETE";
代码语言:javascript
复制
                }
代码语言:javascript
复制
                apiDescription.HttpMethod = methodName;
代码语言:javascript
复制
            }
代码语言:javascript
复制
        }
代码语言:javascript
复制
    }
代码语言:javascript
复制
}

编写完成之后再AddSwaggerGen方法中注册AutoHttpMethodOperationFitler过滤器,如下所示

代码语言:javascript
复制
services.AddSwaggerGen(c =>
代码语言:javascript
复制
{
代码语言:javascript
复制
    c.SwaggerDoc("v1", new OpenApiInfo
代码语言:javascript
复制
    {
代码语言:javascript
复制
        Title = "OrderApi",
代码语言:javascript
复制
        Description = "订单服务接口"
代码语言:javascript
复制
    });
代码语言:javascript
复制
    //这里注册DocumentFilter
代码语言:javascript
复制
    c.DocumentFilter<AutoHttpMethodOperationFitler>();
代码语言:javascript
复制
    var xmlCommentFile = $"{AppContext.BaseDirectory}OrderApi.xml";
代码语言:javascript
复制
    if (File.Exists(xmlCommentFile))
代码语言:javascript
复制
    {
代码语言:javascript
复制
        c.IncludeXmlComments(xmlCommentFile);
代码语言:javascript
复制
    }
代码语言:javascript
复制
});

忙活完这一波之后注释掉AutoHttpMethodOperationFitler扩展方法,添加AutoHttpMethodOperationFitler过滤器,然后运行一波,打开Swagger地址。不过很遗憾还是会报Actions require an explicit HttpMethod binding for Swagger/OpenAPI 3.0这个异常,想了想为啥还会报这个异常无果后,决定还是翻看源码看一下,这一看果然找到了原因,代码如下[点击查看源码👈]

代码语言:javascript
复制
var swaggerDoc = new OpenApiDocument
代码语言:javascript
复制
{
代码语言:javascript
复制
    Info = info,
代码语言:javascript
复制
    Servers = GenerateServers(host, basePath),
代码语言:javascript
复制
    //出现异常的代码方法在这里被调用
代码语言:javascript
复制
    Paths = GeneratePaths(applicableApiDescriptions, schemaRepository),
代码语言:javascript
复制
    Components = new OpenApiComponents
代码语言:javascript
复制
    {
代码语言:javascript
复制
        Schemas = schemaRepository.Schemas,
代码语言:javascript
复制
        SecuritySchemes = new Dictionary<string, OpenApiSecurityScheme>(_options.SecuritySchemes)
代码语言:javascript
复制
    },
代码语言:javascript
复制
    SecurityRequirements = new List<OpenApiSecurityRequirement>(_options.SecurityRequirements)
代码语言:javascript
复制
};
代码语言:javascript
复制
//执行IDocumentFilter Apply方法的地方在这里
代码语言:javascript
复制
var filterContext = new DocumentFilterContext(applicableApiDescriptions, _schemaGenerator, schemaRepository);
代码语言:javascript
复制
foreach (var filter in _options.DocumentFilters)
代码语言:javascript
复制
{
代码语言:javascript
复制
    filter.Apply(swaggerDoc, filterContext);
代码语言:javascript
复制
}

通过上面的源码可以看到,针对数据源信息是否规范的校验,是在执行IDocumentFilter过滤器的Apply方法之前进行的,所以我们在DocumentFilter处理HttpMethod的问题是解决不了的。到这里自己也明白了AutoHttpMethodOperationFitler目前是解决这个问题能想到的最好方式,暂时算是没啥遗憾了。

总结#

    本篇文章讲解了在给ASP.NET Core添加Swagger的时候遇到的一个异常而引发的对相关源码的探究,并最终解决这个问题,这里我们Get到了一个比较实用的技能,ASP.NET Core内置了IApiDescriptionGroupCollectionProvider实现,通过它我们可以很便捷的获取到WebApi中关于Controller和Action的元数据信息,而这些信息方便我们生成帮助文档或者生成调用代码是非常实用的。如果你对源码感兴趣,或者有通过看源码解决问题的意识的话,这种方式还是比较有效的,因为我们作为程序员最懂的还是代码,而代码的报错当然也得看着代码解决。解决这类问题也没啥特别好的技巧,通过异常堆栈找到报错的原始位置,顺序需要用到的代码一步一步的往上找,直到找到源头。而这也正是看源码的乐趣,要么好奇驱使,要么解决问题。更好的理解代码,就有更好的方式解决问题,就比如我没办法挨个给Action添加HttpMethod所以找到另一个途径解决问题。

转载来源:https://www.cnblogs.com/wucy/p/14978259.html

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 由ASP.NET Core WebApi添加Swagger报错引发的探究
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档