نمایش گزارش در پنجره جدید
<asp:Button ID="btnGenerateReport" runat="server"
Text="Generate Report"
CssClass="btn btn-success btn-generate"
resourcekey="GenerateReportButton"
OnClientClick="return openNewWindow();"
OnClick="btnGenerateReport_Click" />
<script type="text/javascript">
function openNewWindow () {
document.forms[0].target = '_blank';
setTimeout(function () { window.document.forms[0].target = ''; }, 0);
}
</script>
در اینجا روش Adjacency model را به علت بیشتر مرسوم بودن آن و شباهت بسیار زیاد آن به «مدلهای خود ارجاع دهنده» بررسی خواهیم کرد.
مدل دادهای Adjacency
در حالت ساختار درختی از نوع مجاورت، علاوه بر خواص اصلی یک کلاس، سه خاصیت دیگر نیز باید تعریف شوند:
using System; namespace jqGrid13.Models { public class BlogComment { // Other properties public int Id { set; get; } public string Body { set; get; } public DateTime AddDateTime { set; get; } // for treeGridModel: 'adjacency' public int? ParentId { get; set; } public bool IsNotExpandable { get; set; } public bool IsExpanded { get; set; } } }
IsNotExpandable به این معنا است که نود جاری آیا قرار است باز شود و فرزندی دارد یا خیر؟ اگر فرزندی ندارد باید مساوی True قرار گیرد.
IsExpanded حالت پیش فرض باز بودن یا نبودن یک نود را مشخص میکند.
نحوهی بازگشت اطلاعات درختی از سمت سرور
در نگارش فعلی jqGrid، در حالت نمایش درختی، مباحث صفحه بندی و مرتب سازی غیرفعال هستند و کدهای مرتبط با آن که در اینجا ذکر شدهاند، فعلا تاثیری ندارند (البته با کمی تغییر در کدهای آن، میتوان این قابلیت را هم فعال کرد. اطلاعات بیشتر).
نکتهی مهم treeGrid، سه پارامتر دیگر هستند که از سمت کلاینت به سرور ارسال میشوند:
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Web.Mvc; using jqGrid13.Models; using JqGridHelper.DynamicSearch; // for dynamic OrderBy using JqGridHelper.Models; using JqGridHelper.Utils; namespace jqGrid13.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult GetComments(JqGridRequest request, int? nodeid, int? parentid, int? n_level) { var list = BlogCommentsDataSource.LatestBlogComments; // در این حالت خاص فعلا در نگارش جای جیکیوگرید صفحه بندی کار نمیکند و فعال نیست و محاسبات ذیل اهمیتی ندارند var pageIndex = request.page - 1; var pageSize = request.rows; var totalRecords = list.Count; var totalPages = (int)Math.Ceiling(totalRecords / (float)pageSize); var productsQuery = list.AsQueryable(); if (nodeid == null) { productsQuery = productsQuery.Where(x => x.ParentId == null); } else { productsQuery = productsQuery.Where(x => x.ParentId == nodeid.Value); } var products = productsQuery.OrderBy(request.sidx + " " + request.sord) .Skip(pageIndex * pageSize) .Take(pageSize) .ToList(); var newLevel = n_level == null ? 0 : n_level.Value + 1; var productsData = new JqGridData { Total = totalPages, Page = request.page, Records = totalRecords, Rows = (products.Select(comment => new JqGridRowData { Id = comment.Id, RowCells = new List<object> { comment.Id, comment.Body, comment.AddDateTime.ToPersianDate(), // اطلاعات خاص نمایش درختی به ترتیب newLevel, comment.ParentId == null ? "" : comment.ParentId.Value.ToString(CultureInfo.InvariantCulture), comment.IsNotExpandable, comment.IsExpanded } })).ToList() }; return Json(productsData, JsonRequestBehavior.AllowGet); } } }
n_level مقدار جلو رفتگی نمایش اطلاعات یک نود را مشخص میکند. در اینجا چون با کلیک بر روی هر نود، فرزند آن از سرور واکشی میشود و lazy loading برقرار است، بازگشت مقدار n_level دریافتی از کلاینت به علاوه یک، کافی است. اگر نیاز است تمام نودها باز شده نمایش داده شوند، این مورد را باید به صورت دستی محاسبه کرده و در مدل BlogComment پیش بینی کنید.
در نهایت آرایهای از خواص مدنظر به همراه 4 خاصیت ساختار درختی باید به ترتیب بازگشت داده شوند.
فعال سازی سمت کاربر treeGrid
برای فعال سازی سمت کاربر نمایش درختی اطلاعات، باید سه خاصیت ذیل تنظیم شوند:
$('#list').jqGrid({ caption: "آزمایش سیزدهم", // .... مانند قبل treeGrid: true, treeGridModel: 'adjacency', ExpandColumn: '@(StronglyTyped.PropertyName<BlogComment>(x => x.Body))' }).jqGrid('gridResize', { minWidth: 400 }); });
یک نکتهی تکمیلی
اگر میخواهید دقیقا به شکل زیر برسید:
تنظیم rownumbers: true گرید را حذف کنید. همچنین ستون Id را نیز با تنظیمهای hidden:true, key: true مخفی نمائید (در تعاریف colModel).
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
jqGrid13.zip
برای مطالعه بیشتر
Tree Grid
Nested Set Model
Adjacency Model
غیرفعال کردن اجرای اسکریپتهای inline
عمدهی حملات XSS زمانی قابلیت اجرا پیدا میکنند که مهاجم بتواند به طریقی (ورودیهای اعتبارسنجی نشده)، اسکریپتی را به درون صفحهی جاری تزریق کند. بنابراین اگر ما به مرورگر اعلام کنیم که دیگر اسکریپتهای inline را پردازش نکن، سایت را تا حد زیادی در مقابل حملات XSS مقاوم کردهایم. این قابلیت به صورت پیش فرض خاموش است؛ چون به طور قطع فعال سازی آن بسیاری از سایتهایی را که عادت کردهاند اسکریپتهای خود را داخل صفحات وب مدفون کنند، از کار میاندازد. این نوع سایتها باید به روز شده و اسکریپتها را از طریق فایلهای خارجی js، به سایت و صفحات خود الحاق کنند.
برای فعال سازی این قابلیت، فقط کافی است هدرهای زیر به Response اضافه شوند:
Content-Security-Policy: script-src 'self' X-WebKit-CSP: script-src 'self' X-Content-Security-Policy: script-src 'self'
بعد از فعال شدن این قابلیت، فقط اسکریپتهایی که از طریق دومین شما به صفحه الحاق شدهاند، قابلیت اجرا را خواهند یافت و کلیه اسکریپتهای مدفون شده داخل صفحات، دیگر اجرا نخواهد شد. در این حالت اگر از CDN برای الحاق اسکریپتی استفاده میکنید، مثلا مانند الحاق jQuery به صفحه، نیاز است مسیر آنرا صراحتا در این هدر ذکر کنید:
Content-Security-Policy: script-src 'self' https://youcdn.com X-WebKit-CSP: script-src 'self' https://yourcdn.com X-Content-Security-Policy: script-src 'self' https://yourcdn.com
Content-Security-Policy: default-src 'self' https://youcdn.com X-WebKit-CSP: default-src 'self' https://yourcdn.com X-Content-Security-Policy: default-src 'self' https://yourcdn.com
نحوهی اضافه کردن هدرهای Content Security Policy به برنامههای ASP.NET
روشی که با هر دو برنامههای وب فرم و MVC کار میکند، تهیه یک HTTP module است؛ به شرح ذیل:
using System; using System.Web; namespace AntiXssHeaders { public class SecurityHeadersConstants { public static readonly string XXssProtectionHeader = "X-XSS-Protection"; public static readonly string XFrameOptionsHeader = "X-Frame-Options"; public static readonly string XWebKitCspHeader = "X-WebKit-CSP"; public static readonly string XContentSecurityPolicyHeader = "X-Content-Security-Policy"; public static readonly string ContentSecurityPolicyHeader = "Content-Security-Policy"; public static readonly string XContentTypeOptionsHeader = "X-Content-Type-Options"; } public class ContentSecurityPolicyModule : IHttpModule { public void Dispose() { } public void Init(HttpApplication app) { app.BeginRequest += AppBeginRequest; } void AppBeginRequest(object sender, EventArgs e) { var app = (HttpApplication)sender; var response = app.Context.Response; setHeaders(response); } private static void setHeaders(HttpResponse response) { response.Headers.Set(SecurityHeadersConstants.XFrameOptionsHeader, "SameOrigin"); // For IE 8+ response.Headers.Set(SecurityHeadersConstants.XXssProtectionHeader, "1; mode=block"); response.Headers.Set(SecurityHeadersConstants.XContentTypeOptionsHeader, "nosniff"); //todo: Add /Home/Report --> public JsonResult Report() { return Json(true); } const string cspValue = "default-src 'self';"; // For Chrome 16+ response.Headers.Set(SecurityHeadersConstants.XWebKitCspHeader, cspValue); // For Firefox 4+ response.Headers.Set(SecurityHeadersConstants.XContentSecurityPolicyHeader, cspValue); response.Headers.Set(SecurityHeadersConstants.ContentSecurityPolicyHeader, cspValue); } } }
//// RegisterGlobalFilters -> filters.Add(new ContentSecurityPolicyFilterAttribute()); public class ContentSecurityPolicyFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var response = filterContext.HttpContext.Response; response.AddHeader("Content-Security-Policy", "script-src 'self'"); // the rest ... base.OnActionExecuting(filterContext); } }
الف) X-XSS-Protection مربوط است به IE 8 به بعد
ب) تنظیم هدر X-Frame-Options به SameOrigin سبب میشود تا صفحات سایت شما دیگر توسط Iframeها در سایتهای دیگر قابل نمایش نباشد و فقط در سایت جاری بتوان صفحهای را از همان دومین در صورت نیاز توسط Iframeها نمایش داد.
ج) تنظیم X-Content-Type-Options به nosniff سبب میشود تا IE سعی نکند با اجرای یک محتوا سعی در تشخیص mime-type آن کند و به این ترتیب امنیت دسترسی و مشاهده اشیاء قرار گرفته در صفحه (و یا تزریق شده توسط مهاجمین) به شدت بالا خواهد رفت.
برای مطالعه بیشتر
Security through HTTP response headers
پروژهی کاملی مخصوص افزودن هدرهای یاد شده
https://nwebsec.codeplex.com/
یک نکته تکمیلی
توصیه شدهاست تا دیگر از روال رویدادگردان PreSendRequestHeaders برای ارسال هدرها استفاده نکنید؛ چون با پردازشهای غیرهمزمان تداخل ایجاد میکند.
با کمک امکانات ارائه شده توسط LINQ ، میتوان بسیاری از اعمال برنامه نویسی را در حجمی کمتر، خواناتر و در نتیجه با قابلیت نگهداری بهتر، انجام داد که تعدادی از آنها را در ادامه مرور خواهیم کرد.
الف) تهیه یک یک رشته، حاوی عناصر یک آرایه، جدا شده با کاما.
using System.Linq;
public class CLinq
{
public static string GetCommaSeparatedListNormal(string[] data)
{
string items = string.Empty;
foreach (var item in data)
{
items += item + ", ";
}
return items.Remove(items.Length - 2, 1).Trim();
}
public static string GetCommaSeparatedList(string[] data)
{
return data.Aggregate((s1, s2) => s1 + ", " + s2);
}
}
ب) پیدا کردن تعداد عناصر یک آرایه حاوی مقداری مشخص
برای مثال آرایه زیر را در نظر بگیرید:
var names = new[] { "name1", "name2", "name3", "name4", "name5", "name6", "name7" };
در تابع GetCountNormal زیر، این کار به شکلی متداول انجام شده و در GetCount از LINQ Count extension method کمک گرفته شده است.
using System.Linq;
public class CLinq
{
public static int GetCountNormal()
{
var names = new[] { "name1", "name2", "name3", "name4", "name5", "name6", "name7" };
var count = 0;
foreach (var name in names)
{
if (name.Contains("name"))
count += 1;
}
return count;
}
public static int GetCount()
{
var names = new[] { "name1", "name2", "name3", "name4", "name5", "name6", "name7" };
return names.Count(name => name.Contains("name"));
}
}
ج) دریافت لیستی از عناصر شروع شده با یک عبارت
در اینجا نیز دو روش متداول و استفاده از LINQ بررسی شده است.
using System.Linq;
using System.Collections.Generic;
public class CLinq
{
public static List<string> GetListNormal()
{
List<string> sampleList = new List<string>() { "A1", "A2", "P1", "P10", "B1", "B@", "J30", "P12" };
List<string> result = new List<string>();
foreach (var item in sampleList)
{
if (item.StartsWith("P"))
result.Add(item);
}
return result;
}
public static List<string> GetList()
{
List<string> sampleList = new List<string>() { "A1", "A2", "P1", "P10", "B1", "B@", "J30", "P12" };
return sampleList.Where(x => x.StartsWith("P")).ToList();
}
}
و در حالت کلی، اکثر حلقههای foreach متداول را میتوان با نمونههای خواناتر کوئریهای LINQ معادل، جایگزین کرد.
ابتدا یک فایل جاوااسکریپ به نام module1 ایجاد میکنیم . در این فایل ابتدا ماژول خود را به Angular معرفی کرده و سپس با استفاده از دستور factory سرویس مورد نظر برای به اشتراک گذاری داده را میسازیم:
var app = angular.module('myApp', []); app.factory('BookData', function () { var books = [ { code: 1, name: 'book1', }, { code: 2, name: 'book2', }, { code: 3, name: 'book3', }, { code: 4, name: 'book4', }, { code: 5, name: 'book5', } ]; return books; });
app.controller('controller1', function ($scope, BookData) { $scope.books = BookData; });
app.controller('controller2', function ($scope, BookData) { $scope.books = BookData; });
<script type="text/javascript" src="~/scripts/app/controller1.js"></script>
<div ng-app="myApp"> <div ng-controller="controller1"> <p>Data from controller1</p> <table> <tr ng-repeat="book in books"> <td> {{book.code}} </td> <td> {{book.name}} </td> </tr> </table> </div> <div ng-controller="controller2"> <p>Data from controller2</p> <table> <tr ng-repeat="book in books"> <td> {{book.code}} </td> <td> {{book.name}} </td> </tr> </table> </div> </div>
با استفاده از ng-repeat به راحتی در بین آرایه کتابها پیمایش کرده و لیست مورد نظر در صفحه نمایش داده میشود. (توضیحات مربوط به ng-repeat و {{}} در پست قبلی شرح داده شده است). خروجی به صورت زیر خواهد بود. واضح است که اطلاعات نمایش داده شده توسط هر دو کنترلر به دلیل استفاده از منبع داده ای یکسان، به یک شکل خواهد بود.
using System.Threading.Tasks; namespace Async05 { class Program { static void Main(string[] args) { var res = doSomethingAsync().Result; } static async Task<int> doSomethingAsync() { await Task.Delay(1); return 1; } } }
البته باید دقت داشت، زمانیکه از خاصیت Result استفاده میشود، این متد همزمان عمل خواهد کرد و نه غیرهمزمان (ترد جاری را بلاک میکند؛ یکی از موارد مجاز استفاده از آن در متد Main برنامههای کنسول است). همچنین اگر در متد doSomethingAsync استثنایی رخ داده باشد، این استثناء زمان استفاده از Result، به صورت یک AggregateException مجددا صادر خواهد شد. وجود کلمهی Aggregate در اینجا به علت امکان استفادهی تجمعی و ترکیب چندین Task باهم و داشتن چندین شکست و استثنای ممکن است.
همچنین اگر از کلمهی کلیدی await بر روی یک faulted task استفاده کنیم، AggregateException صادر نمیشود. در این حالت کامپایلر AggregateException را بررسی کرده و آنرا تبدیل به یک Exception متداول و معمول کدهای دات نت میکند. به عبارتی سعی شدهاست در این حالت، رفتار کدهای async را شبیه به رفتار کدهای متداول همزمان شبیه سازی کنند.
یک مثال
در اینجا توسط متد getTitleAsync، اطلاعات یک صفحهی وب به صورت async دریافت شده و سپس عنوان آن استخراج میشود. در متد showTitlesAsync نیز از آن استفاده شده و در طی یک حلقه، چندین وب سایت مورد بررسی قرار خواهند گرفت. چون متد getTitleAsync از نوع async تعریف شدهاست، فراخوان آن نیز باید async تعریف شود تا بتوان از واژهی کلیدی await برای کار با آن استفاده کرد.
نهایتا در متد Main برنامه، وظیفهی غیرهمزمان showTitlesAsync اجرا شده و تا پایان عملیات آن صبر میشود. چون خروجی آن از نوع Task است و نه Task of T، در اینجا دیگر خاصیت Result قابل دسترسی نیست. متد Wait نیز ترد جاری را همانند خاصیت Result بلاک میکند.
using System; using System.Collections.Generic; using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace Async05 { class Program { static void Main(string[] args) { var task = showTitlesAsync(new[] { "http://www.google.com", "https://www.dntips.ir" }); task.Wait(); Console.WriteLine(); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } static async Task showTitlesAsync(IEnumerable<string> urls) { foreach (var url in urls) { var title = await getTitleAsync(url); Console.WriteLine(title); } } static async Task<string> getTitleAsync(string url) { var data = await new WebClient().DownloadStringTaskAsync(url); return getTitle(data); } private static string getTitle(string data) { const string patternTitle = @"(?s)<title>(.+?)</title>"; var regex = new Regex(patternTitle); var mc = regex.Match(data); return mc.Groups.Count == 2 ? mc.Groups[1].Value.Trim() : string.Empty; } } }
برنامه را در حالت عدم اتصال به اینترنت اجرا کنید. استثنای صادر شده، در متد task.Wait ظاهر میشود (چون متدهای async ترد جاری را خالی کردهاند):
و اگر در اینجا بر روی لینک View details کلیک کنیم، در inner exception حاصل، خطای واقعی قابل مشاهده است:
همانطور که ملاحظه میکنید، استثنای صادر شده از نوع System.AggregateException است. به این معنا که میتواند حاوی چندین استثناء باشد که در اینجا تعداد آنها با عدد یک مشخص شدهاست. بنابراین در این حالات، بررسی inner exception را فراموش نکنید.
در ادامه داخل حلقهی foreach متد showTitlesAsync، یک try/catch قرار میدهیم:
static async Task showTitlesAsync(IEnumerable<string> urls) { foreach (var url in urls) { try { var title = await getTitleAsync(url); Console.WriteLine(title); } catch (Exception ex) { Console.WriteLine(ex); } } }
System.Net.WebException: The remote server returned an error: (502) Bad Gateway. System.Net.WebException: The remote server returned an error: (502) Bad Gateway. Press any key to exit...
مدیریت تمام inner exceptionهای رخ داده در پردازشهای موازی
همانطور که عنوان شد، await تنها یک استثنای حاصل از Task در حال اجرا را به کد فراخوان بازگشت میدهد. در این حالت اگر این Task، چندین شکست را گزارش دهد، چطور باید برای دریافت تمام آنها اقدام کرد؟ برای مثال استفاده از Task.WhenAll میتواند شامل چندین استثنای حاصل از چندین Task باشد، ولی await تنها اولین استثنای دریافتی را بازگشت میدهد. اما اگر از خاصیتی مانند Result یا متد Wait استفاده شود، یک AggregateException حاصل تمام استثناءها را دریافت خواهیم کرد. بنابراین هرچند await تنها اولین استثنای دریافتی را بازگشت میدهد، اما میتوان به Taskهای مرتبط مراجعه کرد و سپس بررسی نمود که آیا استثناهای دیگری نیز وجود دارند یا خیر؟
برای نمونه در مثال فوق، حلقهی foreach تشکیل شده آنچنان بهینه نیست. از این جهت که هر بار تنها یک سایت را بررسی میکند، بجای اینکه مانند مرورگرها چندین ترد را به یک یا چند سایت باز کرده و نتایج را دریافت کند.
البته انجام کارها به صورت موازی همیشه ایدهی خوبی نیست ولی حداقل در این حالت خاص که با یک یا چند سرور راه دور کار میکنیم، درخواستهای همزمان دریافت اطلاعات، سبب کارآیی بهتر برنامه و بالا رفتن سرعت اجرای آن میشوند. اما مثلا در حالتیکه با سخت دیسک سیستم کار میکنیم، اجرای موازی کارها نه تنها کمکی نخواهد کرد، بلکه سبب خواهد شد تا مدام drive head در مکانهای مختلفی مشغول به حرکت شده و در نتیجه کارآیی آن کاهش یابد.
برای ترکیب چندین Task، ویژگی خاصی به زبان سیشارپ اضافه نشده، زیرا نیازی نبوده است. برای این حالت تنها کافی است از متد Task.WhenAll، برای ساخت یک Task مرکب استفاده کرد. سپس میتوان واژهی کلیدی await را بر روی این Task مرکب فراخوانی کرد.
همچنین میتوان از متد ContinueWith یک Task مرکب نیز برای جلوگیری از بازگشت صرفا اولین استثنای رخ داده توسط کامپایلر، استفاده کرد. در این حالت امکان دسترسی به خاصیت Result آن به سادگی میسر میشود که حاوی AggregateException کاملی است.
اعتبارسنجی آرگومانهای ارسالی به یک متد async
زمان اعتبارسنجی آرگومانهای ارسالی به متدهای async مهم است. بعضی از مقادیر را نمیتوان بلافاصله اعتبارسنجی کرد؛ مانند مقادیری که نباید نال باشند. تعدادی دیگر نیز پس از انجام یک Task زمانبر مشخص میشوند که معتبر بودهاند یا خیر. همچنین فراخوانهای این متدها انتظار دارند که متدهای async بلافاصله بازگشت داده شده و ترد جاری را خالی کنند. بنابراین اعتبارسنجیهای آنها باید با تاخیر انجام شود. در این حالات، دو نوع استثنای آنی و به تاخیر افتاده را شاهد خواهیم بود. استثنای آنی زمان شروع به کار متد صادر میشود و استثنای به تاخیر افتاده در حین دریافت نتایج از آن دریافت میگردد. باید دقت داشت کلیه استثناهای صادر شده در بدنهی یک متد async، توسط کامپایلر به عنوان یک استثنای به تاخیر افتاده گزارش داده میشود. بنابراین اعتبارسنجیهای آرگومانها را بهتر است در یک متد سطح بالای غیر async انجام داد تا بلافاصله بتوان استثناءهای حاصل را دریافت نمود.
از دست دادن استثناءها
فرض کنید مانند مثال قسمت قبل، دو وظیفهی async آغاز شده و نتیجهی آنها پس از await هر یک، با هم جمع زده میشوند. در این حالت اگر کل عملیات را داخل یک قطعه کد try/catch قرار دهیم، اولین await ایی که یک استثناء را صادر کند، صرفنظر از وضعیت await دوم، سبب اجرای بدنهی catch میشود. همچنین انجام این عملیات بدین شکل بهینه نیست. زیرا ابتدا باید صبر کرد تا اولین Task تمام شود و سپس دومین Task شروع گردد و به این ترتیب پردازش موازی Taskها را از دست خواهیم داد. در یک چنین حالتی بهتر است از متد await Task.WhenAll استفاده شود. در اینجا دو Task مورد نیاز، تبدیل به یک Task مرکب میشوند. این Task مرکب تنها زمانی خاتمه مییابد که هر دوی Task اضافه شده به آن، خاتمه یافته باشند. به این ترتیب علاوه بر اجرای موازی Taskها، امکان دریافت استثناءهای هر کدام را نیز به صورت تجمعی خواهیم داشت.
مشکل! همانطور که پیشتر نیز عنوان شد، استفاده از await در اینجا سبب میشود تا کامپایلر تنها اولین استثنای دریافتی را بازگشت دهد و نه یک AggregateException نهایی را. روش حل آنرا نیز عنوان کردیم. در این حالت بهتر است از متد ContinueWith و سپس استفاده از خاصیت Result آن برای دریافت کلیه استثناءها کمک گرفت.
حالت دوم از دست دادن استثناءها زمانیاست که یک متد async void را ایجاد میکنید. در این حالات بهتر است از یک Task بجای بازگشت void استفاده شود. تنها علت وجودی async voidها، استفاده از آنها در روالهای رویدادگردان UI است (در سایر حالات code smell درنظر گرفته میشود).
public async Task<double> GetSum2Async() { try { var task1 = GetNumberAsync(); var task2 = GetNumberAsync(); var compositeTask = Task.WhenAll(task1, task2); await compositeTask.ContinueWith(x => { }); return compositeTask.Result[0] + compositeTask.Result[1]; } catch (Exception ex) { //todo: log ex throw; } }
در این مثال دیگر مانند مثال قسمت قبل
public async Task<double> GetSumAsync() { var leftOperand = await GetNumberAsync(); var rightOperand = await GetNumberAsync(); return leftOperand + rightOperand; }
با کمک متد Task.WhenAll ترکیب آنها ایجاد و سپس با فراخوانی await، سبب اجرای موازی چندین Task با هم شدهایم.
مدیریت خطاهای مدیریت نشده
ابتدا مثال زیر را در نظر بگیرید:
using System; using System.Threading.Tasks; namespace Async01 { class Program { static void Main(string[] args) { Test2(); Test(); Console.ReadLine(); GC.Collect(); GC.WaitForPendingFinalizers(); Console.ReadLine(); } public static async Task Test() { throw new Exception(); } public static async void Test2() { throw new Exception(); } } }
اگر برنامه را کامپایل کنید، کامپایلر بر روی سطر فراخوانی متد Test اخطار زیر را صادر میکند. البته برنامه بدون مشکل کامپایل خواهد شد.
Warning 1 Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
همچنین اگر برنامه را اجرا کنید استثنای صادر شده در متد async void سبب کرش برنامه میشود؛ اما نه استثنای صادر شده در متد async Task. متدهای async void چون دارای Synchronization Context نیستند، استثنای صادره را به Thread pool برنامه صادر میکنند. به همین جهت در همان لحظه نیز سبب کرش برنامه خواهند شد. اما در حالت async Task به این نوع استثناءها اصطلاحا Unobserved Task Exception گفته شده و سبب بروز faulted state در Task تعریف شده میگردند.
برای مدیریت آنها در سطح برنامه باید در ابتدای کار و در متد Main، توسط TaskScheduler.UnobservedTaskException روال رخدادگردانی را برای مدیریت اینگونه استثناءها تدارک دید. زمانیکه GC شروع به آزاد سازی منابع میکند، این استثناءها نیز درنظر گرفته شده و سبب کرش برنامه خواهند شد. با استفاده از متد SetObserved همانند قطعه کد زیر، میتوان از کرش برنامه جلوگیری کرد:
using System; using System.Threading.Tasks; namespace Async01 { class Program { static void Main(string[] args) { TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; //Test2(); Test(); Console.ReadLine(); GC.Collect(); GC.WaitForPendingFinalizers(); Console.ReadLine(); } private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) { e.SetObserved(); Console.WriteLine(e.Exception); } public static async Task Test() { throw new Exception(); } public static async void Test2() { throw new Exception(); } } }
به عبارتی رفتار قطعه کد زیر در دات نت 4 و 4.5 متفاوت است:
Task.Factory.StartNew(() => { throw new Exception(); }); Thread.Sleep(100); GC.Collect(); GC.WaitForPendingFinalizers();
<configuration> <runtime> <ThrowUnobservedTaskExceptions enabled="true"/> </runtime> </configuration>
یک نکتهی تکمیلی: ممکن است عبارات lambda مورد استفاده، از نوع async void باشد.
همانطور که عنوان شد باید از async void منهای مواردی که کار مدیریت رویدادهای عناصر UI را انجام میدهند (مانند برنامههای ویندوز 8)، اجتناب کرد. چون پایان کار آنها را نمیتوان تشخیص داد و همچنین کامپایلر نیز اخطاری را در مورد استفاده ناصحیح از آنها بدون await تولید نمیکند (چون نوع void اصطلاحا awaitable نیست). به علاوه بروز استثناء در آنها، بلافاصله سبب خاتمه برنامه میشود. بنابراین اگر جایی در برنامه متد async void وجود دارد، قرار دادن try/catch داخل بدنهی آن ضروری است.
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState) { try { ClickMeButton.Tapped += async (sender, args) => { throw new Exception(); }; } catch (Exception ex) { // This won’t catch exceptions! TextBlock1.Text = ex.Message; } }
public delegate void TappedEventHandler(object sender, TappedRoutedEventArgs e);
تهیه گزارش سرفصل های حسابداری
با تشکر.
SASS #1
SASS چیست؟
SASS مخفف Syntactically Awesome Style Sheets است که توسط آقای Hampton Catlin طراحی و ایجاد شده است و همانند CoffeeScript که پس از کامپایل به جاوااسکریپت تبدیل میشد، SASS نیز پس از کامپایل به CSS تبدیل میشود. SASS با استفاده از متغیرها، mixins، ارث بری و قوانین تودرتو، CSS را با مهارت زیادی در بهترین حالت تولید میکند.
SASS باعث کمتر نوشتن کد CSS، سبب افزایش خوانایی و دستکاری کردن راحتتر و پویای آن میشود. این مساله راهی عالی برای نوشتن کدهای CSS کاربردیتر است و میتواند سرعت گردش کار هر توسعه دهنده و یا طراح وب را افزایش دهد.
وقتی اولین بار SASS عرضه شد، syntax آن تفاوت قابل توجهی با CSS داشت (پسوند فایلهای آن SASS. است) که به جای نوشتن براکتها، از تورفتگی استفاده میشد و دیگر نیازی به نوشتن ";" نبود. البته با عدم استقبال از این syntax مواجه شد و با عرضهی نسخه 3 SASS، (که پسوند فایلهای آن SCSS. است) syntax آن بسیار شبیه به CSS شد؛ البته با همهی ویژگیهای SASS.
برای مثال کد CSS زیر را میخواهیم به دو روش بنویسیم:
header { margin: 0; padding: 0; color: #fff; }
$color: #fff; header { margin: 0; padding:0; color: $color; }
$color: #fff header margin: 0 padding: 0 color: $color
توجه: syntax ایی که در این سری آموزشی با آن کار میکنیم SCSS. است.
کامپایل کردن SASS
روشهای مختلفی برای کامپایل فایلهای SASS وجود دارند:- روش اصلی استفاده از SASS در Ruby است که پس از نصب Ruby و اجرای فرمان SASS ،gem install sass نصب میشود و برای کامپایل، اجرای فرمان زیر:
sass myfile.scss myfile.css
- استفاده از برنامههای گرافیکی مانند Hammer , CodeKit و Compass.
- استفاده از برنامههای رایگان مانند libsass که با یک کامپایلر سریع نوشته شده با ++C/C است و همچنین میتوانید libsass را از طریق NPM با node-sass نصب کنید.
npm install node-sass
- استفاده از افزونه Web Essentials در Visual Studio
خب سوالی که ممکن است برای شما پیش آمده باشد این است که باید از کدام یک از این روشها را استفاده کنیم؟
بستگی به این دارد که شما چه کاری را میخواهید انجام دهید.
- در صورتیکه بر روی یک پروژهی بزرگ با میزان کد زیاد کار میکنید، استفاده از Ruby SASS، کمی کند کار کامپایل را انجام میدهد.
- اگر بخواهید از libsass استفاده کنید، این مسئله وجود دارد که به طور %100 با قابلیتهای Ruby SASS برابری ندارد.
- در صورتیکه نمیخواهید از command line استفاده کنید، برنامههای گرافیکی گزینهای عالی هستند. شما میتوانید طوری تنظیم کنید که تمامی تغییراتی که در فایل SASS انجام میشود، به صورت خودکار کار کامپایل انجام شود.
- اگر هم فقط میخواهید کدی را که نوشتهاید تست کنید، میتوانید از ابزارهای آنلاین مانند SassMeister استفاده کنید.