Verständnis der ASP.NET Core Request and Response Pipeline - Teil 6: Fortgeschrittene Rohrleitungshaken und Erweiterbarkeit (Deutsch (German))

Verständnis der ASP.NET Core Request and Response Pipeline - Teil 6: Fortgeschrittene Rohrleitungshaken und Erweiterbarkeit

Sunday, 09 November 2025

//

14 minute read

Einleitung

Während dieser Serie haben wir die ASP.NET Core Pipeline von ihren Grundlagen bis hin zu Anwendungsmodellen erforscht.

Jetzt wagen wir uns in fortgeschrittenes Gebiet: die Erweiterungspunkte und Haken, die Sie die Pipeline auf einer tiefen Ebene anpassen lassen.

  • Diese fortschrittlichen Techniken ermöglichen es Ihnen:
  • Ändern der Pipeline vor Beginn der Anwendung
  • Fügen Sie Hintergrunddienste hinzu, die neben Ihrer Anwendung laufen
  • Angepasste Endpoint-Datenquellen erstellen
  • Einbinden in Anwendungslebensdauer Ereignisse

Bauen Sie wiederverwendbare Komponenten, die nahtlos mit ASP.NET Core integrieren

Das Verständnis dieser Erweiterungspunkte verwandelt Sie von einem Verbraucher des Rahmens zu jemandem, der es auf einzigartige Anforderungen erweitern kann.

HINWEIS: Dies ist Teil meiner Experimente mit KI / eine Möglichkeit, $1000 Calude Code Web Credits auszugeben.

graph TB
    subgraph "Application Startup"
        A1[IStartupFilter]
        A2[Configure Services]
        A3[Configure Pipeline]
    end

    subgraph "Background Processing"
        B1[IHostedService]
        B2[BackgroundService]
        B3[IHostApplicationLifetime]
    end

    subgraph "Endpoint Discovery"
        C1[EndpointDataSource]
        C2[IEndpointConventionBuilder]
        C3[IEndpointRouteBuilder]
    end

    subgraph "Request Processing"
        D1[IMiddleware]
        D2[IApplicationModelProvider]
        D3[IActionDescriptorProvider]
    end

    Start[Application Start] --> A1
    A1 --> A2
    A2 --> A3
    A3 --> B1
    B1 --> C1
    C1 --> D1


Ich habe dies eine BUNCH Papiere gefüttert, mein Verständnis, Fragen, die ich diesen Artikel generieren musste.

IStartupFilterEs macht Spaß und füllt eine Lücke, die ich nirgendwo sonst gefüllt habe.

Erweiterungspunkte Übersicht

public class RequestTimingStartupFilter : IStartupFilter
{
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return app =>
        {
            // This middleware runs BEFORE your normal pipeline configuration
            app.Use(async (context, next) =>
            {
                var sw = Stopwatch.StartNew();
                context.Items["RequestStartTime"] = DateTime.UtcNow;

                await next(context);

                sw.Stop();
                context.Response.Headers["X-Response-Time-Ms"] = sw.ElapsedMilliseconds.ToString();
            });

            // Call the next startup filter
            next(app);

            // This middleware runs AFTER your normal pipeline configuration
            app.Use(async (context, next) =>
            {
                context.Response.Headers["X-Pipeline-End"] = "true";
                await next(context);
            });
        };
    }
}

// Register the startup filter
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<IStartupFilter, RequestTimingStartupFilter>();

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

IStartupFilter: Ändern der Pipeline bei Startup

sequenceDiagram
    participant Request
    participant SF1 as Startup Filter 1 (Before)
    participant App as Application Middleware
    participant SF1A as Startup Filter 1 (After)
    participant Endpoint

    Request->>SF1: Enter
    Note over SF1: Added before app.Build()
    SF1->>App: Enter
    Note over App: Your middleware (app.Use...)
    App->>Endpoint: Execute endpoint
    Endpoint-->>App: Return
    App-->>SF1A: Return
    Note over SF1A: Added after next(app)
    SF1A-->>Request: Response

ermöglicht es Ihnen, Middleware vor oder nach der normalen Konfiguration der Anwendung zu konfigurieren.

