مطالب
مدیریت استثناءها در Blazor Server - قسمت دوم
در قسمت اول دیدیم که توسط Error boundary می‌توان استثناءها را در Blazor مدیریت کرد؛ اما اگر بخواهیم قدری سفارشی‌تر عمل کرده و علاوه بر نمایش پیغام خطای مناسب به صورت جاوا اسکریپتی، استثنای رخ داده را لاگ کنیم چطور؟
خبر خوب اینکه این مهم نیز به راحتی امکان پذیر است؛ با استفاده از مفهوم CascadingValueها.
یک کامپوننت Error.razor به شکل زیر ایجاد می‌کنیم:
@using Microsoft.Extensions.Logging
@inject ILogger<Error> Logger
@inject IJSRuntime jsRuntime

<CascadingValue Value="this">
    @ChildContent
</CascadingValue>

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    public void ProcessError(Exception ex)
    {
        Logger.LogError("Error:ProcessError - Type: {Type} Message: {Message}", 
            ex.GetType(), ex.Message);
        jsRuntime.ToastrError("متاسفانه خطایی رخ داد");
        //StateHasChanged();
    }
}
همانطور که مشخص است این کامپوننت سفارشی توسط متد ProcessError می‌تواند خطاها را با استفاده از logger توکار Blazor لاگ نموده و پیغام خطای جاوااسکریپتی نمایش دهد. بدیهی است که می‌توان از لاگ کننده‌های دیگری نظیر Serilog، Elmah و حتی لاگ کننده‌های سفارشی دیگر نیز برای لاگ کردن در این متد بهره جست.
از StateHasChanged زمانی استفاده خواهد شد که متد پردازش خطا می‌خواهد به صورت مستقیم در رندر شدن رابط کاربری کامپوننتی که در آن استثنایی رخ داده‌است، دخالت کند. برای مثال زمانیکه می‌خواهیم تغییری در عناصر رندر شده صفحه، بعد از خطا ایجاد کنیم (رنگ دکمه ای عوض شود یا رنگ فونت برچسب یا تکست باکس یا ...).
حال کامپوننت App.razor را به شکل زیر ویرایش می‌نماییم:
<Error>
    <Router ...>
        ...
    </Router>
</Error>
در حقیقت کامپوننت Router را توسط کامپوننت سفارشی خودمان (Error) محصور می‌کنیم تا کامپوننت Error به صورت آبشاری به هر کامپوننت برنامه که Error را به صورت [ CascadingParameter ]  درنظر بگیرد منتقل شود.
حال فقط کافی است برای پردازش خطاها به شکل زیر در کامپوننت‌های دیگر عمل نمود:
@code {
    [CascadingParameter]
    public Error? Error { get; set; }

    private void CreatePost()
    {
        try
        {           
           throw new InvalidOperationException("پست ساخته نشد!");            
        }
        catch (Exception ex)
        {
            Error?.ProcessError(ex);
        }
    }
}
همینطور که ملاحظه می‌نمایید کامپوننت Error به عنوان یک CascadingParameter تعریف شده‌است و در یک بلاک try catch متد ProcessError کامپوننت Error صدا زده شده و استثنای صادر شده به آن ارسال شده‌است. در مثال من کامپوننت Error فقط یک متد پردازش خطا دارد. بدیهی است که این کامپوننت می‌تواند چندین متد پردازش خطای سفارشی دیگر نیز برای مقاصد مختلف داشته باشد.
مطالب
معرفی ELMAH

عموما کاربران نمی‌توانند گزارش خطای خوبی را ارائه بدهند و البته انتظاری هم از آنان نیست. تنها گزارشی که از یک کاربر دریافت می‌کنید این است: "برنامه کار نمی‌کنه!" و همین!
روش‌های متعددی برای لاگ کردن خطاهای یک برنامه ASP.Net موجود است؛ چه خودتان آن‌ها را توسعه دهید و یا از ASP.NET health monitoring استفاده کنید.
روش دیگری که این روزها در وبلاگ‌های متعددی در مورد آن مطلب منتشر می‌شود، استفاده از ELMAH است. (البته ELMAH به تازگی منتشر نشده ولی تا کیفیت محصولی به عموم ثابت شود مدتی زمان می‌برد)

ELMAH یک ماژول رایگان و سورس باز لاگ کردن خطاهای مدیریت نشده برنامه‌های ASP.Net‌ است. برای استفاده از این ماژول نیازی نیست تا تغییری در برنامه خود ایجاد کنید یا حتی آن‌را کامپایل مجدد نمائید. یک فایل dll‌ دارد به همراه کمی تغییر در web.config برنامه جهت معرفی آن و این تمام کاری است که برای برپایی آن لازم است صورت گیرد. این ماژول تمامی خطاهای مدیریت نشده‌ی برنامه شما را لاگ کرده (در حافظه سرور، در یک فایل xml ، در یک دیتابیس اس کیوال سرور یا اوراکل ، در یک دیتابیس اکسس و یا در یک دیتابیس اس کیوال لایت) و برای مرور آن‌ها یک صفحه‌ی وب سفارشی یا فیدی مخصوص را نیز در اختیار شما قرار می‌دهد. همچنین این قابلیت را هم دارد که به محض بروز خطایی یک ایمیل را نیز به شما ارسال نماید.

با توجه به این‌که این ماژول در Google code قرار گرفته احتمالا دسترسی به آن مشکل خواهد بود. سورس و فایل‌های کامپایل شده آن‌را از آدرس‌های زیر نیز می‌توان دریافت نمود:
( + و + و +)

نحوه استفاده از ELMAH :

برای استفاده از ELMAH دو کار را باید انجام دهید:
الف) کپی کردن فایل Elmah.dll در پوشه bin برنامه
ب) تنظیم وب کانفیگ برنامه

بهترین مرجع برای آشنایی با نحوه بکار گیری این ماژول، مراجعه به فایل web.config موجود در پوشه samples آن است. بر اساس این فایل نمونه:

- ابتدا باید configSections آن را به وب کانفیگ خود اضافه کنید.

