使用URL实现Web API版本控制的C#方法

3
我已经努力了好几天,一直在尝试将我的API版本化,使用URL后缀。 例如:http://localhost/api/v1/siteshttp://localhost/api/v2/sites
我觉得我已经接近成功,但突然把所有东西都搞砸了。
非常感谢你的帮助。
现在我收到以下错误信息:"System.invalidOperationException:' The constraint type 'ApiVersionRouteConstraint' which is mapped to constraint key 'apiVersion' must implement the IhttpRouteConstraint interface.' on Global.asax.cs" Global.asax.cs
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace HIDDEN
{
    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }
}

WebApiConfig.cs

using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Owin.Security.OAuth;
using System.Web.Http;
using System.Web.Http.Routing;

namespace HIDDEN
{
    public static class WebApiConfig
        {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services  
            // Configure Web API to use only bearer token authentication.  
            config.SuppressDefaultHostAuthentication();
            config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

            var contraintResolver = new DefaultInlineConstraintResolver()
            {
                ConstraintMap =
                {
                    ["apiVersion"] = typeof(ApiVersionRouteConstraint)
                }
            };

            // Web API routes  
            config.MapHttpAttributeRoutes(contraintResolver);
            config.AddApiVersioning(o =>
            {
                o.AssumeDefaultVersionWhenUnspecified = true;
                //o.DefaultApiVersion = new ApiVersion(1, 0);
            });

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                //routeTemplate: "api/v{version:apiVersion}/{controller}/{id}",
                routeTemplate: "api/v{version:apiVersion}/{controller}",
                //routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            // WebAPI when dealing with JSON & JavaScript!  
            // Setup json serialization to serialize classes to camel (std. Json format)  
            var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
            formatter.SerializerSettings.ContractResolver = new     Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();

            // Adding JSON type web api formatting.  
            config.Formatters.Clear();
            config.Formatters.Add(formatter);

        }
    }
}   

RouteConfig.cs

using System.Web.Mvc;
using System.Web.Routing;


namespace HIDDEN
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}

站点控制器

using FirebirdSql.Data.FirebirdClient;
using System;
using System.Web.Http;
using System.Configuration;
using System.Collections;
using Microsoft.Web.Http;

        namespace HIDDEN.Controllers.V1
        {
        [ApiVersion("1.0")]
        [Route("api/v{version:apiVersion}/[controller]")]

        public class SitesController : ApiController
        {
              //code for V1
        }

        namespace HIDDEN.Controllers.V2
        {
        [ApiVersion("2.0")]
        [Route("api/v{version:apiVersion}/[controller]")]

        public class SitesController : ApiController
        {
              //code for V2
        }
    }

你正在使用 ConfigureAuth 吗? - Ahmed Msaouri
是的,用于 oAuth2 认证。 - Gregory
将API版本属性设置到管道中并在每个使用属性路由的控制器上指定ApiVersion,相比于您可以随时执行[Route("api/v2/[controller]")],这样做有什么优势?它是否在其他地方添加了其他功能? - Craig H
嗨,克雷格,那我可以在控制器中省略 [Route("api/v{version:apiVersion}/[controller]")] 吗? - Gregory
@CraigH - 是的,有额外的功能。API版本控制提供了一个全面的框架,用于版本控制策略、发现、文档等。使用字面字符串 v2 将对 API 版本控制是未知的,因为它不允许、使用或解析“魔术字符串”。{version:apiVersion} 路由参数定义了名称 version,它使用了 ApiVersionRouteConstraint。这就是 API 版本控制知道从哪里提取值的方式。 - Chris Martinez
@Gregory - 不,如果您删除带有apiVersion路由约束的路由参数,则API版本控制将无法工作。这是API版本控制从模板中提取版本号的方式。没有它,v2将成为一个常量字符串片段,并且API版本控制将认为未指定任何版本,因为它不知道如何从请求中_读取_它。 - Chris Martinez
4个回答

4
请确保您使用正确的 ApiVersionRouteConstraint,因为似乎在 Microsoft.AspNetCore.Mvc.RoutingMicrosoft.Web.Http.Routing 之间存在冲突。
如果您正在使用此版本控制库 Microsoft.AspNet.WebApi.Versioning,则应该使用 Microsoft.Web.Http.Routing
using Microsoft.Web.Http.Routing;

另外,您还可以像下面这样在路由中添加apiVersion约束:

config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/v{apiVersion}/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional },
                constraints: new { apiVersion = new ApiVersionRouteConstraint() }
);

谢谢Hasta,我按照你的建议进行了更正。但是仍然收到“System.invalidOperationException”的错误提示。 - Gregory
请问您是否确认正在使用x.x.WebApi.Versioning?如果是的话,您的WebApiConfig中是否有“using Microsoft.Web.Http.Routing;”这一行代码呢? - Hasta Tamang
我相信那是你的问题。请使用Microsoft.Web.Http.Routing。 - Hasta Tamang
请检查您的引用,应该包含 System.Web.Http - Hasta Tamang
值得一提的是,应避免混合使用属性路由和约定路由。是的 - 它们都被支持,但实际上随着时间的推移,这样做可能会变得非常令人困惑。我建议选择其中一种,然后坚持使用它。 - Chris Martinez
显示剩余5条评论

1

嗨,Ahmed,这种方法可行吗?因为请求参数中不会提供“Api-version”(仅在URL中)。 - Gregory
@Gregory - 是的,这个方案可行。API版本控制不关心在哪里请求API版本时报告API版本。所有API版本信息都是预先发现和缓存的。如果你想一想,它必须这样做才能从候选操作中做出决策。匹配候选者确实需要读取一个或多个包含传入API版本的请求源。一旦匹配成功,所有其他候选者已经知道,如果配置了,它们的API版本就可以报告。 - Chris Martinez

1

问题已经解决,最大的问题在于控制器的不同版本位于不同的命名空间中。

以下方法解决了问题:

 namespace HIDDEN.Controllers.Sites
 {
     [Authorize]
     [ApiVersion("1.0")]
     [Route("v{version:apiVersion}/sites")]   
     public class valuesV1Controller : ApiController
     {
         // GET: api/values
         public IEnumerable<string> Get()
         {
             return new string[] { "value1", "value2" };
         }
     }

     [Authorize]
     [ApiVersion("2.0")]
     [ApiVersion("2.1")]
     [Route("v{version:apiVersion}/sites")]
     public class valuesV2Controller : ApiController
     {
         // GET: api/values
         public IEnumerable<string> Get()
         {
             return new string[] { "value3", "value4" };
         }
     }
 }

1
虽然这可能是被接受的答案,但我要补充一点,命名空间并不是问题。控制器可以在不同的命名空间中具有相同的类型名称。API版本库中的按命名空间版本示例演示了一种设置方式。 - Chris Martinez
1
另外需要提到的是,原始问题使用了[controller]标记,但我认为这仅在ASP.NET Core路由模板中受支持。此问题和线程是针对ASP.NET Web API的。我还要重申,混合路由风格(基于约定 vs 基于属性)可能会使故障排除变得困难。我强烈建议选择一种方法。如果您混合使用两种方法,则在Web API中API版本控制将无法正常工作。 - Chris Martinez

-2

您可以使用swagger来获得更好的API文档,同时也可以进行API版本控制。


2
你的回答与所问问题无关。请先阅读问题,然后再添加你的回答。或者更好的方法是,如果有任何不清楚的地方,请写下评论。 - Mubashar Shahzad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接