Filtry w ASP.NET Core

Przez Kirk Larkin, Rick Anderson, Tom Dykstra i Steve Smith

Filtry w ASP.NET Core umożliwiają uruchamianie kodu przed lub po określonych etapach w potoku przetwarzania żądań.

Wbudowane filtry obsługują zadania, takie jak:

  • Autoryzacja, uniemożliwiająca dostęp do zasobów, dla których użytkownik nie jest autoryzowany.
  • Buforowanie odpowiedzi, zwarcie potoku żądania w celu zwrócenia buforowanej odpowiedzi.

Filtry niestandardowe można tworzyć w celu obsługi problemów związanych z wycinaniami krzyżowymi. Przykłady problemów obejmujących krzyżowe obejmują obsługę błędów, buforowanie, konfigurację, autoryzację i rejestrowanie. Filtry unikają duplikowania kodu. Na przykład filtr wyjątków obsługi błędów może skonsolidować obsługę błędów.

Ten dokument dotyczy Razor stron, kontrolerów interfejsu API i kontrolerów z widokami. Filtry nie działają bezpośrednio ze składnikamiRazor. Filtr może mieć wpływ tylko pośrednio na składnik, gdy:

  • Składnik jest osadzony na stronie lub w widoku.
  • Strona lub kontroler i widok używają filtru.

Jak działają filtry

Filtry są uruchamiane w potoku wywołania akcji ASP.NET Core, czasami nazywane potokiem filtru. Potok filtru jest uruchamiany po wybraniu akcji do wykonania przez ASP.NET Core:

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

Typy filtrów

Każdy typ filtru jest wykonywany na innym etapie w potoku filtru:

  • Filtry autoryzacji:

    • Uruchom najpierw polecenie .
    • Ustal, czy użytkownik ma autoryzację do żądania.
    • Zwarcie potoku, jeśli żądanie nie jest autoryzowane.
  • Filtry zasobów:

    • Uruchom polecenie po autoryzacji.
    • OnResourceExecuting uruchamia kod przed resztą potoku filtru. Na przykład OnResourceExecuting uruchamia kod przed powiązaniem modelu.
    • OnResourceExecuted uruchamia kod po zakończeniu pozostałej części potoku.
  • Filtry akcji:

    • Uruchom bezpośrednio przed wywołaną metodą akcji i po jej wywołaniu.
    • Może zmienić argumenty przekazane do akcji.
    • Może zmienić wynik zwrócony z akcji.
    • Nieobsługiwane na Razor stronach.
  • Filtry punktów końcowych:

    • Uruchom bezpośrednio przed wywołaną metodą akcji i po jej wywołaniu.
    • Może zmienić argumenty przekazane do akcji.
    • Może zmienić wynik zwrócony z akcji.
    • Nieobsługiwane na Razor stronach.
    • Można wywołać zarówno na akcjach, jak i na punktach końcowych opartych na programie obsługi tras.
  • Filtry wyjątków stosują zasady globalne do nieobsługiwanych wyjątków występujących przed zapisaniem treści odpowiedzi.

  • Filtry wyników:

    • Uruchom polecenie bezpośrednio przed i po wykonaniu wyników akcji.
    • Uruchom polecenie tylko wtedy, gdy metoda akcji zostanie wykonana pomyślnie.
    • Są przydatne w przypadku logiki, która musi otaczać wykonywanie widoku lub formatera.

Na poniższym diagramie przedstawiono sposób interakcji typów filtrów w potoku filtru:

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

Razor Strony obsługują Razor również filtry strony, które są uruchamiane przed i po procedurze Razor obsługi strony.

Implementacja

Filtry obsługują zarówno implementacje synchroniczne, jak i asynchroniczne za pomocą różnych definicji interfejsu.

Filtry synchroniczne są uruchamiane przed i po etapie potoku. Na przykład OnActionExecuting jest wywoływana przed wywołaniem metody akcji. OnActionExecuted jest wywoływana po zwracaniu metody akcji:

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

Filtry asynchroniczne definiują metodę On-Stage-ExecutionAsync . Na przykład : OnActionExecutionAsync

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // Do something before the action executes.
        await next();
        // Do something after the action executes.
    }
}

W poprzednim kodzie element SampleAsyncActionFilter ma ActionExecutionDelegatewartość , nextktóra wykonuje metodę akcji .

Wiele etapów filtrowania

Interfejsy dla wielu etapów filtrowania można zaimplementować w jednej klasie. Na przykład ActionFilterAttribute klasa implementuje:

Zaimplementuj synchroniczną lub asynchroniczną wersję interfejsu filtru, a nie obie. Środowisko uruchomieniowe najpierw sprawdza, czy filtr implementuje interfejs asynchroniczny, a jeśli tak, wywołuje to. Jeśli nie, wywołuje metody interfejsu synchronicznego. Jeśli oba interfejsy asynchroniczne i synchroniczne są implementowane w jednej klasie, wywoływana jest tylko metoda asynchroniczna. W przypadku używania klas abstrakcyjnych, takich jak ActionFilterAttribute, przesłaniaj tylko metody synchroniczne lub metody asynchroniczne dla każdego typu filtru.

Wbudowane atrybuty filtru

ASP.NET Core zawiera wbudowane filtry oparte na atrybutach, które mogą być podklasowane i dostosowane. Na przykład następujący filtr wyników dodaje nagłówek do odpowiedzi:

public class ResponseHeaderAttribute : ActionFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public ResponseHeaderAttribute(string name, string value) =>
        (_name, _value) = (name, value);

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_name, _value);

        base.OnResultExecuting(context);
    }
}

Atrybuty umożliwiają filtrowanie akceptowania argumentów, jak pokazano w poprzednim przykładzie. Zastosuj element ResponseHeaderAttribute do kontrolera lub metody akcji i określ nazwę i wartość nagłówka HTTP:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

Użyj narzędzia, takiego jak narzędzia deweloperskie przeglądarki, aby zbadać nagłówki. W obszarze Nagłówkifilter-header: Filter Value odpowiedzi zostanie wyświetlona wartość .

Następujący kod dotyczy ResponseHeaderAttribute zarówno kontrolera, jak i akcji:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

    [ResponseHeader("Another-Filter-Header", "Another Filter Value")]
    public IActionResult Multiple() =>
        Content("Examine the response headers using the F12 developer tools.");
}

Odpowiedzi z Multiple akcji obejmują następujące nagłówki:

  • filter-header: Filter Value
  • another-filter-header: Another Filter Value

Kilka interfejsów filtru ma odpowiednie atrybuty, których można używać jako klas bazowych na potrzeby implementacji niestandardowych.

Atrybuty filtru:

Nie można zastosować filtrów do Razor metod obsługi strony. Można je zastosować do Razor modelu strony lub globalnie.

Filtrowanie zakresów i kolejności wykonywania

Filtr można dodać do potoku w jednym z trzech zakresów:

  • Używanie atrybutu na kontrolerze lub Razor stronie.
  • Używanie atrybutu w akcji kontrolera. Nie można zastosować atrybutów filtru do Razor metod obsługi stron.
  • Globalnie dla wszystkich kontrolerów, akcji i Razor stron, jak pokazano w poniższym kodzie:
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalSampleActionFilter>();
    });
    

Domyślna kolejność wykonywania

Jeśli istnieje wiele filtrów dla określonego etapu potoku, zakres określa domyślną kolejność wykonywania filtru. Filtry globalne otaczają filtry klas, które z kolei otaczają filtry metod.

W wyniku zagnieżdżania filtrów kod po filtrach jest uruchamiany w odwrotnej kolejności przed kodem. Sekwencja filtru:

  • Przed kodem filtrów globalnych.
    • Przed kodem filtrów kontrolera.
      • Przed kodem metody akcji filtruje.
      • Po kodzie metody akcji filtruje.
    • Po kodzie kontrolera filtrów.
  • Po kodzie filtrów globalnych.

Poniższy przykład ilustruje kolejność uruchamiania metod filtrowania dla filtrów akcji synchronicznych:

Sequence Zakres filtru Metoda filter
1 Globalnie OnActionExecuting
2 Kontroler OnActionExecuting
3 Akcja OnActionExecuting
4 Akcja OnActionExecuted
5 Kontroler OnActionExecuted
6 Globalnie OnActionExecuted

Filtry na poziomie kontrolera

Każdy kontroler dziedziczony z Controller zawiera OnActionExecutingmetody , OnActionExecutionAsynci OnActionExecuted . Te metody opakowuje filtry uruchamiane dla danej akcji:

  • OnActionExecuting uruchamia się przed dowolnym filtrem akcji.
  • OnActionExecuted uruchamia się po wszystkich filtrach akcji.
  • OnActionExecutionAsync uruchamia się przed dowolnym filtrem akcji. Kod po wywołaniu do next uruchomienia po filtrach akcji.

Następująca ControllerFiltersController klasa:

  • Stosuje element SampleActionFilterAttribute ([SampleActionFilter]) do kontrolera.
  • Przesłonięcia OnActionExecuting i OnActionExecuted.
[SampleActionFilter]
public class ControllerFiltersController : Controller
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuting)}");

        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuted)}");

        base.OnActionExecuted(context);
    }

    public IActionResult Index()
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(Index)}");

        return Content("Check the Console.");
    }
}

Przechodzenie do https://localhost:<port>/ControllerFilters uruchomi następującego kodu:

  • ControllerFiltersController.OnActionExecuting
    • GlobalSampleActionFilter.OnActionExecuting
      • SampleActionFilterAttribute.OnActionExecuting
        • ControllerFiltersController.Index
      • SampleActionFilterAttribute.OnActionExecuted
    • GlobalSampleActionFilter.OnActionExecuted
  • ControllerFiltersController.OnActionExecuted

Filtry na poziomie kontrolera ustawiają właściwość Order na int.MinValue. Filtry na poziomie kontrolera nie można ustawić tak, aby były uruchamiane po zastosowaniu filtrów do metod. Kolejność jest wyjaśniona w następnej sekcji.

Aby uzyskać informacje na temat Razor stron, zobacz Implementowanie Razor filtrów stron przez zastępowanie metod filtrowania.

Zastąpij kolejność domyślną

Domyślną sekwencję wykonywania można zastąpić przez zaimplementowanie metody IOrderedFilter. IOrderedFilter Uwidacznia Order właściwość, która ma pierwszeństwo przed zakresem, aby określić kolejność wykonywania. Filtr o niższej Order wartości:

  • Uruchamia przed kodem przed filtrem o wyższej wartości Order.
  • Uruchamia po kodzie po filtrze z wyższą Order wartością.

W przykładzie filtry na poziomie kontrolera ma zakres globalny, GlobalSampleActionFilter więc działa przed SampleActionFilterAttribute, który ma zakres kontrolera. Aby najpierw wykonać SampleActionFilterAttribute przebieg, ustaw jego kolejność na int.MinValue:

[SampleActionFilter(Order = int.MinValue)]
public class ControllerFiltersController : Controller
{
    // ...
}

Aby najpierw ustawić przebieg filtru GlobalSampleActionFilter globalnego, ustaw wartość Orderint.MinValue:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<GlobalSampleActionFilter>(int.MinValue);
});

Anulowanie i zwarcie

Potok filtru można zwarcić, ustawiając Result właściwość parametru ResourceExecutingContext podanego w metodzie filtru. Na przykład następujący filtr zasobu uniemożliwia wykonywanie pozostałej części potoku:

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult
        {
            Content = nameof(ShortCircuitingResourceFilterAttribute)
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context) { }
}

W poniższym kodzie, zarówno filtr, jak [ShortCircuitingResourceFilter] i docelowy [ResponseHeader]Index filtr metody akcji. Filtr ShortCircuitingResourceFilterAttribute :

  • Uruchamia najpierw, ponieważ jest to filtr zasobów i ResponseHeaderAttribute jest filtrem akcji.
  • Zwarcie pozostałej części potoku.

W związku z tym ResponseHeaderAttribute filtr nigdy nie jest uruchamiany dla Index akcji. To zachowanie byłoby takie samo, jeśli oba filtry zostały zastosowane na poziomie metody akcji, pod warunkiem, że uruchomiono ShortCircuitingResourceFilterAttribute najpierw. Przebiegi ShortCircuitingResourceFilterAttribute najpierw ze względu na jego typ filtru:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ShortCircuitingController : Controller
{
    [ShortCircuitingResourceFilter]
    public IActionResult Index() =>
        Content($"- {nameof(ShortCircuitingController)}.{nameof(Index)}");
}

Wstrzykiwanie zależności

Filtry można dodawać według typu lub wystąpienia. Jeśli wystąpienie zostanie dodane, to wystąpienie jest używane dla każdego żądania. Jeśli typ zostanie dodany, zostanie aktywowany typ. Filtr aktywowany przez typ oznacza:

  • Wystąpienie jest tworzone dla każdego żądania.
  • Wszystkie zależności konstruktora są wypełniane przez wstrzykiwanie zależności (DI).