- سپس تگ elmah باید اضافه شود. در این تگ موارد زیر مشخص می‌شوند:
الف) آیا خطاها توسط آدرس elmah.axd توسط کاربران راه دور قابل مشاهده شود یا خیر.
ب) خطاها کجا ذخیره شوند؟ موارد زیر پشتیبانی می‌شوند:
دیتابیس‌های اس کیوال سرور ، اوراکل ، حافظه سرور، فایل‌های xml ، دیتابیس SQLite ، دیتابیس اکسس و یا دیتابیسی از نوع VistaDB
ج) آیا خطاها ایمیل هم بشوند؟ اگر بلی، تگ مربوطه را تنظیم کنید.
د) آیا خطاها به اکانت twitter شما نیز ارسال شوند؟

- در ادامه تگ مربوط به معرفی این httpModules باید تنظیم شود.

- سپس httpHandlers ایی به نام elmah.axd که جهت مرور خطاها می‌توان از آن استفاده نمود معرفی می‌گردد.

- از IIS7 استفاده می‌کنید؟ قسمت system.webServer را نیز باید اضافه نمائید.

- و در آخر نحوه‌ی دسترسی به elmah.axd مشخص می‌شود. اگر اجازه دسترسی از راه دور را داده باشید، به این طریق می‌شود دسترسی را فقط به کاربران مجاز و تعیین اعتبار شده، اعطاء کرد و یا به نقشی مشخص مانند ادمین و غیره.

برای نمونه، اگر بخواهید از دیتابیس SQLite جهت ذخیره سازی خطاهای حاصل شده استفاده نمائید و نیز از ارسال ایمیل صرفنظر کنید، وب کانفیگ برنامه شما باید به شکل زیر تغییر یابد:
<?xml version="1.0"?>

<configuration>
<configSections>
<sectionGroup name="elmah">
<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
<section name="errorTweet" requirePermission="false" type="Elmah.ErrorTweetSectionHandler, Elmah"/>
</sectionGroup>
</configSections>

<elmah>
<security allowRemoteAccess="1" />
<errorLog type="Elmah.SQLiteErrorLog, Elmah" connectionStringName="cn1" />
</elmah>


<appSettings/>

<connectionStrings>
<add name="cn1" connectionString="data source=~/ErrorsLog/Errors.db" />
</connectionStrings>

<system.web>
<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
</httpModules>

<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>

<compilation debug="true"/>

<authentication mode="Windows" />
</system.web>

</configuration>
در اینجا یک پوشه جدید به نام ErrorsLog را باید به ریشه سایت خود اضافه کنید (یا هر نام دلخواه دیگری که در قسمت connectionStrings باید تنظیم شود). فایل Errors.db به صورت خودکار ایجاد خواهد شد. بدیهی است کاربر ASP.net باید دسترسی write بر روی این پوشه داشته باشد تا عملیات ثبت خطاها با موفقیت صورت گیرد. همچنین فایل System.Data.SQLite.DLL نیز باید در پوشه bin برنامه شما کپی شود.
ساده‌ترین تنظیم این ماژول استفاده از حالت xml است که به ازای هر خطا یک فایل xml را تولید کرده و نیاز به اسمبلی دیگری بجز ماژول مربوطه نخواهد داشت.

تذکر:
از لحاظ امنیتی مثال فوق توصیه نمی‌شود زیرا allowRemoteAccess آن 1 است و قسمت authorization ذکر نشده است. این مثال فقط جهت راه اندازی و آزمایش اولیه ارائه گردیده است. (همچنین بهتر است این نام پیش فرض را به نامی دیگر مثلا myloggermdl.axd تغییر داده و در قسمت httpHandlers تنظیم نمائید. سپس این نام را به تگ location نیز اضافه کنید)

اکنون برای مشاهده خروجی این ماژول به انتهای آدرس سایت خود، elmah.axd را اضافه کرده و سپس enter کنید:



همانطور که در تصویر مشخص است، تمامی خطاهای لاگ شده گزارش داده شده‌اند. همچنین دو نوع فید به همراه امکان دریافت خطاها به صورت CSV نیز موجود است. با کلیک بر روی لینک details ، صفحه‌ی بسیار ارزنده‌ای ارائه می‌شود که تقریبا نحوه‌ی وقوع ماجرا را بازسازی می‌کند.
نکته‌ی مهمی که در صفحه‌ی جزئیات ارائه می‌شود (علاوه بر stack trace‌ و مشخصات کاربر)، مقادیر تمامی فیلدهای یک صفحه هنگام بروز خطا است (قسمت Raw/Source data in XML or in JSON در این صفحه) :



به این صورت دیگر نیازی نیست از کاربر بپرسید چه چیزی را وارد کرده بودید که خطا حاصل شد. دقیقا مقادیر فیلدهای همان صفحه‌ی زمان بروز خطا نیز برای شما لاگ می‌گردد.


نکته:
ماژول SQLite ایی که به همراه مجموعه ELMAH ارائه می‌شود 32 بیتی کامپایل شده (64 بیتی آن نیز موجود است که باید از آن در صورت لزوم استفاده شود). بنابراین برای اینکه در یک سرور 64 بیتی به مشکل برنخورید و خطای BadImageFormat را دریافت نکنید نیاز است تا به این نکته دقت داشت.

برای مطالعه بیشتر:

Error Logging Modules And Handlers
Sending ELMAH Errors Via GMail
Exception-Driven Development
Using HTTP Modules and Handlers to Create Pluggable ASP.NET Components

مطالب
از سرگیری مجدد، لغو درخواست و سعی مجدد دریافت فایل‌های حجیم توسط HttpClient
پس از آشنایی با «نکات دریافت فایل‌های حجیم توسط HttpClient»، در ادامه می‌توان سه قابلیت مهم از سرگیری مجدد، لغو درخواست و سعی مجدد دریافت فایل‌های حجیم را با HttpClient، همانند برنامه‌های download manager نیز پیاده سازی کرد.


از سرگیری مجدد درخواست ارسالی توسط HttpClient

