2016-03-29 8 views
7

私のプロジェクトでは、カスタムルーティング変数を実装して、カスタムヘッダー変数(APIバージョン)を使用してthis sample project on Codeplexに似ていましたが、メジャー。マイナーコンベンションのために。Web APIのヘルプページを取得してカスタムルーティング制約を使用する

これは、その経路FullVersionedRoute属性によって区別される2つの別々のコントローラ作成含む:

Sample1Controller.cs

/// <summary> 
/// v1.0 Controller 
/// </summary> 
public class Sample1Controller : ApiController 
{ 
    [FullVersionedRoute("api/test", "1.0")] 
    public IEnumerable<string> Get() 
    { 
     return new[] { "This is version 1.0 test!" }; 
    } 
} 

Sample2Controller.cs

/// <summary> 
/// v2.0 Controller 
/// </summary> 
public class Sample2Controller : ApiController 
{ 
    [FullVersionedRoute("api/test", "2.0")] 
    public IEnumerable<string> Get() 
    { 
     return new[] { "This is version 2.0 test!" }; 
    } 
} 

FullVersionedRoutをe.cs

using System.Collections.Generic; 
    using System.Web.Http.Routing; 

namespace HelperClasses.Versioning 
{ 
    /// <summary> 
    /// Provides an attribute route that's restricted to a specific version of the api. 
    /// </summary> 
    internal class FullVersionedRoute : RouteFactoryAttribute 
    { 
     public FullVersionedRoute(string template, string allowedVersion) : base(template) 
     { 
      AllowedVersion = allowedVersion; 
     } 

     public string AllowedVersion 
     { 
      get; 
      private set; 
     } 

     public override IDictionary<string, object> Constraints 
     { 
      get 
      { 
       var constraints = new HttpRouteValueDictionary(); 
       constraints.Add("version", new FullVersionConstraint(AllowedVersion)); 
       return constraints; 
      } 
     } 
    } 
} 

FullVersionConstraint.cs

using System.Collections.Generic; 
using System.Linq; 
using System.Net.Http; 
using System.Web.Http.Routing; 

namespace HelperClasses.Versioning 
{ 
    /// <summary> 
    /// A Constraint implementation that matches an HTTP header against an expected version value. 
    /// </summary> 
    internal class FullVersionConstraint : IHttpRouteConstraint 
    { 
     public const string VersionHeaderName = "api-version"; 

     private const string DefaultVersion = "1.0"; 

     public FullVersionConstraint(string allowedVersion) 
     { 
      AllowedVersion = allowedVersion; 
     } 

     public string AllowedVersion 
     { 
      get; 
      private set; 
     } 

     public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection) 
     { 
      if (routeDirection == HttpRouteDirection.UriResolution) 
      { 
       var version = GetVersionHeader(request) ?? DefaultVersion; 
       return (version == AllowedVersion); 
      } 

      return false; 
     } 

     private string GetVersionHeader(HttpRequestMessage request) 
     { 
      IEnumerable<string> headerValues; 

      if (request.Headers.TryGetValues(VersionHeaderName, out headerValues)) 
      { 
       // enumerate the list once 
       IEnumerable<string> headers = headerValues.ToList(); 

       // if we find once instance of the target header variable, return it 
       if (headers.Count() == 1) 
       { 
        return headers.First(); 
       } 
      } 

      return null; 
     } 
    } 
} 

これはうまく動作しますが、彼らは同じルート(ように見えるようauto-generated help filesは二つのコントローラのアクションを区別することはできませんあなたがURLルートに注意を払うのであれば、それはデフォルトで行われます)。そのため、Sample2Controller.csのアクションはSample1Controller.csのアクションを上書きし、Sample2 APIのみがヘルプページに表示されます。

Web APIヘルプページパッケージを構成してカスタム制約を認識し、2つの別々のAPIが存在することを認識し、その後ヘルプページに別々のAPIグループとして表示する方法はありますか?

+0

カスタムルートを使用するだけで同様の問題が発生します。私の質問は、サーバー/ approot /バージョンに展開しない理由は、同じコードベースにネストされた複数のバージョンをすべてのバージョンに変更することは、ロジックが変更されてはいけない元のバージョンを100%にすることはできません – workabyte

+0