Filtry implementowane jako atrybuty i dodawane bezpośrednio do klas kontrolerów lub metod akcji nie mogą mieć zależności konstruktora zapewniane przez wstrzykiwanie zależności (DI). Zależności konstruktora nie mogą być udostępniane przez di, ponieważ atrybuty muszą mieć parametry konstruktora podane tam, gdzie są stosowane.

Następujące filtry obsługują zależności konstruktora dostarczone z di:

Powyższe filtry można zastosować do kontrolera lub akcji.

Rejestratory są dostępne w witrynie DI. Należy jednak unikać tworzenia i używania filtrów wyłącznie do celów rejestrowania. Wbudowane rejestrowanie struktury zwykle zapewnia, co jest potrzebne do rejestrowania. Rejestrowanie dodane do filtrów:

  • Należy skoncentrować się na problemach dotyczących domeny biznesowej lub zachowaniu specyficznym dla filtru.
  • Nie należy rejestrować akcji ani innych zdarzeń struktury. Wbudowane filtry rejestrują już akcje i zdarzenia struktury.

ServiceFilterAttribute

Typy implementacji filtru usługi są rejestrowane w programie Program.cs. Element ServiceFilterAttribute pobiera wystąpienie filtru z di.

Poniższy kod przedstawia klasę LoggingResponseHeaderFilterService , która używa di:

public class LoggingResponseHeaderFilterService : IResultFilter
{
    private readonly ILogger _logger;

    public LoggingResponseHeaderFilterService(
            ILogger<LoggingResponseHeaderFilterService> logger) =>
        _logger = logger;

    public void OnResultExecuting(ResultExecutingContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuting)}");

        context.HttpContext.Response.Headers.Add(
            nameof(OnResultExecuting), nameof(LoggingResponseHeaderFilterService));
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuted)}");
    }
}

W poniższym kodzie LoggingResponseHeaderFilterService zostanie dodany do kontenera DI:

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

W poniższym kodzie ServiceFilter atrybut pobiera wystąpienie filtru LoggingResponseHeaderFilterService z di:

[ServiceFilter<LoggingResponseHeaderFilterService>]
public IActionResult WithServiceFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}");

W przypadku używania polecenia ServiceFilterAttribute, ustawienie ServiceFilterAttribute.IsReusable:

  • Zawiera wskazówkę, że wystąpienie filtru może być ponownie używane poza zakresem żądania, w ramach którego został utworzony. Środowisko uruchomieniowe ASP.NET Core nie gwarantuje:
    • Zostanie utworzone pojedyncze wystąpienie filtru.
    • Filtr nie zostanie ponownie zażądany z kontenera DI w późniejszym czasie.
  • Nie należy używać z filtrem, który zależy od usług z okresem istnienia innym niż pojedynczy.

ServiceFilterAttribute implementuje IFilterFactory. IFilterFactory Uwidacznia metodę CreateInstanceIFilterMetadata tworzenia wystąpienia. CreateInstance ładuje określony typ z DI.

TypeFilterAttribute

TypeFilterAttribute jest podobny do ServiceFilterAttribute, ale jego typ nie jest rozpoznawany bezpośrednio z kontenera DI. Tworzy wystąpienie typu przy użyciu polecenia Microsoft.Extensions.DependencyInjection.ObjectFactory.

Ponieważ TypeFilterAttribute typy nie są rozpoznawane bezpośrednio z kontenera DI:

  • Typy, do których odwołuje się odwołanie przy użyciu TypeFilterAttribute kontenera di, nie muszą być zarejestrowane. Mają one swoje zależności spełnione przez kontener DI.
  • TypeFilterAttribute opcjonalnie może akceptować argumenty konstruktora dla typu.

W przypadku używania polecenia TypeFilterAttribute, ustawienie TypeFilterAttribute.IsReusable:

  • Zawiera wskazówkę, że wystąpienie filtru może być ponownie używane poza zakresem żądania, w ramach którego został utworzony. Środowisko uruchomieniowe ASP.NET Core nie gwarantuje utworzenia pojedynczego wystąpienia filtru.

  • Nie należy używać z filtrem, który zależy od usług z okresem istnienia innym niż pojedynczy.

W poniższym przykładzie pokazano, jak przekazać argumenty do typu przy użyciu polecenia TypeFilterAttribute:

[TypeFilter(typeof(LoggingResponseHeaderFilter),
    Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithTypeFilter)}");

Filtry autoryzacji

Filtry autoryzacji:

  • Czy pierwsze filtry są uruchamiane w potoku filtru.
  • Kontrolowanie dostępu do metod akcji.
  • Przed metodą, ale nie po metodzie.

Niestandardowe filtry autoryzacji wymagają niestandardowej struktury autoryzacji. Preferuj konfigurowanie zasad autoryzacji lub pisanie niestandardowych zasad autoryzacji w celu zapisania filtru niestandardowego. Wbudowany filtr autoryzacji:

  • Wywołuje system autoryzacji.
  • Nie autoryzuje żądań.

Nie zgłaszaj wyjątków w filtrach autoryzacji:

  • Wyjątek nie zostanie obsłużony.
  • Filtry wyjątków nie będą obsługiwać wyjątku.

Rozważ wystawienie wyzwania w przypadku wystąpienia wyjątku w filtrze autoryzacji.

Dowiedz się więcej o autoryzacji.

Filtry zasobów

Filtry zasobów:

Filtry zasobów są przydatne w przypadku zwarć większość potoku. Na przykład filtr buforowania może uniknąć reszty potoku po trafieniu pamięci podręcznej.

Przykłady filtrów zasobów:

Filtry akcji

Filtry akcji nie mają zastosowania do Razor stron. Razor Obsługa stron i IPageFilterIAsyncPageFilter. Aby uzyskać więcej informacji, zobacz Metody filtrowania dla Razor stron.

Filtry akcji:

Poniższy kod przedstawia przykładowy filtr akcji:

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

Element ActionExecutingContext zawiera następujące właściwości:

  • ActionArguments — umożliwia odczytywanie danych wejściowych do metody akcji.
  • Controller — umożliwia manipulowanie wystąpieniem kontrolera.
  • Result - ustawianie Result zwarć wykonywania metody akcji i kolejnych filtrów akcji.

Zgłaszanie wyjątku w metodzie akcji:

  • Zapobiega uruchamianiu kolejnych filtrów.
  • W przeciwieństwie do ustawienia Result, jest traktowany jako błąd zamiast pomyślnego wyniku.

Controller Zapewnia ActionExecutedContext i Result oraz następujące właściwości:

  • Canceled — prawda, jeśli wykonanie akcji zostało zwarcie przez inny filtr.
  • Exception — Wartość inna niż null, jeśli akcja lub wcześniej uruchomiony filtr akcji zwrócił wyjątek. Ustawienie tej właściwości na null:
    • Skutecznie obsługuje wyjątek.
    • Result jest wykonywany tak, jakby został zwrócony z metody akcji.

W przypadku metody IAsyncActionFilterwywołanie metody ActionExecutionDelegate:

  • Wykonuje wszystkie kolejne filtry akcji i metodę akcji.
  • Zwraca wartość ActionExecutedContext.

Aby zwarć, przypisz Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result do wystąpienia wyników i nie wywołuje next metody ().ActionExecutionDelegate

Struktura zapewnia abstrakcję ActionFilterAttribute , która może być podklasowana.

Filtr OnActionExecuting akcji może służyć do:

  • Weryfikowanie stanu modelu.
  • Zwróć błąd, jeśli stan jest nieprawidłowy.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Uwaga

Kontrolery z adnotacjami z atrybutem [ApiController] automatycznie weryfikują stan modelu i zwracają odpowiedź 400. Aby uzyskać więcej informacji, zobacz Automatyczne odpowiedzi HTTP 400.

Metoda OnActionExecuted jest uruchamiana po metodzie akcji:

  • I może wyświetlać i manipulować wynikami akcji za pośrednictwem Result właściwości .
  • Canceled parametr ma wartość true, jeśli wykonanie akcji zostało zwarcie przez inny filtr.
  • Exception parametr jest ustawiony na wartość inną niż null, jeśli akcja lub kolejny filtr akcji zwrócił wyjątek. Ustawienie Exception wartości null:
    • Skutecznie obsługuje wyjątek.
    • ActionExecutedContext.Result jest wykonywany tak, jakby został zwrócony normalnie z metody akcji.

Filtry wyjątków

Filtry wyjątków:

Poniższy przykładowy filtr wyjątku wyświetla szczegółowe informacje o wyjątkach występujących podczas opracowywania aplikacji:

public class SampleExceptionFilter : IExceptionFilter
{
    private readonly IHostEnvironment _hostEnvironment;

    public SampleExceptionFilter(IHostEnvironment hostEnvironment) =>
        _hostEnvironment = hostEnvironment;

    public void OnException(ExceptionContext context)
    {
        if (!_hostEnvironment.IsDevelopment())
        {
            // Don't display exception details unless running in Development.
            return;
        }

        context.Result = new ContentResult
        {
            Content = context.Exception.ToString()
        };
    }
}

Poniższy kod testuje filtr wyjątku:

[TypeFilter<SampleExceptionFilter>]
public class ExceptionController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(ExceptionController)}.{nameof(Index)}");
}

Filtry wyjątków:

  • Nie mają wcześniej i po zdarzeniach.
  • Zaimplementuj OnException lub OnExceptionAsync.
  • Obsługa nieobsługiwanych wyjątków występujących w Razor procesie tworzenia strony lub kontrolera, powiązania modelu, filtrów akcji lub metod akcji.
  • Nie przechwytuj wyjątków występujących w filtrach zasobów, filtrach wyników lub wykonywaniu wyników MVC.

Aby obsłużyć wyjątek, ustaw ExceptionHandled właściwość na true lub przypisz Result właściwość . Spowoduje to zatrzymanie propagacji wyjątku. Filtr wyjątku nie może przekształcić wyjątku w "powodzenie". Tylko filtr akcji może to zrobić.

Filtry wyjątków:

  • Są dobre w przypadku podlewek wyjątków występujących w ramach akcji.
  • Nie są tak elastyczne, jak oprogramowanie pośredniczące obsługujące błędy.

Preferuj oprogramowanie pośredniczące do obsługi wyjątków. Używaj filtrów wyjątków tylko wtedy, gdy obsługa błędów różni się w zależności od wywoływanej metody akcji. Na przykład aplikacja może mieć metody akcji zarówno dla punktów końcowych interfejsu API, jak i dla widoków/HTML. Punkty końcowe interfejsu API mogą zwracać informacje o błędach jako JSWŁĄCZONE, podczas gdy akcje oparte na widoku mogą zwracać stronę błędu jako kod HTML.

Filtry wyników

Filtry wyników:

IResultFilter i IAsyncResultFilter

Poniższy kod przedstawia przykładowy filtr wyników:

public class SampleResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        // Do something before the result executes.
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Do something after the result executes.
    }
}

Rodzaj wykonywanego wyniku zależy od akcji. Akcja zwracająca widok obejmuje wszystkie przetwarzanie razor w ramach wykonywanego ViewResult elementu. Metoda interfejsu API może wykonać serializacji w ramach wykonywania wyniku. Dowiedz się więcej o wynikach akcji.

Filtry wyników są wykonywane tylko wtedy, gdy akcja lub filtr akcji generuje wynik akcji. Filtry wyników nie są wykonywane, gdy:

  • Filtr autoryzacji lub filtr zasobów zwarcie potoku.
  • Filtr wyjątku obsługuje wyjątek, generując wynik akcji.

Metoda Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting może zwarć wykonywanie wyniku akcji i kolejnych filtrów wyników, ustawiając wartość Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Canceltrue. Zapisz w obiekcie odpowiedzi podczas zwarć, aby uniknąć generowania pustej odpowiedzi. Zgłaszanie wyjątku w pliku IResultFilter.OnResultExecuting:

  • Zapobiega wykonywaniu wyniku akcji i kolejnych filtrów.
  • Jest traktowany jako błąd zamiast pomyślnego wyniku.

Po uruchomieniu Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted metody odpowiedź prawdopodobnie została już wysłana do klienta. Jeśli odpowiedź została już wysłana do klienta, nie można jej zmienić.

ResultExecutedContext.Canceled parametr jest ustawiony na true wartość , jeśli wykonanie wyniku akcji zostało zwarcie przez inny filtr.

ResultExecutedContext.Exception parametr jest ustawiony na wartość inną niż null, jeśli wynik akcji lub kolejny filtr wyników zwrócił wyjątek. Ustawienie Exception wartości null skutecznie obsługuje wyjątek i uniemożliwia ponowne zgłoszenie wyjątku w dalszej części potoku. Nie ma niezawodnego sposobu zapisywania danych w odpowiedzi podczas obsługi wyjątku w filtrze wyników. Jeśli nagłówki zostały opróżnione do klienta, gdy wynik akcji zgłasza wyjątek, nie ma niezawodnego mechanizmu wysyłania kodu błędu.