یک نمونه از سرگیری مجدد درخواست را در مطلب «اضافه کردن قابلیت از سرگیری مجدد (resume) به HttpWebRequest» پیشتر در این سایت مطالعه کرده‌اید. اصول کلی آن نیز در اینجا صادق است. HTTP 1.1 از مفهوم range headers‌، برای دریافت پاسخ‌های جزئی پشتیبانی می‌کند. به این ترتیب در صورت پیاده سازی چنین قابلیتی در برنامه‌ی سمت سرور، می‌توان دریافت بازه‌ای از بایت‌ها را بجای دریافت فایل از ابتدا، از سرور درخواست کرد. به یک چنین قابلیتی Resume و یا از سرگیری مجدد گرفته می‌شود و درحین دریافت فایل‌های حجیم بسیار حائز اهمیت است.
var fileInfo = new FileInfo(outputFilePath);
long resumeOffset = 0;
if (fileInfo.Exists)
{
    resumeOffset = fileInfo.Length;
}
if (resumeOffset > 0)
{
    _client.DefaultRequestHeaders.Range = new RangeHeaderValue(resumeOffset, null);
}
در اینجا نحوه‌ی تنظیم یک RangeHeader را مشاهده می‌کنید. ابتدا نیاز است بررسی کنیم آیا فایل دریافتی از پیش موجود است؟ آیا قسمتی از این درخواست پیشتر دریافت شده و محتوای آن هم اکنون به صورت ذخیره شده وجود دارد؟ اگر بله، درخواست دریافت این فایل را بر اساس اندازه‌ی دریافتی فعلی آن، به سرور ارائه می‌کنیم.

یک نکته: تمام وب سرورها و یا برنامه‌های وب از یک چنین قابلیتی پشتیبانی نمی‌کنند.
روش تشخیص آن نیز به صورت زیر است:
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
if (response.Headers.AcceptRanges == null && resumeOffset > 0)
{
    // resume not supported, starting over
}
پس از خواندن هدر درخواست، اگر خاصیت AcceptRanges آن نال بود، یعنی قابلیت از سرگیری مجدد را ندارد. در این حالت باید فایل موجود فعلی را حذف و یا از نو (FileMode.CreateNew) بازنویسی کرد (بجای حالت FileMode.Append).


لغو درخواست ارسالی توسط HttpClient

