Skip to content

3. وحدات التحكم، الإجراءات، التوجيه

دعونا ننظر في بنية تطبيق ASP.NET MVC:

في هذا الفصل، ندرس العملية التي توجه الطلب [1] إلى وحدة التحكم والإجراء [2a] الذي سيقوم بمعالجته، وهي آلية تُعرف باسم التوجيه. كما نقدم الاستجابات المختلفة [3] التي يمكن أن يعيدها الإجراء إلى المتصفح. وقد يكون هذا شيئًا آخر غير العرض V [4b].

3.1. بنية مشروع ASP.NET MVC

لنقم بإنشاء أول مشروع ASP.NET MVC باستخدام Visual Studio Express 2012. سنقوم بإضافته [1] إلى الحل المستخدم في الفصل السابق:

  • في [2]، اسم المشروع الجديد؛
  • في [3، 4]، نختار مشروع ASP.NET MVC أساسي. يوفر لنا هذا القالب تطبيق ويب فارغًا يتضمن مع ذلك جميع الموارد (ملفات DLL ومكتبات JavaScript وما إلى ذلك) اللازمة للبدء.

يظهر المشروع الناتج في [5]. سنجعله [6] مشروع البداية للحل:

لاحظ النقاط التالية في [5]:

  • تعكس بنية المشروع نموذج MVC الخاص به:
  • سيتم وضع وحدات التحكم C في مجلد [Controllers
  • سيتم وضع نماذج البيانات M في مجلد [Models
  • سيتم وضع طرق العرض (V) في مجلد [Views
  • في [1]، سيكون ملف [Site.css] هو ملف CSS الافتراضي للتطبيق؛
  • في [2]، يتوفر عدد من مكتبات JavaScript؛
  • في [3]، توجد ثلاث طرق عرض محددة: _ViewStart و_Layout وError.

ملف [_ViewStart] هو كما يلي:


@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

  • السطر 1: يشير الحرف @ إلى بداية كتلة C# داخل العرض. في الواقع، يمكن تضمين كود C# في العرض؛
  • السطر 2: يُعرّف متغير Layout الذي يحدد العرض الأصلي لجميع العروض. وهو يقابل الصفحة الرئيسية في إطار عمل ASP.NET الكلاسيكي.

الملف [ _Layout ] كما يلي:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    @RenderBody()
 
    @Scripts.Render("~/bundles/jquery")
    @RenderSection("scripts", required: false)
</body>
</html>

عندما يتم عرض طريقة عرض من مجلد [Views]، سيتم عرض نصها بواسطة السطر 11 أعلاه. وهذا يعني أن طريقة العرض لا تحتاج إلى تضمين العلامات <html> و<head> و<body>. يتم توفير هذه العلامات بواسطة الملف أعلاه. في الوقت الحالي، هذا الملف غامض إلى حد ما. دعونا نبسطه:


<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>Tutoriel ASP.NET MVC</title>
</head>
<body>
  <h2>Tutoriel ASP.NET MVC</h2>
  @RenderBody()
</body>
</html>

  • السطر 6: العنوان المشترك بين جميع طرق العرض؛
  • السطر 9: العنوان المشترك بين جميع طرق العرض؛
  • السطر 10: المحتوى الخاص بالعرض المعروض.

Image

  • [Web.config] هو ملف تكوين تطبيق الويب. وهو ملف معقد. ستحتاج إلى تعديله عند استخدام إطار عمل [Spring.net] في بنية متعددة المستويات.
  • يحتوي [Global.asax] على الكود الذي يتم تنفيذه عند بدء تشغيل التطبيق. بشكل عام، يستخدم هذا الكود ملفات التكوين المختلفة للتطبيق، بما في ذلك [Web.config].

3.2. توجيه عناوين URL الافتراضية

الرمز الموجود في [Global.asax] هو حاليًا كما يلي:


using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
 
namespace Exemple_01
{
 
  public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
   ...
    }
  }
}

  • السطر 6: مساحة اسم الفئة. وهي مشتقة مباشرة من اسم المشروع ومدرجة في خصائص المشروع:

Image

نقوم بتعيين [1] كمساحة الاسم الافتراضية. ثم يتم استخدامها بشكل افتراضي لجميع الفئات التي سيتم إنشاؤها في المشروع.

لنعد إلى الكود في [Global.asax]:


using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
 
namespace Exemple_01
{
 
  public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
   ...
    }
  }
}

  • السطر 9: الفئة [MvcApplication] مشتقة من الفئة [HttpApplication]. يمكن تغيير اسم [MvcApplication
  • السطر 11: طريقة [Application_Start] هي الطريقة التي يتم تنفيذها عند بدء تشغيل تطبيق الويب. يتم تنفيذها مرة واحدة فقط. هذا هو المكان الذي يتم فيه تهيئة التطبيق.

رمز [Application_Start] حاليًا كما يلي:


    protected void Application_Start()
    {
      AreaRegistration.RegisterAllAreas();
 
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
}

في الوقت الحالي، لا تحتاج إلى فهم كل هذا الكود. تحدد الأسطر من 5 إلى 8 المسارات التي يقبلها تطبيق الويب. دعونا نعود إلى بنية تطبيق ASP.NET MVC:

لقد أوضحنا أن [Front Controller] يجب أن يوجه عنوان URL إلى الإجراء المسؤول عن معالجته. تُستخدم المسارات لربط نمط عنوان URL بإجراء. يتم تعريف هذه المسارات في مجلد [App_Start] الخاص بالمشروع بواسطة فئات [WebApiConfig، FilterConfig، RouteConfig، BundleConfig]:

Image

في الوقت الحالي، نحن مهتمون فقط بفئة [RouteConfig]:


using System.Web.Mvc;
using System.Web.Routing;
 
namespace Exemple_01
{
  public class RouteConfig
  {
    public static void RegisterRoutes(RouteCollection routes)
    {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
      );
    }
  }
}