public class HttpsEnforcementStartupFilter : IStartupFilter
{
    private readonly IWebHostEnvironment _env;

    public HttpsEnforcementStartupFilter(IWebHostEnvironment env)
    {
        _env = env;
    }

    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return app =>
        {
            // Only enforce HTTPS in production
            if (_env.IsProduction())
            {
                app.Use(async (context, next) =>
                {
                    if (!context.Request.IsHttps)
                    {
                        var httpsUrl = $"https://{context.Request.Host}{context.Request.Path}{context.Request.QueryString}";
                        context.Response.Redirect(httpsUrl, permanent: true);
                        return;
                    }

                    await next(context);
                });

                // Add HSTS header
                app.UseHsts();
            }

            next(app);
        };
    }
}

Grundlegende Verwendung

// Filter 1: Logging
public class LoggingStartupFilter : IStartupFilter
{
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return app =>
        {
            app.Use(async (context, next) =>
            {
                Console.WriteLine($"[LoggingFilter] Before: {context.Request.Path}");
                await next(context);
                Console.WriteLine($"[LoggingFilter] After: {context.Response.StatusCode}");
            });

            next(app);
        };
    }
}

// Filter 2: Security headers
public class SecurityHeadersStartupFilter : IStartupFilter
{
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return app =>
        {
            app.Use(async (context, next) =>
            {
                context.Response.Headers["X-Content-Type-Options"] = "nosniff";
                context.Response.Headers["X-Frame-Options"] = "DENY";
                context.Response.Headers["X-XSS-Protection"] = "1; mode=block";

                await next(context);
            });

            next(app);
        };
    }
}

// Register both filters
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<IStartupFilter, LoggingStartupFilter>();
builder.Services.AddSingleton<IStartupFilter, SecurityHeadersStartupFilter>();

var app = builder.Build();

// Filters execute in registration order

Ausführungsauftrag:

IHostedServicePraktisches Beispiel: Auto-HTTPS-Durchsetzung

Mehrere Startup-Filter komponieren

public class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger<TimedHostedService> _logger;
    private Timer? _timer;
    private int _executionCount = 0;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Hosted Service is starting");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object? state)
    {
        var count = Interlocked.Increment(ref _executionCount);

        _logger.LogInformation(
            "Timed Hosted Service is working. Count: {Count}",
            count);
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Hosted Service is stopping");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

// Register
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHostedService<TimedHostedService>();
var app = builder.Build();

IHostedService: Hintergrundaufgaben

public class QueueProcessorService : BackgroundService
{
    private readonly ILogger<QueueProcessorService> _logger;
    private readonly IServiceProvider _serviceProvider;

    public QueueProcessorService(
        ILogger<QueueProcessorService> logger,
        IServiceProvider serviceProvider)
    {
        _logger = logger;
        _serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Queue Processor Service is starting");

        // Run until cancellation is requested
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await ProcessQueueAsync(stoppingToken);

                // Wait before next iteration
                await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
            }
            catch (OperationCanceledException)
            {
                // Expected when stopping
                break;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing queue");

                // Wait before retrying
                await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
            }
        }

        _logger.LogInformation("Queue Processor Service is stopping");
    }

    private async Task ProcessQueueAsync(CancellationToken cancellationToken)
    {
        // Create a scope for scoped services
        using var scope = _serviceProvider.CreateScope();
        var queueService = scope.ServiceProvider.GetRequiredService<IQueueService>();

        var item = await queueService.DequeueAsync(cancellationToken);

        if (item != null)
        {
            _logger.LogInformation("Processing item: {Item}", item);
            await queueService.ProcessAsync(item, cancellationToken);
        }
    }
}

ermöglicht Hintergrundaufgaben, die für die Lebensdauer der Anwendung ausgeführt werden.

public class StartupTasksService : BackgroundService
{
    private readonly ILogger<StartupTasksService> _logger;
    private readonly IHostApplicationLifetime _lifetime;
    private readonly IServiceProvider _serviceProvider;