پس از شروع غیرهمزمان client.GetAsync می‌توان متد CancelPendingRequests آن‌را فراخوانی کرد تا کلیه درخواست‌های مرتبط با این client لغو شوند. اما این متد صرفا برای حالت پیش‌فرض client.GetAsync که دریافت هدر + محتوا است کار می‌کند (یعنی حالت HttpCompletionOption.ResponseContentRead). اگر همانند نکات بررسی شده‌ی در مطلب «دریافت فایل‌های حجیم توسط HttpClient» صرفا درخواست خواندن هدر را بدهیم (HttpCompletionOption.ResponseHeadersRead)، چون کنترل ادامه‌ی بحث را خودمان بر عهده گرفته‌ایم، لغو آن نیز به عهده‌ی خودمان است و متد CancelPendingRequests بر روی آن تاثیر نخواهد داشت.
این نکته در مورد تنظیم خاصیت TimeOut نیز صادق است. این خاصیت فقط زمانیکه دریافت کل هدر + محتوا توسط متد GetAsync مدیریت شوند، تاثیر گذار است.
بنابراین درحالتیکه نیاز به کنترل بیشتر است، هرچند فراخوانی متد CancelPendingRequests ضرری ندارد، اما الزاما سبب قطع کل درخواست نمی‌شود و باید این لغو را به صورت ذیل پیاده سازی کرد:
ابتدا یک منبع توکن لغو عملیات را به صورت ذیل ایجاد می‌کنیم:
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
سپس، متد لغو برنامه، تنها کافی است متد Cancel این cts را فراخوانی کند؛ تا عملیات دریافت فایل خاتمه یابد.
پس از این فراخوانی (()cts.Cancel)، نحوه‌ی واکنش به آن به صورت ذیل خواهد بود:
var result = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, _cts.Token);
using(var stream = await result.Content.ReadAsStreamAsync())
{
   byte[] buffer = new byte[80000];
   int bytesRead;
   while((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0 &&
         !_cts.IsCancellationRequested)
   {
      outputStream.Write(buffer, 0, bytesRead);
   }
}
در اینجا از cts.Token به عنوان پارامتر سوم متد GetAsync استفاده شده‌است. همچنین قسمت ثبت اطلاعات دریافتی، در استریم خروجی نیز به صورت یک حلقه درآمده‌است تا بتوان خاصیت IsCancellationRequested این توکن لغو را بررسی کرد و نسبت به آن واکنش نشان داد.


سعی مجدد درخواست ارسالی توسط HttpClient

یک روش پیاده سازی سعی مجدد درخواست شکست خورده، توسط کتابخانه‌ی Polly است. روش دیگر آن نیز به صورت ذیل است:
public async Task DownloadFileAsync(string url, string outputFilePath, int maxRequestAutoRetries)
{
            var exceptions = new List<Exception>();

            do
            {
                --maxRequestAutoRetries;
                try
                {
                    await doDownloadFileAsync(url, outputFilePath);
                }
                catch (TaskCanceledException ex)
                {
                    exceptions.Add(ex);
                }
                catch (HttpRequestException ex)
                {
                    exceptions.Add(ex);
                }
                catch (Exception ex) when (isNetworkError(ex))
                {
                    exceptions.Add(ex);
                }

                // Wait a bit and try again later
               if (exceptions.Any())  await Task.Delay(2000, _cts.Token);
            } while (maxRequestAutoRetries > 0 &&
                     !_cts.IsCancellationRequested);

            var uniqueExceptions = exceptions.Distinct().ToList();
            if (uniqueExceptions.Any())
            {
                if (uniqueExceptions.Count() == 1)
                    throw uniqueExceptions.First();
                throw new AggregateException("Could not process the request.", uniqueExceptions);
            }
}

private static bool isNetworkError(Exception ex)
{
    if (ex is SocketException || ex is WebException)
        return true;
    if (ex.InnerException != null)
        return isNetworkError(ex.InnerException);
    return false;
}
در اینجا متد doDownloadFileAsync، پیاده سازی همان متدی است که در قسمت «لغو درخواست ارسالی توسط HttpClient» در مورد آن بحث شد. این قسمت دریافت فایل را در یک حلقه که حداقل یکبار اجرا می‌شود، قرار می‌دهیم. متد GetAsync استثناءهایی مانند TaskCanceledException (در حین TimeOut و یا فراخوانی متد CancelPendingRequests که البته همانطور که توضیح داده شد، بر روی روش کنترل Response تاثیری ندارند)، HttpRequestException پس از فراخوانی متد response.EnsureSuccessStatusCode (جهت اطمینان حاصل کردن از دریافت پاسخی بدون مشکل از طرف سرور) و یا SocketException و WebException را درصورت بروز مشکلی در شبکه، صادر می‌کند. نیازی به بررسی سایر استثناءها در اینجا نیست.
اگر یکی از این استثناءهای یاد شده رخ‌دادند، اندکی صبر کرده و مجددا درخواست را از ابتدا صادر می‌کنیم.
در پایان این سعی‌های مجدد، اگر استثنایی ثبت شده بود و همچنین عملیات نیز با موفقیت به پایان نرسیده بود، آن‌را به فراخوان صادر می‌کنیم.
مطالب
توسعه برنامه‌های Cross Platform با Xamarin Forms & Bit Framework - قسمت چهاردهم
در این قسمت قصد داریم به بررسی نحوه‌ی مدیریت خطاها و لاگ کردن آنها بپردازیم. همچنین در ادامه Analytics را در برنامه فعال می‌کنیم تا اطلاعاتی از دستگاه‌های کاربران و ... را به دست بیاوریم (اگر آخرین تغییرات XamApp را Pull/Clone کنید، حاوی تمامی تغییرات زیر است).

در برنامه‌های Native موبایل که شامل Xamarin Forms نیز می‌شود، هر خطایی می‌تواند باعث Crash کردن کل برنامه شود. در Bit Framework، تمامی خطاها مدیریت می‌شوند، تا جلوی بسته شدن برنامه گرفته شود و از این بابت مشکلی متوجه برنامه نمی‌شود و در کدها نیازی به نوشتن Try/Catch هم نیست. خطاها پس از رخ دادن، به کلاس BitExceptionHandler ارسال می‌شوند. شما می‌توانید از این کلاس ارث بری کنید و به شکل زیر حداقل از رخ دادن خطاها در Debug Mode مطلع شوید:
public class XamAppExceptionHandler : BitExceptionHandler
{
     public override void OnExceptionReceived(Exception exp, IDictionary<string, string> properties = null)
      {
#if DEBUG
            System.Diagnostics.Debugger.Break();
#endif
            base.OnExceptionReceived(exp, properties);
      }
}
سپس در ابتدای برنامه‌های Android/iOS/Windows یعنی در فایل‌های MainActivity.cs/AppDelegate.cs/App.xaml.cs کد زیر را برای معرفی کلاس خودتان استفاده کنید:
BitExceptionHandler.Current = new XamAppExceptionHandler();
به صورت پیش فرض، تمامی خطاها را در قسمت System.Diagnostics.Debugger.Break مشاهده خواهید نمود، ولی می‌توانید با باز کردن Exception Settings از منوی Debug > Windows > Exception Settings در ویژوال استودیو و تیک زدن Common Language Runtime Exceptions، تمامی خطاها را در جائیکه رخ می‌دهند، مشاهده کنید که به شما اطلاعات بیشتری نیز می‌دهد.


حال برای لاگ کردن این خطاها، می‌توانید از Microsoft's AppCenter استفاده کنید. استفاده از امکانات App Center رایگان بوده و برای استفاده‌ی در ایران محدودیتی ندارد. ابتدا در سایت مربوطه ثبت نام کنید و سپس سه بار Add New app را بزنید و به نام‌های XamApp_Windows، XamApp_iOS و XamApp_Android سه برنامه را بسازید و برای Android و iOS گزینه‌ی Xamarin را انتخاب و برای ویندوز نیز UWP را انتخاب کنید.

سپس پکیج‌های Microsoft.AppCenter.Crashes و Microsoft.AppCenter.Analytics را بر روی XamApp نصب نموده و کد زیر را در سه فایل AppDelegate.cs/MainActivity.cs/App.xaml.cs برای iOS/Android/Windows کپی کنید:

AppCenter.Start("copy-your-guid-key-for-iOS-Android-Windows-here", typeof(Crashes), typeof(Analytics));

برای هر یک از برنامه‌های Android/iOS/Windows یک Guid متفاوت دارید که در قسمت Getting Started در سایت App Center می‌توانید آنها را مشاهده کنید. هر بار که این کد را کپی می‌کنید، مقدار Guid درست را بگذارید.

برای گزارش کردن خطاهای برنامه، کافی است کد زیر را به XamAppExceptionHandler.cs که در ابتدای این قسمت در موردش صحبت کرده بودیم اضافه کنید:

Crashes.TrackError(exp, properties);

حال اگر برنامه را اجرا و شروع به تست کنید و یا آن را در اختیار تسترها و مشتری‌ها بگذارید، نه تنها گزارش تمامی خطاها و کرش‌ها را خواهید داشت که حتی آمار استفاده کننده‌های برنامه (شامل کشور و مشخصات دستگاه و ...) را نیز خواهید داشت.


حال در یک کد ده خطی، اگر در خط پنجم خطایی رخ دهد، اگر چه باعث بسته شدن برنامه نمی‌شود و لاگ نیز می‌شود، ولی در مواقعی خیلی خاص، شاید بخواهید در صورت رخ دادن خطا، چند خط کد بعدی کماکان اجرا شوند. در این حالت شما Try/Catch می‌نویسید که برای عبور کردن از خطا از آن استفاده کرده‌اید. در این صورت، ترجیحا آن را به شکل زیر بنویسید:

// code 1...
try
{
      // code 2...
}
catch (Exception ex)
{
                BitExceptionHandler.Current.OnExceptionReceived(ex, new Dictionary<string, string>
                {
                    { "SomeData", "2" }
                });
}
// code 3...

در این کد مثال، فرض کنیم که برخی اوقات در code 2 خطایی رخ می‌دهد که برای ما مهم نیست و می‌خواهیم حتی در صورت رخ دادن خطا، code 3 اجرا شود. توصیه می‌کنیم در این موارد که در برنامه خیلی هم نباید متداول باشد، لااقل خطا را با کمک کد BitExceptionHandler.Current.OnExceptionReceived لاگ کنید و همچنین با داشتن یک Dictionary می‌توانید حتی دیتای بیشتری را نیز به AppCenter فرستاده و در پرتال مربوطه مشاهده کنید.

به صورت کلی بهتر است از این نوع Try/Catch‌ها پرهیز کنید و حتی اگر جایی Catch ای نوشتید، در نهایت دوباره خطا را throw کنید.

try
{
     // some codes...
}
catch
{
      // Do something related to exception...
      // for example, show some alerts to the user.
      throw; // You don't need to call BitExceptionHandler.Current.OnExceptionReceived...
}

در مثال فوق، قصد داریم وقتی خطایی رخ داد، پیامی را به کاربر اطلاع دهیم. در این صورت، پس از نمایش پیام مربوطه، مجددا خطا را throw کنید. در این صورت، نیازی به فراخوانی BitExceptionHandler.Current.OnExceptionReceieved نیز نیست.


البته AppCenter در زمینه پابلیش کردن برنامه و همچنین Push Notification و ... نیز دارای امکاناتی هست که به موضوع این قسمت ارتباطی ندارند.

نظرات مطالب
عبارت using و نحوه استفاده صحیح از آن
با سلام خدمت شما جناب نصیری
با توجه به این مطلب که زیاد هم قدیمی نیست به نظر شما استفاده از using پیشنهاد میشود یا استفاده از try catch ؟
چون بنده در برنامه‌های کاربردی تحت وب بیشتر از using استفاده میکنم و از Elmah  هم برای ثبت خطاهای سیستم استفاده میکنم .
مطالب دوره‌ها
مدیریت استثناءها در حین استفاده از واژه‌های کلیدی async و await
زمانیکه یک متد async، یک Task یا Task of T (نسخه‌ی جنریک Task) را باز می‌گرداند، کامپایلر سی‌شارپ به صورت خودکار تمام استثناءهای رخ داده درون متد را دریافت کرده و از آن برای تغییر حالت Task به اصطلاحا faulted state استفاده می‌کند. همچنین زمانیکه از واژه‌ی کلیدی await استفاده می‌شود، کدهایی که توسط کامپایلر تولید می‌شوند، عملا مباحث Continue موجود در TPL یا Task parallel library معرفی شده در دات نت 4 را پیاده سازی می‌کنند و نهایتا نتیجه‌ی Task را در صورت وجود، دریافت می‌کند. زمانیکه نتیجه‌ی یک Task مورد استفاده قرار می‌گیرد، اگر استثنایی وجود داشته باشد، مجددا صادر خواهد شد. برای مثال اگر خروجی یک متد async از نوع Task of T باشد، امکان استفاده از خاصیتی به نام Result نیز برای دسترسی به نتیجه‌ی آن وجود دارد:
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;
        }
    }
}
در این مثال یکی از روش‌های استفاده از متدهای async را در یک برنامه‌ی کنسول مشاهده می‌کنید. هر چند خروجی متد doSomethingAsync از نوع Task of int است، اما مستقیما یک int بازگشت داده شده است. تبدیلات نهایی در اینجا توسط کامپایلر انجام می‌شود. همچنین نحوه‌ی استفاده از خاصیت Result را نیز در متد Main مشاهده می‌کنید.
البته باید دقت داشت، زمانیکه از خاصیت 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...
در اینجا دیگر خبری از AggregateException نبوده و استثنای واقعی رخ داده در متد await شده بازگشت داده شده‌است. کار واژه‌ی کلیدی await در اینجا، بررسی استثنای رخ داده در متد async فراخوانی شده و بازگشت آن به جریان متداول متد جاری است؛ تا نتیجه‌ی عملیات همانند یک کد کامل همزمان به نظر برسد. به این ترتیب کامپایلر توانسته است رفتار بروز استثناءها را در کدهای همزمان و غیرهمزمان یک دست کند. دقیقا مانند حالتی که یک متد معمولی در این بین فراخوانی شده و استثنایی در آن رخ داده‌است.


