سایتهای بسیاری خودشان را با این الگو وفق دادهاند. برای نمونه Twitter و Github از مفهوم pjax استفادهی وسیعی دارند. برای نمونه، layout یا master page یک سایت را درنظر بگیرید. به ازای مرور هر صفحه، یکبار باید تمام قسمتهای تکراری layout از سرور بارگذاری شوند. توسط pjax به سرور اعلام میکنیم، ما تنها نیاز به body صفحات را داریم و نه کل صفحه را. همچنین اگر مرورگر از جاوا اسکریپت استفاده نمیکند، لطفا کل صفحه را همانند گذشته بازگشت بده. به علاوه مسایل سمت کلاینت مانند تغییر آدرس مرورگر و تغییر عنوان صفحه نیز به صورت خودکار مدیریت شوند. این تکنیک را دقیقا در حین مرور مخزنهای کد Github میتوانید مشاهده کنید. فقط قسمتی که لیست فایلها را ارائه میدهد، از سرور دریافت میگردد و نه کل صفحه.
بکارگیری pjax در ASP.NET MVC
مطابق توضیحاتی که ارائه شد، برای پیاده سازی سازی pjax نیاز به دو فایل layout داریم. یکی برای حالت ajax ایی و دیگری برای حالت بارگذاری کامل صفحه. حالت ajax ایی آن تنها از رندرکردن body پشتیبانی میکند؛ و نه ارائه تمام قسمتهای صفحه مانند هدر، فوتر، منوها و غیره. بنابراین خواهیم داشت:
الف) تعریف فایلهای layout سازگار با pjax
ابتدا یک فایل جدید را به نام _PjaxLayout.cshtml به پوشهی Shared اضافه کنید؛ با این محتوا:
<title>@ViewBag.Title</title> @RenderBody()
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <link href="~/Content/Site.css" rel="stylesheet" /> <script src="~/Scripts/jquery-1.8.2.min.js"></script> <script src="~/Scripts/jquery.pjax.js"></script> <script type="text/javascript"> $(function () { $(document).pjax('a[withpjax]', '#pjaxContainer', { timeout: 5000 }); }); </script> </head> <body> <div>Main layout ...</div> <div id="pjaxContainer"> @RenderBody() </div> </body> </html>
فایل layout اصلی سایت همانند قبل است. فقط RenderBody آن داخل یک div با id مساوی pjaxContainer قرار گرفته و از آن در فراخوانی افزونهی pjax استفاده شدهاست. همانطور که ملاحظه میکنید، مطابق تنظیمات ابتدای هدر layout، فقط لینکهایی که دارای ویژگی withpjax باشند، توسط pjax پردازش خواهند شد.
ب) تغییر فایل ViewStart برنامه
در فایل ViewStart، کار مقدار دهی layout پیش فرض صورت گرفتهاست. اکنون نیاز است این فایل را جهت معرفی layout دوم تعریف شده مخصوص pjax، اندکی ویرایش کنیم:
@{ if (Request.Headers["X-PJAX"] != null) { Layout = "~/Views/Shared/_PjaxLayout.cshtml"; } else { Layout = "~/Views/Shared/_Layout.cshtml"; } }
ج) آزمایش برنامه
using System.Web.Mvc; namespace PajxMvcApp.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult About() { return View(); } } }
سپس View متد Index را به نحو ذیل تغییر دهید:
@{ ViewBag.Title = "Index"; } <h2>Index</h2> @Html.ActionLink(linkText: "About", actionName:"About", routeValues: null, controllerName:"Home", htmlAttributes: new { withpjax = "with-pjax"})
اکنون اگر برنامه را اجرا کنید، چنین خروجی را در برگهی network آن مشاهده خواهید کرد:
همانطور که ملاحظه میکنید، با کلیک بر روی لینک About، یک درخواست pjax ایی به سرور ارسال شدهاست؛ به همراه هدرهای ویژه آن. هنوز قسمتهای اصلی layout سایت مشخص هستند (و مجددا از سرور درخواست نشدهاند). آدرس صفحه عوض شدهاست. به علاوه قسمت body آن تنها تغییر کردهاست.
این مثال را از اینجا نیز میتوانید دریافت کنید
PajxMvcApp.zip
برای مطالعه بیشتر
A Faster Web With PJAX
Favour PJAX over dynamically loaded partial views
What is PJAX and why
Pjax.Mvc
Using pjax with ASP.Net MVC3
Getting started with PJAX with ASP.NET MVC
ASP.NET MVC with PAjax or PushState/ReplaceState and Ajax
در زامارین 2 نوع layout داریم
FindViewbyid در پارامتر ورودی خود از طریق Resource.id، نام کنترل را دریافت نموده و بصورت Object باز میگرداند که بایستی نتیجه خروجی را به کلاس همان کنترل Cast نماییم:
Button btn = FindViewById<Button>(Resource.Id.button1);
FindViewById<EditText>(Resource.Id.txtname).Text = "";
هر layout یک اکتیویتی مربوط به خودش را دارد. اگر بخواهیم بین فرمهای مختلف حرکت کنیم، به ازای هر فرم، یک Activity کنترل کننده همان فرم را اضافه میکنیم. در یک Activity با ارسال سیگنالی به نام Intent که پارامتر اول آن، Activity مبدا یا This و پارامتر دوم آن، Activity مقصد میباشد. سپس به سیستم عامل اطلاع میدهیم که میخواهیم این سیگنال را ارسال کنیم و ارسال آن با StartActivity میباشد:
Intent _intent = new Intent(this, typeof(Activity2)); StartActivity(_intent);
ارسال پارامتر
جهت ارسال دادههای معمولی bool,double,string,float,long,... و آرایه ای از آن، از دستور PutExtra استفاده میشود:
Intent _intent = new Intent(this, typeof(Activity2)); string Test="Vlaueone"; _intent.PutExtra("mainactivity", Test); StartActivity(_intent);
گرفتن پارامتر
در Activity مقصد، اطلاعات مربوط به سیگنال ارسال شده، در مقادیر Global intent ذخیره میشود و بر اساس نوع داده ارسال شده، تابع GetExtra را مینویسیم:
string Getintent = Intent.GetStringExtra("mainactivity");
ارسال object از کلاسها بین Activityها
در زامارین، object کلاسها را به یک string استاندارد بصورت json تبدیل میکنیم و به این عملیات Serialization گفته میشود. این کار نیز توسط کتابخانههای مختلفی انجام میشود مانند NewtonSoft. سپس رشته json ایجاد شده را با تابع PutExtra، به همراه سیگنال Intent، به Activity دوم پاس میدهیم:
List<Product> products; products = new List<Product>(); Product p = new Product { name = FindViewById<EditText>(Resource.Id.mainedittextname).Text, price = Convert.ToInt32(FindViewById<EditText>(Resource.Id.mainedittextprice).Text) }; products.Add(p); Intent _intent = new Intent(this, typeof(Activity2)); _intent.PutExtra("products", JsonConvert.SerializeObject(products)); StartActivity(_intent);
var p = JsonConvert.DeserializeObject<List<Product>>(Intent.GetStringExtra("products")); foreach (var item in p) { Toast.MakeText(this, $" {item.name} , {item.price}", ToastLength.Long).Show(); };
در ادامه کلاسهای مورد نظر را جهت تعریف در دایرکتوری Model ایجاد میکنیم. مثل کلاس Product که بصورت Public نیز میباشد. از منوی Tools -> Nuget Package Management -> Manage nuget Package for Solution را انتخاب و سپس NewtonSoft را اضافه میکنیم.
AlertDialog زیر کلاس Dialog است که میتواند یک، دو و یا سه دکمه را نمایش دهد. اگر فقط میخواهید یک رشته را در این کادر محاوره ای نمایش دهید، از روش SetMessage استفاده کنید. قطعه کد زیر میتواند برای ایجاد یک AlertDialog ساده با دو دکمه حذف و لغو استفاده شود:
//set alert for executing the task AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.SetTitle("Confirm delete"); alert.SetMessage("Lorem ipsum dolor sit amet, consectetuer adipiscing elit."); alert.SetPositiveButton("Delete", (senderAlert, args) => { Toast.MakeText(this, "Deleted!", ToastLength.Short).Show(); }); alert.SetNegativeButton("Cancel", (senderAlert, args) => { Toast.MakeText(this, "Cancelled!", ToastLength.Short).Show(); }); Dialog dialog = alert.Create(); dialog.Show();
Fragment
public class DialogFragmentActivity : DialogFragment { public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { var dlg = inflater.Inflate(Resource.Layout.layoutDialog, container, false); return dlg; } }
DialogFragmentActivity dlg = new DialogFragmentActivity (); dlg.Show(this.FragmentManager, "Fragment");
public override void OnStart() { base.OnStart(); Dialog dialog = Dialog; if (dialog != null) { dialog.Window.SetLayout(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent); dialog.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); } }
public override Dialog OnCreateDialog(Bundle savedInstanceState) { Dialog NotTitle = base.OnCreateDialog(savedInstanceState); NotTitle.Window.RequestFeature(WindowFeatures.NoTitle); return NotTitle; }
کار با کوکیها در ASP.NET Core
مرورگر کروم، از نگارش 80 آن به بعد به همراه یک تغییر غیرسازگار با نگارشهای قبلی آن است: از این پس تمام کوکیهای آن در صورتیکه تنظیمات SameSite را نداشته باشند، به صورت SameSite=Lax تفسیر میشوند؛ که تغییر امنیتی فوق العادهای است و سبب خواهد شد تا بسیاری از حملات مانند CSRF به طور کامل غیرعملی شوند. اما ... این مورد سبب از کار افتادن برنامههایی خواهد شد که از سرویسهایی مانند IdentityServer استفاده میکنند. در یک چنین حالتی نیاز خواهید داشت برای رفع این مشکل، SameSite=None را تنظیم کنید.
اما SameSite چیست؟ تنظیم و خاصیت SameSite از سال 2016 به کوکیها اضافه شد تا بتوان توسط آن در برابر حملات CSRF مقاومت کرد؛ مقادیر اولیهی آن نیز Lax و Strict بودند. Lax به این معنا است که کوکیها در حین مرور یک سایت، باید به صورت خودکار به سمت سرور همان سایت ارسال شوند؛ اما در حالت مرور سایت و هدایت از طریق سایتهای دیگر به سایت ما، فقط درخواستهای GET رسیده میتوانند کوکیها را نیز ارسال کنند. حالت Strict آن فقط کوکیهای تنظیم شدهی درون یک سایت را معتبر شمرده و ارسال میکند. عدم تنظیم SameSite نیز مشکل خاصی را ایجاد نمیکرد. برای مثال اعمال اعتبارسنجی مبتنی بر OpenIdConnect مانند login/logout، نیاز دارند در طی یک درخواست POST، اطلاعاتی را به سایت خارجی درخواست کننده ارسال کنند. برای اینکه این عملیات به درستی صورت گیرد، میبایستی تنظیمات SameSite انجام نمیشد تا جابجایی کوکیها بین دو دومین مختلف در حالت POST، بدون مشکل صورت میگرفت.
وضعیت دات نت در این مورد: با به روز رسانیهای جدید دات نت 4.7.2 و همچنین NET Core 2.1.، مقدار جدید None توسط برنامه (در CookieOptions) قابل تنظیم خواهد بود که سبب تولید SameSite=None میشود. به علاوه در NET Core 3.1.، مقدار SameSite.Unspecified را نیز میتوان تنظیم کرد که سبب خواهد شد تا خاصیت SameSite اصلا تنظیم نشود (اصلا به درخواست اضافه نشود؛ تا مرورگرهایی که نمیتوانند مقدار None را تفسیر کنند، به اشتباه آنرا به Strict تنظیم نکنند).
به صورت خلاصه: اگر برنامهی شما از OpenIdConnect و یا IdentityServer استفاده نمیکند، هیچ تنظیمی را تغییر ندهید! تنظیم پیشفرض SameSite=Lax گوگل سبب میشود تا عملا حملات CSRF دیگر بر روی سایت شما قابل اجرا نباشد. اما اگر از IdentityServer استفاده میکنید، این تنظیم پیشفرض، سبب از کار افتادن امکان ارسال کوکیهای حالت POST، به سایتهای خارجی میشود و برنامه و سیستم شما از کار خواهد افتاد.
طبق تصویر فایلها را به صفحهای که ساختیم اضافه میکنیم:
پروژه را اجرا کرده و توسط افزونهی firebug درخواستهایی را که از سرور شدهاست، بررسی میکنیم. مشاهده خواهید کرد که به ازای هر فایل، یک درخواست به سرور ارسال شده و هیچکدام از فایلها توسط وب سرور فشرده سازی نشدهاند و اطلاعاتی در مورد کش، به هدر آنها اضافه نشده است.
<?xml version="1.0" encoding="utf-8" ?> <combres xmlns='urn:combres'> <filters> <filter type="Combres.Filters.FixUrlsInCssFilter, Combres" /> </filters> <resourceSets url="~/combres.axd" defaultDuration="30" defaultVersion="auto" defaultDebugEnabled="false" defaultIgnorePipelineWhenDebug="true" localChangeMonitorInterval="30" remoteChangeMonitorInterval="60"> <resourceSet name="siteCss" type="css"> <resource path="~/content/Site.css" /> <resource path="~/content/anotherCss.css" /> <resource path="~/scripts/yetAnotherCss.css" /> </resourceSet> <resourceSet name="siteJs" type="js"> <resource path="~/scripts/jquery-1.4.4.js" /> <resource path="~/scripts/anotherJs.js" /> <resource path="~/scripts/yetAnotherJs.js" /> </resourceSet> </resourceSets> </combres>
<?xml version="1.0" encoding="utf-8" ?> <combres xmlns='urn:combres'> <filters> <filter type="Combres.Filters.FixUrlsInCssFilter, Combres" /> </filters> <resourceSets url="~/combres.axd" defaultDuration="30" defaultVersion="auto" defaultDebugEnabled="false" defaultIgnorePipelineWhenDebug="true" localChangeMonitorInterval="30" remoteChangeMonitorInterval="60"> <resourceSet name="siteCss" type="css"> <resource path="~/Styles/Site.css" /> </resourceSet> <resourceSet name="siteJs" type="js"> <resource path="~/Scripts/jquery-1.10.2.js" /> <resource path="~/Scripts/jquery-1.10.2.min.js" /> </resourceSet> </resourceSets> </combres>
<configuration> <configSections> <section name="combres" type="Combres.ConfigSectionSetting, Combres, Version=2.2, Culture=neutral, PublicKeyToken=1ca6b37997dd7536" /> </configSections> <system.web> <pages> <namespaces> <add namespace="Combres" /> </namespaces> </pages> </system.web> <combres definitionUrl="~/App_Data/combres.xml" /> <appSettings> <add key="CombresSectionName" value="combres" /> </appSettings> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="AjaxMin" publicKeyToken="21ef50ce11b5d80f" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.84.4790.14405" newVersion="4.84.4790.14405" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.AddCombresRoute("Combres Route"); }
<%@ Import Namespace="Combres" %> <head runat="server"> <%= WebExtensions.CombresLink("siteCss") %> <%= WebExtensions.CombresLink("siteJs") %> </head>
<%= Url.CombresLink("siteCss") %> <%= Url.CombresLink("siteJs") %>
در ادامه میتوانید فایل site.css قبلی و فعلی را مقایسه کنید!
در قسمت بعد با سازوکار combres و روش استفاده از فیلترها، بیشتر آشنا میشویم.
Strong Name
System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35
برای استفاده از این تکنولوژی ابتدا نیاز است تا یک جفتکلید عمومی/خصوصی (توسط ادمین، منبع گواهینامهها، یک بانک یا یک ابزار خاص) فراهم شود تا از آن برای اینکریپشن استفاده شود. سپس دادههای موردنظر (هر داده کلی که قصد ارسال و توزیع آن را داریم مثل یک اسمبلی) با استفاده از یک الگوریتم هشکردن (مثل MD5، SHA یا ترکیبی از آنها، هرچند MD5 توصیه نمیشود) پردازش شده و یک هشکد مخصوص تولید میشود. این هشکد با استفاده از کلید خصوصی دردسترس اینکریپت میشود و به عنوان یک امضای دیجیتال به همراه داده موردنظر ارسال یا توزیع میشود. در سمت مصرف کننده که با استفاده از یک روش خاص و امن به کلید عمومی دسترسی پیدا کرده است عملیات دیکریپت کردن این امضای دیجیتال با استفاده از کلید عمومی انجام شده و هشکد مربوطه بدست میآید. همچنین عملیات تولید هشکد با استفاده از دادهها در سمت مصرف کننده انجام شده و هشکد دادهها نیز دوباره با استفاده از همان الگوریتم استفاده شده در سمت توزیعکننده تولید میشود. سپس این دو مقدار محاسبه شده در سمت مصرفکننده با یکدیگر مقایسه شده و درصورت برابر بودن میتوان اطمینان حاصل کرد همان دادهای که توزیع کننده در اصل ارسال کرده بدون تغییر به دست مصرف کننده رسیده است. درواقع ویژگی اینکریپت/دیکریپت کردن دادهها توسط جفتکلید این است که بهصورت یکطرفه بوده و دادههای اینکریپت شده با استفاده از یک کلید خصوصی را تنها با استفاده از کلید عمومی همان کلید خصوصی میتوان بدرستی دیکریپت کرد.
1. تولید و مدیریت جفتکلیدهای قوی- نامگذاریشده (Strongly Named Key Pairs)
همانطور که در قسمت قبل اشاره شد برای نامگذاری قوی یک اسمبلی به یک کلید عمومی (public key) و یک کلید خصوصی (private key) که در مجموع به آن یک جفت کلید (key pair) میگویند، نیاز است.برای اینکار میتوان با استفاده از برنامه sn.exe (عنوان کامل آن Microsoft .Net Framework Strong Name Utility است) یک جفت کلید تولید کرده و آن را در یک فایل و یا در CSP (یا همان cryptographic service provider) ذخیره کرد. همچنین اینکار را میتوان توسط ویژوال استودیو نیز انجام داد. امکان موردنظر در فرم پراپرتی یک پروژه و در تب Signing آن وجود دارد.
نکته: یک CSP عنصری از API کریپتوگرافی ویندوز (Win32 CryptoAPI) است که سرویسهایی چون اینکریپشن، دیکریپشن، و تولید امضای دیجیتال را فراهم میکند. این پرووایدرها همچنین تسهیلاتی برای مخازن کلیدها فراهم میکنند که از اینکریپشنهای قوی و ساختار امنیتی سیستم عامل (سیستم امنیتی و دسترسی کاربران ویندوز) برای محافظت از تمام کلیدهای کریپتوگرافی ذخیره شده در مخزن استفاده میکند. بهطور خلاصه و مفید میشود اشاره کرد که میتوان کلیدهای کریپتوگرافی را درون یک مخزن کلید CSP ذخیره کرد و تقریبا مطمئن بود که تا زمانیکه هیچکس کلمه عبور سیستم عامل را نداند، این کلیدها امن خواهند ماند. برای کسب اطلاعات بیشتر به دادههای CryptoAPI در اسناد SDK سیستم عامل خود مراجعه کنید.
برنامه sn به همراه SDKهای ویندوز نصب میشود. البته با نصب ویژوال استودیو تمام SDKهای موردنیاز مطابق با نسخههای موجود، نصب خواهد شد. مسیر نسخه 4 و 32 بیتی این برنامه در سیستم عامل Windows 7 بهصورت زیر است:
C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\sn.exe
با استفاده از آرگومان k همانند دستور زیر یک جفتکلید جدید تولید شده و در فایل MyKeys.snk در ریشه درایو d: ذخیره میشود:
sn –k d:\MyKeys.snk
نکته: به بزرگی و کوچکی حروف سوییچهای دستورات برنامه sn دقت کنید!
این کار یک جفت کلید کریپتوگرافی 1024 بیتی بهصورت تصادفی تولید میکند. این دستور را باید در خط فرمانی (Command Prompt) اجرا نمود که مسیر فایل sn.exe را بداند. برای راحتی کار میتوان از خط فرمان ویژوال استودیو (Visual Studio Command Prompt) استفاده کرد.
نکته: اجرای عملیات فوق در یک شرکت یا قسمت توسعه یک شرکت، تنها یک بار نیاز است زیرا تمام اسمبلیهای تولیدی تا زمانیکه عناوین ساده متمایزی دارند میتوانند از یک جفت کلید مشترک استفاده کنند.
نکته: هرچند که میتوان از پسوندهای دیگری نیز برای نام فایل حاوی جفت کلید استفاده کرد، اما توصیه میشود از همین پسوند snk. استفاده شود.
فایل تولید شده حاوی هر دو کلید «عمومی» و «خصوصی» است. میتوان با استفاده از دستور زیر کلید عمومی موجود در فایل mykeys.snk را استخراج کرده و در فایل mypublickey.snk ذخیره کرد:
sn –p d:\mykeys.snk d:\mypublickey.snk
sn -tp MyPublicKey.snk
sn -i MyKeys.snk MyStrongNameKeys
sn –m n
sn –m y
sn -d MyStrongNameKeys
نکته: برای استفاده از این ویژگی در ویژوال استودیو، باید در تب Signing در تنظیمات پروژه گزینه Sign the Assembly را انتخاب کرد. سپس میتوان فایل حاوی جفت کلیدهای تولیدشده را انتخاب یا فایل جدیدی تولید کرد. البته ویژوال استودیو تا نسخه 2010 امکانی جهت استفاده از مخازن CSP را ندارد.
[assembly:AssemblyKeyFileAttribute("MyKeys.snk")]
sn –vf MyAsm.exe
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. Failed to verify assembly -- Strong name validation failed for assembly MyAsm.exe'.
sn –p d:\MyKeys.snk d:\MyPublicKey.snk sn –pc MyKeysContainer d:\MyPublicKey.snk
csc.exe /delaysign /keyfile:d:\MyPublicKey.snk /out:d:\MyAsm.exe d:\Class1.cs
al /out:<assembly name> <module name> /keyfile:<file name>
sn –Vr d:\MyAsm.exe
sn –R d:\MyAsm.exe MyKeys.snk sn –R d:\MyAsm.exe MyKeysContainer
sn –D assembly1 assembly2
sn –Vu d:\MyAsm.exe
sn –Vx
sn –Vl
C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe
gacutil /i c:\MyAsm.dll
gacutil /u MyAsm
gacutil /u MyAsm,Version=1.3.0.5
gacutil /l
gacutil /l MyAsm
فلوچارتهای داینامیک و متنوع تحت وب
OutputCache در ASP.NET MVC
- روش دوم استفاده از Donut Caching است. در یک چنین حالتی، کد زیر مجاز است:
[LogThis] [DonutOutputCache(Duration=5, Order=100)] public ActionResult Index()