ASP.NET Core のアプリケーション モデルの使用

作成者: Steve Smith

ASP.NET Core MVC では、MVC アプリのコンポーネントを表すアプリケーション モデルを定義できます。 このモデルを読み、操作し、MVC 要素の動作を変更します。 既定で、MVC には、どのクラスがコントローラーとして考慮されるか、それらのクラスのどのメソッドがアクションであるか、パラメーターおよびルーティングがどのように動作するかについて、特定の規則があります。 カスタム規則を作成し、それらをグローバルにまたは属性として適用して、この動作をアプリのニーズに合うようにカスタマイズします。

モデルおよびプロバイダー (IApplicationModelProvider)

ASP.NET Core MVC アプリケーション モデルには、MVC アプリケーションを表現する、抽象インターフェイスと具象実装クラスの両方が含まれています。 このモデルは、既定の規則に従ってアプリのコントローラー、アクション、アクション パラメーター、ルート、およびフィルターを検出する MVC の結果です。 アプリケーション モデルを使用して、既定の MVC の動作とは異なる規則に従うようにアプリを変更します。 パラメーター、名前、ルート、およびフィルターは、すべてアクションおよびコントローラーの構成データとして使用されます。

ASP.NET Core MVC アプリケーション モデルの構造は、次のとおりです。

  • ApplicationModel
    • コントローラー (ControllerModel)
      • アクション (ActionModel)
        • パラメーター (ParameterModel)

このモデルでは、各レベルで、共通の Properties コレクションにアクセスできます。下位レベルでは、階層の上位レベルで設定されたプロパティ値にアクセスしたり上書きしたりできます。 このプロパティは、アクションの作成時、ActionDescriptor.Properties に保存されます。 そして要求の処理時に、規則によって追加または変更されたすべてのプロパティに、ActionContext.ActionDescriptor を介してアクセスできます。 フィルターやモデル バインダー、その他のアプリ モデルの側面をアクションごとに構成するのに、プロパティを使用するのはよい方法です。

Note

アプリのスタートアップ後、ActionDescriptor.Properties コレクションは (書き込みに) スレッド セーフではありません。 このコレクションにデータを安全に追加するには、規則が最善の方法です。

ASP.NET Core MVC は、IApplicationModelProvider インターフェイスによって定義されるプロバイダー パターンを使用して、アプリケーション モデルを読み込みます。 このセクションでは、このプロバイダーがどのように機能するかについての、いくつかの内部実装に関する詳細を説明します。 プロバイダー パターンの使用は、主にフレームワークで使用する場合、高度なテーマです。 ほとんどのアプリでは、プロバイダー パターンではなく、規則を使用する必要があります。

IApplicationModelProvider インターフェイスの実装では互いを "ラップ" します。ここで、各実装ではその Order プロパティに応じて、昇順で OnProvidersExecuting を呼び出します。 次いで、OnProvidersExecuted メソッドが逆順で呼び出されます。 このフレームワークでは、次のいくつかのプロバイダーが定義されます。

1 番目 (Order=-1000):

  • DefaultApplicationModelProvider

次 (Order=-990):

  • AuthorizationApplicationModelProvider
  • CorsApplicationModelProvider

Note

2 つのプロバイダーの Order の値が同じである場合、順序は定義されていないため、これには依存しないようにする必要があります。

Note

IApplicationModelProvider は、フレームワークの作成者が拡張する高度な概念です。 一般に、規則やフレームワークを使う必要があるアプリは、プロバイダーを使う必要があります。 重要な違いは、プロバイダーは常に規則の前に実行されるということです。

DefaultApplicationModelProvider は ASP.NET Core MVC で使用される多数の既定の動作を確立します。 次の役割があります。

  • コンテキストにグローバル フィルターを追加する
  • コンテキストにコントローラーを追加する
  • アクションとしてパブリック コントローラー メソッドを追加する
  • コンテキストにアクション メソッド パラメーターを追加する
  • ルートおよびその他の属性を適用する

いくつかの組み込みの動作は、DefaultApplicationModelProvider によって実装されます。 このプロバイダーは、ActionModelPropertyModelParameterModel インスタンスを代わりに参照する、ControllerModel を構築する役割があります。 DefaultApplicationModelProvider クラスは、今後変更される可能性がある、内部フレームワークの実装についての詳細です。