مدیریت تمام 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;
            }
        }
در مثال فوق، نحوه‌ی ترکیب دو Task را توسط Task.WhenAll جهت اجرای موازی و سپس اعمال نکته‌ی یک ContinueWith خالی و در ادامه استفاده از Result نهایی را جهت دریافت تمامی استثناءهای حاصل، مشاهده می‌کنید.
در این مثال دیگر مانند مثال قسمت قبل
        public async Task<double> GetSumAsync()
        {
            var leftOperand = await GetNumberAsync();
            var rightOperand = await GetNumberAsync();

            return leftOperand + rightOperand;
        }
هر بار صبر نشده‌است تا یک Task تمام شود و سپس Task بعدی شروع گردد.
با کمک متد 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();
        }
    }
}
در این مثال دو متد که یکی async Task و دیگری async void است، تعریف شده‌اند.
اگر برنامه را کامپایل کنید، کامپایلر بر روی سطر فراخوانی متد 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 صادر نمی‌شود. بنابراین ممکن است جایی در کدها، فراخوانی await فراموش شود. اگر خروجی متد شما ازنوع Task و مشتقات آن باشد، کامپایلر حتما اخطاری را جهت رفع آن گوشزد خواهد کرد؛ اما نه در مورد متدهای void که صرفا جهت کاربردهای UI و روال‌های رخدادگردان آن طراحی شده‌اند.
همچنین اگر برنامه را اجرا کنید استثنای صادر شده در متد 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.5 به این شکل تغییر کرده است تا کار با متدهای async ساده‌تر شود. در دات نت 4، یک چنین استثناءهای مدیریت نشده‌ای،‌بلافاصله سبب بروز استثناء و کرش برنامه می‌شدند.
به عبارتی رفتار قطعه کد زیر در دات نت 4 و 4.5 متفاوت است:
Task.Factory.StartNew(() => { throw new Exception(); });

Thread.Sleep(100);
GC.Collect();
GC.WaitForPendingFinalizers();
در دات نت 4  اگر این برنامه را خارج از VS.NET اجرا کنیم، برنامه کرش می‌کند؛ اما در دات نت 4.5 خیر و آن‌ها به UnobservedTaskException یاد شده هدایت خواهند شد. اگر می‌خواهید این رفتار را به همان حالت دات نت 4 تغییر دهید، تنظیم زیر را به فایل config برنامه اضافه کنید:
 <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;
    }
}
در این مثال خاص ویندوز 8، شاید به نظر برسد که try/catch تعریف شده سبب مهار استثنای صادر شده می‌شود؛ اما خیر!
 public delegate void TappedEventHandler(object sender, TappedRoutedEventArgs e);