تحدد الأسطر 12–16 تنسيق عناوين URL التي يقبلها التطبيق. وهذا ما يُسمى مسارًا. يمكن أن يكون هناك عدة مسارات محتملة (= عدة أنماط محتملة لعناوين URL). ويتم تمييزها عن بعضها البعض من خلال اسمها (السطر 13). يتم تحديد تنسيق عناوين URL الخاصة بالمسار في السطر 14. هنا، ستتكون عنوان URL من ثلاثة مكونات:

  • {controller}: اسم فئة مشتقة من [Controller]. سيتم البحث عنها في مجلد [Controllers] الخاص بالمشروع. وفقًا للأعراف، إذا كان عنوان URL هو /X/Y/Z، فإن وحدة التحكم المسؤولة عن معالجة عنوان URL هذا ستكون فئة XController. تضاف اللاحقة Controller إلى اسم وحدة التحكم الموجودة في عنوان URL؛
  • {action}: اسم طريقة في وحدة التحكم المحددة أعلاه. ستتلقى هذه الطريقة المعلمات المصاحبة لعنوان URL وتقوم بمعالجتها. يمكن أن ترجع هذه الطريقة نتائج متنوعة:
    • void: ستقوم الإجراء بإنشاء الاستجابة لمتصفح العميل بنفسها
    • String: تعيد الإجراء سلسلة نصية إلى العميل؛
    • ViewResult: تعيد عرضًا إلى العميل؛
    • PartialViewResult: تعيد عرضًا جزئيًا؛
    • EmptyResult: يتم إرسال استجابة فارغة إلى العميل؛
    • RedirectResult: يوجه العميل إلى إعادة التوجيه إلى عنوان URL
    • RedirectToRouteResult: مثل ما سبق، لكن عنوان URL يتم إنشاؤه من مسارات التطبيق؛
    • JsonResult: يرسل استجابة JSON
    • JavaScriptResult: يعرض كود JavaScript للعميل؛
    • ContentResult: يعيد دفق HTML إلى العميل دون المرور عبر عرض؛
    • FileContentResult: يعيد ملفًا إلى العميل؛
    • FileStreamResult: مثل ما سبق، ولكن عبر طريقة مختلفة؛
    • FilePathResult: ...
  • {id}: معلمة سيتم تمريرها إلى الإجراء. لكي يعمل هذا، يجب أن يحتوي الإجراء على معلمة باسم id.

يحدد السطر 15 القيم الافتراضية عندما لا يكون عنوان URL بالصيغة المتوقعة /{controller}/{action}/{id}. كما يشير إلى أن المعلمة {id} في عنوان URL اختيارية. فيما يلي قائمة بعناوين URL غير مكتملة وعناوين URL المكتملة بالقيم الافتراضية:

