web-dev-qa-db-ja.com

ルーティング属性を持つあいまいなコントローラー名:バージョン管理用に同じ名前で異なる名前空間を持つコントローラー

APIバージョン管理を追加しようとしていますが、バージョンごとに異なる名前空間にコントローラーを作成する予定です。私のプロジェクト構造は次のようになります(注:バージョンごとに個別の領域はありません)

Controllers
 |
 |---Version0
 |      |
 |      |----- ProjectController.cs
 |      |----- HomeController.cs
 |
 |---Version1
       |
       |----- ProjectController.cs
       |----- HomeController.cs

ルートにRoutingAttributeを使用しています。したがって、Version0のProjectControllerには、次のようなルートを持つ関数があります。

namespace MyProject.Controllers.Version0
{
   class ProjectController : BaseController
   {
     ...

     [Route(api/users/project/getProjects/{projectId})]
     public async GetProjects(string projectId) 
     {
       ...
     }
  }
}

バージョン1のProjectControllerには、次のようなルートを持つ関数があります。

namespace MyProject.Controllers.Version1
{
   class ProjectController : BaseController
   {
     ...

     [Route(api/v1/users/project/getProjects/{projectId})]
     public async GetProjects(string projectId) 
     {
      ...
     }
  }
}

しかし、サービスを利用しようとすると404-NotFoundが表示されます。

コントローラの名前を一意の名前(Project1ControllerおよびProject2Controller)に変更すると、ルーティングが機能します。ただし、簡単にするために名前の変更は避けようとしています。

このリンクをたどって問題を解決しましたが、役に立ちませんでした。エリアを作成しましたが、それでも成功しませんでした。 global.aspxファイルにルーティングロジックを追加しても役に立ちません。名前空間も機能しません。 http://haacked.com/archive/2010/01/12/ambiguous-controller-names.aspx/

上記のリンクはエリアの作成を提案していますが、属性ルーティングはリンクに従ってエリアをサポートしていません: http://www.asp.net/web-api/overview/web-api-routing-and-actions/ attribute-routing-in-web-api-2

別の解決策はありますか? RoutingAttributesのバグ?

ありがとうございました!

15
rkd

まず、WebAPIルーティングとMVCルーティングはまったく同じようには機能しません。

最初のリンクは、エリアを含むMVCルーティングを指します。エリアはWebAPIで公式にサポートされていませんが、類似したものを作成することはできます。ただし、そのようなことを行おうとしても、Web APIがコントローラーを検索する方法ではコントローラーの名前空間が考慮されないため、同じエラーが発生します。

したがって、箱から出して、それは決して機能しません。

ただし、ほとんどのWeb APIの動作は変更でき、これも例外ではありません。

Web APIは、コントローラーセレクターを使用して目的のコントローラーを取得します。上で説明した動作は、Web APIに付属する DefaultHttpControllerSelector の動作ですが、独自のセレクターを実装してデフォルトのセレクターを置き換え、新しい動作をサポートすることができます。

「カスタムWebAPIコントローラーセレクター」をグーグルで検索すると、多くのサンプルが見つかりますが、これがまさにあなたの問題にとって最も興味深いと思います。

この実装も興味深いものです。

ご覧のとおり、基本的に次のことを行う必要があります。

  • 独自のIHttpControllerSelectorを実装します。これは、名前空間を考慮してコントローラーを検索し、名前空間のルート変数を使用して、それらの1つを選択します。
  • web API構成を介して、元のセレクターをこれに置き換えます。
21
JotaBe

私はこれがしばらくの間答えられて、元のポスターによってすでに受け入れられていることを知っています。しかし、あなたが私のようで、属性ルーティングの使用を必要とし、提案された答えを試した場合、それは完全には機能しないことがわかります。

これを試したところ、MapHttpAttributeRoutesクラスの拡張メソッドHttpConfigurationを呼び出すことによって生成されるはずのルーティング情報が実際には欠落していることがわかりました。

config.MapHttpAttributeRoutes();

これは、置換SelectController実装のメソッドIHttpControllerSelectorが実際に呼び出されることはなく、リクエストがhttp404応答を生成する理由です。

この問題は、System.Web.Http名前空間の下のSystem.Web.Http.Dispatcherアセンブリの内部クラスであるHttpControllerTypeCacheと呼ばれる内部クラスが原因で発生します。問題のコードは次のとおりです。

    private Dictionary<string, ILookup<string, Type>> InitializeCache()
    {
      return this._configuration.Services.GetHttpControllerTypeResolver().GetControllerTypes(this._configuration.Services.GetAssembliesResolver()).GroupBy<Type, string>((Func<Type, string>) (t => t.Name.Substring(0, t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length)), (IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase).ToDictionary<IGrouping<string, Type>, string, ILookup<string, Type>>((Func<IGrouping<string, Type>, string>) (g => g.Key), (Func<IGrouping<string, Type>, ILookup<string, Type>>) (g => g.ToLookup<Type, string>((Func<Type, string>) (t => t.Namespace ?? string.Empty), (IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase)), (IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase);
    }

このコードでは、名前空間なしでタイプ名でグループ化されていることがわかります。 DefaultHttpControllerSelectorクラスは、コントローラーごとにHttpControllerDescriptorの内部キャッシュを構築するときにこの機能を使用します。 MapHttpAttributeRoutesメソッドを使用する場合、System.Web.Http.Routing名前空間の一部であるAttributeRoutingMapperと呼ばれる別の内部クラスを使用します。このクラスは、ルートを構成するためにGetControllerMappingのメソッドIHttpControllerSelectorを使用します。

したがって、カスタムIHttpControllerSelectorを作成する場合は、それを機能させるためにGetControllerMappingメソッドをオーバーロードする必要があります。私がこれに言及する理由は、私がインターネット上で見た実装のどれもこれを行わないからです。

7
Tom Maher