اخبار، مطالب و رویدادهای مرتبط با توسعه نرم افزار رادکام

تفاوت‌های MVC core با MVC عادی (بخش دوم)

1. پشتیبانی داخلی از Dependency injection در Asp.net core

با dependency injection به کد با قابلیت تست بالاتر و loosely coupled دسترسی پیدا می‌کنیم. این قابلیت از اهمیت بالایی برخوردار است چراکه در نوشتن Unit testing به ما کمک می‌کند. در asp.net mvc نسخه 5، 4 یا حتی اپلیکیشن‌های کلاسیک بر پایه ASPX باید از نگه‌دارنده DI جداگانه مثل Unity، AutoFac، StructureMap و غیره استفاده می‌کردیم.

حالا در asp net core به‌صورت داخلی از این قابلیت پشتیبانی می‌شود و خبری از پیشنیازهای سنگین نیست. تنها کافی است که سرویس‌های موردنیاز خود را در فایل startup.cs به IserviceCollection اضافه کنیم.

هدف اصلی این فایل و کلاس، اضافه کردن سرویس‌هایی نظیر EF، authentication، IEmailSender و ISmsSender است.

2. User secrets در aps net core

گاهی اوقات در زمان توسعه پروژه ممکن است که اطلاعات مهم و حساس پروژه را که نباید در معرض دید عموم باشند مثل اطلاعات ارتباط با پایگاه داده یا توکن‌های شناسایی در زمان استفاده از API های خارجی، سهواً به public repository ها کامیت کنیم و باید از چنین کاری اجتناب کرد.

در asp net mvc core ابزاری به نام user secrets وجود دارد که در آن می‌توانیم اطلاعات این‌چنین را در مکانی خارج از پروژه نگهداری کنیم تا این اطلاعات به TFS یا سورس کنترل آپلود نشوند. تفاوت‌های بسیاری بین این ابزار با نسخه‌های قبلی و شیوه‌های جلوگیری از آپلود این اطلاعات که در گذشته استفاده می‌شده وجود دارد و به همین دلیل وارد جزئیات نمی‌شویم، جهت مطالعه در این زمینه به مقاله The Secrets of ASP.NET Core User Secrets در منابع مراجعه فرمایید.

3. مقایسه razor pages با mvc

به‌عنوان بخشی از .NET Core 2.0 آپدیت‌هایی هم برای asp.net عرضه شد. یکی از این آپدیت‌ها فریمورک جدیدی تحت عنوان Razor pages بود که نسخه‌ای سبک‌تر از mvc framework است و در مواقعی شبیه به نسخه‌ای بهبودیافته از فرم‌های .aspx در WebForms به نظر می‌رسد.

Asp.net razor pages چیست

یک Razor page بسیار شبیه به view component در asp.net mvc است. نحوه نوشتن و قابلیت‌های مشابه دارد.

تفاوت اساسی در اینجاست که مدل، ویو (View) و کنترلر همگی در خود razor page قرار دارند. ساختار این فریمورک بیشتر به MVVM (Model-view-viewmodel) شباهت دارد. امکان two-way binding را فراهم می‌کند و در seperation of concerns یا soc یا به فارسی: تفکیک دغدغه‌ها، تجربه ساده‌تری را به همراه دارد.

در پایین می‌توانیم یک مثال ساده از razor pages را ببینیم که کد به‌صورت inline داخل بلوک @functions قرار دارد. بد نیست بدانید که نوشتن کد در صفحه cshtml اصلاً توصیه نمی‌شود و بهتر است که تمام کدهای C# را به PageModel در فایل جداگانه انتقال دهیم. این کار شبیه به ساختار کد و صفحات در web form است.


@page
@model IndexModel
@using Microsoft.AspNetCore.Mvc.RazorPages

@functions {
public class IndexModel : PageModel
{
    public string Message { get; private set; } = "In page model: ";

    public void OnGet()
    {
        Message += $" Server seconds  { DateTime.Now.Second.ToString() }";
    }
}
}

<h2>In page sample</h2>
<p>
@Model.Message
</p>

 

حالا دو انتخاب پیش رو داریم: MVC یا MVVM

می‌توان این‌طور فکر کرد که حالا دو انتخاب برای فریمورک MVC یا MVVM پیش رو داریم.. قرار نیست وارد جزئیات این دو ساختار بشویم اما تفاوت‌های این دو را می‌توانید در مقاله Understanding MVC, MVP and MVVM Design Patterns در بخش منابع بخوانید. تنها به خاطر داشته باشید که فریمورک‌های MVVM عموماً به خاطر ارتباط two-way binding خود با data model موردتوجه هستند.

MVC برای پروژه‌های زیر مناسب است:

  1. اپلیکیشن‌هایی که تعداد زیادی ویوی دینامیک سمت سرور دارند.
  2. اپلیکیشن‌های SPA یا Single page.
  3. REST API
  4. ajax calls

 

