اشتراکها
سایت Dot Net Core Tutorials
اشتراکها
معرفی C#/WinRT Version 1.0
Today is the official GA release for .NET 5, and along with it we are excited to share the latest updates with our recent release of C#/WinRT version 1.0. C#/WinRT provides WinRT projection support for .NET 5 based apps. The Windows SDK leverages this technology and is now integrated with the .NET 5.0 SDK to expose Windows APIs through the new Target Framework Monikers. In addition to the Windows SDK support added for .NET 5, C#/WinRT itself allows component authors to build their own .NET 5 projections using the CsWinRT NuGet package.
نظرات مطالب
Blazor 5x - قسمت اول - معرفی
- بله. به صورت PWA و یا با استفاده از NET Maui. و یا Binding مخصوص که پایهی Blazor Hybrid است و یا کلا هر برنامهی وبی، قابلیت مرور و استفاده در دستگاههای موبایلی را هم دارد.
- در مورد NET Maui. به این موارد تکمیلی مراجعه کنید.
چند ویدیوی تکمیلی در مورد Blazor Hybrid
- Blazor + .NET MAUI – the perfect “hybrid” | ODFP211
- Create native desktop & mobile apps using web skills in Blazor Hybrid | .NET Conf 2022
- Build Hybrid Mobile with .NET MAUI Blazor, Desktop, and Web apps
- Build native and hybrid mobile apps with Mobile Blazor Bindings
- Native client apps with Blazor Hybrid | OD109
- سری آموزشی Blazor Hybrid
یک سناریوی فرضی را در نظر بگیرید. اگر بخواهیم IdentityDbContext و دیگر DbContextهای اپلیکیشن را ادغام کنیم چه باید کرد؟ مثلا یک سیستم وبلاگ که برخی کاربران میتوانند پست جدید ثبت کنند، برخی تنها میتوانند کامنت بگذارند و تمامی کاربران هم اختیارات مشخص دیگری دارند. در چنین سیستمی شناسه کاربران (User ID) در بسیاری از مدلها (موجودیتها و مدلهای اپلیکیشن) وجود خواهد داشت تا مشخص شود هر رکورد به کدام کاربر متعلق است. در این مقاله چنین سناریو هایی را بررسی میکنیم و best practiceهای مربوطه را مرور میکنیم.
پس از ورود به سایت بعنوان یک مدیر، میتوانید ToDoهای ثبت شده توسط تمام کاربران را مشاهده کنید.
در این پست یک اپلیکیشن ساده ToDo خواهیم ساخت که امکان تخصیص to-doها به کاربران را نیز فراهم میکند. در این مثال خواهیم دید که چگونه میتوان مدلهای مختص به سیستم عضویت (IdentityDbContext) را با مدلهای دیگر اپلیکیشن مخلوط و استفاده کنیم.
تعریف نیازمندیهای اپلیکیشن
- تنها کاربران احراز هویت شده قادر خواهند بود تا لیست ToDoهای خود را ببینند، آیتمهای جدید ثبت کنند یا دادههای قبلی را ویرایش و حذف کنند.
- کاربران نباید آیتمهای ایجاد شده توسط دیگر کاربران را ببینند.
- تنها کاربرانی که به نقش Admin تعلق دارند باید بتوانند تمام ToDoهای ایجاد شده را ببینند.
پس بگذارید ببینیم چگونه میشود اپلیکیشنی با ASP.NET Identity ساخت که پاسخگوی این نیازمندیها باشد.
ابتدا یک پروژه ASP.NET MVC جدید با مدل احراز هویت Individual User Accounts بسازید. در این اپلیکیشن کاربران قادر خواهند بود تا بصورت محلی در وب سایت ثبت نام کنند و یا با تامین کنندگان دیگری مانند گوگل و فیسبوک وارد سایت شوند. برای ساده نگاه داشتن این پست ما از حسابهای کاربری محلی استفاده میکنیم.
در مرحله بعد ASP.NET Identity را راه اندازی کنید تا بتوانیم نقش مدیر و یک کاربر جدید بسازیم. میتوانید با اجرای اپلیکیشن راه اندازی اولیه را انجام دهید. از آنجا که سیستم ASP.NET Identity توسط Entity Framework مدیریت میشود میتوانید از تنظیمات پیکربندی Code First برای راه اندازی دیتابیس خود استفاده کنید.
در قدم بعدی راه انداز دیتابیس را در Global.asax تعریف کنید.
Database.SetInitializer<MyDbContext>(new MyDbInitializer());
ایجاد نقش مدیر و کاربر جدیدی که به این نقش تعلق دارد
اگر به قطعه کد زیر دقت کنید، میبینید که در خط شماره 5 متغیری از نوع UserManager ساخته ایم که امکان اجرای عملیات گوناگونی روی کاربران را فراهم میکند. مانند ایجاد، ویرایش، حذف و اعتبارسنجی کاربران. این کلاس که متعلق به سیستم ASP.NET Identity است همتای SQLMembershipProvider در ASP.NET 2.0 است.
در خط 6 یک RoleManager میسازیم که امکان کار با نقشها را فراهم میکند. این کلاس همتای SQLRoleMembershipProvider در ASP.NET 2.0 است.
در این مثال نام کلاس کاربران (موجودیت کاربر در IdentityDbContext) برابر با "MyUser" است، اما نام پیش فرض در قالبهای پروژه VS 2013 برابر با "ApplicationUser" میباشد.
public class MyDbInitializer : DropCreateDatabaseAlways<MyDbContext> { protected override void Seed(MyDbContext context) { var UserManager = new UserManager<MyUser>(new UserStore<MyUser>(context)); var RoleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context)); string name = "Admin"; string password = "123456"; //Create Role Admin if it does not exist if (!RoleManager.RoleExists(name)) { var roleresult = RoleManager.Create(new IdentityRole(name)); } //Create User=Admin with password=123456 var user = new MyUser(); user.UserName = name; var adminresult = UserManager.Create(user, password); //Add User Admin to Role Admin if (adminresult.Succeeded) { var result = UserManager.AddToRole(user.Id, name); } base.Seed(context); } }
حال فایلی با نام Models/AppModels.cs بسازید و مدل EF Code First اپلیکیشن را تعریف کنید. از آنجا که از EF استفاده میکنیم، روابط کلیدها بین کاربران و ToDoها بصورت خودکار برقرار میشود.
public class MyUser : IdentityUser { public string HomeTown { get; set; } public virtual ICollection<ToDo> ToDoes { get; set; } } public class ToDo { public int Id { get; set; } public string Description { get; set; } public bool IsDone { get; set; } public virtual MyUser User { get; set; } }
در قدم بعدی با استفاده از مکانیزم Scaffolding کنترلر جدیدی بهمراه تمام Viewها و متدهای لازم برای عملیات CRUD بسازید. برای اطلاعات بیشتر درباره نحوه استفاده از مکانیزم Scaffolding به این لینک مراجعه کنید.
لطفا دقت کنید که از DbContext فعلی استفاده کنید. این کار مدیریت دادههای Identity و اپلیکیشن شما را یکپارچهتر میکند. DbContext شما باید چیزی شبیه به کد زیر باشد.
public class MyDbContext : IdentityDbContext<MyUser> { public MyDbContext() : base("DefaultConnection") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { public System.Data.Entity.DbSet<AspnetIdentitySample.Models.ToDo> ToDoes { get; set; } }
تنها کاربران احراز هویت شده باید قادر به اجرای عملیات CRUD باشند
برای این مورد از خاصیت Authorize استفاده خواهیم کرد که در MVC 4 هم وجود داشت. برای اطلاعات بیشتر لطفا به این لینک مراجعه کنید.
[Authorize] public class ToDoController : Controller
کنترلر ایجاد شده را ویرایش کنید تا کاربران را به ToDoها اختصاص دهد. در این مثال تنها اکشن متدهای Create و List را بررسی خواهیم کرد. با دنبال کردن همین روش میتوانید متدهای Edit و Delete را هم بسادگی تکمیل کنید.
یک متد constructor جدید بنویسید که آبجکتی از نوع UserManager میپذیرد. با استفاده از این کلاس میتوانید کاربران را در ASP.NET Identity مدیریت کنید.
private MyDbContext db; private UserManager<MyUser> manager; public ToDoController() { db = new MyDbContext(); manager = new UserManager<MyUser>(new UserStore<MyUser>(db)); }
اکشن متد Create را بروز رسانی کنید
هنگامی که یک ToDo جدید ایجاد میکنید، کاربر جاری را در ASP.NET Identity پیدا میکنیم و او را به ToDoها اختصاص میدهیم.
public async Task<ActionResult> Create ([Bind(Include="Id,Description,IsDone")] ToDo todo) { var currentUser = await manager.FindByIdAsync (User.Identity.GetUserId()); if (ModelState.IsValid) { todo.User = currentUser; db.ToDoes.Add(todo); await db.SaveChangesAsync(); return RedirectToAction("Index"); } return View(todo); }
اکشن متد List را بروز رسانی کنید
در این متد تنها ToDoهای کاربر جاری را باید بگیریم.
public ActionResult Index() { var currentUser = manager.FindById(User.Identity.GetUserId()); return View(db.ToDoes.ToList().Where( todo => todo.User.Id == currentUser.Id)); }
تنها مدیران سایت باید بتوانند تمام ToDoها را ببینند
بدین منظور ما یک اکشن متد جدید به کنترل مربوطه اضافه میکنیم که تمام ToDoها را لیست میکند. اما دسترسی به این متد را تنها برای کاربرانی که در نقش مدیر وجود دارند میسر میکنیم.
[Authorize(Roles="Admin")] public async Task<ActionResult> All() { return View(await db.ToDoes.ToListAsync()); }
نمایش جزئیات کاربران از جدول ToDo ها
از آنجا که ما کاربران را به ToDo هایشان مرتبط میکنیم، دسترسی به دادههای کاربر ساده است. مثلا در متدی که مدیر سایت تمام آیتمها را لیست میکند میتوانیم به اطلاعات پروفایل تک تک کاربران دسترسی داشته باشیم و آنها را در نمای خود بگنجانیم. در این مثال تنها یک فیلد بنام HomeTown اضافه شده است، که آن را در کنار اطلاعات ToDo نمایش میدهیم.
@model IEnumerable<AspnetIdentitySample.Models.ToDo> @{ ViewBag.Title = "Index"; } <h2>List of ToDoes for all Users</h2> <p> Notice that we can see the User info (UserName) and profile info such as HomeTown for the user as well. This was possible because we associated the User object with a ToDo object and hence we can get this rich behavior. 12: </p> <table class="table"> <tr> <th> @Html.DisplayNameFor(model => model.Description) </th> <th> @Html.DisplayNameFor(model => model.IsDone) </th> <th>@Html.DisplayNameFor(model => model.User.UserName)</th> <th>@Html.DisplayNameFor(model => model.User.HomeTown)</th> </tr> 25: 26: @foreach (var item in Model) 27: { 28: <tr> 29: <td> 30: @Html.DisplayFor(modelItem => item.Description) 31: </td> 32: <td> @Html.DisplayFor(modelItem => item.IsDone) </td> <td> @Html.DisplayFor(modelItem => item.User.UserName) </td> <td> @Html.DisplayFor(modelItem => item.User.HomeTown) </td> </tr> } </table>
صفحه Layout را بروز رسانی کنید تا به ToDoها لینک شود
<li>@Html.ActionLink("ToDo", "Index", "ToDo")</li> <li>@Html.ActionLink("ToDo for User In Role Admin", "All", "ToDo")</li>
حال اپلیکیشن را اجرا کنید. همانطور که مشاهده میکنید دو لینک جدید به منوی سایت اضافه شده اند.
ساخت یک ToDo بعنوان کاربر عادی
روی لینک ToDo کلیک کنید، باید به صفحه ورود هدایت شوید چرا که دسترسی تنها برای کاربران احراز هویت شده تعریف وجود دارد. میتوانید یک حساب کاربری محلی ساخته، با آن وارد سایت شوید و یک ToDo بسازید.
پس از ساختن یک ToDo میتوانید لیست رکوردهای خود را مشاهده کنید. دقت داشته باشید که رکوردهایی که کاربران دیگر ثبت کرده اند برای شما نمایش داده نخواهند شد.
مشاهده تمام ToDoها بعنوان مدیر سایت
روی لینک ToDoes for User in Role Admin کلیک کنید. در این مرحله باید مجددا به صفحه ورود هدایت شوید چرا که شما در نقش مدیر نیستید و دسترسی کافی برای مشاهده صفحه مورد نظر را ندارید. از سایت خارج شوید و توسط حساب کاربری مدیری که هنگام راه اندازی اولیه دیتابیس ساخته اید وارد سایت شوید.
User = Admin Password = 123456
من در یکی از پروژهها از Kendo UI Treeview
استفاده کردم و قصد داشتم قابلیت تغییر نام را به گرهها بدهم. به همین جهت پس از جستجو به x-editable
برخوردم. این کتابخانهی جاواسکریپتی در ابتدا برای قالبهای بوت استراپ
طراحی شده بود که در حال حاضر اینگونه نیست و به راحتی در هر پروژهای که
فقط جی کوئری صدا زده شده باشد، قابل اجرا است و نسخهی مخصوص Angular آن هم در این آدرس
قرار دارد. همچنین این قابلیت اختیاری و پیش فرض را دارد که به طور
خودکار اطلاعات تغییر یافته را به سمت url ایی که شما تعیین میکنید، ارسال کند.
برای همین نیازی به استفاده جداگانه از jQuery Ajax برای ارسال اطلاعات نیست
و البته در انتهای مقاله هم هنگام استفاده از درخت کندو به مشکلی برخوردم
که آن را هم بررسی میکنیم.
وارد کردن کتابخانه ها
این کتابخانه شامل دو فایل css و JS میباشد که بسته به محیطی که در آن کار میکنید متفاوت هستند. در این صفحه شما میتوانید برای 4 محیط Jquery ,JqueryUI , Bootstrap2 و Bootsrap3 بستهی مخصوصش را یا به صورت دانلود فایلها یا از طریق CDN دریافت نمایید. در اینجا هم یک دمو از قابلیتهای آن قابل مشاهده است.
برای شروع، کتابخانهی مورد نظر خود را دریافت و آنها را به صفحهی خود اضافه نمایید. در صورتیکه از Bootstrap استفاده میکنید، ابتدا فایلهای زیر را اضافه کنید:
وارد کردن کتابخانه ها
این کتابخانه شامل دو فایل css و JS میباشد که بسته به محیطی که در آن کار میکنید متفاوت هستند. در این صفحه شما میتوانید برای 4 محیط Jquery ,JqueryUI , Bootstrap2 و Bootsrap3 بستهی مخصوصش را یا به صورت دانلود فایلها یا از طریق CDN دریافت نمایید. در اینجا هم یک دمو از قابلیتهای آن قابل مشاهده است.
برای شروع، کتابخانهی مورد نظر خود را دریافت و آنها را به صفحهی خود اضافه نمایید. در صورتیکه از Bootstrap استفاده میکنید، ابتدا فایلهای زیر را اضافه کنید:
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet"> <script src="http://code.jquery.com/jquery-2.0.3.min.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
اولین حالتیکه میتوانید با این کتابخانه کار کنید، استفاده از خاصیت *-data است. نمونه زیر را در نظر بگیرید:
<a href="#" id="favsite" data-type="text" data-pk="1" data-url="@Url.Action(MVC.Categories.EditCategory())" data-title="Enter your favorite site">dotnettips.info</a>
$(document).ready(function () { $('#favsite').editable(); });
$(document).ready(function () { $.fn.editable.defaults.mode = 'inline'; $('#favsite').editable(); });
حالت بعدی که میتوان استفاده کرد به شکل زیر است:
<a href="#" id="favsite" >dotnettips.info</a>
$.fn.editable.defaults.mode = 'inline'; $(document).ready(function () { $('#favsite').editable({ type: 'text', pk: 1, url: '@Url.Action(MVC.Categories.EditCategory())', title: 'Enter your favorite site' }); });
خوبی این روش این است که میتوان اطلاعات بیشتری چون رویدادها را به آن پاس داد. تا الان با نحوهی انتساب آن به اشیاء آشنا شدیم. اجازه دهید تا با خصوصیات آن آشنا شویم.
AjaxOptions | همانطور
که متوجه شدید به طور خودکار اطلاعات ویرایش شده، به سمت آدرس داده شده،
به شیوه Post ارسال میگردند. در صورتیکه قصد دست بردن
در نوع درخواست را دارید، میتوانید از این ویژگی استفاده کنید: ajaxOptions: { type: 'put', dataType: 'json' } |
Anim | این
ویژگی که تنها در حالت inline پاسخ میدهد، میتواند زمان بسته شدن
x-editable را تغییر دهد که به طور پیش فرض با false مقداردهی شده است. جهت
تغییر زمان بسته شدن، کد زیر را وارد نمایید: anim:'false' //or anim: { duration: 2000 } |
autotext | در انتهای جدول آمده است. |
defaultValue | در
صورتیکه عنصر مورد نظر محتوایی نداشته باشد و این خصوصیت را
مقداردهی کنید، موقع ویرایش، این عبارت تعیین شده نمایش مییابد. در مثال
بالا باید متن تگ a را حذف کرده تا نتیجه را ببینید: (البته فیلد value نباید مقداری داشته باشد) defaultValue: 'default val' //or defaultValue: undefined //or defaultValue: null |
disabled | false کردن این ویژگی باعث غیرفعال شدن x-editable بر روی کنترل جاری میگردد. |
display | خاصیت
display یا مقدار بولین false را دریافت میکند، یا نال، یا یک تابع
callback را میتوان به آن پاس داد. این خصوصت زمانی صدا زده میشود که
اطلاعات به سمت آدرس سرور رفته و با موفقیت بازگشت داده میشوند (در صورتی
که این ویژگی غیرفعال باشد، بلافاصه بعد از تایید کاربر، از اطلاعات وارد شده
صدا زده میشود) و سپس متن جدید عنصر تغییر مییابد. حال اگر این خاصیت نال
که مقدار پیش فرض آن است باشد، متن تغییر مییابد. ولی اگر false باشد، متن سابق
باقی خواهد ماند و در صورتیکه تابعی به آن پاس داده باشید، طبق تابع شما
عمل خواهد کرد. پارامترهایی که تابع شما میتواند داشته باشد به شرح زیر است: value : مقدار جدید response : پاسخ سرور ( در صورتی که ارسال از طریق Ajax صورت گرفته باشد) و در صورتیکه عنصر شما checlklist یا select باشد که حاوی منبعی از مقادیر هست، مقادیرشان در قالب یک آرایه با نام sourceData بازگشت خواهد خورد. برای دسترسی به آیتمهای انتخابی هم از کد زیر استفاده میکنیم: $.fn.editableutils.itemsByValue(value, sourceData) |
emptyclass | معرفی یک کلاس css برای موقعیکه عنصر خالی است. |
emptytext | در صورتی خالی بودن عنصر، این متن را برای عنصر نمایش بده. |
highlight | بعد از به روز رسانی متن عنصر، آن را با این رنگ highlight خواهد کرد و کد رنگی باید در مبنای هگز باشد. مقدار پیش فرض آن false است. |
mode | دو
حالت نمایشی دارد که پیش فرض آن popup است و با باز کردن یک پنجره، مقدار
جدید را دریافت میکند. مورد بعدی inline است که به جای باز کردن پنجره،
متن عنصر را به حالت ویرایش تغییر میدهد. |
name | نام فیلدی که مقدارش تغییر میکند. |
onblur | زمانی که کاربر فوکوس را از ویرایشگر میگیرد، ویرایشگر چه پاسخی باید به آن بدهد، باز بماند؟ ignore ، بسته شود؟ cancel و یا مقدار داده شده را تایید کند؟submit |
params | پارامترهای درخواست ایجکسی که کنترل در حالت پیش فرض ارسال میکند؛ شامل Pk که آن را با id
رکورد پر میکنیم. name نام فیلدی که تغییر یافته است و value که مقدار جدید
است. در صورتیکه دوست دارید اطلاعات اضافیتری نیز ارسال شوند،
میتوانید از این خاصیت استفاده کنید و پارامترها را در قالب Object به آن
پاس کنید. ولی اگر بخواهید در کل همهی پارامترها را رونویسی کنید باید یک
تابع را به آن پاس کنید: params: function(params) { //در این حالت پارامترهای پیش فرض ارسال نشده و تنها پارامترهای معرفی شده در این تابع ارسال میشوند params.a = 1; return params; } |
pk | کلید
اصلی رکورد شما در دیتابیس یا هان id است. در صورتی که از کلیدهای ترکیبی
استفاده میکنید، نگران نباشید فکر آن را هم کرده اند.//کلید عدد pk:1, //کلید رشته ای pk:'dp123' //کلید ترکیبی pk:{id: 1, lang: 'en'} //معرفی یک تابع به آن و بازگشت |
Placement | این ویژگی فقط به درد حالت Popup میخورد که پنجره را کجای عنصر نمایش دهد و شامل چهار مقدار left,right,top,bottom میشود. |
saveonchange | زمانی
که مقدار جدید، برابر مقدار فعلی باشد و این خاصیت false باشد، هیچ تغییری رخ
نخواهد داد. ولی اگر برابر true باشد ،مقدار جدید اسال و جایگزین مقدار فعلی
خواهد شد. مقدار پیش فرض آن false است. |
selector |
با استفاده از این خصوصیت در عنصر انتخابی به دنبال عناصری که در selector
تعیین شده میگردد و حالت ویرایش را روی آنها فعال میکند. در این حالت استفاده از خصوصیات emptytext و autotext در ابتدای امر ممکن نیست و بعد از اولین کلیک قابل استفاده هستند. نکته بعدی اینکه شما باید کلاسهای زیر را دستی اضافه کنید. کلاس editable-click برای همه کنترلها وکلاس editable-empty به کنترلهای بدون مقدار و برای مقداردهی کنترلهای بدون مقدار میتوان از خاصیت ''=data-value استفاده کرد. <div id="user"> <!-- empty --> <a href="#" data-name="username" data-type="text" data-value="" title="Username">Empty</a> <!-- non-empty --> <a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" title="Group">Operator</a> </div> <script> $('#user').editable({ selector: 'a', url: '/post', pk: 1 }); </script> |
send | سه
مقدار auto,always و never را دریافت میکند. موقعی که شما آن را روی auto
تنظیم کنید؛ در صورتی مقادیر به سمت سرور ارسال میشوند که دو خاصیت url و
pk تعریف شده باشند. در غیر این صورت ویرایش فقط در حالت محلی و روی سیستم
کاربر رخ خواهد داد. |
showbuttons | در
صورتیکه با false مقداردهی شود، تایید فرم به طور خودکار انجام میگیرد و
اگر با یکی از مقادیر left یا Bottom پر شود، دکمهها را در آن قسمت نشان
میدهد. |
success | اطلاعات به سمت سرور
رفته و با موفقیت با کد 200 بازگشت داده شدهاند. در مستندات نوشته است، هر
کد وضعیتی غیر از 200 بازگشت داده شود، به سمت خاصیت error هدایت میشو.د ولی
آن طور که من با httpresponsemessage
تست کردم، چنین چیزی را مشاهده نکردم و مجددا success صدا زده شد. پس بهتر
هست دادهای را که به سمت کنترل برگشت میدهید، خودتان کنترل کنید. به خصوص
اگر انتقال اطلاعات صحیح باشد. ولی اگر در دیتابیس، تغییر با خطا روبرو گردد
بهتر است نتیجهی آن ارسال شده و از تغییر مقدار فعلی ممانعت به عمل آورید. success: function(response, newValue) { if(!response.success) return response.msg; } |
toggle | اگر قصد دارید که باز و بسته کردن ویرایشگر را بر عهدهی مثلا یک دکمهی روی صفحه بگذارید، این خصوصیت به شما کمک میکند:$('#edit-button').click(function(e) { e.stopPropagation(); $('#favsite').editable('toggle'); }); |
type | نوع
ویرایشی را که قرار است انجام گیرد، مشخص میکند. text برای متن، date برای
تاریخ، textarea جهت متون طولانیتر نسبت به text و بسیاری از موارد دیگر |
unsavedclass | این
کلاس موقعی اعمال میگردد که اطلاعاتی را ویرایش کردهاید، ولی اطلاعاتی
به سمت سرور ارسال نشده است. مثلا pk مقداردهی نشده یا send=never قرار
داید و یا اینکه به صورت محلی ذخیره میکنید و میخواهید در آخر همهی اطلاعات را
ارسال کنید. این خاصیت به طور پیش فرض با کلاس editable-unsaved مقداردهی شده که میتوانید با نال کردن، از شرش خلاص شوید. |
url | این
خاصیت با آدرس سمت سرور پر میشود. ولی میتوان به آن یک تابع هم پاس کرد که
این تابع جایگزین درخواست ایجکسی خودش خواهد شد و برای بازگشت دادن نتیجهی این درخواست به سمت تابعهای callback خودش میتوانید یک deferred object را برگشت دهید: url: function(params) { var d = new $.Deferred; if(params.value === 'abc') { //returning error via deferred object return d.reject('error message'); } else { //async saving data in js model someModel.asyncSaveMethod({ ..., success: function(){ d.resolve(); } }); return d.promise(); } } |
validate | مقدار
پیش فرض آن نال است و میتوان به آن یک تابع را جهت اعتبارسنجی سمت کلاینت پاس
داد. به عنوان آرگومان، مقدار جدیدی را ارسال کرده و در آن به اعتبارسنجی
میپردازید. در صورتی که مقدار، نامعتبر باشد، میتوانید یک پیام خطا از نوع
رشتهای را برگردانید. در صورتی که از نسخهی 1.5.1 به بعد استفاده میکنید، دریافت یک object با مقادیر زیر نیز ممکن شده است: newValue: مقدار جدید و جایگزین مقدار غیر معتبر. msg : پیام خطا. به کدهای زیر در سه حالت نگاه کنید: validate: function (value) { if ($.trim(value) == '') { //در تمامی نسخههای یک پیام متنی باز میگردد return 'This field is required'; //1.5.1 //یک مقدار جدید برگشت میدهد که بلافاصله آن را // تایید میکند و متن عنصر به روز میشود return { newValue: 'validated' }; //متن جدید ار ارسال کرده و پیام هشدار را نشان میدهد //ولی تایید نمیکند و منتظر تایید کاربر است return { newValue: 'validated', |
value | مقدار پیش فرضی که در ویرایشگر نشان میدهد و اگر مقداردهی نشود، از متن عنصر استفاده میکند. |
autotext | سه مقدار دارد auto (پیش فرض)،always و never. موقعیکه عنصر شما متنی نداشته باشد و روی auto تنظیم شده باشد، مقدار value را به عنوان متن عنصر نمایش میدهد. always کاری ندارد که عنصر شما متن دارد یا خالی است؛ مقدار value به آن انتساب داده خواهد شد. never هیچگاه. |
در قسمت بعدی که قسمت پایانی است مطالب را ادامه میدهیم.
پس از بررسی مباحث و نکات پایهای کار با کتابخانهی Moq، در این قسمت تعدادی از نکات تکمیلی آنرا بررسی خواهیم کرد.
حالتهای عملکرد کتابخانهی Moq
کتابخانهی Moq، دو حالت عملکرد را دارد: Strict Mode و Loose mode. زمانیکه یک Mock object را نمونه سازی میکنیم، به صورت پیشفرض کتابخانهی Moq، یک Loose mock را ایجاد میکند. در این حالت این شیء، مقادیر پیشفرض خواص و اشیاء را بازگشت میدهد و استثنائی را صادر نمیکند. اگر این موارد مدنظر نیستند، میتوان به حالت Strict آن رجوع کرد که روش تنظیم آن به صورت زیر است:
در این حالت اگر متد آزمون واحد را اجرا کنیم، با پیام زیر، با شکست مواجه خواهد شد:
در حالت Strict، تمام فراخوانیهای شیء Mock شده باید دارای Setup باشند (نیازی به Setup تمام موارد نیست؛ فقط مواردی که در فراخوانیهای آزمون واحد، مورد استفاده قرار میگیرند، حتما باید تنظیم شوند). برای نمونه در اینجا عنوان کردهاست که در این آزمایش، تنظیمات متد Initialize انجام نشدهاست که با تعریف سطر زیر، این مشکل برطرف میشود:
بنابراین هرچند کارکردن با حالت پیشفرض کتابخانهی Moq سادهاست، اما تنظیم حالت Strict سبب میشود تا تنظیمی را فراموش نکنیم و در نتیجه کیفیت آزمون واحد تهیه شده افزایش مییابد.
صدور استثناءها از طریق Mock objects
اگر در سیستم در حال آزمایش، قسمتی به بررسی خطاها اختصاص دارد، میتوان توسط Mock objects استثناءهایی را تولید و به این ترتیب منطق بررسی خطاها را آزمایش کرد.
برای نمونه در متد Process کلاس LoanApplicationProcessor، یک try/catch را به قسمت CalculateScore اضافه میکنیم:
زمانیکه کار فراخوانی متد CalculateScore صورت میگیرد، برای تنظیم آزمون واحد آن میتوان از متد Throws، برای صدور یک استثناء استفاده کرد:
صدور این استثناء سبب خواهد شد تا درخواست شخص، رد شود. بنابراین در آزمایش آن میتوان این مساله را بررسی کرد و از رسیدن به این قسمت (رد شدن درخواست) اطمینان حاصل نمود:
صدور رخدادها از طریق Mock objects
فرض کنید یک EventArgs سفارشی را به صورت زیر تعریف:
و سپس رخدادی را به نحو زیر به ICreditScorer اضافه کردهایم:
برای اینکه یک Mock object سبب بروز رخداد ResultAvailable شود (به صورت دستی و دقیقا در سطری که مشخص میکنیم)، میتوان به صورت زیر عمل کرد:
ابتدا توسط متد Raise، رخداد مدنظر را ذکر میکنیم و سپس یک نمونهی EventArgs را به آن ارسال خواهیم کرد.
روش دیگر انجام اینکار به صورت زیر است:
در این حالت با فراخوانی متد CalculateScore، رخداد ResultAvailable به صورت خودکار صادر میشود.
معرفی Partial Mocks
در اغلب آزمونهای واحدی که تا اینجا بررسی شدند، ابتدا یک Mock object را ایجاد و سپس وهلهای از سرویس مدنظر را توسط آن تهیه میکنیم. در ادامه تعدادی از متدهای این سرویس را مانند متد Process کلاس LoanApplicationProcessor، فراخوانی میکنیم. اینکار سبب اجرای فعالیتی در این سیستم شده و به همراه آن تعاملی با اشیاء Mock شده نیز صورت میگیرد. در نهایت حالت و یا نتیجهای را دریافت میکنیم و آنرا با حالت یا نتیجهای که انتظار داریم، مقایسه خواهیم کرد. در این روش پس از پایان اجرای سیستم در حال اجرا، حالت و نتیجهی نهایی حاصل از عملکرد آن، مورد بررسی قرار میگیرد. این بررسیها را نیز بر روی اینترفیسها انجام دادیم. اگر بجای اینترفیسها از یک class استفاده شود، به آن partial mock گفته میشود. عموما مواردی را که آزمایش آنها سخت است، با Partial mocks پیاده سازی میکنند؛ مانند کار با فایل سیستم، کار با قطعه کدهای نامعین مانند DateTime.Now، اعداد اتفاقی و یا Guidها.
در مثال زیر، شبیه به متد آزمون واحد Accept که تاکنون آنرا بررسی کردیم، از اشیاء Mock شده استفاده شدهاست؛ با یک تفاوت: بجای اینترفیس IIdentityVerifier، از کلاس پیاده سازی کنندهی آن که در اینجا IdentityVerifierServiceGateway است، استفاده شده:
در اینجا برای اینکه بتوانیم متد CallService را که private بوده، بررسی و تنظیم کنیم، آنرا به public virtual تبدیل کردهایم تا توسط Moq قابل دسترسی و همچنین قابل بازنویسی شود:
تبدیل DateTime.Now به یک مقدار ثابت قابل آزمایش توسط Partial Mocks
در کلاس IdentityVerifierServiceGateway، یک چنین کدی را داریم که از DateTime.Now نامشخص استفاده میکند و آزمون واحد نوشتن برای آن مشکل است؛ چون DateTime.Now در هربار که آزمایش اجرا میشود، تغییر میکند:
برای بالابردن قابلیت آزمون نویسی این کلاس، آنرا به صورت زیر Refactor میکنیم تا DateTime.Now را به صورت یک متد public virtual دریافت کند:
اکنون آزمون واحد نویسی برای این کلاس توسط Mock objects بسیار سادهاست:
در اینجا خروجی متد GetCurrentTime بر روی Mock object تهیه شده، به یک مقدار ثابت تنظیم شدهاست که با هر بار اجرای آزمایش در زمانهای مختلف، تغییری نمیکند و وابستهی به DateTime.Now نامشخص، نیست.
استفاده از متدهای protected بجای استفاده از متدهای public virtual در Partial Mocks
همانطور که مشاهده کردید، برای کار با Partial Mocks نیاز است متدهای معرفی شده، از نوع public virtual باشند. برای نمونه حتی مجبور شدیم یک متد private را نیز public کنیم. اگر علاقمند به این نوع تغییرات نیستید، میتوان بجای public کردن متدهای private، آنها را protected تعریف کرد. به همین جهت دو متدی را که تاکنون public virtual تعریف کردیم، تبدیل به protected virtual میکنیم.
پس از آن در کلاسی که آزمونهای واحد را تهیه کردیم، ابتدا using Moq.Protected را ذکر میکنیم تا بتوانیم به قابلیتهای ویژهی کار با متدهای Protected دسترسی پیدا کنیم.
سپس روش تنظیم این نوع متدهای protected، چون دسترسی مستقیمی به آنها وجود ندارد، به صورت زیر، با ذکر نام رشتهای آنها تغییر میکند:
ابتدا متد Protected شیء Mock شده ذکر میشود و پس از آن متد Setup باید دقیقا نوع بازگشتی متد در حال تنظیم را ذکر کند؛ چون دیگر دسترسی strongly typed ای به آن نداریم. پس از آن، لیست پارامترهای متد، ذکر میشوند.
روش دیگری نیز برای تعریف متدهای protected وجود دارد که اینبار strongly typed است. بالای متد آزمون واحد، اینترفیس private زیر را تعریف میکنیم:
که در آن متدهای تعریف شده، با متدهای protected در حال بررسی، امضای یکسانی دارند (و همواره با هر تغییری در برنامه نیز باید این وضعیت حفظ شود). در ادامه تعاریف تنظیمات این متدها به صورت strongly typed زیر قابل انجام است:
معرفی روش دیگری بجای استفاده از متدهای protected
اگر در کدهای خود نیاز به استفادهی بیش از حد از متدهای protected را مشاهده کردید، این مورد میتوان نشانهی امکان Refactoring این قسمت از کدها به سرویسهایی مجزا باشند. برای مثال میتوان یک اینترفیس INowProvider را به صورت زیر تعریف کرد:
و سپس آنرا به سازندهی کلاس IdentityVerifierServiceGateway تزریق کرد:
و متد GetCurrentTime را حذف و آنرا با متد GetNow این سرویس جایگزین نمود:
به این ترتیب نیاز به تنظیم متد protected بازگشت زمان، حذف شده و میتوان از این سرویس جدید استفاده کرد:
کدهای کامل این سری را از اینجا میتوانید دریافت کنید: MoqSeries-05.zip
حالتهای عملکرد کتابخانهی Moq
کتابخانهی Moq، دو حالت عملکرد را دارد: Strict Mode و Loose mode. زمانیکه یک Mock object را نمونه سازی میکنیم، به صورت پیشفرض کتابخانهی Moq، یک Loose mock را ایجاد میکند. در این حالت این شیء، مقادیر پیشفرض خواص و اشیاء را بازگشت میدهد و استثنائی را صادر نمیکند. اگر این موارد مدنظر نیستند، میتوان به حالت Strict آن رجوع کرد که روش تنظیم آن به صورت زیر است:
var mockIdentityVerifier = new Mock<IIdentityVerifier>(MockBehavior.Strict);
Test method Loans.Tests.LoanApplicationProcessorShould.Accept threw exception: Moq.MockException: IIdentityVerifier.Initialize() invocation failed with mock behavior Strict. All invocations on the mock must have a corresponding setup.
mockIdentityVerifier.Setup(x => x.Initialize());
بنابراین هرچند کارکردن با حالت پیشفرض کتابخانهی Moq سادهاست، اما تنظیم حالت Strict سبب میشود تا تنظیمی را فراموش نکنیم و در نتیجه کیفیت آزمون واحد تهیه شده افزایش مییابد.
صدور استثناءها از طریق Mock objects
اگر در سیستم در حال آزمایش، قسمتی به بررسی خطاها اختصاص دارد، میتوان توسط Mock objects استثناءهایی را تولید و به این ترتیب منطق بررسی خطاها را آزمایش کرد.
برای نمونه در متد Process کلاس LoanApplicationProcessor، یک try/catch را به قسمت CalculateScore اضافه میکنیم:
try { _creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address); } catch { return application.IsAccepted; }
mockCreditScorer.Setup(x => x.CalculateScore(It.IsAny<string>(), It.IsAny<string>())) .Throws(new InvalidOperationException("Test Exception"));
Assert.IsFalse(application.IsAccepted);
صدور رخدادها از طریق Mock objects
فرض کنید یک EventArgs سفارشی را به صورت زیر تعریف:
using System; namespace Loans.Models { public class CreditScoreResultArgs : EventArgs { public int Score { get; set; } } }
public interface ICreditScorer { event EventHandler<CreditScoreResultArgs> ResultAvailable;
mockCreditScorer.Raise(x => x.ResultAvailable += null, new CreditScoreResultArgs());
روش دیگر انجام اینکار به صورت زیر است:
mockCreditScorer.Setup(x => x.CalculateScore(It.IsAny<string>(), It.IsAny<string>())) .Raises(x => x.ResultAvailable += null, new CreditScoreResultArgs());
معرفی Partial Mocks
در اغلب آزمونهای واحدی که تا اینجا بررسی شدند، ابتدا یک Mock object را ایجاد و سپس وهلهای از سرویس مدنظر را توسط آن تهیه میکنیم. در ادامه تعدادی از متدهای این سرویس را مانند متد Process کلاس LoanApplicationProcessor، فراخوانی میکنیم. اینکار سبب اجرای فعالیتی در این سیستم شده و به همراه آن تعاملی با اشیاء Mock شده نیز صورت میگیرد. در نهایت حالت و یا نتیجهای را دریافت میکنیم و آنرا با حالت یا نتیجهای که انتظار داریم، مقایسه خواهیم کرد. در این روش پس از پایان اجرای سیستم در حال اجرا، حالت و نتیجهی نهایی حاصل از عملکرد آن، مورد بررسی قرار میگیرد. این بررسیها را نیز بر روی اینترفیسها انجام دادیم. اگر بجای اینترفیسها از یک class استفاده شود، به آن partial mock گفته میشود. عموما مواردی را که آزمایش آنها سخت است، با Partial mocks پیاده سازی میکنند؛ مانند کار با فایل سیستم، کار با قطعه کدهای نامعین مانند DateTime.Now، اعداد اتفاقی و یا Guidها.
در مثال زیر، شبیه به متد آزمون واحد Accept که تاکنون آنرا بررسی کردیم، از اشیاء Mock شده استفاده شدهاست؛ با یک تفاوت: بجای اینترفیس IIdentityVerifier، از کلاس پیاده سازی کنندهی آن که در اینجا IdentityVerifierServiceGateway است، استفاده شده:
namespace Loans.Tests { [TestClass] public class LoanApplicationProcessorShould { [TestMethod] public void AcceptUsingPartialMock() { var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m}; var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0}; var applicant = new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0}; var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant}; var mockIdentityVerifier = new Mock<IdentityVerifierServiceGateway>(); mockIdentityVerifier.Setup(x => x.CallService(applicant.Name, applicant.Age, applicant.Address)) .Returns(true); var mockCreditScorer = new Mock<ICreditScorer>(); mockCreditScorer.Setup(x => x.ScoreResult.ScoreValue.Score).Returns(110_000); var sut = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object); sut.Process(application); Assert.IsTrue(application.IsAccepted); } } }
public virtual bool CallService(string applicantName, int applicantAge, string applicantAddress)
تبدیل DateTime.Now به یک مقدار ثابت قابل آزمایش توسط Partial Mocks
در کلاس IdentityVerifierServiceGateway، یک چنین کدی را داریم که از DateTime.Now نامشخص استفاده میکند و آزمون واحد نوشتن برای آن مشکل است؛ چون DateTime.Now در هربار که آزمایش اجرا میشود، تغییر میکند:
public bool Validate(string applicantName, int applicantAge, string applicantAddress) { Connect(); var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress); LastCheckTime = DateTime.Now; Disconnect(); return isValidIdentity; }
public bool Validate(string applicantName, int applicantAge, string applicantAddress) { Connect(); var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress); LastCheckTime = GetCurrentTime(); Disconnect(); return isValidIdentity; } public virtual DateTime GetCurrentTime() { return DateTime.Now; }
var expectedTime = new DateTime(2000, 1, 1); mockIdentityVerifier.Setup(x => x.GetCurrentTime()) .Returns(expectedTime); // ... Assert.AreEqual(expectedTime, mockIdentityVerifier.Object.LastCheckTime);
استفاده از متدهای protected بجای استفاده از متدهای public virtual در Partial Mocks
همانطور که مشاهده کردید، برای کار با Partial Mocks نیاز است متدهای معرفی شده، از نوع public virtual باشند. برای نمونه حتی مجبور شدیم یک متد private را نیز public کنیم. اگر علاقمند به این نوع تغییرات نیستید، میتوان بجای public کردن متدهای private، آنها را protected تعریف کرد. به همین جهت دو متدی را که تاکنون public virtual تعریف کردیم، تبدیل به protected virtual میکنیم.
پس از آن در کلاسی که آزمونهای واحد را تهیه کردیم، ابتدا using Moq.Protected را ذکر میکنیم تا بتوانیم به قابلیتهای ویژهی کار با متدهای Protected دسترسی پیدا کنیم.
سپس روش تنظیم این نوع متدهای protected، چون دسترسی مستقیمی به آنها وجود ندارد، به صورت زیر، با ذکر نام رشتهای آنها تغییر میکند:
mockIdentityVerifier.Protected().Setup<bool>( "CallService",applicant.Name, applicant.Age, applicant.Address) .Returns(true); var expectedTime = new DateTime(2000, 1, 1); mockIdentityVerifier.Protected().Setup<DateTime>("GetCurrentTime") .Returns(expectedTime);
روش دیگری نیز برای تعریف متدهای protected وجود دارد که اینبار strongly typed است. بالای متد آزمون واحد، اینترفیس private زیر را تعریف میکنیم:
interface IIdentityVerifierServiceGatewayProtectedMembers { DateTime GetCurrentTime(); bool CallService(string applicantName, int applicantAge, string applicantAddress); }
mockIdentityVerifier.Protected() .As<IIdentityVerifierServiceGatewayProtectedMembers>() .Setup(x => x.CallService(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>())) .Returns(true); var expectedTime = new DateTime(2000, 1, 1); mockIdentityVerifier.Protected() .As<IIdentityVerifierServiceGatewayProtectedMembers>() .Setup(x => x.GetCurrentTime()) .Returns(expectedTime);
معرفی روش دیگری بجای استفاده از متدهای protected
اگر در کدهای خود نیاز به استفادهی بیش از حد از متدهای protected را مشاهده کردید، این مورد میتوان نشانهی امکان Refactoring این قسمت از کدها به سرویسهایی مجزا باشند. برای مثال میتوان یک اینترفیس INowProvider را به صورت زیر تعریف کرد:
using System; namespace Loans.Services.Contracts { public interface INowProvider { DateTime GetNow(); } }
public class IdentityVerifierServiceGateway : IIdentityVerifier { private readonly INowProvider _nowProvider; public DateTime LastCheckTime { get; private set; } public IdentityVerifierServiceGateway(INowProvider nowProvider) { _nowProvider = nowProvider; }
public bool Validate(string applicantName, int applicantAge, string applicantAddress) { Connect(); var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress); LastCheckTime = _nowProvider.GetNow(); // ...
var mockNowProvider = new Mock<INowProvider>(); mockNowProvider.Setup(x => x.GetNow()).Returns(expectedTime); var mockIdentityVerifier = new Mock<IdentityVerifierServiceGateway>(mockNowProvider.Object);
خلاصهای را در مورد SQL Server CE قبلا در این سایت مطالعه کردهاید. در ادامه خلاصهای کاربردی را از تنظیمات و نکات مرتبط به کار با SQL-CE به کمک NHibernate ملاحظه خواهید نمود:
1) دریافت SQL-CE 4.0
همین مقدار برای استفاده از SQL-CE 4.0 به کمک NHibernate کفایت میکند و حتی نیازی به نصب سرویس پک یک VS 2010 هم نیست.
2) ابزار سازی جهت ایجاد یک بانک اطلاعاتی خالی SQL-CE
using System;
using System.IO;
namespace NHibernate.Helper.DbSpecific
{
public class SqlCEDbHelper
{
const string engineTypeName = "System.Data.SqlServerCe.SqlCeEngine, System.Data.SqlServerCe";
/// <summary>
/// note: this method will delete existing db and then creates a new one.
/// </summary>
/// <param name="filename"></param>
/// <param name="password"></param>
public static void CreateEmptyDatabaseFile(string filename, string password = "")
{
if (File.Exists(filename))
File.Delete(filename);
var type = System.Type.GetType(engineTypeName);
var localConnectionString = type.GetProperty("LocalConnectionString");
var createDatabase = type.GetMethod("CreateDatabase");
var engine = Activator.CreateInstance(type);
string connectionStr = string.Format("Data Source='{0}';Password={1};Encrypt Database=True", filename, password);
if (string.IsNullOrWhiteSpace(password))
connectionStr = string.Format("Data Source='{0}'", filename);
localConnectionString.SetValue(
obj: engine,
value: connectionStr,
index: null);
createDatabase.Invoke(engine, new object[0]);
}
/// <summary>
/// use this method to compact or encrypt existing db or decrypt it to a new db with all records
/// </summary>
/// <param name="sourceConnection"></param>
/// <param name="destConnection"></param>
public static void CompactDatabase(string sourceConnection, string destConnection)
{
var type = System.Type.GetType(engineTypeName);
var engine = Activator.CreateInstance(type);
var localConnectionString = type.GetProperty("LocalConnectionString");
localConnectionString.SetValue(
obj: engine,
value: sourceConnection,
index: null);
var compactDatabase = type.GetMethod("Compact");
compactDatabase.Invoke(engine, new object[] { destConnection });
}
}
}
کلاس فوق، یک کلاس عمومی است و مرتبط به NHibernate نیست و در همه جا قابل استفاده است.
متد CreateEmptyDatabaseFile یک فایل بانک اطلاعاتی خالی با فرمت مخصوص SQL-CE را برای شما تولید خواهد کرد. به این ترتیب میتوان بدون نیاز به ابزار خاصی، سریعا یک بانک خالی را تولید و شروع به کار کرد. در این متد اگر کلمه عبوری را وارد نکنید، بانک اطلاعاتی رمزنگاری شده نخواهد بود و اگر کلمه عبور را وارد کنید، دیتابیس اولیه به همراه کلیه اعمال انجام شده بر روی آن در طول زمان، با کمک الگوریتم AES به صورت خودکار رمزنگاری خواهند شد. کل کاری را هم که باید انجام دهید ذکر این کلمه عبور در کانکشن استرینگ است.
متد CompactDatabase، یک متد چند منظوره است. اگر بانک اطلاعاتی SQL-CE رمزنگاری نشدهای دارید و میخواهید کل آنرا به همراه تمام اطلاعات درون آن رمزنگاری کنید، میتوانید جهت سهولت کار از این متد استفاده نمائید. آرگومان اول آن به کانکشن استرینگ بانکی موجود و آرگومان دوم به کانکشن استرینگ بانک جدیدی که تولید خواهد شد، اشاره میکند.
همچنین اگر یک بانک اطلاعاتی SQL-CE رمزنگاری شده دارید و میخواهید آنرا به صورت یک بانک اطلاعاتی جدید به همراه تمام رکوردهای آن رمزگشایی کنید، باز هم میتوان از این متد استفاده کرد. البته بدیهی است که کلمه عبور را باید داشته باشید و این کلمه عبور جایی درون فایل بانک اطلاعاتی ذخیره نمیشود. در این حالت در کانکشن استرینگ اول باید کلمه عبور ذکر شود و کانکشن استرینگ دوم نیازی به کلمه عبور نخواهد داشت.
فرمت کلی کانکشن استرینگ SQL-CE هم به شکل زیر است:
Data Source=c:\path\db.sdf;Password=1234;Encrypt Database=True
البته این برای حالتی است که قصد داشته باشید بانک اطلاعاتی مورد استفاده را رمزنگاری کنید یا از یک بانک اطلاعاتی رمزنگاری شده استفاده نمائید. اگر بانک اطلاعاتی شما کلمه عبوری ندارد، ذکر Data Source=c:\path\db.sdf کفایت میکند.
این کلاس هم از این جهت مطرح شد که NHibernate میتواند ساختار بانک اطلاعاتی را بر اساس تعاریف نگاشتها به صورت خودکار تولید و اعمال کند، «اما» بر روی یک بانک اطلاعاتی خالی SQL-CE از قبل تهیه شده (در غیراینصورت خطای The database file cannot be found. Check the path to the database را دریافت خواهید کرد).
نکته:
اگر دقت کرده باشید در این کلاس engineTypeName به صورت رشته ذکر شده است. چرا؟
علت این است که با ذکر engineTypeName به صورت رشته، میتوان از این کلاس در یک کتابخانه عمومی هم استفاده کرد، بدون اینکه مصرف کننده نیازی داشته باشد تا ارجاع مستقیمی را به اسمبلی SQL-CE به برنامه خود اضافه کند. اگر این ارجاع وجود داشت، متدهای یاد شده کار میکنند، در غیراینصورت در گوشهای ساکت و بدون دردسر و بدون نیاز به اسمبلی خاصی برای روز مبادا قرار خواهند گرفت.
3) ابزار مرور اطلاعات بانک اطلاعاتی SQL-CE
با استفاده از management studio خود SQL Server هم میشود با بانکهای اطلاعاتی SQL-CE کار کرد، اما ... اینبار برخلاف نگارش کامل اس کیوال سرور، با یک نسخهی بسیار بدوی، که حتی امکان rename فیلدها را هم ندارد مواجه خواهید شد. به همین جهت به شخصه برنامه SqlCe40Toolbox را ترجیح میدهم و اطمینان داشته باشید که امکانات آن برای کار با SQL-CE از امکانات ارائه شده توسط management studio مایکروسافت، بیشتر و پیشرفتهتر است!
4) تنظیمات NHibernate جهت کار با SQL-CE
الف) پس از نصب SQL-CE ، فایلهای آنرا در مسیر C:\Program Files\Microsoft SQL Server Compact Edition\v4.0 میتوان یافت. درایور ADO.NET آن هم در مسیر C:\Program Files\Microsoft SQL Server Compact Edition\v4.0\Desktop قرار دارد. بنابراین در ابتدا نیاز است تا ارجاعی را به اسمبلی System.Data.SqlServerCe.dll به برنامه خود اضافه کنید (نام پوشه desktop آن هم غلط انداز است. از این جهت که نگارش 4 آن، به راحتی در برنامههای ذاتا چند ریسمانی ASP.Net بدون مشکل قابل استفاده است).
نکته مهم: در این حالت NHibernate قادر به یافتن فایل درایور یاد شده نخواهد بود و پیغام خطای «Could not create the driver from NHibernate.Driver.SqlServerCeDriver» را دریافت خواهید کرد. برای رفع آن، اسمبلی System.Data.SqlServerCe.dll را در لیست ارجاعات برنامه یافته و در برگه خواص آن، خاصیت «Copy Local» را true کنید. به این معنا که NHibernate این اسمبلی را در کنار فایل اجرایی برنامه شما جستجو خواهد کرد.
ب) مطلب بعد، تنظیمات ابتدایی NHibernate است جهت شناساندن SQL-CE . مابقی مسایل (نکات mapping، کوئریها و غیره) هیچ تفاوتی با سایر بانکهای اطلاعاتی نخواهد داشت و یکی است. به این معنا که اگر برنامه شما از ویژگیهای خاص بانکهای اطلاعاتی استفاده نکند (مثلا اگر از رویههای ذخیره شده اس کیوال سرور استفاده نکرده باشد)، فقط با تغییر کانکشن استرینگ و معرفی dialect و driver جدید، به سادگی میتواند به یک بانک اطلاعاتی دیگر سوئیچ کند؛ بدون اینکه حتی بخواهید یک سطر از کدهای اصلی برنامه خود را تغییر دهید.
تنها نکته جدید آن این متد است:
private Configuration getConfig()
{
var configure = new Configuration();
configure.SessionFactoryName("BuildIt");
configure.DataBaseIntegration(db =>
{
db.ConnectionProvider<DriverConnectionProvider>();
db.Dialect<MsSqlCe40Dialect>();
db.Driver<SqlServerCeDriver>();
db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
db.IsolationLevel = IsolationLevel.ReadCommitted;
db.ConnectionString = ConnectionString;
db.Timeout = 10;
//for testing ...
db.LogFormattedSql = true;
db.LogSqlInConsole = true;
});
return configure;
}
که در آن نحوه تعریف MsSqlCe40Dialect و SqlServerCeDriver مشخص شده است.
نکته حاشیهای!
در این مثال primary key از نوع identity تعریف شده و بدون مشکل کار کرد. همین را اگر با EF تست کنید، این خطا را دریافت میکنید: «Server-generated keys and server-generated values are not supported by SQL Server Compact». بله، EF نمیتواند با primary key از نوع identity حین کار با SQL-CE کار کند. برای رفع آن توصیه شده است که از Guid استفاده کنید!
نکته تکمیلی:
استفاده از Dialect سفارشی در NHibernate
نکته پایانی!
و در پایان باید اشاره کرد که SQL-CE یک بانک اطلاعاتی نوشته شده با دات نت نیست (با CPP نوشته شده است و نصب آن هم نیاز به ران تایم به روز VC را دارد). به این معنا که جهت سیستمهای 64 بیتی و 32 بیتی باید نسخه مناسب آنرا توزیع کنید. یا اینکه Target platform پروژه جاری دات نت خود را بر روی X86 قرار دهید (نه بر روی Any CPU پیش فرض) و در این حالت تنها یک نسخه X86 بانک اطلاعاتی SQL-CE و همچنین برنامه خود را برای تمام سیستمها توزیع کنید.