W przypadku elementu IAsyncResultFilterwywołanie metody await next na obiekcie ResultExecutionDelegate wykonuje wszystkie kolejne filtry wyników i wynik akcji. Aby zwarć, ustaw wartość ResultExecutingContext.Canceltrue i nie wywołaj metody ResultExecutionDelegate:

public class SampleAsyncResultFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(
        ResultExecutingContext context, ResultExecutionDelegate next)
    {
        if (context.Result is not EmptyResult)
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }
    }
}

Struktura zapewnia abstrakcję ResultFilterAttribute , która może być podklasowana. Pokazana wcześniej klasa ResponseHeaderAttribute jest przykładem atrybutu filtru wyników.

IAlwaysRunResultFilter i IAsyncAlwaysRunResultFilter

Interfejsy IAlwaysRunResultFilter i IAsyncAlwaysRunResultFilter deklarują implementację uruchamianą IResultFilter dla wszystkich wyników akcji. Obejmuje to wyniki akcji wygenerowane przez:

  • Filtry autoryzacji i filtry zasobów, które są zwarcie.
  • Filtry wyjątków.

Na przykład następujący filtr zawsze jest uruchamiany i ustawia wynik akcji (ObjectResult) z kodem stanu jednostki nieprzetworzonej 422, gdy negocjowanie zawartości kończy się niepowodzeniem:

public class UnprocessableResultFilter : IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult
            && statusCodeResult.StatusCode == StatusCodes.Status415UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Unprocessable")
            {
                StatusCode = StatusCodes.Status422UnprocessableEntity
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context) { }
}

IFilterFactory

IFilterFactory implementuje IFilterMetadata. IFilterFactory W związku z tym wystąpienie może być używane jako IFilterMetadata wystąpienie w dowolnym miejscu w potoku filtru. Gdy środowisko uruchomieniowe przygotowuje się do wywołania filtru, próbuje rzutować go do elementu IFilterFactory. Jeśli rzutowanie powiedzie się, metoda jest wywoływana w CreateInstance celu utworzenia wywoływanego IFilterMetadata wystąpienia. Zapewnia to elastyczny projekt, ponieważ dokładny potok filtru nie musi być jawnie ustawiany podczas uruchamiania aplikacji.

IFilterFactory.IsReusable:

  • Jest wskazówką fabryki, że wystąpienie filtru utworzone przez fabrykę może być ponownie używane poza zakresem żądania, w ramach którego został utworzony.
  • Nie należy używać z filtrem, który zależy od usług z okresem istnienia innym niż pojedynczy.

Środowisko uruchomieniowe ASP.NET Core nie gwarantuje:

  • Zostanie utworzone pojedyncze wystąpienie filtru.
  • Filtr nie zostanie ponownie zażądany z kontenera DI w późniejszym czasie.

Ostrzeżenie

Skonfiguruj wartość tak, aby zwracać IFilterFactory.IsReusabletrue tylko wtedy, gdy źródło filtrów jest jednoznaczne, filtry są bezstanowe, a filtry są bezpieczne do użycia w wielu żądaniach HTTP. Na przykład nie zwracaj filtrów z di, które są zarejestrowane jako zakres lub przejściowe, jeśli IFilterFactory.IsReusable zwraca wartość true.

IFilterFactory można zaimplementować przy użyciu implementacji atrybutów niestandardowych jako innego podejścia do tworzenia filtrów:

public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
        new InternalResponseHeaderFilter();

    private class InternalResponseHeaderFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context) =>
            context.HttpContext.Response.Headers.Add(
                nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));

        public void OnActionExecuted(ActionExecutedContext context) { }
    }

Filtr jest stosowany w następującym kodzie:

[ResponseHeaderFilterFactory]
public IActionResult Index() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(Index)}");

IFilterFactory zaimplementowany w atrybucie

Filtry implementujące IFilterFactory są przydatne w przypadku filtrów, które:

  • Nie wymagaj przekazywania parametrów.
  • Mieć zależności konstruktora, które muszą być wypełnione przez di.

TypeFilterAttribute implementuje IFilterFactory. IFilterFactory Uwidacznia metodę CreateInstanceIFilterMetadata tworzenia wystąpienia. CreateInstance ładuje określony typ z kontenera usług (DI).

public class SampleActionTypeFilterAttribute : TypeFilterAttribute
{
    public SampleActionTypeFilterAttribute()
         : base(typeof(InternalSampleActionFilter)) { }

    private class InternalSampleActionFilter : IActionFilter
    {
        private readonly ILogger<InternalSampleActionFilter> _logger;

        public InternalSampleActionFilter(ILogger<InternalSampleActionFilter> logger) =>
            _logger = logger;

        public void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuting)}");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuted)}");
        }
    }
}

Poniższy kod przedstawia trzy podejścia do zastosowania filtru:

[SampleActionTypeFilter]
public IActionResult WithDirectAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithDirectAttribute)}");

[TypeFilter<SampleActionTypeFilterAttribute>]
public IActionResult WithTypeFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithTypeFilterAttribute)}");

[ServiceFilter<SampleActionTypeFilterAttribute>]
public IActionResult WithServiceFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithServiceFilterAttribute)}");

W poprzednim kodzie preferowane jest pierwsze podejście do zastosowania filtru.

Używanie oprogramowania pośredniczącego w potoku filtru

Filtry zasobów działają jak oprogramowanie pośredniczące, które otaczają wykonywanie wszystkich elementów, które są dostępne później w potoku. Jednak filtry różnią się od oprogramowania pośredniczącego, ponieważ są one częścią środowiska uruchomieniowego, co oznacza, że mają dostęp do kontekstu i konstrukcji.

Aby użyć oprogramowania pośredniczącego jako filtru, utwórz typ z metodą określającą Configure oprogramowanie pośredniczące do wstrzykiwania do potoku filtru. W poniższym przykładzie użyto oprogramowania pośredniczącego do ustawienia nagłówka odpowiedzi:

public class FilterMiddlewarePipeline
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            context.Response.Headers.Add("Pipeline", "Middleware");

            await next();
        });
    }
}

Użyj polecenia , MiddlewareFilterAttribute aby uruchomić oprogramowanie pośredniczące:

[MiddlewareFilter<FilterMiddlewarePipeline>]
public class FilterMiddlewareController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}

Filtry oprogramowania pośredniczącego są uruchamiane na tym samym etapie potoku filtru co filtry zasobów, przed powiązaniem modelu i po pozostałej części potoku.

Bezpieczeństwo wątkowe

Podczas przekazywania wystąpienia filtru do Addelementu , a nie do Typeelementu , filtr jest pojedynczym elementem i nie jest bezpieczny wątkowo.

Dodatkowe zasoby

Przez Kirk Larkin, Rick Anderson, Tom Dykstra i Steve Smith

Filtry w ASP.NET Core umożliwiają uruchamianie kodu przed lub po określonych etapach w potoku przetwarzania żądań.

Wbudowane filtry obsługują zadania, takie jak:

  • Autoryzacja, uniemożliwiająca dostęp do zasobów, dla których użytkownik nie jest autoryzowany.
  • Buforowanie odpowiedzi, zwarcie potoku żądania w celu zwrócenia buforowanej odpowiedzi.

Filtry niestandardowe można tworzyć w celu obsługi problemów związanych z wycinaniami krzyżowymi. Przykłady problemów obejmujących krzyżowe obejmują obsługę błędów, buforowanie, konfigurację, autoryzację i rejestrowanie. Filtry unikają duplikowania kodu. Na przykład filtr wyjątków obsługi błędów może skonsolidować obsługę błędów.

Ten dokument dotyczy Razor stron, kontrolerów interfejsu API i kontrolerów z widokami. Filtry nie działają bezpośrednio ze składnikamiRazor. Filtr może mieć wpływ tylko pośrednio na składnik, gdy:

  • Składnik jest osadzony na stronie lub w widoku.
  • Strona lub kontroler i widok używają filtru.

Jak działają filtry

Filtry są uruchamiane w potoku wywołania akcji ASP.NET Core, czasami nazywane potokiem filtru. Potok filtru jest uruchamiany po wybraniu akcji do wykonania przez ASP.NET Core:

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

Typy filtrów

Każdy typ filtru jest wykonywany na innym etapie w potoku filtru:

  • Filtry autoryzacji:

    • Uruchom najpierw polecenie .
    • Ustal, czy użytkownik ma autoryzację do żądania.
    • Zwarcie potoku, jeśli żądanie nie jest autoryzowane.
  • Filtry zasobów:

    • Uruchom polecenie po autoryzacji.
    • OnResourceExecuting uruchamia kod przed resztą potoku filtru. Na przykład OnResourceExecuting uruchamia kod przed powiązaniem modelu.
    • OnResourceExecuted uruchamia kod po zakończeniu pozostałej części potoku.
  • Filtry akcji:

    • Uruchom bezpośrednio przed wywołaną metodą akcji i po jej wywołaniu.
    • Może zmienić argumenty przekazane do akcji.
    • Może zmienić wynik zwrócony z akcji.
    • Nieobsługiwane na Razor stronach.
  • Filtry punktów końcowych:

    • Uruchom bezpośrednio przed wywołaną metodą akcji i po jej wywołaniu.
    • Może zmienić argumenty przekazane do akcji.
    • Może zmienić wynik zwrócony z akcji.
    • Nieobsługiwane na Razor stronach.
    • Można wywołać zarówno na akcjach, jak i na punktach końcowych opartych na programie obsługi tras.
  • Filtry wyjątków stosują zasady globalne do nieobsługiwanych wyjątków występujących przed zapisaniem treści odpowiedzi.

  • Filtry wyników:

    • Uruchom polecenie bezpośrednio przed i po wykonaniu wyników akcji.
    • Uruchom polecenie tylko wtedy, gdy metoda akcji zostanie wykonana pomyślnie.
    • Są przydatne w przypadku logiki, która musi otaczać wykonywanie widoku lub formatera.

Na poniższym diagramie przedstawiono sposób interakcji typów filtrów w potoku filtru:

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

Razor Strony obsługują Razor również filtry strony, które są uruchamiane przed i po procedurze Razor obsługi strony.

Implementacja

Filtry obsługują zarówno implementacje synchroniczne, jak i asynchroniczne za pomocą różnych definicji interfejsu.

Filtry synchroniczne są uruchamiane przed i po etapie potoku. Na przykład OnActionExecuting jest wywoływana przed wywołaniem metody akcji. OnActionExecuted jest wywoływana po zwracaniu metody akcji:

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

Filtry asynchroniczne definiują metodę On-Stage-ExecutionAsync . Na przykład : OnActionExecutionAsync

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // Do something before the action executes.
        await next();
        // Do something after the action executes.
    }
}

W poprzednim kodzie element SampleAsyncActionFilter ma ActionExecutionDelegatewartość , nextktóra wykonuje metodę akcji .

Wiele etapów filtrowania

Interfejsy dla wielu etapów filtrowania można zaimplementować w jednej klasie. Na przykład ActionFilterAttribute klasa implementuje:

Zaimplementuj synchroniczną lub asynchroniczną wersję interfejsu filtru, a nie obie. Środowisko uruchomieniowe najpierw sprawdza, czy filtr implementuje interfejs asynchroniczny, a jeśli tak, wywołuje to. Jeśli nie, wywołuje metody interfejsu synchronicznego. Jeśli oba interfejsy asynchroniczne i synchroniczne są implementowane w jednej klasie, wywoływana jest tylko metoda asynchroniczna. W przypadku używania klas abstrakcyjnych, takich jak ActionFilterAttribute, przesłaniaj tylko metody synchroniczne lub metody asynchroniczne dla każdego typu filtru.

Wbudowane atrybuty filtru

ASP.NET Core zawiera wbudowane filtry oparte na atrybutach, które mogą być podklasowane i dostosowane. Na przykład następujący filtr wyników dodaje nagłówek do odpowiedzi:

public class ResponseHeaderAttribute : ActionFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public ResponseHeaderAttribute(string name, string value) =>
        (_name, _value) = (name, value);

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_name, _value);

        base.OnResultExecuting(context);
    }
}

Atrybuty umożliwiają filtrowanie akceptowania argumentów, jak pokazano w poprzednim przykładzie. Zastosuj element ResponseHeaderAttribute do kontrolera lub metody akcji i określ nazwę i wartość nagłówka HTTP:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

Użyj narzędzia, takiego jak narzędzia deweloperskie przeglądarki, aby zbadać nagłówki. W obszarze Nagłówkifilter-header: Filter Value odpowiedzi zostanie wyświetlona wartość .

Następujący kod dotyczy ResponseHeaderAttribute zarówno kontrolera, jak i akcji:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

    [ResponseHeader("Another-Filter-Header", "Another Filter Value")]
    public IActionResult Multiple() =>
        Content("Examine the response headers using the F12 developer tools.");
}

Odpowiedzi z Multiple akcji obejmują następujące nagłówki:

  • filter-header: Filter Value
  • another-filter-header: Another Filter Value