AuthorizationApplicationModelProvider は、AuthorizeFilter 属性および AllowAnonymousFilter 属性に関連付けられた動作を適用します。 詳細については、「ASP.NET Core での単純なな認可」を参照してください。

CorsApplicationModelProvider は、IEnableCorsAttribute および IDisableCorsAttribute に関連付けられた動作を実装します。 詳細については、「ASP.NET Core でクロスオリジン要求 (CORS) を有効にする」を参照してください。

このセクションで説明するフレームワークの内部プロバイダーに関する情報は、.NET API ブラウザーでは入手できません。 ただし、プロバイダーは ASP.NET Core 参照ソース (dotnet/aspnetcore GitHub リポジトリ) で調べることができます。 GitHub 検索を使用して名前でプロバイダーを検索し、[Switch branches/tags]\(ブランチ/タグの切り替え\) ドロップダウン リストの一覧でソースのバージョンを選択します。

規約

このアプリケーション モデルでは、モデルまたはプロバイダー全体をオーバーライドするよりも簡単に、モデルの動作をカスタマイズできる、規則の抽象化を定義できます。 これらの抽象化は、アプリの動作の変更に推奨されます。 規則は、カスタマイズを動的に適用するコードを記述する方法を提供します。 フィルターでは、フレームワークの動作を変更できるのに対して、カスタマイズではアプリ全体がどのように連携するかを制御できます。

次の規則があります。

規則を適用するには、それらを MVC オプションに追加するか、属性を実装してコントローラー、アクション、またはアクション パラメーターに適用します (フィルターに似ています)。フィルターとは異なり、規則は、各要求の一部としてではなく、アプリの起動時にのみ実行されます。

Note

Razor Pages ルートとアプリケーション モデル プロバイダーの規則の詳細については、「ASP.NET Core での Razor Pages のルートとアプリの規則」を参照してください。

ApplicationModel を変更する

次の規則は、アプリケーション モデルにプロパティを追加するために使用します。

using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
    public class ApplicationDescription : IApplicationModelConvention
    {
        private readonly string _description;

        public ApplicationDescription(string description)
        {
            _description = description;
        }

        public void Apply(ApplicationModel application)
        {
            application.Properties["description"] = _description;
        }
    }
}

アプリケーション モデルの規則は、MVC が Startup.ConfigureServices に追加されるときに、オプションとして適用されます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Conventions.Add(new ApplicationDescription("My Application Description"));
        options.Conventions.Add(new NamespaceRoutingConvention());
    });
}

プロパティは、コントローラー アクション内の ActionDescriptor.Properties コレクションからアクセスできます。

public class AppModelController : Controller
{
    public string Description()
    {
        return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
    }
}

ControllerModel の説明を変更する

コントローラー モデルには、カスタム プロパティを含めることもできます。 カスタム プロパティは、アプリケーション モデルで指定した同じ名前の既存のプロパティをオーバーライドします。 次の規則属性では、コントローラー レベルで説明が追加されます。

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
    public class ControllerDescriptionAttribute : Attribute, IControllerModelConvention
    {
        private readonly string _description;

        public ControllerDescriptionAttribute(string description)
        {
            _description = description;
        }

        public void Apply(ControllerModel controllerModel)
        {
            controllerModel.Properties["description"] = _description;
        }
    }
}

この規則は、コントローラーの属性として適用されます。

[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
    public string Index()
    {
        return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
    }

ActionModel の説明を変更する

既にアプリケーションまたはコントローラー レベルに適用されている動作をオーバーライドして、個別のアクションに別の属性規則を適用することができます。

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
    public class ActionDescriptionAttribute : Attribute, IActionModelConvention
    {
        private readonly string _description;

        public ActionDescriptionAttribute(string description)
        {
            _description = description;
        }

        public void Apply(ActionModel actionModel)
        {
            actionModel.Properties["description"] = _description;
        }
    }
}

コントローラーのアクションにこれを適用することにより、コントローラー レベルで規則をオーバーライドする方法を示しています。

