۱۳۹۱/۰۱/۲۷

ASP.NET MVC #16


مديريت خطاها در يك برنامه ASP.NET MVC


استفاده از فيلتر HandleError

يكي از فيلترهاي توكار ASP.NET MVC به نام HandleError،‌ مي‌تواند كار هدايت كاربر را به يك صفحه‌ي خطاي عمومي، در حين بروز استثنايي در برنامه،‌ انجام دهد. براي آزمايش آن يك برنامه خالي جديد ASP.NET MVC را آغاز كنيد. سپس يك كنترلر جديد را با محتواي زير به آن اضافه نمائيد:

using System;
using System.Web.Mvc;

namespace MvcApplication13.Controllers
{
    public class HomeController : Controller
    {
        [HandleError]
        public ActionResult Index()
        {
            throw new InvalidOperationException();
            return View();
        }
    }
}

در اينجا جهت آزمايش برنامه، به عمد يك استثناي دستي را صادر مي‌كنيم. براي آزمايش برنامه هم نياز است آن‌را خارج از ديباگر VS.NET اجرا كرد (آدرس برنامه را مستقيما خارج از VS.NET در يك مرورگر وارد كنيد). همچنين يك سطر زير را نيز لازم است به فايل web.config برنامه اضافه نمائيد:

<system.web>
   <customErrors mode="On" />

اكنون اگر برنامه را خارج از مرورگر اجرا كنيد، با توجه به استفاده از ويژگي HandleError و همچنين بروز يك استثنا در متد Index، خودبخود صفحه Views\Shared\Error.cshtml به كاربر نمايش داده خواهد شد. در غيراينصورت صفحه زرد رنگ پيش فرض خطاي ASP.NET به كاربر نمايش داده مي‌شود كه محتواي آن‌ها بيشتر براي برنامه نويس‌ها مناسب است و نه كاربران نهايي سيستم.
اگر علاقمند باشيد كه اين ويژگي به صورت خودكار به تمام متدهاي كنترلرهاي برنامه اعمال شود، كافي است يك سطر زير را به متد Application_Start فايل Global.asax.cs اضافه نمائيد:

GlobalFilters.Filters.Add(new HandleErrorAttribute());

البته نيازي به انجام اينكار نيست زيرا اگر به متد RegisterGlobalFilters فايل Global.asax.cs دقت كنيم، اينكار پيشتر توسط قالب پيش فرض VS.NET انجام شده است. فقط براي فعال سازي آن نياز است تگ customErrors در فايل وب كانفيگ برنامه مقدار دهي و تنظيم شود.



استفاده از صفحه خطاي سفارشي ديگري بجاي فايل Error.cshtml

امكان تنظيم نمايش صفحه خطاي سفارشي ديگري نيز وجود دارد. براي مثال استفاده از فايل Views\Shared\CustomErrorView.cshtml :

[HandleError(View = "CustomErrorView")]



استفاده از صفحات خطاي متفاوت به ازاي استثناهاي مختلف

مي‌توان فيلتر HandleError را تنها به يك نوع استثناي خاص محدود كرد. همچنين امكان استفاده از چندين ويژگي HandleError براي يك متد نيز وجود دارد:

[HandleError(ExceptionType = typeof(NullReferenceException), View = "ErrorHandling")]



دسترسي به اطلاعات استثناء در صفحه نمايش خطاها

زمانيكه برنامه به صفحه خطا هدايت مي‌شود، نوع Model آن System.Web.Mvc.HandleErrorInfo مي‌باشد:

@model System.Web.Mvc.HandleErrorInfo
            
@{
    ViewBag.Title = "DbError";
}
 
<h2>An Error Has Occurred</h2>
 
@if (Model != null)
{
    <p>@Model.Exception.GetType().Name<br />
    thrown in @Model.ControllerName @Model.ActionName</p>
}

البته اين نكته را صرفا به عنوان اطلاعات عمومي در نظر داشته باشيد. زيرا اگر قرار باشد مجددا اصل استثناء را نمايش دهيم، همان صفحه زرد رنگ ASP.NET شايد بهتر باشد.



استفاده از تگ customErrors در فايل Web.config برنامه

ويژگي حالت تگ customErrors در فايل web.config برنامه، سه مقدار را مي‌تواند بپذيرد:
الف) Off : صفحه زرد رنگ معرفي خطاي ASP.NET را به همراه تمام اطلاعات مرتبط با استثناي رخ داده نمايش مي‌دهد.
ب) RemoteOnly : همان حالت الف است با اين تفاوت كه صفحه خطا را فقط در كامپيوتري كه وب سرور بر روي آن نصب است نمايش خواهد داد.
ج) On : يك صفحه خطاي سفارشي شده را نمايش مي‌دهد.