Kilka interfejsów filtru ma odpowiednie atrybuty, których można używać jako klas bazowych na potrzeby implementacji niestandardowych.

Atrybuty filtru:

Nie można zastosować filtrów do Razor metod obsługi strony. Można je zastosować do Razor modelu strony lub globalnie.

Filtrowanie zakresów i kolejności wykonywania

Filtr można dodać do potoku w jednym z trzech zakresów:

  • Używanie atrybutu na kontrolerze lub Razor stronie.
  • Używanie atrybutu w akcji kontrolera. Nie można zastosować atrybutów filtru do Razor metod obsługi stron.
  • Globalnie dla wszystkich kontrolerów, akcji i Razor stron, jak pokazano w poniższym kodzie:
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalSampleActionFilter>();
    });
    

Domyślna kolejność wykonywania

Jeśli istnieje wiele filtrów dla określonego etapu potoku, zakres określa domyślną kolejność wykonywania filtru. Filtry globalne otaczają filtry klas, które z kolei otaczają filtry metod.

W wyniku zagnieżdżania filtrów kod po filtrach jest uruchamiany w odwrotnej kolejności przed kodem. Sekwencja filtru:

  • Przed kodem filtrów globalnych.
    • Przed kodem filtrów kontrolera.
      • Przed kodem metody akcji filtruje.
      • Po kodzie metody akcji filtruje.
    • Po kodzie kontrolera filtrów.
  • Po kodzie filtrów globalnych.

Poniższy przykład ilustruje kolejność uruchamiania metod filtrowania dla filtrów akcji synchronicznych:

Sequence Zakres filtru Metoda filter
1 Globalnie OnActionExecuting
2 Kontroler OnActionExecuting
3 Akcja OnActionExecuting
4 Akcja OnActionExecuted
5 Kontroler OnActionExecuted
6 Globalnie OnActionExecuted

Filtry na poziomie kontrolera

Każdy kontroler dziedziczony z Controller zawiera OnActionExecutingmetody , OnActionExecutionAsynci OnActionExecuted . Te metody opakowuje filtry uruchamiane dla danej akcji:

  • OnActionExecuting uruchamia się przed dowolnym filtrem akcji.
  • OnActionExecuted uruchamia się po wszystkich filtrach akcji.
  • OnActionExecutionAsync uruchamia się przed dowolnym filtrem akcji. Kod po wywołaniu do next uruchomienia po filtrach akcji.

Następująca ControllerFiltersController klasa:

  • Stosuje element SampleActionFilterAttribute ([SampleActionFilter]) do kontrolera.
  • Przesłonięcia OnActionExecuting i OnActionExecuted.
[SampleActionFilter]
public class ControllerFiltersController : Controller
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuting)}");

        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuted)}");

        base.OnActionExecuted(context);
    }

    public IActionResult Index()
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(Index)}");

        return Content("Check the Console.");
    }
}

Przechodzenie do https://localhost:<port>/ControllerFilters uruchomi następującego kodu:

  • ControllerFiltersController.OnActionExecuting
    • GlobalSampleActionFilter.OnActionExecuting
      • SampleActionFilterAttribute.OnActionExecuting
        • ControllerFiltersController.Index
      • SampleActionFilterAttribute.OnActionExecuted
    • GlobalSampleActionFilter.OnActionExecuted
  • ControllerFiltersController.OnActionExecuted

Filtry na poziomie kontrolera ustawiają właściwość Order na int.MinValue. Filtry na poziomie kontrolera nie można ustawić tak, aby były uruchamiane po zastosowaniu filtrów do metod. Kolejność jest wyjaśniona w następnej sekcji.

Aby uzyskać informacje na temat Razor stron, zobacz Implementowanie Razor filtrów stron przez zastępowanie metod filtrowania.

Zastąpij kolejność domyślną

Domyślną sekwencję wykonywania można zastąpić przez zaimplementowanie metody IOrderedFilter. IOrderedFilter Uwidacznia Order właściwość, która ma pierwszeństwo przed zakresem, aby określić kolejność wykonywania. Filtr o niższej Order wartości:

  • Uruchamia przed kodem przed filtrem o wyższej wartości Order.
  • Uruchamia po kodzie po filtrze z wyższą Order wartością.

W przykładzie filtry na poziomie kontrolera ma zakres globalny, GlobalSampleActionFilter więc działa przed SampleActionFilterAttribute, który ma zakres kontrolera. Aby najpierw wykonać SampleActionFilterAttribute przebieg, ustaw jego kolejność na int.MinValue:

[SampleActionFilter(Order = int.MinValue)]
public class ControllerFiltersController : Controller
{
    // ...
}

Aby najpierw ustawić przebieg filtru GlobalSampleActionFilter globalnego, ustaw wartość Orderint.MinValue:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<GlobalSampleActionFilter>(int.MinValue);
});

Anulowanie i zwarcie

Potok filtru można zwarcić, ustawiając Result właściwość parametru ResourceExecutingContext podanego w metodzie filtru. Na przykład następujący filtr zasobu uniemożliwia wykonywanie pozostałej części potoku:

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult
        {
            Content = nameof(ShortCircuitingResourceFilterAttribute)
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context) { }
}

W poniższym kodzie, zarówno filtr, jak [ShortCircuitingResourceFilter] i docelowy [ResponseHeader]Index filtr metody akcji. Filtr ShortCircuitingResourceFilterAttribute :

  • Uruchamia najpierw, ponieważ jest to filtr zasobów i ResponseHeaderAttribute jest filtrem akcji.
  • Zwarcie pozostałej części potoku.

W związku z tym ResponseHeaderAttribute filtr nigdy nie jest uruchamiany dla Index akcji. To zachowanie byłoby takie samo, jeśli oba filtry zostały zastosowane na poziomie metody akcji, pod warunkiem, że uruchomiono ShortCircuitingResourceFilterAttribute najpierw. Przebiegi ShortCircuitingResourceFilterAttribute najpierw ze względu na jego typ filtru:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ShortCircuitingController : Controller
{
    [ShortCircuitingResourceFilter]
    public IActionResult Index() =>
        Content($"- {nameof(ShortCircuitingController)}.{nameof(Index)}");
}

Wstrzykiwanie zależności

Filtry można dodawać według typu lub wystąpienia. Jeśli wystąpienie zostanie dodane, to wystąpienie jest używane dla każdego żądania. Jeśli typ zostanie dodany, zostanie aktywowany typ. Filtr aktywowany przez typ oznacza:

  • Wystąpienie jest tworzone dla każdego żądania.
  • Wszystkie zależności konstruktora są wypełniane przez wstrzykiwanie zależności (DI).

Filtry implementowane jako atrybuty i dodawane bezpośrednio do klas kontrolerów lub metod akcji nie mogą mieć zależności konstruktora zapewniane przez wstrzykiwanie zależności (DI). Zależności konstruktora nie mogą być udostępniane przez di, ponieważ atrybuty muszą mieć parametry konstruktora podane tam, gdzie są stosowane.

Następujące filtry obsługują zależności konstruktora dostarczone z di:

Powyższe filtry można zastosować do kontrolera lub akcji.

Rejestratory są dostępne w witrynie DI. Należy jednak unikać tworzenia i używania filtrów wyłącznie do celów rejestrowania. Wbudowane rejestrowanie struktury zwykle zapewnia, co jest potrzebne do rejestrowania. Rejestrowanie dodane do filtrów:

  • Należy skoncentrować się na problemach dotyczących domeny biznesowej lub zachowaniu specyficznym dla filtru.
  • Nie należy rejestrować akcji ani innych zdarzeń struktury. Wbudowane filtry rejestrują już akcje i zdarzenia struktury.

ServiceFilterAttribute

Typy implementacji filtru usługi są rejestrowane w programie Program.cs. Element ServiceFilterAttribute pobiera wystąpienie filtru z di.

Poniższy kod przedstawia klasę LoggingResponseHeaderFilterService , która używa di:

public class LoggingResponseHeaderFilterService : IResultFilter
{
    private readonly ILogger _logger;

    public LoggingResponseHeaderFilterService(
            ILogger<LoggingResponseHeaderFilterService> logger) =>
        _logger = logger;

    public void OnResultExecuting(ResultExecutingContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuting)}");

        context.HttpContext.Response.Headers.Add(
            nameof(OnResultExecuting), nameof(LoggingResponseHeaderFilterService));
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuted)}");
    }
}

W poniższym kodzie LoggingResponseHeaderFilterService zostanie dodany do kontenera DI:

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

W poniższym kodzie ServiceFilter atrybut pobiera wystąpienie filtru LoggingResponseHeaderFilterService z di:

[ServiceFilter(typeof(LoggingResponseHeaderFilterService))]
public IActionResult WithServiceFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}");

W przypadku używania polecenia ServiceFilterAttribute, ustawienie ServiceFilterAttribute.IsReusable:

  • Zawiera wskazówkę, że wystąpienie filtru może być ponownie używane poza zakresem żądania, w ramach którego został utworzony. Środowisko uruchomieniowe ASP.NET Core nie gwarantuje:
    • Zostanie utworzone pojedyncze wystąpienie filtru.
    • Filtr nie zostanie ponownie zażądany z kontenera DI w późniejszym czasie.
  • Nie należy używać z filtrem, który zależy od usług z okresem istnienia innym niż pojedynczy.

ServiceFilterAttribute implementuje IFilterFactory. IFilterFactory Uwidacznia metodę CreateInstanceIFilterMetadata tworzenia wystąpienia. CreateInstance ładuje określony typ z DI.

TypeFilterAttribute

TypeFilterAttribute jest podobny do ServiceFilterAttribute, ale jego typ nie jest rozpoznawany bezpośrednio z kontenera DI. Tworzy wystąpienie typu przy użyciu polecenia Microsoft.Extensions.DependencyInjection.ObjectFactory.

Ponieważ TypeFilterAttribute typy nie są rozpoznawane bezpośrednio z kontenera DI:

  • Typy, do których odwołuje się odwołanie przy użyciu TypeFilterAttribute kontenera di, nie muszą być zarejestrowane. Mają one swoje zależności spełnione przez kontener DI.
  • TypeFilterAttribute opcjonalnie może akceptować argumenty konstruktora dla typu.

W przypadku używania polecenia TypeFilterAttribute, ustawienie TypeFilterAttribute.IsReusable:

  • Zawiera wskazówkę, że wystąpienie filtru może być ponownie używane poza zakresem żądania, w ramach którego został utworzony. Środowisko uruchomieniowe ASP.NET Core nie gwarantuje utworzenia pojedynczego wystąpienia filtru.

  • Nie należy używać z filtrem, który zależy od usług z okresem istnienia innym niż pojedynczy.

W poniższym przykładzie pokazano, jak przekazać argumenty do typu przy użyciu polecenia TypeFilterAttribute:

[TypeFilter(typeof(LoggingResponseHeaderFilter),
    Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithTypeFilter)}");

Filtry autoryzacji

Filtry autoryzacji:

  • Czy pierwsze filtry są uruchamiane w potoku filtru.
  • Kontrolowanie dostępu do metod akcji.
  • Przed metodą, ale nie po metodzie.

Niestandardowe filtry autoryzacji wymagają niestandardowej struktury autoryzacji. Preferuj konfigurowanie zasad autoryzacji lub pisanie niestandardowych zasad autoryzacji w celu zapisania filtru niestandardowego. Wbudowany filtr autoryzacji:

  • Wywołuje system autoryzacji.
  • Nie autoryzuje żądań.

Nie zgłaszaj wyjątków w filtrach autoryzacji:

  • Wyjątek nie zostanie obsłużony.
  • Filtry wyjątków nie będą obsługiwać wyjątku.

Rozważ wystawienie wyzwania w przypadku wystąpienia wyjątku w filtrze autoryzacji.

Dowiedz się więcej o autoryzacji.

Filtry zasobów

Filtry zasobów:

Filtry zasobów są przydatne w przypadku zwarć większość potoku. Na przykład filtr buforowania może uniknąć reszty potoku po trafieniu pamięci podręcznej.

Przykłady filtrów zasobów:

Filtry akcji

Filtry akcji nie mają zastosowania do Razor stron. Razor Obsługa stron i IPageFilterIAsyncPageFilter. Aby uzyskać więcej informacji, zobacz Metody filtrowania dla Razor stron.

Filtry akcji:

Poniższy kod przedstawia przykładowy filtr akcji:

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

Element ActionExecutingContext zawiera następujące właściwości:

  • ActionArguments — umożliwia odczytywanie danych wejściowych do metody akcji.
  • Controller — umożliwia manipulowanie wystąpieniem kontrolera.
  • Result - ustawianie Result zwarć wykonywania metody akcji i kolejnych filtrów akcji.

Zgłaszanie wyjątku w metodzie akcji:

  • Zapobiega uruchamianiu kolejnych filtrów.
  • W przeciwieństwie do ustawienia Result, jest traktowany jako błąd zamiast pomyślnego wyniku.

