ساخت Single Page Application
ASP.NET MVC #5
«به روز رسانی غیرهمزمان قسمتی از صفحه به کمک jQuery در ASP.NET MVC »
+ مباحث کش کردن اطلاعات را هم مدنظر داشته باشید.
ASP.NET MVC #21
نحوه استفاده از افزونه Firebug برای دیباگ برنامههای ASP.NET مبتنی بر jQuery
مروری بر قابلیت جدید ASP.NET FriendlyUrls
بله این روش سربار اضافی داره. روشی که گفتید خیلی بهتر هست.
ولی خوب مسئله اینه که چطور میشه داده نوع DateTime در ASP.Net رو با jQuery خوند؟
با تشکر
هدف از بحث
ارائه راه حلی جهت تزریق یک وهله از واحد کار تشکیل شده (همان شیء سشن در RavenDB) به کلیه کلاسهای لایه سرویس برنامه و همچنین زنده نگه داشتن شیء document store آن در طول عمر برنامه است. ایجاد شیء document store که کار اتصال به بانک اطلاعاتی را مدیریت میکند، بسیار پرهزینه است. به همین جهت این شیء تنها یکبار باید در طول عمر برنامه ایجاد شود.
ابزارها و پیشنیازهای لازم
ابتدا یک برنامه جدید ASP.NET MVC را آغاز کنید. سپس ارجاعات لازم را به کلاینت RavenDB، سرور درون پروسهای آن (RavenDB.Embedded) و همچنین StructureMap با استفاده از نیوگت، اضافه نمائید:
PM> Install-Package RavenDB.Client PM> Install-Package RavenDB.Embedded -Pre PM> Install-Package structuremap
دریافت کدهای کامل این مثال
این مثال، به همراه فایلهای باینری ارجاعات یاد شده، نیست (جهت کاهش حجم 100 مگابایتی آن). برای بازیابی آنها میتوانید به مطلبی در اینباره در سایت مراجعه کنید.
این پروژه از چهار قسمت مطابق شکل زیر تشکیل شده است:
الف) لایه سرویسهای برنامه
using RavenDB25Mvc4Sample.Models; using System.Collections.Generic; namespace RavenDB25Mvc4Sample.Services.Contracts { public interface IUsersService { User AddUser(User user); IList<User> GetUsers(int page, int count = 20); } } using System.Collections.Generic; using System.Linq; using Raven.Client; using RavenDB25Mvc4Sample.Models; using RavenDB25Mvc4Sample.Services.Contracts; namespace RavenDB25Mvc4Sample.Services { public class UsersService : IUsersService { private readonly IDocumentStore _documentStore; private readonly IDocumentSession _documentSession; public UsersService(IDocumentStore documentStore, IDocumentSession documentSession) { _documentStore = documentStore; _documentSession = documentSession; } public User AddUser(User user) { _documentSession.Store(user); return user; } public IList<User> GetUsers(int page, int count = 20) { return _documentSession.Query<User>() .Skip(page * count) .Take(count) .ToList(); } //todo: سایر متدهای مورد نیاز در اینجا } }
هر کلاس لایه سرویس با یک اینترفیس مشخص شده و اعمال آنها از طریق این اینترفیسها در اختیار کنترلرهای برنامه قرار میگیرند.
ب) لایه Infrastructure برنامه
در این لایه کدهای اتصالات IoC Container مورد استفاده قرار میگیرند. کدهایی که به برنامه جاری وابستهاند، اما حالت عمومی و مشترکی ندارند تا در سایر پروژههای مشابه استفاده شوند.
using Raven.Client; using Raven.Client.Embedded; using RavenDB25Mvc4Sample.Services; using RavenDB25Mvc4Sample.Services.Contracts; using StructureMap; namespace RavenDB25Mvc4Sample.Infrastructure { public static class IoCConfig { public static void ApplicationStart() { ObjectFactory.Initialize(x => { // داکیومنت استور سینگلتون تعریف شده چون باید در طول عمر برنامه زنده نگه داشته شود x.ForSingletonOf<IDocumentStore>().Use(() => { return new EmbeddableDocumentStore { DataDirectory = "App_Data" }.Initialize(); }); // سشن در برنامه وب هیبرید تعریف شده تا در طول عمر یک درخواست زنده نگه داشته شود // در برنامههای ویندوزی حالت هیبرید را حذف کنید x.For<IDocumentSession>().HybridHttpOrThreadLocalScoped().Use(context => { return context.GetInstance<IDocumentStore>().OpenSession(); }); // اتصالات لایه سرویس در اینجا x.For<IUsersService>().Use<UsersService>(); // ... }); } public static void ApplicationEndRequest() { ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects(); } } }
IDocumentStore و IDocumentSession، دو اینترفیس تعریف شده در کلاینت RavenDB هستند. اولی کار اتصال به بانک اطلاعاتی را مدیریت خواهد کرد و دومی کار مدیریت الگوی واحد کار را انجام میدهد. IDocumentStore به صورت Singleton تعریف شده است؛ چون باید در طول عمر برنامه زنده نگه داشته شود. اما IDocumentStore در ابتدای هر درخواست رسیده، وهله سازی شده و سپس در پایان هر درخواست در متد ApplicationEndRequest به صورت خودکار Dispose خواهد شد.
اگر به فایل Global.asax.cs پروژه وب برنامه مراجعه کنید، نحوه استفاده از این کلاس را مشاهده خواهید کرد:
using System; using System.Globalization; using System.Web.Mvc; using System.Web.Routing; using RavenDB25Mvc4Sample.Infrastructure; using StructureMap; namespace RavenDB25Mvc4Sample { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { IoCConfig.ApplicationStart(); AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); //Set current Controller factory as StructureMapControllerFactory ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory()); } protected void Application_EndRequest(object sender, EventArgs e) { IoCConfig.ApplicationEndRequest(); } } public class StructureMapControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) throw new InvalidOperationException(string.Format("Page not found: {0}", requestContext.HttpContext.Request.Url.AbsoluteUri.ToString(CultureInfo.InvariantCulture))); return ObjectFactory.GetInstance(controllerType) as Controller; } } }
ج) استفاده از کلاسهای لایه سرویس در کنترلرهای برنامه
using System.Web.Mvc; using Raven.Client; using RavenDB25Mvc4Sample.Models; using RavenDB25Mvc4Sample.Services.Contracts; namespace RavenDB25Mvc4Sample.Controllers { public class HomeController : Controller { private readonly IDocumentSession _documentSession; private readonly IUsersService _usersService; public HomeController(IDocumentSession documentSession, IUsersService usersService) { _documentSession = documentSession; _usersService = usersService; } [HttpGet] public ActionResult Index() { return View(); //نمایش صفحه ثبت } [HttpPost] public ActionResult Index(User user) { if (this.ModelState.IsValid) { _usersService.AddUser(user); _documentSession.SaveChanges(); return RedirectToAction("Index"); } return View(user); } } }
Cookie - قسمت سوم
Cookie - قسمت اول: مقدمه، تاریخچه، معرفی، و شرح کامل
Cookie - قسمت دوم: کوکی در جاوا اسکریپت
نکته مهم: خواندن قسمتهای قبلی این سری (مخصوصا قسمت اول) برای درک بهتر مطالب پیشنهاد میشود.
.
کوکی در ASP.NET - بخش اول
در قسمتهای قبلی مقدمات و مباحث کلی راجع به کوکیها و انواع آن، شرح کامل خواص، نحوه رفتار مرورگرها با انواع کوکیها و درنهایت نحوه کار کردن با کوکیها در سمت کلاینت با استفاده از زبان محبوب جاوا اسکریپت پرداخته شد.
در ادامه این سری مطالب به نحوه برخورد ASP.NET با کوکیها و چگونگی کار کردن با کوکی در سمت سرور آشنا خواهیم شد. در بخش اول این قسمت مباحث ابتدایی و اولیه برای کار با کوکیها در ASP.NET ارائه میشود. در بخش دوم مباحث پیشرفتهتر همچون SubCookieها در ASP.NET و نیز سایر نکات ریز کار با کوکیها در ASP.NET بحث خواهد شد.
.
.
Response و Request در ASP.NET
در قسمت اول این سری به مفاهیم Http Response و Http Request اشاره کوتاهی شده بود. بهصورت خلاصه، درخواستی که از سمت یک کلاینت به یک وب سرور ارسال میشود Request و پاسخی که وب سرور به آن درخواست میدهد Response نامیده میشود.
در ASP.NET، کلیه اطلاعات مرتبط با درخواست رسیده از سمت یک کلاینت در نمونهای منحصر به فرد از کلاس HttpRequest نگهداری میشود. محل اصلی نگهداری این نمونه در پراپرتی Request از نمونه جاری کلاس System.Web.HttpContext (قابل دسترسی ازطریق HttpContext.Current) است. البته کلاس Page هم یک پراپرتی با نام Request دارد که دقیقا از همین پراپرتی کلاس HttpContext استفاده میکند.
همچنین کلیه اطلاعات مرتبط با پاسخ ارسالی وب سرور به سمت کلاینت مربوطه در نمونهای از کلاس HttpResponse ذخیره میشود. محل اصلی نگهداری این نمونه نیز در پراپرتی Response از نمونه جاری کلاس HttpContext است. همانند Request، کلاس Page یک پراپرتی با نام Response برای نگهداری این نمونه دارد که این هم دقیقا از پراپرتی متناظر در کلاس HttpContext استفاده میکند.
.
.
کوکیها در Response و Request
هر دو کلاس HttpResponse و HttpRequest یک پراپرتی با عنوان Cookies (^ و ^) دارند که مخصوص نگهداری کوکیهای مربوطه هستند. این پراپرتی از نوع System.Web.HttpCookieCollection است که یک کالکشن مخصوص برای ذخیره کوکیهاست.
- این پراپرتی (Cookies) در کلاس HttpRequest محل نگهداری کوکیهای ارسالی توسط مرورگر در درخواست متناظر آن است. کوکیهایی که مرورگر با توجه به شرایط جاری و تنظیمات کوکیها اجازه ارسال به سمت سرور را به آنها داده و در درخواست ارسالی ضمیمه کرده است (با استفاده از هدر :Cookie که در قسمت اول شرح داده شد) و ASP.NET پس از پردازش و Parse دادهها، درون این پراپرتی اضافه کرده است.
- این پراپرتی (Cookies) در کلاس HttpResponse محل ذخیره کوکیهای ارسالی از وب سرور به سمت مرورگر کلاینت در پاسخ به درخواست متناظر است. کوکیهای درون این پراپرتی پس از بررسی و استخراج دادههای موردنیاز توسط ASP.NET در هدر پاسخ ارسالی ضمیمه خواهند شد (با استفاده از هدر :Set-Cookie که در قسمت اول توضیح داده شد).
.
.
ایجاد و بهروزرسانی کوکی در ASP.NET
برای ایجاد یک کوکی و ارسال آن به سمت کلاینت همانطور که در بالا نیز اشاره شد، باید از پراپرتی Response.Cookies از کلاس HttpContext استفاده کرد. برای ایجاد یک کوکی روشهای مختلفی وجود دارد.
- در روش اول با استفاده از ویژگی مخصوص ایندکسر کلاس HttpCookieCollection عملیات تولید کوکی انجام میشود. در این روش، ابتدا بررسی میشود که کوکی موردنظر در لیست کوکیهای جاری وجود دارد یا خیر. درصورتیکه با این نام قبلا یک کوکی ثبت شده باشد، مقدار کوکی موجود بروزرسانی خواهد شد. اما اگر این نام وجود نداشته باشد یک کوکی جدید با این نام به لیست افزوده شده و مقدار آن ثبت میشود. مثال:
HttpContext.Current.Response.Cookies["myCookie"].Value = "myCookieValue";
- روش بعدی استفاده از متد Add در کلاس HttpCookieCollection است. در این روش ابتدا یک نمونه از کلاس HttpCookie ایجاد شده و سپس این نمونه به لیست کوکیها اضافه میشود. کد زیر چگونگی استفاده از این روش را نشان میدهد:
var myCookie = new HttpCookie("myCookie", "myCookieValue"); HttpContext.Current.Response.Cookies.Add(myCookie);
- روش دیگر استفاده از متد Set کلاس HttpCookieCollection است. تفاوت این متد با متد Add در این است که متد Set ابتدا سعی میکند عملیات update انجام دهد. یعنی عملیات افزودن تنها وقتیکه نام کوکی موردنظر در لیست کوکیها یافته نشود انجام خواهد شد. برای مثال:
HttpContext.Current.Response.Cookies.Set(new HttpCookie("myCookie", "myCookieValue"));
نکته: باتوجه به توضیحات بالا، متد Set اجازه افزودن دو کوکی با یک نام را نمیدهد. برای اینکار باید از متد Add استفاده کرد. درباره این موضوع در قسمت بعدی بیشتر توضیح داده خواهد شد.
- روش دیگری که برای ایجاد یکی کوکی میتوان از آن استفاده کرد، بکارگیری متد AppnedCookie از کلاس HttpResponse است. در این روش نیز ابتدا باید یک نمونه از کلاس HttpCookie تولید شود. این روش همانند استفاده از متد Add از کلاس HttpCookieCollection است. کد زیر مثالی از این روش را نشان میدهد:
HttpContext.Current.Response.AppendCookie(new HttpCookie("myCookie", "myCookieValue"));
- روش بعدی استفاده از متد SetCookie از کلاس HttpResponse است. فرق این متد با متد AppendCookie در این است که در متد SetCookie ابتدا وجود یک کوکی با نام ارائه شده بررسی میشود و درصورت وجود، مقدار این کوکی بروزرسانی میشود. درصورتیکه قبلا یک کوکی با این نام وجود نداشته باشد، یک کوکی جدید به لیست کوکیها اضافه میشود. این روش همانند استفاده از متد Set از کلاس HttpCookieCollection است. نمونهای از نحوه استفاده از این متد در زیر آورده شده است:
HttpContext.Current.Response.SetCookie(new HttpCookie("myCookie", "myCookieValue"));
نکته: تمامی فرایندهای نشان داده شده در بالا تنها موجب تغییر محتویات کالکشن کوکیها درون HttpContext میشود و تا زمانیکه توسط وب سرور با استفاده از دستور Set-Cookie به سمت مرورگر ارسال نشوند تغییری در کلاینت بوجود نخواهند آورد.
برای آشنایی بیشتر با این روند کد زیر را برای تعریف یک کوکی جدید درنظر بگیرید:
HttpContext.Current.Response.Cookies["myCookie"].Value = "myValue";
همانطور که مشاهده میکنید دستور ایجاد یک کوکی با نام و مقدار وارده در هدر پاسخ تولیدی توسط وب سرور گنجانیده شده است.
نکته: در ASP.NET به صورت پیش فرض از مقدار "/" برای پراپرتی Path استفاده میشود.
.
خواص کوکی در ASP.NET
برای تعیین یا تغییر خواص یک کوکی در ASP.NET باید به نمونه HttpCookie مربوطه دست یافت. سپس با استفاده از پراپرتیهای این کلاس میتوان خواص موردنظر را تعیین کرد. برای مثال:
var myCookie = new HttpCookie(string.Empty); myCookie.Name = "myCookie"; myCookie.Value = "myCookieValue"; myCookie.Domain = "dotnettip.info"; myCookie.Path = "/post"; myCookie.Expires = new DateTime(2015, 1, 1); myCookie.Secure = true; myCookie.HttpOnly = true;
نکته مهم: امکان تغییر خواص یک کوکی به صورت مستقیم در سمت سرور وجود ندارد. درواقع برای اعمال این تغییرات در سمت کلاینت باید به ازای هر کوکی موردنظر یک کوکی جدید با مقادیر جدید ایجاد و به کالکشن کوکیها در Http Response مربوطه اضافه شود تا پس از قرار دادن دستور Set-Cookie متناظر در هدر پاسخ ارسالی به سمت کلاینت و اجرای آن توسط مرورگر، مقادیر خواص مورنظر در سمت کلاینت بروزرسانی شوند. دقت کنید که تمامی نکات مرتبط با هویت یک کوکی که در قسمت اول شرح داده شد در اینجا نیز کاملا صادق است.
روش دیگری نیز برای تعیین برخی خواص کوکیها به صورت کلی در فایل وب کانفیگ وجود دارد. برای اینکار از تگ httpCookies در قسمت system.web استفاده میشود. برای مثال:
<httpCookies domain="www.example.com" httpOnlyCookies="true" requireSSL="true" />
این امکان از ASP.NET 2.0 به بعد اضافه شده است. با استفاده از این تگ، تنظیمات اعمال شده برای تمامی کوکیها درنظر گرفته میشود. البته درصورتیکه تنظیم موردنظر برای کوکی به صورت صریح آورده نشده باشد. برای نمونه به کد زیر دقت کنید:
var myCookie = new HttpCookie("myCookie", "myCookieValue"); myCookie.Domain = "test.com"; HttpContext.Current.Response.Cookies.Add(myCookie); var myCookie2 = new HttpCookie("myCookie2", "myCookieValue2"); myCookie2.HttpOnly = false; myCookie2.Secure = false; HttpContext.Current.Response.Cookies.Add(myCookie2);
با استفاده از تنظیمات تگ httpCookies که در بالا نشان داده شده است، هدر پاسخ تولیدی توسط وب سرور به صورت زیر خواهد بود:
همانطور که میبینید تنها مقادیر پراپرتیهایی که صراحتا برای کوکی آورده نشده است از تنظیمات وب کانفیگ خوانده میشود.
.
.
حذف کوکی در ASP.NET
برای حذف یک کوکی در ASP.NET یک روش کلی وجود دارد که در قسمتهای قبلی نیز شرح داده شده است، یعنی تغییر خاصیت Expires کوکی به تاریخی در گذشته. برای نمونه داریم:
var myCookie = new HttpCookie("myCookie", "myCookieValue"); myCookie.Expires = DateTime.Now.AddYears(-1);
نکته مهم: در کلاس HttpCookieCollection یک متد با نام Remove وجود دارد. از این متد برای حذف یک کوکی از لیست موجود در این کلاس استفاده میشود. دقت کنید که حذف یک کوکی از لیست کوکیها با استفاده از این متد تاثیری بر موجودیت آن کوکی در سمت کلاینت نخواهد گذاشت و تنها روش موجود برای حذف یک کوکی در سمت کلاینت همان تنظیم مقدار خاصیت Expires است.
.
خواندن کوکی در ASP.NET
برای خواندن مقدار یک کوکی ارسالی از مرورگر کلاینت در ASP.NET، باتوجه به توضیحات ابتدای این مطلب، طبیعی است که باید از پراپرتی Request.Cookies در نمونه جاری از کلاس HttpContext استفاده کرد. برای این کار نیز چند روش وجود دارد.
- روش اول استفاده از ایندکسر کلاس HttpCookieCollection است. برای اینکار نیاز به نام یا ایندکس کوکی موردنظر در لیست مربوطه داریم. برای مثال:
var myCookie = HttpContext.Current.Request.Cookies["myCookie"];
- یا این نمونه با استفاده از ایندکسر عددی:
var myCookie = HttpContext.Current.Request.Cookies[0];
- روش دیگری که برای خواند مقدار یک کوکی میتوان بکار برد، استفاده از متد Get از کلاس HttpCookieCollection است. این متد همانند ایندکسر این کلاس نیاز به نام یا ایندکس کوکی موردنظر دارد. برای نمونه:
var myCookie = HttpContext.Current.Request.Cookies.Get("myCookie");
- یا استفاده از ایندکس کوکی:
var myCookie = HttpContext.Current.Request.Cookies.Get(0);
.
.
بحث و نتیجه گیری
تا اینجا با مفاهیم اولیه درباره نحوه برخورد ASP.NET با کوکیها آشنا شدیم. روشهای مختلف ایجاد و یا بهروزرسانی کوکیها نشان داده شد. با تعیین انواع خواص کوکیها آشنا شدیم. نحوه حذف یک کوکی در ASP.NET را دیدیم. روشهای خواندن مقادیر کوکیها را نیز مشاهده کردیم.
باز هم تاکید میکنم که تمامی تغییرات اعمالی در سمت سرور تا زمانیکه بهصورت دستورات Set-Cookie در هدر پاسخ وب سرور قرار نگیرند هیچ کاری در سمت کلاینت انجام نمیدهند.
در قسمت بعدی این سری مطالب به مباحث پیشرفتهتری چون SubCookieها در ASP.NET و هویت منحصر به فرد کوکیها در سمت سرور پرداخته میشود.
.
.
منابع
شروع به کار با RavenDB
- مروری بر مفاهیم مقدماتی NoSQL
- ردهها و انواع مختلف بانکهای اطلاعاتی NoSQL
- چه زمانی بهتر است از بانکهای اطلاعاتی NoSQL استفاده کرد و چه زمانی خیر؟
لطفا یکبار این پیشنیازها را پیش از شروع به کار مطالعه نمائید؛ چون بسیاری از مفاهیم پایهای و اصطلاحات مرسوم دنیای NoSQL در این سه قسمت بررسی شدهاند و از تکرار مجدد آنها در اینجا صرفنظر خواهد شد.
RavenDB چیست؟
RavenDB یک بانک اطلاعاتی سورس باز NoSQL سندگرای تهیه شده با دات نت است. ساختار کلی بانکهای اطلاعاتی NoSQL سندگرا، از لحاظ نحوه ذخیره سازی اطلاعات، با بانکهای اطلاعاتی رابطهای متداول، کاملا متفاوت است. در اینگونه بانکهای اطلاعاتی، رکوردهای اطلاعات، به صورت اشیاء JSON ذخیره میشوند. اشیاء JSON یا JavaScript Object Notation بسیار شبیه به anonymous objects سی شارپ هستند. JSON روشی است که توسط آن JavaScript اشیاء خود را معرفی و ذخیره میکند. به عنوان رقیبی برای XML مطرح است؛ نسبت به XML اندکی فشردهتر بوده و عموما دارای اسکیمای خاصی نیست و در بسیاری از اوقات تفسیر المانهای آن به مصرف کننده واگذار میشود.
در JSON عموما سه نوع المان پایه مشاهده میشوند:
- اشیاء که به صورت {object} تعریف میشوند.
- مقادیر "key":"value" که شبیه به نام خواص و مقادیر آنها در دات نت هستند.
- و آرایهها به صورت [array]
همچنین ترکیبی از این سه عنصر یاد شده نیز همواره میسر است. برای مثال، یک key مشخص میتواند دارای مقداری حاوی یک آرایه یا شیء نیز باشد.
JSON: JavaScript Object Notation document :{ key: "Value", another_key: { name: "embedded object" }, some_date: new Date(), some_number: 12 } C# anonymous object var Document = new { Key= "Value", AnotherKey= new { Name = "embedded object" }, SomeDate = DateTime.Now(), SomeNumber = 12 };
یک نکته مهم
اگر پیشنیازهای بحث را مطالعه کرده باشید، حتما بارها با این جمله که دنیای NoSQL از تراکنشها پشتیبانی نمیکند، برخورد داشتهاید. این مطلب در مورد RavenDB صادق نیست و این بانک اطلاعاتی NoSQL خاص، از تراکنشها پشتیبانی میکند. RavenDB در Document store خود ACID عملکرده و از تراکنشها پشتیبانی میکند. اما تهیه ایندکسهای آن بر مبنای مفهوم عاقبت یک دست شدن عمل میکند.
مجوز استفاده از RavenDB
هرچند مجموعه سرور و کلاینت RavenDB سورس باز هستند، اما این مورد به معنای رایگان بودن آن نیست. مجوز استفاده از RavenDB نوع خاصی به نام AGPL است. به این معنا که یا کل کار مشتق شده خود را باید به صورت رایگان و سورس باز ارائه دهید و یا اینکه مجوز استفاده از آنرا برای کارهای تجاری بسته خود خریداری نمائید. نسخه استاندارد آن نزدیک به هزار دلار است و نسخه سازمانی آن نزدیک به 2800 دلار به ازای هر سرور.
شروع به نوشتن اولین برنامه کار با RavenDB
ابتدا یک پروژه کنسول ساده را آغاز کنید. سپس کلاسهای مدل زیر را به آن اضافه نمائید:
using System.Collections.Generic; namespace RavenDBSample01.Models { public class Question { public string By { set; get; } public string Title { set; get; } public string Content { set; get; } public List<Comment> Comments { set; get; } public List<Answer> Answers { set; get; } public Question() { Comments = new List<Comment>(); Answers = new List<Answer>(); } } } namespace RavenDBSample01.Models { public class Comment { public string By { set; get; } public string Content { set; get; } } } namespace RavenDBSample01.Models { public class Answer { public string By { set; get; } public string Content { set; get; } } }
PM> Install-Package RavenDB.Client PM> Install-Package RavenDB.Server
اکنون به پوشه packages\RavenDB.Server.2.5.2700\tools مراجعه کرده و برنامه Raven.Server.exe را اجرا کنید تا سرور RavenDB شروع به کار کند. این سرور به صورت پیش فرض بر روی پورت 8080 اجرا میشود. از این جهت که در RavenDB نیز همانند سایر Document Stores مطرح، امکان دسترسی به اسناد از طریق REST API و Urlها وجود دارد.
البته لازم به ذکر است که RavenDB در 4 حالت برنامه کنسول (همین سرور فوق)، نصب به عنوان یک سرویس ویندوز NT، هاست شدن در IIS و حالت مدفون شده یا Embedded قابل استفاده است.
خوب؛ همین اندازه برای برپایی اولیه RavenDB کفایت میکند.
using Raven.Client.Document; using RavenDBSample01.Models; namespace RavenDBSample01 { class Program { static void Main(string[] args) { using (var store = new DocumentStore { Url = "http://localhost:8080" }.Initialize()) { using (var session = store.OpenSession()) { session.Store(new Question { By = "users/Vahid", Title = "Raven Intro", Content = "Test...." }); session.SaveChanges(); } } } } }
کار با ایجاد یک DocumentStore که به آدرس سرور اشاره میکند و کار مدیریت اتصالات را برعهده دارد، شروع خواهد شد. اگر نمیخواهید Url را درون کدهای برنامه مقدار دهی کنید، میتوان از فایل کانفیگ برنامه نیز برای این منظور کمک گرفت:
<connectionStrings> <add name="ravenDB" connectionString="Url=http://localhost:8080"/> </connectionStrings>
سپس با ایجاد Session در حقیقت یک Unit of work آغاز میشود که درون آن میتوان انواع و اقسام دستورات را صادر نمود و سپس در پایان کار، با فراخوانی SaveChanges، این اعمال ذخیره میگردند. در RavenDB یک سشن باید طول عمری کوتاه داشته باشد و اگر تعداد عملیاتی که در آن صادر کردهاید، زیاد است با خطای زیر متوقف خواهید شد:
The maximum number of requests (30) allowed for this session has been reached.
در یک برنامه واقعی، ایجاد DocumentStore یکبار در آغاز کار برنامه باید انجام گردد. اما هر سشن یا هر واحد کاری آن، به ازای تراکنشهای مختلفی که باید صورت گیرند، بر روی این DocumentStore، ایجاد شده و سپس بسته خواهند شد. برای مثال در یک برنامه ASP.NET، در فایل Global.asax در زمان آغاز برنامه، کار ایجاد DocumentStore انجام شده و سپس به ازای هر درخواست رسیده، یک سشن RavenDB ایجاد و در پایان درخواست، این سشن آزاد خواهد شد.
برنامه را اجرا کنید، سپس به کنسول سرور RavenDB که پیشتر آنرا اجرا نمودیم مراجعه نمائید تا نمایی از عملیات انجام شده را بتوان مشاهده کرد:
Raven is ready to process requests. Build 2700, Version 2.5.0 / 6dce79a Server started in 14,438 ms Data directory: D:\Prog\RavenDBSample01\packages\RavenDB.Server.2.5.2700\tools\Database\System HostName: <any> Port: 8080, Storage: Esent Server Url: http://localhost:8080/ Available commands: cls, reset, gc, q Request # 1: GET - 514 ms - <system> - 404 - /docs/Raven/Replication/Destinations Request # 2: GET - 763 ms - <system> - 200 - /queries/?&id=Raven%2FHilo%2Fquestions&id=Raven%2FServerPrefixForHilo Request # 3: PUT - 185 ms - <system> - 201 - /docs/Raven/Hilo/questions Request # 4: POST - 103 ms - <system> - 200 - /bulk_docs PUT questions/1
عملیات PUT حتما نیاز به یک Id از پیش مشخص دارد و این Id، پیشتر در سطری که Hilo در آن ذکر شده (یکی از الگوریتمهای محاسبه Id در RavenDB)، محاسبه گردیده است. برای نمونه در اینجا الگوریتم Hilo مقدار "questions/1" را به عنوان Id محاسبه شده بازگشت داده است.
در سطری که عملیات Post به آدرس bulk_docs سرور ارسال گردیده است، کار ارسال یکباره چندین شیء JSON برای کاهش رفت و برگشتها به سرور انجام میشود.
و برای کوئری گرفتن مقدماتی از اطلاعات ثبت شده میتوان نوشت:
using (var session = store.OpenSession()) { var question1 = session.Load<Question>("questions/1"); Console.WriteLine(question1.By); }
نگاهی به بانک اطلاعاتی ایجاد شده
در همین حال که سرور RavenDB در حال اجرا است، مرورگر دلخواه خود را گشوده و سپس آدرس http://localhost:8080 را وارد نمائید. بلافاصله، کنسول مدیریتی تحت وب این بانک اطلاعاتی که با سیلورلایت نوشته شده است، ظاهر خواهد شد:
و اگر بر روی هر سطر اطلاعات دوبار کلیک کنید، به معادل JSON آن نیز خواهید رسید:
اینبار برنامه را به صورت زیر تغییر دهید تا روابط بین کلاسها را نیز پیاده سازی کند:
using System; using Raven.Client.Document; using RavenDBSample01.Models; namespace RavenDBSample01 { class Program { static void Main(string[] args) { using (var store = new DocumentStore { Url = "http://localhost:8080" }.Initialize()) { using (var session = store.OpenSession()) { var question = new Question { By = "users/Vahid", Title = "Raven Intro", Content = "Test...." }; question.Answers.Add(new Answer { By = "users/Farid", Content = "بررسی میشود" }); session.Store(question); session.SaveChanges(); } using (var session = store.OpenSession()) { var question1 = session.Load<Question>("questions/1"); Console.WriteLine(question1.By); } } } } }
اگر برنامه فوق را اجرا کرده و به ساختار اطلاعات ذخیره شده نگاهی بیندازیم به شکل زیر خواهیم رسید:
نکته جالبی که در اینجا وجود دارد، عدم نیاز به join نویسی برای دریافت اطلاعات وابسته به یک شیء است. اگر سؤالی وجود دارد، پاسخهای به آن و یا سایر نظرات، یکجا داخل همان سؤال ذخیره میشوند و به این ترتیب سرعت دسترسی نهایی به اطلاعات بیشتر شده و همچنین قفل گذاری روی سایر اسناد کمتر. این مساله نیز به ذات NoSQL و یا غیر رابطهای RavenDB بر میگردد. در بانکهای اطلاعاتی NoSQL، مفاهیمی مانند کلیدهای خارجی، JOIN بین جداول و امثال آن وجود خارجی ندارند. برای نمونه اگر به کلاسهای مدلهای برنامه دقت کرده باشید، خبری از وجود Id در آنها نیست. RavenDB یک Document store است و نه یک Relation store. در اینجا کل درخت تو در توی خواص یک شیء دریافت و به صورت یک سند ذخیره میشود. به حاصل این نوع عملیات در دنیای بانکهای اطلاعاتی رابطهای، Denormalized data هم گفته میشود.
البته میتوان به کلاسهای تعریف شده خاصیت رشتهای Id را نیز اضافه کرد. در این حالت برای مثال در حالت فراخوانی متد Load، این خاصیت رشتهای، با Id تولید شده توسط RavenDB مانند "questions/1" مقدار دهی میشود. اما از این Id برای تعریف ارجاعات به سؤالات و پاسخهای متناظر استفاده نخواهد شد؛ چون تمام آنها جزو یک سند بوده و داخل آن قرار میگیرند.