استفاده از pjax بجای ajax در ASP.NET MVC
ایجاد یک بانک اطلاعاتی با پشتیبانی از جداول بهینه سازی شده برای حافظه
برای ایجاد جداول بهینه سازی شده برای حافظه، ابتدا نیاز است تا تنظیمات خاصی را به بانک اطلاعاتی آن اعمال کنیم. برای اینکار میتوان یک بانک اطلاعاتی جدید را به همراه یک filestream filegroup ایجاد کرد که جهت جداول بهینه سازی شده برای حافظه، ضروری است؛ یا اینکه با تغییر یک بانک اطلاعاتی موجود و افزودن filegroup یاد شده نیز میتوان به این مقصود رسید.
در اینگونه جداول خاص، اطلاعات در حافظهی سیستم ذخیره میشوند و برخلاف جداول مبتنی بر دیسک سخت، صفحات اطلاعات وجود نداشته و نیازی نیست تا به کش بافر وارد شوند. برای مقاصد ذخیره سازی نهایی اطلاعات جداول بهینه سازی شده برای حافظه، موتور OLTP درون حافظهای آن، فایلهای خاصی را به نام checkpoint در یک filestream filegroup ایجاد میکند که از آنها جهت ردیابی اطلاعات استفاده خواهد کرد و نحوی ذخیره سازی اطلاعات در آنها از شیوهی با کارآیی بالایی به نام append only mode پیروی میکند.
با توجه به متفاوت بودن نحوهی ذخیره سازی نهایی اطلاعات اینگونه جداول و دسترسی به آنها از طریق استریمها، توصیه شدهاست که filestream filegroupهای تهیه شده را در یک SSD یا Solid State Drive قرار دهید.
پس از اینکه بانک اطلاعاتی خود را به روشهای معمول ایجاد کردید، به برگهی خواص آن در management studio مراجعه کنید. سپس صفحهی file groups را در آن انتخاب کرده و در پایین برگهی آن، در قسمت جدید memory optimized data، بر روی دکمهی Add کلیک کنید. سپس نام دلخواهی را وارد نمائید.
پس از ایجاد یک گروه فایل جدید، به صفحهی files خواص بانک اطلاعاتی مراجعه کرده و بر روی دکمهی Add کلیک کنید. سپس File type این ردیف اضافه شده را از نوع file stream data و file group آنرا همان گروه فایلی که پیشتر ایجاد کردیم، تنظیم کنید. در ادامه logical name دلخواهی را وارد کرده و در آخر بر روی دکمهی Ok کلیک کنید تا تنظیمات مورد نیاز جهت تعاریف جدول بهینه سازی شده برای حافظه به پایان برسد.
این مراحل را توسط دو دستور T-SQL ذیل نیز میتوان سریعتر انجام داد:
USE [master] GO ALTER DATABASE [testdb2] ADD FILEGROUP [InMemory_InMemory] CONTAINS MEMORY_OPTIMIZED_DATA GO ALTER DATABASE [testdb2] ADD FILE ( NAME = N'InMemory_InMemory', FILENAME = N'D:\SQL_Data\MSSQL11.MSSQLSERVER\MSSQL\DATA\InMemory_InMemory' ) TO FILEGROUP [InMemory_InMemory] GO
ساختار گروه فایل بهینه سازی شده برای حافظه
گروه فایل بهینه سازی شده برای حافظه، دارای چندین دربرگیرنده است که هر کدام چندین فایل را در خود جای خواهند داد:
- Root File که در برگیرندهی متادیتای اطلاعات است.
- Data File که شامل ردیفهای اطلاعات ثبت شده در جداول بهینه سازی شدهی برای حافظه هستند. این ردیفها همواره به انتهای data file اضافه میشوند و دسترسی به آنها ترتیبی است. کارآیی IO این روش نسبت به روش دسترسی اتفاقی به مراتب بالاتر است. حداکثر اندازه این فایل 128 مگابایت است و پس از آن یک فایل جدید ساخته میشود.
- Delta File شامل ردیفهایی است که حذف شدهاند. به ازای هر ردیف، حداقل اطلاعاتی از آن را در خود ذخیره خواهد کرد؛ شامل ID ردیف حذف شده و شماره تراکنش آن. همانطور که پیشتر نیز ذکر شد، این موتور جدید درون حافظهای، برای یافتن راه چارهای جهت به حداقل رسانی قفل گذاری بر روی اطلاعات، چندین نگارش از ردیفها را به همراه timestamp آنها در خود ذخیره میکند. به این ترتیب، هر به روز رسانی به همراه یک حذف و سپس ثبت جدید است. به این ترتیب دیگر بانک اطلاعاتی نیازی نخواهد داشت تا به دنبال رکورد موجود برگردد و سپس اطلاعات آنرا به روز نماید. این موتور جدید فقط اطلاعات به روز شده را در انتهای رکوردهای موجود با فرمت خود ثبت میکند.
ایجاد جداول بهینه سازی شده برای حافظه
پس از آماده سازی بانک اطلاعاتی خود و افزودن گروه فایل استریم جدیدی به آن برای ذخیره سازی اطلاعات جداول بهینه سازی شده برای حافظه، اکنون میتوانیم اینگونه جداول خاص را در کنار سایر جداول متداول موجود، تعریف و استفاده نمائیم:
-- It is not a Memory Optimized CREATE TABLE tblNormal ( [CustomerID] int NOT NULL PRIMARY KEY NONCLUSTERED, [Name] nvarchar(250) NOT NULL, CustomerSince DATETIME not NULL INDEX [ICustomerSince] NONCLUSTERED ) -- DURABILITY = SCHEMA_AND_DATA CREATE TABLE tblMemoryOptimized_Schema_And_Data ( [CustomerID] INT NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1000000), [Name] NVARCHAR(250) NOT NULL, [CustomerSince] DATETIME NOT NULL INDEX [ICustomerSince] NONCLUSTERED ) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA) -- DURABILITY = SCHEMA_ONLY CREATE TABLE tblMemoryOptimized_Schema_Only ( [CustomerID] INT NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1000000), [Name] NVARCHAR(250) NOT NULL, [CustomerSince] DATETIME NOT NULL INDEX [ICustomerSince] NONCLUSTERED ) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY)
همانطور که مشخص است، دو جدول بهینه سازی شده برای حافظه، همان سه ستون جدول معمولی مبتنی بر دیسک سخت را دارا هستند؛ اما با این تفاوتها:
- دارای ویژگی MEMORY_OPTIMIZED = ON میباشند. به این ترتیب اینگونه جداول نسبت به جداول متداول مبتنی به دیسک سخت متمایز خواهند شد.
- دارای ویژگی DURABILITY بوده و توسط مقدار SCHEMA_AND_DATA آن مشخص میکنیم که آیا قرار است اطلاعات و ساختار جدول، ذخیره شوند یا تنها قرار است ساختار جدول ذخیره گردد (حالت SCHEMA_ONLY).
- بر روی ستون Id آنها یک hash index ایجاد شدهاست که وجود آن ضروری است و در کل بیش از 8 ایندکس را نمیتوان تعریف کرد.
برخلاف ایندکسهای B-tree جداول مبتنی بر سخت دیسک، ایندکسهای جداول بهینه سازی شده برای حافظه، اطلاعات را تکرار نمیکنند. اینها صرفا اشارهگرهایی هستند به ردیفهای اصلی اطلاعات. به این معنا که این ایندکسها لاگ نشده و همچنین بر روی سخت دیسک ذخیره نمیشوند. کار بازسازی مجدد آنها در اولین بار بازیابی بانک اطلاعاتی و آغاز آن به صورت خودکار انجام میشود. به همین جهت مباحثی مانند index fragmentation و نگهداری ایندکسها دیگر در اینجا معنا پیدا نمیکنند.
دو نوع ایندکس را در اینجا میتوان تعریف کرد. اولین آنها hash index است و دومین آنها range index. هش ایندکسها برای حالاتی که در کوئریها از عملگر تساوی استفاده میشود بسیار مناسب هستند. برای عملگرهای مقایسهای از ایندکسهای بازهای استفاده میشود.
همچنین باید دقت داشت که پس از ایجاد ایندکسها، دیگر امکان تغییر آنها و یا تغییر ساختار جدول ایجاد شده نیست.
همچنین ایندکسهای تعریف شده در جداول بهینه سازی شده برای حافظه، تنها بر روی ستونهایی غیرنال پذیر از نوع BIN2 collation مانند int و datetime قابل تعریف هستند. برای مثال اگر سعی کنیم بر روی ستون Name ایندکسی را تعریف کنیم، به این خطا خواهیم رسید:
Indexes on character columns that do not use a *_BIN2 collation are not supported with indexes on memory optimized tables.
- نوعهای قابل تعریف ستونها نیز در اینجا به موارد ذیل محدود هستند و جمع طول آنها از 8060 نباید بیشتر شود:
bit, tinyint, smallint, int, bigint, money, smallmoney, float, real, datetime, smalldatetime, datetime2, date, time, numberic, decimal, char(n), varchar(n) ,nchar(n), nvarchar(n), sysname, binary(n), varbinary(n), and Uniqueidentifier
همچنین در management studio، گزینهی جدید new -> memory optimized table نیز اضافه شدهاست و انتخاب آن سبب میشود تا قالب T-SQL ایی برای تهیه این نوع جداول، به صورت خودکار تولید گردد.
البته این گزینه تنها برای بانکهای اطلاعاتی که دارای گروه فایل استریم مخصوص جداول بهینه سازی شده برای حافظه هستند، فعال میباشد.
ثبت اطلاعات در جداول معمولی و بهینه سازی شده برای حافظه و مقایسه کارآیی آنها
در مثال زیر، 100 هزار رکورد را در سه جدولی که پیشتر ایجاد کردیم، ثبت کرده و سپس مدت زمان اجرای هر کدام از مجموعه عملیات را بر حسب میلی ثانیه بررسی میکنیم:
set statistics time off SET STATISTICS IO Off set nocount on go ----------------------------- Print 'insert into tblNormal' DECLARE @start datetime = getdate() declare @insertCount int = 100000 declare @startId int = 1 declare @customerID int = @startId while @customerID < @startId + @insertCount begin insert into tblNormal values (@customerID, 'Test', '2013-01-01T00:00:00') set @customerID +=1 end Print DATEDIFF(ms,@start,getdate()); go ----------------------------- Print 'insert into tblMemoryOptimized_Schema_And_Data' DECLARE @start datetime = getdate() declare @insertCount int = 100000 declare @startId int = 1 declare @customerID int = @startId while @customerID < @startId + @insertCount begin insert into tblMemoryOptimized_Schema_And_Data values (@customerID, 'Test', '2013-01-01T00:00:00') set @customerID +=1 end Print DATEDIFF(ms,@start,getdate()); Go ----------------------------- Print 'insert into tblMemoryOptimized_Schema_Only' DECLARE @start datetime = getdate() declare @insertCount int = 100000 declare @startId int = 1 declare @customerID int = @startId while @customerID < @startId + @insertCount begin insert into tblMemoryOptimized_Schema_Only values (@customerID, 'Test', '2013-01-01T00:00:00') set @customerID +=1 end Print DATEDIFF(ms,@start,getdate()); Go
insert into tblNormal 36423 insert into tblMemoryOptimized_Schema_And_Data 30516 insert into tblMemoryOptimized_Schema_Only 3176
set nocount on print 'tblNormal' set statistics time on select count(CustomerID) from tblNormal set statistics time off go print 'tblMemoryOptimized_Schema_And_Data' set statistics time on select count(CustomerID) from tblMemoryOptimized_Schema_And_Data set statistics time off go print 'tblMemoryOptimized_Schema_Only' set statistics time on select count(CustomerID) from tblMemoryOptimized_Schema_Only set statistics time off go
tblNormal SQL Server Execution Times: CPU time = 46 ms, elapsed time = 52 ms. tblMemoryOptimized_Schema_And_Data SQL Server Execution Times: CPU time = 32 ms, elapsed time = 33 ms. tblMemoryOptimized_Schema_Only SQL Server Execution Times: CPU time = 31 ms, elapsed time = 30 ms.
برای مطالعه بیشتر
Getting started with SQL Server 2014 In-Memory OLTP
Introduction to SQL Server 2014 CTP1 Memory-Optimized Tables
Overcoming storage speed limitations with Memory-Optimized Tables for SQL Server
Memory-optimized Table – Day 1 Test
Memory-Optimized Tables – Insert Test
Memory Optimized Table – Insert Test …Again
PM> Install-Package DNTFrameworkCore.Web
- Refactor کردن فرمهای ثبت و ویرایش مرتبط با یک Aggregate، به یک PartialView که با یک ViewModel کار میکند. برای موجودیتهای ساده و پایه، همان Model/DTO، به عنوان Model متناظر با یک ویو یا به اصطلاح ViewModel استفاده میشود؛ ولی برای سایر موارد، از مدلی که نام آن با نام موجودیت + کلمه ModalViewModel یا FormViewModel تشکیل میشود، استفاده خواهیم کرد.
- یک فرم، در قالب یک پارشالویو، به صورت Ajaxای با استفاده از افزونه jquery-unobtrusive-ajax بارگذاری شده و به سرور ارسال خواهد شد.
- یک فرم براساس طراحی خود میتواند در قالب یک مودال باز شود، یا به منظور inline-editing آن را بارگذاری و به قسمتی از صفحه که مدنظرتان میباشد اضافه شود.
- وجود ویو Index به همراه پارشالویو _List برای نمایش لیستی و یک پارشالویو برای عملیات ثبت و ویرایش الزامی میباشد. البته اگر از مکانیزمی که در مطلب « طراحی یک گرید با jQuery Ajax و ASP.NET MVC به همراه پیاده سازی عملیات CRUD» مطرح شد، استفاده نمیکنید و نیاز دارید تا اطلاعات صفحهبندی شده، مرتب شده و فیلتر شدهای را در قالب JSON دریافت کنید، از اکشنمتد ReadPagedList کنترلر پایه استفاده کنید.
public class BlogsController : CrudController<IBlogService, int, BlogModel> { public BlogsController(IBlogService service) : base(service) { } protected override string CreatePermissionName => PermissionNames.Blogs_Create; protected override string EditPermissionName => PermissionNames.Blogs_Edit; protected override string ViewPermissionName => PermissionNames.Blogs_View; protected override string DeletePermissionName => PermissionNames.Blogs_Delete; protected override string ViewName => "_BlogModal"; }
<form asp-action="@(Model.IsNew() ? "Create" : "Edit")" asp-controller="Blogs" asp-modal-form="BlogForm"> <div> <input type="hidden" name="save-continue" value="true"/> <input asp-for="RowVersion" type="hidden"/> <input asp-for="Id" type="hidden"/> <div> <div> <label asp-for="Title"></label> <input asp-for="Title" autocomplete="off"/> <span asp-validation-for="Title"></span> </div> </div> <div> <div> <label asp-for="Url"></label> <input asp-for="Url" type="url"/> <span asp-validation-for="Url"></span> </div> </div> </div> ... </form>
<div> <a asp-modal-delete-link asp-model-id="@Model.Id" asp-modal-toggle="false" asp-controller="Blogs" asp-action="Delete" asp-if="!Model.IsNew()" asp-permission="@PermissionNames.Blogs_Delete" title="Delete Blog"> <i></i> </a> <a title="Refresh Blog" asp-if="!Model.IsNew()" asp-modal-link asp-modal-toggle="false" asp-controller="Blogs" asp-action="Edit" asp-route-id="@Model.Id"> <i></i> </a> <a title="New Blog" asp-modal-link asp-modal-toggle="false" asp-controller="Blogs" asp-action="Create"> <i></i> </a> <button type="button" data-dismiss="modal"> <i></i> Cancel </button> <button type="submit"> <i></i> Save Changes </button> </div>
public class RoleModalViewModel : RoleModel { public IReadOnlyList<LookupItem> PermissionList { get; set; } }
protected override IActionResult RenderView(RoleModel role) { var model = _mapper.Map<RoleModalViewModel>(role); model.PermissionList = ReadPermissionList(); return PartialView(ViewName, model); }
برای مدیریت سناریوهای Master-Detail به مانند قسمت مدیریت دسترسیها در تب Permissions فرم بالا، امکاناتی در زیرساخت تعبیه شده است ولی پیادهسازی آن را به عنوان یک تمرین و با توجه به سری مطالب «Editing Variable Length Reorderable Collections in ASP.NET MVC» به شما واگذار میکنم.
نکته تکمیلی: برای ارسال اطلاعات اضافی به ویو Index متناظر با یک موجودیت میتوانید متد RenderIndex را به شکل زیر بازنویسی کنید:
protected override IActionResult RenderIndex(IPagedQueryResult<RoleReadModel> model) { var indexModel = new RoleIndexViewModel { Items = model.Items, TotalCount = model.TotalCount, Permissions = ReadPermissionList() }; return Request.IsAjaxRequest() ? (IActionResult) PartialView(indexModel) : View(indexModel); }
مدل RoleIndexViewModel استفاده شده در تکه کد بالا نیز به شکل زیر خواهد بود:
public class RoleIndexViewModel : PagedQueryResult<RoleReadModel> { public IReadOnlyList<LookupItem> Permissions { get; set; } }
فرآیند بارگذاری یک پارشالویو در مودال
به عنوان مثال برای استفاده از مودالهای بوت استرپ، ایده کار به این شکل است که یک مودال را به شکل زیر در فایل Layout قرار دهید:
<div class="modal fade" @*tabindex="-1"*@ id="main-modal" data-keyboard="true" data-backdrop="static" role="dialog" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered" role="document"> <div class="modal-content"> <div class="modal-body"> Loading... </div> </div> </div> </div>
سپس در زمان کلیک بروی یک دکمه Ajaxای، ابتدا main-modal را نمایش داده و بعد از دریافت پارشالویو از سرور، آن را با محتوای modal-content جایگزین میکنیم. به همین دلیل Tag Halperهای مطرح شده در مطلب جاری، callbackهای failure/complete/success متناظر با unobtrusive-ajax را نیز مقداردهی میکنند. برای این منظور نیاز است تا متدهای جاوااسکریپتی زیر نیز در سطح شیء window تعریف شده باشند:
/*---------------------------------- asp-modal-link ---------------------------*/ window.handleModalLinkLoaded = function (data, status, xhr) { prepareForm('#main-modal.modal form'); }; window.handleModalLinkFailed = function (xhr, status, error) { //.... }; /*---------------------------------- asp-modal-form ---------------------------*/ window.handleModalFormBegin = function (xhr) { $('#main-modal a').addClass('disabled'); $('#main-modal button').attr('disabled', 'disabled'); }; window.handleModalFormComplete = function (xhr, status) { $('#main-modal a').removeClass('disabled'); $('#main-modal button').removeAttr('disabled'); }; window.handleModalFormSucceeded = function (data, status, xhr) { if (xhr.getResponseHeader('Content-Type') === 'text/html; charset=utf-8') { prepareForm('#main-modal.modal form'); } else { hideMainModal(); } }; window.handleModalFormFailed = function (xhr, status, error, formId) { if (xhr.status === 400) { handleBadRequest(xhr, formId); } };
برای بررسی بیشتر، پیشنهاد میکنم پروژه DNTFrameworkCore.TestWebApp موجود در مخزن این زیرساخت را بازبینی کنید.
public class album { public string name; public string des; public string[] images; }
+ نرم افزار IDM اگر از طریق افزونههای آن (^) اقدام به دریافت این فایل کند، مشکلی نخواهد داشت؛ چون این افزونهها اطلاعات سشن جاری کاربر را به برنامه، جهت شبیه سازی و استفادهی مجدد منتقل میکنند (اطلاعاتی مانند کوکیها و تمام مشخصات جاری صفحهی لاگین کرده). به عبارتی اگر شخصی به سایت لاگین کند و از طریق مرورگری که افزونهی IDM بر روی آن نصب است، اقدام به دریافت فایل کند (بر روی لینک کلیک کند تا مرورگر توسط افزونهی مربوطه، درخواست را به برنامه ارسال کند)، دریافت فایل از دید او معمولی و مانند قبل خواهد بود.
- جداول تعاریف Definition Table
- جداول ارجاع References Table
- جداول manifest
جداول تعریف
جدول زیر تعدادی از جداول تعریفها را توضیح میدهد:
ModuleDef | شامل آدرس یا مدخلی است که ماژول در آن تعریف شده است. این آدرس شامل نام ماژول به همراه پسوند آن است؛ بدون ذکر مسیر. در صورتی که کامپایل به صورت GUID انجام گرفته باشد، Version ID ماژول هم همراه آنها خواهد بود. در صورتیکه نام فایل تغییر کند، این جدول باز نام اصلی ماژول را به همراه خواهد داشت. هر چند تغییر نام فایل به شدت رد شده و ممکن است باعث شود CLR نتواند در زمان اجرا آن را پیدا کند. |
TypeDef | شامل یک مدخل ورودی برای هر نوعی است که تعریف شده است. هر آدرس ورودی شامل نام نوع ، پرچمها (همان مجوزهای public و private و ...) میباشد. همچنین شامل اندیس هایی به متدها است که شامل جدول MethodDef میباشند یا فیلدهایی که شامل جدول FieldDef میباشند و الی آخر... |
MethodDef | شامل آدرسی برای هر متد تعریف شده در ماژول است که شامل نام متد و پرچم هاست. همچنین شامل امضای متد و نقطهی آغاز کد IL آن در ماژول هم میشود و آن آدرس هم میتواند ارجاعی به جدول ParamDef جهت شناسایی پارامترها باشد. |
FieldDef | شامل اطلاعاتی در مورد فیلدهاست که این اطلاعات ، پرچم، نام و نوع فیلد را مشخص میکنند. |
ParamDef | حاوی اطلاعات پارامتر متدهاست که این اطلاعات شامل پرچمها (in , out ,retval) ، نوع و نام است. |
PropertyDef | برای هر پراپرتی یا خصوصیت، شامل یک آدرس است که شامل نام، نوع و پرچم میشود. |
EventDef | برای هر رویداد شامل یک آدرس است که این آدرس شامل نام و نوع است. |
جداول ارجاعی
AssemblyRef | شامل آدرس اسمبلی است که ماژولی به آن ارجاع داده است و این آدرس شامل اطلاعات ضروری جهت اتصال به اسمبلی میشود و این اطلاعات شامل نام اسمبلی (بدون ذکر پسوند و مسیر)، شماره نسخه اسمبلی، سیستم فرهنگی و منطقهای تعیین شده اسمبلی culture و یک کلید عمومی که عموما توسط ناشر ایجاد میگردد که هویت ناشر آن اسمبلی را مشخص میکند. هر آدرس شامل یک پرچم و یک کد هش هست که بری ارزیابی از صحت و بی خطا بودن بیتهای اسمبلی ارجاع شده Checksum استفاده میشود. |
ModuleRef | شامل یک آدرس ورودی به هدر PE ماژول است به نوعهای پیاده سازی شده آن ماژول در آن اسمبلی. هر آدرس شامل نام فایل و پسوند آن بدون ذکر مسیر است. این جدول برای اتصال به نوعهایی استفاده میشود که در یک ماژول متفاوت از ماژول اسمبلی صدا زده شده پیاده سازی شده است. |
TypeRef | شامل یک آدرس یا ورودی برای هر نوعی است که توسط ماژول ارجاع داده شده است. هر آدرس شامل نام نوع و آدرسی است که نوع در آن جا قرار دارد. اگر این نوع داخل نوع دیگری پیاده سازی شود، ارجاعات به سمت یک جدول TypeDef خواهد بود. اگر نوع داخل همان ماژول تعریف شده باشد، ارجاع به سمت جدول ModuleDef خواهد بود و اگر نوع در ماژول دیگری از آن اسمبلی پیاده سازی شده باشد، ارجاع به سمت یک جدول ModuleRef خواهد بود و اگر نوع در یک اسمبلی جداگانه تعریف شده باشد، ارجاع به جدول AssemblyRef خواهد بود. |
MemberRef | شامل یک آدرس ورودی برای هر عضو (فیلد و متدها و حتی پراپرتی و رویدادها) است که توسط آن آن ماژول ارجاع شده باشد. هر آدرس شامل نام عضو، امضاء و یک اشارهگر به جدول TypeRef است، برای نوعهایی که به تعریف عضو پرداختهاند. |
ILDasm Program.exe
View/MetaInfo/Show
=========================================================== ScopeName : Program.exe MVID : {CA73FFE80D424610A8D39276195C35AA} =========================================================== Global functions Global fields Global MemberRefs TypeDef #1 (02000002) TypDefName: Program (02000002) Flags : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass] [BeforeFieldInit] (00100101) Extends : 01000001 [TypeRef] System.Object Method #1 (06000001) [ENTRYPOINT] MethodName: Main (06000001) Flags : [Public] [Static] [HideBySig] [ReuseSlot] (00000096) RVA : 0x00002050 ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] ReturnType: Void No arguments. Method #2 (06000002) MethodName: .ctor (06000002) Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886) RVA : 0x0000205c ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #1 (01000001) Token: 0x01000001 ResolutionScope: 0x23000001 TypeRefName: System.Object MemberRef #1 (0a000004) Member: (0a000004) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #2 (01000002) Token: 0x01000002 ResolutionScope: 0x23000001 TypeRefName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute MemberRef #1 (0a000001) Member: (0a000001) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: I4 TypeRef #3 (01000003) Token: 0x01000003 ResolutionScope: 0x23000001 TypeRefName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute MemberRef #1 (0a000002) Member: (0a000002) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #4 (01000004) Token: 0x01000004 ResolutionScope: 0x23000001 TypeRefName: System.Console MemberRef #1 (0a000003) Member: (0a000003) WriteLine: CallCnvntn: [DEFAULT] ReturnType: Void 1 Arguments Argument #1: String Assembly Token: 0x20000001 Name : Program Public Key : Hash Algorithm : 0x00008004 Version: 0.0.0.0 Major Version: 0x00000000 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> Flags : [none] (00000000) CustomAttribute #1 (0c000001) CustomAttribute Type: 0a000001 CustomAttributeName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute :: instance void .ctor(int32) Length: 8 Value : 01 00 08 00 00 00 00 00 > < ctor args: (8) CustomAttribute #2 (0c000002) CustomAttribute Type: 0a000002 CustomAttributeName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute :: instance void .ctor() Length: 30 Value : 01 00 01 00 54 02 16 57 72 61 70 4e 6f 6e 45 78 > T WrapNonEx< : 63 65 70 74 69 6f 6e 54 68 72 6f 77 73 01 >ceptionThrows < ctor args: () AssemblyRef #1 (23000001) Token: 0x23000001 Public Key or Token: b7 7a 5c 56 19 34 e0 89 Name: mscorlib Version: 4.0.0.0 Major Version: 0x00000004 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> HashValue Blob: Flags: [none] (00000000) User Strings 70000001 : ( 2) L"Hi" Coff symbol name overhead: 0
View/statistics
File size : 3584 PE header size : 512 (496 used) (14.29%) PE additional info : 1411 (39.37%) Num.of PE sections : 3 CLR header size : 72 ( 2.01%) CLR metadata size : 612 (17.08%) CLR additional info : 0 ( 0.00%) CLR method headers : 2 ( 0.06%) Managed code : 20 ( 0.56%) Data : 2048 (57.14%) Unaccounted : 1093 (30.50%) Num.of PE sections : 3 .text 1024 .rsrc 1536 .reloc 512 CLR metadata size : 612 Module 1 (10 bytes) TypeDef 2 (28 bytes) 0 interfaces, 0 explicit layout TypeRef 4 (24 bytes) MethodDef 2 (28 bytes) 0 abstract, 0 native, 2 bodies MemberRef 4 (24 bytes) CustomAttribute 2 (12 bytes) Assembly 1 (22 bytes) AssemblyRef 1 (20 bytes) Strings 184 bytes Blobs 68 bytes UserStrings 8 bytes Guids 16 bytes Uncategorized 168 bytes CLR method headers : 2 Num.of method bodies 2 Num.of fat headers 0 Num.of tiny headers 2 Managed code : 20 Ave method size 10
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="OAuthWebTest.Login" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div id="loginform"> <div id="NotLoggedIn" runat="server"> <asp:ImageButton ID="ButtonLoginWithGoogle" runat="server" ImageUrl="Google.JPG" OnCommand="ButtonLoginWithGoogle_Click" CommandArgument="https://www.google.com/accounts/o8/id" /> <p /> <asp:Label runat="server" ID="LabelMessage" /> </div> </div> </form> </body> </html>
using System; using System.Web.UI; using System.Web.UI.WebControls; using DotNetOpenAuth.OpenId.RelyingParty; namespace OAuthWebTest { public partial class Login : Page { protected void Page_Load(object sender, EventArgs e) { OpenIdRelyingParty openIdRelyingParty = new OpenIdRelyingParty(); var response = openIdRelyingParty.GetResponse(); if (response == null) return; switch (response.Status) { case AuthenticationStatus.Authenticated: NotLoggedIn.Visible = false; Session["GoogleIdentifier"] = response.ClaimedIdentifier.ToString(); Response.Redirect("Default.aspx"); break; case AuthenticationStatus.Canceled: LabelMessage.Text = "Cancelled."; break; case AuthenticationStatus.Failed: LabelMessage.Text = "Login Failed."; break; } } protected void ButtonLoginWithGoogle_Click(object src, CommandEventArgs e) { string discoveryUri = e.CommandArgument.ToString(); OpenIdRelyingParty openId = new OpenIdRelyingParty(); var returnToUrl = new UriBuilder(Request.Url) { Query = "" }; var request = openId.CreateRequest(discoveryUri, returnToUrl.Uri, returnToUrl.Uri); request.RedirectToProvider(); } } }
طی مقاله چک لیست تولید برنامه Asp.net mvc و بررسی امنیتی ایجکس هنگام استفاده در مورد چک لیست امنیتی
سایت سرفصلهای مهم عنوان و بررسی شده است که یکی از موارد، مقاوم ساختن وب اپلیکشن در برابر حملات CSRF میباشد. اینگونه حملات بر پایه این استراتژی شکل میگیرند که با
ارسال درخواستی به نیابت از سمت سیستم/مرورگر کاربر تایید هویت شده، سایت مقصد را مجبور
به انجام عملی کند. برای مثال اگر شما در سایت a.com یک
کاربر تایید شده باشید و هم اکنون در سایت فوق نیز لاگین باشید، مهاجم با ارسال یک برنامه/صفحه یا موارد مشابه و در قالب src یک
عکس یا با ترغیب شما با کلیک بر روی یک لینک با href آلوده یا موارد
مشابه، از سمت مرورگر شما درخواستی را به سمت سایت a.com ارسال میکند .
این درخواست ممکن است شامل حذف اطلاعات، تغییر مشخصات، پرداخت هزینه یا موارد مشابه باشد. جهت مقابله با این حمله، یکی از موارد مهم، استفاده همیشگی از Html.AntiForgeryToken() در تمامی فرمهای ورود اطلاعات است. همچنین استفاده همیشگی از متد Post و بررسی تایید مبدا درخواستهای ایجکسی، بررسی http referrer ، محدود کردن طول عمر کوکی، استفاده از کپچهای قوی مانند کپچای گوگل میتواند تا حد زیادی وب اپلیکیشن را در مورد اینگونه حملات، مصون کند.
در این بین یکی از موارد دیگر، اضافه کردن AntiForgeryToken به درخواستهای ایجکسی سایت میباشد. جهت حصول این منظور، راههای مختلفی موجود است. یکی از راه حلها استفاده از یک هلپر جهت تولید توکن مورد نظر است.
ساختار
هلپر مورد نظر به شرح زیر است :
public static class AntiForgeryToken { public static MvcHtmlString AntiForgeryTokenForAjaxPost(this HtmlHelper helper) { var antiForgeryInputTag = helper.AntiForgeryToken().ToString(); // Above gets the following: <input name="__RequestVerificationToken" type="hidden" value="some value" /> var removedStart = antiForgeryInputTag.Replace(@"<input name=""__RequestVerificationToken"" type=""hidden"" value=""", ""); var tokenValue = removedStart.Replace(@""" />", ""); if (antiForgeryInputTag == removedStart || removedStart == tokenValue) throw new InvalidOperationException("Oops! The Html.AntiForgeryToken() method seems to return something I did not expect."); return new MvcHtmlString($@"{"__RequestVerificationToken"}:""{tokenValue}"""); } }
در مرحله بعد طبق الگوی زیر، درخواست ایجکسی به همراه توکن تولید شده و به کنترلر ارسال خواهد شد:
function AddToCart(pid) { $.ajax({ url: '@Url.Action("AddToBasket","Shop")', data: { 'pid': pid,@Html.AntiForgeryTokenForAjaxPost() }, type: 'post', success:function(e) { //do something } }); }
در مرحله آخر، باید کنترلر مورد نظر شامل ویژگیهای [HttpPost] [ValidateAntiForgeryToken] باشد تا صحت توکن تولیدی را بررسی کند و در صورت نامعتبر بودن، از اجرای دستورات جلوگیری گردد.