EF Code First #1
در ادامه بحث ASP.NET MVC میشود به ابزاری به نام MVC Scaffolding اشاره کرد. کار این ابزار که توسط یکی از اعضای تیم ASP.NET MVC به نام استیو اندرسون تهیه شده، تولید کدهای اولیه یک برنامه کامل ASP.NET MVC از روی مدلهای شما میباشد. حجم بالایی از کدهای تکراری آغازین برنامه را میشود توسط این ابزار تولید و بعد سفارشی کرد. MVC Scaffolding حتی قابلیت تولید کد بر اساس الگوی Repository و یا نوشتن Unit tests مرتبط را نیز دارد. بدیهی است این ابزار جای یک برنامه نویس را نمیتواند پر کند اما کدهای آغازین یک سری کارهای متداول و تکراری را به خوبی میتواند پیاده سازی و ارائه دهد. زیر ساخت این ابزار، علاوه بر ASP.NET MVC، آشنایی با Entity framework code first است.
در طی سری ASP.NET MVC که در این سایت تا به اینجا مطالعه کردید من به شدت سعی کردم از ابزارگرایی پرهیز کنم. چون شخصی که نمیداند مسیریابی چیست، اطلاعات چگونه به یک کنترلر منتقل یا به یک View ارسال میشوند، قراردادهای پیش فرض فریم ورک چیست یا زیر ساخت امنیتی یا فیلترهای ASP.NET MVC کدامند، چطور میتواند از ابزار پیشرفته Code generator ایی استفاده کند، یا حتی در ادامه کدهای تولیدی آنرا سفارشی سازی کند؟ بنابراین برای استفاده از این ابزار و درک کدهای تولیدی آن، نیاز به یک پیشنیاز دیگر هم وجود دارد: «Entity framework code first»
امسال دو کتاب خوب در این زمینه منتشر شدهاند به نامهای:
Programming Entity Framework: DbContext, ISBN: 978-1-449-31296-1
Programming Entity Framework: Code First, ISBN: 978-1-449-31294-7
که هر دو به صورت اختصاصی به مقوله EF Code first پرداختهاند.
در طی روزهای بعدی EF Code first را با هم مرور خواهیم کرد و البته این مرور مستقل است از نوع فناوری میزبان آن؛ میخواهد یک برنامه کنسول باشد یا WPF یا یک سرویس ویندوز NT و یا ... یک برنامه وب.
البته از دیدگاه مایکروسافت، M در MVC به معنای EF Code first است. به همین جهت MVC3 به صورت پیش فرض ارجاعی را به اسمبلیهای آن دارد و یا حتی به روز رسانی که برای آن ارائه داده نیز در جهت تکمیل همین بحث است.
مروری سریع بر تاریخچه Entity framework code first
ویژوال استودیو 2010 و دات نت 4، به همراه EF 4.0 ارائه شدند. با این نگارش امکان استفاده از حالتهای طراحی database first و model first مهیا است. پس از آن، به روز رسانیهای EF خارج از نوبت و به صورت منظم، هر از چندگاهی ارائه میشوند و در زمان نگارش این مطلب، آخرین نگارش پایدار در دسترس آن 4.3.1 میباشد. از زمان EF 4.1 به بعد، نوع جدیدی از مدل سازی به نام Code first به این فریم ورک اضافه شد و در نگارشهای بعدی آن، مباحث DB migration جهت ساده سازی تطابق اطلاعات مدلها با بانک اطلاعاتی، اضافه گردیدند. در روش Code first، کار با طراحی کلاسها که در اینجا مدل دادهها نامیده میشوند، شروع گردیده و سپس بر اساس این اطلاعات، تولید یک بانک اطلاعاتی جدید و یا استفاده از نمونهای موجود میسر میگردد.
پیشتر در روش database first ابتدا یک بانک اطلاعاتی موجود، مهندسی معکوس میشد و از روی آن فایل XML ایی با پسوند EDMX تولید میگشت. سپس به کمک entity data model designer ویژوال استودیو، این فایل نمایش داده شده و یا امکان اعمال تغییرات بر روی آن میسر میشد. همچنین در روش دیگری به نام model first نیز کار از entity data model designer جهت طراحی موجودیتها آغاز میگشت.
اما با روش Code first دیگر در ابتدای امر مدل فیزیکی و یک بانک اطلاعاتی وجود خارجی ندارد. در اینجا EF تعاریف کلاسهای شما را بررسی کرده و بر اساس آن، اطلاعات نگاشتهای خواص کلاسها به جداول و فیلدهای بانک اطلاعاتی را تشکیل میدهد. البته عموما تعاریف ساده کلاسها بر این منظور کافی نیستند. به همین جهت از یک سری متادیتا به نام ویژگیها یا اصطلاحا data annotations مهیا در فضای نام System.ComponentModel.DataAnnotations برای افزودن اطلاعات لازم مانند نام فیلدها، جداول و یا تعاریف روابط ویژه نیز استفاده میگردد. به علاوه در روش Code first یک API جدید به نام Fluent API نیز جهت تعاریف این ویژگیها و روابط، با کدنویسی مستقیم نیز درنظر گرفته شده است. نهایتا از این اطلاعات جهت نگاشت کلاسها به بانک اطلاعاتی و یا برای تولید ساختار یک بانک اطلاعاتی خالی جدید نیز میتوان کمک گرفت.
مزایای EF Code first
- مطلوب برنامه نویسها! : برنامه نویسهایی که مدتی تجربه کار با ابزارهای طراح را داشته باشند به خوبی میدانند این نوع ابزارها عموما demo-ware هستند. چندجا کلیک میکنید، دوبار Next، سه بار OK و ... به نظر میرسد کار تمام شده. اما واقعیت این است که عمری را باید صرف نگهداری و یا پیاده سازی جزئیاتی کرد که انجام آنها با کدنویسی مستقیم بسیار سریعتر، سادهتر و با کنترل بیشتری قابل انجام است.
- سرعت: برای کار با EF Code first نیازی نیست در ابتدای کار بانک اطلاعاتی خاصی وجود داشته باشد. کلاسهای خود را طراحی و شروع به کدنویسی کنید.
- سادگی: در اینجا دیگر از فایلهای EDMX خبری نیست و نیازی نیست مرتبا آنها را به روز کرده یا نگهداری کرد. تمام کارها را با کدنویسی و کنترل بیشتری میتوان انجام داد. به علاوه کنترل کاملی بر روی کد نهایی تهیه شده نیز وجود دارد و توسط ابزارهای تولید کد، ایجاد نمیشوند.
- طراحی بهتر بانک اطلاعاتی نهایی: اگر طرح دقیقی از مدلهای برنامه داشته باشیم، میتوان آنها را به المانهای کوچک و مشخصی، تقسیم و refactor کرد. همین مساله در نهایت مباحث database normalization را به نحوی مطلوب و با سرعت بیشتری میسر میکند.
- امکان استفاده مجدد از طراحی کلاسهای انجام شده در سایر ORMهای دیگر. چون طراحی مدلهای برنامه به بانک اطلاعاتی خاصی گره نمیخورند و همچنین الزاما هم قرار نیست جزئیات کاری EF در آنها لحاظ شود، این کلاسها در صورت نیاز در سایر پروژهها نیز به سادگی قابل استفاده هستند.
- ردیابی سادهتر تغییرات: روش اصولی کار با پروژههای نرم افزاری همواره شامل استفاده از یک ابزار سورس کنترل مانند SVN، Git، مرکوریال و امثال آن است. به این ترتیب ردیابی تغییرات انجام شده به سادگی توسط این ابزارها میسر میشوند.
- سادهتر شدن طراحیهای پیچیدهتر: برای مثال پیاده سازی ارث بری، ایجاد کلاسهای خود ارجاع دهنده و امثال آن با کدنویسی سادهتر است.
دریافت آخرین نگارش EF
برای دریافت و نصب آخرین نگارش EF نیاز است از NuGet استفاده شود و این مزایا را به همراه دارد:
به کمک NuGet امکان با خبر شدن از به روز رسانی جدید صورت گرفته به صورت خودکار درنظر گرفته شده است و همچنین کار دریافت بستههای مرتبط و به روز رسانی ارجاعات نیز در این حالت خودکار است. به علاوه توسط NuGet امکان دسترسی به کتابخانههایی که مثلا در گوگلکد قرار دارند و به صورت معمول امکان دریافت آنها برای ما میسر نیست، نیز بدون مشکل فراهم است (برای نمونه ELMAH، که اصل آن از گوگلکد قابل دریافت است؛ اما بسته نیوگت آن نیز در دسترس میباشد).
پس از نصب NuGet، تنها کافی است بر روی گره References در Solution explorer ویژوال استودیو، کلیک راست کرده و به کمک NuGet آخرین نگارش EF را نصب کرد. در گالری آنلاین آن، عموما EF اولین گزینه است (به علت تعداد بالای دریافت آن).
حین استفاده از NuGet جهت نصب Ef، ابتدا ارجاعاتی به اسمبلیهای زیر به برنامه اضافه خواهند شد:
System.ComponentModel.DataAnnotations.dll
System.Data.Entity.dll
EntityFramework.dll
بدیهی است بدون استفاده از NuGet، تمام این کارها را باید دستی انجام داد.
سپس در پوشهای به نام packages، فایلهای مرتبط با EF قرار خواهند گرفت که شامل اسمبلی آن به همراه ابزارهای DB Migration است. همچنین فایل packages.config که شامل تعاریف اسمبلیهای نصب شده است به پروژه اضافه میشود. NuGet به کمک این فایل و شماره نگارش درج شده در آن، شما را از به روز رسانیهای بعدی مطلع خواهد ساخت:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="4.3.1" />
</packages>
همچنین اگر به فایل app.config یا web.config برنامه نیز مراجعه کنید، یک سری تنظیمات ابتدایی اتصال به بانک اطلاعاتی در آن ذکر شده است:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.3.1.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</configSections>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
<parameters>
<parameter value="Data Source=(localdb)\v11.0; Integrated Security=True; MultipleActiveResultSets=True" />
</parameters>
</defaultConnectionFactory>
</entityFramework>
</configuration>
همانطور که ملاحظه میکنید بانک اطلاعاتی پیش فرضی که در اینجا ذکر شده است، LocalDB میباشد. این بانک اطلاعاتی را از این آدرس نیز میتوانید دریافت کنید.
البته ضرورتی هم به استفاده از آن نیست و از سایر نگارشهای SQL Server نیز میتوان استفاده کرد ولی خوب ... مزیت استفاده از آن برای کاربر نهایی این است که «نیازی به یک مهندس برای نصب، راه اندازی و نگهداری ندارد». تنها مشکل آن این است که از ویندوز XP پشتیبانی نمیکند. البته SQL Server CE 4.0 این محدودیت را ندارد.
ضمن اینکه باید درنظر داشت EF به فناوری میزبان خاصی گره نخورده است و مثالهایی که در اینجا بررسی میشوند صرفا تعدادی برنامه کنسول معمولی هستند و نکات عنوان شده در آنها در تمام فناوریهای میزبان موجود به یک نحو کاربرد دارند.
قراردادهای پیش فرض EF Code first
عنوان شد که اطلاعات کلاسهای ساده تشکیل دهنده مدلهای برنامه، برای تعریف جداول و فیلدهای یک بانک اطلاعات و همچنین مشخص سازی روابط بین آنها کافی نیستند و مرسوم است برای پر کردن این خلاء از یک سری متادیتا و یا Fluent API مهیا نیز استفاده گردد. اما در EF Code first یک سری قرار داد توکار نیز وجود دارند که مطلع بودن از آنها سبب خواهد شد تا حجم کدنویسی و تنظیمات جانبی این فریم ورک به حداقل برسند. برای نمونه مدلهای معروف بلاگ و مطالب آنرا درنظر بگیرید:
namespace EF_Sample01.Models
{
public class Post
{
public int Id { set; get; }
public string Title { set; get; }
public string Content { set; get; }
public virtual Blog Blog { set; get; }
}
}
using System.Collections.Generic;
namespace EF_Sample01.Models
{
public class Blog
{
public int Id { set; get; }
public string Title { set; get; }
public string AuthorName { set; get; }
public IList<Post> Posts { set; get; }
}
}
یکی از قراردادهای EF Code first این است که کلاسهای مدل شما را جهت یافتن خاصیتی به نام Id یا ClassId مانند BlogId، جستجو میکند و از آن به عنوان primary key و فیلد identity جدول بانک اطلاعاتی استفاده خواهد کرد.
همچنین در کلاس Blog، خاصیت لیستی از Posts و در کلاس Post خاصیت virtual ایی به نام Blog وجود دارند. به این ترتیب روابط بین دو کلاس و ایجاد کلید خارجی متناظر با آنرا به صورت خودکار انجام خواهد داد.
نهایتا از این اطلاعات جهت تشکیل database schema یا ساختار بانک اطلاعاتی استفاده میگردد.
اگر به فضاهای نام دو کلاس فوق دقت کرده باشید، به کلمه Models ختم شدهاند. به این معنا که در پوشهای به همین نام در پروژه جاری قرار دارند. یا مرسوم است کلاسهای مدل را در یک پروژه class library مجزا به نام DomainClasses نیز قرار دهند. این پروژه نیازی به ارجاعات اسمبلیهای EF ندارد و تنها به اسمبلی System.ComponentModel.DataAnnotations.dll نیاز خواهد داشت.
EF Code first چگونه کلاسهای مورد نظر را انتخاب میکند؟
ممکن است دهها و صدها کلاس در یک پروژه وجود داشته باشند. EF Code first چگونه از بین این کلاسها تشخیص خواهد داد که باید از کدامیک استفاده کند؟ اینجا است که مفهوم جدیدی به نام DbContext معرفی شده است. برای تعریف آن یک کلاس دیگر را به پروژه برای مثال به نام Context اضافه کنید. همچنین مرسوم است که این کلاس را در پروژه class library دیگری به نام DataLayer اضافه میکنند. این پروژه نیاز به ارجاعی به اسمبلیهای EF خواهد داشت. در ادامه کلاس جدید اضافه شده باید از کلاس DbContext مشتق شود:
using System.Data.Entity;
using EF_Sample01.Models;
namespace EF_Sample01
{
public class Context : DbContext
{
public DbSet<Blog> Blogs { set; get; }
public DbSet<Post> Posts { set; get; }
}
}
سپس در اینجا به کمک نوع جنریکی به نام DbSet، کلاسهای دومین برنامه را معرفی میکنیم. به این ترتیب، EF Code first ابتدا به دنبال کلاسی مشتق شده از DbContext خواهد گشت. پس از یافتن آن، خواصی از نوع DbSet را بررسی کرده و نوعهای متناظر با آنرا به عنوان کلاسهای دومین درنظر میگیرد و از سایر کلاسهای برنامه صرفنظر خواهد کرد. این کل کاری است که باید انجام شود.
اگر دقت کرده باشید، نام کلاسهای موجودیتها، مفرد هستند و نام خواص تعریف شده به کمک DbSet، جمع میباشند که نهایتا متناظر خواهند بود با نام جداول بانک اطلاعاتی تشکیل شده.
تشکیل خودکار بانک اطلاعاتی و افزودن اطلاعات به جداول
تا اینجا بدون تهیه یک بانک اطلاعاتی نیز میتوان از کلاس Context تهیه شده استفاده کرد و کار کدنویسی را آغاز نمود. بدیهی است جهت اجرای نهایی کدها، نیاز به یک بانک اطلاعاتی خواهد بود. اگر تنظیمات پیش فرض فایل کانفیگ برنامه را تغییر ندهیم، از همان defaultConnectionFactory یاده شده استفاده خواهد کرد. در این حالت نام بانک اطلاعاتی به صورت خودکار تنظیم شده و مساوی «EF_Sample01.Context» خواهد بود.
برای سفارشی سازی آن نیاز است فایل app.config یا web.config برنامه را اندکی ویرایش نمود:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
...
</configSections>
<connectionStrings>
<clear/>
<add name="Context"
connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true"
providerName="System.Data.SqlClient"
/>
</connectionStrings>
...
</configuration>
در اینجا به بانک اطلاعاتی testdb2012 در وهله پیش فرض SQL Server نصب شده، اشاره شده است. فقط باید دقت داشت که تگ configSections باید در ابتدای فایل قرار گیرد و مابقی تنظیمات پس از آن.
یا اگر علاقمند باشید که از SQL Server CE استفاده کنید، تنظیمات رشته اتصالی را به نحو زیر مقدار دهی نمائید:
<connectionStrings>
<add name="MyContextName"
connectionString="Data Source=|DataDirectory|\Store.sdf"
providerName="System.Data.SqlServerCe.4.0" />
</connectionStrings>
در هر دو حالت، name باید به نام کلاس مشتق شده از DbContext اشاره کند که در مثال جاری همان Context است.
یا اگر علاقمند بودید که این قرارداد توکار را تغییر داده و نام رشته اتصالی را با کدنویسی تعیین کنید، میتوان به نحو زیر عمل کرد:
public class Context : DbContext
{
public Context()
: base("ConnectionStringName")
{
}
البته ضرورتی ندارد این بانک اطلاعاتی از پیش موجود باشد. در اولین بار اجرای کدهای زیر، به صورت خودکار بانک اطلاعاتی و جداول Blogs و Posts و روابط بین آنها تشکیل میگردد:
using EF_Sample01.Models;
namespace EF_Sample01
{
class Program
{
static void Main(string[] args)
{
using (var db = new Context())
{
db.Blogs.Add(new Blog { AuthorName = "Vahid", Title = ".NET Tips" });
db.SaveChanges();
}
}
}
}
در این تصویر چند نکته حائز اهمیت هستند:
الف) نام پیش فرض بانک اطلاعاتی که به آن اشاره شد (اگر تنظیمات رشته اتصالی قید نگردد).
ب) تشکیل خودکار primary key از روی خواصی به نام Id
ج) تشکیل خودکار روابط بین جداول و ایجاد کلید خارجی (به کمک خاصیت virtual تعریف شده)
د) تشکیل جدول سیستمی به نام dbo.__MigrationHistory که از آن برای نگهداری سابقه به روز رسانیهای ساختار جداول کمک گرفته خواهد شد.
ه) نوع و طول فیلدهای متنی، nvarchar از نوع max است.
تمام اینها بر اساس پیش فرضها و قراردادهای توکار EF Code first انجام شده است.
در کدهای تعریف شده نیز، ابتدا یک وهله از شیء Context ایجاد شده و سپس به کمک آن میتوان به جدول Blogs اطلاعاتی را افزود و در آخر ذخیره نمود. استفاده از using هم دراینجا نباید فراموش شود، زیرا اگر استثنایی در این بین رخ دهد، کار پاکسازی منابع و بستن اتصال گشوده شده به بانک اطلاعاتی به صورت خودکار انجام خواهد شد.
در ادامه اگر بخواهیم مطلبی را به Blog ثبت شده اضافه کنیم، خواهیم داشت:
using EF_Sample01.Models;
namespace EF_Sample01
{
class Program
{
static void Main(string[] args)
{
//addBlog();
addPost();
}
private static void addPost()
{
using (var db = new Context())
{
var blog = db.Blogs.Find(1);
db.Posts.Add(new Post
{
Blog = blog,
Content = "data",
Title = "EF"
});
db.SaveChanges();
}
}
private static void addBlog()
{
using (var db = new Context())
{
db.Blogs.Add(new Blog { AuthorName = "Vahid", Title = ".NET Tips" });
db.SaveChanges();
}
}
}
}
متد db.Blogs.Find، بر اساس primary key بلاگ ثبت شده، یک وهله از آنرا یافته و سپس از آن جهت تشکیل شیء Post و افزودن آن به جدول Posts استفاده میشود. متد Find ابتدا Contxet جاری را جهت یافتن شیءایی با id مساوی یک جستجو میکند (اصطلاحا به آن first level cache هم گفته میشود). اگر موفق به یافتن آن شد، بدون صدور کوئری اضافهای به بانک اطلاعاتی از اطلاعات همان شیء استفاده خواهد کرد. در غیراینصورت نیاز خواهد داشت تا ابتدا کوئری لازم را به بانک اطلاعاتی ارسال کرده و اطلاعات شیء Blog متناظر با id=1 را دریافت کند. همچنین اگر نیاز داشتیم تا تنها با سطح اول کش کار کنیم، در EF Code first میتوان از خاصیتی به نام Local نیز استفاده کرد. برای مثال خاصیت db.Blogs.Local بیانگر اطلاعات موجود در سطح اول کش میباشد.
نهایتا کوئری Insert تولید شده توسط آن به شکل زیر خواهد بود (لاگ شده توسط برنامه SQL Server Profiler):
exec sp_executesql N'insert [dbo].[Posts]([Title], [Content], [Blog_Id])
values (@0, @1, @2)
select [Id]
from [dbo].[Posts]
where @@ROWCOUNT > 0 and [Id] = scope_identity()',
N'@0 nvarchar(max) ,@1 nvarchar(max) ,@2 int',
@0=N'EF',
@1=N'data',
@2=1
این نوع کوئرهای پارامتری چندین مزیت مهم را به همراه دارند:
الف) به صورت خودکار تشکیل میشوند. تمام کوئریهای پشت صحنه EF پارامتری هستند و نیازی نیست مرتبا مزایای این امر را گوشزد کرد و باز هم عدهای با جمع زدن رشتهها نسبت به نوشتن کوئریهای نا امن SQL اقدام کنند.
ب) کوئرهای پارامتری در مقابل حملات تزریق اس کیوال مقاوم هستند.
ج) SQL Server با کوئریهای پارامتری همانند رویههای ذخیره شده رفتار میکند. یعنی query execution plan محاسبه شده آنها را کش خواهد کرد. همین امر سبب بالا رفتن کارآیی برنامه در فراخوانیهای بعدی میگردد. الگوی کلی مشخص است. فقط پارامترهای آن تغییر میکنند.
د) مصرف حافظه SQL Server کاهش مییابد. چون SQL Server مجبور نیست به ازای هر کوئری اصطلاحا Ad Hoc رسیده یکبار execution plan متفاوت آنها را محاسبه و سپس کش کند. این مورد مشکل مهم تمام برنامههایی است که از کوئریهای پارامتری استفاده نمیکنند؛ تا حدی که گاهی تصور میکنند شاید SQL Server دچار نشتی حافظه شده، اما مشکل جای دیگری است.
مشکل! ساختار بانک اطلاعاتی تشکیل شده مطلوب کار ما نیست.
تا همینجا با حداقل کدنویسی و تنظیمات مرتبط با آن، پیشرفت خوبی داشتهایم؛ اما نتیجه حاصل آنچنان مطلوب نیست و نیاز به سفارشی سازی دارد. برای مثال طول فیلدها را نیاز داریم به مقدار دیگری تنظیم کنیم، تعدادی از فیلدها باید به صورت not null تعریف شوند یا نام پیش فرض بانک اطلاعاتی باید مشخص گردد و مواردی از این دست. با این موارد در قسمتهای بعدی بیشتر آشنا خواهیم شد.
در سایت رسمی nic.ir ، دومین جاری را ثبت و سپس برای تعیین DNS آن، از سرویس رایگان cloudflare.com استفاده کردم که خلاصهای از روش انجام اینکار را در ادامه مطالعه خواهید کرد.
ثبت دومین در سایت nic.ir
صرفنظر از معلق شدن دومین info. سایت، شاید جالب باشد بدانید قیمت تمدید این نوع دامنهها برای یکسال چقدر شدهاست:
این رقم، بیش 10 برای رقمی است که در ابتدای کار این سایت، برای ثبت دامنه پرداخت کرده بودم. اما ... ثبت یک دامنهی ir.، در سایت رسمی nic.ir برای 5 سال، دقیقا 48 هزار تومان تمام میشود که فوق العادهاست!
برای شروع به کار با سایت nic.ir، ابتدا نیاز است یک شناسه را در این سایت ایجاد کنید. برای مثال اگر فقط میخواهید یک دومین ir. ساده را داشته باشید، همان انتخاب گزینهی اول «شخص حقیقی» کفایت میکند.
مرحلهی بعد، تکمیل فرم متناظر با آن است که در اینجا اطلاعات را باید با همان قالبی که در مثالهای آن ذکر کرده، وارد کنید. در این فرم، پرسش محرمانه را خوب بخاطر بسپارید؛ چون در حین تکمیل قسمتهای بعدی کار، مدام سؤال پرسیده میشود. همچنین تمام مکاتبهها و اطلاعات مراحل بعدی را در میلباکس خود دریافت خواهید کرد.
پس از تائید ایمیل خود، میتوانید از منوی «دامنهها / ثبت دامنه»، نسبت به ثبت یک دومین جدید و در همانجا پرداخت وجه متناظر با آن اقدام کنید. پس از مدتی (تا سه ساعت بعد)، این اطلاعات توسط nic.ir بررسی شده و تائیدیه نهایی را در میل باکس خود دریافت خواهید کرد.
تعریف name-serverهای مخصوص یک دومین ir.
تا اینجا، دومین شما تعریف و فعال شده ... اما قابل استفاده نیست. مرحلهی بعدی، تعریف رکوردهای DNS دومین است، تا پس از وارد کردن آدرس سایت در مرورگر، به آدرس IP متناظری (برای مثال آدرس IP ثابت سرور مجازی / VPS شما) اشاره کند. برای اینکار میتوان از سرویس رایگان cloudflare.com استفاده کرد.
در این سایت ثبت نام کنید و پس از فعالسازی ایمیل خود، از پلنهای مختلف کاربری آن، پلن رایگان آنرا که خدمات DNS را ارائه میدهد، انتخاب کنید. در اینجا میتوان از طریق منوی بالای صفحه و انتخاب گزینهی Add site، آدرس دومین خود را وارد کنید، تا مراحل ثبت اطلاعات DNS آن آغاز شود.
پس از ثبت سایت خود در cloudflare.com، باید به اطلاعاتی که ارائه میکند، دقت داشت:
عنوان میکند که به محل ثبت دامنهی خود مراجعه کرده و اطلاعات فوق را در آن وارد کنید (منظور همان دو nameserver جدید lovisa.ns.cloudflare.com و todd.ns.cloudflare.com است)؛ همچنین اگر پیشتر اطلاعات دیگری را در آنجا وارد کرده بودید، باید تمام آنها را هم حذف کنید. در غیراینصورت درخواست شما پردازش نخواهد شد.
برای این منظور به اکانت nic.ir خود وارد شده و به قسمت «دامنه / دامنههای من» وارد شوید. در اینجا بر روی لینک NS ای که مشاهده میکنید، کلیک کنید:
منظور از NS، همان nameserver هایی است که عنوان شد. اکنون در صفحهی تنظیمات DNS، اطلاعات NSهای cloudflare.com را وارد کرده و ذخیره کنید:
تکمیل ثبت رکوردهای DNS یک دومین ir.
پس از ثبت nameserverهای cloudflare.com در سایت nic.ir، مرحلهی آخر کار، تکمیل رکوردهای DNS دومین است. به همین جهت به اکانت cloudflare.com خود وارد شده و در تنظیمات دومین ثبت شده، گزینهی DNS را انتخاب کرده و رکوردهای آن را به صورت زیر تکمیل کنید:
بدیهی است در اینجا تنها تفاوتهای مورد نیاز، تغییر نام دامنه و آدرس IP متناظر با آن است. در مورد رکورد spf1 در اینجا بیشتر توضیح داده شدهاست.
پس از اینکار، بر روی لینک منوی overview در بالای صفحه کلیک کرده و در پایین این صفحه، بر روی دکمهی «check nameservers» کلیک کنید، تا cloudflare کار بررسی اطلاعات تنظیم شدهی توسط شما را شروع کند:
این بررسی نیز چند ساعتی طول میکشد و نتیجهی نهایی را از طریق ایمیل دریافت خواهید کرد. پس از فعال شدن دومین خود در cloudflare، مجددا به قسمت تنظیمات DNS آن وارد شده و DNS Sec را نیز بر روی آن فعال کنید:
اکنون دومین شما قابل استفادهاست!
شاید سادهترین تعریف برای Saltarelle این باشد که «کامپایلریست که کدهای C# را به جاوا اسکریپت تبدیل میکند». محاسن زیادی را میتوان برای اینگونه کامپایلرها نام برد؛ مخصوصا در پروژههای سازمانی که نگهداری از کدهای جاوا اسکریپت بسیار سخت و گاهی خارج از توان است و این شاید مهمترین عامل ظهور ابزارهای جدید از قبیل Typescript باشد.
در هر صورت اگر حوصله و وقت کافی برای تجهیز تیم نرم افزاری، به دانش یک زبان جدید مانند Typescript نباشد، استفاده از توان و دانش تیم تولید، از زبان C# سادهترین راه حل است و اگر ابزاری مطمئن برای استفاده از حداکثر قدرت JavaScript همراه با امکانات نگهداری و توسعه کدها وجود داشته باشد، بی شک Saltarelle یکی از بهترینهای آنهاست.
قبلا کامپایلر هایی از این دست مانند Script# وجود داشتند، اما فاقد همه امکانات C# بوده وعملا قدرت کامل C# در کد نویسی وجود نداشت. اما با توجه به ادعای توسعه دهندگان این کامپایلر سورس باز در استفادهی حداکثری از کلیه ویژگیهای C# 5 و با وجود Library های متعدد میتوان Saltarelle را عملا یک کامپایلر موفق در این زمینه دانست.
برای استفاده از Saltarelle در یک برنامه وب ساده باید یک پروژه Console Application به Solution اضافه کرد و پکیج Saltarelle.Compiler را از nuget نصب نمایید. بعد از نصب این پکیج، کلیه Reference ها از پروژه حدف میشوند و هر بار Build توسط کامپایلر Saltarelle انجام میشود. البته با اولین Build، مقداری Error را خواهید دید که برای از بین بردنشان نیاز است پکیج Saltarelle.Runtime را نیز در این پروژه نصب نمایید:
PM> Install-Package Saltarelle.Compiler PM> Install-Package Saltarelle.Runtime
در صورتیکه کماکان Build نهایی با Error همرا بود، یکبار این پروژه را Unload و سپس مجددا Load نمایید
UI یک پروژه وب MVC است و Client یک Console Application که پکیجهای مورد نیاز Saltarelle روی آن نصب شده است.
در صورتیکه پروژه را Build نماییم و نگاهی به پوشهی Debug بیاندازیم، یک فایل JavaScript همنام پروژه وجود دارد:
برای اینکه بعد از هر بار Build ، فایل اسکریپت به پوشهی مربوطه در پروژه UI منتقل شود کافیست کد زیر را در Post Build پروژه Client بنویسیم:
copy "$(TargetDir)$(TargetName).js" "$(SolutionDir)SalratelleSample.UI\Scripts"
اکنون پس از هر بار Build ، فایل اسکریپت مورد نظر در پوشهی Scripts پروژه UI آپدیت میشود:
در ادامه کافیست فایل اسکریپت را به layout اضافه کنیم.
<script src="~/Scripts/SaltarelleSample.Client.js"></script>
در پوشهی Saltarelle.Runtime در پکیجهای نصب شده، یک فایل اسکریپت به نام mscorlib.min.js نیز وجود دارد که حاوی اسکریپتهای مورد نیاز Saltarelle در هنگام اجراست. آن را به پوشه اسکریپتهای پروژه UI کپی نمایید و سپس به Layout اضافه کنید.
<script src="~/Scripts/mscorlib.min.js"></script> <script src="~/Scripts/SaltarelleSample.Client.js"></script>
حال نوبت به اضافه نمودن libraryهای مورد نیازمان است. برای دسترسی به آبجکت هایی از قبیل document, window, element و غیره در جاوااسکریپت میتوان پکیج Saltarelle.Web را در پروژهی Client نصب نمود و برای دسترسی به اشیاء و فرمانهای jQuery، پکیج Salratelle.jQuery را نصب نمایید.
> Install-Package Saltarelle.Web > Install-Package Saltarelle.jQuery
به این libraryها imported library میگویند. در واقع، در زمان کامپایل، برای این libraryها فایل اسکریپتی تولید نمیشود و فقط آبجکتهای #C هستند که که هنگام کامپایل تبدیل به کدهای ساده اسکریپت میشوند که اگر اسکریپت مربوط به آنها به صفحه اضافه نشده باشد، اجرای اسکریپت با خطا مواجه میشود.
به طور سادهتر وقتی از jQuery library استفاده میکنید هیچ فایل اسکریپت اضافهای تولید نمیشود، اما باید اسکریپت jQuery به صفحه شما اضافه شده باشد.
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
مثال ما یک اپلیکیشن ساده برای خواندن فیدهای همین سایت است. ابتدا کدهای سمت سرور را در پروژه UI می نویسیم.
کلاسهای مورد نیاز ما برای این فید ریدر:
public class Feed { public string FeedId { get; set; } public string Title { get; set; } public string Address { get; set; } } public class Item { public string Title { get; set; } public string Link { get; set; } public string Description { get; set; } }
و یک کلاس برای مدیریت منطق برنامه
public class SiteManager { private static List<Feed> _feeds; public static List<Feed> Feeds { get { if (_feeds == null) _feeds = CreateSites(); return _feeds; } } private static List<Feed> CreateSites() { return new List<Feed>() { new Feed(){ FeedId = "1", Title = "آخرین تغییرات سایت", Address = "https://www.dntips.ir/rss.xml" }, new Feed(){ FeedId = "2", Title = "مطالب سایت", Address = "https://www.dntips.ir/feeds/posts" }, new Feed(){ FeedId = "3", Title = "نظرات سایت", Address = "https://www.dntips.ir/feeds/comments" }, new Feed(){ FeedId = "4", Title = "خلاصه اشتراک ها", Address = "https://www.dntips.ir/feed/news" }, }; } public static IEnumerable<Item> GetNews(string id) { XDocument feedXML = XDocument.Load(Feeds.Find(s=> s.FeedId == id).Address); var feeds = from feed in feedXML.Descendants("item") select new Item { Title = feed.Element("title").Value, Link = feed.Element("link").Value, Description = feed.Element("description").Value }; return feeds; } }
کلاس SiteManager فقط یک لیست از فیدها دارد و متدی که با گرفتن شناسهی فید ، یک لیست از آیتمهای موجود در آن فید ایجاد میکند.
حال دو ApiController برای دریافت دادهها ایجاد میکنیم
public class FeedController : ApiController { // GET api/<controller> public IEnumerable<Feed> Get() { return SiteManager.Feeds; } } public class ItemsController : ApiController { // GET api/<controller>/5 public IEnumerable<Item> Get(string id) { return SiteManager.GetNews(id); } }
در View پیشفرض که Index از کنترلر Home است، یک Html ساده برای
فرم صفحه اضافه میکنیم
<div> <div> <h2>Feeds</h2> <ul id="Feeds"> </ul> </div> <div> <h2>Items</h2> <p id="FeedItems"> </p> </div> </div>
در المنت Feeds لیست فیدها را قرار میدهیم و در FeedItems آیتمهای مربوط به هر فید. حال به سراغ کدهای سمت کلاینت میرویم و به جای جاوا اسکریپت از Saltarelle استفاده میکنیم.
کلاس Program را از پروژه Client باز میکنیم و متد Main را به شکل زیر تغییر میدهیم:
static void Main() { jQuery.OnDocumentReady(() => { FillFeeds(); }); }
بعد از کامپایل شدن، کد #C شارپ بالا به صورت زیر در میآید:
$SaltarelleSample_Client_$Program.$main = function() { $(function() { $SaltarelleSample_Client_$Program.$fillFeeds(); }); }; $SaltarelleSample_Client_$Program.$main();
و این همان متد معروف jQuery است که Saltarelle.jQuery برایمان ایجاد کرده است.
متد FillFeeds را به شکل زیر پیاده سازی میکنیم
private static void FillFeeds() { jQuery.Ajax(new jQueryAjaxOptions() { Url = "/api/feed", Type = "GET", Success = (d,t,r) => { // Fill var ul = jQuery.Select("#Feeds"); jQuery.Each((List<Feed>)d, (idx,i) => { var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer"); li.Click(eve => { FillData(i.FeedId); }); ul.Append(li); }); } }); }
آبجکت jQuery، متدی به نام Ajax دارد که یک شی از کلاس jQueryAjaxOptions را به عنوان پارامتر میپذیرد. این کلاس کلیه خصوصیات متد Ajax در jQuery را پیاده سازی میکند. نکته شیرین آن توانایی نوشتن lambda برای Delegate هاست.
خاصیت Success یک Delegate است که 3 پارامتر ورودی را میپذیرد.
public delegate void AjaxRequestCallback(object data, string textStatus, jQueryXmlHttpRequest request);
data همان مقداریست که api باز میگرداند که یک لیست از Feed هاست. برای زیبایی کار، من یک کلاس Feed در پروژه Client اضافه میکنم که خصوصیاتی مشترک با کلاس اصلی سمت سرور دارد و مقدار برگشی Ajax را به آن تبدیل میکنم.
کلاس Feed و Item
[PreserveMemberCase()] public class Feed { //[ScriptName("FeedId")] public string FeedId; //[ScriptName("Title")] public string Title; //[ScriptName("Address")] public string Address; } [PreserveMemberCase()] public class Item { // [ScriptName("Title")] public string Title; // [ScriptName("Link")] public string Link; // [ScriptName("Description")] public string Description; }
jQuery.Each((List<Feed>)d, (idx,i) => { var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer"); li.Click(eve => { FillData(i.FeedId); }); ul.Append(li); });
به ازای هر آیتمی که در شیء بازگشتی وجود دارد، با استفاد از متد each در jQuery یک li ایجاد میکنیم. همان طور که میبینید کلیه خواص، به شکل Fluent قابل اضافه شدن میباشد. سپس برای li یک رویداد کلیک که در صورت وقوع، متد FillData را با شناسه فید کلیک شده فراخوانی میکند و در آخر li را به المنت ul اضافه میکنیم.
برای هر کلیک هم مانند مثال بالا api را با شناسهی فید مربوطه فراخوانی کرده و به ازای هر آیتم، یک سطر ایجاد میکنیم.
private static void FillData(string p) { jQuery.Ajax(new jQueryAjaxOptions() { Url = "/api/items/" + p, Type = "GET", Success = (d, t, r) => { var content = jQuery.Select("#FeedItems"); content.Html(""); foreach (var item in (List<Item>)d) { var row = jQuery.Select("<div>").AddClass("row").CSS("direction", "rtl"); var link = jQuery.Select("<a>").Attribute("href", item.Link).Text(item.Title); row.Append(link); content.Append(row); } } }); }
در این مثال ما از Saltarelle.jQuery برای استفاده از jQuery.js استفاده نمودیم. libraryهای متعددی برای Saltarelle از قبیل linq,angular,knockout,jQueryUI,nodeJs ایجاد شده و همچنین قابلیتهای زیادی برای نوشتن imported libraryهای سفارشی نیز وجود دارد.
مطمئنا استفاده از چنین کامپایلرهایی راه حلی سریع برای رهایی از مشکلات متعدد کد نویسی با جاوا اسکریپت در نرم افزارهای بزرگ مقیاس است. اما مقایسه آنها با ابزارهایی از قبیل typescript احتیاج به زمان و تجربه کافی در این زمینه دارد.
در حالت پیشرفتهی تزریق وابستگیها در دات نت، با توجه به اینکه کار وهله سازی کلاسها به یک کتابخانه جانبی به نام IoC Container واگذار میشود، امکان یک سری دخل و تصرف نیز در این میان فراهم میگردد. برای مثال الان که ما میتوانیم یک کلاس را توسط IoC container به صورت خودکار وهله سازی کنیم، خوب، چرا اجرای متدهای آنرا تحت نظر قرار ندهیم. مثلا حاصل آنها را بتوانیم پیش از اینکه به فراخوان بازگشت داده شود، کش کنیم یا کلا تغییر دهیم. به این کار AOP یا Aspect orinted programming نیز گفته میشود.
واقعیت این است که یک چنین مفهومی از سالهای دور به نام Hooking در برنامههای WIN32 API خالص نیز وجود داشته است. Hookها یا قلابها دقیقا کار Interception دنیای AOP را انجام میدهند. به این معنا که خودشان را بجای یک متد ثبت کرده و کار ردیابی یا حتی تغییر عملکرد آن متد خاص را میتوانند انجام دهند. برای مثال اگر برای متد gethostbyname ویندوز یک Hook بنویسیم، برنامه استفاده کننده، تنها متد ما را بجای متد اصلی gethostbyname واقع در Kernel32 ویندوز، مشاهده میکند و درخواستهای DNS خودش را به این متد ویژه ما ارسال خواهد کرد؛ بجای ارسال درخواستها به متد اصلی. در این بین میتوان درخواستهای DNS را لاگ کرد و یا حتی تغییر جهت داد.
انجام Interception در دنیای دات نت با استفاده از امکانات Reflection و Reflection.Emit قابل انجام است و یا حتی بازنویسی اسمبلیها و افزودن کدهای IL مورد نیاز به آنها که به آن IL Weaving هم گفته میشود. اما در دنیای WIN32 انجام چنین کاری ساده نیست و ترکیبی است از زبان اسمبلی و کتابخانههای نوشته شده به زبان C.
برای ساده سازی نوشتن Hookهای ویندوزی، کتابخانهای به نام easy hook ارائه شده است که امکان تزریق Hookهای دات نتی را به درون پروسه برنامههای Native ویندوز دارد. این قلابها که در اینجا متدهای دات نتی هستند، نهایتا بجای توابع اصلی ویندوز جا زده خواهند شد. بنابراین میتوانند عملیات آنها را ردیابی کنند و یا حتی پارامترهای آنها را دریافت و مقدار دیگری را بجای تابع اصلی، بازگشت دهند. در ادامه قصد داریم اصول و نکات کار با easy hook را در طی یک مثال بررسی کنیم.
صورت مساله
میخواهیم کلیه درخواستهای تاریخ اکسپلورر ویندوز را ردیابی کرده و بجای ارائه تاریخ استاندارد میلادی، تاریخ شمسی را جایگزین آن کنیم.
از کجا شروع کنیم؟
ابتدا باید دریابیم که اکسپلورر ویندوز از چه توابع API ایی برای پردازشهای درخواستهای تاریخ و ساعت خودش استفاده میکند، تا بتوانیم برای آنها Hook بنویسیم. برای این منظور میتوان از برنامهی بسیار مفیدی به نام API Monitor استفاده کرد. این برنامهی رایگان را از آدرس ذیل میتوانید دریافت کنید:
اگر علاقمند به ردیابی برنامههای 32 بیتی هستید باید apimonitor-x86.exe را اجرا کنید و اگر نیاز به ردیابی برنامههای 64 بیتی دارید باید apimonitor-x64.exe را اجرا نمائید. بنابراین اگر پس از اجرای این برنامه، برای مثال فایرفاکس را در لیست پروسههای آن مشاهده نکردید، یعنی apimonitor-x64.exe را اجرا کردهاید؛ از این جهت که فایرفاکس عمومی تا این تاریخ، نسخه 32 بیتی است و نه 64 بیتی.
پس از اجرای برنامه API Monitor، در قسمت API Filter آن باید مشخص کنیم که علاقمند به ردیابی کدامیک از توابع API ویندوز هستیم. در اینجا چون نمیدانیم دقیقا کدام تابع کار ارائه تاریخ را به اکسپلورر ویندوز عهده دار است، شروع به جستجو میکنیم و هر تابعی را که نام date یا time در آن وجود داشت، تیک میزنیم تا در کار ردیابی لحاظ شود.
سپس نیاز است بر روی نام اکسپلورر در لیست پروسههای این برنامه کلیک راست کرده و گزینه Start monitoring را انتخاب کرد:
اندکی صبر کنید یا یک صفحه جدید اکسپلورر ویندوز را باز کنید تا کار ردیابی شروع شود:
همانطور که مشاهده میکنید، ویندوز برای ردیابی تاریخ در اکسپلورر خودش از توابع GetDateFormatW و GetTimeFormatW استفاده میکند. ابتدا یک تاریخ را توسط آرگومان lpDate یا lpTime به یکی از توابع یاد شده ارسال کرده و سپس خروجی را از آرگومان lpDateStr یا lpTimeStr دریافت میکند.
خوب؛ به نظر شما اگر این خروجیها را با یک ساعت و تاریخ شمسی جایگزین کنیم بهتر نیست؟!
نوشتن Hook برای توابع GetDateFormatW و GetTimeFormatW ویندوز اکسپلورر
ابتدا کتابخانه easy hook را از مخزن کد CodePlex آن دریافت کنید:
سپس یک پروژه کنسول ساده را آغاز کنید. همچنین به این Solution، یک پروژه Class library جدید را نیز اضافه نمائید. پروژه کنسول، کار نصب Hook را انجام میدهد و پروژه کتابخانهای اضافه شده، کار مدیریت هوکها را انجام خواهد داد. سپس به هر دو پروژه، ارجاعی را به اسمبلی EasyHook.dll اضافه کنید.
الف) ساختار کلی کلاس Hook
کلاس Hook واقع در پروژه Class library باید یک چنین امضایی را داشته باشد:
namespace ExplorerPCal.Hooks { public class GetDateTimeFormatInjection : IEntryPoint { public GetDateTimeFormatInjection(RemoteHooking.IContext context, string channelName) { // connect to host... _interface = RemoteHooking.IpcConnectClient<MessagesReceiverInterface>(channelName); _interface.Ping(); } public void Run(RemoteHooking.IContext context, string channelName) { } } }
ب) نوشتن توابع Hook
کار نوشتن قلاب برای توابع API ویندوز در متد Run انجام میشود. سپس باید توسط متد LocalHook.Create کار را شروع کرد. در اینجا مشخص میکنیم که نیاز است تابع GetDateFormatW واقع در kernel32.dll ردیابی شود.
public void Run(RemoteHooking.IContext context, string channelName) { GetDateFormatHook = LocalHook.Create( InTargetProc: LocalHook.GetProcAddress("kernel32.dll", "GetDateFormatW"), InNewProc: new GetDateFormatDelegate(getDateFormatInterceptor), InCallback: this);
ج) نحوه مشخص سازی امضای delegateهای Hook
اگر امضای متد GetDateFormatW به نحو ذیل باشد:
[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)] public static extern int GetDateFormatW( uint locale, uint dwFlags, // NLS_DATE_FLAGS SystemTime lpDate, [MarshalAs(UnmanagedType.LPWStr)] string lpFormat, StringBuilder lpDateStr, int sbSize);
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)] private delegate int GetDateFormatDelegate( uint locale, uint dwFlags, SystemTime lpDate, [MarshalAs(UnmanagedType.LPWStr)] string lpFormat, StringBuilder lpDateStr, int sbSize);
private int getDateFormatInterceptor( uint locale, uint dwFlags, SystemTime lpDate, string lpFormat, StringBuilder lpDateStr, int sbSize) { }
د) نصب Hook نوشته شده
باید دقت داشت که هر دو برنامه نصاب Hook و همچنین کتابخانه Hook، باید دارای امضای دیجیتال باشند. بنابراین به برگه signing خواص پروژه مراجعه کرده و یک فایل snk را به هر دو پروژه اضافه نمائید.
سپس در برنامه نصاب، یک کلاس را با امضای ذیل تعریف کنید:
public class MessagesReceiverInterface : MarshalByRefObject { public void Ping() { } }
سپس در ابتدای برنامه نصاب، یک کانال Remoting باز شده (که آرگومان جنریک آن دقیقا همین نام کلاس MessagesReceiverInterface فوق را دریافت میکند)
var channel = RemoteHooking.IpcCreateServer<MessagesReceiverInterface>(ref _channelName, WellKnownObjectMode.SingleCall);
RemoteHooking.Inject( explorer.Id, InjectionOptions.Default | InjectionOptions.DoNotRequireStrongName, "ExplorerPCal.Hooks.dll", // 32-bit version (the same, because of using AnyCPU) "ExplorerPCal.Hooks.dll", // 64-bit version (the same, because of using AnyCPU) _channelName );
پارامتر و پارامترهای بعدی، اطلاعاتی هستند که به سازنده کلاس هوک ارسال میشوند. بنابراین این سازنده میتواند تعداد پارامترهای متغیری داشته باشد:
.ctor(IContext, %ArgumentList%) void Run(IContext, %ArgumentList%)
چند نکته تکمیلی مهم برای کار با کتابخانه Easy hook
- کتابخانه easy hook فعلا با ویندوز 8 سازگار نیست.
- برای توزیع هوکهای خود باید تمام فایلهای همراه کتابخانه easy hook را نیز توزیع کنید و فقط به چند DLL ابتدایی آن بسنده نباید کرد.
- اگر هوک شما بلافاصله سبب کرش پروسه هدف شد، یعنی امضای تابع API شما ایراد دارد و نیاز است چندین و سایت را جهت یافتن امضایی صحیح بررسی کنید. برای مثال در امضای عمومی متد GetDateFormatW، پارامتر SystemTime به صورت struct تعریف شده است؛ درحالیکه ویندوز ممکن است برای دریافت زمان جاری به این پارامتر نال ارسال کند. اما struct دات نت برخلاف struct زبان C یک value type است و نال پذیر نیست. به همین جهت کلیه امضاهای عمومی که در مورد این متد در اینترنت یافت میشوند، در عمل غلط هستند و باید SystemTime را یک کلاس دات نتی که Refrence type است، تعریف کرد تا نال پذیر شود و hook کرش نکرده یا اشتباه عمل نکند.
- زمانیکه یک هوک easy hook بر روی پروسه هدف نصب میشود، دیگر قابل unload کامل نیست و نیاز است برای کارهای برنامه نویسی و به روز رسانی فایل dll جدید، پروسه هدف را خاتمه داد.
- متد Run هوک باید همیشه در حال اجرا باشد تا توسط CLR بلافاصه خاتمه نیافته و هوک از حافظه خارج نشود. اینکار را توسط روش ذیل انجام دهید:
try { while (true) { Thread.Sleep(500); _interface.Ping(); } } catch { _interface = null; // .NET Remoting will raise an exception if host is unreachable }
- استفاده همزمان از API Monitor ذکر شده در ابتدای بحث و یک هوک نصب شده، سبب کرش برنامه هدف خواهد شد.
سورس کامل این پروژه را در اینجا میتوانید دریافت کنید
- چطور یک اپلیکیشن وب ASP.NET MVC 5 بسازید و آن را روی یک وب سایت Windows Azure منتشر کنید.
- چگونه از OAuth، OpenID و سیستم عضویت ASP.NET برای ایمن سازی اپلیکیشن خود استفاده کنید.
- چگونه از API جدید سیستم عضویت برای مدیریت اعضا و نقشها استفاده کنید.
- چگونه از یک دیتابیس SQL برای ذخیره دادهها در Windows Azure استفاده کنید.
توجه: برای تمام کردن این مقاله به یک حساب کاربری Windows Azure نیاز دارید، که بصورت رایگان میتوانید آن را بسازید. برای اطلاعات بیشتر به Windows Azure Free Trial مراجعه کنید.
در این مقاله:
- برپایی محیط توسعه (development environment)
- برپایی محیط Windows Azure
- ایجاد یک اپلیکیشن ASP.NET MVC 5
- توزیع اپلیکیشن روی Windows Azure
- افزودن یک دیتابیس به اپلیکیشن
- افزودن یک OAuth Provider
- استفاده از Membership API
- توزیع اپلیکیشن روی Windows Azure
- قدمهای بعدی
برپایی محیط توسعه
هنگامی که این مرحله با موفقیت به اتمام رسید، تمام ابزار لازم برای شروع به کار را در اختیار دارید.
برپایی محیط Windows Azure
وب سایت Windows Azure شما در یک محیط اشتراکی (shared) میزبانی میشود، و این بدین معنا است که وب سایتهای شما روی ماشینهای مجازی (virtual machines) اجرا میشوند که با مشتریان دیگر Windows Azure به اشتراک گذاشته شده اند. یک محیط میزبانی اشتراکی گزینه ای کم هزینه برای شروع کار با رایانشهای ابری است. اگر در آینده ترافیک وب سایت شما رشد چشم گیری داشته باشد، میتوانید اپلیکیشن خود را طوری توسعه دهید که به نیازهای جدید پاسخگو باشد و آن را روی یک ماشین مجازی اختصاصی (dedicated VMs) میزبانی کنید. اگر معماری پیچیدهتری نیاز دارید، میتوانید به یک سرویس Windows Azure Cloud مهاجرت کنید. سرویسهای ابری روی ماشینهای مجازی اختصاصی اجرا میشوند که شما میتوانید تنظیمات آنها را بر اساس نیازهای خود پیکربندی کنید.
- در پرتال مدیریتی Windows Azure روی Web Sites در قسمت چپ صفحه کلیک کنید، و گزینه New را برگزینید.
- روی Web Site و سپس Custom Create کلیک کنید.
- در مرحله Create Web Site در قسمت URL یک رشته وارد کنید که آدرسی منحصر بفرد برای اپلیکیشن شما خواهد بود. آدرس کامل وب سایت شما، ترکیبی از مقدار این فیلد و مقدار روبروی آن است.
- در لیست Database گزینه Create a free 20 MB SQL Database را انتخاب کنید.
- در لیست Region همان مقداری را انتخاب کنید که برای وب سایت تان انتخاب کرده اید. تنظیمات این قسمت مشخص میکند که ماشین مجازی (VM) شما در کدام مرکز داده (data center) خواهد بود.
- در قسمت DB Connection String Name مقدار پیش فرض DefaultConnection را بپذیرید.
- دکمه فلش پایین صفحه را کلیک کنید تا به مرحله بعد، یعنی مرحله Specify Database Settings بروید.
- در قسمت Name مقدار ContactDB را وارد کنید (تصویر زیر).
- در قسمت Server گزینه New SQL Database Server را انتخاب کنید. اگر قبلا دیتابیس ساخته اید میتوانید آن را از کنترل dropdown انتخاب کنید.
- مقدار قسمت Region را به همان مقداری که برای ایجاد وب سایت تان تنظیم کرده اید تغییر دهید.
- یک Login Name و Password مدیر (administrator) وارد کنید. اگر گزینه New SQL Database server را انتخاب کرده اید، چنین کاربری وجود ندارد و در واقع اطلاعات یک حساب کاربری جدید را وارد میکنید تا بعدا هنگام دسترسی به دیتابیس از آن استفاده کنید. اگر دیتابیس دیگری را از لیست انتخاب کرده باشید، اطلاعات یک حساب کاربری موجود از شما دریافت خواهد شد. در مثال این مقاله ما گزینه Advanced را رها میکنیم. همچنین در نظر داشته باشید که برای دیتابیسهای رایگان تنها از یک Collation میتوانید استفاده کنید.
دکمه تایید پایین صفحه را کلیک کنید تا مراحل تمام شود.
تصویر زیر استفاده از یک SQL Server و حساب کاربری موجود (existing) را نشان میدهد.
پرتال مدیریتی پس از اتمام مراحل، به صفحه وب سایتها باز میگردد. ستون Status نشان میدهد که سایت شما در حال ساخته شدن است. پس از مدتی (معمولا کمتر از یک دقیقه) این ستون نشان میدهد که سایت شما با موفقیت ایجاد شده. در منوی پیمایش سمت چپ، تعداد سایت هایی که در اکانت خود دارید در کنار آیکون Web Sites نمایش داده شده است، تعداد دیتابیسها نیز در کنار آیکون SQL Databases نمایش داده میشود.
یک اپلیکیشن ASP.NET MVC 5 بسازید
نوع پروژه را ASP.NET Web Application انتخاب کنید.
نکته: در تصویر بالا نام پروژه "MyExample" است اما حتما نام پروژه خود را به "ContactManager" تغییر دهید. قطعه کدهایی که در ادامه مقاله خواهید دید نام پروژه را ContactManager فرض میکنند.
در دیالوگ جدید ASP.NET نوع اپلیکیشن را MVC انتخاب کنید و دکمه Change Authentication را کلیک کنید.
گزینه پیش فرض Individual User Accounts را بپذیرید. برای اطلاعات بیشتر درباره متدهای دیگر احراز هویت به این لینک مراجعه کنید. دکمههای OK را کلیک کنید تا تمام مراحل تمام شوند.
تنظیم تیتر و پاورقی سایت
- فایل Layout.cshtml_ را باز کنید. دو نمونه از متن "My ASP.NET MVC Application" را با عبارت "Contact Manager" جایگزین کنید.
- عبارت "Application name" را هم با "CM Demo" جایگزین کنید.
اپلیکیشن را بصورت محلی اجرا کنید
اپلیکیشن شما فعلا آماده است و میتوانید آن را روی Windows Azure توزیع کنید. بعدا دیتابیس و دسترسی داده نیز اضافه خواهد شد.
اپلیکیشن را روی Windows Azure منتشر کنید
حال دیالوگ Import Publish Profile نمایش داده میشود.
یکی از متدهای زیر را استفاده کنید تا ویژوال استودیو بتواند به اکانت Windows Azure شما متصل شود.
- روی Sign In کلیک کنید تا با وارد کردن اطلاعات حساب کاربری وارد Windows Azure شوید.
- روی Manage subscriptions کلیک کنید تا یک management certificate نصب کنید، که دسترسی به حساب کاربری شما را ممکن میسازد.
در دیالوگ باکس Publish Web روی Publish کلیک کنید.
اپلیکیشن شما حالا در فضای ابری اجرا میشود. دفعه بعد که اپلیکیشن را منتشر کنید تنها فایلهای تغییر کرده (یا جدید) آپلود خواهند شد.
یک دیتابیس به اپلیکیشن اضافه کنید
کلاسهای مدل Contacts را اضافه کنید
نام کلاس را به Contact.cs تغییر دهید و دکمه Add را کلیک کنید.
کد فایل Contact.cs را با قطعه کد زیر مطابقت دهید.
using System.ComponentModel.DataAnnotations; using System.Globalization; namespace ContactManager.Models { public class Contact { public int ContactId { get; set; } public string Name { get; set; } public string Address { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } [DataType(DataType.EmailAddress)] public string Email { get; set; } } }
این کلاس موجودیت Contact را در دیتابیس معرفی میکند. داده هایی که میخواهیم برای هر رکورد ذخیره کنیم تعریف شده اند، بعلاوه یک فیلد Primary Key که دیتابیس به آن نیاز دارد.
یک کنترلر و نما برای دادهها اضافه کنید
در دیالوگ باکس Add Scaffold گزینه MVC 5 Controller with views, using EF را انتخاب کنید.
در دیالوگ Add Controller نام "CmController" را برای کنترلر وارد کنید. (تصویر زیر.)
در لیست Model گزینه (Contact (ContactManager.Models را انتخاب کنید.
در قسمت Data context class گزینه (ApplicationDbContext (ContactManager.Models را انتخاب کنید. این ApplicationDbContext هم برای اطلاعات سیستم عضویت و هم برای دادههای Contacts استفاده خواهد شد.
روی Add کلیک کنید. ویژوال استودیو بصورت خودکار با استفاده از Scaffolding متدها و Viewهای لازم برای عملیات CRUD را فراهم میکند، که همگی از مدل Contact استفاده میکنند.
فعالسازی مهاجرت ها، ایجاد دیتابیس، افزودن داده نمونه و یک راه انداز
در پنجره باز شده فرمان زیر را وارد کنید.
enable-migrations
فرمان enable-migrations یک پوشه با نام Migrations می سازد و فایلی با نام Configuration.cs را به آن اضافه میکند. با استفاده از این کلاس میتوانید دادههای اولیه دیتابیس را وارد کنید و مهاجرتها را نیز پیکربندی کنید.
در پنجره Package Manager Console فرمان زیر را وارد کنید.
add-migration Initial
فرمان add-migration initial فایلی با نام data_stamp> initial> ساخته و آن را در پوشه Migrations ذخیره میکند. در این مرحله دیتابیس شما ایجاد میشود. در این فرمان، مقدار initial اختیاری است و صرفا برای نامگذاری فایل مهاجرت استفاده شده. فایلهای جدید را میتوانید در Solution Explorer مشاهده کنید.
در کلاس Initial متد Up جدول Contacts را میسازد. و متد Down (هنگامی که میخواهید به وضعیت قبلی بازگردید) آن را drop میکند.
حال فایل Migrations/Configuration.cs را باز کنید. فضای نام زیر را اضافه کنید.
using ContactManager.Models;
حال متد Seed را با قطعه کد زیر جایگزین کنید.
protected override void Seed(ContactManager.Models.ApplicationDbContext context) { context.Contacts.AddOrUpdate(p => p.Name, new Contact { Name = "Debra Garcia", Address = "1234 Main St", City = "Redmond", State = "WA", Zip = "10999", Email = "debra@example.com", }, new Contact { Name = "Thorsten Weinrich", Address = "5678 1st Ave W", City = "Redmond", State = "WA", Zip = "10999", Email = "thorsten@example.com", }, new Contact { Name = "Yuhong Li", Address = "9012 State st", City = "Redmond", State = "WA", Zip = "10999", Email = "yuhong@example.com", }, new Contact { Name = "Jon Orton", Address = "3456 Maple St", City = "Redmond", State = "WA", Zip = "10999", Email = "jon@example.com", }, new Contact { Name = "Diliana Alexieva-Bosseva", Address = "7890 2nd Ave E", City = "Redmond", State = "WA", Zip = "10999", Email = "diliana@example.com", } ); }
این متد دیتابیس را Seed میکند، یعنی دادههای پیش فرض و اولیه دیتابیس را تعریف میکند. برای اطلاعات بیشتر به Seeding and Debugging Entity Framework (EF) DBs مراجعه کنید.
در پنجره Package Manager Console فرمان زیر را وارد کنید.
update-database
فرمان update-database مهاجرت نخست را اجرا میکند، که دیتابیس را میسازد. بصورت پیش فرض این یک دیتابیس SQL Server Express LocalDB است.
حال پروژه را با CTRL + F5 اجرا کنید.
همانطور که مشاهده میکنید، اپلیکیشن دادههای اولیه (Seed) را نمایش میدهد، و لینک هایی هم برای ویرایش، حذف و مشاهده جزئیات رکوردها فراهم میکند. میتوانید دادهها را مشاهده کنید، رکورد جدید ثبت کنید و یا دادههای قبلی را ویرایش و حذف کنید.
یک تامین کننده OAuth2 و OpenID اضافه کنید
استفاده از Membership API
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework;
bool AddUserAndRole(ContactManager.Models.ApplicationDbContext context) { IdentityResult ir; var rm = new RoleManager<IdentityRole> (new RoleStore<IdentityRole>(context)); ir = rm.Create(new IdentityRole("canEdit")); var um = new UserManager<ApplicationUser>( new UserStore<ApplicationUser>(context)); var user = new ApplicationUser() { UserName = "user1", }; ir = um.Create(user, "Passw0rd1"); if (ir.Succeeded == false) return ir.Succeeded; ir = um.AddToRole(user.Id, "canEdit"); return ir.Succeeded; }
protected override void Seed(ContactManager.Models.ApplicationDbContext context) { AddUserAndRole(context); context.Contacts.AddOrUpdate(p => p.Name, // Code removed for brevity }
کدی موقتی برای تخصیص نقش canEdit به کاربران جدید Social Provider ها
await UserManager.AddToRoleAsync(user.Id, "CanEdit");
در ادامه مقاله اپلیکیشن خود را روی Windows Azure منتشر خواهید کرد و با استفاده از Google و تامین کنندگان دیگر وارد سایت میشوید. هر فردی که به آدرس سایت شما دسترسی داشته باشد، و یک حساب کاربری Google هم در اختیار داشته باشد میتواند در سایت شما ثبت نام کند و سپس دیتابیس را ویرایش کند. برای جلوگیری از دسترسی دیگران، میتوانید وب سایت خود را متوقف (stop) کنید.
در پنجره Package Manager Console فرمان زیر را وارد کنید.
Update-Database
فرمان را اجرا کنید تا متد Seed را فراخوانی کند. حال AddUserAndRole شما نیز اجرا میشود. تا این مرحله نقش canEdit ساخته شده و کاربر جدیدی با نام user1 ایجاد و به آن افزوده شده است.
محافظت از اپلیکیشن توسط SSL و خاصیت Authorize
در این قسمت شما با استفاده از خاصیت Authorize دسترسی به اکشن متدها را محدود میکنید. کاربران ناشناس (Anonymous) تنها قادر به مشاهده متد Index در کنترلر home خواهند بود. کاربرانی که ثبت نام کرده اند به متدهای Index و Details در کنترلر Cm و صفحات About و Contact نیز دسترسی خواهند داشت. همچنین دسترسی به متدهایی که دادهها را تغییر میدهند تنها برای کاربرانی وجود دارد که در نقش canEdit هستند.
خاصیت Authorize و RequireHttps را به اپلیکیشن اضافه کنید. یک راه دیگر افزودن این خاصیتها به تمام کنترلرها است، اما تجارب امنیتی توصیه میکند که این خاصیتها روی کل اپلیکیشن اعمال شوند. با افزودن این خاصیتها بصورت global تمام کنترلرها و اکشن متدهایی که میسازید بصورت خودکار محافظت خواهند شد، و دیگر لازم نیست بیاد داشته باشید کدام کنترلرها و متدها را باید ایمن کنید.
برای اطلاعات بیشتر به Securing your ASP.NET MVC App and the new AllowAnonymous Attribute مراجعه کنید.
فایل App_Start/FilterConfig.cs را باز کنید و متد RegisterGlobalFilters را با کد زیر مطابقت دهید.
public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); filters.Add(new System.Web.Mvc.AuthorizeAttribute()); filters.Add(new RequireHttpsAttribute()); }
خاصیت Authorize در کد بالا از دسترسی کاربران ناشناس به تمام متدهای اپلیکیشن جلوگیری میکند. شما برای اعطای دسترسی به متدهایی خاص از خاصیت AllowAnonymous استفاده خواهید کرد. در آخر خاصیت RequireHTTPS باعث میشود تا تمام دسترسیها به اپلیکیشن وب شما از طریق HTTPS صورت گیرد.
حالا خاصیت AllowAnonymous را به متد Index در کنترلر Home اضافه کنید. از این خاصیت برای اعطای دسترسی به تمامی کاربران سایت استفاده کنید. قسمتی از کد کنترلر Home را در زیر میبینید.
namespace ContactManager.Controllers { public class HomeController : Controller { [AllowAnonymous] public ActionResult Index() { return View(); }
یک جستجوی عمومی برای عبارت AllowAnonymous انجام دهید. همانطور که مشاهده میکنید این خاصیت توسط متدهای ورود و ثبت نام در کنترلر Account نیز استفاده شده است.
در کنترلر CmController خاصیت [("Authorize(Roles="canEdit] را به تمام متدهایی که با داده سر و کار دارند اضافه کنید، به غیر از متدهای Index و Details. قسمتی از کد کامل شده در زیر آمده است.
فعال سازی SSL برای پروژه
روی نام پروژه کلیک راست کنید و Properties را انتخاب کنید. در قسمت چپ گزینه Web را انتخاب کنید. حالا مقدار Project Url را به آدرسی که کپی کرده اید تغییر دهید. نهایتا تغییرات را ذخیره کنید و پنجره را ببندید.
حال پروژه را اجرا کنید. مرورگر شما باید یک پیام خطای اعتبارسنجی به شما بدهد. دلیلش این است که اپلیکیشن شما از یک Valid Certificate استفاده نمیکند. هنگامی که پروژه را روی Windows Azure منتشر کنید دیگر این پیغام را نخواهید دید. چرا که سرورهای مایکروسافت همگی لایسنسهای معتبری دارند. برای اپلیکیشن ما میتوانید روی Continue to this website را انتخاب کنید.
حال مرورگر پیش فرض شما باید صفحه Index از کنترلر home را به شما نمایش دهد.
اگر از یک نشست قبلی هنوز در سایت هستید (logged-in) روی لینک Log out کلیک کنید و از سایت خارج شوید.
روی لینکهای About و Contact کلیک کنید. باید به صفحه ورود به سایت هدایت شوید چرا که کاربران ناشناس اجازه دسترسی به این صفحات را ندارند.
روی لینک Register کلیک کنید و یک کاربر محلی با نام Joe بسازید. حال مطمئن شوید که این کاربر به صفحات Home, About و Contact دسترسی دارد.
روی لینک CM Demo کلیک کنید و مطمئن شوید که دادهها را مشاهده میکنید.
حال روی یکی از لینکهای ویرایش (Edit) کلیک کنید. این درخواست باید شما را به صفحه ورود به سایت هدایت کند، چرا که کاربران محلی جدید به نقش canEdit تعلق ندارند.
با کاربر user1 که قبلا ساختید وارد سایت شوید. حال به صفحه ویرایشی که قبلا درخواست کرده بودید هدایت میشوید.
اگر نتوانستید با این کاربر به سایت وارد شوید، کلمه عبور را از سورس کد کپی کنید و مجددا امتحان کنید. اگر همچنان نتوانستید به سایت وارد شوید، جدول AspNetUsers را بررسی کنید تا مطمئن شوید کاربر user1 ساخته شده است. این مراحل را در ادامه مقاله خواهید دید.
در آخر اطمینان حاصل کنید که میتوانید دادهها را تغییر دهید.
اپلیکیشن را روی Windows Azure منتشر کنید
در دیالوگ باز شده روی قسمت Settings کلیک کنید. روی File Publish Options کلیک کنید تا بتوانید Remote connection string را برای ApplicationDbContext و دیتابیس ContactDB انتخاب کنید.
اگر ویژوال استودیو را پس از ساخت Publish profile بسته و دوباره باز کرده اید، ممکن است رشته اتصال را در لیست موجود نبینید. در چنین صورتی، بجای ویرایش پروفایل انتشار، یک پروفایل جدید بسازید. درست مانند مراحلی که پیشتر دنبال کردید.
زیر قسمت ContactManagerContext گزینه Execute Code First Migrations را انتخاب کنید.
حال Publish را کلیک کنید تا اپلیکیشن شما منتشر شود. با کاربر user1 وارد سایت شوید و بررسی کنید که میتوانید دادهها را ویرایش کنید یا خیر.
حال از سایت خارج شوید و توسط یک اکانت Google یا Facebook وارد سایت شوید، که در این صورت نقش canEdit نیز به شما تعلق میگیرد.
برای جلوگیری از دسترسی دیگران، وب سایت را متوقف کنید
یک راه دیگر متوقف کردن وب سایت از طریق پرتال مدیریت Windows Azure است.
فراخوانی AddToRoleAsync را حذف و اپلیکیشن را منتشر و تست کنید
await UserManager.AddToRoleAsync(user.Id, "CanEdit");
دکمه Start Preview را فشار دهید. در این مرحله تنها فایل هایی که نیاز به بروز رسانی دارند آپلود خواهند شد.
وب سایت را راه اندازی کنید. سادهترین راه از طریق پرتال مدیریت Windows Azure است. توجه داشته باشید که تا هنگامی که وب سایت شما متوقف شده، نمیتوانید اپلیکیشن خود را منتشر کنید.
حال به ویژوال استودیو بازگردید و اپلیکیشن را منتشر کنید. اپلیکیشن Windows Azure شما باید در مرورگر پیش فرض تان باز شود. حال شما در حال مشاهده صفحه اصلی سایت بعنوان یک کاربر ناشناس هستید.
روی لینک About کلیک کنید، که شما را به صفحه ورود هدایت میکند.
روی لینک Register در صفحه ورود کلیک کنید و یک حساب کاربری محلی بسازید. از این حساب کاربری برای این استفاده میکنیم که ببینیم شما به صفحات فقط خواندنی (read-only) و نه صفحاتی که دادهها را تغییر میدهند دسترسی دارید یا خیر. بعدا در ادامه مقاله، دسترسی حسابهای کاربری محلی (local) را حذف میکنیم.
مطمئن شوید که به صفحات About و Contact دسترسی دارید.
لینک CM Demo را کلیک کنید تا به کنترلر CmController هدایت شوید.
روی یکی از لینکهای Edit کلیک کنید. این کار شما را به صفحه ورود به سایت هدایت میکند. در زیر قسمت User another service to log in یکی از گزینههای Google یا Facebook را انتخاب کنید و توسط حساب کاربری ای که قبلا ساختید وارد شوید.
حال بررسی کنید که امکان ویرایش اطلاعات را دارید یا خیر.
نکته: شما نمیتوانید در این اپلیکیشن از اکانت گوگل خود خارج شده، و با همان مرورگر با اکانت گوگل دیگری وارد اپلیکیشن شوید. اگر دارید از یک مرورگر استفاده میکنید، باید به سایت گوگل رفته و از آنجا خارج شوید. برای وارد شدن به اپلیکیشن توسط یک اکانت دیگر میتوانید از یک مرورگر دیگر استفاده کنید.
دیتابیس SQL Azure را بررسی کنید
توجه: اگر نمیتوانید گره SQL Databases را باز کنید و یا ContactDB را در ویژوال استودیو نمیبینید، باید مراحلی را طی کنید تا یک پورت یا یکسری پورت را به فایروال خود اضافه کنید. دقت داشته باشید که در صورت اضافه کردن Port Rangeها ممکن است چند دقیقه زمان نیاز باشد تا بتوانید به دیتابیس دسترسی پیدا کنید.
روی جدول AspNetUsers کلیک راست کرده و View Data را انتخاب کنید.
حالا روی AspNetUserRoles کلیک راست کنید و View Data را انتخاب کنید.
اگر شناسه کاربران (User ID) را بررسی کنید، مشاهده میکنید که تنها دو کاربر user1 و اکانت گوگل شما به نقش canEdit تعلق دارند.
Cannot open server login error
شما باید آدرس IP خود را به لیست آدرسهای مجاز (Allowed IPs) اضافه کنید. در پرتال مدیریتی Windows Azure در قسمت چپ صفحه، گزینه SQL Databases را انتخاب کنید.
دیتابیس مورد نظر را انتخاب کنید. حالا روی لینک Set up Windows Azure firewall rules for this IP address کلیک کنید.
هنگامی که با پیغام "?The current IP address xxx.xxx.xxx.xxx is not included in existing firewall rules. Do you want to update the firewall rules" مواجه شدید Yes را کلیک کنید. افزودن یک آدرس IP بدین روش معمولا کافی نیست و در فایروالهای سازمانی و بزرگ باید Range بیشتری را تعریف کنید.
مرحله بعد اضافه کردن محدوده آدرسهای مجاز است.
مجددا در پرتال مدیریتی Windows Azure روی SQL Databases کلیک کنید. سروری که دیتابیس شما را میزبانی میکند انتخاب کنید.
در بالای صفحه لینک Configure را کلیک کنید. حالا نام rule جدید، آدرس شروع و پایان را وارد کنید.
در پایین صفحه Save را کلیک کنید.
در آخر میتوانید توسط SSOX به دیتابیس خود متصل شوید. از منوی View گزینه SQL Server Object Explorer را انتخاب کنید. روی SQL Server کلیک راست کرده و Add SQL Server را انتخاب کنید.
در دیالوگ Connect to Server متد احراز هویت را به SQL Server Authentication تغییر دهید. این کار نام سرور و اطلاعات ورود پرتال Windows Azure را به شما میدهد.
در مرورگر خود به پرتال مدیریتی بروید و SQL Databases را انتخاب کنید. دیتابیس ContactDB را انتخاب کرده و روی View SQL Database connection strings کلیک کنید. در صفحه Connection Strings مقادیر Server و User ID را کپی کنید. حالا مقادیر را در دیالوگ مذکور در ویژوال استودیو بچسبانید. مقدار فیلد User ID در قسمت Login وارد میشود. در آخر هم کلمه عبوری که هنگام ساختن دیتابیس تنظیم کردید را وارد کنید.
حالا میتوانید با مراحلی که پیشتر توضیح داده شد به دیتابیس Contact DB مراجعه کنید.
افزودن کاربران به نقش canEdit با ویرایش جداول دیتابیس
حالا RoleId را کپی کنید و در ردیف جدید بچسبانید.
شناسه کاربر مورد نظر را از جدول AspNetUsers پیدا کنید و مقدار آن را در ردیف جدید کپی کنید. همین! کاربر جدید شما به نقش canEdit اضافه شد.
نکاتی درباره ثبت نام محلی (Local Registration)
- در کنترلر Account متدهای Register را ویرایش کنید و خاصیت AllowAnonymous را از آنها حذف کنید (هر دو متد GET و POST). این کار ثبت نام کاربران ناشناس و بدافزارها (bots) را غیر ممکن میکند.
- در پوشه Views/Shared فایل LoginPartial.cshtml_ را باز کنید و لینک Register را از آن حذف کنید.
- در فایل Views/Account/Login.cshtml نیز لینک Register را حذف کنید.
- اپلیکیشن را دوباره منتشر کنید.
قدمهای بعدی
استفاده از mocking frameworks :
تعدادی از چارچوبهای تقلید نوشته شده برای دات نت فریم ورک مطابق لیست زیر بوده و هدف از آنها ایجاد سادهتر اشیاء تقلید برای ما میباشد:
Nmock : http://www.nmock.org
Moq : http://code.google.com/p/moq
Rhino Mocks : http://ayende.com/projects/rhino-mocks.aspx
TypeMock : http://www.typemock.com
EasyMock.Net : http://sourceforge.net/projects/easymocknet
در این بین Rhino Mocks که توسط یکی از اعضای اصلی تیم NHibernate به وجود آمده است، در مجامع مرتبط بیشتر مورد توجه است. برای آشنایی بیشتر با آن میتوان به این ویدیوی رایگان آموزشی در مورد آن مراجعه نمود (حدود یک ساعت است).
خلاصهای در مورد نحوهی استفاده از Rhino Mocks :
پس از دریافت کتابخانه سورس باز Rhino Mocks ، ارجاعی را به اسمبلی Rhino.Mocks.dll آن، در پروژه آزمون واحد خود اضافه نمائید.
یک Rhino mock test با ایجاد شیءایی از MockRepository شروع میشود و کلا از سه قسمت تشکیل میگردد:
الف) ایجاد شیء Mock یا Arrange . هدف از ایجاد شیء mock ، جایگزین کردن و یا تقلید یک شیء واقعی جهت مباحثی مانند ایزوله سازی آزمایشات، بالابردن سرعت آنها و متکی به خود کردن این آزمایشات میباشد. همچنین در این حالت نتایج false positive نیز کاهش مییابند. منظور از نتایج false positive این است که آزمایش باید با موفقیت به پایان برسد اما اینگونه نشده و علت آن بررسی سیستمی دیگر در خارج از مرزهای سیستم فعلی است و مشکل از جای دیگری نشات گرفته که اساسا هدف از تست ما بررسی عملکرد آن سیستم نبوده است. کلا در این موارد از mocking objects استفاده میشود:
- دسترسی به شیء مورد نظر کند است مانند دسترسی به دیتابیس یا محاسبات بسیار طولانی
- شیء مورد نظر از call back استفاده میکند
- شیء مورد آزمایش باید به منابع خارجی دسترسی پیدا کند که اکنون مهیا نیستند. برای مثال دسترسی به شبکه.
- شیءایی که میخواهیم آنرا تست کنیم یا برای آن آزمایشات واحد تهیه نمائیم، هنوز کاملا توسعه نیافته و نیمه کاره است.
ب) تعریف رفتارهای مورد نظر یا Act
ج) بررسی رفتارهای تعریف شده یا Assert
مثال:
متد ساده زیر را در نظر بگیرید:
public class ImageManagement
{
public string GetImageForTimeOfDay()
{
int currentHour = DateTime.Now.Hour;
return currentHour > 6 && currentHour < 21 ? "sun.jpg" : "moon.jpg";
}
}
using System;
using NUnit.Framework;
[TestFixture]
public class CMyTest
{
[Test]
public void DaytimeTest()
{
int currentHour = DateTime.Now.Hour;
if (currentHour > 6 && currentHour < 21)
{
const string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay();
Assert.AreEqual(expectedImagePath, path);
}
else
{
Assert.Ignore("تنها در طول روز قابل بررسی است");
}
}
[Test]
public void NighttimeTest()
{
int currentHour = DateTime.Now.Hour;
if (currentHour < 6 || currentHour > 21)
{
const string expectedImagePath = "moon.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay();
Assert.AreEqual(expectedImagePath, path);
}
else
{
Assert.Ignore("تنها در طول شب قابل بررسی است");
}
}
}
برای اینکار باید DateTime.Now.Hour را تقلید نموده و اینترفیسی را بر اساس آن طراحی نمائیم. سپس Rhino Mocks کار پیاده سازی این اینترفیس را انجام خواهد داد:
using NUnit.Framework;
using Rhino.Mocks;
namespace testWinForms87
{
public interface IDateTime
{
int GetHour();
}
public class ImageManagement
{
public string GetImageForTimeOfDay(IDateTime time)
{
int currentHour = time.GetHour();
return currentHour > 6 && currentHour < 21 ? "sun.jpg" : "moon.jpg";
}
}
[TestFixture]
public class CMocking
{
[Test]
public void DaytimeTest()
{
MockRepository mocks = new MockRepository();
IDateTime timeController = mocks.CreateMock<IDateTime>();
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(15);
}
using (mocks.Playback())
{
const string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
}
[Test]
public void NighttimeTest()
{
MockRepository mocks = new MockRepository();
IDateTime timeController = mocks.CreateMock<IDateTime>();
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(1);
}
using (mocks.Playback())
{
const string expectedImagePath = "moon.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
}
}
}
MockRepository mocks = new MockRepository();
سپس اینترفیسی باید به آن پاس شود تا انتظارات سیستم را بتوان در آن بر پا نمود:
IDateTime timeController = mocks.CreateMock<IDateTime>();
using (mocks.Record())
{
Expect.Call(timeController.GetHour()).Return(15);
}
به این صورت آزمایش ما بر اساس وضعیت مشخصی از سیستم صورت میگیرد و وابسته به ساعت جاری سیستم نخواهد بود.
همانطور که ملاحظه میکنید، روش Test Driven Development بر روی نحوهی برنامه نویسی ما و ایجاد کلاسها و اینترفیسهای اولیه نیز تاثیر زیادی خواهد گذاشت. استفاده از اینترفیسها یکی از اصول پایهای برنامه نویسی شیءگرا است و در اینجا مقید به ایجاد آنها خواهیم شد.
پس از آنکه در قسمت mocks.Record ، انتظارات خود را ثبت کردیم، اکنون نوبت به وضعیت Playback میرسد:
using (mocks.Playback())
{
string expectedImagePath = "sun.jpg";
ImageManagement image = new ImageManagement();
string path = image.GetImageForTimeOfDay(timeController);
Assert.AreEqual(expectedImagePath, path);
}
با توجه به اینکه پس از تغییر طراحی متد GetImageForTimeOfDay ، این متد اکنون از شیء IDateTime به عنوان ورودی استفاده میکند، میتوان پیاده سازی آن اینترفیس را در آزمایشات واحد تقلید نمود و یا جایی که قرار است در برنامه استفاده شود، میتواند پیاده سازی واقعی خود را داشته باشد و دیگر آزمایشات ما وابسته به آن نخواهد بود:
public class DateTimeController : IDateTime
{
public int GetHour()
{
return DateTime.Now.Hour;
}
}
@Html.ActionLink("text", "Index", "Home")
@Html.ActionLink("text", result: MVC.Home.Index())
پروژهی T4MVC توسط یکی از اعضای تیم ASP.NET تهیه شدهاست. همچنین مدتی است مایکروسافت پروژهی دیگری را نیز به نام Microsoft.AspNet.Mvc.Futures در حال تهیه و آزمایش دارد که از آن نیز میتوان برای تولید لینکهای Strongly typed استفاده کرد.
نصب کتابخانهی Microsoft ASP.NET MVC Futures
برای نصب کتابخانهی آیندهی ASP.NET MVC، تنها کافی است دستور ذیل را در کنسول پاورشل نیوگت صادر کنید:
PM> Install-Package Microsoft.AspNet.Mvc.Futures
نحوهی تعریف مسیرهای Strongly typed، توسط کتابخانهی آیندهی ASP.NET MVC
پس از نصب بستهی Microsoft.AspNet.Mvc.Futures، جهت سهولت کار نیاز است اسمبلی آنرا که Microsoft.Web.Mvc.dll نام دارد، به تمام صفحات سایت معرفی کنیم. برای این منظور فایل web.config پوشهی views را گشوده و یک سطر تعریف فضای نام Microsoft.Web.Mvc را به آن اضافه کنید:
<system.web.webPages.razor> <host /> <pages pageBaseType="System.Web.Mvc.WebViewPage"> <namespaces> <!-- سایر تعاریف --> <add namespace="Microsoft.Web.Mvc"/> <!-- این سطر اضافه شود --> </namespaces> </pages> </system.web.webPages.razor>
یک نکتهی مهم
این بسته در حال حاضر هرچند دارای پوشهی دات نت 4 است، اما عملا برای دات نت 4.5 یا به عبارتی ASP.NET MVC 5 کامپایل شدهاست و در ASP.NET MVC 4 قابل استفاده نیست:
The primary reference "Microsoft.Web.Mvc" could not be resolved because it was built against the ".NETFramework,Version=v4.5" framework. This is a higher version than the currently targeted framework ".NETFramework,Version=v4.0"
ActionLinkهای جدید بستهی Microsoft.AspNet.Mvc.Futures
به این ترتیب برای مثال در مورد ActionLink، دو overload جدید را میتوان در Viewها استفاده کرد:
public static System.Web.Mvc.MvcHtmlString ActionLink<TController>(this System.Web.Mvc.HtmlHelper helper, System.Linq.Expressions.Expression<Action<TController>> action, string linkText) public static System.Web.Mvc.MvcHtmlString ActionLink<TController>(this System.Web.Mvc.HtmlHelper helper, System.Linq.Expressions.Expression<Action<TController>> action, string linkText, object htmlAttributes)
@(Html.ActionLink<HomeController>(action => action.Index(id: 1), "Test"))
همچنین اگر اکشن متد Index کنترلر HomeController دارای پارامتر نیز باشد، در همینجا قابل مقدار دهی است.
RenderAction و BeginFormهای جدید بستهی Microsoft.AspNet.Mvc.Futures
از این نوع متدهای الحاقی Expression Action دار، برای RenderAction و BeginForm نیز طراحی شدهاند:
public static void RenderAction<TController>(this System.Web.Mvc.HtmlHelper helper, System.Linq.Expressions.Expression<Action<TController>> action) public static System.Web.Mvc.Html.MvcForm BeginForm<TController>(this System.Web.Mvc.HtmlHelper helper, System.Linq.Expressions.Expression<Action<TController>> action, System.Web.Mvc.FormMethod method, System.Collections.Generic.IDictionary<string,object> htmlAttributes)
@{ Html.RenderAction<HomeController>(action => action.Index(id: 1)); }
@using (Html.BeginForm<HomeController>(action => action.Index(null))) { }
RedirectToAction جدید بستهی Microsoft.AspNet.Mvc.Futures
به همراه دو متد کمکی Expression Action دار، جهت استفاده در متدهای کنترلرهای سایت؛ برای ساخت Url و همچنین redirect به یک اکشن متد دیگر:
public static string BuildUrlFromExpression<TController>(System.Web.Routing.RequestContext context, System.Web.Routing.RouteCollection routeCollection, System.Linq.Expressions.Expression<Action<TController>> action) public static System.Web.Mvc.RedirectToRouteResult RedirectToAction<TController>(this System.Web.Mvc.Controller controller, System.Linq.Expressions.Expression<Action<TController>> action)
using System.Web.Mvc; using Microsoft.Web.Mvc; namespace MVC5Basic.Controllers { public class HomeController : Controller { public ActionResult Index() { var link = LinkBuilder.BuildUrlFromExpression<HomeController>( this.Request.RequestContext, null, action => action.About()); this.RedirectToAction<HomeController>(action => action.About()); return View(); } public ActionResult About() { ViewBag.Message = "Your application description page."; return View(); } } }
کدامیک بهتر است؟ T4MVC یا ASP.NET MVC Futures ؟
T4MVC موارد بیشتری را پوشش میدهد؛ حتی مسیرهای تصاویر ثابت و فایلهای js را نیز میتوان توسط آن تعریف کرد. فقط نگهداری آن هر بار نیاز به اجرای فایل t4 مرتبط با آن دارد و در اینجا کار با ASP.NET MVC Futures سادهتر است.
برای مطالعه بیشتر
بررسی که در اینجا صورت گرفت صرفا در مورد امکانات تولید مسیرهای strongly typed این کتابخانه است. سایر امکانات آنرا در مطلب ذیل میتوانید پیگیری کنید:
Using the Features of ASP.NET MVC 3 Futures
تزریق مستقیم کلاس Context برنامه، تزریق وابستگیها نام ندارد!
در همان قسمت اول سری شروع به کار با EF Core 1.0، مشاهده کردیم که پس از انجام تنظیمات اولیهی آن در کلاس آغازین برنامه:
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped);
public class TestDBController : Controller { private readonly ApplicationDbContext _ctx; public TestDBController(ApplicationDbContext ctx) { _ctx = ctx; } public IActionResult Index() { var name = _ctx.Persons.First().FirstName; return Json(new { firstName = name }); } }
مشکلاتی را که تزریق مستقیم کلاسها و نوعها به همراه دارند به شرح زیر است:
- اگر نام این کلاس تغییر کند، باید این نام، در تمام کلاسهایی که به صورت مستقیم از آن استفاده میکنند نیز تغییر داده شود.
- اگر سازندهای به آن اضافه شد و یا امضای سازندهی موجود آن، تغییر کرد، باید نحوهی وهله سازی این کلاس را در تمام کلاسهای وابسته نیز اصلاح کرد.
- یکی از مهمترین دلایل استفادهی از تزریق وابستگیها، بالابردن قابلیت تست پذیری برنامه است. زمانیکه از اینترفیسها استفاده میشود، میتوان در مورد نحوهی تقلید (mocking) رفتار کلاسی خاص، مستقلا تصمیم گیری کرد. اما هنگامیکه یک کلاس را به همان شکل اولیهی آن تزریق میکنیم، به این معنا است که همواره دقیقا همین پیاده سازی خاص مدنظر ما است و این مساله، نوشتن آزمونهای واحد را با مشکل کردن mocking آنها، گاهی از اوقات غیرممکن میکند. هرچند تعدادی از فریم ورکهای پیشرفتهی mocking گاهی از اوقات امکان تقلید رفتار کلاسها و نوعها را نیز فراهم میکنند، اما با این شرط که تمام خواص و متدهای آنها را virtual تعریف کنید؛ تا بتوانند متدهای اصلی را با نمونههای مدنظر شما بازنویسی (override) کنند.
به همین جهت در ادامه، به همان طراحی EF Code First #12 با نوشتن اینترفیس IUnitOfWork خواهیم رسید. یعنی کلاس Context برنامه را با این اینترفیس نشانه گذاری میکنیم (در انتهای لیست تمام اینترفیسهای دیگری که ممکن است در اینجا ذکر شده باشند):
public class ApplicationDbContext : IUnitOfWork
طراحی اینترفیس IUnitOfWork
برای اینکه دیگر با کلاس ApplicationDbContext مستقیما کار نکرده و وابستگی به آنرا در تمام قسمتهای برنامه پخش نکنیم، اینترفیسی را ایجاد میکنیم که تنها قسمتهای مشخصی از DbContext را عمومی کند:
public interface IUnitOfWork : IDisposable { DbSet<TEntity> Set<TEntity>() where TEntity : class; void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class; void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class; EntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class; void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class; void ExecuteSqlCommand(string query); void ExecuteSqlCommand(string query, params object[] parameters); int SaveAllChanges(); Task<int> SaveAllChangesAsync(); }
- در این طراحی شاید عنوان کنید که DbSet، اینترفیس نیست. تعریف DbSet در EF Core به صورت زیر است و در حقیقت همانند اینترفیسها یک abstraction به حساب میآید:
public abstract class DbSet<TEntity> : IQueryable<TEntity>, IEnumerable<TEntity>, IEnumerable, IQueryable, IAsyncEnumerableAccessor<TEntity>, IInfrastructure<IServiceProvider> where TEntity : class
- این اینترفیس به عمد به صورت IDisposable تعریف شدهاست. این مساله به IoC Containers کمک خواهد کرد که بتوانند پاکسازی خودکار نوعهای IDisposable را در انتهای هر درخواست انجام دهند و برنامه مشکلی نشتی حافظه را پیدا نکند.
- اصل کار این اینترفیس، تعریف DbSet و متدهای SaveChanges است. سایر متدهایی را که مشاهده میکنید، صرفا جهت بیان اینکه چگونه میتوان قابلیتی از DbContext را بدون عمومی کردن خود کلاس DbContext، در کلاسهایی که از اینترفیس IUnitOfWork استفاده میکنند، میسر کرد.
پس از اینکه این اینترفیس تعریف شد، اعمال آن به کلاس Context برنامه به صورت ذیل خواهد بود:
public class ApplicationDbContext : DbContext, IUnitOfWork { private readonly IConfigurationRoot _configuration; public ApplicationDbContext(IConfigurationRoot configuration) { _configuration = configuration; } //public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) //{ //} public virtual DbSet<Blog> Blog { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer( _configuration["ConnectionStrings:ApplicationDbContextConnection"] , serverDbContextOptionsBuilder => { var minutes = (int)TimeSpan.FromMinutes(3).TotalSeconds; serverDbContextOptionsBuilder.CommandTimeout(minutes); } ); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); } public void AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class { base.Set<TEntity>().AddRange(entities); } public void RemoveRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class { base.Set<TEntity>().RemoveRange(entities); } public void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class { base.Entry(entity).State = EntityState.Modified; // Or use ---> this.Update(entity); } public void ExecuteSqlCommand(string query) { base.Database.ExecuteSqlCommand(query); } public void ExecuteSqlCommand(string query, params object[] parameters) { base.Database.ExecuteSqlCommand(query, parameters); } public int SaveAllChanges() { return base.SaveChanges(); } public Task<int> SaveAllChangesAsync() { return base.SaveChangesAsync(); } }
public class ApplicationDbContext : DbContext, IUnitOfWork
public interface IUnitOfWork : IDisposable
در مورد تزریق IConfigurationRoot به سازندهی کلاس Context برنامه، در مطلب اول این سری در قسمت «یک نکته: امکان تزریق IConfigurationRoot به کلاس Context برنامه» پیشتر بحث شدهاست.
ثبت تنظیمات تزریق وابستگیهای IUnitOfWork
پس از تعریف و پیاده سازی اینترفیس IUnitOfWork، اکنون نوبت به معرفی آن به سیستم تزریق وابستگیهای ASP.NET Core است:
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; }); services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped); services.AddScoped<IUnitOfWork, ApplicationDbContext>();
استفاده از IUnitOfWork در لایه سرویسهای برنامه
اکنون لایه سرویس برنامه و فایل project.json آن چنین شکلی را پیدا میکند:
{ "version": "1.0.0-*", "dependencies": { "Core1RtmEmptyTest.DataLayer": "1.0.0-*", "Core1RtmEmptyTest.Entities": "1.0.0-*", "Core1RtmEmptyTest.ViewModels": "1.0.0-*", "Microsoft.Extensions.Configuration.Abstractions": "1.0.0", "Microsoft.Extensions.Options": "1.0.0", "NETStandard.Library": "1.6.0" }, "frameworks": { "netstandard1.6": { "imports": "dnxcore50" } } }
پس از تنظیم وابستگیهای این اسمبلی، اکنون یک کلاس نمونه از لایه سرویس برنامه، به شکل زیر خواهد بود:
namespace Core1RtmEmptyTest.Services { public interface IBlogService { IReadOnlyList<Blog> GetPagedBlogsAsNoTracking(int pageNumber, int recordsPerPage); } public class BlogService : IBlogService { private readonly IUnitOfWork _uow; private readonly DbSet<Blog> _blogs; public BlogService(IUnitOfWork uow) { _uow = uow; _blogs = _uow.Set<Blog>(); } public IReadOnlyList<Blog> GetPagedBlogsAsNoTracking(int pageNumber, int recordsPerPage) { var skipRecords = pageNumber * recordsPerPage; return _blogs .AsNoTracking() .Skip(skipRecords) .Take(recordsPerPage) .ToList(); } } }
استفاده از امکانات لایه سرویس برنامه، در دیگر لایههای آن
خروجی لایه سرویس، توسط اینترفیسهایی مانند IBlogService در قسمتهای دیگر برنامه قابل استفاده و دسترسی میشود.
به همین جهت نیاز است مشخص کنیم، این اینترفیس را کدام کلاس ویژه قرار است پیاده سازی کند. برای این منظور همانند قبل در متد ConfigureServices کلاس آغازین برنامه این تنظیم را اضافه خواهیم کرد:
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; }); services.AddDbContext<ApplicationDbContext>(ServiceLifetime.Scoped); services.AddScoped<IUnitOfWork, ApplicationDbContext>(); services.AddScoped<IBlogService, BlogService>();
public class TestDBController : Controller { private readonly IBlogService _blogService; private readonly IUnitOfWork _uow; public TestDBController(IBlogService blogService, IUnitOfWork uow) { _blogService = blogService; _uow = uow; }
AccessTokenExpiresDateTime
"AccessTokenExpirationMinutes": 2, "RefreshTokenExpirationMinutes": 1,