Controller Zapewnia ActionExecutedContext i Result oraz następujące właściwości:

  • Canceled — prawda, jeśli wykonanie akcji zostało zwarcie przez inny filtr.
  • Exception — Wartość inna niż null, jeśli akcja lub wcześniej uruchomiony filtr akcji zwrócił wyjątek. Ustawienie tej właściwości na null:
    • Skutecznie obsługuje wyjątek.
    • Result jest wykonywany tak, jakby został zwrócony z metody akcji.

W przypadku metody IAsyncActionFilterwywołanie metody ActionExecutionDelegate:

  • Wykonuje wszystkie kolejne filtry akcji i metodę akcji.
  • Zwraca wartość ActionExecutedContext.

Aby zwarć, przypisz Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result do wystąpienia wyników i nie wywołuje next metody ().ActionExecutionDelegate

Struktura zapewnia abstrakcję ActionFilterAttribute , która może być podklasowana.

Filtr OnActionExecuting akcji może służyć do:

  • Weryfikowanie stanu modelu.
  • Zwróć błąd, jeśli stan jest nieprawidłowy.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Uwaga

Kontrolery z adnotacjami z atrybutem [ApiController] automatycznie weryfikują stan modelu i zwracają odpowiedź 400. Aby uzyskać więcej informacji, zobacz Automatyczne odpowiedzi HTTP 400.

Metoda OnActionExecuted jest uruchamiana po metodzie akcji:

  • I może wyświetlać i manipulować wynikami akcji za pośrednictwem Result właściwości .
  • Canceled parametr ma wartość true, jeśli wykonanie akcji zostało zwarcie przez inny filtr.
  • Exception parametr jest ustawiony na wartość inną niż null, jeśli akcja lub kolejny filtr akcji zwrócił wyjątek. Ustawienie Exception wartości null:
    • Skutecznie obsługuje wyjątek.
    • ActionExecutedContext.Result jest wykonywany tak, jakby został zwrócony normalnie z metody akcji.

Filtry wyjątków

Filtry wyjątków:

Poniższy przykładowy filtr wyjątku wyświetla szczegółowe informacje o wyjątkach występujących podczas opracowywania aplikacji:

public class SampleExceptionFilter : IExceptionFilter
{
    private readonly IHostEnvironment _hostEnvironment;

    public SampleExceptionFilter(IHostEnvironment hostEnvironment) =>
        _hostEnvironment = hostEnvironment;

    public void OnException(ExceptionContext context)
    {
        if (!_hostEnvironment.IsDevelopment())
        {
            // Don't display exception details unless running in Development.
            return;
        }

        context.Result = new ContentResult
        {
            Content = context.Exception.ToString()
        };
    }
}

Poniższy kod testuje filtr wyjątku:

[TypeFilter(typeof(SampleExceptionFilter))]
public class ExceptionController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(ExceptionController)}.{nameof(Index)}");
}

Filtry wyjątków:

  • Nie mają wcześniej i po zdarzeniach.
  • Zaimplementuj OnException lub OnExceptionAsync.
  • Obsługa nieobsługiwanych wyjątków występujących w Razor procesie tworzenia strony lub kontrolera, powiązania modelu, filtrów akcji lub metod akcji.
  • Nie przechwytuj wyjątków występujących w filtrach zasobów, filtrach wyników lub wykonywaniu wyników MVC.

Aby obsłużyć wyjątek, ustaw ExceptionHandled właściwość na true lub przypisz Result właściwość . Spowoduje to zatrzymanie propagacji wyjątku. Filtr wyjątku nie może przekształcić wyjątku w "powodzenie". Tylko filtr akcji może to zrobić.

Filtry wyjątków:

  • Są dobre w przypadku podlewek wyjątków występujących w ramach akcji.
  • Nie są tak elastyczne, jak oprogramowanie pośredniczące obsługujące błędy.

Preferuj oprogramowanie pośredniczące do obsługi wyjątków. Używaj filtrów wyjątków tylko wtedy, gdy obsługa błędów różni się w zależności od wywoływanej metody akcji. Na przykład aplikacja może mieć metody akcji zarówno dla punktów końcowych interfejsu API, jak i dla widoków/HTML. Punkty końcowe interfejsu API mogą zwracać informacje o błędach jako JSWŁĄCZONE, podczas gdy akcje oparte na widoku mogą zwracać stronę błędu jako kod HTML.

Filtry wyników

Filtry wyników:

IResultFilter i IAsyncResultFilter

Poniższy kod przedstawia przykładowy filtr wyników:

public class SampleResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        // Do something before the result executes.
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Do something after the result executes.
    }
}

Rodzaj wykonywanego wyniku zależy od akcji. Akcja zwracająca widok obejmuje wszystkie przetwarzanie razor w ramach wykonywanego ViewResult elementu. Metoda interfejsu API może wykonać serializacji w ramach wykonywania wyniku. Dowiedz się więcej o wynikach akcji.

Filtry wyników są wykonywane tylko wtedy, gdy akcja lub filtr akcji generuje wynik akcji. Filtry wyników nie są wykonywane, gdy:

  • Filtr autoryzacji lub filtr zasobów zwarcie potoku.
  • Filtr wyjątku obsługuje wyjątek, generując wynik akcji.

Metoda Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting może zwarć wykonywanie wyniku akcji i kolejnych filtrów wyników, ustawiając wartość Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Canceltrue. Zapisz w obiekcie odpowiedzi podczas zwarć, aby uniknąć generowania pustej odpowiedzi. Zgłaszanie wyjątku w pliku IResultFilter.OnResultExecuting:

  • Zapobiega wykonywaniu wyniku akcji i kolejnych filtrów.
  • Jest traktowany jako błąd zamiast pomyślnego wyniku.

Po uruchomieniu Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted metody odpowiedź prawdopodobnie została już wysłana do klienta. Jeśli odpowiedź została już wysłana do klienta, nie można jej zmienić.

ResultExecutedContext.Canceled parametr jest ustawiony na true wartość , jeśli wykonanie wyniku akcji zostało zwarcie przez inny filtr.

ResultExecutedContext.Exception parametr jest ustawiony na wartość inną niż null, jeśli wynik akcji lub kolejny filtr wyników zwrócił wyjątek. Ustawienie Exception wartości null skutecznie obsługuje wyjątek i uniemożliwia ponowne zgłoszenie wyjątku w dalszej części potoku. Nie ma niezawodnego sposobu zapisywania danych w odpowiedzi podczas obsługi wyjątku w filtrze wyników. Jeśli nagłówki zostały opróżnione do klienta, gdy wynik akcji zgłasza wyjątek, nie ma niezawodnego mechanizmu wysyłania kodu błędu.

W przypadku elementu IAsyncResultFilterwywołanie metody await next na obiekcie ResultExecutionDelegate wykonuje wszystkie kolejne filtry wyników i wynik akcji. Aby zwarć, ustaw wartość ResultExecutingContext.Canceltrue i nie wywołaj metody ResultExecutionDelegate:

public class SampleAsyncResultFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(
        ResultExecutingContext context, ResultExecutionDelegate next)
    {
        if (context.Result is not EmptyResult)
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }
    }
}

Struktura zapewnia abstrakcję ResultFilterAttribute , która może być podklasowana. Pokazana wcześniej klasa ResponseHeaderAttribute jest przykładem atrybutu filtru wyników.

IAlwaysRunResultFilter i IAsyncAlwaysRunResultFilter

Interfejsy IAlwaysRunResultFilter i IAsyncAlwaysRunResultFilter deklarują implementację uruchamianą IResultFilter dla wszystkich wyników akcji. Obejmuje to wyniki akcji wygenerowane przez:

  • Filtry autoryzacji i filtry zasobów, które są zwarcie.
  • Filtry wyjątków.

Na przykład następujący filtr zawsze jest uruchamiany i ustawia wynik akcji (ObjectResult) z kodem stanu jednostki nieprzetworzonej 422, gdy negocjowanie zawartości kończy się niepowodzeniem:

public class UnprocessableResultFilter : IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult
            && statusCodeResult.StatusCode == StatusCodes.Status415UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Unprocessable")
            {
                StatusCode = StatusCodes.Status422UnprocessableEntity
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context) { }
}

IFilterFactory

IFilterFactory implementuje IFilterMetadata. IFilterFactory W związku z tym wystąpienie może być używane jako IFilterMetadata wystąpienie w dowolnym miejscu w potoku filtru. Gdy środowisko uruchomieniowe przygotowuje się do wywołania filtru, próbuje rzutować go do elementu IFilterFactory. Jeśli rzutowanie powiedzie się, metoda jest wywoływana w CreateInstance celu utworzenia wywoływanego IFilterMetadata wystąpienia. Zapewnia to elastyczny projekt, ponieważ dokładny potok filtru nie musi być jawnie ustawiany podczas uruchamiania aplikacji.

IFilterFactory.IsReusable:

  • Jest wskazówką fabryki, że wystąpienie filtru utworzone przez fabrykę może być ponownie używane poza zakresem żądania, w ramach którego został utworzony.
  • Nie należy używać z filtrem, który zależy od usług z okresem istnienia innym niż pojedynczy.

Środowisko uruchomieniowe ASP.NET Core nie gwarantuje:

  • Zostanie utworzone pojedyncze wystąpienie filtru.
  • Filtr nie zostanie ponownie zażądany z kontenera DI w późniejszym czasie.

Ostrzeżenie

Skonfiguruj wartość tak, aby zwracać IFilterFactory.IsReusabletrue tylko wtedy, gdy źródło filtrów jest jednoznaczne, filtry są bezstanowe, a filtry są bezpieczne do użycia w wielu żądaniach HTTP. Na przykład nie zwracaj filtrów z di, które są zarejestrowane jako zakres lub przejściowe, jeśli IFilterFactory.IsReusable zwraca wartość true.

IFilterFactory można zaimplementować przy użyciu implementacji atrybutów niestandardowych jako innego podejścia do tworzenia filtrów:

public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
        new InternalResponseHeaderFilter();

    private class InternalResponseHeaderFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context) =>
            context.HttpContext.Response.Headers.Add(
                nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));

        public void OnActionExecuted(ActionExecutedContext context) { }
    }

Filtr jest stosowany w następującym kodzie:

[ResponseHeaderFilterFactory]
public IActionResult Index() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(Index)}");

IFilterFactory zaimplementowany w atrybucie

Filtry implementujące IFilterFactory są przydatne w przypadku filtrów, które:

  • Nie wymagaj przekazywania parametrów.
  • Mieć zależności konstruktora, które muszą być wypełnione przez di.

TypeFilterAttribute implementuje IFilterFactory. IFilterFactory Uwidacznia metodę CreateInstanceIFilterMetadata tworzenia wystąpienia. CreateInstance ładuje określony typ z kontenera usług (DI).

public class SampleActionTypeFilterAttribute : TypeFilterAttribute
{
    public SampleActionTypeFilterAttribute()
         : base(typeof(InternalSampleActionFilter)) { }

    private class InternalSampleActionFilter : IActionFilter
    {
        private readonly ILogger<InternalSampleActionFilter> _logger;

        public InternalSampleActionFilter(ILogger<InternalSampleActionFilter> logger) =>
            _logger = logger;

        public void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuting)}");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuted)}");
        }
    }
}

Poniższy kod przedstawia trzy podejścia do zastosowania filtru:

[SampleActionTypeFilter]
public IActionResult WithDirectAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithDirectAttribute)}");

[TypeFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithTypeFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithTypeFilterAttribute)}");

[ServiceFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithServiceFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithServiceFilterAttribute)}");

W poprzednim kodzie preferowane jest pierwsze podejście do zastosowania filtru.

Używanie oprogramowania pośredniczącego w potoku filtru

Filtry zasobów działają jak oprogramowanie pośredniczące, które otaczają wykonywanie wszystkich elementów, które są dostępne później w potoku. Jednak filtry różnią się od oprogramowania pośredniczącego, ponieważ są one częścią środowiska uruchomieniowego, co oznacza, że mają dostęp do kontekstu i konstrukcji.

Aby użyć oprogramowania pośredniczącego jako filtru, utwórz typ z metodą określającą Configure oprogramowanie pośredniczące do wstrzykiwania do potoku filtru. W poniższym przykładzie użyto oprogramowania pośredniczącego do ustawienia nagłówka odpowiedzi:

public class FilterMiddlewarePipeline
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            context.Response.Headers.Add("Pipeline", "Middleware");

            await next();
        });
    }
}

Użyj polecenia , MiddlewareFilterAttribute aby uruchomić oprogramowanie pośredniczące:

[MiddlewareFilter(typeof(FilterMiddlewarePipeline))]
public class FilterMiddlewareController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}

Filtry oprogramowania pośredniczącego są uruchamiane na tym samym etapie potoku filtru co filtry zasobów, przed powiązaniem modelu i po pozostałej części potoku.

Bezpieczeństwo wątkowe

Podczas przekazywania wystąpienia filtru do Addelementu , a nie do Typeelementu , filtr jest pojedynczym elementem i nie jest bezpieczny wątkowo.

