در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 3 - Middleware چیست؟» با اصول مقدماتی Middlewareها آشنا شدیم. همچنین در مطلب «آشنایی با OWIN و بررسی نقش آن در ASP.NET Core» یک مثال سفارشی از آنها، بررسی شد. در اینجا میخواهیم نکات بیشتری را در مورد تهیهی Middlewareهای سفارشی بررسی کنیم.
تفاوت بین متدهای app.Use و app.Run در چیست؟
Middlewareها به همان ترتیبی که در متد Configure کلاس آغازین برنامه معرفی میشوند، اجرا خواهند شد؛ اما نکتهی مهم اینجا است که middleware ایی که توسط متد app.Use تعریف میشود، میتواند middleware بعدی ثبت شدهرا، فراخوانی کند؛ اما app.Run خیر. برای درک بهتر این مفهوم، به مثال زیر دقت کنید:
اگر در این حالت برنامه را اجرا کنیم، چنین خروجی را مشاهده خواهیم کرد:
همانطور که در تصویر نیز مشخص است، ابتدا کدهای پیش از فراخوانی دلیگیت next میانافزار اول اجرا شدهاست. سپس باتوجه به فراخوانی دلیگیت next، کدهای دومین میانافزار ثبت شده، فراخوانی گردیدهاست و سپس کدهای پس از فراخوانی دلیگیت next میانافزار اول، اجرا شدهاند.
این دلیگیت در اصل یک چنین امضایی را دارد:
در اینجا چون میانافزار دوم از نوع app.Run است و قابلیت فراخوانی دلیگیت next را ندارد، از نوع terminal یا خاتمه دهنده بهشمار آمده و دیگر میان افزار بعدی ثبت شده، یعنی میانافزار سوم، اجرا نخواهد شد و کار پردازش برنامه در همین مرحله به پایان میرسد.
باید دقت داشت که فراخوانی دلیگیت next در میانافزارهای از نوع app.Use الزامی نبوده و اگر اینکار انجام نشود، بین app.Run و app.Use تفاوتی نخواهد بود و هر دو terminal به حساب میآیند.
تفاوت بین متدهایapp.Map و app.MapWhen در چیست؟
متد app.Map در صورت برآورده شدن شرطی، سبب اجرای میانافزاری مشخص میشود (امکان اجرای غیر خطی میانافزارها).
فرض کنید قطعه کد زیر را پس از اولین app.Use مثال فوق قرار دادهایم:
در این حالت اگر برنامه را اجرا کنیم، خروجی جدیدی را مشاهده نخواهیم کرد و خروجی حاصل دقیقا مانند تصویر مثال فوق است. اما اگر آدرس ویژهی dnt/ درخواست شود (الگوی تطابق اولین پارامتر متد Map)، آنگاه میان افزار ثبت شدهی app.Run ویژهی این حالت خاص، اجرا میشود:
در اینجا چون app.Run داخلی فراخوانی شده، از نوع terminal است، دیگر میان افزارهای پس از آن اجرا نشدهاند. بدیهی است در اینجا نیز میتوان به هر تعدادی که نیاز است میان افزارهای جدیدی را به appBuilder متد app.Map اضافه کرد.
پارامتر اول متد Map برای تطابق با الگوهایی خاص و مشخص، مناسب است. اما در اگر در اینجا نیاز به اطلاعات بیشتری از HttpContext جاری داشته باشیم، میتوانیم از متد app.MapWhen استفاده کنیم که اولین پارامتر آن یک دلیگیت است که HttpContext را در اختیار استفاده کننده قرار میدهد و اگر در نهایت true را دریافت کند، سبب اجرای میان افزارهای قسمت appBuilder آن خواهد شد:
در این مثال، شرط ارائه شدهی در پارامتر اول، اندکی پیچیدهتر است از حالت app.Map. در اینجا مشخص شدهاست که اگر آدرس دریافتی از کاربر، دارای کوئری استرینگی به نام dnt بود، آنگاه میان افزار(های) ارائه شدهی در قسمت appBuilder، اجرا شود. برای نمونه درخواست آدرس ذیل، سبب فراخوانی appBuilder.Run ذکر شده میشود:
نظم بخشیدن به تعاریف میانافزارها
متدهای app.Run و app.Use و امثال آنها برای تعریف سریع میان افزارها مناسب هستند. اما اگر بخواهیم کدهای کلاس آغازین برنامه را اندکی خلوت کرده و به تعاریف میانافزارها نظم ببخشیم، میتوان کدهای آنها را به کلاسهایی با امضایی خاص منتقل کرد:
در اینجا نحوهی تعریف یک کلاس میانافزار سفارشی را مشاهده میکنید. دو پارامتر context و next ایی را که در متد app.Use مشاهده کردید، دراینجا به نحو واضحتری مشخص شدهاند. دلیگیت next اشاره کنندهی به اجرای میان افزار بعدی، در سازندهی کلاس این میان افزار تزریق شدهاست. همچنین در اینجا میتوان سرویسهایی را که به IoC Container توکار ASP.NET Core معرفی کردهایم نیز تزریق کنیم و از این لحاظ محدودیتی ندارد (و همچنین امضای آن میتواند کاملا متغیر باشد). قسمت اصلی اجرایی این میان افزار، متد Invoke آن است که اطلاعات HttpContext جاری را در اختیار مصرف کننده قرار میدهد.
در اینجا نیز اگر دلیگیت next_ فراخوانی نشود، این میانافزار سبب خاتمهی اجرای پردازش درخواست جاری میگردد.
مرحلهی بعد، روش معرفی این میانافزار تعریف شده، به لیست میانافزارهای موجود است. برای این منظور میتوان متد app.UseMiddleware را به صورت مستقیم در کلاس آغازین برنامه فراخوانی کرد و یا مرسوم است اگر کتابخانهای را طراحی کردهاید، به نحو ذیل متد الحاقی خاصی را برای آن تدارک دید:
سپس مصرف کننده تنها باید این متد را در کلاس آغازین برنامه فراخوانی کند:
تفاوت بین متدهای app.Use و app.Run در چیست؟
Middlewareها به همان ترتیبی که در متد Configure کلاس آغازین برنامه معرفی میشوند، اجرا خواهند شد؛ اما نکتهی مهم اینجا است که middleware ایی که توسط متد app.Use تعریف میشود، میتواند middleware بعدی ثبت شدهرا، فراخوانی کند؛ اما app.Run خیر. برای درک بهتر این مفهوم، به مثال زیر دقت کنید:
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>"); });
همانطور که در تصویر نیز مشخص است، ابتدا کدهای پیش از فراخوانی دلیگیت next میانافزار اول اجرا شدهاست. سپس باتوجه به فراخوانی دلیگیت next، کدهای دومین میانافزار ثبت شده، فراخوانی گردیدهاست و سپس کدهای پس از فراخوانی دلیگیت next میانافزار اول، اجرا شدهاند.
این دلیگیت در اصل یک چنین امضایی را دارد:
public delegate Task RequestDelegate(HttpContext context);
باید دقت داشت که فراخوانی دلیگیت next در میانافزارهای از نوع app.Use الزامی نبوده و اگر اینکار انجام نشود، بین app.Run و app.Use تفاوتی نخواهد بود و هر دو terminal به حساب میآیند.
تفاوت بین متدهایapp.Map و app.MapWhen در چیست؟
متد app.Map در صورت برآورده شدن شرطی، سبب اجرای میانافزاری مشخص میشود (امکان اجرای غیر خطی میانافزارها).
فرض کنید قطعه کد زیر را پس از اولین app.Use مثال فوق قرار دادهایم:
app.Map("/dnt", appBuilder => { appBuilder.Run(async context => { await context.Response.WriteAsync(@"<div>Inside Map(/dnt) --> Run</div>"); }); });
در اینجا چون app.Run داخلی فراخوانی شده، از نوع terminal است، دیگر میان افزارهای پس از آن اجرا نشدهاند. بدیهی است در اینجا نیز میتوان به هر تعدادی که نیاز است میان افزارهای جدیدی را به appBuilder متد app.Map اضافه کرد.
پارامتر اول متد Map برای تطابق با الگوهایی خاص و مشخص، مناسب است. اما در اگر در اینجا نیاز به اطلاعات بیشتری از HttpContext جاری داشته باشیم، میتوانیم از متد app.MapWhen استفاده کنیم که اولین پارامتر آن یک دلیگیت است که HttpContext را در اختیار استفاده کننده قرار میدهد و اگر در نهایت true را دریافت کند، سبب اجرای میان افزارهای قسمت appBuilder آن خواهد شد:
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
نظم بخشیدن به تعاریف میانافزارها
متدهای app.Run و app.Use و امثال آنها برای تعریف سریع میان افزارها مناسب هستند. اما اگر بخواهیم کدهای کلاس آغازین برنامه را اندکی خلوت کرده و به تعاریف میانافزارها نظم ببخشیم، میتوان کدهای آنها را به کلاسهایی با امضایی خاص منتقل کرد:
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>"); } } }
در اینجا نیز اگر دلیگیت next_ فراخوانی نشود، این میانافزار سبب خاتمهی اجرای پردازش درخواست جاری میگردد.
مرحلهی بعد، روش معرفی این میانافزار تعریف شده، به لیست میانافزارهای موجود است. برای این منظور میتوان متد app.UseMiddleware را به صورت مستقیم در کلاس آغازین برنامه فراخوانی کرد و یا مرسوم است اگر کتابخانهای را طراحی کردهاید، به نحو ذیل متد الحاقی خاصی را برای آن تدارک دید:
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();