با سلام؛ بنده یک پروژه webapi ساختم که فرایند لاگین مشابه شما را انجام دادهام و یک پروژه سمت کلاینت دارم که asp.net core هست. الان برای ارسال درخواست، توکن رو به header میدم و درخواستم ارسال میکنم. اما پاسخ BadRequest هست
اینجا قسمتی هست که درخواست سمت api ارسال میشه. ممنون میشم بهم بگین که کجا ی کار ایراد داره!
نظرات مطالب
AngularJS #2
خود angularjs دارای یک سری api قدرتمند برای اعتبار سنجی هستند. البته انتظار helperهای شسته رفته asp.net mvc را نداشته باشید که بر اساس model بتواند اعتبار سنجی سمت کلاینت کند.
باید برایش خودتان دستی کد بنویسید. البته فکر نکنم تهیهی helper برای angular که بر اساس model، کدهای متناظر اعتبار سنجی را تولید کند کار مشکلی باشد.
- Eloquent JavaScript – کتاب رایگان و تعاملی آموزش جاوااسکریپت | potatozone.com
- رسم نمودار با گوگل | potatozone.com
- The World According to LINQ - ACM Queue | queue.acm.org
- Advanced SQL Server Monitor | www.codeproject.com
- کنترل ASP.net SplitButton | nimabaghdadi.ir
- معرفی Roslyn Scripting API | blogs.msdn.com
استفاده از سرویسهای متنوع گوگل همگی با یک آکانت واحد، ایدهی جالبی است که پایهی ایجاد پروژهای به نام IdentityServer بوده است.
IdentityServer یک پروژهی متن باز است که قرار بود و شاید هنوز هم هست که بخشی از ویژوال استودیو باشد.
این پروژه یک سرور واحد برای مدیریت هویت ایجاد میکند که تمام کلاینتها از این سرور اهراز هویت شده و سپس از سرویسها استفاده میکنند. یعنی بخش مدیریت هویت تمام کاربران در پروژه برعهدهی IdentityServer گذاشته میشود و همهی برنامهها هویت کاربران را از IdentityServer می پرسند.
تصویری که توسعه دهندگان این پروژه برای معماری پروژه خود ارائه دادهاند:
برای استفاده از آیدنتیتیسرور، ابتدا آن را از مخزن گیتهاب، بارگذاری میکنیم و سپس برای پروژه، یک Application جدید در IIS ایجاد میکنیم.
دقت داشته باشید که IdentityServer از SSL و بستر امن استفاده میکند که تنظیمات ساخت Certificate را میتوانید از اینجا فرا بگیرید.
با توجه به پشتیبانی گستردهی این پروژه از OpenID و OAuth2 مطالعهای مختصر در این موارد به درک فرایند اهراز هویت توسط آی دنتیتی سرور بسیار کمک خواهد کرد.
پس از راه اندازی SSL و تنظیمات Certificate مربوط به آن میتوانید شروع به راه اندازی سرور خود کنید. راه اندازی اولیه سرور تنظیمات مربوط به بانک اطلاعاتی، نکات ریزی دارد که بخش کلی از آن اینجا آمده است.
برای شروع به استفاده از سرور و درک چگونگی عملکرد آن نیاز دارید تا مدیر سرور را نصب کنید؛ تا هم بتوانید کاربر تعریف کنید و هم نقشها (Roles) و دسترسیها را مدیریت کنید. نگارش مدیر آیدنتیتیسرور نیز از اینجا قابل دسترسی میباشد.
پس از نصب آیدنتیتیسرور باید تنظیمات مربوط به ذخیره سازی اطلاعات آن را انجام دهید که آیدنتیتیسرور پیاده سازی خوبی برای Entity Framework دارد که میتوانید با نصب آن همهی کارهای ذخیره سازی در بانک اطلاعاتی را به EF بسپارید. البته برای ذخیرهی یوزر میتوان از حالت InMemory هم استفاده کرد که در نسخهی مثال یک کاربر با نام bob و رمز bob در داخل کد نویسی تعریف شده بود، که میتوان برای پروژههای کوچک همان را توسعه داد.
در سطح بانک هم آیدنتیتیسرور از دو بانک اطلاعاتی، استفاده میکند؛ یکی برای ذخیرهی تنظیمات سرور و دیگری برای ذخیرهی اطلاعات هویتی.
مزیت بزرگ آیدنتیتیسرور در اعتبار سنجی جدای از پیاده سازیهای فراوان انجام شده برای محیطهای مختلف و در قابلیت اعتبار سنجی دو طرفهی آن میباشد.
یعنی هم سمت سرور و هم سمت کلاینت و هم سرویسهایی که از آیدنتیتیسرور استفاده میکنند، باید اعتبار سنجی شوند و همه چیز به یک رمز و نام کاربری ساده خلاصه نمیشود.
در زمان نگارش این متن، نسخهی 2 نسخهی پایدار ارائه شده است و نسخهی سه در مرحله تست میباشد. البته پیاده سازیهایی هم از نسخهی 3 در محیطهای مختلف مثل MVC و WEB API ارائه شده است؛ ولی هنوز به پایداری لازم نرسیده است.
ASP.NET Core 8x به همراه یک IResult جدید بهنام RazorComponentResult است که توسط آن میتوان در Endpointهای Minimal-API و همچنین اکشن متدهای MVC، از کامپوننتهای Blazor، خروجی گرفت. این خروجی نه فقط static یا به عبارتی SSR، بلکه حتی میتواند تعاملی هم باشد. در این مطلب، جزئیات فعالسازی و استفاده از این IResult جدید را در یک برنامهی Minimal-API بررسی میکنیم.
ایجاد یک برنامهی Minimal-API جدید در دات نت 8
پروژهای را که در اینجا پیگیری میکنیم، بر اساس قالب استاندارد تولید شدهی توسط دستور dotnet new webapi تکمیل میشود.
ایجاد یک صفحهی Blazor 8x به همراه مسیریابی و دریافت پارامتر
در ادامه قصد داریم که یک کامپوننت جدید را به نام SsrTest.razor در پوشهی جدید Components\Tests ایجاد کرده و برای آن مسیریابی از نوع page@ هم تعریف کنیم. یعنی نهفقط قصد داریم آنرا توسط RazorComponentResult رندر کنیم، بلکه میخواهیم اگر آدرس آنرا در مرورگر هم وارد کردیم، قابل دسترسی باشد.
به همین جهت یک پوشهی جدید را به نام Components در ریشهی پروژهی Web API جاری ایجاد میکنیم، با این محتوا:
برای ایده گرفتن از محتوای مورد نیاز، به «معرفی قالبهای جدید شروع پروژههای Blazor در دات نت 8» قسمت دوم این سری مراجعه کرده و برای مثال قالب سادهترین حالت ممکن را توسط دستور زیر تولید میکنیم (در یک پروژهی مجزا، خارج از پروژهی جاری):
پس از اینکار، محتویات پوشهی Components آنرا مستقیما داخل پوشهی پروژهی Minimal-API جاری کپی میکنیم. یعنی در نهایت در این پروژهی جدید Web API، به فایلهای زیر میرسیم:
- فایل Imports.razor_ ساده شده برای سهولت کار با فضاهای نام در کامپوننتهای Blazor (فضاهای نامی را که در آن وجود ندارند و مرتبط با پروژهی دوم هستند، حذف میکنیم).
- فایل App.razor، برای تشکیل نقطهی آغازین برنامهی Blazor.
- فایل Routes.razor برای معرفی مسیریابی صفحات Blazor تعریف شده.
- پوشهی Layout برای معرفی فایل MainLayout.razor که در Routes.razor استفاده شدهاست.
و ... یک فایل آزمایشی جدید به نام Components\Tests\SsrTest.razor با محتوای زیر:
این فایل، میتواند پارامتر Data را از طریق فراخوانی مستقیم آدرس فرضی http://localhost:5227/ssr-page/2 دریافت کند و یا ... از طریق خروجی جدید RazorComponentResult که توسط یک Endpoint سفارشی ارائه میشود:
تغییرات مورد نیاز در فایل Program.cs برنامهی Web-API برای فعالسازی رندر سمت سرور Blazor
در ادامه کل تغییرات مورد نیاز جهت اجرای این برنامه را مشاهده میکنید:
توضیحات:
- همین اندازه تغییر در جهت فعالسازی رندر سمت سرور کامپوننتهای Blazor در یک برنامهی ASP.NET Core کفایت میکند. یعنی اضافه شدن:
AddRazorComponents ،UseAntiforgery و MapRazorComponents
- در اینجا نحوهی ارسال پارامترها را به یک RazorComponentResult نیز مشاهده میکنید.
- در حالت فراخوانی از طریق مسیر endpoint (یعنی فراخوانی مسیر http://localhost:5227/ssr-component در مثال فوق)، خود کامپوننت فراخوانی شده، بدون layout تعریف شدهی در فایل App.razor، رندر میشود. علت اینجا است که layout برنامه به همراه کامپوننت Router و RouteView آن فعال میشود که این دو هم مختص به صفحات دارای مسیریابی Blazor هستند و برای رندر کامپوننتهای خالص آن بکار گرفته نمیشوند. خروجی RazorComponentResult تنها یک static SSR خالص است؛ مگر اینکه فایل blazor.web.js را نیز بارگذاری کند.
یک نکته: اگر در حالت رندر توسط RazorComponentResult، علاقمند به استفادهی از layout هستید، میتوان از کامپوننت LayoutView داخل یک کامپوننت فرضی به صورت زیر استفاده کرد؛ اما این مورد هم شامل اطلاعات فایل App.razor نمیشود:
سؤال: آیا در این حالت کامپوننتهای تعاملی هم کار میکنند؟
پاسخ: بله. فقط برای ایده گرفتن، یک نمونه پروژهی تعاملی Blazor 8x را در ابتدا ایجاد کنید و قسمتهای اضافی AddRazorComponents و MapRazorComponents آنرا در اینجا کپی کنید؛ یعنی برای مثال جهت فعالسازی کامپوننتهای تعاملی Blazor Server، به این دو تغییر زیر نیاز است:
همچنین باید دقت داشت که امکانات تعاملی، به دلیل وجود و دسترسی به یک سطر ذیل که در فایل Components\App.razor واقع شده، اجرایی میشوند:
و همانطور که عنوان شد، اگر از روش new RazorComponentResult استفاده میشود، باید این سطر را به صورت دستی اضافهکرد؛ چون به همراه رندر layout تعریف شدهی در فایل App.razor نیست. برای مثال فرض کنید کامپوننت معروف Counter را به صورت زیر داریم که حالت رندر آن به InteractiveServer تنظیم شدهاست:
در این حالت پس از تعریف endpoint زیر، خروجی آن فقط یک صفحهی استاتیک SSR خواهد بود و دکمهی Click me آن کار نمیکند:
علت اینجا است که اگر به سورس HTML رندر شده مراجعه کنیم، خبری از درج اسکریپت blazor.web.js در انتهای آن نیست. به همین جهت برای مثال فایل جدید CounterInteractive.razor را به صورت زیر اضافه میکنیم که ساختار آن شبیه به فایل App.razor است:
و هدف اصلی از آن، تشکیل یک قالب و درج اسکریپت blazor.web.js در انتهای آن است.
سپس تعریف endpoint متناظر را به صورت زیر تغییر میدهیم:
اینبار به علت بارگذاری فایل blazor.web.js، امکانات تعاملی کامپوننت Counter فعال شده و قابل استفاده میشوند.
سؤال: آیا میتوان این خروجی static SSR کامپوننتهای بلیزر را در سرویسهای یک برنامه ASP.NET Core هم دریافت کرد؟
منظور این است که آیا میتوان از یک کامپوننت Blazor، به همراه تمام پیشرفتهای Razor در آن که در Viewهای MVC قابل دسترسی نیستند، بهشکل یک رشتهی خالص، خروجی گرفت و برای مثال از آن بهعنوان قالب پویای محتوای ایمیلها استفاده کرد؟
پاسخ: بله! زیر ساخت RazorComponentResult که از سرویس HtmlRenderer استفاده میکند، بدون نیاز به برپایی یک endpoint هم قابل دسترسی است:
برای کار با آن، ابتدا باید سرویس فوق را به صورت زیر ثبت و معرفی کرد:
و سپس یک نمونه مثال فرضی نحوهی تزریق و فراخوانی سرویس BlazorStaticRendererService به صورت زیر است که در آن روش ارسال پارامترها هم بررسی شدهاست:
کدهای کامل این مطلب را میتوانید از اینجا دریافت کنید: WebApi8x.zip
ایجاد یک برنامهی Minimal-API جدید در دات نت 8
پروژهای را که در اینجا پیگیری میکنیم، بر اساس قالب استاندارد تولید شدهی توسط دستور dotnet new webapi تکمیل میشود.
ایجاد یک صفحهی Blazor 8x به همراه مسیریابی و دریافت پارامتر
در ادامه قصد داریم که یک کامپوننت جدید را به نام SsrTest.razor در پوشهی جدید Components\Tests ایجاد کرده و برای آن مسیریابی از نوع page@ هم تعریف کنیم. یعنی نهفقط قصد داریم آنرا توسط RazorComponentResult رندر کنیم، بلکه میخواهیم اگر آدرس آنرا در مرورگر هم وارد کردیم، قابل دسترسی باشد.
به همین جهت یک پوشهی جدید را به نام Components در ریشهی پروژهی Web API جاری ایجاد میکنیم، با این محتوا:
برای ایده گرفتن از محتوای مورد نیاز، به «معرفی قالبهای جدید شروع پروژههای Blazor در دات نت 8» قسمت دوم این سری مراجعه کرده و برای مثال قالب سادهترین حالت ممکن را توسط دستور زیر تولید میکنیم (در یک پروژهی مجزا، خارج از پروژهی جاری):
dotnet new blazor --interactivity None
- فایل Imports.razor_ ساده شده برای سهولت کار با فضاهای نام در کامپوننتهای Blazor (فضاهای نامی را که در آن وجود ندارند و مرتبط با پروژهی دوم هستند، حذف میکنیم).
- فایل App.razor، برای تشکیل نقطهی آغازین برنامهی Blazor.
- فایل Routes.razor برای معرفی مسیریابی صفحات Blazor تعریف شده.
- پوشهی Layout برای معرفی فایل MainLayout.razor که در Routes.razor استفاده شدهاست.
و ... یک فایل آزمایشی جدید به نام Components\Tests\SsrTest.razor با محتوای زیر:
@page "/ssr-page/{Data:int}" <PageTitle>An SSR component</PageTitle> <h1>An SSR component rendered by a Minimal-API!</h1> <div> Data: @Data </div> @code { [Parameter] public int Data { get; set; } }
تغییرات مورد نیاز در فایل Program.cs برنامهی Web-API برای فعالسازی رندر سمت سرور Blazor
در ادامه کل تغییرات مورد نیاز جهت اجرای این برنامه را مشاهده میکنید:
var builder = WebApplication.CreateBuilder(args); // ... builder.Services.AddRazorComponents(); // ... // http://localhost:5227/ssr-component?data=2 // or it can be called directly http://localhost:5227/ssr-page/2 app.MapGet("/ssr-component", (int data = 1) => { var parameters = new Dictionary<string, object?> { { nameof(SsrTest.Data), data }, }; return new RazorComponentResult<SsrTest>(parameters); }); app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>(); app.Run(); // ...
- همین اندازه تغییر در جهت فعالسازی رندر سمت سرور کامپوننتهای Blazor در یک برنامهی ASP.NET Core کفایت میکند. یعنی اضافه شدن:
AddRazorComponents ،UseAntiforgery و MapRazorComponents
- در اینجا نحوهی ارسال پارامترها را به یک RazorComponentResult نیز مشاهده میکنید.
- در حالت فراخوانی از طریق مسیر endpoint (یعنی فراخوانی مسیر http://localhost:5227/ssr-component در مثال فوق)، خود کامپوننت فراخوانی شده، بدون layout تعریف شدهی در فایل App.razor، رندر میشود. علت اینجا است که layout برنامه به همراه کامپوننت Router و RouteView آن فعال میشود که این دو هم مختص به صفحات دارای مسیریابی Blazor هستند و برای رندر کامپوننتهای خالص آن بکار گرفته نمیشوند. خروجی RazorComponentResult تنها یک static SSR خالص است؛ مگر اینکه فایل blazor.web.js را نیز بارگذاری کند.
یک نکته: اگر در حالت رندر توسط RazorComponentResult، علاقمند به استفادهی از layout هستید، میتوان از کامپوننت LayoutView داخل یک کامپوننت فرضی به صورت زیر استفاده کرد؛ اما این مورد هم شامل اطلاعات فایل App.razor نمیشود:
<LayoutView Layout="@typeof(MainLayout)"> <PageTitle>Home</PageTitle> <h2>Welcome to your new app.</h2> </LayoutView>
سؤال: آیا در این حالت کامپوننتهای تعاملی هم کار میکنند؟
پاسخ: بله. فقط برای ایده گرفتن، یک نمونه پروژهی تعاملی Blazor 8x را در ابتدا ایجاد کنید و قسمتهای اضافی AddRazorComponents و MapRazorComponents آنرا در اینجا کپی کنید؛ یعنی برای مثال جهت فعالسازی کامپوننتهای تعاملی Blazor Server، به این دو تغییر زیر نیاز است:
// ... builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); // ... app.MapRazorComponents<App>().AddInteractiveServerRenderMode(); // ...
<script src="_framework/blazor.web.js"></script>
@rendermode InteractiveServer <h1>Counter</h1> <p role="status">Current count: @_currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int _currentCount; private void IncrementCount() { _currentCount++; } }
// http://localhost:5227/server-interactive-component app.MapGet("/server-interactive-component", () => new RazorComponentResult<Counter>());
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Interactive server component</title> <base href="/"/> </head> <body> <h1>Interactive server component</h1> <Counter/> <script src="_framework/blazor.web.js"></script> </body> </html>
سپس تعریف endpoint متناظر را به صورت زیر تغییر میدهیم:
// http://localhost:5227/server-interactive-component app.MapGet("/server-interactive-component", () => new RazorComponentResult<CounterInteractive>());
سؤال: آیا میتوان این خروجی static SSR کامپوننتهای بلیزر را در سرویسهای یک برنامه ASP.NET Core هم دریافت کرد؟
منظور این است که آیا میتوان از یک کامپوننت Blazor، به همراه تمام پیشرفتهای Razor در آن که در Viewهای MVC قابل دسترسی نیستند، بهشکل یک رشتهی خالص، خروجی گرفت و برای مثال از آن بهعنوان قالب پویای محتوای ایمیلها استفاده کرد؟
پاسخ: بله! زیر ساخت RazorComponentResult که از سرویس HtmlRenderer استفاده میکند، بدون نیاز به برپایی یک endpoint هم قابل دسترسی است:
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; namespace WebApi8x.Services; public class BlazorStaticRendererService { private readonly HtmlRenderer _htmlRenderer; public BlazorStaticRendererService(HtmlRenderer htmlRenderer) => _htmlRenderer = htmlRenderer; public Task<string> StaticRenderComponentAsync<T>() where T : IComponent => RenderComponentAsync<T>(ParameterView.Empty); public Task<string> StaticRenderComponentAsync<T>(Dictionary<string, object?> dictionary) where T : IComponent => RenderComponentAsync<T>(ParameterView.FromDictionary(dictionary)); private Task<string> RenderComponentAsync<T>(ParameterView parameters) where T : IComponent => _htmlRenderer.Dispatcher.InvokeAsync(async () => { var output = await _htmlRenderer.RenderComponentAsync<T>(parameters); return output.ToHtmlString(); }); }
builder.Services.AddScoped<HtmlRenderer>(); builder.Services.AddScoped<BlazorStaticRendererService>();
app.MapGet("/static-renderer-service-test", async (BlazorStaticRendererService renderer, int data = 1) => { var parameters = new Dictionary<string, object?> { { nameof(SsrTest.Data), data }, }; var html = await renderer.StaticRenderComponentAsync<SsrTest>(parameters); return Results.Content(html, "text/html"); });
کدهای کامل این مطلب را میتوانید از اینجا دریافت کنید: WebApi8x.zip
API کار با کوکیها نیز در ASP.NET Core نسبت به نگارشهای دیگر تغییریافتهاست که در ادامه این موارد را بررسی خواهیم کرد. همچنین با کمک مطلب «تغییرات رمزنگاری اطلاعات در NET Core.» یک تامین کنندهی سفارشی کوکیهای رمزنگاری شده را نیز ایجاد میکنیم.
خلاصهای از روشهای کار با کوکیها در ASP.NET Core
ایجاد یک کوکی جدید
کوکی جدید را میتوان توسط متد Append مجموعهی کوکیها، به Response اضافه کرد:
همانطور که در تصویر نیز مشخص است، طول عمر این کوکی، به سشن تنظیم شدهاست و با پایان سشن جاری مرورگر (بسته شدن کل مرورگر)، این کوکی نیز غیرمعتبر شده و به صورت خودکار حذف خواهد شد. برای تعیین عمر دقیق یک کوکی میتوان از خاصیت Expires شیء CookieOptions که در مثال فوق مقدار دهی نشدهاست، استفاده کرد؛ مانند:
خواندن محتویات کوکی ذخیره شده
پس از ثبت کوکی در Response، خواندن آن در Request بعدی به شکل زیر است:
در این حالت اگر کلید درخواستی در مجموعهی کوکیها یافت نشد، نال بازگشت داده میشود.
شیء this.Request.Cookies از نوع IRequestCookieCollection است:
و همانطور که ملاحظه میکنید، برای دریافت مقدار یک کوکی یا میتوان از indexer آن مانند مثال فوق و یا از متد TryGetValue استفاده کرد.
در مستندات آن عنوان شدهاست که در حالت استفادهی از indexer، درصورت یافت نشدن کلید، string.Empty بازگشت داده میشود (که آزمایشات null را نمایش میدهند). اما در حالت استفادهی از TryGetValue بر اساس خروجی bool آن دقیقا میتوان مشخص کرد که آیا این کوکی وجود داشتهاست یا خیر.
در اینجا همچنین متد ContainsKey نیز جهت بررسی وجود یک کلید، در مجموعهی کلیدها نیز پیش بینی شدهاست.
بنابراین بهتر است جهت یافتن مقادیر کوکیها از روش ذیل استفاده کرد:
حذف کوکیهای موجود
در اینجا متد Delete نیز پیش بینی شدهاست که باید بر روی کوکیهای Response فراخوانی شود:
کار آن افزودن یک کوکی دیگر با همین کلید، اما منقضی شدهاست؛ تا مرورگر را مجبور به حذف آن کند. در اینجا در صورت نیاز میتوان به عنوان پارامتر دوم، CookieOptions این کوکی جدید را نیز تنظیم کرد.
همانطور که در تصویر نیز مشخص است، در صورت عدم تنظیم CookieOptions، این کوکی جدید اضافه شده، دارای تاریخ انقضای 1970 است که سبب خواهد شد تا توسط مرورگر، غیرمعتبر درنظر گرفته شده و حذف شود.
طراحی یک تامین کنندهی کوکیهای امن
پس از آشنایی با مقدمات کوکیها و همچنین «بررسی تغییرات رمزنگاری اطلاعات در NET Core.»، اکنون میتوان یک تامین کنندهی کوکیهای رمزنگاری شده را برای ASP.NET Core به نحو ذیل طراحی کرد:
- نکاتی را که در ابتدای مطلب در مورد ثبت و خواندن و حذف کوکیها مطالعه کردید، به این کلاس اضافه شدهاند. با این تغییر که پیش از ذخیرهی مقدار کوکی، این مقدار رمزنگاری میشود و همچنین پس از خواندن آن، رمزگشایی خواهد شد.
- در این تامین کنندهی کوکیهای امن، IProtectionProvider تزریقی به سازندهی کلاس را در مطلب «تغییرات رمزنگاری اطلاعات در NET Core.» پیشتر ملاحظه کردهاید.
- در اینجا برای ثبت سرویس جدید، تنظیمات ابتدایی برنامه چنین شکلی را پیدا میکنند و پس از آن میتوان سرویس ISecureCookiesProvider را به کنترلرهای برنامه تزریق و استفاده کرد:
- چون در کلاس SecureCookiesProvider، خاصیت Expires تنظیم نشدهاست، طول عمر این کوکیها محدود است به مدت زمان باز بودن مرورگر. بنابراین در صورت نیاز این مورد را تغییر دهید.
خلاصهای از روشهای کار با کوکیها در ASP.NET Core
ایجاد یک کوکی جدید
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Core1RtmEmptyTest.Controllers { public class TestCookiesController : Controller { public IActionResult Index() { this.Response.Cookies.Append("key", "value", new CookieOptions { HttpOnly = true, Path = this.Request.PathBase.HasValue ? this.Request.PathBase.ToString() : "/", Secure = this.Request.IsHttps }); return Content("OK!"); } } }
همانطور که در تصویر نیز مشخص است، طول عمر این کوکی، به سشن تنظیم شدهاست و با پایان سشن جاری مرورگر (بسته شدن کل مرورگر)، این کوکی نیز غیرمعتبر شده و به صورت خودکار حذف خواهد شد. برای تعیین عمر دقیق یک کوکی میتوان از خاصیت Expires شیء CookieOptions که در مثال فوق مقدار دهی نشدهاست، استفاده کرد؛ مانند:
Expires = DateTimeOffset.UtcNow.AddDays(2)
خواندن محتویات کوکی ذخیره شده
پس از ثبت کوکی در Response، خواندن آن در Request بعدی به شکل زیر است:
var value = this.Request.Cookies["key"];
شیء this.Request.Cookies از نوع IRequestCookieCollection است:
public interface IRequestCookieCollection : IEnumerable<KeyValuePair<string, string>>, IEnumerable { string this[string key] { get; } ICollection<string> Keys { get; } bool ContainsKey(string key); bool TryGetValue(string key, out string value); }
در مستندات آن عنوان شدهاست که در حالت استفادهی از indexer، درصورت یافت نشدن کلید، string.Empty بازگشت داده میشود (که آزمایشات null را نمایش میدهند). اما در حالت استفادهی از TryGetValue بر اساس خروجی bool آن دقیقا میتوان مشخص کرد که آیا این کوکی وجود داشتهاست یا خیر.
در اینجا همچنین متد ContainsKey نیز جهت بررسی وجود یک کلید، در مجموعهی کلیدها نیز پیش بینی شدهاست.
بنابراین بهتر است جهت یافتن مقادیر کوکیها از روش ذیل استفاده کرد:
string cookieValue; if (this.Request.Cookies.TryGetValue(key, out cookieValue)) { // TODO: use the cookieValue } else { // this cookie doesn't exist. }
حذف کوکیهای موجود
در اینجا متد Delete نیز پیش بینی شدهاست که باید بر روی کوکیهای Response فراخوانی شود:
this.Response.Cookies.Delete("key");
همانطور که در تصویر نیز مشخص است، در صورت عدم تنظیم CookieOptions، این کوکی جدید اضافه شده، دارای تاریخ انقضای 1970 است که سبب خواهد شد تا توسط مرورگر، غیرمعتبر درنظر گرفته شده و حذف شود.
طراحی یک تامین کنندهی کوکیهای امن
پس از آشنایی با مقدمات کوکیها و همچنین «بررسی تغییرات رمزنگاری اطلاعات در NET Core.»، اکنون میتوان یک تامین کنندهی کوکیهای رمزنگاری شده را برای ASP.NET Core به نحو ذیل طراحی کرد:
public interface ISecureCookiesProvider { void Add(HttpContext context, string token, string value); bool Contains(HttpContext context, string token); string GetValue(HttpContext context, string token); void Remove(HttpContext context, string token); } public class SecureCookiesProvider : ISecureCookiesProvider { private readonly IProtectionProvider _protectionProvider; public SecureCookiesProvider(IProtectionProvider protectionProvider) { _protectionProvider = protectionProvider; } public void Add(HttpContext context, string token, string value) { value = _protectionProvider.Encrypt(value); context.Response.Cookies.Append(token, value, getCookieOptions(context)); } public bool Contains(HttpContext context, string token) { return context.Request.Cookies.ContainsKey(token); } public string GetValue(HttpContext context, string token) { string cookieValue; if (!context.Request.Cookies.TryGetValue(token, out cookieValue)) { return null; } return _protectionProvider.Decrypt(cookieValue); } public void Remove(HttpContext context, string token) { if (context.Request.Cookies.ContainsKey(token)) { context.Response.Cookies.Delete(token); } } /// <summary> /// Expires at the end of the browser's session. /// </summary> private CookieOptions getCookieOptions(HttpContext context) { return new CookieOptions { HttpOnly = true, Path = context.Request.PathBase.HasValue ? context.Request.PathBase.ToString() : "/", Secure = context.Request.IsHttps }; } }
- در این تامین کنندهی کوکیهای امن، IProtectionProvider تزریقی به سازندهی کلاس را در مطلب «تغییرات رمزنگاری اطلاعات در NET Core.» پیشتر ملاحظه کردهاید.
- در اینجا برای ثبت سرویس جدید، تنظیمات ابتدایی برنامه چنین شکلی را پیدا میکنند و پس از آن میتوان سرویس ISecureCookiesProvider را به کنترلرهای برنامه تزریق و استفاده کرد:
public class Startup { public void ConfigureServices(IServiceCollection services) { services.TryAddSingleton<IProtectionProvider, ProtectionProvider>(); services.TryAddSingleton<ISecureCookiesProvider, SecureCookiesProvider>();
بحث اصلی مطلب جاری این است که NET Core. (پیاده سازی) با NET Standard. (قرارداد) یکی نیست. NET Core. یکی از پیاده سازیهای NET Standard. است؛ مانند دات نت 4.6.1 که آن هم پیاده سازی کنندهی دات نت استاندارد 2 است. هر دوی اینها دارای یک سری API خاص خودشان هم هستند که در NET Standard. وجود ندارند. سطح API دات نت Core هم بیشتر است از NET Standard. و در یکسری از موارد هم کاملا ناسازگار با دات نت کامل. بنابراین توافقی که در اینجا وجود دارد، صرفا پیاده سازی قرارداد NET Standard. است و بیشتر از آن هم مربوط است به توسعه دهندگان آن سکوی کاری خاص که الزامی به یکی بودن آنها نیست.
کاری که پیشتر میخواستند انجام دهند، محدود کردن ASP.NET Core به سطح API موجود در NET Core. بود (NET Core 2.0 only. یا تصویر زیر) و نه صرفا به NET Standard. . این مساله برای توسعه دهندگان ASP.NET Core میتوانست فوق العاده باشد؛ چون سطح API بیشتر و بهروزتری را در اختیارشان قرار میداد و اگر قابلیتی در NET Standard. وجود نداشت، نمیبایستی درخواست میدادند تا اضافه شود تا بعد بتوانند استفاده کنند.
اما چون این مساله و سطح API بیشتر و گاهی از اوقات کاملا متفاوت، سازگاری با کتابخانههایی را که در میدان دید این API قرار نمیگرفتند، زیر سؤال میبرد، ارتقاء برنامههای قبلی را با مشکل مواجه میکرد. به همین جهت تصمیم گرفتهاند که ASP.NET Core را سازگار با دات نت فریم ورک کامل نیز ارائه دهند و بنابراین «محدودش کنند» به NET Standard 2.0. و نه حالت NET Core only. قبلی: اطلاعات بیشتر
به علاوه باید درنظر داشت که امکان اضافه کردن یک بستهی نیوگت از یک کتابخانهی نوشته شدهی برای دات نت کامل در برنامههای دات نت Core به معنای تضمینی برای کار کردن آن در زمان اجرا نخواهد بود. از این جهت که دات نت کامل، به همراه قسمتهایی است که در NET Standard. وجود خارجی ندارند. بنابراین اگر کتابخانهی استفاده شده صرفا این API مشترک را هدف قرار دادهاست، هم قابلیت اتصال و هم قابلیت اجرا را خواهد داشت؛ اما اگر برای مثال کسی بستهی NServiceBus را به پروژهی ASP.NET Core 2.0 اضافه کند، بدون مشکل کامپایل خواهد شد. اما از آنجائیکه این کتابخانه از MSMQ استفاده میکند که خارج از میدان دید این استاندارد است، در زمان اجرا با شکست مواجه خواهد شد.
کاری که پیشتر میخواستند انجام دهند، محدود کردن ASP.NET Core به سطح API موجود در NET Core. بود (NET Core 2.0 only. یا تصویر زیر) و نه صرفا به NET Standard. . این مساله برای توسعه دهندگان ASP.NET Core میتوانست فوق العاده باشد؛ چون سطح API بیشتر و بهروزتری را در اختیارشان قرار میداد و اگر قابلیتی در NET Standard. وجود نداشت، نمیبایستی درخواست میدادند تا اضافه شود تا بعد بتوانند استفاده کنند.
اما چون این مساله و سطح API بیشتر و گاهی از اوقات کاملا متفاوت، سازگاری با کتابخانههایی را که در میدان دید این API قرار نمیگرفتند، زیر سؤال میبرد، ارتقاء برنامههای قبلی را با مشکل مواجه میکرد. به همین جهت تصمیم گرفتهاند که ASP.NET Core را سازگار با دات نت فریم ورک کامل نیز ارائه دهند و بنابراین «محدودش کنند» به NET Standard 2.0. و نه حالت NET Core only. قبلی: اطلاعات بیشتر
به علاوه باید درنظر داشت که امکان اضافه کردن یک بستهی نیوگت از یک کتابخانهی نوشته شدهی برای دات نت کامل در برنامههای دات نت Core به معنای تضمینی برای کار کردن آن در زمان اجرا نخواهد بود. از این جهت که دات نت کامل، به همراه قسمتهایی است که در NET Standard. وجود خارجی ندارند. بنابراین اگر کتابخانهی استفاده شده صرفا این API مشترک را هدف قرار دادهاست، هم قابلیت اتصال و هم قابلیت اجرا را خواهد داشت؛ اما اگر برای مثال کسی بستهی NServiceBus را به پروژهی ASP.NET Core 2.0 اضافه کند، بدون مشکل کامپایل خواهد شد. اما از آنجائیکه این کتابخانه از MSMQ استفاده میکند که خارج از میدان دید این استاندارد است، در زمان اجرا با شکست مواجه خواهد شد.
نظرات مطالب
مقدمهای بر تزریق وابستگیها درASP.NET Core
موردی که مدنظر شما است تزریق وابستگیها نام ندارد و الگوی service locator است. اگر بخواهید از آن در ASP.NET Core استفاده کنید، نظرات و نکات تکمیلی مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 6 - سرویسها و تزریق وابستگیها» را مطالعه کنید و خصوصا عبارات « GetService » و یا « GetRequiredService » را در آن جستجو کنید؛
مانند Request.HttpContext.RequestServices.GetService.