この記事によれば、そのソリューションは複数の異なるリソース(つまりv1/myresource、v2/myresource、v3/myresource)を持っているという印象を与えるからです。実際には1つのリソース(すなわち/ myresource)が存在し、その異なるバージョン(Acceptヘッダーに基づいて提供されます。これはHTTP SPECの目的です)。これは意味論的な議論です。 http://www.troyhunt.com/2014/02/your-api-versioning-is-wrong-which-is.html – bperniciaro

答えて

1

これは、IApiExplorerを実装することによってこれを達成する方法を説明しているthis articleです。要するに

、何がやりたいだろうがそう

using System; 
using System.Collections.ObjectModel; 
using System.Collections.Generic; 
using System.Linq; 
using System.Reflection; 
using System.Web.Http; 
using System.Web.Http.Controllers; 
using System.Web.Http.Description; 
using System.Web.Http.Routing; 

namespace HelperClasses.Versioning 
{ 
    public class VersionedApiExplorer<TVersionConstraint> : IApiExplorer 
    { 
     private IApiExplorer _innerApiExplorer; 
     private HttpConfiguration _configuration; 
     private Lazy<Collection<ApiDescription>> _apiDescriptions; 
     private MethodInfo _apiDescriptionPopulator; 

     public VersionedApiExplorer(IApiExplorer apiExplorer, HttpConfiguration configuration) 
     { 
      _innerApiExplorer = apiExplorer; 
      _configuration = configuration; 
      _apiDescriptions = new Lazy<Collection<ApiDescription>>(
       new Func<Collection<ApiDescription>>(Init)); 
     } 

     public Collection<ApiDescription> ApiDescriptions 
     { 
      get { return _apiDescriptions.Value; } 
     } 

     private Collection<ApiDescription> Init() 
     { 
      var descriptions = _innerApiExplorer.ApiDescriptions; 

      var controllerSelector = _configuration.Services.GetHttpControllerSelector(); 
      var controllerMappings = controllerSelector.GetControllerMapping(); 

      var flatRoutes = FlattenRoutes(_configuration.Routes); 
      var result = new Collection<ApiDescription>(); 

      foreach (var description in descriptions) 
      { 
       result.Add(description); 

       if (controllerMappings != null && description.Route.Constraints.Any(c => c.Value is TVersionConstraint)) 
       { 
        var matchingRoutes = flatRoutes.Where(r => r.RouteTemplate == description.Route.RouteTemplate && r != description.Route); 

        foreach (var route in matchingRoutes) 
         GetRouteDescriptions(route, result); 
       } 
      } 
      return result; 
     } 

     private void GetRouteDescriptions(IHttpRoute route, Collection<ApiDescription> apiDescriptions) 
     { 
      var actionDescriptor = route.DataTokens["actions"] as IEnumerable<HttpActionDescriptor>; 

      if (actionDescriptor != null && actionDescriptor.Count() > 0) 
       GetPopulateMethod().Invoke(_innerApiExplorer, new object[] { actionDescriptor.First(), route, route.RouteTemplate, apiDescriptions }); 
     } 

     private MethodInfo GetPopulateMethod() 
     { 
      if (_apiDescriptionPopulator == null) 
       _apiDescriptionPopulator = _innerApiExplorer.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(
        m => m.Name == "PopulateActionDescriptions" && m.GetParameters().Length == 4); 

      return _apiDescriptionPopulator; 
     } 

     public static IEnumerable<IHttpRoute> FlattenRoutes(IEnumerable<IHttpRoute> routes) 
     { 
      var flatRoutes = new List<HttpRoute>(); 

      foreach (var route in routes) 
      { 
       if (route is HttpRoute) 
        yield return route; 

       var subRoutes = route as IReadOnlyCollection<IHttpRoute>; 
       if (subRoutes != null) 
        foreach (IHttpRoute subRoute in FlattenRoutes(subRoutes)) 
         yield return subRoute; 
      } 
     } 
    } 
} 

ようIApiExplorerを実装する新しいVersionedApiExplorerクラスを追加してからWebAPIConfigに

var apiExplorer = config.Services.GetApiExplorer(); 
config.Services.Replace(typeof(IApiExplorer), new VersionedApiExplorer<FullVersionConstraint>(apiExplorer, config)); 

これを追加しているあなたは、両方表示されるはずですWeb APIヘルプ・ページのSample1およびSample2 API。

+0

優れています。それはトリックでした! – bperniciaro