تعداد درخواست بر اساس هر Urls
بله. احتمالا مشکلی شبیه به این مطلب را دارید: «حذف پردازش درخواستهای فایلهای استاتیک در متد Application_AuthenticateRequest»
ساختار گزارش خاص
الف) استفاده از قالب سفارشی سلولها: (^)
ب) استفاده از قالبهای open office و ترکیب آن با PdfReport: (^)
تولید پویای کد در زمان اجرا توسط Reflection.Emit
Reflection چیست؟
Reflection چیزهایی هستند که با نگاه در یک آینه قابل مشاهدهاند. در این حالت شخص میتواند قسمتهای مختلف ظاهر خود را برانداز کرده یا قسمتی را تغییر دهید. اما این مساله چه ربطی به دنیای دات نت دارد؟ در دات نت با استفاده از Reflection میتوان به اطلاعات اشیاء یک برنامهی در حال اجرا دسترسی یافت. برای مثال نام کلاسهای مختلف آن چیست یا درون کلاسی خاص، چه متدهایی قرار دارند. همچنین با استفاده از Reflection میتوان رفتارهای جدیدی را نیز به کلاسها و اشیاء افزود یا آنها را تغییر داد.
همواره عنوان میشود که از Reflection به دلیل سربار بالای آن پرهیز کنید و تنها از آن به عنوان آخرین راه حل موجود استفاده نمائید و این دقیقا موردی است که در مباحث جاری بیشتر از آن استفاده خواهد شد: ساخت اشیاء جدید در زمان اجرا به کمک کدهای IL و امکانات Reflection
نگاهی به امکانات متداول Reflection
در مثال بعد، نگاهی خواهیم داشت به امکانات متداول Reflection، مانند دسترسی به متدها و خواص یک کلاس و تعویض مقدار یا فراخوانی آنها:
using System; namespace FastReflectionTests { class Person { public string Name { set; get; } public string Speak() { return string.Format("Hello, my name is {0}.", this.Name); } } class Program { static void Main(string[] args) { //روش متداول var vahid = new Person { Name = "Vahid" }; Console.WriteLine(vahid.Speak()); var type = vahid.GetType(); //نمایش متدهای یک کلاس var methods = type.GetMethods(); foreach (var method in methods) { Console.WriteLine(method.Name); } //تغییر مقدار یک خاصیت var setNameMethod = type.GetMethod("set_Name"); setNameMethod.Invoke(obj: vahid, parameters: new[] { "Ali" }); //فراخوانی یک متد var speakMethod = type.GetMethod("Speak"); var result = speakMethod.Invoke(obj: vahid, parameters: null); Console.WriteLine(result); } } }
Hello, my name is Vahid. set_Name get_Name Speak ToString Equals GetHashCode GetType Hello, my name is Ali.
در اینجا یک کلاس شخص با خاصیت نام او تعریف شده است؛ به همراه متدی که رشتهای را نمایش خواهد داد.
در متد Main برنامه، ابتدا یک وهله جدید از این شخص ایجاد شده و سپس به روش متداول، متد Speak آن فراخوانی گردیده است. در ادامه کار از امکانات Reflection برای انجام همین امور کمک گرفته شده است.
کار با دریافت نوع یک وهله شروع میشود. برای نمونه در اینجا توسط vahid.GetType به نوع وهله ساخته شده دسترسی یافتهایم. سپس با داشتن این type، میتوان به کلیه امکانات Reflection دسترسی یافت. برای مثال توسط GetMethods، لیست کلیه متدهای موجود در کلاس شخص بازگشت داده میشود.
اگر به خروجی فوق دقت کنید، پس از سطر اول، 7 سطر بعدی نمایانگر متدهای موجود در کلاس شخص هستند. شاید عنوان کنید که این کلاس به نظر یک متد بیشتر ندارد. اما در دات نت اشیاء از شیء Object مشتق میشوند و چهار متد ToString، Equals، GetHashCode و GetType متعلق به آن هستند. همچنین خواص تعریف شده نیز در اصل به دو متد set و get به صورت خودکار در کدهای IL برنامه ترجمه خواهند شد. از همین متد set_Name در ادامه برای مقدار دهی خاصیت نام وهله ایجاد شده استفاده شده است.
همانطور که ملاحظه میکنید برای فراخوانی یک وهله از طریق Reflection، ابتدا توسط متد type.GetMethod میتوان به آن دسترسی یافت و سپس با فراخوانی متد Invoke، میتوان متد مدنظر را بر روی یک شیء مهیا با پارامترهایی که ذکر میکنیم، فراخوانی کرد. اگر این متد پارامتری ندارد، آنرا نال قرار خواهیم داد.
تا اینجا مقدمهای را ملاحظه نمودید که بیشتر جهت تکمیل بحث، حفظ روابط منطقی قسمتهای مختلف آن و یادآوری مباحث مرتبط با Reflection ذکر شدند.
ایجاد اشیاء در زمان اجرای برنامه
یکی از کلاسهای مهم Reflection که در منابع مختلف کمتر به آن پرداخته شده است، کلاس DynamicMethod آن است که از آن میتوان برای ایجاد اشیاء و یا متدهایی پویا در زمان اجرا استفاده کرد. این کلاس قرار گرفته در فضای نام System.Reflection.Emit، دارای یک ILGenerator است که میتوان به آن OpCodeهایی را اضافه کرد. زمانیکه کار ایجاد این متدپویا به پایان رسید، با استفاده از Delegates امکان دسترسی و اجرای این متد پویا وجود خواهد داشت.
یک مثال کامل را در این زمینه در ادامه ملاحظه مینمائید:
using System; using System.Reflection.Emit; namespace FastReflectionTests { class Program { static double Divider(int a, int b) { return a / b; } delegate double DividerDelegate(int a, int b); static void Main(string[] args) { //روش متداول Console.WriteLine(Divider(10, 2)); //تعریف امضای متد var myMethod = new DynamicMethod( name: "DividerMethod", returnType: typeof(double), parameterTypes: new[] { typeof(int), typeof(int) }, m: typeof(Program).Module); //تعریف بدنه متد var il = myMethod.GetILGenerator(); il.Emit(opcode: OpCodes.Ldarg_0); //بارگذاری پارامتر اول بر روی پشته ارزیابی il.Emit(opcode: OpCodes.Ldarg_1); //بارگذاری پارامتر دوم بر روی پشته ارزیابی il.Emit(opcode: OpCodes.Div); // دو پارامتر از پشته ارزیابی دریافت و تقسیم خواهند شد il.Emit(opcode: OpCodes.Ret); // دریافت نتیجه نهایی از پشته ارزیابی و بازگشت آن //فراخوانی متد پویا //روش اول var result = myMethod.Invoke(obj: null, parameters: new object[] { 10, 2 }); Console.WriteLine(result); //روش دوم var method = (DividerDelegate)myMethod.CreateDelegate(delegateType: typeof(DividerDelegate)); Console.WriteLine(method(10, 2)); } } }
در ابتدای این مثال جدید یک متد متداول تقسیم کننده دو عدد را ملاحظه میکنید. در ادامه قصد داریم overload دیگری از این متد را توسط کدهای MSIL در زمان اجرا ایجاد کنیم که دو پارامتر int را قبول میکند.
کار با وهله سازی کلاس DynamicMethod موجود در فضای نام System.Reflection.Emit شروع میشود. در اینجا کار تعریف امضای متد جدید باید صورت گیرد. برای مثال نام آن چیست، نوع خروجی آن کدام است. نوع پارامترهای آن چیست و نهایتا این متدی که قرار است به صورت پویا به برنامه اضافه شود، باید در کجا قرار گیرد. برای اینکار از Module خود کلاس Program برنامه استفاده شده است.
پس از تعریف امضای متد پویا، نوبت به تعریف بدنهی آن میرسد. کار با دریافت یک ILGenerator که میتوان در آن کدهای IL را وارد کرد شروع میشود. مابقی آن تعریف کدهای IL توسط متد Emit است و پیشتر با مقدمات اسمبلی دات نت در قسمتهای قبلی مبحث جاری آشنا شدهایم. ابتدا دو Ldarg فراخوانی شدهاند تا دو پارامتر ورودی متد را دریافت کنند. سپس Div بر روی آنها صورت گرفته و نهایتا نتیجه بازگشت داده شده است.
خوب؛ تا اینجا موفق شدیم اولین متد پویای خود را ایجاد نمائیم. برای اجرا آن حداقل دو روش وجود دارد:
الف) فراخوانی متد Invoke بر روی آن. با توجه به اینکه قرار نیست این متد بر روی وهلهی خاصی اجرا شود، اولین پارامتر آن null وارد شده است و سپس پارامترهای این متد پویا توسط آرگومان دوم متد Invoke وارد شدهاند.
ب) میتوان این عملیات را اندکی شکیلتر کرد. برای اینکار پیش از متد Main برنامه یک delegate به نام DividerDelegate تعریف شده است. سپس با استفاده از متد CreateDelegate، خروجی این متد پویا را تبدیل به یک delegate کردهایم. اینبار فراخوانی متد پویا بسیار شبیه به متدهای معمولی میشود.
یکی از API های کاربردی و جدید در دنیای وب، BroadcastChannel است که امکان ارسال اطلاعات بین windowها ، Tabها و iframeهای مختلف را که در یک دامنه هستند، فراهم میکند. برای
مثال اگر شما در مرورگری در پنجرههای مختلف یک سایت را باز کرده باشید،
با تغییر در یکی از این پنجرهها، قادر خواهید بود سایر پنجرها را هم مطلع کنید تا در
صورت نیاز، مجددا بارگذاری شوند.
چرا از این API استفاده کنیم؟
یکی از
وب سایتهای مورد علاقهی خود را در مرورگر باز کنید. مثلا یوتیوب و لاگین کنید. حالا در
پنجرهی جدیدی، همین وب سایت را مجددا باز کرده و لاگین کنید. حالا در یکی از پنجرهها، از یوتیوب Logout کنید. خب شما حالا در یکی از پنجرهها لاگین هستید و در یکی دیگر Logout کردهاید. حالا
پنجرههای مرورگر شما دارای دو وضعیت متفاوت هستند. Logged-in در برابر Logged-out و این گاهی باعث دردسر خواهد شد.
این وضعیت حتی میتواند باعث خطرهای امنیتی نیز بشود. تصور کنید که کاربری در یک فضای عمومی مثل یک کافی شاپ وارد سایت شما شدهاست و داشبرد مخصوص به خود را باز کردهاست. بنا به دلایلی کاربر قصد ترک محل را کرده و طبیعتا از برنامه شما Logout خواهد کرد . در این حالت اگر این کاربر برنامه شما را در صفحات مختلف مرورگر باز کرده باشد و لاگین نیز کرده باشد، هر کسی که بعد از او قصد استفاده از این کامپیوتر را داشته باشد ، میتواند به اطلاعات کاربر مورد نظر در آن صفحات دسترسی پیدا کند؛ چه این اطلاعات روی صفحه باشد و چه مثلا اطلاعات یک JWT token. چون کاربر فراموش کرده در صفحات دیگر هم Logout کند.
کد نویسی BroadcastChannel API
در نگاه اول، استفاده از این API ممکن است سخت به نظر برسد؛ ولی در واقع خیلی راحت است. برای نمونه قطعه کد زیر را درنظر بگیرید:
<!DOCTYPE html> <body> <!-- The title will change to greet the user --> <h1 id="title">Hey</h1> <input id="name-field" placeholder="Enter Your Name"/> </body> <script> var bc = new BroadcastChannel('gator_channel'); (()=>{ const title = document.getElementById('title'); const nameField = document.getElementById('name-field'); const setTitle = (userName) => { title.innerHTML = 'Hey ' + userName; } bc.onmessage = (messageEvent) => { // If our broadcast message is 'update_title' then get the new title from localStorage if (messageEvent.data === 'update_title') { // localStorage is domain specific so when it changes in one window it changes in the other setTitle(localStorage.getItem('title')); } } // When the page loads check if the title is in our localStorage if (localStorage.getItem('title')) { setTitle(localStorage.getItem('title')); } else { setTitle('please tell us your name'); } nameField.onchange = (e) => { const inputValue = e.target.value; // In the localStorage we set title to the user's input localStorage.setItem('title', inputValue); // Update the title on the current page setTitle(inputValue); // Tell the other pages to update the title bc.postMessage('update_title'); } })() </script>
این کد شامل یک Input باکس و یک title است. وقتی کاربر نام خود را در Input باکس وارد میکند، برنامه آن را در Localstorage با کلیدی به نام userName ذخیره میکند و بعد title صفحه جاری را به سلام + userName تغییر میدهد. مثلا اگر کاربر در Input باکس، عبارت بابک را وارد کند، title صفحه به سلام بابک تغییر داده میشود.
بدون BroadcastChannel، چنانچه کاربر در پنجرههای مختلف مرورگر، برنامه را باز کرده باشد، تغییری در Title آن صفحات داده نخواهد شد؛ مگر اینکه مجددا توسط کاربر بارگذاری شود.
در کد فوق ما یک وهله از BroadcastChannel را به نام gator_channel ایجاد کردهایم و بعد onmessage را مساوی متدی با یک آرگومان جهت دریافت پیام قرار دادهایم. در این متد چک شده که اگر نام پیام، مساوی update_title باشد، متغیر ذخیره شدهی در LocalStorage خوانده شود.
هربار که متد postMessage ، از BroadcastChannel را فراخوانی میکنیم، این متد، باعث اجرای متد onmessage در سایر پنجرهها میشود. پس اگر در پنجرهی جاری در Input باکس، کلمه فرهاد را بنویسیم، متد bc.postMessage('update_title') در پنجره جاری اجرا شده و باعث اجرای متد onmessage در سایر پنجرههایی که سایتمان در آن باز است میشود.
این API در چه حالتهایی کار میکند
برخلاف سایر APIها مثل window.postMessage، شما لازم نیست چیزی در مورد اینکه چند تا صفحه از سایتتان بر روی مرورگر جاری باز شده را بدانید. (توجه کنید که روی عبارت «مرورگر جاری» تاکید میکنم. چون اگر برنامه روی دو مرورگر مثلا Chrome و Firefox به صورت همزمان باز باشد، این API فقط روی صفحات باز مرورگر جاری فراخوانی خواهد شد و نه مرورگر دوم؛ توضیحات بیشتر در ادامه داده شده است)
BroadcastChannel فقط روی مرورگر جاری و صفحاتی از یک دامنه، اجرا خواهد شد. این به این معنا است که شما میتوانید پیامهایتان را از دامنه مثلا : https://alligator.io به دامنه https://alligator.io/js/broadcast_channels ارسال کنید. تنها نکتهای که لازم است تا رعایت شود این است که آبجکت BroadcastChannel در هر دو صفحه، از یک نام برای channel استفاده کرده باشند:
const bc = new BroadcastChannel('alligator_channel'); bc.onmessage = (eventMessage) => { // do something different on each page }
در حالتهای زیر این API کار نخواهد کرد:
هاستهای متفاوت:
https://alligator.io
https://www.aligator.io
پورتهای متفاوت:
https://alligator.io
https://alligator.io :8080
پروتکلهای متفاوت:
https://alligator.io
http://alligator.io
و یا برای مثال اگر مثلا در مرورگر Chrome یکی از صفحات به صورت Incognito باز شده باشد.
سازگاری این API با مرورگرهای مختلف
با توجه به اطلاعات سایت caniuse.com، این API در 75.6% مرورگرها پشتیبانی میشود. ولی مرورگرهای Safari و Internet Explorer از این API پشتیبانی نمیکنند. همچنین امکان استفاده از این API توسط کتابخانه sysend.js نیز فراهم شدهاست.
چه نوع پیامهایی را میتوانید به کمک این API ارسال کنید
- تمامی تایپها (Boolean,Null, Undefined,Number,BigInt, String) به غیر از symbol
- آبجکتهای Boolean و String
- Dates
- Regular Expressions
- Blobs
- Files, File Lists
- Array Buffer, ArrayBufferViews
- ImageBitmaps, ImageDates
- Arrays,Objects,Maps and Sets
قطعه کد زیر، بجای string، یک object را ارسال میکند:
bc.onmessage = (messageEvent) => { const data = messageEvent.data // If our broadcast message is 'update_title' then get the new title from localStorage switch (data.type) { case 'update_title': if (data.title){ setTitle(data.title); } else setTitle(localStorage.getItem('title')); break default: console.log('we received a message') } }; // ... Skipping Code bc.postMessage({type: 'update_title', title: inputValue});
چه کارهایی را میتوانید به کمک این API انجام دهید
چیزهای زیادی را میتوان مجسم کرد. محتملترین گزینه، به اشتراک گذاری state جاری برنامه است. برای مثال اگه از کتابخانههای flux یا redux برای مدیریت state برنامه استفاده میکنید، به کمک این API میتوانید state جاری را در تمامی صفحات باز برنامه، بروز رسانی کنید. حتی میتوانید به چیزی شبیه به machine state فکر کنید.
یا مثلا آخرین وضعیت سبد خرید کالای مشتری و یا موجودی کالاها، در یک سایت خرید آنلاین. همچنین به اشتراک گذاری فایلهای حجیم مثل عکس و غیره جهت جلوگیری از دانلود مجدد آنها در سایر صفحات.
به کمک دستور ()bc.close در هر زمانی میتوانید channel باز شده را ببندید و مجددا بسته به وضعیت برنامه، آن را باز کنید.
var blogs = from blog in Blogs where blog.Name.Contains("Development") select blog;
اما اکنون چطور؟
var blogs = from blog in Blogs where blog.Name.ComputeHash() == 0 select blog;
یک مثال: بررسی تاثیر ارزیابیهای سمت کلاینت در EF Core
فرض کنید ساختار جدول بلاگها به صورت زیر است:
public class Blog { public int BlogId { get; set; } public string Url { get; set; } }
public static class StringExtensions { public static int ComputeHash(this string str) { var hash = 0; foreach (var ch in str) { hash += (int)ch; } return hash; } }
using (var context = new BloggingContext()) { var blogs = context.Blogs .Where(blog => blog.Url.ComputeHash() >= 10) .ToList(); Console.WriteLine(blogs.First().Url); }
SELECT [blog].[BlogId], [blog].[Url] FROM [Blogs] AS [blog]
الف) مفسر LINQ در EF Core، شروع به ارزیابی کوئری نوشته شده میکند و هرجائیکه متدی را یافت و از درک آن عاجز بود (معادل SQL ایی را برای آن نیافت)، آنرا از کوئری حذف میکند.
ب) کوئری SQL نهایی بدون متد ComputeHash بر روی بانک اطلاعاتی اجرا شده و نتیجه به سمت کلاینت بازگشت داده میشود. به همین جهت است که در خروجی SQL فوق خبری از متد ComputeHash نیست.
ج) اکنون که EF Core اطلاعات لازم را از سمت سرور دریافت کردهاست، متد ComputeHash را در سمت کلاینت بر روی این نتیجهی دریافتی اعمال میکند. یعنی مرحلهی آخر همان LINQ to Objects متداول خواهد بود.
به این ترتیب است که EF Core قابلیت اجرای هر نوع متدی را که معادل SQL ایی برای آن وجود ندارد، خواهد یافت.
چگونه متوجه شویم که ارزیابی سمت کلاینت رخ دادهاست؟
EF Core این قابلیت را دارد تا گزارش کاملی را از ارزیابیهای سمت کلاینت صورت گرفته ارائه دهد. هرچند در مثال فوق متد الحاقی ComputeHash بسیار واضح است، اما برای نمونه متد string.Join نیز معادل SQL ایی ندارد:
var idUrls = context.Blogs .Select(b => new { IdUrlString = string.Join(", ", b.BlogId, b.Url), }).ToList();
public class BloggingContext : DbContext { public BloggingContext() { } public BloggingContext(DbContextOptions options) : base(options) { } public DbSet<Blog> Blogs { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Demo.ClientSideEvaluation;Trusted_Connection=True;"); optionsBuilder.ConfigureWarnings(warnings => { warnings.Log(CoreEventId.IncludeIgnoredWarning); warnings.Log(RelationalEventId.QueryClientEvaluationWarning); }); } } }
warn: Microsoft.EntityFrameworkCore.Query[200500] The LINQ expression 'where ([blog].Url.ComputeHash() >= 10)' could not be translated and will be evaluated locally.
اگر میخواهید ارزیابی سمت کلاینت را ممنوع کنید، در تنظیمات فوق warnings.Log را به warnings.Throw تغییر دهید. این مورد سبب خواهد شد تا اگر برنامه به این نوع ارزیابیها رسید، با یک استثناء متوقف شود (شبیه به حالت EF 6.x).
تاثیر ارزیابیهای سمت کلاینت بر روی کارآیی برنامه
هرچند قابلیت ارزیابیهای سمت کلاینت بسیار مفید است اما باید دقت داشت:
الف) در این حالت چون ابتدا متدهایی که قابلیت ارزیابی در سمت سرور را دارا نیستند، حذف خواهند شد، ممکن است تمام رکوردها به سمت کلاینت بازگشت داده شده و سپس فیلترینگ نهایی در سمت کلاینت صورت گیرد. مانند مثال محاسبهی hash که در SQL تولیدی آن، خبری از قسمت where نیست و این شرط در انتهای کار، در سمت کلاینت و به صورت LINQ to Objects اعمال میشود.
ب) این قابلیت ممکن است برنامه نویسها را از تفکر در مورد یافتن روشهای محاسباتی سمت سرور دور کند. برای مثال هر چند مثال string.Join نوشته شده در سمت کلاینت محاسبه خواهد شد و این کوئری بدون مشکل اجرا میشود، اما اگر آنرا به صورت ذیل جایگزین کنیم:
var idUrls2 = context.Blogs .Select(b => new { IdUrlString = b.BlogId + "," + b.Url }).ToList();
SELECT (CAST([b].[BlogId] AS nvarchar(max)) + N',') + [b].[Url] AS [IdUrlString] FROM [Blogs] AS [b]
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: ClientSideEvaluation.zip
در این آموزش هدف ما ایجاد برنامهای بر اساس TodoMVC است که میتواند خودش را با یک دیتابیس آنلاین همگام سازی کند.
مطمئن باشید بیشتر از 10 دقیقه وقت شمارا نخواهد گرفت !
نصب PouchDB
فایل index.html را باز کنید و فایلهای PouchDB را به آن اضافه کنید :
<script src="//cdn.jsdelivr.net/pouchdb/2.2.0/pouchdb.min.js"></script> <script src="js/base.js"></script> <script src="js/app.js"></script>
حالا PouchDB نصب شده و آماده به کار است . ( در نسخه نهایی بهتر است از فایلهای local استفاده کنید )
ایجاد بانک اطلاعاتی
بقیه کارها باید در فایل app.js انجام شود . برای شروع باید بانک اطلاعاتی جدیدی را برای درج اطلاعات خودمان ایجاد کنیم . برای ایجاد بانک اطلاعاتی خیلی ساده یک instance جدید از آبجکت PouchDB میسازیم .
var db = new PouchDB('todos'); var remoteCouch = false;
نیازی نیست که برای بانک خود، نما (Schema) ایجاد کنید! بعد از اینکه اسم را مشخص کنید، بانک آماده به کار است.
ثبت اطلاعات در بانک اطلاعاتی
اولین کاری که باید انجام دهیم این است که یک متد را ایجاد کنیم و توسط آن اطلاعات خودمان را در بانک اطلاعاتی ذخیره کنیم. نام متد را addTodo انتخاب میکنیم و کارش این است که وقتی کاربر کلید Enter را فشار داد، اطلاعات را داخل بانک اطلاعاتی ذخیره کند. متد مورد نظر به این صورت هست:
function addTodo(text) { var todo = { _id: new Date().toISOString(), title: text, completed: false }; db.put(todo, function callback(err, result) { if (!err) { console.log('Successfully posted a todo!'); } }); }
در PouchDB هر پرونده نیاز دارد تا یک فیلد unique با نام _id داشته باشد. اگر دادهای بخواهد در بانک اطلاعاتی ثبت شود و دارای _id مشابه باشد، با آن به صورت یک update رفتار خواهد شد. در اینجا ما از تاریخ با عنوان id استفاده کردیم که در این مورد خاص میباشد. شما میتواند از db.post() نیز استفاده کنید؛ اگر یک id را به صورت random میخواهید. تنها چیزی که اجباری است در هنگام ساختن یک پرونده، همین _id است و بقیه موارد کاملا اختیاری هستند.
نمایش اطلاعات
در اینجا ما از یک function کمکی به نام redrawTodosUI استفاده خواهیم کرد که وظیفهاش این است تا یک array را دریافت کرده و آن را هر طور که مشخص کردید نمایش دهد. البته آن را به سلیقه خودتان میتوانید آماده کنید.
تنها کاری که باید انجام دهیم این است که اطلاعات را از بانک اطلاعاتی استخراج کرده و به function مورد نظر پاس دهیم.
در اینجا میتوانیم به سادگی از db.allDocs برای خواندن اطلاعات از بانک اطلاعاتی استفاده کنیم.
خاصیت include_docs به PouchDB این دستور را میدهد که ما درخواست دریافت همه اطلاعات داخل پروندهها را داریم و descending هم نحوه مرتب سازی را که بر اساس _id هست، مشخص میکند تا بتوانیم جدیدترین اطلاعات را اول دریافت کنیم .
function showTodos() { db.allDocs({include_docs: true, descending: true}, function(err, doc) { redrawTodosUI(doc.rows); }); }
به روزرسانی خودکار
هر بار که ما اطلاعات جدیدی را در بانک اطلاعاتی وارد میکنیم، نیازمند این هستیم تا اطلاعات جدید را به صورت خودکار دریافت و نمایش دهیم. برای این منظور میتوانیم به روش زیر عمل کنیم :
var remoteCouch = false; db.info(function(err, info) { db.changes({ since: info.update_seq, live: true }).on('change', showTodos); });
حالا هر بار که اطلاعات جدیدی در بانک اطلاعات ثبت شود، به صورت خودکار نمایش داده خواهد شد. خاصیت live مشخص میکند که این کار میتواند بی نهایت بار انجام شود.
ویرایش اطلاعات
وقتی که کاربر یک آیتم Todo را کامل میکند، یک چک باکس را علامت میزند و اعلام میکند که این کار انجام شده است.
function checkboxChanged(todo, event) { todo.completed = event.target.checked; db.put(todo); }
این بخش شبیه به قسمت ثبت اطلاعات است، با این تفاوت که باید شامل یک فیلد _rev ( اضافه بر _id ) نیز باشد. در غیر اینصورت تغییرات ثبت نخواهد شد. این کار برای این است که اشتباهی، اطلاعاتی در بانک ثبت نشود.
حذف اطلاعات
برای حذف اطلاعات باید از متد db.remove به همراه آبجکت مورد نظر استفاده کنید .
function deleteButtonPressed(todo) { db.remove(todo); }
مانند بخش ویرایش نیز باید در اینجا هم _id و هم _rev مشخص شود.
باید توجه داشته باشید در اینجا همان آبجکتی را که از بانک فراخوانی کردهایم، به این متد پاس میدهیم.
البته شما میتونید آبجکت خودتان را نیز ایجاد کنید {_id: todo._id, _rev: todo._rev} اما خوب همان روش قبلی عاقلانهتر است و احتمال خطای کمتری دارد .
نصب CouchDB
حالا میخواهیم همگام سازی را اجرا کنیم و برای این کار نیاز هست یا CouchDB را به صورت Local نصب کنیم یا از سرویسهای آنلاین مثل IrisCouch استفاده کنید .
فعال سازی CORS
برای اینکه به صورت مستقیم با CouchDB در ارتباط باشید باید مطمئن شوید که CORS فعال هست.
در اینجا فقط نام کاربری و رمز ورود را مشخص کنید. به صورت پیش فرض CouchDB به صورت Admin Party نصب میشود و نیازی به User و password ندارد. مگر اینکه برایش مشخص کنید.
همچنین شما باید myname.iriscouch.com را با سرور خودتان جایگزین کنید که اگر به صورت local کار میکنید 127.0.0.1:5984 است.
$ export HOST=http://username:password@myname.iriscouch.com $ curl -X PUT $HOST/_config/httpd/enable_cors -d '"true"' $ curl -X PUT $HOST/_config/cors/origins -d '"*"' $ curl -X PUT $HOST/_config/cors/credentials -d '"true"' $ curl -X PUT $HOST/_config/cors/methods -d '"GET, PUT, POST, HEAD, DELETE"' $ curl -X PUT $HOST/_config/cors/headers -d \ '"accept, authorization, content-type, origin"'
راه اندازی ارتباط ساده دو طرفه
به فایل app.js برگردید . در اینجا باید آدرس بانک اطلاعاتی آنلاین را مشخص کنیم.
var db = new PouchDB('todos'); var remoteCouch = 'http://user:pass@mname.iriscouch.com/todos';
فراموش نکنید که نام کاربری و رمز ورود را بسته به نیاز خود تغییر دهید.
حالا میتونیم function که وظیفه همگام سازی اطلاعات را به عهده دارد بنویسیم :
function sync() { syncDom.setAttribute('data-sync-state', 'syncing'); var opts = {live: true}; db.replicate.to(remoteCouch, opts, syncError); db.replicate.from(remoteCouch, opts, syncError); }
db.replicate() به PouchDB میگوید که که همه اطلاعات را "به" یا "از" remoteCouch انتقال دهد.
ما دوبار این درخواست را دادیم. در بار اول اطلاعات به داخل سرور ریموت منتقل میشود و در بار دوم اطلاعات local جایگزین میشوند.
یک callback هم وقتی که این کار به پایان برسد اجرا خواهد شد .
میتوانید برنامه خود را در دو مرورگر مختلف اجرا کنید تا نتیجه کار را ببینید.
تبریک میگوییم !
شما توانستید اولین برنامه خود را توسط PouchDB ایجاد کنید. البته این یک برنامه ساده بود و در دنیای واقعی نیاز هست تا خیلی کارهای بیشتری را انجام دهید. اما باز هم اصول اولیه کار را یاد گرفتید و ادامه راه را میتوانید تنهایی ادامه دهید .
*در دنیای NoSql پیاده سازیهای متفاوتی از دیتابیسها انجام شده است و هر دیتابیس، ویژگیها و مزایا و معایب خاص خودش را دارد. باید قبول کرد که همیشه و همه جا نمیتوان از یک پایگاه داده NoSql مشخص استفاده نماییم (دلایلی نظیر محدودیتهای License، هزینه پیاده سازی و...). در نتیجه بررسی یک دیتابیس دیگر با شرایط و توانمندیهای خاص آن خالی از سود نیست.
» این دیتاییس در گروه Graph databasesها قرار دارد و از زبان SPARQL (بخوانید Sparkle) برای کوئری گرفتن و کار با دادهها بهره میبرد؛
» متن باز و رایگان است
» پشتیبانی از دات نت چهار به بعد؛
» قابل استفاده در Mobile Application - Windows phone 7 , 8؛
» بدون شما (Schema Less) و با قابیلت ذخیره سازی triple و به فرمت RDF
» پشتیبانی از Linq و OData؛
» پشتیبانی از تراکنشها ؛
» پیاده سازی مدل برنامه به صورت Code First؛
» سرعت بالا جهت ذخیره سازی و لود اطلاعات؛
» نیاز به پیکربندیهای خاص جهت پیاده سازی ندارد؛
» ارائه افزونه رایگان Polaris جهت کوئری گفتن و نمایش Visual داده ها.
و غیره که در ادامه با آنها آشنا خواهید شد.
در B*Db دو روش برای ذخیره سازی اطلاعات وجود دارد:
» Append Only : در این روش تمامی تغییرات (عملیات نوشتن) در انتهای فایل index اضافه خواهد شد. این روش مزایای زیر را به دنبال خواهد داشت:
- عملیات نوشتن هیچگاه عملیات خواندن اطلاعات را block نمیکند. در
نتیجه هر تعداد عملیات خواندن فایل (منظور اجرای کوئریهای SPQRL است) میتواند به صورت موازی بر روی Storeها اجرا شود.
- به دلیل اینکه عمل ویرایش واقعی هیچ گاه انجام نمیشود (دادهها فقط اضافه خواهند شد) همیشه میتوانید وضعیت Store را به حالتهای قبلی بازگردانید.
- عملیات نوشتن اطلاعات بسیار سریع خواهد بود.
نکته ای که باید به آن دقت داشت این است که فقط در هنگام ساخت Storeها میتوانید نوع ذخیره سازی آن را تعیین نمایید، بعد از ساخت Store امکان سوئیچ بین حالات امکان پذیر نیست.
همان طور که پیشتر گفته شد B*Db برای ذخیره سازی اطلاعات از سند RDF بهره میبرد. البته با RDF Syntaxهای متفاوت :
هم چنین در B*Db چهار روش برای دست یابی با دادهها (پیاده سازی عملیات CRUD) وجود دارد از قبیل:
» B*Db EntityFramewok
» Data Object Layer
» RDF Client Api
» Dynamic API
که هر کدام در طی پستهای متفاوت بررسی خواهد شد.
بررسی یک مثال با روش B*Db EntityFramework
برای شروع ابتدا یک پروژه جدید از نوع Console Application ایجاد کنید. سپس از طریق Nuget اقدام به نصب Package زیر نمایید:
pm> install-Package BirghtStarDb
PM> Install-Package BirghtStarDbLibs
بعد از نصب پکیجهای بالا یک فایل Text Template با نام MyEntityContext.tt نیز به پروژه افزوده خواهد شد. این فایل جهت تولید خودکار مدلهای برنامه استفاده میشود. اما برای این کار لازم است به ازای هر مدل ابتدا یک اینترفیس ایجاد نمایید. برای مثال:
[Entity] public interface IBook { public int Code { get; set; } public string Title { get; set; } }
» نیاز به ایجاد یک خاصیت به عنوان Id وجود ندارد. به صورت پیش فرض خاصیت Id با نوع string برای هر مدل پیاده سازی میشود. اما اگر قصد دارید این نام خاصیت را تغییر دهید میتوانید به صورت زیر عمل کنید:
[Entity] public interface IBook { [Identifier] public string MyId { get; } public int Code { get; set; } public string Title { get; set; } }
استفاده از اینترفیس برای ساخت مدل انعطاف پذیری بالایی را در اختیار ما قرار میدهد که بعدا مفصل بحث خواهد شد. برای عملیات درج داده میتوان به صورت زیر عمل کنید:
MyEntityContext context = new MyEntityContext("type=embedded;storesdirectory=c:\brightstar;storename=test"); var book = context.Books.Create(); book.Code = 1; book.Title = "Test"; context.Books.Add(book); context.SaveChanges();
»embedded : در این حالت از طریق آدرس فیزیکی فایل مورد نظر میتوان یک Connection ایجاد کرد.
»rest : یا استفاده از HTTP یا HTTPS میتوان به سرویس B*Db دسترسی داشت.
»dotNetRdf : برای ارتباط با یک Store دیگر با استفاده از اتصال دهندههای DotNetRDf.
»sparql : اتصال به منبع داده ای دیگر با استفاده از پروتکل SPARQL
در هنگام ایجاد اتصال باید نوع مورد نظر را از حتما تعیین نمایید. با استفاده از storedirctory مکان فیزیکی فایل تعیین خواهد شد.
این مطلب در ادامهی "آشنایی با الگوی IOC یا Inversion of Control (واگذاری مسئولیت)" میباشد که هر از چندگاهی یک قسمت جدید و یا کاملتر از آن ارائه خواهد شد.
==============
به صورت خلاصه ترزیق وابستگی و یا dependency injection ، الگویی است جهت تزریق وابستگیهای خارجی یک کلاس به آن، بجای استفاده مستقیم از آنها در درون کلاس.
برای مثال شخصی را در نظر بگیرید که قصد خرید دارد. این شخص میتواند به سادگی با کمک یک خودرو خود را به اولین محل خرید مورد نظر برساند. حال تصور کنید که 7 نفر عضو یک گروه، با هم قصد خرید دارند. خوشبختانه چون تمام خودروها یک اینترفیس مشخصی داشته و کار کردن با آنها تقریبا شبیه به یکدیگر است، حتی اگر از یک ون هم جهت رسیدن به مقصد استفاده شود، امکان استفاده و راندن آن همانند سایر خودروها میباشد و این دقیقا همان مطلبی است که هدف غایی الگوی تزریق وابستگیها است. بجای اینکه همیشه محدود به یک خودرو برای استفاده باشیم، بنابر شرایط، خودروی متناسبی را نیز میتوان مورد استفاده قرار داد.
در دنیای نرم افزار، وابستگی کلاس Driver ، کلاس Car است. اگر موارد ذکر شده را بدون استفاده از تزریق وابستگیها پیاده سازی کنیم به کلاسهای زیر خواهیم رسید:
//Person.cs
namespace DependencyInjectionForDummies
{
class Person
{
public string Name { get; set; }
}
}
//Car.cs
using System;
using System.Collections.Generic;
namespace DependencyInjectionForDummies
{
class Car
{
List<Person> _passengers = new List<Person>();
public void AddPassenger(Person p)
{
_passengers.Add(p);
Console.WriteLine("{0} added!", p.Name);
}
public void Drive()
{
foreach (var passenger in _passengers)
Console.WriteLine("Driving {0} ...!", passenger.Name);
}
}
}
//Driver.cs
using System.Collections.Generic;
namespace DependencyInjectionForDummies
{
class Driver
{
private Car _myCar = new Car();
public void DriveToMarket(IList<Person> passengers)
{
foreach (var passenger in passengers)
_myCar.AddPassenger(passenger);
_myCar.Drive();
}
}
}
//Program.cs
using System.Collections.Generic;
using System;
namespace DependencyInjectionForDummies
{
class Program
{
static void Main(string[] args)
{
new Driver().DriveToMarket(
new List<Person>
{
new Person{ Name="Ali" },
new Person{ Name="Vahid" }
});
Console.WriteLine("Press a key ...");
Console.ReadKey();
}
}
}
توضیحات:
کلاس شخص (Person) جهت تعریف مسافرین، اضافه شده؛ سپس کلاس خودرو (Car) که اشخاص را میتوان به آن اضافه کرده و سپس به مقصد رساند، تعریف گردیده است. همچنین کلاس راننده (Driver) که بر اساس لیست مسافرین، آنها را به خودروی خاص ذکر شده هدایت کرده و سپس آنها را با کمک کلاس خودرو به مقصد میرساند؛ نیز تعریف شده است. در پایان هم یک کلاینت ساده جهت استفاده از این کلاسها ذکر شده است.
همانطور که ملاحظه میکنید کلاس راننده به کلاس خودرو گره خورده است و این راننده همیشه تنها از یک نوع خودروی مشخص میتواند استفاده کند و اگر روزی قرار شد از یک ون کمک گرفته شود، این کلاس باید بازنویسی شود.
خوب! اکنون اگر این کلاسها را بر اساس الگوی تزریق وابستگیها (روش تزریق در سازنده که در قسمت قبل بحث شد) بازنویسی کنیم به کلاسهای زیر خواهیم رسید:
//ICar.cs
using System;
namespace DependencyInjectionForDummies
{
interface ICar
{
void AddPassenger(Person p);
void Drive();
}
}
//Car.cs
using System;
using System.Collections.Generic;
namespace DependencyInjectionForDummies
{
class Car : ICar
{
//همانند قسمت قبل
}
}
//Van.cs
using System;
using System.Collections.Generic;
namespace DependencyInjectionForDummies
{
class Van : ICar
{
List<Person> _passengers = new List<Person>();
public void AddPassenger(Person p)
{
_passengers.Add(p);
Console.WriteLine("{0} added!", p.Name);
}
public void Drive()
{
foreach (var passenger in _passengers)
Console.WriteLine("Driving {0} ...!", passenger.Name);
}
}
}
//Driver.cs
using System.Collections.Generic;
namespace DependencyInjectionForDummies
{
class Driver
{
private ICar _myCar;
public Driver(ICar myCar)
{
_myCar = myCar;
}
public void DriveToMarket(IList<Person> passengers)
{
foreach (var passenger in passengers)
_myCar.AddPassenger(passenger);
_myCar.Drive();
}
}
}
//Program.cs
using System.Collections.Generic;
using System;
namespace DependencyInjectionForDummies
{
class Program
{
static void Main(string[] args)
{
Driver driver = new Driver(new Van());
driver.DriveToMarket(
new List<Person>
{
new Person{ Name="Ali" },
new Person{ Name="Vahid" }
});
Console.WriteLine("Press a key ...");
Console.ReadKey();
}
}
}
توضیحات:
در اینجا یک اینترفیس جدید به نام ICar اضافه شده است و بر اساس آن میتوان خودروهای مختلفی را با نحوهی بکارگیری یکسان اما با جزئیات پیاده سازی متفاوت تعریف کرد. برای مثال در ادامه، یک کلاس ون با پیاده سازی این اینترفیس تشکیل شده است. سپس کلاس رانندهی ما بر اساس ترزیق این اینترفیس در سازندهی آن بازنویسی شده است. اکنون این کلاس دیگر نمیداند که دقیقا چه خودرویی را باید مورد استفاده قرار دهد و از وابستگی مستقیم به نوعی خاص از آنها رها شده است؛ اما میداند که تمام خودروها، اینترفیس مشخص و یکسانی دارند. به تمام آنها میتوان مسافرانی را افزود و سپس به مقصد رساند. در پایان نیز یک راننده جدید بر اساس خودروی ون تعریف شده، سپس یک سری مسافر نیز تعریف گردیده و نهایتا متد DriveToMarket فراخوانی شده است.
به این صورت به یک سری کلاس اصطلاحا loosely coupled رسیدهایم. دیگر رانندهی ما وابستهی به یک خودروی خاص نیست و هر زمانی که لازم بود میتوان خودروی مورد استفادهی او را تغییر داد بدون اینکه کلاس راننده را بازنویسی کنیم.
یکی دیگر از مزایای تزریق وابستگیها ساده سازی unit testing کلاسهای برنامه توسط mocking frameworks است. به این صورت توسط این نوع فریمورکها میتوان رفتار یک خودرو را تقلید کرد بجای اینکه واقعا با تمام ریز جرئیات آنها بخواهیم سروکار داشته باشیم (وابستگیها را به صورت مستقل میتوان آزمایش کرد).
- همچنین از آنجائیکه ما در یک دنیای محض زندگی نمیکنیم، نیاز خواهید داشت از IUnitOfWork گاهی از اوقات در لایه نمایشی هم استفاده کنید تا بتوانید یک تراکنش حاصل از چند موجودیت و چند کلاس لایه سرویس را در پایان کار درخواست رسیده (فقط یک تراکنش به ازای کل تعاملات یک درخواست)، به بانک اطلاعاتی اعمال کنید. بنابراین internal تعریف کردن آن در دنیای واقعی میسر نیست. حتی برای تعریف سیمکشیهای تزریق وابستگیهای اولیهی آن هم نباید internal تعریف شود. همچنین باز هم یک برنامهی واقعی الزاما تمام نیازهای تجاریاش در لایه سرویس قرار نمیگیرد. مثلا نیاز خواهید داشت با میانافزارها، اکشن فیلترها و غیره هم کار کنید که اینها محل قرارگیری استاندارد و مشخصی ندارند و هر جائی قابل تعریف هستند.