۱۳۹۱/۰۱/۱۱

ASP.NET MVC #6


آشنايي با انواع ActionResult

در قسمت چهارم، اولين متد يا اكشني كه به صورت خودكار توسط VS.NET به برنامه اضافه شد، اينچنين بود:

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/
        public ActionResult Index()
        {
            return View();
        }
    }
}

توضيحات تكميلي مرتبط با خروجي از نوع ActionResult ايي را كه مشاهده مي‌كنيد، در اين قسمت ارائه خواهد شد.
رفتار يك كنترلر توسط متدهايي كه در آن كلاس تعريف مي‌شوند، مشخص مي‌گردد. هر متد هم از طريق يك URL مجزا قابل دسترسي و فراخواني خواهد بود. اين متدها كه به آن‌ها اكشن نيز گفته مي‌شود بايد عمومي بوده، استاتيك يا متد الحاقي (extension method) نباشند و همچنين داراي پارامترهايي از نوع ref و out نيز نباشند.
هر درخواست رسيده، به يك كنترلر و متدي عمومي در آن توسط سيستم مسيريابي، نگاشت خواهد شد. اگر علاقمند باشيد كه در يك كلاس كنترلر، متدي عمومي را از اين سيستم خارج كنيد، تنها كافي است آن‌را با ويژگي (attribute) به نام NonAction مزين كنيد:

using System.Web.Mvc;

namespace MvcApplication2.Controllers
{
    public class HomeController : Controller
    {
        [NonAction]
        public string ShowData()
        {
            return "Text";
        }

        public ActionResult Index()
        {
            ViewBag.Message = string.Format("{0}/{1}/{2}",
                                              RouteData.Values["controller"],
                                              RouteData.Values["action"],
                                              RouteData.Values["id"]);
            return View();
        }

        public ActionResult Search(string data = "*")
        {
            // do something ...
            return View();
        }
    }
}

چند نكته در اين مثال قابل ذكر است:
الف) در اينجا اگر شخصي آدرس http://localhost/home/showdata را درخواست نمايد، با توجه به استفاده از ويژگي NonAction، با پيغام يافت نشد يا 404 مواجه مي‌گردد.
ب) صرفنظر از پارامترهاي يك متد و ساختار كلاس جاري، اطلاعات مسيريابي از طريق شيء RouteData.Values نيز در دسترس هستند كه نمونه‌اي از آن‌را در اينجا بر اساس مقادير پيش فرض تعاريف مسيريابي يك پروژه ASP.NET MVC ملاحظه مي‌نمائيد.
ج) در متد Search، از قابليت امكان تعريف مقداري پيش فرض جهت آرگومان‌ها در سي شارپ 4 استفاده شده است. به اين ترتيب اگر شخصي آدرس http://localhost/home/search را وارد كند، چون پارامتري را ذكر نكرده است، به صورت خودكار از مقدار پيش فرض آرگومان data استفاده مي‌گردد.


انواع Action Results در ASP.NET MVC

در ASP.NET MVC بجاي استفاده مستقيم از شيء Response، از شيء ActionResult جهت ارائه خروجي يك متد استفاده مي‌شود و مهم‌ترين دليل آن هم مشكل بودن نوشتن آزمون‌هاي واحد براي شيء Response است كه وهله سازي آن مساوي است با به كار اندازي موتور ASP.NET و Http Runtime آن توسط يك وب سرور (بنابراين در ASP.NET MVC سعي كنيد شيء Response را فراموش كنيد).
سلسله مراتب ActionResult‌هاي قابل استفاده در ASP.NET در تصوير زير مشخص شده‌اند:


و در مثال زير تقريبا انواع و اقسام ActionResult‌هاي مهم و كاربردي ASP.NET MVC را مي‌توانيد مشاهده كنيد:

using System.Web.Mvc;

namespace MvcApplication2.Controllers
{
    public class ActionResultsController : Controller
    {
        //http://localhost/actionresults/welcome
        public string Welcome()
        {
            return "Hello, World";
        }

        //http://localhost/actionresults/index
        public ActionResult Index() // or ContentResult
        {
            return Content("Hello, World");
        }

        //http://localhost/actionresults/SendMail
        public void SendMail()
        {
        }

        public ActionResult SendMailCompleted() // or EmptyResult
        {
            // do whatever
            return new EmptyResult();
        }