Razor pages برای صفحاتی که read-only هستند یا اطلاعات ابتدایی از کاربر می‌گیرند مناسب است.

خوبی‌ها و بدی‌های razor pages

توجه کنید کسی که این لیست را نوشته است 15 سال تجربه کار با فریمورک‌های asp net را دارد:

1. نظم بیشتر و جادوی کمتر

کسی که برای اولین بار با asp net mvc آشنا می‌شود، درک شیوه مسیردهی URL و کنترلر برایش خیلی عجیب و غیرقابل درک است و طول می‌کشد تا به آن عادت کند. به‌عنوان‌مثال درک اینکه صفحه /home/ به اکشن index از HomeController متصل می‌شود دشوار است.

درک Razor page با ساختار شبیه به webForm آن ساده‌تر است، چراکه تمام کدهای هر صفحه، پشت همان صفحه قرار می‌گیرد.

2. Single responsibility از SOLID

اگر قبلاً از فریمورک MVC استفاده کرده باشید کلاس‌های عظیم کنترلر را دیده‌اید که با تعداد زیادی اکشن پر می‌شوند. مثل یک ویروس می‌مانند که به‌مرورزمان رشد می‌کند.

با razor page، هر پیج کدهای خود را در فایل cs پشت ویو و محتوای ویو را در cshtml نگه می‌دارد. این رویکرد تضمین‌کننده single responsibility نیز است.

3. استفاده از چندین اکشن POST یا GET به کمک handler ها

هر razor page به‌صورت پیش‌فرض دو متد OnGetAsync و OnPostAsync دارد. اگر می‌خواهید که درون فایل cs پیج خود اکشن‌های مختلفی داشته باشید، باید از چیزی به نام handler استفاده کنید. اگر پیج شما AJAX callback، چند نقطه برای submit فرم یا دیگر سناریوها داشته باشد، به این قابلیت نیاز پیدا می‌کنید.

پس به‌عنوان‌مثال اگر از یک Kendo grid استفاده کرده و می‌خواهید که برای آن از ajax call استفاده کنید، باید از یک handler برای پاسخ دادن به ajax call استفاده کنید. هر نوعی از SPA از تعداد زیادی handler استفاده می‌کند و نهایتاً مجبور می‌شویم که تمام این AJAX call ها را به سمت یک کنترلر MVC هدایت کنیم.

تصور کنید در فایل cs ویو، متدی اضافی به نام OnGetHelloWorldAsync() ایجاد کردیم. چطور می‌توانیم این متد را فراخوانی کنیم؟

3 راه متفاوت برای استفاده از handler ها وجود دارد:

  • استفاده از query string. مثل:
    /managepage/2177/?handler=helloworld
  • به‌عنوان یک route در ویو آن را معرفی کنیم: @page"{handler?}" . بعد در url از /helloworld استفاده کنیم.
  • روی دکمه submit آن را معرفی کنیم مثل:
    <input type=”submit” asp-page-handler=”JoinList” value=”Join” />

 

در مقاله Introduction to Razor Pages in ASP.NET Core در بخش منابع می‌توانید مثال‌های بیشتری ببینید.

4. یک سناریوی خوب استفاده از Razor pages

نویسنده در این قسمت پیشنهاد داده که تمام ویوها را به‌صورت razor page ایجاد کنیم و موارد زیر را به کنترلر MVC انتقال دهیم:

  • تمام متدهایی که قرار است به ajax calls پاسخ دهند
  • صرفاً یک عملیات را انجام می‌دهند و Status code بازگردانی می‌کنند.
  • یک کالکشن یا شیء (Object) در قالب JSON بازگردانی می‌کنند.

 

این رویکرد مشکل به‌هم‌ریختگی و سنگین بودن کنترلرها را برطرف می‌کند.

توجه کنید که از PageModel هم می‌توان به کمک JsonResult() مقدار JSON بازگردانی کرد و محدودیت فنی نداریم. این رویکرد صرفاً یک قرارداد خواهد بود.

5. مقایسه کد بین razor page و MVC

حالا می‌خواهیم که یک فرم ساده را در هردو حالت MVC و razor page مقایسه کنیم.

ویوی MVC:


@model RazorPageTest.Models.PageClass

<form asp-action="ManagePage">
<p class="form-horizontal">
    <h4>Client</h4>
    <hr />
    <p asp-validation-summary="ModelOnly" class="text-danger"></p>
    <input type="hidden" asp-for="PageDataID" />
    <p class="form-group">
        <label asp-for="Title" class="col-md-2 control-label"></label>
        <p class="col-md-10">
            <input asp-for="Title" class="form-control" />
            <span asp-validation-for="Title" class="text-danger"></span>
        </p>
    </p>
  
    <p class="form-group">
        <p class="col-md-offset-2 col-md-10">
            <input type="submit" value="Save" class="btn btn-default" />
        </p>
    </p>