[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
    public string Index()
    {
        return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
    }

    [ActionDescription("Action Description")]
    public string UseActionDescriptionAttribute()
    {
        return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
    }
}

ParameterModel を変更する

次の規則をアクション パラメーターに適用して、BindingInfo を変更することができます。 次の規則では、パラメーターがルート パラメーターである必要があります。 クエリ文字列値など、その他の潜在的なバインド ソースは無視されます。

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace AppModelSample.Conventions
{
    public class MustBeInRouteParameterModelConvention : Attribute, IParameterModelConvention
    {
        public void Apply(ParameterModel model)
        {
            if (model.BindingInfo == null)
            {
                model.BindingInfo = new BindingInfo();
            }
            model.BindingInfo.BindingSource = BindingSource.Path;
        }
    }
}

この属性は、任意のアクション パラメーターに適用できます。

public class ParameterModelController : Controller
{
    // Will bind:  /ParameterModel/GetById/123
    // WON'T bind: /ParameterModel/GetById?id=123
    public string GetById([MustBeInRouteParameterModelConvention]int id)
    {
        return $"Bound to id: {id}";
    }
}

すべてのアクション パラメーターに規則を適用するには、MustBeInRouteParameterModelConventionStartup.ConfigureServicesMvcOptions に追加します。

options.Conventions.Add(new MustBeInRouteParameterModelConvention());

ActionModel 名を変更する

次の規則は、ActionModel が適用されるアクションの名前を更新してそれを変更します。 この新しい名前は、属性にパラメーターとして提供されます。 この新しい名前はルーティングによって使用されるので、このアクション メソッドに到達するのに使用されるルートに影響します。

using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
    public class CustomActionNameAttribute : Attribute, IActionModelConvention
    {
        private readonly string _actionName;

        public CustomActionNameAttribute(string actionName)
        {
            _actionName = actionName;
        }

        public void Apply(ActionModel actionModel)
        {
            // this name will be used by routing
            actionModel.ActionName = _actionName;
        }
    }
}

この属性は、HomeController のアクション メソッドに適用されます。

// Route: /Home/MyCoolAction
[CustomActionName("MyCoolAction")]
public string SomeName()
{
    return ControllerContext.ActionDescriptor.ActionName;
}

メソッド名は SomeName ですが、このメソッド名を使用する MVC 規則をこの属性はオーバーライドし、アクション名を MyCoolAction に置換します。 したがって、このアクションに到達するのに使用されるルートは、/Home/MyCoolAction です。

Note

このセクションのこの例は、基本的に、組み込みの ActionNameAttribute 属性を使用するのと同じです。

カスタム ルーティング規則

IApplicationModelConvention を使用して、ルーティングの動作をカスタマイズします。 たとえば、次の規則は、名前空間の . をルートの / に置き換えてコントローラーの名前空間をそのルートに組み込みます。

using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Linq;

namespace AppModelSample.Conventions
{
    public class NamespaceRoutingConvention : IApplicationModelConvention
    {
        public void Apply(ApplicationModel application)
        {
            foreach (var controller in application.Controllers)
            {
                var hasAttributeRouteModels = controller.Selectors
                    .Any(selector => selector.AttributeRouteModel != null);

                if (!hasAttributeRouteModels
                    && controller.ControllerName.Contains("Namespace")) // affect one controller in this sample
                {
                    // Replace the . in the namespace with a / to create the attribute route
                    // Ex: MySite.Admin namespace will correspond to MySite/Admin attribute route
                    // Then attach [controller], [action] and optional {id?} token.
                    // [Controller] and [action] is replaced with the controller and action
                    // name to generate the final template
                    controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel()
                    {
                        Template = controller.ControllerType.Namespace.Replace('.', '/') + "/[controller]/[action]/{id?}"
                    };
                }
            }

            // You can continue to put attribute route templates for the controller actions depending on the way you want them to behave
        }
    }
}

この規則は、Startup.ConfigureServices のオプションとして追加されています。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Conventions.Add(new ApplicationDescription("My Application Description"));
        options.Conventions.Add(new NamespaceRoutingConvention());
    });
}

ヒント