Dodatkowe zasoby

Przez Kirk Larkin, Rick Anderson, Tom Dykstra i Steve Smith

Filtry w ASP.NET Core umożliwiają uruchamianie kodu przed lub po określonych etapach w potoku przetwarzania żądań.

Wbudowane filtry obsługują zadania, takie jak:

  • Autoryzacja, uniemożliwiająca dostęp do zasobów, dla których użytkownik nie jest autoryzowany.
  • Buforowanie odpowiedzi, zwarcie potoku żądania w celu zwrócenia buforowanej odpowiedzi.

Filtry niestandardowe można tworzyć w celu obsługi problemów związanych z wycinaniami krzyżowymi. Przykłady problemów obejmujących krzyżowe obejmują obsługę błędów, buforowanie, konfigurację, autoryzację i rejestrowanie. Filtry unikają duplikowania kodu. Na przykład filtr wyjątków obsługi błędów może skonsolidować obsługę błędów.

Ten dokument dotyczy Razor stron, kontrolerów interfejsu API i kontrolerów z widokami. Filtry nie działają bezpośrednio ze składnikamiRazor. Filtr może mieć wpływ tylko pośrednio na składnik, gdy:

  • Składnik jest osadzony na stronie lub w widoku.
  • Strona lub kontroler i widok używają filtru.

Wyświetl lub pobierz przykład (jak pobrać).

Jak działają filtry

Filtry są uruchamiane w potoku wywołania akcji ASP.NET Core, czasami nazywane potokiem filtru. Potok filtru jest uruchamiany po wybraniu akcji do wykonania przez ASP.NET Core.

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

Typy filtrów

Każdy typ filtru jest wykonywany na innym etapie w potoku filtru:

  • Filtry autoryzacji są uruchamiane jako pierwsze i służą do określania, czy użytkownik jest autoryzowany do żądania. Autoryzacja filtruje zwarcie potoku, jeśli żądanie nie jest autoryzowane.

  • Filtry zasobów:

    • Uruchom polecenie po autoryzacji.
    • OnResourceExecuting uruchamia kod przed resztą potoku filtru. Na przykład OnResourceExecuting uruchamia kod przed powiązaniem modelu.
    • OnResourceExecuted uruchamia kod po zakończeniu pozostałej części potoku.
  • Filtry akcji:

    • Uruchom kod bezpośrednio przed wywołaną metodą akcji i po jej wywołaniu.
    • Może zmienić argumenty przekazane do akcji.
    • Może zmienić wynik zwrócony z akcji.
    • Nieobsługiwane na Razor stronach.
  • Filtry wyjątków stosują zasady globalne do nieobsługiwanych wyjątków występujących przed zapisaniem treści odpowiedzi.

  • Filtry wyników uruchamiają kod bezpośrednio przed i po wykonaniu wyników akcji. Są one uruchamiane tylko wtedy, gdy metoda akcji została wykonana pomyślnie. Są one przydatne w przypadku logiki, która musi otaczać wykonywanie widoku lub formatera.

Na poniższym diagramie przedstawiono sposób interakcji typów filtrów w potoku filtru.

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

Implementacja

Filtry obsługują zarówno implementacje synchroniczne, jak i asynchroniczne za pomocą różnych definicji interfejsu.

Filtry synchroniczne uruchamiają kod przed i po etapie potoku. Na przykład OnActionExecuting jest wywoływana przed wywołaniem metody akcji. OnActionExecuted jest wywoływana po zwracaniu metody akcji.

public class MySampleActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }
}

W poprzednim kodzie MyDebug jest funkcją narzędzia w przykładowym pobieraniu.

Filtry asynchroniczne definiują metodę On-Stage-ExecutionAsync . Na przykład : OnActionExecutionAsync

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context,
        ActionExecutionDelegate next)
    {
        // Do something before the action executes.

        // next() calls the action method.
        var resultContext = await next();
        // resultContext.Result is set.
        // Do something after the action executes.
    }
}

W poprzednim kodzie SampleAsyncActionFilter element ma ActionExecutionDelegate wartość (next), która wykonuje metodę akcji.

Wiele etapów filtrowania

Interfejsy dla wielu etapów filtrowania można zaimplementować w jednej klasie. Na przykład ActionFilterAttribute klasa implementuje:

Zaimplementuj synchroniczną lub asynchroniczną wersję interfejsu filtru, a nie obie. Środowisko uruchomieniowe najpierw sprawdza, czy filtr implementuje interfejs asynchroniczny, a jeśli tak, wywołuje to. Jeśli nie, wywołuje metody interfejsu synchronicznego. Jeśli oba interfejsy asynchroniczne i synchroniczne są implementowane w jednej klasie, wywoływana jest tylko metoda asynchroniczna. W przypadku używania klas abstrakcyjnych, takich jak ActionFilterAttribute, przesłaniaj tylko metody synchroniczne lub metody asynchroniczne dla każdego typu filtru.

Wbudowane atrybuty filtru

ASP.NET Core zawiera wbudowane filtry oparte na atrybutach, które mogą być podklasowane i dostosowane. Na przykład następujący filtr wyników dodaje nagłówek do odpowiedzi:

public class AddHeaderAttribute : ResultFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public AddHeaderAttribute(string name, string value)
    {
        _name = name;
        _value = value;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add( _name, new string[] { _value });
        base.OnResultExecuting(context);
    }
}

Atrybuty umożliwiają filtrowanie akceptowania argumentów, jak pokazano w poprzednim przykładzie. Zastosuj element AddHeaderAttribute do kontrolera lub metody akcji i określ nazwę i wartość nagłówka HTTP:

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

Użyj narzędzia, takiego jak narzędzia deweloperskie przeglądarki, aby zbadać nagłówki. W obszarze Nagłówkiauthor: Rick Anderson odpowiedzi zostanie wyświetlona wartość .

Poniższy kod implementuje ten ActionFilterAttribute kod:

  • Odczytuje tytuł i nazwę z systemu konfiguracji. W przeciwieństwie do poprzedniego przykładu poniższy kod nie wymaga dodania parametrów filtru do kodu.
  • Dodaje tytuł i nazwę do nagłówka odpowiedzi.
public class MyActionFilterAttribute : ActionFilterAttribute
{
    private readonly PositionOptions _settings;

    public MyActionFilterAttribute(IOptions<PositionOptions> options)
    {
        _settings = options.Value;
        Order = 1;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_settings.Title, 
                                                 new string[] { _settings.Name });
        base.OnResultExecuting(context);
    }
}

Opcje konfiguracji są udostępniane z systemu konfiguracji przy użyciu wzorca opcji. Na przykład z appsettings.json pliku:

{
  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

W pliku StartUp.ConfigureServices:

  • Klasa PositionOptions jest dodawana do kontenera usługi z obszarem "Position" konfiguracji.
  • Element MyActionFilterAttribute jest dodawany do kontenera usługi.
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
             Configuration.GetSection("Position"));
    services.AddScoped<MyActionFilterAttribute>();

    services.AddControllersWithViews();
}

Poniższy kod przedstawia klasę PositionOptions :

public class PositionOptions
{
    public string Title { get; set; }
    public string Name { get; set; }
}

Poniższy kod ma zastosowanie MyActionFilterAttribute do Index2 metody :

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

W obszarze Nagłówkiauthor: Rick Anderson odpowiedzi , i Editor: Joe Smith jest wyświetlany po wywołaniu punktu końcowegoSample/Index2.

Poniższy kod stosuje element MyActionFilterAttribute i AddHeaderAttribute do Razor strony:

[AddHeader("Author", "Rick Anderson")]
[ServiceFilter(typeof(MyActionFilterAttribute))]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

Nie można zastosować filtrów do Razor metod obsługi strony. Można je zastosować do Razor modelu strony lub globalnie.

Kilka interfejsów filtru ma odpowiednie atrybuty, których można używać jako klas bazowych na potrzeby implementacji niestandardowych.

Atrybuty filtru:

Filtrowanie zakresów i kolejności wykonywania

Filtr można dodać do potoku w jednym z trzech zakresów:

  • Używanie atrybutu w akcji kontrolera. Nie można zastosować atrybutów filtru do Razor metod obsługi stron.
  • Używanie atrybutu na kontrolerze lub Razor stronie.
  • Globalnie dla wszystkich kontrolerów, akcji i Razor stron, jak pokazano w poniższym kodzie:
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter));
    });
}

Domyślna kolejność wykonywania

Jeśli istnieje wiele filtrów dla określonego etapu potoku, zakres określa domyślną kolejność wykonywania filtru. Filtry globalne otaczają filtry klas, które z kolei otaczają filtry metod.

W wyniku zagnieżdżania filtrów kod po filtrach jest uruchamiany w odwrotnej kolejności przed kodem. Sekwencja filtru:

  • Przed kodem filtrów globalnych.
    • Przed kodem filtrów kontrolera i Razor strony.
      • Przed kodem metody akcji filtruje.
      • Po kodzie metody akcji filtruje.
    • Po kodzie kontrolera i Razor filtrów strony.
  • Po kodzie filtrów globalnych.

Poniższy przykład ilustrujący kolejność wywoływanych metod filtrowania dla filtrów akcji synchronicznych.

Sequence Zakres filtru Metoda filter
1 Globalnie OnActionExecuting
2 Kontroler lub Razor strona OnActionExecuting
3 Metoda OnActionExecuting
4 Metoda OnActionExecuted
5 Kontroler lub Razor strona OnActionExecuted
6 Globalnie OnActionExecuted

Filtry na poziomie kontrolera

Każdy kontroler dziedziczony z klasy bazowej Controller obejmuje Controller.OnActionExecutingmetody , Controller.OnActionExecutionAsynci Controller.OnActionExecutedOnActionExecuted . Te metody:

  • Zawijaj filtry uruchamiane dla danej akcji.
  • OnActionExecuting jest wywoływana przed dowolnym filtrem akcji.
  • OnActionExecuted jest wywoływany po wszystkich filtrach akcji.
  • OnActionExecutionAsync jest wywoływana przed dowolnym filtrem akcji. Kod w filtrze po uruchomieniu po next uruchomieniu po metodzie akcji.

Na przykład w przykładzie MySampleActionFilter pobierania aplikacja jest stosowana globalnie podczas uruchamiania.

Element TestController:

  • Stosuje element SampleActionFilterAttribute ([SampleActionFilter]) do FilterTest2 akcji.
  • Przesłonięcia OnActionExecuting i OnActionExecuted.
public class TestController : Controller
{
    [SampleActionFilter(Order = int.MinValue)]
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

MyDisplayRouteInfo jest dostarczany przez pakiet NuGet Rick.Docs.Samples.RouteInfo i wyświetla informacje o trasie.

Przechodzenie do https://localhost:5001/Test/FilterTest2 uruchomi następującego kodu:

  • TestController.OnActionExecuting
    • MySampleActionFilter.OnActionExecuting
      • SampleActionFilterAttribute.OnActionExecuting
        • TestController.FilterTest2
      • SampleActionFilterAttribute.OnActionExecuted
    • MySampleActionFilter.OnActionExecuted
  • TestController.OnActionExecuted

Filtry na poziomie kontrolera ustawiają właściwość Order na int.MinValue. Filtry na poziomie kontrolera nie można ustawić tak, aby były uruchamiane po zastosowaniu filtrów do metod. Kolejność jest wyjaśniona w następnej sekcji.

Aby uzyskać informacje na temat Razor stron, zobacz Implementowanie Razor filtrów stron przez zastępowanie metod filtrowania.

Zastępowanie kolejności domyślnej

Domyślną sekwencję wykonywania można zastąpić przez zaimplementowanie metody IOrderedFilter. IOrderedFilter Uwidacznia Order właściwość, która ma pierwszeństwo przed zakresem, aby określić kolejność wykonywania. Filtr o niższej Order wartości:

  • Uruchamia przed kodem przed filtrem o wyższej wartości Order.
  • Uruchamia po kodzie po filtrze z wyższą Order wartością.

Właściwość Order jest ustawiana za pomocą parametru konstruktora:

[SampleActionFilter(Order = int.MinValue)]

Rozważ dwa filtry akcji w następującym kontrolerze:

[MyAction2Filter]
public class Test2Controller : Controller
{
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

Filtr globalny jest dodawany w pliku StartUp.ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter));
    });
}

3 filtry są uruchamiane w następującej kolejności:

  • Test2Controller.OnActionExecuting
    • MySampleActionFilter.OnActionExecuting
      • MyAction2FilterAttribute.OnActionExecuting
        • Test2Controller.FilterTest2
      • MyAction2FilterAttribute.OnResultExecuting
    • MySampleActionFilter.OnActionExecuted
  • Test2Controller.OnActionExecuted

