武装你的WEBAPI-OData之API版本管理

本文属于OData系列

Intro

对外提供WEBAPI时,如果遇上了版本升级,那么控制WEBAPI的版本也是非常必要的。OData官方提供了版本控制以及管理的解决方案,我个人是实践体会是不好用,好在社区提供了对应的nuget包,与.NET主版本同步更新。

介绍

ASP.NET API Versioning是一个提供ASP.NET WEBAPI版本管理的包,支持ASP.NET、ASP.NET CORE、ASP.NET CORE ODATA,作者以前是微软的员工,现在不在微软工作了,因此原先的命名空间不能继续用了。现在这个项目已经加入.NET Foundation,作者也非常活跃。

版本管理

首先对现有的项目安装这个包:

Install-Package Asp.Versioning.OData

在Program.cs文件中修改一下:

var builder = WebApplication.CreateBuilder( args );

builder.Services.AddControllers().AddOData();
builder.Services.AddProblemDetails();
builder.Services.AddApiVersioning().AddOData(
    options =>
    {
        options.AddRouteComponents();
    } );

var app = builder.Build();

app.MapControllers();
app.Run();

然后在需要控制版本的控制器上加上[ApiVersion]修饰就可以了。

[ApiVersion( 1.0 )]
public class PeopleController : ODataController
{
    [EnableQuery]
    public IActionResult Get() => Ok( new[] { new Person() } );
}

注意,默认的版本是1.0,不过最好显式声明一下。

EDM升级

EDM根据版本不同也会有一些区别,需要分别进行配置,原来的GetEdm()模式显得有点麻烦,而EDM配置在这个库中变得非常灵活,使用的是Configuration模式。

示例代码如下:

public class DeviceInfoModelConfiguration : IModelConfiguration
{
	public void Apply(ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix)
	{
		switch (apiVersion.MajorVersion)
		{
			case 1:
				builder.EntitySet<DeviceInfo>("DeviceInfoes").EntityType.HasKey(p => p.DeviceId);
				break;
			case 2:
				builder.EntitySet<DeviceInfo>("DeviceInfos").EntityType.HasKey(p => p.DeviceId).Ignore(w => w.Layout);
				break;
			default:
				break;
		};
	}
}

只需要实现IModelConfiguration接口,并在Apply函数中根据版本对实体或者DTO对象进行配置,不同版本的EDM可以不一样。

一般实践是一个实体对象一个IModelConfiguration,方便后面管理。

配置Swagger

因为有重复配置的模型,直接使用默认的Swagger会报错,这个时候需要使用到Versioned API Explorer,对Swagger拓展版本信息。

Install-Package Asp.Versioning.OData.ApiExplorer

安装Asp.Versioning.OData.ApiExplorer,重新改造一下Program.cs文件:

var builder = WebApplication.CreateBuilder( args );

builder.Services.AddControllers().AddOData();
builder.Services.AddProblemDetails();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddApiVersioning()
                .AddOData( options => options.AddRouteComponents() )
                .AddODataApiExplorer(
                     // format the version as "'v'major[.minor][-status]"
                     options => options.GroupNameFormat = "'v'VVV" );

services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
services.AddSwaggerGen();

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI(
    options =>
    {
        foreach ( var description in app.DescribeApiVersions() )
        {
            var url = $"/swagger/{description.GroupName}/swagger.json";
            var name = description.GroupName.ToUpperInvariant();
            options.SwaggerEndpoint( url, name );
        }
    } );
app.MapControllers();
app.Run();

还需要一个配置的类如下:

public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
  readonly IApiVersionDescriptionProvider provider;

  public ConfigureSwaggerOptions( IApiVersionDescriptionProvider provider ) =>
    this.provider = provider;

  public void Configure( SwaggerGenOptions options )
  {
    foreach ( var description in provider.ApiVersionDescriptions )
    {
      options.SwaggerDoc(
        description.GroupName,
          new OpenApiInfo()
          {
            Title = $"Example API {description.ApiVersion}",
            Version = description.ApiVersion.ToString(),
          } );
    }
  }
}

这样,swagger界面就可以下拉选择不同版本的API了。
武装你的WEBAPI-OData之API版本管理

旧系统升级

WEBAPI Versioning对这个内容有介绍,其中需要注意的是,基于路径的版本匹配并不支持默认版本的特性,对于以前系统直接使用api/开头的控制器,并不能直接默认转到到api/v1(参考介绍)。为了兼容旧系统,我们只能在ASP.NET CORE的管线上想想办法:插入一个中间件,对路径进行判断,如果是api开头的,就直接转到api/v1;如果是api/v开头的,那么就直接下一步。

    public class RedirectMiddlewareForV1
    {
        private readonly RequestDelegate _next;

        public RedirectMiddlewareForV1(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            if (context.Request.Path.StartsWithSegments("/api") && !context.Request.Path.Value.StartsWith("/api/v"))
            {
                //千万小心,一定需要保留原来的QueryString
                var newUrl = $"{context.Request.Path.Value.Replace("/api/", "/api/v1/")}{context.Request.QueryString}";
                //permanent指示永久迁移,preserveMethod指示保留原来的谓词与body
                context.Response.Redirect(newUrl, permanent: true, preserveMethod: true);
            }
            else
            {
                await _next(context);
            }
        }


    }

然后在configure函数中注册这个中间件就可以了。

app.UseMiddleware<RedirectMiddlewareForV1>();

请注意:

  • context.Request.Path.StartsWithSegments函数只能匹配完整的路径词汇,/api/v2去匹配/api/v会返回false。
  • 另外需要了解HTTP 301/302/307/308之间的区别,如果需要保留原来的请求body,需要使用307/308,308是永久移动。
  • Redirect并不保留原来的QueryString,需要手动拼接。