امضای متد TappedEventHandler از نوع delegate void است. بنابراین try/catch را باید داخل بدنه‌ی روال رویدادگردان تعریف شده قرار داد و نه خارج از آن.
مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
اگر به دو مطلب استفاده از Quartz.Net (^ و ^) و خصوصا نظرات آن دقت کرده باشید به این نتیجه خواهید رسید که ... این کتابخانه‌ی در اصل جاوایی گنگ طراحی شده‌است. در سایت جاری برای انجام کارهای زمانبندی شده (مانند ارسال ایمیل‌های روزانه خلاصه مطالب، تهیه خروجی PDF و XML سایت، تبدیل پیش نویس‌ها به مطالب، بازسازی ایندکس‌های جستجو و امثال آن) از یک Thread timer استفاده می‌شود که حجم نهایی کتابخانه‌ی محصور کننده و مدیریت کننده‌ی وظایف آن جمعا 8 کیلوبایت است؛ متشکل از ... سه کلاس. در ادامه کدهای کامل و نحوه‌ی استفاده از آن را بررسی خواهیم کرد.


دریافت کتابخانه DNT Scheduler و مثال آن

DNTScheduler 
در این بسته، کدهای کتابخانه‌ی DNT Scheduler و یک مثال وب فرم را، ملاحظه خواهید کرد. از این جهت که برای ثبت وظایف این کتابخانه، از فایل global.asax.cs استفاده می‌شود، اهمیتی ندارد که پروژه‌ی شما وب فرم است یا MVC. با هر دو حالت کار می‌کند.



نحوه‌ی تعریف یک وظیفه‌ی جدید

کار با تعریف یک کلاس و پیاده سازی ScheduledTaskTemplate شروع می‌شود:
 public class SendEmailsTask : ScheduledTaskTemplate
برای نمونه :
using System;

namespace DNTScheduler.TestWebApplication.WebTasks
{
    public class SendEmailsTask : ScheduledTaskTemplate
    {
        /// <summary>
        /// اگر چند جاب در یک زمان مشخص داشتید، این خاصیت ترتیب اجرای آن‌ها را مشخص خواهد کرد
        /// </summary>
        public override int Order
        {
            get { return 1; }
        }

        public override bool RunAt(DateTime utcNow)
        {
            if (this.IsShuttingDown || this.Pause)
                return false;

            var now = utcNow.AddHours(3.5);
            return now.Minute % 2 == 0 && now.Second == 1;
        }

        public override void Run()
        {
            if (this.IsShuttingDown || this.Pause)
                return;

            System.Diagnostics.Trace.WriteLine("Running Send Emails");
        }

        public override string Name
        {
            get { return "ارسال ایمیل"; }
        }
    }
}
- در اینجا Order، ترتیب اجرای وظیفه‌ی جاری را در مقایسه با سایر وظیفه‌هایی که قرار است در یک زمان مشخص اجرا شوند، مشخص می‌کند.
- متد RunAt ثانیه‌ای یکبار فراخوانی می‌شود (بنابراین بررسی now.Second را فراموش نکنید). زمان ارسالی به آن UTC است و اگر برای نمونه می‌خواهید بر اساس ساعت ایران کار کنید باید 3.5 ساعت به آن اضافه نمائید. این مساله برای سرورهایی که خارج از ایران قرار دارند مهم است. چون زمان محلی آن‌ها برای تصمیم گیری در مورد زمان اجرای کارها مفید نیست.
در متد RunAt فرصت خواهید داشت تا منطق زمان اجرای وظیفه‌ی جاری را مشخص کنید. برای نمونه در مثال فوق، این وظیفه هر دو دقیقه یکبار اجرا می‌شود. یا اگر خواستید اجرای آن فقط در سال 23 و 33 دقیقه هر روز باشد، تعریف آن به نحو ذیل خواهد بود:
        public override bool RunAt(DateTime utcNow)
        {
            if (this.IsShuttingDown || this.Pause)
                return false;

            var now = utcNow.AddHours(3.5);
            return now.Hour == 23 && now.Minute == 33 && now.Second == 1;
        }
- خاصیت IsShuttingDown موجود در کلاس پایه ScheduledTaskTemplate، توسط کتابخانه‌ی DNT Scheduler مقدار دهی می‌شود. این کتابخانه قادر است زمان خاموش شدن پروسه‌ی فعلی IIS را تشخیص داده و خاصیت IsShuttingDown را true کند. بنابراین در حین اجرای وظیفه‌ای مشخص، به مقدار IsShuttingDown دقت داشته باشید. اگر true شد، یعنی فقط 30 ثانیه وقت دارید تا کار را تمام کنید.
خاصیت Pause هر وظیفه را برنامه می‌تواند تغییر دهد. به این ترتیب در مورد توقف یا ادامه‌ی یک وظیفه می‌توان تصمیم گیری کرد. خاصیت ScheduledTasksCoordinator.Current.ScheduledTasks، لیست وظایف تعریف شده را در اختیار شما قرار می‌دهد.
- در متد Run، منطق وظیفه‌ی تعریف شده را باید مشخص کرد. برای مثال ارسال ایمیل یا تهیه‌ی بک آپ.
- Name نیز نام وظیفه‌ی جاری است که می‌تواند در گزارشات مفید باشد.

همین مقدار برای تعریف یک وظیفه کافی است.


نحوه‌ی ثبت و راه اندازی وظایف تعریف شده

پس از اینکه چند وظیفه را تعریف کردیم، برای مدیریت بهتر آن‌ها می‌توان یک کلاس ثبت و معرفی کلی را مثلا به نام ScheduledTasksRegistry ایجاد کرد:
using System;
using System.Net;

namespace DNTScheduler.TestWebApplication.WebTasks
{
    public static class ScheduledTasksRegistry
    {
        public static void Init()
        {
            ScheduledTasksCoordinator.Current.AddScheduledTasks(
                new SendEmailsTask(),
                new DoBackupTask());

            ScheduledTasksCoordinator.Current.OnUnexpectedException = (exception, scheduledTask) =>
            {
                //todo: log the exception.
                System.Diagnostics.Trace.WriteLine(scheduledTask.Name + ":" + exception.Message);
            };

            ScheduledTasksCoordinator.Current.Start();
        }