Właściwość Order zastępuje zakres podczas określania kolejności uruchamiania filtrów. Filtry są sortowane najpierw według kolejności, a następnie zakres jest używany do przerywania więzi. Wszystkie wbudowane filtry implementują IOrderedFilter i ustawiają wartość domyślną Order na 0. Jak wspomniano wcześniej, filtry na poziomie kontrolera ustawiają właściwość Order na int.MinValue Wartość Dla wbudowanych filtrów, zakres określa kolejność, chyba że Order ustawiono wartość inną niż zero.

W poprzednim kodzie ma zakres globalny, MySampleActionFilter dlatego jest uruchamiany przed elementem MyAction2FilterAttribute, który ma zakres kontrolera. Aby najpierw wykonać MyAction2FilterAttribute uruchomienie, ustaw kolejność na int.MinValue:

[MyAction2Filter(int.MinValue)]
public class Test2Controller : Controller
{
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

Aby najpierw uruchomić filtr MySampleActionFilter globalny, ustaw wartość Orderint.MinValue:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter),
                            int.MinValue);
    });
}

Anulowanie i zwarcie

Potok filtru można zwarcić, ustawiając Result właściwość parametru ResourceExecutingContext podanego w metodzie filtru. Na przykład następujący filtr zasobu uniemożliwia wykonywanie pozostałej części potoku:

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult()
        {
            Content = "Resource unavailable - header not set."
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

W poniższym kodzie, zarówno filtr, jak ShortCircuitingResourceFilter i docelowy AddHeaderSomeResource filtr metody akcji. Element ShortCircuitingResourceFilter:

  • Uruchamia najpierw, ponieważ jest to filtr zasobów i AddHeader jest filtrem akcji.
  • Zwarcie pozostałej części potoku.

W związku z tym AddHeader filtr nigdy nie jest uruchamiany dla SomeResource akcji. To zachowanie byłoby takie samo, jeśli oba filtry zostały zastosowane na poziomie metody akcji, pod warunkiem, że uruchomiono ShortCircuitingResourceFilter najpierw. Uruchamia ShortCircuitingResourceFilter najpierw ze względu na typ filtru lub jawne Order użycie właściwości.

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

    [ShortCircuitingResourceFilter]
    public IActionResult SomeResource()
    {
        return Content("Successful access to resource - header is set.");
    }

    [AddHeaderWithFactory]
    public IActionResult HeaderWithFactory()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }
}

Wstrzykiwanie zależności

Filtry można dodawać według typu lub wystąpienia. Jeśli wystąpienie zostanie dodane, to wystąpienie jest używane dla każdego żądania. Jeśli typ zostanie dodany, zostanie aktywowany typ. Filtr aktywowany przez typ oznacza:

  • Wystąpienie jest tworzone dla każdego żądania.
  • Wszystkie zależności konstruktora są wypełniane przez wstrzykiwanie zależności (DI).

Filtry implementowane jako atrybuty i dodawane bezpośrednio do klas kontrolerów lub metod akcji nie mogą mieć zależności konstruktora zapewniane przez wstrzykiwanie zależności (DI). Zależności konstruktora nie mogą być udostępniane przez di, ponieważ:

  • Atrybuty muszą mieć parametry konstruktora podane tam, gdzie są stosowane.
  • Jest to ograniczenie działania atrybutów.

Następujące filtry obsługują zależności konstruktora dostarczone z di:

Powyższe filtry można zastosować do kontrolera lub metody akcji:

Rejestratory są dostępne w witrynie DI. Należy jednak unikać tworzenia i używania filtrów wyłącznie do celów rejestrowania. Wbudowane rejestrowanie struktury zwykle zapewnia, co jest potrzebne do rejestrowania. Rejestrowanie dodane do filtrów:

  • Należy skoncentrować się na problemach dotyczących domeny biznesowej lub zachowaniu specyficznym dla filtru.
  • Nie należy rejestrować akcji ani innych zdarzeń struktury. Wbudowane filtry rejestrują akcje i zdarzenia struktury.

ServiceFilterAttribute

Typy implementacji filtru usługi są rejestrowane w programie ConfigureServices. Element ServiceFilterAttribute pobiera wystąpienie filtru z di.

Poniższy kod przedstawia element AddHeaderResultServiceFilter:

public class AddHeaderResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var headerName = "OnResultExecuting";
        context.HttpContext.Response.Headers.Add(
            headerName, new string[] { "ResultExecutingSuccessfully" });
        _logger.LogInformation("Header added: {HeaderName}", headerName);
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
        _logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
    }
}

W poniższym kodzie AddHeaderResultServiceFilter zostanie dodany do kontenera DI:

public void ConfigureServices(IServiceCollection services)
{
    // Add service filters.
    services.AddScoped<AddHeaderResultServiceFilter>();
    services.AddScoped<SampleActionFilterAttribute>();

    services.AddControllersWithViews(options =>
   {
       options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
           "Result filter added to MvcOptions.Filters"));         // An instance
        options.Filters.Add(typeof(MySampleActionFilter));         // By type
        options.Filters.Add(new SampleGlobalActionFilter());       // An instance
    });
}

W poniższym kodzie ServiceFilter atrybut pobiera wystąpienie filtru AddHeaderResultServiceFilter z di:

[ServiceFilter(typeof(AddHeaderResultServiceFilter))]
public IActionResult Index()
{
    return View();
}

W przypadku używania polecenia ServiceFilterAttribute, ustawienie ServiceFilterAttribute.IsReusable:

  • Zawiera wskazówkę, że wystąpienie filtru może być ponownie używane poza zakresem żądania, w ramach którego został utworzony. Środowisko uruchomieniowe ASP.NET Core nie gwarantuje:

    • Zostanie utworzone pojedyncze wystąpienie filtru.
    • Filtr nie zostanie ponownie zażądany z kontenera DI w późniejszym czasie.
  • Nie należy używać z filtrem, który zależy od usług z okresem istnienia innym niż pojedynczy.

ServiceFilterAttribute implementuje IFilterFactory. IFilterFactory Uwidacznia metodę CreateInstanceIFilterMetadata tworzenia wystąpienia. CreateInstance ładuje określony typ z DI.

TypeFilterAttribute

TypeFilterAttribute jest podobny do ServiceFilterAttribute, ale jego typ nie jest rozpoznawany bezpośrednio z kontenera DI. Tworzy wystąpienie typu przy użyciu polecenia Microsoft.Extensions.DependencyInjection.ObjectFactory.

Ponieważ TypeFilterAttribute typy nie są rozpoznawane bezpośrednio z kontenera DI:

  • Typy, do których odwołuje się odwołanie przy użyciu TypeFilterAttribute kontenera di, nie muszą być zarejestrowane. Mają one swoje zależności spełnione przez kontener DI.
  • TypeFilterAttribute opcjonalnie może akceptować argumenty konstruktora dla typu.

W przypadku używania polecenia TypeFilterAttribute, ustawienie TypeFilterAttribute.IsReusable:

  • Zawiera wskazówkę, że wystąpienie filtru może być ponownie używane poza zakresem żądania, w ramach którego został utworzony. Środowisko uruchomieniowe ASP.NET Core nie gwarantuje utworzenia pojedynczego wystąpienia filtru.

  • Nie należy używać z filtrem, który zależy od usług z okresem istnienia innym niż pojedynczy.

W poniższym przykładzie pokazano, jak przekazać argumenty do typu przy użyciu polecenia TypeFilterAttribute:

[TypeFilter(typeof(LogConstantFilter),
    Arguments = new object[] { "Method 'Hi' called" })]
public IActionResult Hi(string name)
{
    return Content($"Hi {name}");
}

Filtry autoryzacji

Filtry autoryzacji:

  • Czy pierwsze filtry są uruchamiane w potoku filtru.
  • Kontrolowanie dostępu do metod akcji.
  • Przed metodą, ale nie po metodzie.

Niestandardowe filtry autoryzacji wymagają niestandardowej struktury autoryzacji. Preferuj konfigurowanie zasad autoryzacji lub pisanie niestandardowych zasad autoryzacji w celu zapisania filtru niestandardowego. Wbudowany filtr autoryzacji:

  • Wywołuje system autoryzacji.
  • Nie autoryzuje żądań.

Nie zgłaszaj wyjątków w filtrach autoryzacji:

  • Wyjątek nie zostanie obsłużony.
  • Filtry wyjątków nie będą obsługiwać wyjątku.

Rozważ wystawienie wyzwania w przypadku wystąpienia wyjątku w filtrze autoryzacji.

Dowiedz się więcej o autoryzacji.

Filtry zasobów

Filtry zasobów:

Filtry zasobów są przydatne w przypadku zwarć większość potoku. Na przykład filtr buforowania może uniknąć reszty potoku po trafieniu pamięci podręcznej.

Przykłady filtrów zasobów:

Filtry akcji

Filtry akcji nie mają zastosowania do Razor stron. Razor Obsługa stron i IPageFilterIAsyncPageFilter. Aby uzyskać więcej informacji, zobacz Metody filtrowania dla Razor stron.

Filtry akcji:

Poniższy kod przedstawia przykładowy filtr akcji:

public class MySampleActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }
}

Element ActionExecutingContext zawiera następujące właściwości:

  • ActionArguments — umożliwia odczytywanie danych wejściowych do metody akcji.
  • Controller — umożliwia manipulowanie wystąpieniem kontrolera.
  • Result - ustawianie Result zwarć wykonywania metody akcji i kolejnych filtrów akcji.

Zgłaszanie wyjątku w metodzie akcji:

  • Zapobiega uruchamianiu kolejnych filtrów.
  • W przeciwieństwie do ustawienia Result, jest traktowany jako błąd zamiast pomyślnego wyniku.

Controller Zapewnia ActionExecutedContext i Result oraz następujące właściwości:

  • Canceled — prawda, jeśli wykonanie akcji zostało zwarcie przez inny filtr.

  • Exception — Wartość inna niż null, jeśli akcja lub wcześniej uruchomiony filtr akcji zwrócił wyjątek. Ustawienie tej właściwości na null:

    • Skutecznie obsługuje wyjątek.
    • Result jest wykonywany tak, jakby został zwrócony z metody akcji.

W przypadku metody IAsyncActionFilterwywołanie metody ActionExecutionDelegate:

  • Wykonuje wszystkie kolejne filtry akcji i metodę akcji.
  • Zwraca wartość ActionExecutedContext.

Aby zwarć, przypisz Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result do wystąpienia wyników i nie wywołuje next metody ().ActionExecutionDelegate

Struktura zapewnia abstrakcję ActionFilterAttribute , która może być podklasowana.

Filtr OnActionExecuting akcji może służyć do:

  • Weryfikowanie stanu modelu.
  • Zwróć błąd, jeśli stan jest nieprawidłowy.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext 
                                           context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(
                                                context.ModelState);
        }
    }

Uwaga

Kontrolery z adnotacjami z atrybutem [ApiController] automatycznie weryfikują stan modelu i zwracają odpowiedź 400. Aby uzyskać więcej informacji, zobacz Automatyczne odpowiedzi HTTP 400. Metoda OnActionExecuted jest uruchamiana po metodzie akcji:

  • I może wyświetlać i manipulować wynikami akcji za pośrednictwem Result właściwości .

  • Canceled parametr ma wartość true, jeśli wykonanie akcji zostało zwarcie przez inny filtr.

  • Exception parametr jest ustawiony na wartość inną niż null, jeśli akcja lub kolejny filtr akcji zwrócił wyjątek. Ustawienie Exception wartości null:

    • Skutecznie obsługuje wyjątek.
    • ActionExecutedContext.Result jest wykonywany tak, jakby został zwrócony normalnie z metody akcji.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext 
                                           context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(
                                                context.ModelState);
        }
    }


    public override void OnActionExecuted(ActionExecutedContext 
                                          context)
    {
        var result = context.Result;
        // Do something with Result.
        if (context.Canceled == true)
        {
            // Action execution was short-circuited by another filter.
        }

        if(context.Exception != null)
        {
            // Exception thrown by action or action filter.
            // Set to null to handle the exception.
            context.Exception = null;
        }
        base.OnActionExecuted(context);
    }
}

Filtry wyjątków

Filtry wyjątków:

Poniższy przykładowy filtr wyjątków używa niestandardowego widoku błędu, aby wyświetlić szczegółowe informacje o wyjątkach występujących podczas opracowywania aplikacji:

public class CustomExceptionFilter : IExceptionFilter
{
    private readonly IWebHostEnvironment _hostingEnvironment;
    private readonly IModelMetadataProvider _modelMetadataProvider;

    public CustomExceptionFilter(
        IWebHostEnvironment hostingEnvironment,
        IModelMetadataProvider modelMetadataProvider)
    {
        _hostingEnvironment = hostingEnvironment;
        _modelMetadataProvider = modelMetadataProvider;
    }

    public void OnException(ExceptionContext context)
    {
        if (!_hostingEnvironment.IsDevelopment())
        {
            return;
        }
        var result = new ViewResult {ViewName = "CustomError"};
        result.ViewData = new ViewDataDictionary(_modelMetadataProvider,
                                                    context.ModelState);
        result.ViewData.Add("Exception", context.Exception);
        // TODO: Pass additional detailed data via ViewData
        context.Result = result;
    }
}