FAQ

  1. 无法正确显示不同版本的Swagger,提示InvalidOperationException: Can't use schemaId "\(B" for type "\)A.B". The same schemaId is already used for type "$A.B"
    这个问题是由多次对同一个类型Schema生成造成的。最常见的情况是你的控制器有方法不属于OData Routing的一部分(比如直接使用HttpGet指定),这样程序在扫描的过程中会重复对对象进行生成。解决办法有两种:
  1. 无法加载Swagger,提示System.MissingMethodException: Method not found: 'Microsoft.OData.ModelBuilder.Config.DefaultQuerySettings Microsoft.AspNetCore.OData.ODataOptions.get_QuerySettings()
    这个是版本问题,本人使用的OData版本在8.1.0,有一些破坏性更改,只要保持引用的OData版本<= 8.0.12就可以了。
    详细分析看这个MissingMethodException with OData v8.1.0 · Issue #980 · dotnet/aspnet-api-versioning (github.com)
  2. 找不到DescribeApiVersions()方法
    app找不到这个方法,大概率是在.NET 6的Minimal API之前的代码升级出现的,之前app是用IWebhostBuilder构建的,而现在的app是直接用过WebApplication构建得到的,含义不同,最简单的方法是改造一下,使用WebApplication重写一下Startup内容。

参考

原文链接:https://www.cnblogs.com/podolski/p/17375269.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:武装你的WEBAPI-OData之API版本管理 - Python技术站

(0)
上一篇 2023年5月9日
下一篇 2023年5月10日

相关文章

  • C#图书管理系统 附源码下载

    项目简介 “C#图书管理系统 附源码下载”是一个基于C#语言和微软.NET框架的图书管理系统项目,插图库采用DevExpress控件。该项目是一个班级的课程设计作业,其目的是为了让学生们在实践中掌握C#语言和.NET框架的应用。 项目结构 该项目主要分为以下三个部分: 登录界面:用户需要先进行注册并登录才能进行图书管理操作。 图书查询/添加:用户可以查询已有…

    C# 2023年5月31日
    00
  • C#线程委托BeginInvoke与EndInvoke的用法

    当涉及到多线程编程时,往往需要使用BeginInvoke和EndInvoke这两个方法。 BeginInvoke允许我们异步执行代码块,并立即返回主线程,而EndInvoke则允许我们等待异步代码块的完成并检索其结果。 一、BeginInvoke和EndInvoke的基本用法 以下是BeginInvoke和EndInvoke基本用法的示例。假设有以下定义的委…

    C# 2023年5月15日
    00
  • C#实现IDisposable接口释放非托管资源

    下面是“C#实现IDisposable接口释放非托管资源”的完整攻略: 什么是IDisposable接口 IDisposable接口是一个管理非托管资源的机制,它允许程序员自行释放非托管资源。IDisposable接口包含Dispose()方法,该方法释放由实现对象持有的所有资源。 下面是实现IDisposable接口的步骤 实现IDisposable接口并…

    C# 2023年6月7日
    00
  • C#12预览版释出,新功能一览

    概述 C#是微软开发的一种流行的编程语言,广泛用于开发桌面,Web和移动应用程序。在每个新版本中,C# 都会带来令人兴奋的功能和改进,使其更强大、更具表现力和更高效。C# 的最新版本是2022年发布的 C#11,它引入了一系列新功能,例如abstract 和 virtual 引入到静态方法中、泛型 attribute等。现在,微软已经在开发下一个版本的C#,…

    C# 2023年4月19日
    00
  • C#实现Socket通信的解决方法

    C#实现Socket通信的解决方法,可以分为以下几个步骤: 步骤一:创建Socket对象 首先,需要使用C#提供的System.Net命名空间下的Socket类创建一个Socket对象,可以使用以下代码: using System.Net; using System.Net.Sockets; Socket socket = new Socket(Addres…

    C# 2023年6月1日
    00
  • c#中如何获取指定字符前的字符串

    在C#中获取指定字符(或字符串)前的字符串,可以采用String类的Substring和IndexOf方法来实现。 方法1:Substring方法 Substring方法是String类提供的一个获取子字符串的方法,可以通过指定起始位置和截取长度来获取指定范围的子字符串。我们可以通过查找指定字符(或字符串)的位置,然后取其前面的子串来获取需要的字符串。 示例…

    C# 2023年6月6日
    00
  • C#编程调用Cards.dll实现图形化发牌功能示例

    C#编程调用Cards.dll实现图形化发牌功能示例 一、背景介绍 随着计算机的不断普及,越来越多的人选择使用电脑进行休闲娱乐。而纸牌游戏作为一种简单、有趣的休闲游戏,在计算机上得到了广泛的应用。本篇攻略主要介绍了C#编程调用Cards.dll实现图形化发牌功能的方法。 二、技术点介绍 C#编程语言:C#是一种面向对象的编程语言,它不仅继承了C++语言的特点…

    C# 2023年6月7日
    00
  • C#使用dir命令实现文件搜索功能示例

    下面是C#使用dir命令实现文件搜索功能示例的详细攻略: 1.使用Process类调用cmd命令 在C#中,可以使用Process类调用外部程序,通过cmd命令来实现搜索文件的功能。具体过程如下: //定义Process进程调用cmd Process p = new Process(); p.StartInfo.FileName = "cmd.ex…

    C# 2023年6月1日
    00
合作推广
合作推广
分享本页
返回顶部