نوشتن Middleware سفارشی در ASP.NET Core
نویسنده: وحید نصیری
تاریخ: ۱۳۹۵/۰۸/۰۱ ۱۲:۵۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
using Microsoft.AspNetCore.Http;
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("<div>from middleware-1, inside app.Use, before next()</div>");
await next();
await context.Response.WriteAsync("<div>from middleware-1, inside app.Use, after next()</div>");
});
app.Run(async context =>
{
await context.Response.WriteAsync("<div>Inside middleware-2 defined using app.Run</div>");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("<div>from middleware-3, inside app.Use, before next()</div>");
await next();
await context.Response.WriteAsync("<div>from middleware-3, inside app.Use, after next()</div>");
});
public delegate Task RequestDelegate(HttpContext context);
app.Map("/dnt", appBuilder =>
{
appBuilder.Run(async context =>
{
await context.Response.WriteAsync(@"<div>Inside Map(/dnt) --> Run</div>");
});
});
app.MapWhen(context =>
{
return context.Request.Query.ContainsKey("dnt");
},
appBuilder =>
{
appBuilder.Run(async context =>
{
await context.Response.WriteAsync(@"<div>Inside MapWhen(?dnt) --> Run</div>");
});
}); http://localhost:7742/?dnt=true
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Core1RtmEmptyTest.StartupCustomizations
{
public class MyMiddleware1
{
private readonly RequestDelegate _next;
public MyMiddleware1(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
context.Response.ContentType = "text/html";
context.Response.StatusCode = 200;
await context.Response.WriteAsync("<div>Hello from MyMiddleware1.</div>");
await _next.Invoke(context);
await context.Response.WriteAsync("<div>End of action.</div>");
}
}
} using Microsoft.AspNetCore.Builder;
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder app)
{
app.UseMiddleware<MyMiddleware1>();
return app;
}
} public void Configure(IApplicationBuilder app)
{
app.UseMyMiddleware(); namespace MyApp.Modules
{
public class MyModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication application)
{
application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
application.EndRequest += (new EventHandler(this.Application_EndRequest));
}
private void Application_BeginRequest(Object source, EventArgs e)
{
HttpContext context = ((HttpApplication)source).Context;
// Do something with context near the beginning of request processing.
}
private void Application_EndRequest(Object source, EventArgs e)
{
HttpContext context = ((HttpApplication)source).Context;
// Do something with context near the end of request processing.
}
}
} public class EndRequestMiddleware
{
private readonly RequestDelegate _next;
public EndRequestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// Do tasks before other middleware here, aka 'BeginRequest'
// ...
// Let the middleware pipeline run
await _next(context);
// Do tasks after middleware here, aka 'EndRequest'
// ...
}
} string responseBody = new StreamReader(context.Response.Body).ReadToEnd();
ArgumentException: Stream was not readable.
public IPCheckMiddleware( RequestDelegate next ,IIP iP)
{
_next = next;
_IP = iP;
} public class IPService:IIP
{
private readonly IHttpContextAccessor _Http;
private readonly IUnitOfWork _uow;
private readonly DbSet<AccessIp> _IP;
public IPService(IHttpContextAccessor http , IUnitOfWork unitOfWork)
{
_Http = http;
_uow = unitOfWork;
_IP = _uow.Set<AccessIp>();
}
} services.AddScoped<IUnitOfWork, ApplicationDbContext>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IIP, IPService>(); An error occurred while starting the application.
InvalidOperationException: Cannot resolve scoped service 'MohasebKhodro.Services.Shared.IIP' from root provider.
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, ServiceProvider serviceProvider)
InvalidOperationException: Cannot resolve scoped service 'MohasebKhodro.Services.Shared.IIP' from root provider.
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, ServiceProvider serviceProvider)
Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
Microsoft.Extensions.Internal.ActivatorUtilities+ConstructorMatcher.CreateInstance(IServiceProvider provider)
Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+<>c__DisplayClass4_0.<UseMiddleware>b__0(RequestDelegate next)
Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder.Build()
Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication() public async Task Invoke(HttpContext context, IIP iip)
{
} using System;
using System.Threading;
using System.Threading.Tasks;
namespace MyApp
{
public class AsyncLock : IDisposable
{
private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);
public async Task<AsyncLock> LockAsync()
{
await _semaphoreSlim.WaitAsync();
return this;
}
public void Dispose()
{
_semaphoreSlim.Release();
}
}
} private static readonly AsyncLock _mutex = new AsyncLock();
using(await _mutex.LockAsync())
{
// Critical section... You can await here!
} [OutputCache(Duration = 60, VaryByParam = "none")]
[ServiceFilter(typeof(LockFilter(Duration = 60, VaryByParam = "none" )))]
public class ModelStateFeatureFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var state = context.ModelState;
// store state ...
await next();
}
} یک نکتهی تکمیلی: کار با اینترفیس IMiddleware جهت تعریف میانافزارهای سفارشی
اگر امروز قصد تعریف میانافزارهای سفارشی را دارید، بهتر است از روش باز public async Task Invoke(HttpContext context) که در این مطلب معرفی شد، دیگر استفاده نکنید؛ چون مهمترین محدودیتهای آن، داشتن طول عمر Singleton غیرقابل تغییر و همچنین عدم امکان پیاده سازی اینترفیس IDisposable در آن جهت پاکسازی خودکار منابع است. امروز روش توصیه شده، استفاده از اینترفیس IMiddleware است. در این حالت متد Task Invoke فوق، به متد مشخص و ثابت Task InvokeAsync(HttpContext context, RequestDelegate next) تغییر میکند. چون در این حالت دیگر نمیتوان پارامترهای این متد مشخص را مانند قبل که اینترفیسی را پیاده سازی نمیکرد، به صورت پویا کم و زیاد کرد، میتوان سرویسهای مدنظر را به سازندهی کلاس، تزریق کرد. به همین جهت نیاز است، آنرا به نحو زیر به سیستم تزریق وابستگیها معرفی کرد:
builder.Services.AddTransient<MyNewMiddleware>();
این الزام به تعریف آن به صورت یک سرویس رسمی، مزیتهای زیر را به همراه دارد:
الف) میتوان طول عمری، غیر از Singleton را هم در صورت نیاز، تعریف کرد (و مشکل کار با سرویسهایی با طول عمرهای غیر از Singleton کمتر میشود).
ب) چون طول عمر این میانافزار اکنون توسط سیستم تزریق وابستگیها مدیریت میشود، اگر این میانافزار اینترفیس IDisposable را پیاده سازی کند، کار پاکسازی منابع آن خودکار خواهد شد.
نکته 1: روش معرفی آن به سیستم تزریق وابستگیها، به صورت Concrete type است؛ یعنی اصل کلاس باید معرفی شود (مانند سطر فوق) و نه اینکه به صورت متداول زیر به همراه ذکر اینترفیس IMiddleware باشد:
builder.Services.AddTransient<IMiddleware, MyNewMiddleware>();
مابقی کار با آن، با میانافزارهای متداول، تفاوتی ندارد. یعنی قسمت UseMiddleware آن یکی است:
app.UseMiddleware<MyNewMiddleware>();
بنابراین این روش نسبت به روش متداول قبلی، دو تفاوت پیاده سازی اینترفیس مشخص IMiddleware و ثبت کلاس آن به صورت یک سرویس رسمی را دارد؛ مابقی نکات آن، مانند قبل است.
نکته 2: اگر از Scrutor برای ثبت خودکار سرویسهای برنامه استفاده میکنید، روش ثبت خودکار اینگونه سرویسها به صورت زیر و با استفاده از متد ()AsSelf است:
services.Scan(scan => scan.FromAssembliesOf(typeof(IDataSeedersRunner))
.AddClasses(classes => classes.Where(type =>
{
var allInterfaces = type.GetInterfaces();
return allInterfaces.Contains(typeof(IMiddleware)) &&
allInterfaces.Contains(typeof(ISingletonService));
}))
.AsSelf()
.WithSingletonLifetime());در این مثال، تمام IMiddleware هایی که با نشانگر ISingletonService هم مزین شدهاند، یافت شده و به صورت Concrete type هایی، با طول عمر Singleton، به سیستم تزریق وابستگیها اضافه میشوند.