Arbeiten mit dem Anwendungsmodell in ASP.NET Core

Von Steve Smith

ASP.NET Core MVC definiert ein Anwendungsmodell, in dem die Komponenten einer MVC-App dargestellt werden. Lesen und bearbeiten Sie dieses Modell, um das Verhalten von MVC-Elementen zu ändern. MVC folgt standardmäßig bestimmten Konventionen, um zu ermitteln, welche Klassen als Controller betrachtet werden, welche Methoden in diesen Klassen Aktionen darstellen und wie sich Parameter und das Routing verhalten. Passen Sie dieses Verhalten an die Anforderungen Ihrer App an, indem Sie Ihre eigenen Konventionen schaffen und sie global oder in Form von Attributen anwenden.

Modelle und Anbieter (IApplicationModelProvider)

Das ASP.NET Core-MVC-Anwendungsmodell enthält sowohl abstrakte Schnittstellen als auch konkrete Implementierungsklassen, die eine MVC-Anwendung beschreiben. Dieses Modell resultiert aus der MVC-Ermittlung von Controllern, Aktionen, Aktionsparametern, Routen und Filtern der App gemäß den Standardkonventionen. Wenn Sie mit dem Anwendungsmodell arbeiten, können Sie Ihre App so ändern, dass sie Konventionen entspricht, die von den Konventionen des MVC-Standardverhaltens abweichen. Die Parameter, Namen, Routen und Filter werden alle als Konfigurationsdaten für Aktionen und Controller verwendet.

Das ASP.NET Core-MVC-Anwendungsmodell weist folgende Struktur auf:

  • ApplicationModel
    • Controller (ControllerModel)
      • Aktionen (ActionModel)
        • Parameter (ParameterModel)

Jede Modellebenen kann auf eine allgemeine Properties-Sammlung zugreifen. Zudem ist auf niedrigeren Ebenen der Zugriff auf Eigenschaftswerte sowie die Überschreibung von Eigenschaftswerten möglich, die auf höheren Ebenen in der Hierarchie festgelegt wurden. Die Eigenschaften werden bei der Erstellung der Aktionen in der Datei ActionDescriptor.Properties permanent gespeichert. Wenn eine Anforderung dann verarbeitet wird, kann über ActionContext.ActionDescriptor auf sämtliche Eigenschaften zugegriffen werden, die über eine Konvention hinzugefügt oder geändert wurden. Die Verwendung von Eigenschaften ist eine gute Möglichkeit zum Konfigurieren von Filtern, Modellbindungen und anderen Modellaspekten für einzelne Aktionen.

Hinweis

Die ActionDescriptor.Properties-Auflistung ist nach dem Start der App nicht threadsicher (bei Schreibvorgängen). Konventionen stellen die beste Möglichkeit zum sicheren Hinzufügen von Daten zu dieser Sammlung dar.

ASP.NET Core MVC lädt das Anwendungsmodell über ein durch die Schnittstelle IApplicationModelProvider definiertes Anbietermuster. Dieser Abschnitt enthält einige interne Implementierungsdetails zur Funktionsweise dieses Anbieters. Die Verwendung des Anbietermusters ist ein komplexeres Thema und in erster Linie für die Frameworkverwendung relevant. Die meisten Apps sollten Konventionen verwenden, nicht das Anbietermuster.

Implementierungen der Schnittstelle IApplicationModelProvider umschließen sich gegenseitig. Dabei wird bei den einzelnen Implementierungen die Methode OnProvidersExecuting in aufsteigender Reihenfolge basierend auf der zugehörigen Eigenschaft Order aufgerufen. Anschließend wird die Methode OnProvidersExecuted in umgekehrter Reihenfolge aufgerufen. Das Framework definiert verschiedene Anbieter:

Erst (Order=-1000):

  • DefaultApplicationModelProvider

Then (Order=-990):

  • AuthorizationApplicationModelProvider
  • CorsApplicationModelProvider

Hinweis