        public ActionResult GetFile() // or FilePathResult
        {
            return File(Server.MapPath("~/content/site.css"), "text/css", "mySite.css");
        }

        public ActionResult UnauthorizedStatus() // or HttpStatusCodeResult/HttpUnauthorizedResult
        {
            return new HttpUnauthorizedResult("You need to login first.");
        }

        public ActionResult Status() // or HttpStatusCodeResult
        {
            return new HttpStatusCodeResult(501, "Server Error");
        }

        public ActionResult GetJavaScript() // or JavaScriptResult
        {
            return JavaScript("...JavaScript...");
        }

        public ActionResult GetJson() // or JsonResult
        {
            var obj = new { prop1 = 1, prop2 = "data" };
            return Json(obj, JsonRequestBehavior.AllowGet);
        }

        public ActionResult RedirectTo() // or RedirectResult
        {
            return RedirectPermanent("http://www.site.com");
            //return RedirectToAction("Home", "Index");
        }

        public ActionResult ShowView() // or ViewResult
        {
            return View();
        }
    }
}

چند نكته در اين مثال وجود دارد:
1) مثلا متد GetJavaScript را درنظر بگيريد. در اين متد خاص، چه بنويسيد public ActionResult GetJavaScript يا بنويسيد public JavaScriptResult GetJavaScript تفاوتي نمي‌كند. در ساير موارد هم به همين ترتيب است. علت را در تصوير سلسله مراتبي ActionResult‌ها مي‌توان جستجو كرد. تمام اين كلاس‌ها نوعي ActionResult هستند و از يك كلاس پايه به ارث رسيده‌اند.
2) مثلا ContentResult شبيه به همان Response.Write سابق ASP.NET عمل مي‌كند. علت وجودي آن هم عدم وابستگي مستقيم به شيء Response و ساده‌تر سازي نوشتن آزمون‌هاي واحد براي اين نوع اكشن متدها است.
3) منهاي متد آخري كه نمايش داده شده (ShowView)، هيچكدام از متدهاي ديگر نيازي به View متناظر ندارند. يعني نيازي نيست تا روي متد كليك راست كرده و Add view را انتخاب كنيم. چون در همين متد كنترلر، كار Response به پايان مي‌رسد و مرحله بعدي ندارد. مثلا در حالت return File، يك فايل به درون مرورگر كاربر Flush خواهد شد و تمام.
4) متد Welcome و متد Index در اينجا به يك صورت تفسير مي‌شوند. به اين معنا كه اگر خروجي متد تعريف شده در يك كنترلر از نوع ActionResult نباشد، به صورت پيش فرض درون يك ContentResult محصور خواهد شد.
5) اگر خروجي متدي در اينجا از نوع void باشد، با ActionResult ايي به نام EmptyResult يكسان خواهد بود. بنابراين با متدهاي SendMail و SendMailCompleted به يك نحو رفتار مي‌گردد.
6) return Json ياد شده كه خروجي‌اش از نوع JsonResultاست در پياده سازي‌هاي Ajax ايي كاربرد دارد.
7) جهت بازگرداندن حالت وضعيت 403 يا غيرمجاز مي‌توان از return new HttpUnauthorizedResult استفاده كرد.
8) يا جهت اعلام مشكلي در سمت سرور به كمك return new HttpStatusCodeResultكد ويژه‌اي را مي‌توان به كاربر نمايش داد.
9) به كمك return RedirectToAction مي‌توان به يك كنترلر و متدي خاص در آن، كاربر را هدايت كرد.

و خلاصه اينكه تمام كارهايي را كه پيشتر در ASP.NET Web forms ، مستقيما به كمك شيء Response انجام مي‌داديد (Response.Write، Response.End، Response.Redirect و غيره)، اينبار به كمك يكي از ActionResult‌هاي ياد شده انجام دهيد تا بتوان بدون نياز به راه اندازي يك وب سرور، براي متدهاي كنترلرها آزمون واحد نوشت. براي مثال:

[TestMethod]
public void TestMethod1()
{
    // Arrange
    var controller = new ActionResultsController();

    // Act
    var result = controller.Index() as ContentResult;

    // Assert
    Assert.NotNull(result);
    Assert.AreEqual( "Hello, World", result.Content);
}