        public static void End()
        {
            ScheduledTasksCoordinator.Current.Dispose();
        }

        public static void WakeUp(string pageUrl)
        {
            try
            {
                using (var client = new WebClient())
                {
                    client.Credentials = CredentialCache.DefaultNetworkCredentials;
                    client.Headers.Add("User-Agent", "ScheduledTasks 1.0");
                    client.DownloadData(pageUrl);
                }
            }
            catch (Exception ex)
            {
                //todo: log ex
                System.Diagnostics.Trace.WriteLine(ex.Message);
            }
        }
    }
}
- شیء ScheduledTasksCoordinator.Current، نمایانگر تنها وهله‌ی مدیریت وظایف برنامه است.
- توسط متد ScheduledTasksCoordinator.Current.AddScheduledTasks، تنها کافی است کلاس‌های وظایف مشتق شده از ScheduledTaskTemplate، معرفی شوند.
- به کمک متد ScheduledTasksCoordinator.Current.Start، کار Thread timer برنامه شروع می‌شود.
- اگر در حین اجرای متد Run، استثنایی رخ دهد، آن‌را توسط یک Action delegate به نام ScheduledTasksCoordinator.Current.OnUnexpectedException می‌توانید دریافت کنید. کتابخانه‌ی DNT Scheduler برای اجرای وظایف، از یک ترد با سطح تقدم Below normal استفاده می‌کند تا در حین اجرای وظایف، برنامه‌ی جاری با اخلال و کندی مواجه نشده و بتواند به درخواست‌های رسیده پاسخ دهد. در این بین اگر استثنایی رخ دهد، می‌تواند کل پروسه‌ی IIS را خاموش کند. به همین جهت این کتابخانه کار try/catch استثناهای متد Run را نیز انجام می‌دهد تا از این لحاظ مشکلی نباشد.
- متد ScheduledTasksCoordinator.Current.Dispose کار مدیر وظایف برنامه را خاتمه می‌دهد.
- از متد WakeUp تعریف شده می‌توان برای بیدار کردن مجدد برنامه استفاده کرد.


استفاده از کلاس ScheduledTasksRegistry تعریف شده

پس از اینکه کلاس ScheduledTasksRegistry را تعریف کردیم، نیاز است آن‌را به فایل استاندارد global.asax.cs برنامه به نحو ذیل معرفی کنیم:
using System;
using System.Configuration;
using DNTScheduler.TestWebApplication.WebTasks;

namespace DNTScheduler.TestWebApplication
{
    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            ScheduledTasksRegistry.Init();
        }

        protected void Application_End()
        {
            ScheduledTasksRegistry.End();
            //نکته مهم این روش نیاز به سرویس پینگ سایت برای زنده نگه داشتن آن است
            ScheduledTasksRegistry.WakeUp(ConfigurationManager.AppSettings["SiteRootUrl"]);
        }
    }
}
- متد ScheduledTasksRegistry.Init در حین آغاز برنامه فراخوانی می‌شود.
- متد ScheduledTasksRegistry.End در پایان کار برنامه جهت پاکسازی منابع باید فراخوانی گردد.
همچنین در اینجا با فراخوانی ScheduledTasksRegistry.WakeUp، می‌توانید برنامه را مجددا زنده کنید! IIS مجاز است یک سایت ASP.NET را پس از مثلا 20 دقیقه عدم فعالیت (فعالیت به معنای درخواست‌های رسیده به سایت است و نه کارهای پس زمینه)، از حافظه خارج کند (این عدد در application pool برنامه قابل تنظیم است). در اینجا در فایل web.config برنامه می‌توانید آدرس یکی از صفحات سایت را برای فراخوانی مجدد تعریف کنید:
 <?xml version="1.0"?>
<configuration>
  <appSettings>
      <add key="SiteRootUrl" value="http://localhost:10189/Default.aspx" />
  </appSettings>
</configuration>
همینکه درخواست مجددی به این صفحه برسد، مجددا برنامه توسط IIS بارگذاری شده و اجرا می‌گردد. به این ترتیب وظایف تعریف شده، در طول یک روز بدون مشکل کار خواهند کرد.


گزارشگیری از وظایف تعریف شده

برای دسترسی به کلیه وظایف تعریف شده، از خاصیت ScheduledTasksCoordinator.Current.ScheduledTasks استفاده نمائید:
var jobsList = ScheduledTasksCoordinator.Current.ScheduledTasks.Select(x => new
{
   TaskName = x.Name,
   LastRunTime = x.LastRun,
   LastRunWasSuccessful = x.IsLastRunSuccessful,
   IsPaused = x.Pause,
}).ToList();
لیست حاصل را به سادگی می‌توان در یک Grid نمایش داد.
مطالب
نکاتی در مورد ELMAH
سفارشی سازی ایمیل ارسالی :
در مورد ELMAH(Erro Logging Module And Handlers) آقای نصیری چندین مطلب نوشته اند ( + و + و ... )
قبل از ارسال ایمیل توسط ELMAH رخدادی به نام Mailing اجرا (Raise) می‌شود. اگر برای این رخداد یک Event Handler ایجاد کنیم، می‌توانیم جزئیات مربوط به خطایی که قرار است ارسال شود را تغییر دهیم. به عنوان مثال می‌توانیم بگوئیم اگر Error ما از نوع Application Exception بود آنرا به آدرس دیگری ارسال کن و .... برای ایجاد یک Event Handler برای رخداد Mailing ابتدا فایل Global.asax رو به پروژه اضافه کنید و کد زیر را به آن اضافه کنید :
void ErrorMailModuleName_Mailing(object sender, Elmah.ErrorMailEventArgs e)
{
        
}
 دقت داشته باشید که در کد فوق به جای ErrorMailModuleName نام  HTTPModule را بنویسید، این ماژول در فایل Web.Config قرار دارد :
<httpModules>     
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />      
</httpModules>
که در کد فوق ErrorMail می‌باشد در نهایت :
 void ErrorMail_Mailing(object sender, Elmah.ErrorMailEventArgs e)
    {
        
    }