次の方法を使用して、MvcOptions を介してミドルウェアに規則を追加します。 {CONVENTION} プレースホルダーは、次のように追加する規則です。

services.Configure<MvcOptions>(c => c.Conventions.Add({CONVENTION}));

次の例では、コントローラー名に Namespace が含まれる、属性のルーティングを使用していないルートに規則が適用されます:

using Microsoft.AspNetCore.Mvc;

namespace AppModelSample.Controllers
{
    public class NamespaceRoutingController : Controller
    {
        // using NamespaceRoutingConvention
        // route: /AppModelSample/Controllers/NamespaceRouting/Index
        public string Index()
        {
            return "This demonstrates namespace routing.";
        }
    }
}

WebApiCompatShim でのアプリケーション モデルの使用

ASP.NET Core MVC と ASP.NET Web API 2 とでは使用する規則のセットが異なります。 カスタム規則を使用すると、Web API アプリの動作と一致するように、ASP.NET Core MVC アプリの動作を変更できます。 Microsoft では、この目的専用に WebApiCompatShim NuGet パッケージを提供しています。

Note

ASP.NET Web API からの移行の詳細については、「ASP.NET Web API から ASP.NET Coreへの移行」を参照してください。

Web API 互換性 Shim を使用するには:

  • Microsoft.AspNetCore.Mvc.WebApiCompatShim パッケージをプロジェクトに追加します。
  • Startup.ConfigureServicesAddWebApiConventions を呼び出して、MVC に規則を追加します。
services.AddMvc().AddWebApiConventions();

Shim が提供するこの規則は、それに特定の属性が適用されたアプリの一部にのみ適用されます。 次の 4 つの属性は、Shim の規則でどのコントローラーが規則を変更する必要があるかを制御するために使用されます。

アクション規則

UseWebApiActionConventionsAttribute は、名前に基づいてアクションに HTTP メソッドをマップするために使用されます (たとえば、GetHttpGet にマップされます)。 これは、属性のルーティングを使用しないアクションにのみ適用されます。

オーバーロード

UseWebApiOverloadingAttribute は、WebApiOverloadingApplicationModelConvention 規則を適用するために使用されます。 この規則は、アクションの選択プロセスに OverloadActionConstraint を追加します。これによって、候補のアクションは、要求で省略可能でないすべてのパラメーターが満たされるものに制限されます。

パラメーター規則

UseWebApiParameterConventionsAttribute は、WebApiParameterConventionsApplicationModelConvention アクション規則を適用するために使用されます。 この規則では、アクション パラメーターとして使用される単純な型は、要求本文からバインドされる複雑な型に対し、既定で URI からバインドされることが指定されます。

Routes

UseWebApiRoutesAttribute は、WebApiApplicationModelConvention コントローラー規則が適用されるかどうかを制御します。 有効な場合、この規則はルートに領域のサポートを追加するために使用され、コントローラーが api 領域にあることを示します。

互換パッケージには、規則のセットに加え、Web API によって提供されるものの代わりとなる System.Web.Http.ApiController 基底クラスが含まれます。 これにより、Web API 用に記述され、その ApiController から継承された Web API コントローラーが、ASP.NET Core MVC での実行中に動作できるようにします。 前述のすべての UseWebApi* 属性は、基本コントローラー クラスに適用されます。 ApiController では、Web API にあるものと互換性のあるプロパティ、メソッド、および結果の型が公開されます。

ApiExplorer を使用してアプリを文書化する

このアプリケーション モデルは、アプリの構造をスキャンするために使用できる ApiExplorerModel プロパティを各レベルで公開します。 これは、Swagger などのツールを使用して、Web API のヘルプ ページを生成するために使用できます。 ApiExplorer プロパティは、アプリのモデルのどの部分を公開するか指定するために設定できる IsVisible プロパティを公開します。 次の規則を使用して、この設定を構成します。

using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
    public class EnableApiExplorerApplicationConvention : IApplicationModelConvention
    {
        public void Apply(ApplicationModel application)
        {
            application.ApiExplorer.IsVisible = true;
        }
    }
}

この方法を使用すると (必要に応じて追加の規則が必要)、アプリ内で任意のレベルで API の可視性を有効または無効にできます。