Die Reihenfolge, in der zwei Anbieter mit dem gleichen Wert für Order aufgerufen werden, ist nicht definiert und daher nicht verlässlich.

Hinweis

IApplicationModelProvider ist ein erweitertes, auszuweitendes Konzept für Autoren von Frameworks. Im Allgemeinen sollten in Apps Konventionen und in Frameworks Anbieter verwendet werden. Der Hauptunterschied besteht darin, dass Anbieter immer vor Konventionen ausgeführt werden.

Das Konzept DefaultApplicationModelProvider etabliert viele der von ASP.NET Core MVC verwendeten Standardverhaltensweisen. Zu den Zuständigkeiten des Konzepts zählen die folgenden:

  • Hinzufügen globaler Filter zum Kontext
  • Hinzufügen von Controllern zum Kontext
  • Hinzufügen öffentlicher Controllermethoden in Form von Aktionen
  • Hinzufügen von Aktionsmethodenparametern zum Kontext
  • Anwenden einer Route und anderer Attribute

Einige integrierte Verhaltensweisen werden vom DefaultApplicationModelProvider implementiert. Dieser Anbieter ist zuständig für die Erstellung des Controllermodells (ControllerModel), das wiederum auf die Instanzen ActionModel, PropertyModel und ParameterModel verweist. Die DefaultApplicationModelProvider-Klasse stellt ein Detail zur Implementierung des internen Frameworks dar, das sich möglicherweise noch ändert.

AuthorizationApplicationModelProvider ist für die Anwendung des Verhaltens zuständig, das den Attributen AuthorizeFilter und AllowAnonymousFilter zugeordnet ist. Weitere Informationen finden Sie unter Einfache Autorisierung in ASP.NET Core.

CorsApplicationModelProvider implementiert Verhalten, das IEnableCorsAttribute und IDisableCorsAttribute zugeordnet ist. Weitere Informationen finden Sie unter Aktivieren ursprungsübergreifender Anforderungen (Cross-Origin Requests, CORS) in ASP.NET Core.

Informationen zu den in diesem Abschnitt beschriebenen internen Anbietern des Frameworks sind nicht über den .NET API-Browser verfügbar. Die Anbieter können jedoch in der ASP.NET Core-Referenzquelle (GitHub-Repository „dotnet/aspnetcore“) überprüft werden. Verwenden Sie die GitHub-Suche, um die Anbieter anhand des Namens zu suchen, und wählen Sie die Version der Quelle über die Dropdownliste Branches/Tags wechseln aus.

Konventionen

Das Anwendungsmodell definiert Konventionsabstraktionen, die eine einfachere Möglichkeit zur Anpassung des Verhaltens der Modelle bieten, wodurch auf eine Überschreibung des gesamten Modells oder Anbieters verzichtet werden kann. Diese Abstraktionen werden als Methode für eine Änderung des Verhaltens einer App empfohlen. Konventionen ermöglichen das Schreiben von Code, der Anpassungen dynamisch anwendet. Während Filter eine Möglichkeit zum Ändern des Frameworkverhaltens bieten, können Sie mithilfe von Anpassungen steuern, wie die einzelnen Komponenten der App zusammenarbeiten.

Folgende Konventionen sind verfügbar:

Konventionen werden angewendet, indem sie MVC-Optionen hinzugefügt oder indem Attribute implementiert und auf Controller, Aktionen oder Aktionsparameter angewendet werden (ähnlich wie Filter). Im Gegensatz zu Filtern werden Konventionen nur ausgeführt, wenn die App gestartet wird (also nicht als Teil jeder Anforderung).

Hinweis

Informationen zu Routen- und Anwendungsmodellanbieter-Konventionen von Razor Pages finden Sie unter Razor Pages: Routen- und App-Konventionen in ASP.NET Core.

Ändern des Anwendungsmodells (ApplicationModel)

Die folgende Konvention wird verwendet, um dem Anwendungsmodell eine Eigenschaft hinzuzufügen:

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;
        }
    }
}