    public StartupTasksService(
        ILogger<StartupTasksService> logger,
        IHostApplicationLifetime lifetime,
        IServiceProvider serviceProvider)
    {
        _logger = logger;
        _lifetime = lifetime;
        _serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // Wait for application to fully start
        await Task.Delay(100, stoppingToken);

        try
        {
            using var scope = _serviceProvider.CreateScope();

            // Run startup tasks
            await WarmUpCacheAsync(scope, stoppingToken);
            await ValidateDatabaseAsync(scope, stoppingToken);
            await LoadConfigurationAsync(scope, stoppingToken);

            _logger.LogInformation("All startup tasks completed successfully");
        }
        catch (Exception ex)
        {
            _logger.LogCritical(ex, "Startup tasks failed");

            // Stop the application if startup tasks fail
            _lifetime.StopApplication();
            return;
        }

        // Continue running for the lifetime of the application
        await Task.Delay(Timeout.Infinite, stoppingToken);
    }

    private async Task WarmUpCacheAsync(IServiceScopeScope, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Warming up cache...");
        await Task.Delay(500, cancellationToken);
        _logger.LogInformation("Cache warmed up");
    }

    private async Task ValidateDatabaseAsync(IServiceScope scope, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Validating database...");
        await Task.Delay(300, cancellationToken);
        _logger.LogInformation("Database validated");
    }

    private async Task LoadConfigurationAsync(IServiceScope scope, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Loading configuration...");
        await Task.Delay(200, cancellationToken);
        _logger.LogInformation("Configuration loaded");
    }
}

Grundlegender Hostdienst

sequenceDiagram
    participant App as Application
    participant Lifetime as IHostApplicationLifetime
    participant Service as BackgroundService

    App->>Lifetime: Application starting
    Lifetime->>Service: StartAsync()
    Service->>Service: ExecuteAsync() begins

    Note over App,Service: Application running

    Lifetime->>Lifetime: ApplicationStarted event

    Note over Service: Background work continues

    App->>Lifetime: Shutdown requested
    Lifetime->>Lifetime: ApplicationStopping event
    Lifetime->>Service: StopAsync()
    Service->>Service: Cancel ExecuteAsync()
    Service-->>Lifetime: Stopped

    Lifetime->>Lifetime: ApplicationStopped event
    App->>App: Exit

HintergrundService: Vereinfachte Hintergrundaufgaben

Koordinieren mit der Anwendungslebenszeit

public class PluginEndpointDataSource : EndpointDataSource
{
    private readonly List<Endpoint> _endpoints = new();
    private readonly IChangeToken _changeToken = NullChangeToken.Singleton;

    public PluginEndpointDataSource()
    {
        // Discover plugins and create endpoints
        DiscoverPlugins();
    }

    public override IReadOnlyList<Endpoint> Endpoints => _endpoints;

    public override IChangeToken GetChangeToken() => _changeToken;

    private void DiscoverPlugins()
    {
        // Example: Create endpoints for each discovered plugin
        var plugins = new[]
        {
            new { Name = "Plugin1", Path = "/plugins/plugin1" },
            new { Name = "Plugin2", Path = "/plugins/plugin2" }
        };

        foreach (var plugin in plugins)
        {
            var requestDelegate = CreatePluginDelegate(plugin.Name);

            var routeEndpoint = new RouteEndpoint(
                requestDelegate,
                RoutePatternFactory.Parse(plugin.Path),
                order: 0,
                new EndpointMetadataCollection(
                    new DisplayNameMetadata(plugin.Name)),
                displayName: plugin.Name);

            _endpoints.Add(routeEndpoint);
        }
    }

    private RequestDelegate CreatePluginDelegate(string pluginName)
    {
        return async context =>
        {
            await context.Response.WriteAsJsonAsync(new
            {
                plugin = pluginName,
                message = $"Response from {pluginName}",
                timestamp = DateTime.UtcNow
            });
        };
    }
}

// Register the endpoint data source
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<EndpointDataSource, PluginEndpointDataSource>();

var app = builder.Build();

// Endpoints from PluginEndpointDataSource are automatically available

app.Run();

Lebenszyklus-Visualisierung:

public class DatabaseEndpointDataSource : EndpointDataSource
{
    private readonly List<Endpoint> _endpoints = new();
    private CancellationTokenSource _cts = new();
    private IChangeToken _changeToken;