به عنوان مثال در کد زیر اگر Error از نوع Application Exception بود Error Log به آدرس sir1afifi@gmail.com نیز ارسال می‌شود :
void ErrorMail_Mailing(object sender, Elmah.ErrorMailEventArgs e)
    {
        if (e.Error.Exception is ApplicationException)
        {
            e.Mail.To.Add("sir1afifi@gmail.com");
        }
    }

ذخیره Error‌ها در دیتابیس SQL SERVER :
ELMAH خطاهای Log شده را در جدولی با نام ELMAH_Error ذخیره می‌کند، اگر به داخل پوشه ELMAH توجه کرده باشید یک فایل با نام SQLServer.sql وجود دارد که حاوی اسکریپت مربوط به ساخت جدول فوق می‌باشد. با اجرای این اسکریپت جدول مربوطه همرا با سه SP با نام‌های ELMAH_GetErrorsXml ، ELMAH_GetErrorXml ،  ELMAH_LogError ساخته می‌شوند. بعد از ساخت جدول مربوطه باید تگ زیر را در فایل Web.Config بنویسیم :
 <errorLog type="Elmah.SqlErrorLog, Elmah" 
            connectionStringName="..." />
connectionStringName هم نام کانکشن استرینگ را در این قسمت قرار می‌دهیم به عنوان مثال با داشتن کانکشن استرینگ زیر :
<connectionStrings>
    <add name="conn" connectionString="Data Source=.;Initial Catalog=test;User ID=user1;Password=123456;"/>
  </connectionStrings>
به این صورت connectionStringName  برابر با مقدار name یعنی conn می‌شود. 
نظرات مطالب
پردازش‌های Async در Entity framework 6
با سپاس از مقاله شما
بنده در پروژه خود اکثر متدهایی که با سرویس‌ها در ارتباط هستند رو توسط یک متد در  BaseController اجرا میکنم که مدیریت خطا، ارسال نوتیفیکیشن در صورت نیاز و... راحتر صورت بگیره...
  [HttpPost]
        [AjaxOnly]
        public virtual async Task<ActionResult> GetParts([DataSourceRequest] DataSourceRequest request)
        {
            return await DoBaseOperationAsync(() =>
            {
                var result = _partService.GetParts.Where(p => p.IsActive).AsLookupItemModel(p => p.PartId, p => p.CodeName);

                var data = result.ToDataSourceResult(request);
                return Json(data);
            });
        }

که متد DoBaseOperationAsync موجود در Action فوق در BaseController بصورت زیر پیاده سازی شده است.
 protected async Task<ActionResult> DoBaseOperationAsync(Func<ActionResult> func, string title = null, string message = null, MessageType messageType = MessageType.Error)
        {
            try
            {           
                AsyncManager.OutstandingOperations.Increment();

                var cultureInfo = new CultureInfo(CultureHelper.GetCultureString());

                return func != null ? await Task.Factory.StartNew(() =>
                {
                    System.Web.HttpContext.Current = ControllerContext.HttpContext.ApplicationInstance.Context;

                    Thread.CurrentThread.CurrentUICulture = cultureInfo;
                    Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureInfo.Name);

                    return func.Invoke();

                }) : null;

            }
            catch (Exception ex)
            {
                ErrorSignal.FromCurrentContext().Raise(ex);

           // Some code for error handling
            }
        }

حال سوال من این است، اجرا بصورت کد فوق Async تقلبی است (سربار اضافی) یا خیر ؟
آیا میتوان آنرا بهینه کرد که از حالت تقلبی دربیاید ؟
باسپاس./
مطالب
مروری بر کاربردهای Action و Func - قسمت سوم
در ادامه مثال سوم قسمت قبل، در مورد حذف کدهای تکراری توسط Action و Func، در این قسمت به یک مثال نسبتا پرکاربرد دیگر آن جهت ساده سازی try/catch/finally اشاره خواهد شد.
احتمالا هزاران بار در کدهای خود چنین قطعه کدی را تکرار کرده‌اید:
try {
       // code
} catch(Exception ex) {
       // do something
}
این مورد را نیز می‌توان توسط Actionها کپسوله کرد و پیاده سازی قسمت بدنه try آن‌را به فراخوان واگذار نمود:
void Execute(Action action) {
    try {
       action();
    } catch(Exception ex) {
       // log errors
    }
}
و برای نمونه جهت استفاده از آن خواهیم داشت:
Execute(() => {open a file});

یا اگر عمل انجام شده باید خروجی خاصی را بازگرداند (برخلاف یک Action که خروجی از آن انتظار نمی‌رود)، می‌توان طراحی متد Execute را با Func انجام داد:
public static class SafeExecutor
{
    public static T Execute<T>(Func<T> operation)
    {
        try
        {
            return operation();
        }
        catch (Exception ex)
        {
            // Log Exception
        }
        return default(T);
    }
}
در این حالت فراخوانی متد Execute به نحو زیر خواهد بود:
var data = SafeExecutor.Execute<string>(() =>
{
    // do something
    return "result";
});
و اگر در این بین استثنایی رخ دهد، علاوه بر ثبت جزئیات خطای رخ داده شده، نال را بازگشت خواهد داد.

از همین دست می‌توان به کپسوله سازی منطق «سعی مجدد» در انجام کاری اشاره کرد:
public static class RetryHelper
{
   public static void RetryOperation(Action action, int numRetries, int retryTimeout)
   {
       if( action == null )
           throw new ArgumentNullException("action");

       do
       {
          try {  action(); return;  }
          catch
          { 
              if( numRetries <= 0 ) throw;
              else 
                 Thread.Sleep( retryTimeout );
           }
       } while( numRetries-- > 0 );
   }
}
برای مثال فرض کنید برنامه قرار است اطلاعاتی را از وب دریافت کند. ممکن است در سعی اول آن، خطای اتصال یا در دسترس نبودن لحظه‌ای سایت رخ دهد. در اینجا نیاز خواهد بود تا این عملیات چندین بار تکرار شود؛ که نمونه‌ای از آن‌را در ذیل ملاحظه می‌کنید:
RetryHelper.RetryOperation(() => SomeFunction(), 3, 1000);