Konventionen von Anwendungsmodellen werden als Optionen angewendet, wenn MVC in Startup.ConfigureServices hinzugefügt wird:

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

Auf Eigenschaften kann innerhalb von Controlleraktionen über die ActionDescriptor.Properties-Auflistung zugegriffen werden:

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

Ändern der ControllerModel-Beschreibung

Das Controllermodell kann auch benutzerdefinierte Eigenschaften enthalten. Benutzerdefinierte Eigenschaften setzen vorhandene Eigenschaften mit dem gleichen Namen, die im Anwendungsmodell angegeben sind, außer Kraft. Das folgende Konventionsattribut fügt eine Beschreibung auf Controllerebene hinzu:

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;
        }
    }
}

Diese Konvention wird als Attribut für einen Controller angewendet:

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

Ändern der ActionModel-Beschreibung

Auf einzelne Aktionen kann eine separate Attributkonvention angewendet werden, mit der das bereits auf Anwendungs- oder Controllerebene angewendete Verhalten außer Kraft gesetzt wird:

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;
        }
    }
}

Die Anwendung dieser Konvention auf eine Aktion innerhalb des Controllers veranschaulicht, wie die Konvention auf Controllerebene außer Kraft gesetzt wird:

[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"];
    }
}

Ändern des Parametermodells (ParameterModel)

Die folgende Konvention kann auf Aktionsparameter zur Änderung ihrer BindingInfo angewendet werden. Für die folgende Konvention muss der Parameter ein Routenparameter sein. Andere potenzielle Bindungsquellen (z. B. Abfragezeichenfolgenwerte) werden ignoriert:

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;
        }
    }
}

Das Attribut kann möglicherweise auf sämtliche Aktionsparameter angewendet werden:

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}";
    }
}

Um die Konvention auf alle Aktionsparameter anzuwenden, fügen Sie MustBeInRouteParameterModelConvention zu MvcOptions in Startup.ConfigureServices hinzu:

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

Ändern des ActionModel-Namens

Folgende Konvention ändert das ActionModel, um den Namen der Aktion zu aktualisieren, auf die sie angewendet wird. Der neue Name wird als Parameter für das Attribut bereitgestellt. Dieser neue Name wird beim Routing verwendet und beeinflusst die Route, die zum Erreichen dieser Aktionsmethode verwendet wird:

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;
        }
    }
}

Dieses Attribut wird auf eine Aktionsmethode im HomeController angewendet:

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

Obwohl der Methodenname SomeName lautet, überschreibt das Attribut die MVC-Konvention für die Verwendung des Methodennamens und ersetzt den Aktionsnamen durch MyCoolAction. Daher lautet die Route zum Erreichen dieser Aktion /Home/MyCoolAction.

Hinweis

Das Beispiel in diesem Abschnitt ist im Wesentlichen mit der Verwendung des integrierten Aktionsnamenattribut (ActionNameAttribute) identisch.

Benutzerdefinierte Routingkonvention

Verwenden Sie IApplicationModelConvention, um die Funktionsweise des Routings anzupassen. Die folgende Konvention bezieht beispielsweise Namespaces von Controllern in ihre Routen ein und ersetzt . im Namespace durch / in der Route:

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
        }
    }
}

Die Konvention wird als Option in Startup.ConfigureServices hinzugefügt:

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

Tipp

Verwenden Sie den folgenden Ansatz, um Konventionen über MvcOptions zur Middleware hinzuzufügen. Der Platzhalter {CONVENTION} ist die hinzuzufügende Konvention:

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

Im folgenden Beispiel wird eine Konvention auf Routen angewendet, die kein Attributrouting verwenden, bei dem der Name des Controllers Namespace enthält:

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.";
        }
    }
}

Anwendungsmodellnutzung in WebApiCompatShim

ASP.NET Core MVC verwendet andere Konventionen aus ASP.NET-Web-API 2. Mithilfe von benutzerdefinierten Konventionen können Sie das Verhalten einer ASP.NET Core-MVC-App so ändern, dass es mit dem einer Web-API-App konsistent ist. Speziell für diesen Zweck wird von Microsoft das NuGet-Paket WebApiCompatShim bereitgestellt.