    public DatabaseEndpointDataSource(IServiceProvider serviceProvider)
    {
        _changeToken = new CancellationChangeToken(_cts.Token);

        // Load endpoints from database
        LoadEndpointsFromDatabase(serviceProvider);

        // Set up periodic refresh
        Task.Run(async () =>
        {
            while (true)
            {
                await Task.Delay(TimeSpan.FromMinutes(5));
                ReloadEndpoints(serviceProvider);
            }
        });
    }

    public override IReadOnlyList<Endpoint> Endpoints => _endpoints;

    public override IChangeToken GetChangeToken() => _changeToken;

    private void LoadEndpointsFromDatabase(IServiceProvider serviceProvider)
    {
        using var scope = serviceProvider.CreateScope();

        // Simulate loading from database
        var routes = new[]
        {
            new { Path = "/dynamic/users", Handler = "GetUsers" },
            new { Path = "/dynamic/products", Handler = "GetProducts" }
        };

        _endpoints.Clear();

        foreach (var route in routes)
        {
            var requestDelegate = CreateDynamicDelegate(route.Handler);

            var endpoint = new RouteEndpoint(
                requestDelegate,
                RoutePatternFactory.Parse(route.Path),
                order: 0,
                new EndpointMetadataCollection(),
                displayName: route.Handler);

            _endpoints.Add(endpoint);
        }
    }

    private void ReloadEndpoints(IServiceProvider serviceProvider)
    {
        LoadEndpointsFromDatabase(serviceProvider);

        // Notify of changes
        var oldCts = _cts;
        _cts = new CancellationTokenSource();
        _changeToken = new CancellationChangeToken(_cts.Token);
        oldCts.Cancel();
    }

    private RequestDelegate CreateDynamicDelegate(string handler)
    {
        return async context =>
        {
            await context.Response.WriteAsJsonAsync(new
            {
                handler,
                message = $"Dynamic endpoint: {handler}",
                path = context.Request.Path.Value
            });
        };
    }
}

Benutzerdefinierte Endpoint-Datenquellen

Erstellen Sie dynamische Endpunkte, die zur Laufzeit entdeckt werden:IMiddlewareDynamische Endpoint-Generierung

public class DatabaseHealthCheckMiddleware : IMiddleware
{
    private readonly ILogger<DatabaseHealthCheckMiddleware> _logger;

    // Can inject scoped services
    public DatabaseHealthCheckMiddleware(ILogger<DatabaseHealthCheckMiddleware> logger)
    {
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        if (context.Request.Path == "/health/db")
        {
            // Can resolve scoped services from context
            var dbContext = context.RequestServices.GetRequiredService<MyDbContext>();

            try
            {
                await dbContext.Database.CanConnectAsync();

                context.Response.StatusCode = 200;
                await context.Response.WriteAsJsonAsync(new
                {
                    status = "healthy",
                    database = "connected",
                    timestamp = DateTime.UtcNow
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Database health check failed");

                context.Response.StatusCode = 503;
                await context.Response.WriteAsJsonAsync(new
                {
                    status = "unhealthy",
                    database = "disconnected",
                    error = ex.Message
                });
            }

            return;
        }

        await next(context);
    }
}

// Register
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<DatabaseHealthCheckMiddleware>();

var app = builder.Build();

app.UseMiddleware<DatabaseHealthCheckMiddleware>();

app.Run();

IMiddleware: Klassenbasiertes Middleware mit DI

Im Gegensatz zu konventionsbasierten Middleware,

public class CustomApplicationModelProvider : IApplicationModelProvider
{
    public int Order => -1000 + 10; // Run after default provider

    public void OnProvidersExecuting(ApplicationModelProviderContext context)
    {
        // Runs before the default provider
        foreach (var controller in context.Result.Controllers)
        {
            // Add custom route prefix to all controllers
            controller.Selectors.Add(new SelectorModel
            {
                AttributeRouteModel = new AttributeRouteModel
                {
                    Template = "api/v2/[controller]"
                }
            });
        }
    }

    public void OnProvidersExecuted(ApplicationModelProviderContext context)
    {
        // Runs after the default provider
        foreach (var controller in context.Result.Controllers)
        {
            // Add custom metadata to all actions
            foreach (var action in controller.Actions)
            {
                action.Properties["CustomProperty"] = "CustomValue";
            }
        }
    }
}

// Register
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
    options.ModelMetadataDetailsProviders.Add(
        new CustomApplicationModelProvider());
});