بنابراين هيچگاه از حالت Off استفاده نكنيد. زيرا خطاهاي نمايش داده شده، علاوه بر برنامه نويس، براي مهاجم به يك سايت نيز بسيار دلپذير است!
حالت RemoteOnly در زمان توسعه برنامه توصيه مي‌شود.
حالت On حين توزيع برنامه بايد بكارگرفته شود.



مديريت خطاهاي رخ داده خارج از MVC Pipeline

HandleErrorAttribute تنها استثناهاي رخ داده داخل ASP.NET MVC Pipeline را مديريت مي‌كند (يا خطاهايي از نوع 500). اگر اين نوع استثناها خارج از آن رخ دهند مثلا فايلي يافت نشود (خطاي 404) و امثال آن، بايد به روش زير عمل كرد:

<customErrors mode="On" defaultRedirect="error">
      <error statusCode="404" redirect="error/notfound" />
      <error statusCode="403" redirect="error/forbidden" />
</customErrors>

در اينجا اگر فايلي يافت نشد، كاربر به كنترلري به نام error و متدي به نام notfound هدايت خواهد شد. بنابراين نياز به كنترلر زير وجود دارد؛ به علاوه به ازاي هر متد هم يك View متناظر بايد اضافه شود (كليك راست روي نام متد و انتخاب گزينه افزودن View جديد).

using System.Web.Mvc;

namespace MvcApplication13.Controllers
{
    public class ErrorController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult NotFound()
        {
            return View();
        }

        public ActionResult Forbidden()
        {
            return View();
        }
    }
}

براي آزمايش اين قسمت، برنامه را اجرا كرده و سپس مثلا آدرس غيرموجود http://localhost/xyz را وارد كنيد.



استفاده از فيلتر HandleError اجباري نيست

در همين قسمت قبل پس از افزودن customErrors و defaultRedirect آن كه به نام يك كنترلر اشاره مي‌كند، كليه فيلترهاي HandleError اضافه شده به برنامه را حذف كنيد. سپس برنامه را خارج از محيط VS.NET اجرا كنيد. باز هم متد Index كنترلر Error اجرا خواهد شد. به عبارتي الزاما نيازي به استفاده از فيلتر HandleError نيست و به كمك مقدار دهي صحيح تگ customErrors، كار نمايش خودكار صفحه سفارشي خطاها به كاربر انجام خواهد شد.
البته بديهي است كه گزينه‌هاي نمايش يك View خاص به ازاي استثنايي ويژه، يكي از مزيت‌هاي استفاده از فيلتر HandleError مي‌باشد كه امكان تنظيم آن در فايل web.config وجود ندارد.



ثبت اطلاعات استثناهاي رخ داده به كمك ELMAH

نمايش صفحه‌ي خطاي سفارشي به كاربر، يكي از موارد ضروري تمام برنامه‌هاي ASP.NET است، اما كافي نيست. ثبت اطلاعات جزئيات استثناهاي رخ داده در طول زمان مي‌توانند به بالا بردن كيفيت برنامه به شدت كمك كنند. براي اين منظور مي‌توان همانند سابق از متد Application_Error قابل تعريف در فايل Global.asax.cs كمك گرفت؛ اما با وجود افزونه‌اي به نام ELMAH اينكار اتلاف وقت است و اصلا توصيه نمي‌شود. همچنين به كمك ELMAH مي‌توان مشكلات را تبديل به ايميل‌هاي خودكار كرد يا از آن‌ها فيد RSS درست نمود.
براي دريافت ELMAH يا به سايت اصلي آن مراجعه نمائيد و يا به كمك NuGet هم به سادگي قابل دريافت است. پس از دريافت، ارجاعي را به اسمبلي آن (Elmah.dll) اضافه نمائيد. در ادامه فايل web.config برنامه را گشوده و چند سطر زير را به آن در قسمت configuration اضافه كنيد:

<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>

سپس ذيل قسمت appSettings، تنظيمات پروايدر ذخيره سازي اطلاعات آن‌را وارد نمائيد. مثلا در اينجا از فايل‌هاي XML براي ذخيره سازي اطلاعات استفاده خواهد شد (كه امن‌ترين حالت ممكن است؛ از اين لحاظ كه اگر بانك اطلاعاتي را انتخاب كنيد، ممكن است مشكل اصلي از همانجا ناشي شده باشد. بنابراين خطايي ثبت نخواهد شد. همچنين در اين حالت نيازي به ساير DLLهاي همراه ELMAH هم نيست). در اينجا مسير ذخيره سازي اطلاعات در پوشه app_data/errorslog تنظيم شده است:

<elmah>
    <security allowRemoteAccess="1"/>
    <errorLog type="Elmah.XmlFileErrorLog, Elmah" logPath="~/App_Data/ErrorsLog"/>
  </elmah>

در ادامه در قسمت system.web، دو تعريف زير را اضافه نمائيد. به اين ترتيب امكان دسترسي به آدرس http://server/elmah.axd مهيا مي‌گردد:

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

البته براي IIS7 تنظيمات ذيل نيز بايد اضافه شوند:

<system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
    </modules>
    <handlers>
      <add name="Elmah" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah"/>
    </handlers>
</system.webServer>

و به اين ترتيب تنظيمات اوليه ELMAH به پايان مي‌رسد (و با ASP.NET Web forms هيچ تفاوتي ندارد).
مرحله بعد، تنظيمات مسيريابي ASP.NET MVC است براي اينكه آدرس http://server/elmah.axd را وارد سيستم پردازشي خود نكند. البته اينكار پيشتر انجام شده است:

public static void RegisterRoutes(RouteCollection routes)
{
    //routes.IgnoreRoute("elmah.axd");
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

بنابراين همين تنظيمات، به همراه قالب پيش فرض يك پروژه جديد ASP.NET MVC براي استفاده از ELMAH كفايت مي‌كند. اكنون پروژه جاري را يكبار ديگر خارج از VS.NET اجرا كرده و سپس به مسير http://localhost/elmah.axd جهت مشاهده خطاهاي لاگ شده به همراه جزئيات كامل آن‌ها مراجعه كنيد.

مشكل: استثناهاي برنامه توسط ELMAH لاگ نمي‌شوند!

فيلتر HandleError با ELMAH سازگار نيست. زيرا با استفاده از آن، متدهاي كنترلرها به صورت خودكار داخل يك try/catch اجرا شده و به اين ترتيب استثناهاي رخ داده، مديريت گرديده و به ELMAH هدايت نمي‌شوند. بنابراين نياز است به متد RegisterGlobalFilters فايل Global.asax.cs مراجعه كرده و سطر زير را حذف كنيد:

filters.Add(new HandleErrorAttribute());

و يا اگر قصد نداشتيد اينكار را انجام دهيد، مي‌توان به نحو زير نيز مشكل را حل كرد:

using System.Web.Mvc;
using Elmah;

namespace MvcApplication13.CustomFilters
{
    public class ElmahHandledErrorLoggerFilter : IExceptionFilter
    {
        public void OnException(ExceptionContext context)
        {
            if (context.ExceptionHandled)
                ErrorSignal.FromCurrentContext().Raise(context.Exception);
            // all other exceptions will be caught by ELMAH anyway
        }
    }
}

در اينجا يك فيلتر سفارشي به برنامه اضافه شده است تا خطاهاي مديريت شده برنامه (خطاهاي مديريت شده توسط فيلتر HandleError توكار) را به موتور ELMAH هدايت كند. ساير خطاهاي مديريت نشده به صورت خودكار توسط ELMAH ثبت خواهند شد و نيازي به انجام كار اضافي در اين مورد نيست.
سپس اين فيلتر جديد را به صورت سراسري تعريف كنيد:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
      filters.Add(new ElmahHandledErrorLoggerFilter());
      filters.Add(new HandleErrorAttribute());
}

ترتيب اين‌ها هم مهم است. ابتدا بايد ElmahHandledErrorLoggerFilter معرفي شود.


تذكر مهم!
حين استفاده از ELMAH يك نكته را فراموش نكنيد:
اگر allowRemoteAccess آن‌را به عدد 1 تنظيم كرده‌ايد، به هيچ عنوان از نام پيش فرض elmah.axd استفاده نكنيد (هر نام اختياري ديگري را كه علاقمند بوديد و به سادگي قابل حدس زدن نبود، در فايل web.config وارد كنيد).


خلاصه بحث
1- در ASP.NET MVC نيازي نيست تا متدهاي كنترلرها را با try/catch شلوغ كنيد.
2- حتما قسمت customErrors فايل وب كانفيگ برنامه را دهي كنيد (اين مورد را به چك ليست اجباري تهيه يك برنامه ASP.NET MVC اضافه كنيد).
3- استفاده از فيلتر HandleError اختياري است. اگر از قابليت فيلتر كردن استثناهاي ويژه آن استفاده نمي‌كنيد، مقدار دهي customErrors وب كانفيگ برنامه هم همان كار را انجام مي‌دهد.
4- براي ثبت جزئيات دقيق استثناهاي رخ داده در برنامه، از ELMAH استفاده كنيد و بي‌جهت وقت خودتان را صرف بازنويسي اين افزونه ارزشمند نكنيد.

مطالب مشابه
معرفي ELMAH
ثبت استثناهاي مديريت شده توسط ELMAH