Hinweis

Weitere Informationen zur Migration von der ASP.NET-Web-API finden Sie unter Migrieren von der ASP.NET-Web-API zu ASP.NET Core.

So verwenden Sie das Web-API-Kompatibilitätsshim:

  • Fügen Sie dem Projekt das Paket Microsoft.AspNetCore.Mvc.WebApiCompatShim hinzu.
  • Fügen Sie die Konventionen zu MVC hinzu, indem Sie AddWebApiConventions in Startup.ConfigureServices aufrufen:
services.AddMvc().AddWebApiConventions();

Die von der Shim bereitgestellten Konventionen gelten nur für Teile der App, auf die bestimmte Attribute angewendet worden sind. Die folgenden vier Attribute steuern, bei welchen Controllern die zugehörigen Konventionen durch die Konventionen der Shim geändert werden sollten:

Aktionskonventionen

UseWebApiActionConventionsAttribute wird für die Zuordnung der HTTP-Methode zu Aktionen basierend auf ihrem Namen verwendet. (Get wird beispielsweise der Aktion HttpGet zugeordnet.) Es gilt nur für Aktionen, die kein Attributrouting verwenden.

Überladen

UseWebApiOverloadingAttribute wird zum Anwenden der Konvention WebApiOverloadingApplicationModelConvention verwendet. Diese Konvention fügt eine OverloadActionConstraint zum Aktionsauswahlprozess hinzu, wodurch mögliche Aktionen auf die Aktionen begrenzt werden, bei denen die Anforderung alle nicht optionalen Parameter erfüllt.

Parameterkonventionen

UseWebApiParameterConventionsAttribute wird zum Anwenden der Aktionskonvention WebApiParameterConventionsApplicationModelConvention verwendet. Diese Konvention gibt an, dass einfache Typen, die als Aktionsparameter verwendet werden, standardmäßig vom URI abhängen, während komplexe Typen vom Anforderungstext abhängen.

Routes

UseWebApiRoutesAttribute steuert, ob die Controllerkonvention WebApiApplicationModelConvention angewendet wird. Bei Aktivierung wird diese Konvention verwendet, um Unterstützung für Bereiche zur Route hinzuzufügen, und sie gibt an, dass sich der Controller im api-Bereich befindet.

Neben einer Reihe von Konventionen enthält das Kompatibilitätspaket eine System.Web.Http.ApiController-Basisklasse, die die von der Web-API bereitgestellte Klasse ersetzt. Dadurch können Ihre Web-API-Controller, die für die Web-API geschrieben wurden und von dem zugehörigen API-Controller ApiController erben, in ASP.NET Core MVC ausgeführt werden. Alle oben aufgeführten UseWebApi*-Attribute werden auf die Basiscontrollerklasse angewendet. Der API-Controller (ApiController) macht Eigenschaften, Methoden und Ergebnistypen verfügbar, die mit denen in der Web-API kompatibel sind.

Dokumentieren einer App mithilfe von ApiExplorer

Das Anwendungsmodell macht auf jeder Ebene, auf der die Struktur der App durchlaufen werden kann, eine Eigenschaft vom Typ ApiExplorerModel verfügbar. Dies ermöglicht das Generieren von Hilfeseiten für Ihre Web-APIs mithilfe von Tools wie Swagger. Die Eigenschaft ApiExplorer macht eine Eigenschaft vom Typ IsVisible verfügbar, die festgelegt werden kann, um anzugeben, welche Teile des Modells der App verfügbar gemacht werden sollen. Konfigurieren Sie diese Einstellung mithilfe einer Konvention:

using Microsoft.AspNetCore.Mvc.ApplicationModels;

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

Mithilfe dieses Ansatzes (und ggf. zusätzlicher Konventionen) wird die API-Sichtbarkeit auf einer beliebigen Ebene innerhalb einer App aktiviert oder deaktiviert.