vollständig unterstützt Abhängigkeitsinjektion:

Anbieter von Anwendungsmodellen

public class DynamicActionDescriptorProvider : IActionDescriptorProvider
{
    public int Order => -1000; // Run early

    public void OnProvidersExecuting(ActionDescriptorProviderContext context)
    {
        // Add dynamic actions
        context.Results.Add(new ActionDescriptor
        {
            RouteValues = new Dictionary<string, string>
            {
                ["controller"] = "Dynamic",
                ["action"] = "Generated"
            },
            DisplayName = "Dynamic Generated Action",
            AttributeRouteInfo = new AttributeRouteInfo
            {
                Template = "dynamic/generated"
            }
        });
    }

    public void OnProvidersExecuted(ActionDescriptorProviderContext context)
    {
        // Modify existing actions
        foreach (var action in context.Results)
        {
            // Add custom metadata
            action.Properties["Timestamp"] = DateTime.UtcNow;
        }
    }
}

Passen Sie an, wie MVC Controller und Aktionen entdeckt und konfiguriert:

Anbieter von Aktionsbeschreibungen

public class PluginFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
    public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
    {
        // Load plugin assemblies
        var pluginPath = Path.Combine(AppContext.BaseDirectory, "Plugins");

        if (!Directory.Exists(pluginPath))
            return;

        var pluginAssemblies = Directory.GetFiles(pluginPath, "*.dll")
            .Select(Assembly.LoadFrom);

        foreach (var assembly in pluginAssemblies)
        {
            var controllers = assembly.GetTypes()
                .Where(t => typeof(Controller).IsAssignableFrom(t) && !t.IsAbstract);

            foreach (var controller in controllers)
            {
                feature.Controllers.Add(controller.GetTypeInfo());
            }
        }
    }
}

// Register
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews()
    .ConfigureApplicationPartManager(manager =>
    {
        manager.FeatureProviders.Add(new PluginFeatureProvider());
    });

Aktionsdeskriptoren dynamisch hinzufügen oder ändern:

Leistungserbringer

public class RequireApiKeyConvention : IEndpointConventionBuilder
{
    private readonly List<Action<EndpointBuilder>> _conventions = new();

    public void Add(Action<EndpointBuilder> convention)
    {
        _conventions.Add(convention);
    }

    public void ApplyConventions(EndpointBuilder builder)
    {
        foreach (var convention in _conventions)
        {
            convention(builder);
        }
    }
}

// Usage
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var apiGroup = app.MapGroup("/api")
    .WithMetadata(new RequiresApiKeyMetadata())
    .AddEndpointFilter(async (context, next) =>
    {
        var apiKey = context.HttpContext.Request.Headers["X-API-Key"].FirstOrDefault();

        if (string.IsNullOrEmpty(apiKey))
        {
            return Results.Unauthorized();
        }

        return await next(context);
    });

apiGroup.MapGet("/data", () => new { data = "Protected data" });

app.Run();

class RequiresApiKeyMetadata { }

Steuerung, welche Baugruppen und Typen nach Reglern gescannt werden:

Endpoint-Übereinkommen

// Plugin interface
public interface IPlugin
{
    string Name { get; }
    string Version { get; }
    void ConfigureServices(IServiceCollection services);
    void ConfigureRoutes(IEndpointRouteBuilder endpoints);
}

// Plugin discovery service
public class PluginLoader : BackgroundService
{
    private readonly ILogger<PluginLoader> _logger;
    private readonly IServiceProvider _serviceProvider;
    private readonly List<IPlugin> _plugins = new();