Poniższy kod testuje filtr wyjątku:

[TypeFilter(typeof(CustomExceptionFilter))]
public class FailingController : Controller
{
    [AddHeader("Failing Controller", 
               "Won't appear when exception is handled")]
    public IActionResult Index()
    {
        throw new Exception("Testing custom exception filter.");
    }
}

Filtry wyjątków:

  • Nie mają wcześniej i po zdarzeniach.
  • Zaimplementuj OnException lub OnExceptionAsync.
  • Obsługa nieobsługiwanych wyjątków występujących w Razor procesie tworzenia strony lub kontrolera, powiązania modelu, filtrów akcji lub metod akcji.
  • Nie przechwytuj wyjątków występujących w filtrach zasobów, filtrach wyników lub wykonywaniu wyników MVC.

Aby obsłużyć wyjątek, ustaw ExceptionHandled właściwość na true lub przypisz Result właściwość . Spowoduje to zatrzymanie propagacji wyjątku. Filtr wyjątku nie może przekształcić wyjątku w "powodzenie". Tylko filtr akcji może to zrobić.

Filtry wyjątków:

  • Są dobre w przypadku podlewek wyjątków występujących w ramach akcji.
  • Nie są tak elastyczne, jak oprogramowanie pośredniczące obsługujące błędy.

Preferuj oprogramowanie pośredniczące do obsługi wyjątków. Używaj filtrów wyjątków tylko wtedy, gdy obsługa błędów różni się w zależności od wywoływanej metody akcji. Na przykład aplikacja może mieć metody akcji zarówno dla punktów końcowych interfejsu API, jak i dla widoków/HTML. Punkty końcowe interfejsu API mogą zwracać informacje o błędach jako JSWŁĄCZONE, podczas gdy akcje oparte na widoku mogą zwracać stronę błędu jako kod HTML.

Filtry wyników

Filtry wyników:

IResultFilter i IAsyncResultFilter

Poniższy kod przedstawia filtr wyników, który dodaje nagłówek HTTP:

public class AddHeaderResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var headerName = "OnResultExecuting";
        context.HttpContext.Response.Headers.Add(
            headerName, new string[] { "ResultExecutingSuccessfully" });
        _logger.LogInformation("Header added: {HeaderName}", headerName);
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
        _logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
    }
}

Rodzaj wykonywanego wyniku zależy od akcji. Akcja zwracająca widok obejmuje wszystkie przetwarzanie razor w ramach wykonywanego ViewResult elementu. Metoda interfejsu API może wykonać serializacji w ramach wykonywania wyniku. Dowiedz się więcej o wynikach akcji.

Filtry wyników są wykonywane tylko wtedy, gdy akcja lub filtr akcji generuje wynik akcji. Filtry wyników nie są wykonywane, gdy:

  • Filtr autoryzacji lub filtr zasobów zwarcie potoku.
  • Filtr wyjątku obsługuje wyjątek, generując wynik akcji.

Metoda Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting może zwarć wykonywanie wyniku akcji i kolejnych filtrów wyników, ustawiając wartość Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Canceltrue. Zapisz w obiekcie odpowiedzi podczas zwarć, aby uniknąć generowania pustej odpowiedzi. Zgłaszanie wyjątku w pliku IResultFilter.OnResultExecuting:

  • Zapobiega wykonywaniu wyniku akcji i kolejnych filtrów.
  • Jest traktowany jako błąd zamiast pomyślnego wyniku.

Po uruchomieniu Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted metody odpowiedź prawdopodobnie została już wysłana do klienta. Jeśli odpowiedź została już wysłana do klienta, nie można jej zmienić.

ResultExecutedContext.Canceled parametr jest ustawiony na true wartość , jeśli wykonanie wyniku akcji zostało zwarcie przez inny filtr.

ResultExecutedContext.Exception parametr jest ustawiony na wartość inną niż null, jeśli wynik akcji lub kolejny filtr wyników zwrócił wyjątek. Ustawienie Exception wartości null skutecznie obsługuje wyjątek i uniemożliwia ponowne zgłoszenie wyjątku w dalszej części potoku. Nie ma niezawodnego sposobu zapisywania danych w odpowiedzi podczas obsługi wyjątku w filtrze wyników. Jeśli nagłówki zostały opróżnione do klienta, gdy wynik akcji zgłasza wyjątek, nie ma niezawodnego mechanizmu wysyłania kodu błędu.

W przypadku elementu IAsyncResultFilterwywołanie metody await next na obiekcie ResultExecutionDelegate wykonuje wszystkie kolejne filtry wyników i wynik akcji. Aby zwarć, ustaw wartość ResultExecutingContext.Canceltrue i nie wywołaj metody ResultExecutionDelegate:

public class MyAsyncResponseFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(ResultExecutingContext context,
                                             ResultExecutionDelegate next)
    {
        if (!(context.Result is EmptyResult))
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }

    }
}

Struktura zapewnia abstrakcję ResultFilterAttribute , która może być podklasowana. Pokazana wcześniej klasa AddHeaderAttribute jest przykładem atrybutu filtru wyników.

IAlwaysRunResultFilter i IAsyncAlwaysRunResultFilter

Interfejsy IAlwaysRunResultFilter i IAsyncAlwaysRunResultFilter deklarują implementację uruchamianą IResultFilter dla wszystkich wyników akcji. Obejmuje to wyniki akcji wygenerowane przez:

  • Filtry autoryzacji i filtry zasobów, które są zwarcie.
  • Filtry wyjątków.

Na przykład następujący filtr zawsze jest uruchamiany i ustawia wynik akcji (ObjectResult) z kodem stanu jednostki nieprzetworzonej 422, gdy negocjowanie zawartości kończy się niepowodzeniem:

public class UnprocessableResultFilter : Attribute, IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult &&
            statusCodeResult.StatusCode == (int) HttpStatusCode.UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Can't process this!")
            {
                StatusCode = (int) HttpStatusCode.UnsupportedMediaType,
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

IFilterFactory

IFilterFactory implementuje IFilterMetadata. IFilterFactory W związku z tym wystąpienie może być używane jako IFilterMetadata wystąpienie w dowolnym miejscu w potoku filtru. Gdy środowisko uruchomieniowe przygotowuje się do wywołania filtru, próbuje rzutować go do elementu IFilterFactory. Jeśli rzutowanie powiedzie się, metoda jest wywoływana w CreateInstance celu utworzenia wywoływanego IFilterMetadata wystąpienia. Zapewnia to elastyczny projekt, ponieważ dokładny potok filtru nie musi być jawnie ustawiany podczas uruchamiania aplikacji.

IFilterFactory.IsReusable:

  • Jest wskazówką fabryki, że wystąpienie filtru utworzone przez fabrykę może być ponownie używane poza zakresem żądania, w ramach którego został utworzony.
  • Nie należy używać z filtrem, który zależy od usług z okresem istnienia innym niż pojedynczy.

Środowisko uruchomieniowe ASP.NET Core nie gwarantuje:

  • Zostanie utworzone pojedyncze wystąpienie filtru.
  • Filtr nie zostanie ponownie zażądany z kontenera DI w późniejszym czasie.

Ostrzeżenie

Skonfiguruj wartość tak, aby zwracać IFilterFactory.IsReusabletrue tylko wtedy, gdy źródło filtrów jest jednoznaczne, filtry są bezstanowe, a filtry są bezpieczne do użycia w wielu żądaniach HTTP. Na przykład nie zwracaj filtrów z di, które są zarejestrowane jako zakres lub przejściowe, jeśli IFilterFactory.IsReusable zwraca wartość true. IFilterFactory można zaimplementować przy użyciu implementacji atrybutów niestandardowych jako innego podejścia do tworzenia filtrów:

public class AddHeaderWithFactoryAttribute : Attribute, IFilterFactory
{
    // Implement IFilterFactory
    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        return new InternalAddHeaderFilter();
    }

    private class InternalAddHeaderFilter : IResultFilter
    {
        public void OnResultExecuting(ResultExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add(
                "Internal", new string[] { "My header" });
        }

        public void OnResultExecuted(ResultExecutedContext context)
        {
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

Filtr jest stosowany w następującym kodzie:

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

    [ShortCircuitingResourceFilter]
    public IActionResult SomeResource()
    {
        return Content("Successful access to resource - header is set.");
    }

    [AddHeaderWithFactory]
    public IActionResult HeaderWithFactory()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }
}

Przetestuj powyższy kod, uruchamiając przykład pobierania:

  • Wywołaj narzędzia deweloperskie F12.
  • Przejdź do https://localhost:5001/Sample/HeaderWithFactory.

Narzędzia deweloperskie F12 wyświetlają następujące nagłówki odpowiedzi dodane przez przykładowy kod:

  • authorRick Anderson:
  • globaladdheader:Result filter added to MvcOptions.Filters
  • Wewnętrznego:My header

Powyższy kod tworzy wewnętrzny nagłówek odpowiedzi:My header .

IFilterFactory zaimplementowany w atrybucie

Filtry implementujące IFilterFactory są przydatne w przypadku filtrów, które:

  • Nie wymagaj przekazywania parametrów.
  • Mieć zależności konstruktora, które muszą być wypełnione przez di.

TypeFilterAttribute implementuje IFilterFactory. IFilterFactory Uwidacznia metodę CreateInstanceIFilterMetadata tworzenia wystąpienia. CreateInstance ładuje określony typ z kontenera usług (DI).

public class SampleActionFilterAttribute : TypeFilterAttribute
{
    public SampleActionFilterAttribute()
                         :base(typeof(SampleActionFilterImpl))
    { 
    }

    private class SampleActionFilterImpl : IActionFilter
    {
        private readonly ILogger _logger;
        public SampleActionFilterImpl(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
           _logger.LogInformation("SampleActionFilterAttribute.OnActionExecuting");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation("SampleActionFilterAttribute.OnActionExecuted");
        }
    }
}

Poniższy kod przedstawia trzy podejścia do zastosowania elementu [SampleActionFilter]:

[SampleActionFilter]
public IActionResult FilterTest()
{
    return Content("From FilterTest");
}

[TypeFilter(typeof(SampleActionFilterAttribute))]
public IActionResult TypeFilterTest()
{
    return Content("From TypeFilterTest");
}

// ServiceFilter must be registered in ConfigureServices or
// System.InvalidOperationException: No service for type '<filter>'
// has been registered. Is thrown.
[ServiceFilter(typeof(SampleActionFilterAttribute))]
public IActionResult ServiceFilterTest()
{
    return Content("From ServiceFilterTest");
}

W poprzednim kodzie dekorowanie metody za [SampleActionFilter] pomocą metody jest preferowanym podejściem do stosowania metody SampleActionFilter.

Używanie oprogramowania pośredniczącego w potoku filtru

Filtry zasobów działają jak oprogramowanie pośredniczące, które otaczają wykonywanie wszystkich elementów, które są dostępne później w potoku. Jednak filtry różnią się od oprogramowania pośredniczącego, ponieważ są one częścią środowiska uruchomieniowego, co oznacza, że mają dostęp do kontekstu i konstrukcji.

Aby użyć oprogramowania pośredniczącego jako filtru, utwórz typ z metodą określającą Configure oprogramowanie pośredniczące do wstrzykiwania do potoku filtru. W poniższym przykładzie użyto oprogramowania pośredniczącego lokalizacji do ustanowienia bieżącej kultury dla żądania:

public class LocalizationPipeline
{
    public void Configure(IApplicationBuilder applicationBuilder)
    {
        var supportedCultures = new[]
        {
            new CultureInfo("en-US"),
            new CultureInfo("fr")
        };

        var options = new RequestLocalizationOptions
        {
            DefaultRequestCulture = new RequestCulture(
                                       culture: "en-US", 
                                       uiCulture: "en-US"),
            SupportedCultures = supportedCultures,
            SupportedUICultures = supportedCultures
        };
        options.RequestCultureProviders = new[] 
            { new RouteDataRequestCultureProvider() {
                Options = options } };

        applicationBuilder.UseRequestLocalization(options);
    }
}

Użyj polecenia , MiddlewareFilterAttribute aby uruchomić oprogramowanie pośredniczące:

[Route("{culture}/[controller]/[action]")]
[MiddlewareFilter(typeof(LocalizationPipeline))]
public IActionResult CultureFromRouteData()
{
    return Content(
          $"CurrentCulture:{CultureInfo.CurrentCulture.Name},"
        + $"CurrentUICulture:{CultureInfo.CurrentUICulture.Name}");
}

Filtry oprogramowania pośredniczącego są uruchamiane na tym samym etapie potoku filtru co filtry zasobów, przed powiązaniem modelu i po pozostałej części potoku.

Bezpieczeństwo wątkowe

Podczas przekazywania wystąpienia filtru do Addelementu , a nie do Typeelementu , filtr jest pojedynczym elementem i nie jest bezpieczny wątkowo.

Następne akcje