عنوان URL الأصلي
عنوان URL المكتمل
/
/Home/Index
/الأنشطة
/Do/Index
/افعل/شيء
/افعل/شيء
/افعل/شيء ما/4
/افعل/شيء ما/4
/افعل/شيئًا/x/y/z
عنوان URL غير موجه

3.3. إنشاء وحدة تحكم وإجراء أول

لنقم بإنشاء وحدة تحكم أولى:

Image

  • في [1]، أدخل اسم وحدة التحكم مع اللاحقة [Controller
  • في [2]، قم بإنشاء وحدة تحكم MVC فارغة؛
  • في [3]، تم إنشاؤها.

في [FirstController]، تم إنشاؤها.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace Exemple_01.Controllers
{
    public class FirstController : Controller
    {
        //
        // GET: /First/
 
        public ActionResult Index()
        {
            return View();
        }
 
    }
}
  • السطر 7: تم إنشاء مساحة اسم افتراضية؛
  • السطر 9: الفئة [FirstController] مشتقة من الفئة [System.Web.Mvc.Controller
  • الأسطر 14-17: تم إنشاء إجراء [Index] افتراضيًا. وهو عام. وهذا أمر مهم؛ وإلا فلن يتم العثور عليه. وهو يُرجع نوع [ActionResult]، وهو فئة مجردة تُشتق منها معظم النتائج التي يُرجعها الإجراء عادةً. وهذا نوع "شامل" يمكن تحديده باستبداله بالاسم الفعلي للنوع المُرجع؛
  • السطر 16: لا تقوم الطريقة بأي شيء. إنها تعيد ببساطة عرضًا، أي نوع [ViewResult]. لم يتم تحديد اسم العرض. في هذه الحالة، يبحث إطار العمل في المجلد [Views/First] عن عرض يحمل نفس اسم الإجراء، وهو هنا [Index.cshtml].

لنقم بإنشاء عرض [Index.cshtml]:

  • في [1]، انقر بزر الماوس الأيمن على كود الإجراء واختر خيار [Add View
  • في [2]، يقترح المعالج عرضًا يحمل نفس اسم الإجراء. وهذا ما نريده هنا؛
  • في [3]، بشكل افتراضي، يُقترح استخدام الصفحة الرئيسية [_Layout.cshtml
  • في [4]، بمجرد التأكيد، يقوم المعالج بإنشاء العرض في مجلد فرعي ضمن مجلد [Views] يحمل اسم وحدة التحكم (First).

الرمز الذي تم إنشاؤه لعرض [Index] هو كما يلي:


@{
    ViewBag.Title = "Index";
}
 
<h2>Index</h2>
  • الأسطر 1–3: كود C# الذي يحدد متغيرًا؛
  • السطر 5: علامة HTML.

استبدل كل الكود السابق بهذا:


<strong>Vue [Index]...</strong>

للتلخيص:

  • لدينا وحدة تحكم C# تسمى [First
  • لدينا إجراء يسمى [Index] يطلب عرض عرض يسمى [Index
  • لدينا العرض V [Index].

يمكننا استدعاء الإجراء [Index] بطريقتين:

  • /First/Index؛
  • /أولاً، نظرًا لأن [Index] هو أيضًا الإجراء الافتراضي في المسارات.

دعونا نشغل التطبيق (CTRL-F5). نحصل على الصفحة التالية:

Image

في [1]، كان عنوان URL المطلوب هو http://localhost:49302. ولا يوجد مسار. ونحن نعلم أن جهاز التوجيه الخاص بنا يتوقع أن تكون عناوين URL بالصيغة /{controller}/{action}/{id}. ونظرًا لعدم وجود هذه العناصر، يتم استخدام القيم الافتراضية. ويصبح عنوان URL هو http://localhost:49302/Home/Index. ولا يوجد وحدة التحكم [Home]. ولذلك، يتم رفض عنوان URL.

الآن دعونا نجرب عنوان URL http://localhost:49302/First/Index عن طريق كتابته مباشرة في المتصفح:

تم إنشاء الصفحة أعلاه بواسطة الإجراء [Index] لوحدة التحكم [First]. الصفحة التي تم إنشاؤها بواسطة هذا الإجراء هي طريقة العرض [Index]، والتي كان كودها كما يلي:


<strong>Vue [Index]...</strong>

وهي تُنشئ الجزء [1]. أما الجزء [2]، فيأتي من الصفحة الرئيسية [_Layout] التي حددناها سابقًا:


<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>Tutoriel ASP.NET MVC</title>
</head>
<body>
  <h2>Tutoriel ASP.NET MVC</h2>
  @RenderBody()
</body>
</html>

السطر 9 أنشأ القسم [2] من الصفحة. ومع ذلك، لا تظهر طريقة العرض [Index] إلا في السطر 10.

إذا قمنا بعرض شفرة المصدر للصفحة المستلمة، يمكننا أن نرى أن صفحة [Index] مدرجة بالفعل (السطر 10 أدناه) في صفحة [Layout]:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>Tutoriel ASP.NET MVC</title>
</head>
<body>
  <h2>Tutoriel ASP.NET MVC</h2>
  <strong>Vue [Index]...</strong>
</body>
</html>

الآن دعونا نجرب عنوان URL [/First]:

Image

كان عنوان URL [/First] غير مكتمل. تم استكماله بالقيم الافتراضية للمسار وأصبح [/First/Index]. وبالتالي نحصل على نفس النتيجة السابقة.

3.4. إجراء بنتيجة من النوع [ContentResult] - 1

لنقم بإنشاء إجراء جديد في وحدة التحكم [First]:


using System.Text;
using System.Web.Mvc;
 
namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
    // Index
    public ViewResult Index()
    {
      return View();
    }
    // Action01
    public ContentResult Action01()
    {
      return Content("<h1>Action [Action01]</h1>", "text/plain", Encoding.UTF8);
    }
  }
}

يتم تعريف الإجراء الجديد في الأسطر 14–18. وهو ببساطة يعيد سلسلة نصية باستخدام طريقة [Content] (السطر 16) من فئة [Controller] (السطر 6). معلمات الطريقة هي:

  1. سلسلة الاستجابة؛
  2. مؤشر لنوع النص الذي يتم إرساله: "text/plain"، "text/html"، "text/xml"، إلخ. يُسمى هذا المؤشر بنوع MIME (http://fr.wikipedia.org/wiki/Type_MIME
  3. المعلمة الثالثة تحدد نوع الترميز المستخدم للنص.

بدلاً من استخدام النوع المجرد [ActionResult]، تحدد طرقنا نوع الإرجاع الفعلي (السطران 9 و 14).

دعونا نطلب عنوان URL [/First/Action01]. نحصل على الصفحة التالية:

Image

دعونا نلقي نظرة على شفرة المصدر للصفحة التي تم استلامها:

<h1>Action [Action01]</h1>

لم يتلق المتصفح سوى النص الذي أرسلته الإجراء ولم يتلق أي شيء آخر. هذا الوضع مفيد عندما تريد طلب البيانات الأولية من خادم الويب دون علامات HTML المحيطة بها. لاحظ أعلاه أن المتصفح لم يفسر علامة HTML <h1>. لفهم السبب، دعونا نلقي نظرة على تبادلات HTTP في Chrome:

أرسل المتصفح رؤوس HTTP التالية:

1
2
3
4
5
6
7
8
GET /First/Action01 HTTP/1.1
Host: localhost:49302
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4

رد الخادم بالرؤوس التالية:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMQ==?=
X-Powered-By: ASP.NET
Date: Mon, 23 Sep 2013 15:22:33 GMT
Content-Length: 141
  • السطر 3: يحدد نوع المستند. نرى السمات المحددة في الأسلوب [Action01]. ويرجع السبب في عدم تفسير المتصفح لعلامة <h1> الموجودة في المستند المستلم إلى أنه تم إعلامه بأن المستند من النوع "text/plain" وليس "text/html".

3.5. إجراء بنتيجة من النوع [ContentResult] - 2

لننظر إلى الإجراء الثالث التالي:


using System.Text;
using System.Web.Mvc;
 
namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
   ...
    // Action02
    public ContentResult Action02()
    {
      string data = "<action><name>Action02</name><description>renvoie un texte XML</description></action>";
      return Content(data, "text/xml", Encoding.UTF8);
    }
  }
}
  • السطر 12: نحدد نص XML؛
  • السطر 13: نرسله إلى المتصفح، مع تحديد أنه XML بنوع MIME "text/xml".

في المتصفح، نحصل على الصفحة التالية:

Image

دعونا نلقي نظرة على استجابة HTTP للخادم في Chrome:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMg==?=
X-Powered-By: ASP.NET
Date: Mon, 23 Sep 2013 15:29:34 GMT
Content-Length: 176
  • السطر 3: يحدد نوع المستند. يتم تضمين السمات المحددة في طريقة [Action02] هنا؛
  • السطر 12: حجم المستند المرسَل من الخادم بالبايت.

المستند الذي أرسله الخادم هو هذا (لقطة شاشة من Chrome):

Image

3.6. إجراء بنتيجة من نوع [JsonResult]

دعونا نضيف الإجراء التالي إلى وحدة التحكم [First]:


    // Action03
    public JsonResult Action03()
    {
      dynamic personne = new ExpandoObject();
      personne.nom = "someone";
      personne.age = 20;
      return Json(personne,JsonRequestBehavior.AllowGet);
}
  • السطر 4: متغير من النوع dynamic. في وقت التشغيل، يمكن إضافة الخصائص بحرية إلى مثل هذا المتغير. يتم إنشاء الخاصية في نفس الوقت الذي يتم فيه تهيئتها؛
  • السطران 5-6: نقوم بتهيئة خاصيتين، name و age؛
  • السطر 7: يُرجع تمثيل JSON (ترميز كائنات JavaScript) للكائن. يسمح JSON بتسلسل كائن إلى سلسلة، وبالعكس، بإلغاء تسلسل سلسلة إلى كائن. وهو بديل لتسلسل/إلغاء تسلسل XML؛
  • السطر 2: تعرض الإجراء نوع [JsonResult]. لا يمكن عرض هذا النوع إلا لطلب POST. إذا كنت ترغب في عرضه لطريقة GET، يجب عليك تعيين المعلمة الثانية لمُنشئ فئة Json (السطر 7) إلى JsonRequestBehavior.AllowGet.

عند طلب عنوان URL [/First/Action03]، يعرض المتصفح ما يلي:

Image

استجابة HTTP للخادم هي كما يلي:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMw==?=
X-Powered-By: ASP.NET
Date: Mon, 23 Sep 2013 15:48:53 GMT
Content-Length: 58
  • السطر 3: يحدد أن المستند المرسل هو JSON؛
  • السطر 10: حجم المستند المرسل هو 58 بايت. وهو كما يلي:
[{"Key":"nom","Value":"someone"},{"Key":"age","Value":20}]

يُنظر إلى العنصر الديناميكي [person] بواسطة JSON على أنه مصفوفة من القواميس حيث كل قاموس:

  • يتوافق مع حقل من متغير [person
  • يحتوي على مفتاحين، "Key" و"Value". يحتوي "Key" على اسم الحقل كقيمة مرتبطة به، ويحتوي "Value" على قيمة الحقل.

3.7. إجراء بنتيجة [string]

دعونا نضيف الإجراء التالي إلى وحدة التحكم [First]:


    // Action04
    public string Action04()
    {
      return "<h3>Contrôleur=First, Action=Action04</h3>";
}

عندما نطلب عنوان URL [/First/Action04] في Chrome، نحصل على الاستجابة التالية:

Image

يمكننا أن نرى أن العلامة <h3> قد تم تفسيرها. دعونا نلقي نظرة على استجابة HTTP للخادم:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNA==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 07:49:00 GMT
Content-Length: 156

والوثيقة التالية:

<h3>Contrôleur=First, Action=Action04</h3>

كما يظهر في السطر 3، أشار الخادم إلى أنه كان يرسل نصًا بتنسيق HTML. ولهذا السبب قام المتصفح بتفسير العلامة <h3>. لذلك، عندما تريد إرسال نص عادي، يفضل إرجاع [ContentResult] بدلاً من [string]. تسمح لنا [ContentResult] بتحديد نوع MIME "text/plain" للإشارة إلى أننا نرسل نصًا غير منسق، والذي لن يحاول المتصفح تفسيره.

3.8. إجراء بنوع [EmptyResult]

انظر الإجراء الجديد التالي:


    // Action05
    public EmptyResult Action05()
    {
      return new EmptyResult();
}

تقوم الإجراء ببساطة بإرجاع نوع [EmptyResult]. في هذه الحالة، يرسل الخادم استجابة فارغة إلى العميل، كما هو موضح في استجابة HTTP الخاصة به:

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Cache-Control: private
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNQ==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:11:12 GMT
Content-Length: 0
  • السطر 9: يُعلم الخادم عميله بأنه يرسل مستندًا فارغًا.

3.9. إجراء بنتيجة من النوع [RedirectResult] - 1

ضع في اعتبارك الإجراء الجديد التالي:


    // Action06
    public RedirectResult Action06()
    {
      return new RedirectResult("/First/Action05");
}

تُرجع الإجراء نوع [RedirectResult]. يسمح هذا النوع بإرسال طلب إعادة توجيه إلى العميل إلى عنوان URL المحدد في المنشئ (السطر 4). سيقوم العميل بعد ذلك بإرسال طلب GET جديد إلى [/First/Action05]. وبالتالي، يقوم العميل بإرسال طلبين إجمالاً.

يعرض المتصفح نتيجة الطلب الثاني:

Image

دعونا نفحص استجابة HTTP للخادم في Chrome:

Image

في الأعلى، نرى طلبين للمتصفح. دعونا نفحص الطلب الأول [Action06]. استجابة HTTP للخادم هي كما يلي:

HTTP/1.1 302 Found
Cache-Control: private
Content-Type: text/html; charset=utf-8
Location: /First/Action05
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNg==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:16:59 GMT
Content-Length: 132
  • السطر 1: استجاب الخادم برمز الحالة 302 Found. في السابق، كان الرمز 200 OK، مما يعني أنه تم العثور على المستند المطلوب. يشير الرمز 302 إلى أنه تم طلب إعادة توجيه. يتم توفير عنوان URL لإعادة التوجيه في السطر 4. وهذا يتطابق مع عنوان URL لإعادة التوجيه الذي حددناه في كود الإجراء؛
  • السطر 11: يشير الخادم إلى أنه مع استجابة HTTP الخاصة به، فإنه يرسل مستند text/html (السطر 3) بحجم 132 بايت (السطر 11). عندما نفحص الاستجابة لطلب [Action06] في Chrome، نجدها فارغة، كما هو متوقع. ربما هناك تفسير لذلك، لكنني لا أعرف ما هو.

بسبب إعادة التوجيه، يقوم المتصفح بإرسال طلب GET جديد إلى عنوان URL المحدد في السطر 4 أعلاه، كما يمكن رؤيته في Chrome في السطر 1 أدناه:

1
2
3
4
5
6
7
GET /First/Action05 HTTP/1.1
Host: localhost:49302
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4

3.10. إجراء بنتيجة من النوع [RedirectResult] - 2

ضع في اعتبارك الإجراء الجديد التالي:


    // Action07
    public RedirectResult Action07()
    {
      return new RedirectResult("/First/Action05",true);
}

في السطر 4، أضفنا معلمة ثانية إلى منشئ [RedirectResult]. وهي قيمة منطقية (boolean) تكون قيمتها الافتراضية false. وعند تعيينها إلى true، فإنها تُعدّل استجابة HTTP المرسلة إلى العميل. وتصبح:

HTTP/1.1 301 Moved Permanently

وبالتالي، أصبح رمز الاستجابة المرسل إلى العميل هو 301 Moved Permanently. تتم إعادة التوجيه كما في السابق، لكننا نشير إلى أن إعادة التوجيه هذه دائمة. وهذا يسمح لمحركات البحث باستبدال عنوان URL القديم بالعنوان الجديد في نتائجها.

3.11. إجراء بنتيجة من النوع [RedirectToRouteResult]

لننظر إلى الإجراء الجديد التالي:


    // Action08
    public RedirectToRouteResult Action08()
    {
      return new RedirectToRouteResult("Default",new RouteValueDictionary(new {controller="First",action="Action05"}));
}
  • السطر 2: تعرض الإجراء نوع [RedirectToRouteResult]. يسمح هذا النوع بإعادة توجيه العميل إلى عنوان URL محدد، ليس عبر سلسلة نصية كما في السابق، بل عبر مسار.

يتم تعريف المسارات في [App_Start/RouteConfig]. يوجد حاليًا مسار واحد فقط:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
  • في السطر 4، يتم توجيه العميل لإعادة التوجيه إلى المسار المسمى [Default] مع تعيين متغير وحدة التحكم على "First" ومتغير الإجراء على "Action05". سيقوم نظام التوجيه بعد ذلك بإنشاء عنوان URL لإعادة التوجيه /First/Action05. يظهر هذا في استجابة HTTP للخادم:
HTTP/1.1 302 Found
Cache-Control: private
Content-Type: text/html; charset=utf-8
Location: /First/Action05
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wOA==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:58:19 GMT
Content-Length: 132
  • السطر 1: إعادة التوجيه؛
  • السطر 4: عنوان URL لإعادة التوجيه الذي تم إنشاؤه بواسطة نظام توجيه عناوين URL.

3.12. إجراء بنوع إرجاع [void]

لننظر إلى الإجراء الجديد التالي:


    // Action09
    public void Action09()
    {
      string nom = Request.QueryString["nom"] ?? "inconnu";
      Response.AddHeader("Content-Type", "text/plain");
      Response.Write(string.Format("<h3>Action09</h3>nom={0}", nom));
}
  • السطر 2: لا تُرجع الإجراء أي نتيجة. بل تكتب مباشرةً إلى دفق الاستجابة المرسَل إلى العميل؛
  • السطر 4: نسترد معلمة محتملة باسم [name] من الطلب. يمكن الوصول إليها عبر الخاصية [Request] لفئة [Controller] التي يرث منها وحدة التحكم [First]. المعلمة [name] التي تم تمريرها في النموذج [/First/Action09?name=something] متاحة في Request.QueryString["name"]. صيغة السطر 4 تعادل:
string nom=Request.QueryString["nom"];
if(nom==null){
    nom="inconnu";
}
  • السطر 5: يمكن الوصول إلى الاستجابة المرسلة إلى العميل عبر خاصية [Response] لفئة [Controller] التي يرث منها وحدة التحكم [First
  • السطر 5: نقوم بتعيين رأس HTTP [Content-Type]، الذي يشير إلى طبيعة المستند الذي سيقوم الخادم بإرساله إلى العميل. هنا، يشير "text/plain" إلى أن المستند عبارة عن نص عادي لا يجب أن يفسره المتصفح؛
  • السطر 6: نكتب سلسلة من الأحرف في نص الاستجابة. وقد أدرجنا علامات HTML لا ينبغي للمتصفح تفسيرها، حيث إن المتصفح سيكون قد تلقى مسبقًا رأس HTTP [Content-Type: text/plain]. وهذا ما نريد التحقق منه.

دعونا نجمع المشروع ونطلب عنوان URL [/First/Action09?name=someone ][1] ثم عنوان URL [/First/Action09 ] [2]:

الآن دعونا نلقي نظرة على استجابة HTTP للخادم في Chrome:

1
2
3
4
5
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
...
Content-Length: 144
  • السطر 3: نرى رأس HTTP الذي قمنا بتعيينه بأنفسنا في كود الإجراء.

3.13. وحدة تحكم ثانية

لنقم بإنشاء وحدة تحكم ثانية في المشروع. سنتبع الطريقة الموضحة في القسم 3.1، في الصفحة 36. سنسميها [Second].

Image

والكود الذي تم إنشاؤه هو كما يلي:


namespace Exemple_01.Controllers
{
  public class SecondController : Controller
  {
    //
    // GET: /Second/
 
    public ActionResult Index()
    {
      return View();
    }
 
  }
}

دعونا نعدلها على النحو التالي:


using System.Text;
using System.Web.Mvc;
 
namespace Exemple_01.Controllers
{
  public class SecondController : Controller
  {
    // /Second/Action01
    public ContentResult Action01()
    {
      return Content("Contrôleur=Second, Action=Action01", "text/plain", Encoding.UTF8);
    }
 
  }
}

ثم لنطلب عنوان URL [/Second/Action01] باستخدام متصفح. نحصل على الاستجابة التالية:

Image

تم طلب عنوان URL هذا باستخدام طلب HTTP GET، كما هو موضح في سجلات HTTP للطلب في Chrome:

GET /Second/Action01 HTTP/1.1

يمكن أيضًا طلب عنوان URL هذا باستخدام طلب HTTP POST. لتوضيح ذلك، دعونا نستخدم تطبيق [Advanced Rest Client] مرة أخرى:

  • في [1]، قم بتشغيل التطبيق (في علامة التبويب [Applications] في علامة تبويب Chrome جديدة)؛
  • في [2]، حدد خيار [Request
  • في [3]، حدد عنوان URL المطلوب؛
  • في [4]، حدد أن عنوان URL يجب أن يُطلب باستخدام طلب POST؛

نقوم بتفعيل سجلات Chrome (CTRL-I) لعرض استجابة HTTP من الخادم. وعندما نضغط على [إرسال] لتنفيذ الطلب السابق، تكون تبادلات HTTP كما يلي:

يرسل المتصفح الطلب التالي:

POST /Second/Action01 HTTP/1.1
Host: localhost:49302
Connection: keep-alive
Content-Length: 0
Origin: chrome-extension://hgmloofddffdnphfgcellkdfbfbjeloo
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
  • السطر 1: تم بالفعل طلب عنوان URL باستخدام POST؛
  • السطر 4: حجم العناصر المنشورة بالبايت. لا يوجد أي منها هنا.

استجابة HTTP للخادم هي كما يلي:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxTZWNvbmRcQWN0aW9uMDE=?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 10:47:59 GMT
Content-Length: 148
  • السطر 3: يرسل الخادم مستند نصي غير منسق (عادي)؛
  • السطر 12: 148 حرفًا.

المستند المرسل هو كما يلي:

Image

نحصل على نفس المستند كما في طلب GET.

3.14. إجراء تمت تصفيته بواسطة سمة

لنقم بإنشاء الإجراء الجديد التالي:


    // /Second/Action02
    [HttpPost]
    public ContentResult Action02()
    {
      return Content("Contrôleur=Second, Action=Action02", "text/plain", Encoding.UTF8);
}

يشبه الإجراء [Action02] الإجراء [Action01]، لكنه يحدد أنه لا يمكن الوصول إليه إلا عبر طلب HTTP POST (السطر 2). يمكن استخدام سمات أخرى:

HttpGet
يخدم طلب GET فقط
HttpHead
يخدم طلب HEAD فقط
HttpOptions
يدعم فقط الأمر OPTIONS
HttpPut
يدعم فقط الأمر PUT
HttpDelete
يُستخدم فقط للأمر DELETE

دعونا نطلب عنوان URL [/Second/Action02] مباشرةً في المتصفح. ثم يتم طلبه عبر GET. بعد ذلك يعرض المتصفح الاستجابة التالية:

Image

كان رد HTTP من الخادم كما يلي:

1
2
3
HTTP/1.1 404 Not Found
...
Content-Length: 3807
  • السطر 1: يشير رمز HTTP 404 غير موجود إلى أن الخادم لم يتمكن من العثور على المستند المطلوب. هنا، لم تتمكن الإجراء [Action02] من معالجة طلب GET لأنها تعالج طلبات POST فقط؛
  • السطر 3: حجم المستند الذي تم إرجاعه. هذه هي الصفحة التي تم عرضها بواسطة المتصفح:

Image

3.15. استرداد العناصر من مسار

في الإجراءين الموصوفين أعلاه، كتبنا شيئًا مثل:


public ContentResult Action02()
    {
      return Content("Contrôleur=Second, Action=Action02", "text/plain", Encoding.UTF8);
}

تم ترميز أسماء وحدة التحكم والإجراء بشكل ثابت في الكود. إذا قمنا بتغيير هذه الأسماء، فلن يكون الكود صحيحًا بعد ذلك. يمكننا الوصول إلى وحدة التحكم والإجراء على النحو التالي:


    // /Second/Action03
    public ContentResult Action03()
    {
      string texte = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return Content(texte, "text/plain", Encoding.UTF8);
}

المسار المحدد في [App_Start/RouteConfig] هو كما يلي:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

السطر 3: يمكن استرداد عناصر المسار الثلاثة باستخدام RouteData.Values["element"], حيث element هو أحد [controller, action, id].

دعونا نطلب عنوان URL [http://localhost:49302/Second/Action03]:

Image

لقد نجحنا في استرداد اسم وحدة التحكم واسم الإجراء.