    public PluginLoader(ILogger<PluginLoader> logger, IServiceProvider serviceProvider)
    {
        _logger = logger;
        _serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Loading plugins...");

        var pluginPath = Path.Combine(AppContext.BaseDirectory, "Plugins");

        if (Directory.Exists(pluginPath))
        {
            var pluginAssemblies = Directory.GetFiles(pluginPath, "*.dll")
                .Select(Assembly.LoadFrom);

            foreach (var assembly in pluginAssemblies)
            {
                var pluginTypes = assembly.GetTypes()
                    .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract);

                foreach (var type in pluginTypes)
                {
                    var plugin = (IPlugin)Activator.CreateInstance(type)!;
                    _plugins.Add(plugin);

                    _logger.LogInformation(
                        "Loaded plugin: {Name} v{Version}",
                        plugin.Name,
                        plugin.Version);
                }
            }
        }

        _logger.LogInformation("Loaded {Count} plugins", _plugins.Count);

        await Task.CompletedTask;
    }

    public IReadOnlyList<IPlugin> GetPlugins() => _plugins;
}

// Startup filter that configures plugins
public class PluginStartupFilter : IStartupFilter
{
    private readonly PluginLoader _pluginLoader;

    public PluginStartupFilter(PluginLoader pluginLoader)
    {
        _pluginLoader = pluginLoader;
    }

    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return app =>
        {
            next(app);

            // Configure plugin routes after main application
            var plugins = _pluginLoader.GetPlugins();

            foreach (var plugin in plugins)
            {
                app.UseRouting();
                app.UseEndpoints(endpoints =>
                {
                    plugin.ConfigureRoutes(endpoints);
                });
            }
        };
    }
}

// Register everything
var builder = WebApplication.CreateBuilder(args);

// Register plugin system
var pluginLoader = new PluginLoader(
    builder.Services.BuildServiceProvider().GetRequiredService<ILogger<PluginLoader>>(),
    builder.Services.BuildServiceProvider());

builder.Services.AddSingleton(pluginLoader);
builder.Services.AddHostedService(sp => sp.GetRequiredService<PluginLoader>());
builder.Services.AddSingleton<IStartupFilter, PluginStartupFilter>();

var app = builder.Build();

app.MapGet("/plugins", (PluginLoader loader) =>
{
    var plugins = loader.GetPlugins();

    return plugins.Select(p => new
    {
        p.Name,
        p.Version
    });
});

app.Run();

Metadaten und Konfiguration auf Endpunkte anwenden:

  • Praktisches Beispiel: Komplettes ErweiterungssystemLassen Sie uns ein komplettes Plugin-System erstellen:
  • Schlüssel zum MitnehmenIStartupFilter
  • ändert die Pipeline vor und nach der normalen KonfigurationIHostedService/BackgroundService
  • ermöglicht Hintergrundaufgaben während der gesamten AnwendungslebensdauerBenutzerdefinierte EndpointDataSource
  • erzeugt dynamische, auffindbare EndpunkteImiddleware
  • bietet volle DI-Unterstützung für MiddlewareAnbieter von Anwendungsmodellen
  • MVC Controller/Action Discovery anpassenLeistungserbringer
  • steuern, welche Typen als Controller gescannt werden

Endpoint-Übereinkommen


Metadaten und Konfiguration auf Endpunkte anwenden

Diese Erweiterungspunkte arbeiten zusammen, um anspruchsvolle, steckbare Architekturen zu schaffen

  1. **Das Verständnis dieser erweiterten Hooks ermöglicht es Ihnen, Frameworks auf ASP.NET Core zu bauen, Plugin-Systeme zu erstellen und die Pipeline auf einzigartige Anforderungen anzupassen.**Serie Schlussfolgerung
  2. **Während dieser sechsteiligen Serie sind wir durch jede Schicht der ASP.NET Core Request- und Response-Pipeline gereist:**Teil 1
  3. : Aufbau des Fundaments und des mentalen ModellsTeil 2
  4. : Erkundet Kestrel und die Hosting-SchichtTeil 3
  5. : Gemasterte Middleware und die VerarbeitungspipelineTeil 4
  6. : Erlerntes Routing und Endpoint-MatchingTeil 5

: Prüfung von MVC, Razor-Seiten und minimalen APIs

Teil 6

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.