ASP.NET 5&MVC 6シリーズの解読(12):Lamda式に基づく強いタイプRouting実装
10913 ワード
原文:
ASP.NET 5&MVC 6シリーズの解読(12):Lamda式に基づく強いタイプRouting実装
前述のRouting章では,MVCにおいて,デフォルトのASP.NET 5を用いたルーティング登録方式に加えて,Attributeに基づく特性(RouteとHttpXXXシリーズ手法)を用いて定義することも可能である.この章では、Lambda式に基づく強いタイプについて説明します.
この方式の基本的な使用例は以下の通りである.
例から分かるように、我々はGetRouteやPostRouteなどの拡張方法でrouteを定義することができ、後にLambda式を用いてControllerのタイプとActionの方法を決定することができる.
なお、ここでActionを取得するメソッド名は、そのActionメソッドの実行を依頼することによって実現される(実際には実行されず、これに基づいてそのActionを取得するMethodInfo).
実装の原理
インタフェースの
ここで最も重要なのは
新しいIApplicationModelConventionの登録方法は以下の通りです.
そこで,この手法を用いて,MVC全体のプログラムモデルに適切なタイミングで応答する調整と修正を行うことができ,この章の強いタイプのルーティングはこの特性を利用して実現される.
実装手順
まず、強いタイプのルーティングモデル
このクラスの主な機能は、転送をサポートするControllerタイプを定義し、チェーン呼び出しをサポートすることです.
次に、継承
このクラスには、Lamda式で宣言されたすべてのルーティングを保存し、既存のControllersコレクションで検索および変更した後、
ここでは、単純に置き換えるだけ
最適化の際には、Controller上の
次に、MvcOptionsでは、TypeRouteModelに拡張方法を追加して使用しやすくします.コードは次のとおりです.
上記のコードでは、
他の拡張メソッドは、関連するrouteを宣言するために使用されます.最初の例では、action情報を取得する方法は、このactionメソッドを呼び出すように依頼することですが(実際に呼び出されていません)、パラメータがある方法があります.どうすればいいですか.このため、パラメータを無視したParamクラスを次のように指定します.
これにより,パラメータを含むAboutメソッドをルーティングする際に,コードを以下のように定義することができる.
また、TypeRouteModelではチェーン呼び出しが可能な方法が多いため、routeの名前を指定することもできます.サンプルコードは以下の通りです.
これで、強いタイプのルーティング全体の機能が実現し、皆さんが使用するときに、もう一つの選択肢が増えました.
弊害(またはBug)
上記では、
次に、Lamda式によって、次のような強いタイプのルーティングが定義されます.
では、あなたは
ただし、前述のLamda式では、Controllerで定義されているRouteプロパティ定義は上書きされていませんので、ProductsControllerでRouteプロパティを定義すると、次のように組み合わせられます.
では、あなたのアクセス先は
あなたのアクセス先は
参考:http://www.strathweb.com/2015/03/strongly-typed-routing-asp-net-mvc-6-iapplicationmodelconvention/
同期と推奨
この文書はディレクトリインデックスに同期済み:ASP.NET 5&MVC 6シリーズの解読
ASP.NET 5&MVC 6シリーズの解読(12):Lamda式に基づく強いタイプRouting実装
前述のRouting章では,MVCにおいて,デフォルトのASP.NET 5を用いたルーティング登録方式に加えて,Attributeに基づく特性(RouteとHttpXXXシリーズ手法)を用いて定義することも可能である.この章では、Lambda式に基づく強いタイプについて説明します.
この方式の基本的な使用例は以下の通りである.
services.Configure<MvcOptions>(opt =>
{
opt.EnableTypedRouting();
opt.GetRoute("homepage", c => c.Action<ProductsController>(x => x.Index()));
opt.GetRoute("aboutpage/{name}", c => c.Action<ProductsController>(x => x.About(Param<string>.Any)));
opt.PostRoute("sendcontact", c => c.Action<ProductsController>(x => x.Contact()));
});
例から分かるように、我々はGetRouteやPostRouteなどの拡張方法でrouteを定義することができ、後にLambda式を用いてControllerのタイプとActionの方法を決定することができる.
なお、ここでActionを取得するメソッド名は、そのActionメソッドの実行を依頼することによって実現される(実際には実行されず、これに基づいてそのActionを取得するMethodInfo).
実装の原理
Stratup.cs
のConfigureServices
メソッドでサービスを構成する場合、MVCサイトで使用するコアプロファイルMvcOptions
を構成することができます.このクラスにはApplicationModelConventions
属性(List<IApplicationModelConvention
>)が1つIApplicationModelConvention
インタフェースの集合を保存し、インタフェースを変更することでMVCプログラムのプログラムモデルをパイプライン処理することができます.インタフェースの定義は次のとおりです.public interface IApplicationModelConvention
{
void Apply(ApplicationModel application);
}
インタフェースの
Apply
メソッドが受信するパラメータのタイプはApplicationModel
であり、ApplicationModel
2つの極めて重要な内容が操作できる.1つはControllerモデルの集合であり、1つは各種Filterの集合であり、このクラスの定義は以下の通りである.public class ApplicationModel
{
public ApplicationModel();
public IList<ControllerModel> Controllers { get; }
public IList<IFilter> Filters { get; }
}
ここで最も重要なのは
ControllerModel
クラスであり、このクラスと関連アクション上のルーティング定義データ、API記述情報、ルーティング制約など、様々な重要で操作可能な情報が格納されている.新しいIApplicationModelConventionの登録方法は以下の通りです.
services.Configure<MvcOptions>(opt =>
{
opts.ApplicationModelConventions.Add(new MyApplicationModelConvention());
});
そこで,この手法を用いて,MVC全体のプログラムモデルに適切なタイミングで応答する調整と修正を行うことができ,この章の強いタイプのルーティングはこの特性を利用して実現される.
実装手順
まず、強いタイプのルーティングモデル
TypedRouteModel
クラスを定義します.このクラスはAttributeRouteModel
に継承されます.AttributeRouteModel
クラスはAttributeルーティングに基づく基本モデルです.TypedRouteModel
クラスのコードは以下の通りです.public class TypedRouteModel : AttributeRouteModel
{
public TypedRouteModel(string template)
{
Template = template;
HttpMethods = new string[0];
}
public TypeInfo ControllerType { get; private set; }
public MethodInfo ActionMember { get; private set; }
public IEnumerable<string> HttpMethods { get; private set; }
public TypedRouteModel Controller<TController>()
{
ControllerType = typeof(TController).GetTypeInfo();
return this;
}
public TypedRouteModel Action<T, U>(Expression<Func<T, U>> expression)
{
ActionMember = GetMethodInfoInternal(expression);
ControllerType = ActionMember.DeclaringType.GetTypeInfo();
return this;
}
public TypedRouteModel Action<T>(Expression<Action<T>> expression)
{
ActionMember = GetMethodInfoInternal(expression);
ControllerType = ActionMember.DeclaringType.GetTypeInfo();
return this;
}
private static MethodInfo GetMethodInfoInternal(dynamic expression)
{
var method = expression.Body as MethodCallExpression;
if (method != null)
return method.Method;
throw new ArgumentException("Expression is incorrect!");
}
public TypedRouteModel WithName(string name)
{
Name = name;
return this;
}
public TypedRouteModel ForHttpMethods(params string[] methods)
{
HttpMethods = methods;
return this;
}
}
このクラスの主な機能は、転送をサポートするControllerタイプを定義し、チェーン呼び出しをサポートすることです.
次に、継承
IApplicationModelConvention
インタフェースのTypedRoutingApplicationModelConvention
クラスを定義します.コードは次のとおりです.public class TypedRoutingApplicationModelConvention : IApplicationModelConvention
{
internal static readonly Dictionary<TypeInfo, List<TypedRouteModel>> Routes = new Dictionary<TypeInfo, List<TypedRouteModel>>();
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
if (Routes.ContainsKey(controller.ControllerType))
{
var typedRoutes = Routes[controller.ControllerType];
foreach (var route in typedRoutes)
{
var action = controller.Actions.FirstOrDefault(x => x.ActionMethod == route.ActionMember);
if (action != null)
{
action.AttributeRouteModel = route;
// , Controller Route
foreach (var method in route.HttpMethods)
{
action.HttpMethods.Add(method);
}
}
}
}
}
}
}
このクラスには、Lamda式で宣言されたすべてのルーティングを保存し、既存のControllersコレクションで検索および変更した後、
AttributeRouteModel
プロパティを置き換え、応答するHttp Methodを設定する静的変数Routesが保存されています(設定しない場合は、デフォルトではすべての方法が許可されます).ここでは、単純に置き換えるだけ
action.AttributeRouteModel
ですので、いくつかの欠陥(例えば、1つのアクションは1つのルーティングパスしかサポートできず、最後のパスに準じて)を招き、皆さんは自分の能力に応じて最適化することができます.最適化の際には、Controller上の
Route
集合がcontroller.Attributes
属性に保存され、Action上のRoute集合がaction.Attributes
属性に保存されていることに注意して最適化することができる.次に、MvcOptionsでは、TypeRouteModelに拡張方法を追加して使用しやすくします.コードは次のとおりです.
public static class MvcOptionsExtensions
{
public static TypedRouteModel GetRoute(this MvcOptions opts, string template, Action<TypedRouteModel> configSetup)
{
return AddRoute(template, configSetup).ForHttpMethods("GET");
}
public static TypedRouteModel PostRoute(this MvcOptions opts, string template, Action<TypedRouteModel> configSetup)
{
return AddRoute(template, configSetup).ForHttpMethods("POST");
}
public static TypedRouteModel PutRoute(this MvcOptions opts, string template, Action<TypedRouteModel> configSetup)
{
return AddRoute(template, configSetup).ForHttpMethods("PUT");
}
public static TypedRouteModel DeleteRoute(this MvcOptions opts, string template, Action<TypedRouteModel> configSetup)
{
return AddRoute(template, configSetup).ForHttpMethods("DELETE");
}
public static TypedRouteModel TypedRoute(this MvcOptions opts, string template, Action<TypedRouteModel> configSetup)
{
return AddRoute(template, configSetup);
}
private static TypedRouteModel AddRoute(string template, Action<TypedRouteModel> configSetup)
{
var route = new TypedRouteModel(template);
configSetup(route);
if (TypedRoutingApplicationModelConvention.Routes.ContainsKey(route.ControllerType))
{
var controllerActions = TypedRoutingApplicationModelConvention.Routes[route.ControllerType];
controllerActions.Add(route);
}
else
{
var controllerActions = new List<TypedRouteModel> { route };
TypedRoutingApplicationModelConvention.Routes.Add(route.ControllerType, controllerActions);
}
return route;
}
public static void EnableTypedRouting(this MvcOptions opts)
{
opts.ApplicationModelConventions.Add(new TypedRoutingApplicationModelConvention());
}
}
上記のコードでは、
EnableTypedRouting
拡張メソッドを追加し、MvcOptions.ApplicationModelConventions
属性に新しいTypedRoutingApplicationModelConvention
タイプ例を追加しました.他の拡張メソッドは、関連するrouteを宣言するために使用されます.最初の例では、action情報を取得する方法は、このactionメソッドを呼び出すように依頼することですが(実際に呼び出されていません)、パラメータがある方法があります.どうすればいいですか.このため、パラメータを無視したParamクラスを次のように指定します.
public static class Param<TValue>
{
public static TValue Any
{
get { return default(TValue); }
}
}
これにより,パラメータを含むAboutメソッドをルーティングする際に,コードを以下のように定義することができる.
opt.GetRoute("aboutpage/{name}", c => c.Action<HomeController>(x => x.About(Param<string>.Any)));
また、TypeRouteModelではチェーン呼び出しが可能な方法が多いため、routeの名前を指定することもできます.サンプルコードは以下の通りです.
opt.GetRoute("homepage", c => c.Action<HomeController>(x => x.Index())).WithName("foo");
これで、強いタイプのルーティング全体の機能が実現し、皆さんが使用するときに、もう一つの選択肢が増えました.
弊害(またはBug)
上記では、
IApplicationModelConvention
インタフェースを実装する場合、単純にaction.AttributeRouteModel
を置き換えるだけです.つまり、ActionですでにRoute
プロパティを持っている場合、彼はあなたの情報を上書きし、routeが失効することになります.たとえば、このようなカスタムルーティングを定義した場合:public class ProductsController : Controller
{
[Route("index")]
public IActionResult Index()
{
return Content("Index");
}
}
次に、Lamda式によって、次のような強いタイプのルーティングが定義されます.
opt.GetRoute("homepage", c => c.Action<ProductsController>(x => x.Index()));
では、あなたは
/homepage
を通じてしかアクセスできません./index
を通じてアクセスできません.それはあなたのRouteをカバーしているからです.ただし、前述のLamda式では、Controllerで定義されているRouteプロパティ定義は上書きされていませんので、ProductsControllerでRouteプロパティを定義すると、次のように組み合わせられます.
[Route("products")]
public class ProductsController : Controller
{
public IActionResult Index()
{
return Content("Index");
}
}
では、あなたのアクセス先は
/product/homepage
ではなく/homepage
ですが、Lamda式のコードであれば、次のようになります.opt.GetRoute("/homepage", c => c.Action<ProductsController>(x => x.Index()));
あなたのアクセス先は
/homepage
です.このルート文字は絶対パスですから/homepage
ではなくhomepage
です.参考:http://www.strathweb.com/2015/03/strongly-typed-routing-asp-net-mvc-6-iapplicationmodelconvention/
同期と推奨
この文書はディレクトリインデックスに同期済み:ASP.NET 5&MVC 6シリーズの解読