</p>
</form>


کنترلر MVC: (مدل این ویو PageCLass با تنها دو property است)


public class HomeController : Controller
{
    public IConfiguration Configuration;

    public HomeController(IConfiguration config)
    {
        Configuration = config;
    }

    public async Task<IActionResult> ManagePage(int id)
    {
        PageClass page;

        using (var conn = new SqlConnection(Configuration.GetConnectionString("contentdb")))
        {
            await conn.OpenAsync();

            var pages = await conn.QueryAsync<PageClass>("select * FROM PageData Where PageDataID = @p1", new { p1 = id });

            page = pages.FirstOrDefault();
        }

        return View(page);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> ManagePage(int id, PageClass page)
    {

        if (ModelState.IsValid)
        {
            try
            {
                //Save to the database
                using (var conn = new SqlConnection(Configuration.GetConnectionString("contentdb")))
                {
                    await conn.OpenAsync();
                    await conn.ExecuteAsync("UPDATE PageData SET Title = @Title WHERE PageDataID = @PageDataID", new { page.PageDataID, page.Title});
                }
            }
            catch (Exception)
            {
               //log it
            }
            return RedirectToAction("Index", "Home");
        }
        return View(page);
    }
}


ویوی Razor page:


@page "{id:int}"
@model RazorPageTest2.Pages.ManagePageModel

<form asp-action="ManagePage">
<p class="form-horizontal">
    <h4>Manage Page</h4>
    <hr />
    <p asp-validation-summary="ModelOnly" class="text-danger"></p>
    <input type="hidden" asp-for="PageDataID" />
    <p class="form-group">
        <label asp-for="Title" class="col-md-2 control-label"></label>
        <p class="col-md-10">
            <input asp-for="Title" class="form-control" />
            <span asp-validation-for="Title" class="text-danger"></span>
        </p>
    </p>

    <p class="form-group">
        <p class="col-md-offset-2 col-md-10">
            <input type="submit" value="Save" class="btn btn-default" />
        </p>
    </p>
</p>
</form>


Razor PageModel (یا کد cs پشت ویو):


public class ManagePageModel : PageModel
{
    public IConfiguration Configuration;

    public ManagePageModel(IConfiguration config)
    {
        Configuration = config;
    }

    [BindProperty]
    public int PageDataID { get; set; }
    [BindProperty]
    public string Title { get; set; } 

    public async Task<IActionResult> OnGetAsync(int id)
    {
        using (var conn = new SqlConnection(Configuration.GetConnectionString("contentdb")))
        {
            await conn.OpenAsync();
            var pages = await conn.QueryAsync("select * FROM PageData Where PageDataID = @p1", new { p1 = id });

            var page = pages.FirstOrDefault();

            this.Title = page.Title;
            this.PageDataID = page.PageDataID;
        }

        return Page();
    }

    public async Task<IActionResult> OnPostAsync(int id)
    {

        if (ModelState.IsValid)
        {
            try
            {
                //Save to the database
                using (var conn = new SqlConnection(Configuration.GetConnectionString("contentdb")))
                {
                    await conn.OpenAsync();
                    await conn.ExecuteAsync("UPDATE PageData SET Title = @Title WHERE PageDataID = @PageDataID", new { PageDataID, Title });
                }
            }
            catch (Exception)
            {
               //log it
            }
            return RedirectToPage("/");
        }
        return Page();
    }
}


مقایسه Razor Page با MVC

کد بین این دو تقریباً یکسان است. در پایین می‌توانید تفاوت‌های این دو را بخوانید:

  1. ویوی هردو کاملاً یکسان است. با این تفاوت که Razor page یک مقدار @page اضافه‌تر دارد.
  2. ManagePageModel دو متد OnGetAsync و OnPostAsync داشت که جایگزین دو اکشن از کنترلر ManagePage شدند.
  3. ManagePageModel دارای دو پراپرتی است که قبلاً در کلاس جدای PageClass بودند.

 

در mvc برای یک درخواست HTTP POST شیء را به MVC Action ارسال می‌کردیم (مثل ManagePage(int id, PageClass page))؛ اما با Razor page به جای این کار از two-way databinding استفاده می‌کنیم. برای اینکه databinding کار کند، باید برای پراپرتی‌هایی که می‌خواهیم دیتا انتقال دهند، از صفت [BindProperty] استفاده کنیم. اگر بخواهیم هم می‌توانیم بالای کلاس موردنظر، صفت [BindProperties] قرار دهیم.

منابع

 

پست های مرتبط

نام را وارد کنید
تعداد کاراکتر باقیمانده: 1000
نظر خود را وارد کنید