نگاهی به SignalR Clients
مصرف کنندگان یک Hub میتوانند انواع و اقسام برنامههای کلاینت مانند jQuery Clients و یا حتی یک برنامه کنسول ساده باشند و همچنین Hubهای دیگر نیز قابلیت استفاده از این امکانات Hubهای موجود را دارند. تیم SignalR امکان استفاده از Hubهای آنرا در برنامههای دات نت 4 به بعد، برنامههای WinRT، ویندوز فون 8، سیلورلایت 5، jQuery و همچنین برنامههای CPP نیز مهیا کردهاند. به علاوه گروههای مختلف نیز با توجه به سورس باز بودن این مجموعه، کلاینتهای iOS Native، iOS via Mono و Android via Mono را نیز به این لیست اضافه کردهاند.
بررسی کلاینتهای jQuery
با توجه به پروتکل مبتنی بر JSON سیگنالآر، استفاده از آن در کتابخانههای جاوا اسکریپتی همانند jQuery نیز به سادگی مهیا است. برای نصب آن نیاز است در کنسول پاور شل نوگت، دستور زیر را صادر کنید:
PM> Install-Package Microsoft.AspNet.SignalR.JS
با استفاده از افزونه SignalR jQuery، به دو طریق میتوان به یک Hub اتصال برقرار کرد:
الف) استفاده از فایل proxy تولید شده آن (این فایل، در زمان اجرای برنامه تولید میشود و یا امکان استفاده از آن به کمک ابزارهای کمکی نیز وجود دارد)
نمونهای از آنرا در قسمت قبل ملاحظه کردید؛ همان فایل تولید شده در مسیر /signalr/hubs برنامه. به نوعی به آن Service contract نیز گفته میشود (ارائه متادیتا و قراردادهای کار با یک سرویس Hub). این فایل همانطور که عنوان شد به صورت پویا در زمان اجرای برنامه ایجاد میشود.
امکان تولید آن توسط برنامه کمکی signalr.exe نیز وجود دارد؛ برای دریافت آن میتوان از طریق NuGet اقدام کرد (بسته Microsoft.AspNet.SignalR.Utils) که نهایتا در پوشه packages قرار خواهد گرفت. نحوه استفاده از آن نیز به صورت زیر است:
Signalr.exe ghp http://localhost/
ب) بدون استفاده از فایل proxy و به کمک روش late binding (انقیاد دیر هنگام)
برای کار با یک Hub از طریق jQuery مراحل ذیل باید طی شوند:
1) ارجاعی به Hub باید مشخص شود.
2) روالهای رخدادگردان تنظیم گردند.
3) اتصال به Hub برقرار گردد.
4) متدی فراخوانی شود.
در اینجا باید دقت داشت که امکانات Hub به صورت خواص
$.connection
$.connection.chatHub
خوب، تا اینجا فرض بر این است که یک پروژه خالی ASP.NET را آغاز و سپس فرمان نصب Microsoft.AspNet.SignalR.JS را نیز همانطور که عنوان شد، صادر کردهاید. در ادامه یک فایل ساده html را به نام chat.htm، به این پروژه جدید اضافه کنید (برای استفاده از کتابخانه جاوا اسکریپتی SignalR الزامی به استفاده از صفحات کامل پروژههای وب نیست).
<!DOCTYPE> <html> <head> <title></title> <script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script> <script src="Scripts/jquery.signalR-1.0.1.min.js" type="text/javascript"></script> <script src="http://localhost:1072/signalr/hubs" type="text/javascript"></script> </head> <body> <div> <input id="txtMsg" type="text" /><input id="send" type="button" value="send msg" /> <ul id="messages"> </ul> </div> <script type="text/javascript"> $(function () { var chat; $.connection.hub.logging = true; //اطلاعات بیشتری را در جاوا اسکریپت کنسول مرورگر لاگ میکند chat = $.connection.chat; //این نام مستعار پیشتر توسط ویژگی نام هاب تنظیم شده است chat.client.hello = function (message) { //متدی که در اینجا تعریف شده دقیقا مطابق نام متد پویایی است که در هاب تعریف شده است //به این ترتیب سرور میتواند کلاینت را فراخوانی کند $("#messages").append("<li>" + message + "</li>"); }; $.connection.hub.start(/*{ transport: 'longPolling' }*/); // فاز اولیه ارتباط را آغاز میکند $("#send").click(function () { // Hub's `SendMessage` should be camel case here chat.server.sendMessage($("#txtMsg").val()); }); }); </script> </body> </html>
توضیحات:
همانطور که ملاحظه میکنید ابتدا ارجاعاتی به jquery و jquery.signalR-1.0.1.min.js اضافه شدهاند. سپس نیاز است مسیر دقیق فایل پروکسی هاب خود را نیز مشخص کنیم. اینکار با تعریف مسیر signalr/hubs انجام شده است.
<script src="http://localhost:1072/signalr/hubs" type="text/javascript"></script>
سپس ارجاعی به هاب تعریف شده، تعریف گردیده است. اگر از قسمت قبل به خاطر داشته باشید، توسط ویژگی HubName، نام chat را برگزیدیم. بنابراین connection.chat ذکر شده دقیقا به این هاب اشاره میکند.
سپس سطر chat.client.hello مقدار دهی شده است. متد hello، متدی dynamic و تعریف شده در سمت هاب برنامه است. به این ترتیب میتوان به پیامهای رسیده از طرف سرور گوش فرا داد. در اینجا، این پیامها، به li ایی با id مساوی messages اضافه میشوند.
سپس توسط فراخوانی متد connection.hub.start، فاز negotiation شروع میشود. در اینجا حتی میتوان نوع transport را نیز صریحا انتخاب کرد که نمونهای از آن را به صورت کامنت شده جهت آشنایی با نحوه تعریف آن مشاهده میکنید. مقادیر قابل استفاده در آن به شرح زیر هستند:
- webSockets - forverFrame - serverSentEvents - longPolling
اکنون به صورت جداگانه یکبار برنامه hub را در مرورگر باز کنید. سپس بر روی فایل chat.htm کلیک راست کرده و گزینه مشاهده آن را در مرورگر نیز انتخاب نمائید (گزینه View in browser منوی کلیک راست).
خوب! پروژه کار نمیکند! برای اینکه مشکلات را بهتر بتوانید مشاهده کنید نیاز است به JavaScript Console مرورگر خود مراجعه نمائید. برای مثال در مرورگر کروم دکمه F12 را فشرده و برگه Console آنرا باز کنید. در اینجا اعلام میکند که فاز negotiation قابل انجام نیست؛ چون مسیر پیش فرضی را که انتخاب کرده است، همین مسیر پروژه دومی است که اضافه کردهایم (کلاینت ما در پروژه دوم قرار دارد و نه در همان پروژه اول هاب).
برای اینکه مسیر دقیق hub را در این حالت مشخص کنیم، سطر زیر را به ابتدای کدهای جاوا اسکریپتی فوق اضافه نمائید:
$.connection.hub.url = 'http://localhost:1072/signalr'; //چون در یک پروژه دیگر قرار داریم
using System; using System.Web; using System.Web.Routing; using Microsoft.AspNet.SignalR; namespace SignalR02 { public class Global : HttpApplication { protected void Application_Start(object sender, EventArgs e) { // Register the default hubs route: ~/signalr RouteTable.Routes.MapHubs(new HubConfiguration { EnableCrossDomain = true }); } } }
SignalR: Auto detected cross domain url. jquery.signalR-1.0.1.min.js:10 SignalR: Negotiating with 'http://localhost:1072/signalr/negotiate'. jquery.signalR-1.0.1.min.js:10 SignalR: SignalR: Initializing long polling connection with server. jquery.signalR-1.0.1.min.js:10 SignalR: Attempting to connect to 'http://localhost:1072/signalr/connect?transport=longPolling&connectionToken…NRh72omzsPkKqhKw2&connectionData=%5B%7B%22name%22%3A%22chat%22%7D%5D&tid=3' using longPolling. jquery.signalR-1.0.1.min.js:10 SignalR: Longpolling connected jquery.signalR-1.0.1.min.js:10
در برگه شبکه، مطابق شکل فوق، امکان آنالیز اطلاعات رد و بدل شده مهیا است. برای مثال در حالتیکه سرور پیام دریافتی را به کلیه کلاینتها ارسال میکند، نام متد و نام هاب و سایر پارامترها در اطلاعات به فرمت JSON آن به خوبی قابل مشاهده هستند.
یک نکته:
اگر از ویندوز 8 (یعنی IIS8) و VS 2012 استفاده میکنید، برای استفاده از حالت Web socket، ابتدا فایل وب کانفیگ برنامه را باز کرده و در قسمت httpRunTime، مقدار ویژگی targetFramework را بر روی 4.5 تنظیم کنید. اینبار اگر مراحل negotiation را بررسی کنید در همان مرحله اول برقراری اتصال، از روش Web socket استفاده گردیده است.
تمرین 1
به پروژه ساده و ابتدایی فوق یک تکست باکس دیگر به نام Room را اضافه کنید؛ به همراه دکمه join. سپس نکات قسمت قبل را در مورد الحاق به یک گروه و سپس ارسال پیام به اعضای گروه را پیاده سازی نمائید. (تمام نکات آن با مطلب فوق پوشش داده شده است و در اینجا باید صرفا فراخوانی متدهای عمومی دیگری در سمت هاب، صورت گیرد)
تمرین 2
در انتهای قسمت دوم به نحوه ارسال پیام از یک هاب به هابی دیگر اشاره شد. این MonitorHub را ایجاد کرده و همچنین یک کلاینت جاوا اسکریپتی را نیز برای آن تهیه کنید تا بتوان اتصال و قطع اتصال کلیه کاربران سیستم را مانیتور و مشاهده کرد.
پیاده سازی کلاینت jQuery بدون استفاده از کلاس Proxy
در مثال قبل، از پروکسی پویای مهیای در آدرس signalr/hubs استفاده کردیم. در اینجا قصد داریم، بدون استفاده از آن نیز کار برپایی کلاینت را بررسی کنیم.
بنابراین یک فایل جدید html را مثلا به نام chat_np.html به پروژه دوم برنامه اضافه کنید. سپس محتویات آنرا به نحو زیر تغییر دهید:
<!DOCTYPE> <html> <head> <title></title> <script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script> <script src="Scripts/jquery.signalR-1.0.1.min.js" type="text/javascript"></script> </head> <body> <div> <input id="txtMsg" type="text" /><input id="send" type="button" value="send msg" /> <ul id="messages"> </ul> </div> <script type="text/javascript"> $(function () { $.connection.hub.logging = true; //اطلاعات بیشتری را در جاوا اسکریپت کنسول مرورگر لاگ میکند var connection = $.hubConnection(); connection.url = 'http://localhost:1072/signalr'; //چون در یک پروژه دیگر قرار داریم var proxy = connection.createHubProxy('chat'); proxy.on('hello', function (message) { //متدی که در اینجا تعریف شده دقیقا مطابق نام متد پویایی است که در هاب تعریف شده است //به این ترتیب سرور میتواند کلاینت را فراخوانی کند $("#messages").append("<li>" + message + "</li>"); }); $("#send").click(function () { // Hub's `SendMessage` should be camel case here proxy.invoke('sendMessage', $("#txtMsg").val()); }); connection.start(); }); </script> </body> </html>
کلاینتهای دات نتی SignalR
تا کنون Solution ما حاوی یک پروژه Hub و یک پروژه وب کلاینت جیکوئری است. به همین Solution، یک پروژه کلاینت کنسول ویندوزی را نیز اضافه کنید.
سپس در خط فرمان پاور شل نوگت دستور زیر را صادر نمائید تا فایلهای مورد نیاز به پروژه کنسول اضافه شوند:
PM> Install-Package Microsoft.AspNet.SignalR.Client
پس از نصب آن اگر به پوشه packages مراجعه کنید، نگارشهای مختلف آنرا مخصوص سیلورلایت، دات نتهای 4 و 4.5، WinRT و ویندوز فون8 نیز میتوانید در پوشه Microsoft.AspNet.SignalR.Client ملاحظه نمائید. البته در ابتدای نصب، انتخاب نگارش مناسب، بر اساس نوع پروژه جاری به صورت خودکار صورت میگیرد.
مدل برنامه نویسی آن نیز بسیار شبیه است به حالت عدم استفاده از پروکسی در حین استفاده از jQuery که در قسمت قبل بررسی گردید و شامل این مراحل است:
1) یک وهله از شیء HubConnection را ایجاد کنید.
2) پروکسی مورد نیاز را جهت اتصال به Hub از طریق متد CreateProxy تهیه کنید.
3) رویدادگردانها را همانند نمونه کدهای جاوا اسکریپتی قسمت قبل، توسط متد On تعریف کنید.
4) به کمک متد Start، اتصال را آغاز نمائید.
5) متدها را به کمک متد Invoke فراخوانی نمائید.
using System; using Microsoft.AspNet.SignalR.Client.Hubs; namespace SignalR02.WinClient { class Program { static void Main(string[] args) { var hubConnection = new HubConnection(url: "http://localhost:1072/signalr"); var chat = hubConnection.CreateHubProxy(hubName: "chat"); chat.On<string>("hello", msg => { Console.WriteLine(msg); }); hubConnection.Start().Wait(); chat.Invoke<string>("sendMessage", "Hello!"); Console.WriteLine("Press a key to terminate the client..."); Console.Read(); } } }
نکته مهم
کلیه فراخوانیهایی که در اینجا ملاحظه میکنید غیرهمزمان هستند.
به همین جهت پس از متد Start، متد Wait ذکر شدهاست تا در این برنامه ساده، پس از برقراری کامل اتصال، کار invoke صورت گیرد و یا زمانیکه callback تعریف شده توسط متد chat.On فراخوانی میشود نیز این فراخوانی غیرهمزمان است و خصوصا اگر نیاز است رابط کاربری برنامه را در این بین به روز کنید باید به نکات به روز رسانی رابط کاربری از طریق یک ترد دیگر دقت داشت.
مسیریابی در AngularJs #بخش اول
Asp.Net Identity #2
اعمال کنترل دسترسی پویا در پروژههای ASP.NET Core با استفاده از AuthorizationPolicyProvider سفارشی
در مطلب «سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاستهای دسترسی پویا» به طور مفصل به قضیه کنترل دسترسی پویا در ASP.NET Core Identity پرداخته شدهاست؛ در این مطلب روش دیگری را بررسی خواهیم کرد.
مشخص میباشد که بدون وابستگی به روش خاصی، خیلی ساده میتوان به شکل زیر عمل کرد:
services.AddAuthorization(options => { options.AddPolicy("View Projects", policy => policy.RequireClaim(CustomClaimTypes.Permission, "projects.view")); });
[Authorize("View Projects")] public IActionResult Index(int siteId) { return View(); }
Using a large range of policies (for different room numbers or ages, for example), so it doesn’t make sense to add each individual authorization policy with an AuthorizationOptions.AddPolicy call.
کار با پیاده سازی واسط IAuthorizationPolicyProvider شروع میشود؛ یا شاید ارث بری از DefaultAuthorizationPolicyProvider رجیستر شدهی در سیستم DI و توسعه آن هم کافی باشد.
public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider { public AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options) : base(options) { } public override Task<AuthorizationPolicy> GetPolicyAsync(string policyName) { if (!policyName.StartsWith(PermissionAuthorizeAttribute.PolicyPrefix, StringComparison.OrdinalIgnoreCase)) { return base.GetPolicyAsync(policyName); } var permissionNames = policyName.Substring(PermissionAuthorizeAttribute.PolicyPrefix.Length).Split(','); var policy = new AuthorizationPolicyBuilder() .RequireClaim(CustomClaimTypes.Permission, permissionNames) .Build(); return Task.FromResult(policy); } }
متد GetPolicyAsync موظف به یافتن و بازگشت یک Policy ثبت شده میباشد؛ با این حال میتوان با بازنویسی آن و با استفاده از وهلهای از AuthorizationPolicyBuilder، فرآیند تعریف سیاست درخواست شده را که احتمالا در تنظیمات آغازین پروژه تعریف نشده و پیشوند مدنظر را نیز دارد، خوکار کرد. در اینجا امکان ترکیب کردن چندین دسترسی را هم خواهیم داشت که برای این منظور میتوان دسترسیهای مختلف را به صورت comma separated به سیستم معرفی کرد.
نکتهی مهم در تکه کد بالا مربوط است به PolicyPrefix که با استفاده از آن مشخص کردهایم که برای هر سیاست درخواستی، این فرآیند را طی نکند و موجب اختلال در سیستم نشود.
پس از پیاده سازی واسط مطرح شده، لازم است این پیاده سازی جدید را به سیستم DI هم معرفی کنید:
services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
خوب، تا اینجا فرآیند تعریف سیاستها به صورت خودکار انجام شد. در ادامه نیاز است با تعریف یک فیلتر Authorization، بتوان لیست دسترسیهای مورد نظر برای اکشنی خاص را نیز مشخص کرد تا در متد GetPolicyAsync فوق، کار ثبت خودکار سیاست دسترسی متناظر با آنرا توسط فراخوانی متد policyBuilder.RequireClaim، انجام دهد تا دیگر نیازی به تعریف دستی و جداگانهی آن، در کلاس آغازین برنامه نباشد. برای این منظور به شکل زیر عمل خواهیم کرد:
public class PermissionAuthorizeAttribute : AuthorizeAttribute { internal const string PolicyPrefix = "PERMISSION:"; /// <summary> /// Creates a new instance of <see cref="AuthorizeAttribute"/> class. /// </summary> /// <param name="permissions">A list of permissions to authorize</param> public PermissionAuthorizeAttribute(params string[] permissions) { Policy = $"{PolicyPrefix}{string.Join(",", permissions)}"; } }
همانطور که مشخص میباشد، رشته PERMISSION به عنوان پیشوند رشته تولیدی از لیست اسامی دسترسیها، استفاده شدهاست و در پراپرتی Policy قرار داده شدهاست. این بار برای کنترل دسترسی میتوان به شکل زیر عمل کرد:
[PermissionAuthorize(PermissionNames.Projects_View)] public IActionResult Get(FilteredQueryModel query) { //... } [PermissionAuthorize(PermissionNames.Projects_Create)] public IActionResult Post(ProjectModel model) { //... }
برای مثال در اولین فراخوانی فیلتر PermissionAuthorize فوق، مقدار ثابت PermissionNames.Projects_View به عنوان یک Policy جدید به متد GetPolicyAsync کلاس AuthorizationPolicyProvider سفارشی ما ارسال میشود. چون دارای پیشوند «:PERMISSION» است، مورد پردازش قرار گرفته و توسط متد policyBuilder.RequireClaim به صورت خودکار به سیستم معرفی و ثبت خواهد شد.
همچنین راه حل مطرح شده برای مدیریت دسترسیهای پویا، در gist به اشتراک گذاشته شده «موجودیتهای مرتبط با مدیریت دسترسیهای پویا» را نیز مد نظر قرار دهید.
در این مقاله آموزشی که یکی دیگر از سری مقالات آموزشی اصول و مبانی پایگاه داده پیشرفته میباشد، قصد داریم به یکی دیگر از مقولههای مهم در طراحی سیستمهای مدیریت پایگاه داده (DBMS) بپردازیم. همانطور که در مباحث قبلی بیان کردیم یکی از وظایف سیستم مدیریت پایگاه داده، حفظ سازگاری(consistency) دادهها میباشد. برای مثال یکی از راهکار هایی که برای این منظور ارائه میدهد انجام عملیات در قالب تراکنش هاست که در مبحث مربوط به تراکنش ها مفصل در مورد آن بحث کردیم. با این حال گاهی خطاها و شکست هایی (failure) در حین عملیات ممکن است پیش بیاید که منجر به خروج سیستم از وضعیت سازگار خود گردد. بعنوان مثال ممکن است سخت افزار سیستم دچار مشکل شود، مثلا دیسک از کار بیفتد (disk crash) یا آنکه برق قطع شود. خطاهای نرم افزاری نیز میتوانند جزو موارد شکست و خرابی بحساب آیند که خطای منطق برنامه (logic) از این نمونه میباشد. در چنین شرایطی بحثی مطرح میشود تحت عنوان بازیابی (recovery) و ترمیم پایگاه داده که در این مقاله قصد داریم در مورد آن صحبت کنیم. بنا به تعریف بازیابی به معنای بازگرداندن یک پایگاه داده به وضعیت سازگار گذشته خود، بعد از وقوع یک شکست یا خرابی است. توجه داشته باشید که اهمیت بازیابی و ترمیم پایگاه داده تا آنجایی است که حدود 10 درصد از سیستمهای مدیریت پایگاه داده را به خود اختصاص میدهند.
آنچه که در اینجا در مورد آن صحبت خواهیم کرد بازیابی بصورت نرم افزاری است که از آن تحت عنوان fail soft نام برده میشود. دقت داشته باشید در بیشتر مواقع میتوان از طریق نرم افزاری عمل بازیابی را انجام داد، اما در کنار راهکارهای نرم افزاری باید حتما اقدامات سخت افزاری ضروری نیز پیش بینی شود. بعنوان مثال گرفتن نسخههای پشتیبان یک امر ضروری در سیستمهای اطلاعاتی است. چرا که گاهی اوقات خرابیهای فیزیکی باعث از دست رفتن تمامی اطلاعات میگردند که در این صورت نسخههای پشتیبان میتوانند به کمک آیند و با کمک آنها سیستم را مجدد بازیابی کرد. در شکل زیر نمونه ای از روشهای پشتیبان گیری بنام mirroring نشان داده شده است که روش رایجی در سیستمهای بانک اطلاعاتی بشمار میرود. همانطور که در شکل نشان داده شده است در کنار نسخه اصلی (DISK)، نسخه(MIRROR) آن قرار داده شده است. این دو نسخه کاملا مشابه یکدیگرند و هر عملی که در DICK انجام میشود در MIRROR ان نیز اعمال میشود تا در مواقع خرابی DISK بتوان از نسخه MIRROR استفاده نمود.
در شکل زیر نمونه بسیار ساده از نحوه لاگ کردن در حین اجرای تراکنشها را مشاهده میکنید.
نیازمندیهای اصلی در بازیابی پایگاه داده
برای آنکه وارد بحث اصلی شویم باید بگویم در یک نگاه کلی میتوان گفت که ساختار زیر سیستم بازیابی پایگاه داده بر پایه سه عملیات استوار است که عبارتند از log ، redo و undo . برای آنکه بتوان در هنگام رخ دادن خطا عمل ترمیم و بازیابی را انجام داد، سیستم پایگاه داده با استفاده از مکانیزم لاگ کردن(logging) خود تمامی عملیاتی را که در پایگاه داده رخ میدهد و بنحوی منجر به تغییر وضعیت ان میگردد را در جایی ثبت و نگهداری میکند. اهمیت لاگ کردن وقایع بسیار بالاست، چرا که پس از رخ دادن شکست در سیستم ملاک ما برای بازیابی و ترمیم فایلهای لاگ (log files) می باشند.
سیستم دقیقا خط به خط این لاگها را میخواند و بر اساس وقایعی که رخ داده است تصمیمات لازم را برای بازیابی اتخاذ میکند. در حین خواندن فایلهای لاگ، سیستم برخی از وقایع را باید بی اثر کند. یعنی عمل عکس آنها را انجام دهد تا اثر آنها بر روی پایگاه داده از بین برود. به این عمل undo کردن میگوییم که همانطور که در بالا گفته شد یکی از عملیات اصلی در بازیابی است. عمل دیگری وجود دارد بنام انجام مجدد یا redo کردن که در برخی از مواقع باید صورت بگیرد. انجام مجدد همانطور که از اسمش پیداست به این معنی است که عملی که از لاگ فایل خوانده شده است باید مجدد انجام گیرد. بعنوان مثال در فایل لاگ به تراکنشی برخورد میکنیم و سیستم تصیم میگیرد که آن را مجدد از ابتدا به اجرا در آورد. دقت داشته باشید که سیستم بر اساس قوانین و قواعدی تصمیم میگیرد که تراکنشی را redo و یا undo نماید که در ادامه این بحث آن قوانین را باز خواهیم کرد.
در کنار لاگ فایل ها، که مبنای کار در بازیابی هستند، فایل دیگری نیز در سیستم وجود دارد که به DBMS در بازیابی کمک میکند. این فایل raster file نام دارد که در بخشهای بعدی این مقاله در مورد آن و کارایی آن بیشتر صحبت خواهیم نمود.
Recovery Manager
مسئولیت انجام بازیابی بصورت نرم افزاری (fail soft) بر عهده زیر سیستمی از DBMS بنام مدیر بازیابی (recovery manager) می باشد و همانطور که اشاره شد این زیر سیستم چیزی در حدود 10 در صد DBMSرا به خود اختصاص میدهد. برای آنکه این زیر سیستم بتواند مسئولیت خود را بنحو احسن انجام دهد بطوری که عمل بازیابی بدون نقص و قابل اعتماد باشد، باید به نکاتی توجه نمود. اولین نکته اینست که در لاگ کردن و همچنین خواندن لاگ فایل به جهت بازیابی و ترمیم پایگاه داده هیچ تراکنشی نباید از قلم بیفتد. تمامی تراکنشها در طول حیات سیستم باید لاگ شود تا بازیابی ما قابل اعتماد و بدون نقص باشد. نکته دوم اینست که اگر تصمیم به اجرای مجدد (redo) تراکنشی گرفته شد، طوری باید عمل Redo انجام شود که بلحاظ منطقی آن تراکنش یک بار انجام شود و تاثیرش یکبار بر دیتابیس اعمال گردد. بعنوان مثال فرض کنید که در طی یک تراکنش مبلغ یک میلیون تومان به حساب شخصی واریز میشود. مدتی بعد از اجرای و تمکیل تراکنش سیستم دچار مشکل میشود و مجبور به انجام بازیابی میشویم. در حین عمل بازیابی سیستم مدیریت بازیابی و ترمیم تصمیم به اجرای مجدد تراکنش مذکور میگیرد. در اینجا سیستم نباید مجدد یک میلیون تومان دیگر به حساب ان شخص واریز کند. چرا که در این صورت موجودی حساب فرد دو میلیون تومان خواهد شد که این اشتباه است. سیستم باید طوری عمل کند که پس از انجام مجدد تراکنش باز هم موجودی همان یک میلیون تومان باشد. یعنی مثلا ابتدا یک میلیون کسر و سپس یک میلیون به آن اضافه کند. این مسئله نکته بسیار مهمی است که طراحان DBMS باید حتما آن را مد نظر قرار دهند.
لاگ کردن:
همانطور که گفته شد هر تغییری که در پایگاه داده رخ میدهد باید لاگ شود. لاگ کردن به این معنی است که هر گونه عملیاتی که در پایگاه داده انجام میشود در فایل هایی به نام فایل لاگ (log file) ذخیره شود. توجه داشته باشید لاگ فایلها در بسیاری از سیستمهای نرم افزاری دیگر نیز استفاده میشود. بعنوان مثال در سیستم عامل ما انواع مختلفی فایل لاگ داریم. بعنوان نمونه یک فراخوانی سیستمی (system call) که در سیستم عامل توسط کاربر انجام میشود در فایلی مخصوص لاگ میشود. یکی از کاربرد این لاگ فایل شناسایی کاربران بد و خرابکار (malicious users) می تواند باشد که کارهای تحقیقاتی زیادی هم در این رابطه انجام شده و میشود. بدین صورت که میتوان با بررسی این فایل لاگ و آنالیز فراخوانیهای یک کاربر بدنبال فراخوانی هایی غیر عادی گشت و از این طریق تشخیص داد که کاربر بدنبال خرابکاری بوده یا خیر. مشابه چنین فایل هایی در DBMS نیز وجود دارد که هدف نهایی تمامی انها حفظ صحت، سازگاری و امنیت اطلاعات میباشد.
حال ببینیم در لاگ فایل مربوط به بازیابی اطلاعات چه چیز هایی نوشته میشود. در طول حیات پایگاه داده عملیات بسیار گوناگونی انجام میگیرد که جزئیات تمامی آنها باید لاگ شود. بعنوان مثال هنگامی که رکوردی درج میشود در لاگ فایل باید مشخص شود که در چه زمانی، توسط چه کاربری چه رکوردی، با چه شناسه ای به کدام جدول از دیتابیس اضافه شد. یا اینکه در موقع حذف باید مشخص شود چه رکوردی از چه جدولی حذف شده است. در هنگام بروز رسانی (update) باید علاوه بر مواردی که در درج لاگ میکنیم نام فیلد ویرایش شده، مقدار قبلی و مقدار جدید آن نیز مشخص شود. تمامی عملیات ریز لاگ میشوند و هیچ عملی نباید از قلم بیفتد. بنابراین فایل لاگ با سرعت زیاد بزرگ خواهد و اندازه دیتابیس نیز افزایش خواهد یافت. این افزایش اندازه مشکل ساز میتواند باشد. چراکه معمولا فضایی که ما بر روی دیسک به دیتابیس اختصاص میدهیم فضایی محدود است. بهمین دلیل به لحاظ فیزیکی نمیتوان فایل لاگی با اندازه نامحدود داشت. این در حالی است که چنین فایل هایی باید نامحدود باشند تا همه چیز را در خود ثبت نمایند. برای پیاده سازی ظرفیت نامحدود به لحاظ منطقی یکی از روشها پیاده سازی فایلهای حلقه ای(circular) است. بدین صورت که هنگامی که سیستم به انتهای فایل لاگ میرسد مجددا به ابتدا آن بر میگردد و از ابتدا شروع به نوشتن میکند. البته چنین ساختار هایی بدون اشکال نیستند. چرا که پس از رسیدن به انتهای فایل و شروع مجدد از ابتدا ما برخی از تراکنشهای گذشته را از دست خواهیم داد. این مسئله یکی از دلایلی است که بر اساس آن پیشنهاد میشود تا جایی که امکان دارد تراکنشها را کوچک پیاده سازی کنیم. گاهی اوقات بر روی لاگ فایل عمل فشرده سازی را نیز انجام میدهند. البته فشرده سازی بمعنای رایج ان مطرح نیست. بلکه منظور از فشرده سازی آنست که رکورد هایی که غیر ضروری هستند را حذف کنیم. بعنوان مثال فرض کنید رکوردی را از 50 به 60 تغییر داده ایم. مجددا همان رکورد را از 60 به 70 تغییر میدهیم. در این صورت برای این عملیات دو رکورد در فایل لاگ ثبت شده است که در هنگام فشرده سازی در صورت امکان میتوان ان دو را به یک رکورد تبدیل نمود (تغییر از 50 به 70 را بجای ان دو لاگ کرد). بعنوان مثال دیگر فرض کنید تراکنشی در گذشته دور انجام شده است و با موفقیت کامیت شده است. میتوان رکوردهای لاگ مربوط به این تراکنش را نیز بنا به شرایط حذف کرد.
دقت داشته باشید که ما عملیاتی مانند عملیات محاسباتی را در این لاگ فایل ثبت نمیکنیم. بعنوان مثال اگر دو فیلد با هم باید جمع شوند و نتیجه در فیلدی باید بروز گردد، جمع دو فیل را در سیستم لاگ نمیکنیم بلکه تنها مقدار نهایی ویرایش شده را ثبت میکنیم. چرا که عملیات محاسباتی در بازیابی ضروری نیستند و ثبت انها تنها باعث بزرگ شدن فایل میشود.
در برخی از سیستمهای حساس، ممکن است برای فایلهای لاگ هم یک کپی تهیه کنند تا در صورت بروز خطا در لاگ فایل بتوان آن را نیز بازیابی نمود.
انواع رکوردهای لاگ فایل :
در فایل لاگ رکوردهای مختلفی ممکن است درج شود که در این جا به چند نمونه از انها اشاره میکنیم:
- [start-transaction, T]
- [write-item, T, X, old-value, new-value]
- [read-item, T, X]
- [commit, T]
در شکل زیر نمونه بسیار ساده از نحوه لاگ کردن در حین اجرای تراکنشها را مشاهده میکنید.
در این شکل نکته ای وجود دارد که به آن اشاره ای میکنیم. همانطور که میبینید در شکل از اصطلاحimmediate update استفاده شده است. در برخی از سیستمها تغییرات تراکنشها بصورت فوری اعمال میشوند که اصطلاحا میگوییم immediate updates دارند. در مقابل این اصطلاح ما deffered را داریم. در این مدل تغییرات در انتهای کار اعمال میشوند (در زمان commit).
Write-Ahead Log (WAL) :
بر اساس آنچه تابحال گفته شد هر تغییری در پایگاه داده شامل دو عمل میشود. یکی انجام تغییر (اجرای تراکنش) و دیگری ثبت آن در لاگ فایل. حال سوالی که ممکن است مطرح شود اینست که کدامیک از این دو کار بر دیگری تقدم دارد؟ آیا اول تراکنش را باید اجرا کرد و سپس لاگ آن را نوشت و یا برعکس باید عمل کرد. یعنی پیش از هر تراکنشی ابتدا باید لاگ آن را ثبت کرد و سپس تراکنش را اجرا نمود. بر همین اساس سیاستی تعریف میشود بنام سیاست write-ahead log یا WAL که سوال دوم را تایید میکند. یعنی میگوید هنگامی که قرار است عملی در پایگاه داده صورت گیرد ابتدا باید ان عمل بطور کامل لاگ شود و سپس آن را اجرا نمود. این سیاست هدفی را دنبال میکند.
پیش از آنکه هدف این سیاست را توضیح دهیم لازم است نکته ای در مورد عملیات redo و undo بیان شود. شما با این دو عملیات در برنامههای مختلفی مانند آفیس، فتوشاپ و غیره آشنایی دارید. اما توجه داشته باشید که در DBMS این دو عملیات از پیچیدگی بیشتری برخوردار میباشند. اصطلاحا در پایگاه داده گفته میشود که عملیات redo و undo باید idempotent باشند. معنی idempotent بودن اینست که اگر قرار است تراکنشی در پایگاه داده undo شود، اگر بارها و بارها عمل undo را بر روی آن تراکنش انجام دهیم مانند این باشد این عمل را تنها یکبار انجام داده ایم. در مورد redo نیز این مسئله صادق است.
در تعریف idempotent بودن ویژگیهای دیگری نیز وجود دارد. بعنوان مثال گفته میشود undo بر روی عملی که هنوز انجام نشده هیچ تاثیری نخواهد داشت. این مسئله یکی از دلایل اهمیت استفاده از سیاستWAL را بیان میکند. بعنوان مثال فرض کنید میخواهیم رکوردی را در جدولی درج کنیم. همانطور که گفتیم دو روش برای این منظور وجود دارد. در روش اول ابتدا رکورد را در جدول مورد نظر درج میکنیم و سپس لاگ آن را مینویسیم. در این صورت اگر پس از درج رکورد سیستم با مشکل مواجه شود و مجبور به انجام عمل بازیابی شویم، بدلیل آنکه برای بازیابی بر اساس لاگ فایل عمل میکنیم و برای درج آن رکورد لاگی در سیستم ثبت نشده است، آن عمل را از دست میدهیم. در نتیجه بازیابی بطور کامل نمیتواند سیستم را ترمیم نماید. چراکه درج صورت گرفته اما لاگی برای آن ثبت نشده است. در روش دوم فرض کنید بر اساس سیاست WAL عمل میکنیم. ابتدا لاگ مربوط به درج رکورد را مینویسم. سپس پیش از آنکه عمل درج را انجام دهیم سیستم crash می کند و مجبور به بازیابی میشویم. دراین صورت هنگامی که Recovery Manager به رکورد مربوط به عمل درج در لاگ فایل میرسد یا باید آن را redo کند و یا undo (بعدا میگوییم بر چه اساس تصمیم گیری میکند). اگر تصمیم به undo کردن بگیرد بدلیل ویژگی گفته شده، عمل undo بر روی عملی که انجام نشده است هیچ تاثیری در پایگاه داده نخواهد گذاشت. اگر عمل redo را بخواهد انجام دهد نیز بدلیل آنکه لاگ مربوط به عمل درج در سیستم ثبت شده بدون هیچ مشکلی این عمل مجددا انجام میگیرد. بنابراین بر خلاف روش قبل هیچ تراکنشی را از دست نمیدهیم و سیستم بطور کامل بازیابی و ترمیم میشود. به این دلیل است که توصیه میشود در طراحیDBMS ها سیاست WAL بکار گیری شود.
نکته بسیار مهمی که در اینجا ذکر آن ضروری بنظر میرسد اینست که در هنگام لاگ کردن تراکنش ها، علاوه بر آنکه خود تراکنش لاگ میشود و این لاگها نیز در فایل فیزیکی باید نوشته شوند، عملیات لازم برای Redo کردن و یا undo کردن آن نیز لاگ میشود تا سیستم در هنگام بازیابی بداند که چه کاری برایredo و undo کردن باید انجام دهد. توجه داشته باشید در این سیاست، COMMIT تراکنشی انجام نمیشود مگر انکه تمامی لاگهای مربوط به عملیات redo و undo آن تراکنش در لاگ فایل فیزیکی ثبت شود.
قرار دادن checkpoint در لاگ فایل:
گفتیم که در هنگام رخ دادن یک خطا، برای بازیابی و ترمیم پایگاه داده به لاگ فایل مراجعه میکنیم و بر اساس تراکنش هایی که در آن ثبت شده است، عمل ترمیم را انجام میدهیم. علاوه بر آن، این را هم گفتیم که لاگ فایل، معمولا فایلی بزرگ است که از نظر منطقی با ظرفیت بینهایت پیاده سازی میشود. حال سوال اینجاست که اگر بعد گذشت ساعتها از عمر پایگاه داده و ثبت رکوردهای متعدد در لاگ فایل خطایی رخ داد، آیا مدیر بازیابی و ترمیم پایگاه داده باید از ابتدای لاگ فایل شروع به خواندن و بازیابی نماید؟ اگر چنین باشد در بانکهای اطلاعاتی بسیار بزرگ عمل بازیابی بسیار زمان بر و پر هزینه خواهد بود. برای جلوگیری از این کار مدیر بازیابی پایگاه داده وظیفه دارد در فواصل مشخصی در لاگ فایل نقاطی را علامت گذاری کند تا اگر خطایی رخ داد عمل undo کردن تراکنش را تنها تا همان نقطه انجام دهیم (نه تا ابتدای فایل). به این نقاط checkpoint گفته میشود که انتخاب صحیح آنها تاثیر بسیاری در کیفیت و کارایی عمل بازیابی دارد.
نکته بسیار مهمی که در مورد checkpoint ها وجود دارد اینست که آنها چیزی فراتر از یک علامت در لاگ فایل هستند. هنگامی که DBMS به زمانی میرسد که باید در لاگ فایل checkpoint قرار دهد، باید اعمال مهمی ابتدا انجام شود. اولین کاری که در زمان checkpoint باید صورت بگیرد اینست که رکورد هایی از لاگ فایل که هنوز به دیسک منتقل نشده اند، بر روی لاگ فایل فیزیکی بر روی دیسک نوشته شوند. به این عمل flush کردن لاگ رکوردها نیز گفته میشود. دومین کاری که در این زمان باید صورت بگیرید اینست که رکوردی خاص بعنوان checkpoint record در لاگ فایل درج گردد. در این رکورد در واقع تصویری از وضعیت دیتابیس در زمان checkpoint را نگهداری میکنیم. دقت داشته باشید که در زمان checkpoint،DBMS برای یک لحظه تمامی تراکنشهای در حال اجرا را متوقف میکند و لیستی از این تراکنشها را در رکورد مربوط به checkpoint نگهداری میکند تا در زمان بازیابی بداند چه تراکنش هایی در آن زمان هنوز commit نشده و تاثیرشان به پایگاه داده اعمال نشده است. سومین کاری که در این لحظه بایدا انجام گیرد ایسنت که اگر داده هایی از پایگاه داده هستند که عملیات مربوط به آنها COMMIT شده اند اما هنوز به دیسک منتقل نشده اند بر روی دیسک نوشته شوند.آخرین کاری که باید انجام شود اینست که آدرس رکورد مربوط به checkpoint در فایلی بنام raster file ذخیره شود. علت این کار آنست که در هنگام بازیابی بتوانیم بسرعت آدرس آخرین checkpoint را بدست آوریم.
عمل UNDO :
در اینجا قصد داریم معنی و مفهوم عمل undo را بر روی انواع مختلف تراکنشها را بیان کنیم.
- هنگامی که میگوییم یک عمل بروز رسانی (update) را میخواهیم undo کنیم منظور اینست که مقدار قبلی فیلد مورد نظر را به جای مقدار جدید آن قرار دهیم.
- هنگامی که عمل undo را بر روی عملیات حذف میخواهیم انجام دهیم منظور اینست که مقدار قبلی جدول (رکورد حذف شده) را مجددا باز گردانیم.
- هنگامی که عمل undo را بر روی عملیات درج (insert) می خواهیم انجام دهیم منظور این است که مقدار جدید درج شده در جدول را حذف کنیم.
انجام عمل بازیابی و ترمیم :
تا اینجا مقدمات لازم برای ترمیم پایگاه داده را گفتیم. حال میخواهیم بسراغ چگونگی انجام عمل ترمیم برویم. هنگامی که میخواهیم پایگاه داده ای را ترمیم کنیم اولین کاری که باید انجام گیرد اینست که بوسیله raster file، آدرس آخرین checkpoint لاگ فایل را پیدا کنیم. سپس فایل لاگ را از نقطه checkpoint به پایین اسکن میکنیم. در هنگام اسکن کردن باید تراکنشها را به دو گروه تقکیک کنیم، تراکنش هایی که باید undo شوند و تراکنش هایی که باید عمل redo بر روی انها انجام گیرد. علت این کار اینست که در هنگام undo کردن از انتهای لاگ فایل به سمت بالا باید حرکت کنیم و برای Redo کردن بصورت عکس، از بالا به سمت پایین میآییم. بنابراین جهت حرکت در لاگ فایل برای این دو عمل متفاوت است. بهمین دلیل باید ابتدا تراکنشها تفکیک شوند. اما چگونه این تفکیک صورت میگیرد؟
هنگام اسکن کردن (از نقطه checkpoint به سمت انتهای لاگ فایل (لحظه خطا) )، هر تراکنشی که رکورد لاگ مربوط به commit آن دیده شود باید در گروه redo قرار گیرد. بعبارت دیگر تراکنش هایی که در این فاصله commit شده اند را در گروه redo قرار میدهیم. در مقابل هر تراکنشی که commit آن دیده نشود (commit نشده اند) باید undo شود. باز هم تاکید میکنیم که این عمل تنها در فاصله بین آخرینcheckpoint تا لحظه وقوع خطا انجام میشود.
دقت داشته باشید که در شروع اسکن کردن اولین رکوردی که خوانده میشود رکورد مربوط بهcheckpoint می باشد که حاوی تراکنش هایی است که در زمان checkpoint در حال انجام بوده اند، یعنی هنوز commit نشده اند. بنابراین تمامی این تراکنشها را ابتدا در گروه تراکنش هایی که باید undo شوند قرار میدهیم. بمرور که عمل اسکن را ادامه میدهیم اگر به تراکنشی رسیدیم که رکورد مربوط به شروع ان ثبت شده باشد، باید آن تراکنش را در لیست undo قرار دهیم. تراکنش هایی که commit آنها دیده شود را نیز باید از گروه undo حذف و به گروه Redo اضافه نماییم. پس از خاتمه عمل اسکن ما دو لیست از تراکنشها داریم. یکی تراکنش هایی که باید Redo شوند و دیگری آنهایی که باید undo گردند.
پس از مشخص شدن دو لیست Redo و Undo، باید دو کار دیگر انجام شود. اولین کار اینست که تراکنش هایی که باید undo شوند را از پایین به بالا undo کنیم. یکی از دلایل اینکه ابتدا عملیات undo را انجام میدهیم ایسنت هنگامی که تراکنش ها commit نشده اند، قفل هایی را که بر روی منابع پایگاه داده زده اند هنوز آزاد نکرده اند. با عمل undo کردن این قفلها را آزاد میکنیم و بدین وسیله کمک میکنیم تا درجه همروندی پایگاه داده پایین نیاید. پس از خاتمه عملیات undo، به نقطه checkpoint می رسیم. در این لحظه مانند اینست که هیچ تراکنشی در سیستم وجود ندارد. حالا بر اساس لیست redo از بالا یعنی نقطهcheckpoint به سمت پایین فایل لاگ حرکت میکنیم و تراکنشهای موجود در لیست redo را مجدد اجرا میکنیم. پس از خاتمه این گام نیز عملیات بازیابی خاتمه مییابد میتوان گفت سیستم به وضعیت پایدار قبلی خود باز گشسته است.
برای روشنتر شدن موضوع به شکل زیر توجه کنید. در این شکل نقطه Tf زمان رخ دادن خطا را در پایگاه داده نشان میدهد. اولین کاری که برای بازیابی باید انجام گیرد، همانطور که گفته شده اینست که آدرس مربوط به زمان checkpoint (Tc) از raster file خوانده شود. پس از این کار از لحظه Tc به سمت Tf شروع به اسکن کردن لاگ فایل میکنیم. بدلیل آنکه در زمان Tc دو تراکنش T2 و T3 در حال اجرا بودند (و نام آنها در checkpoint record نیز ثبت شده است)، این دو تراکنش را در لیست redo قرار میدهیم. سپس عمل اسکن را به سمت پایین ادامه میدهیم. در حین اسکن کردن ابتدا به رکورد start trasnactionمربوط به تراکنش T4 می رسیم. بهمین دلیل این تراکنش را به لیست undo ها اضافه میکنیم. پس از آن به commit تراکنش T2 می رسیم. همانطور که گفته شد باید T2 را از لیست undo ها خارج و به یست تراکنش هایی که باید redo شوند اضافه گردد. سپس به تراکنش T5 می رسیم که تازه آغاز شده است. ان را نیز در گروه undo قرار میدهیم. بعد از ان رکورد مربوط به commit تراکنش T4 دیده میشود و ان را از لیست undo حذف و لیست redo اضافه میکنی. اسکن را ادامه میدهیم تا به نقطه Tf می رسیم. در ان لحظه لیست undo ها شامل دو تراکنش T3 و T5 و لیست Redo ها شامل تراکنش های T2 و T4 می باشند. در مورد تراکنش T1 نیز چون پیش از لحظه Tc کامیت شده است عملی صورت نمیگیرد.
موفق و پیروز باشید
%windir%\system32\inetsrv\config\schema
احتمال زیاد دسترسی برای ویرایش این دایرکتوری به خاطر مراتب امنیتی با مشکل برخواهید خورد برای ویرایش این نکته امنیتی از اینجا یا به خصوص از اینجا کمک بگیرید.<configSchema> <sectionSchema name="system.webServer/imageCopyright"> <attribute name="enabled" type="bool" defaultValue="false" /> <attribute name="message" type="string" defaultValue="Your Copyright Message" /> <attribute name="color" type="string" defaultValue="Red"/> </sectionSchema> </configSchema>
- enabled: آیا این هندلر فعال باشد یا خیر.
- message: پیامی که باید به عنوان تگ درج شود.
- color: رنگ متن که به طور پیش فرض قرمز رنگ است.
به هر کدام از تگهای بالا یک مقدار پیش فرض داده ایم تا اگر مقداردهی نشدند، ماژول طبق مقادیر پیش فرض کار خود را انجام هد.
<configSections> ... <sectionGroup name="system.webServer"> <section name="imageCopyright" overrideModeDefault="Allow"/> ... </sectionGroup> </configSections>
<section name="windowsAuthentication" overrideModeDefault="Allow" />
<location path="AdministratorSite" overrideMode="Allow"> <security> <authentication> <providers> <windowsAuthentication enabled="false"> </providers> <add value="Negotiate" /> <add value="NTLM" /> </location> </windowsAuthentication> </authentication> </security>
<system.webServer> <imageCopyright /> </system.webServer>
<system.webServer> <imageCopyright enabled="true" message="an example of www.dotnettips.info" color="Blue" /> </system.webServer>
%windir%\system32\inetsrv\appcmd set config -section:system.webServer/imageCopyright /color:yellow /message:"Dotnettips.info" /enabled:true
%windir%\system32\inetsrv\appcmd list config -section:system.webServer/imageCopyright
c:\inetpub\mypictures
%windir%\system32\inetsrv\appcmd add app -site.name:"Default Web Site" -path:/mypictures -physicalPath:%systemdrive%\inetpub\mypictures
%windir%\system32\inetsrv\appcmd set config "Default Web Site/mypictures" -section:directoryBrowse -enabled:true
c:\inetpub\mypictures\App_Code\imagecopyrighthandler.cs
#region Using directives using System; using System.Web; using System.Drawing; using System.Drawing.Imaging; using Microsoft.Web.Administration; #endregion namespace IIS7Demos { public class imageCopyrightHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { ConfigurationSection imageCopyrightHandlerSection = WebConfigurationManager.GetSection("system.webServer/imageCopyright"); HandleImage( context, (bool)imageCopyrightHandlerSection.Attributes["enabled"].Value, (string)imageCopyrightHandlerSection.Attributes["message"].Value, (string)imageCopyrightHandlerSection.Attributes["color"].Value ); } void HandleImage( HttpContext context, bool enabled, string copyrightText, string color ) { try { string strPath = context.Request.PhysicalPath; if (enabled) { Bitmap bitmap = new Bitmap(strPath); // add copyright message Graphics g = Graphics.FromImage(bitmap); Font f = new Font("Arial", 50, GraphicsUnit.Pixel); SolidBrush sb = new SolidBrush(Color.FromName(color)); g.DrawString( copyrightText, f, sb, 5, bitmap.Height - f.Height - 5 ); f.Dispose(); g.Dispose(); // slow, but good looking resize for large images context.Response.ContentType = "image/jpeg"; bitmap.Save( context.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg ); bitmap.Dispose(); } else { context.Response.WriteFile(strPath); } } catch (Exception e) { context.Response.Write(e.Message); } } public bool IsReusable { get { return true; } } } }
<system.web> <compilation> <assemblies> <add assembly="Microsoft.Web.Administration, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"/> </assemblies> </compilation> </system.web>
appcmd set config "Default Web Site/mypictures/" -section:handlers /+[name='JPGimageCopyrightHandler',path='*.jpg',verb='GET',type='IIS7Demos.imageCopyrightHandler']
appcmd recycle AppPool DefaultAppPool
در این بلاگ میتوان:
- یک مطلب جدید را ارسال کرد.
- مطالب قابل ویرایش و یا حذف هستند.
- مطالب بلاگ قسمت ارسال نظرات دارند.
- امکان گزارشگیری از آخرین نظرات ارسالی وجود دارد.
- سایت صفحات درباره و تماس با ما را نیز دارا است.
ساختار پوشههای برنامه
در تصویر ذیل، ساختار پوشههای برنامه بلاگ را ملاحظه میکنید. چون قسمت سمت کلاینت این برنامه کاملا جاوا اسکریپتی است، پوشههای App، Controllers، Libs، Models، Routes و Templates آن در پوشهی Scripts تعریف شدهاند و به این ترتیب میتوان تفکیک بهتری را بین اجزای تشکیل دهندهی یک برنامهی تک صفحهای وب Emeber.js پدید آورد.
فایل CSS بوت استرپ نیز به پوشهی Content اضافه شدهاست.
دریافت پیشنیازهای سمت کاربر برنامه
در ساختار پوشههای فوق، از پوشهی Libs برای قرار دادن کتابخانههای پایه برنامه مانند jQuery و Ember.js استفاده خواهیم کرد. به این ترتیب:
- نیاز به آخرین نگارشهای Ember.js و همچنین افزونهی Ember-Data آن برای کار سادهتر با دادهها و سرور وجود دارد. این فایلها را از آدرس ذیل میتوانید دریافت کنید (نسخههای نیوگت به دلیل قدیمی بودن و به روز نشدن مداوم آنها توصیه نمیشوند):
http://emberjs.com/builds/#/beta
برای حالت آزمایش برنامه، استفاده از فایلهای دیباگ آن توصیه میشوند (فایلهایی با نام اصلی و بدون پسوند prod یا min). زیرا این فایلها خطاها و اطلاعات بسیار مفصلی را از اشکالات رخ داده، در کنسول وب مرورگرها، فایرباگ و یا Developer tools آنها نمایش میدهند. نسخهی min برای حالت ارائهی نهایی برنامه است. نسخهی prod همان نسخهی دیباگ است با حذف اطلاعات دیباگ (نسخهی production فشرده نشده). نسخهی فشرده شدهی prod آن، فایل min نهایی را تشکیل میدهد.
- دریافت جیکوئری
- آخرین نگارش handlebars.js را از سایت رسمی آن دریافت کنید: http://handlebarsjs.com
- Ember Handlebars Loader: https://github.com/michaelrkn/ember-handlebars-loader
- Ember Data Local Storage Adapter: https://github.com/kurko/ember-localstorage-adapter
ترتیب تعریف و قرارگیری این فایلها را پس از دریافت، در فایل index.html قرار گرفته در ریشهی سایت، در کدهای ذیل مشاهده میکنید:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Ember Blog</title> <link href="Content/bootstrap.css" rel="stylesheet" /> <link href="Content/bootstrap-theme.css" rel="stylesheet" /> <link href="Content/styles.css" rel="stylesheet" /> <script src="Scripts/Libs/jquery-2.1.1.min.js" type="text/javascript"></script> <script src="Scripts/Libs/bootstrap.min.js" type="text/javascript"></script> <script src="Scripts/Libs/handlebars-v2.0.0.js" type="text/javascript"></script> <script src="Scripts/Libs/ember.js" type="text/javascript"></script> <script src="Scripts/Libs/ember-handlebars-loader-0.0.1.js" type="text/javascript"></script> <script src="Scripts/Libs/ember-data.js" type="text/javascript"></script> <script src="Scripts/Libs/localstorage_adapter.js" type="text/javascript"></script> </head> <body> </body> </html>
اصلاح فایل ember-handlebars-loader-0.0.1.js
اگر به فایل ember-handlebars-loader-0.0.1.js مراجعه کنید، مسیر فایلهای قالب handlebars قسمتهای مختلف برنامه را از پوشهی templates واقع در ریشهی سایت میخواند. با توجه به تصویر ساختار پوشهی پروژهی جاری، پوشهی template به داخل پوشهی Scripts منتقل شدهاست و نیاز به یک چنین اصلاحی دارد:
url: "Scripts/Templates/" + name + ".hbs",
<system.webServer> <staticContent> <mimeMap fileExtension=".hbs" mimeType="text/x-handlebars-template" /> </staticContent> </system.webServer>
مزیت استفاده از نسخهی دیباگ ember.js در حین توسعهی برنامه
نسخهی دیباگ ember.js علاوه بر به همراه داشتن خطاهای بسیار جامع و توضیح علل مشکلات، مواردی را که در آینده منسوخ خواهند شد نیز توضیح میدهد:
برای مثال در اینجا عنوان شدهاست که دیگر از linkTo استفاده نکنید و آنرا به link-to تغییر دهید.
همچنین اگر از مرورگر کروم استفاده میکنید، افزونهی Ember Inspector را نیز میتوانید نصب کنید تا اطلاعات بیشتری را از جزئیات مسیریابیهای تعریف شده و قالبهای Ember.js بتوان مشاهده کرد. این افزونه به صورت یک برگهی جدید در Developer tools آن ظاهر میشود.
ایجاد شیء Application
همانطور که در قسمتهای پیشین نیز عنوان شد (^ و ^ )، یک برنامهی Ember.js با تعریف وهلهای از شیء Application آن آغاز میشود. برای این منظور به پوشهی App مراجعه کرده و فایل جدید Scripts\App\blogger.js را اضافه کنید؛ با این محتوا:
Blogger = Ember.Application.create();
<script src="Scripts/App/blogger.js" type="text/javascript"></script>
تعاریف مسیریابی و قالبها
اکنون در ادامه قصد داریم لیستی از عناوین مطالب ارسالی را نمایش دهیم. در ابتدا این عناوین را از یک آرایهی ثابت جاوا اسکریپتی دریافت خواهیم کرد و پس از آن از یک Web API کنترلر، جهت دریافت اطلاعات از سرور کمک خواهیم گرفت.
کار router در Ember.js، نگاشت آدرس درخواستی توسط کاربر، به یک route یا مسیریابی تعریف شدهاست. به این ترتیب مدل، کنترلر و قالب آن route به صورت خودکار بارگذاری و پردازش خواهند.
با مراجعه به ریشهی سایت، فایل index.html بارگذاری میشود.
در اینجا تصویری از صفحهی آغازین بلاگ را مشاهده میکنید. در آن صفحهی posts همان ریشهی سایت نیز میباشد. بنابراین نیاز است ابتدا مسیریابی آنرا تعریف کرد. برای این منظور، فایل جدید Scripts\App\router.js را به پوشهی App اضافه کنید؛ با این محتوا:
Blogger.Router.map(function () { this.resource('posts', { path: '/' }); });
همچنین مدخل آنرا نیز در فایل index.html تعریف نمائید:
<script src="Scripts/App/blogger.js" type="text/javascript"></script> <script src="Scripts/App/router.js" type="text/javascript"></script>
افزودن مسیریابی و قالب posts
در ادامه فایل جدید Scripts\Templates\posts.hbs را اضافه کنید. به این ترتیب قالب خالی مطالب به پوشهی templates اضافه میشود. محتوای این فایل را به نحو ذیل تنظیم کنید:
<div class="container"> <h1>Emeber.js blog</h1> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div>
برای بارگذاری این قالب نیاز است آنرا به template loader توضیح داده شده در ابتدای بحث، در فایل index.html اضافه کنیم:
<script> EmberHandlebarsLoader.loadTemplates([ 'posts' ]); </script>
افزودن مسیریابی و قالب about
در ادامه قصد داریم صفحهی about را اضافه کنیم. مجددا با افزودن مسیریابی آن به فایل Scripts\App\router.js شروع میکنیم:
Blogger.Router.map(function () { this.resource('posts', { path: '/' }); this.resource('about'); });
<h1>About Ember Blog</h1> <p>Bla bla bla!</p>
<script> EmberHandlebarsLoader.loadTemplates([ 'posts', 'about' ]); </script>
<script type="text/x-handlebars" data-template-name="about"> </script>
برای آزمایش این مسیر و قالب جدید آن، آدرس http://localhost/#/about را بررسی کنید.
اضافه کردن منوی ثابت بالای سایت
روش اول این است که به ابتدای هر دو قالب about.hbs و posts.hbs، تعاریف منو را اضافه کنیم. مشکل اینکار، تکرار کدها و پایین آمدن قابلیت نگهداری برنامه است. روش بهتر، افزودن کدهای مشترک بین صفحات، در قالب application برنامه است. نمونهی آنرا در مثال قسمت قبل مشاهده کردهاید. در اینجا چون قصد نداریم به صورت دستی قالبها را به صفحه اضافه کنیم و همچنین برای ساده شدن نگهداری برنامه، قالبها را در فایلهای مجزایی قرار دادهایم، تنها کافی است فایل جدید Scripts\Templates\application.hbs را به پوشهی قالبهای برنامه اضافه کنیم؛ با این محتوا:
<div class='container'> <nav class='navbar navbar-default' role='navigation'> <ul class='nav navbar-nav'> <li>{{#link-to 'posts'}}Posts{{/link-to}}</li> <li>{{#link-to 'about'}}About{{/link-to}}</li> </ul> </nav> {{outlet}} </div>
<script> EmberHandlebarsLoader.loadTemplates([ 'posts', 'about', 'application' ]); </script>
افزودن مسیریابی و قالب contact
برای افزودن صفحهی تماس با مای سایت، ابتدا مسیریابی آنرا در فایل Scripts\App\router.js تعریف میکنیم:
Blogger.Router.map(function () { this.resource('posts', { path: '/' }); this.resource('about'); this.resource('contact'); });
<h1>Contact</h1> <ul> <li>Phone: ...</li> <li>Email: ...</li> </ul>
<script> EmberHandlebarsLoader.loadTemplates([ 'posts', 'about', 'application', 'contact' ]); </script>
<div class='container'> <nav class='navbar navbar-default' role='navigation'> <ul class='nav navbar-nav'> <li>{{#link-to 'posts'}}Posts{{/link-to}}</li> <li>{{#link-to 'about'}}About{{/link-to}}</li> <li>{{#link-to 'contact'}}Contact{{/link-to}}</li> </ul> </nav> {{outlet}} </div>
تعریف مسیریابی تو در تو در صفحهی contact
در حال حاضر صفحهی Contact، ایمیل و شماره تماس را در همان بار اول نمایش میدهد. میخواهیم این دو را تبدیل به دو لینک جدید کنیم که با کلیک بر روی هر کدام، محتوای مرتبط، در قسمتی از همان صفحه بارگذاری شده و نمایش داده شود.
برای اینکار نیاز است مسیریابی را تو در تو تعریف کنیم:
Blogger.Router.map(function () { this.resource('posts', { path: '/' }); this.resource('about'); this.resource('contact', function () { this.resource('email'); this.resource('phone'); }); });
پس از آن دو فایل قالب جدید Scripts\Templates\email.hbs را با محتوای:
<h2>Email</h2> <p> <span></span> Email name@site.com. </p>
<h2>Phone</h2> <p> <span></span> Call 12345678. </p>
<script> EmberHandlebarsLoader.loadTemplates([ 'posts', 'about', 'application', 'contact', 'email', 'phone' ]); </script>
<h1>Contact</h1> <div class="row"> <div class="col-md-6"> <p> Want to get in touch? <ul> <li>{{#link-to 'phone'}}Phone{{/link-to}}</li> <li>{{#link-to 'email'}}Email{{/link-to}}</li> </ul> </p> </div> <div class="col-md-6"> {{outlet}} </div> </div>
در اینجا نحوهی پردازش مسیریابی contact را ملاحظه میکنید. ابتدا درخواستی جهت مشاهدهی آدرس http://localhost/#/contact دریافت میشود. سپس router این درخواست را به مسیریابی همنامی منتقل میکند. این مسیریابی ابتدا قالب عمومی application را رندر کرده و سپس قالب اصلی و همنام مسیریابی جاری یا همان contact.hbs را رندر میکند. در این صفحه چون مسیریابی تو در تویی تعریف شدهاست، اگر درخواستی برای مشاهدهی http://localhost/#/contact/phone دریافت شود، محتوای آنرا در {{outlet}} قالب contact.hbs جاری رندر میکند.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
EmberJS03_01.zip
{"jti":"26bdfd20-104f-45d4-a4e1-111044808041", "iss":"http://localhost:5000/", "iat":1531729854, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier":"1", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"Vahid", "DisplayName":"وحید", "http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber":"046fb152a7474043952475cfa952cdc9", "http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata":"1", "DynamicPermission":[":MyProtectedApi2:Get", ":MyProtectedEditorsApi:Get", ":MyProtectedApi3:Get", ":MyProtectedApi4:Get"], "http://schemas.microsoft.com/ws/2008/06/identity/claims/role":["Admin", "Editor", "User"], "nbf":1531729855, "exp":1531729975, "aud":"Any"}
public bool CanUserAccess(ClaimsPrincipal user, string area, string controller, string action) { var currentClaimValue = $"{area}:{controller}:{action}"; var securedControllerActions = _mvcActionsDiscoveryService.GetAllSecuredControllerActionsWithPolicy(ConstantPolicies.DynamicPermission); if (!securedControllerActions.SelectMany(x => x.MvcActions).Any(x => x.ActionId == currentClaimValue)) { throw new KeyNotFoundException($@"The `secured` area={area}/controller={controller}/action={action} with `ConstantPolicies.DynamicPermission` policy not found. Please check you have entered the area/controller/action names correctly and also it's decorated with the correct security policy."); } if (!user.Identity.IsAuthenticated) { return false; } if (user.IsInRole("Admin")) { // Admin users have access to all of the pages. return true; } // Check for dynamic permissions // A user gets its permissions claims from the `ApplicationClaimsPrincipalFactory` class automatically and it includes the role claims too. //for check user has claim for access to action return user.HasClaim(claim => claim.Type == ConstantPolicies.DynamicPermissionClaimType && claim.Value == currentClaimValue); }