نکات قابل تامل در رابطه با فرایند مدل بایندینگ و مدل های تو در تو در ASP.NET MVC
  در این مقاله به معرفی یکی از مشکلات متداول توسعه گران تکنولوژی ASP.NET MVC جهت کار با مدل بایندر می پردازم
   ASP.NET MVC
   ۱۱۴۱۲
   دانلود
   مرتضی صحراگرد
   ۱۳۹۳/۶/۲۳
ارسال لینک صفحه برای دوستان ارسال لینک صفحه برای دوستان  اضافه کردن به علاقه مندیها اضافه کردن به علاقه مندیها   نسخه قابل چاپ نسخه قابل چاپ

 

سال ها از ارائه تکنولوژی ASP.NET MVC می گذرد و خوشبختانه هم اکنون این تکنولوژی کاملا بالغ شده و تبدیل به انتخاب اول جهت توسعه وب سایت ها تحت بستر شرکت مایکروسافت گردیده است. کتاب ها و ویدئو های فراوانی جهت آموزش جنبه های متخلف این تکنولوژی منتشر شده و می شوند ولی به عقیده بنده یکی از قسمت هایی که توجه و تاکیید مناسبی بر آن نشده است، مبحث مدل بایندینگ (Model Binding) می باشد. این موضوع باعث شده است که دیباگ نمودن و رفع برخی مشکلات در برنامه های تجاری پیچیده، بسیار زمانبر گردد. در این مقاله قست دارم به بررسی یک مشکل معمول برنامه نویسان هنگام کار با مدل های تو در تو (Nested Models) بپردازم.

همانطور که حتما مستحضر هستید و احتمالا در اکثر کتاب ها و مقالات آموزشی مرتبط ذکر شده است، مدل بایندر پیشفرض ASP.NET MVC بر اساس نام (Name) کنترل های موجود در صفحه وب (و نه ID) و تطبیق آن ها با نام خصوصیت های مدل، عمل بایندینگ را انجام می دهد.

تذکر:

در این مقاله ما در مورد ارسال متغیر ها از طریق عناصر موجود در فرم (Form) صفحه وب صحبت می کنیم. بنابراین بایندینگ متغیر ها از طریق Query String و Rout و ... خارج از بحث ما می باشند.

سورس کد این مقاله از طریق لینک بالای صفحه قابل دانلود می باشد.

فکر می کنم بهترین راه برای شروع بحث، طرح یک مثال می باشد. در این مقاله سعی شده است که مثال به ساده ترین شکل ممکن ارائه گردد تا به بهترین شکل ممکن گویای مقصود نویسنده مقاله گردد.

قصد داریم که مشخصات و آدرس یک شخص را به شکل قابل ویرایش به یک ویو ارسال کنیم. مدل ما شامل کلاس Person و Address به شکل زیر می باشد.

public class Person
{
    public string Name { getset; }
    public string Website { getset; }
    public Address Address { getset; }
}
 
public class Address
{
    public string Street { getset; }
    public int Number { getset; }
}


همانطور که می بینید، هر شخص دارای یک آدرس می باشد.

در ضمن ما از دو ویو جهت نمایش اطلاعات استفاده می کنیم. نام اولین ویو PersonEdit بوده که جهت ویرایش نام و وب سایت شخص استفاده شده است و دیگری _AddressEdit از نوع پارشال بوده و در درون PersonEdit فراخوانی می شود و جهت ویرایش آدرس کاربر استفاده می گردد.

در سناریوی اول محتوای دو ویو به شکل زیر می باشد.

PersonEdit:

@model MvcModelBindingSample.Models.Person
 
@{
    Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    @using (Html.BeginForm())
    {
        <div>
            <h4>Person</h4>
            <hr />
            <div>
                Name:
                <div>
                    @Html.TextBoxFor(model => model.Name)
                </div>
            </div>
            <div>
                Website:
                <div>
                    @Html.TextBoxFor(model => model.Website)
                </div>
            </div>
 
            @Html.Partial("_AddressEdit"Model)
 
            <input type="submit" value="Save" />
        </div>
    }
 
</body>
</html>


_AddressEdit
:

@model MvcModelBindingSample.Models.Person
@{
    Html.EnableClientValidation(false);
}
<div>
    <h4>Address</h4>
    <hr />
    <div>
        Street:
        <div>
            @Html.TextBoxFor(model => Model.Address.Street)
        </div>
    </div>
 
    <div>
        Number:
        <div>
            @Html.TextBoxFor(model => Model.Address.Number)
        </div>
    </div>
</div>


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

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

public ActionResult Index()
{
    var person = GetPerson();
    return View("PersonEdit",person);
}
 
[HttpPost]
public ActionResult Index(Person person)
{
 
    return Index();
}
 
private Person GetPerson()
{
    var person = new Person()
    {
        Name = "Morteza",
        Website = "http://www.30sharp.com",
        Address = new Address()
        {
            Street = "Street One",
            Number = 42
        }
    };
    return person;
}


اکنون برنامه را اجرا کنید. نتیجه باید چیزی شبیه شکل زیر باشد.


خوب، دکمه Save رو کلیک نموده و نگاهی به دیتای ارسال شده به سرور و عملکرد مدل بایندر می انداریم.


همانطور که مشخص می باشد، عمل بایندینگ با موفقیت انجام گردیده و کلاس های Person و Address با مقادیر مرتبط پر شده اند.

اکنون نگاهی به کدهای HTML تولید شده برای فرم خود می اندازیم.


در کدهای HTML تولید شده، ما نام دو کنترل رو با علامت قرمز مشخص نموده ایم تا در مورد آن ها بحث کنیم.

نام یکی از کنترل های این صفحه Name می باشد. این کنترل در ویو  PersonEdit قرار دارد و با توجه به اینکه مدل ارسال شده به این ویو از نوع Person می باشد، پس مدل بایندر تصور می کند که باید کلاسی به نام Person و خصوصیتی به نام Name وجود داشته باشد و مقدار این کنترل باید در آن قرارگیرد و درحقیقت الگویی شبیه به Person.Name مورد جستجو قرار می گیرد.

اما در مورد کنترل کنترل آدرس می بینیم که برای خیابان نامی به شکل Address.Street تولید شده است. با توجه به اینکه این کنترل در ویو _AddressEdit تولید شده است و به این ویو یک مدل از نوع Person ارسال شده است پس مدل بایند به جستجوی الگوی Person.Address.Street می گردد که البته در شی person در اکشن Index موجود بوده و بنابراین عملیات بایندینگ با موفقیت کامل انجام می گردد.

در این مثال با توجه به اینکه ما از متدهای کمکی TextBoxFor استفاده کرده بودیم، نام ها به شکل استاندارد تولید شده اند ولی اگر بخواهیم از معادل های این متدهای کمکی به طور مثال Html.TextBox استفاده کنیم، نتیجه یکسان خواهد بود.

@Html.TextBox("Address.Street", Model.Address.Street);


تذکر بسیار مهم:

با توجه به اینکه مدلی از نوع Person به ویو _AddressEdit  ارسال شده است، ما نام کنترل را Address.Street گذاشته ایم. اگر نام کنترل را به اشتباه Person.Address.Street بگذارید، عملیات بایندینگ برای مدل Person با شکست روبرو خواهد شد.

خوب، اکنون قصد داریم تغییر کوجکی در مثال به وجود آوریم. این بار می خواهیم کلاس Address را به عنوان مدل به ویو _AddressEdit ارسال کنیم در حالی که قبلا خود کلاس Person را ارسال نموده بودیم. پس در ویو Person تنها خط زیر تغییر می کند.

@Html.Partial("_AddressEdit"Model.Address)


اما در ویو _AddressEdit تغییرات زیر مشاهده می شود. در حقیقت تنها تغییر این است که به عنوان مثال به جای Model.Address.Street ما از Model.Street استفاده کرده ایم چون مدل ما اینبار از نوع Address است و نه Person.

دکمه Save را فشار داده و نتیجه رو ملاحظه می کنیم.


با کمال تعجب مشاهده می شود که مقدار Address پس از این تغییر null شده است! مشکل کجاست؟!

قبل از اینکه به جواب این سوال بپردازیم اجازه بدهید یک کلاس از نوع Address به پارامتر های اکشن اضافه نموده و نتیجه را ملاحظه کنیم.


همانطور که می بینید مدل بایندر با موفقیت شی address را پر نموده است! پس دلیل اینکه انجام این عمل در داخل شی person با شکست روبرو شده است، چیست؟

برای درک این موضوع بهتر است نگاهی به نام کنترل Street در کد HTLM تولید شده در مرورگر بیاندازیم.


همانطور که مشاهده می کنید این بار برای این کنترل تنها نام Street تولید شده است. پس با توجه به اینکه مدلی از نوع Address به این ویو ارسال شده است، مدل بایندر به دنبال الگوی Address.Street می گردد که در شی person موجود نیست. در حقیقت مدل بایندر باید دنبال الگوی Person.Address.Street می گشت تا در شی person آن را پیدا می کرد. اما این الگو در شی address یافت شده و بنابراین این شی مقدار دهی شده است.

نتیجه مهم:

عملیات مدل بایندینگ با توجه به مدلی که به هر ویو ارسال می شود به شکل مستقل از سایر ویو ها انجام می گردد (شاید این جمله لزوما و به شکل 100 درصد صحیح نباشد! اصولا وقت قرار به آموزش مبحثی نسبتا پیچیده به زبان ساده باشد، متاسفانه در بسیاری موارد باید مقداری از دقت را قربانی آموزش روان و آسان کرد. امیدوارم موارد مطرح شده در این مقاله مورد استفاده مخاطبین قرار گرفته باشد و دید بهتری به شما داده باشد). بنابراین اگر در سناریو های واقعی و برنامه های تجاری قصد ایجاد یه ویوی اصلی و تعدادی پارشال ویو را داشتید، هنگام ارسال مدل به ویوهای پارشال باید نحوه انجام عملیات مدل بایندینگ را هم در نظر داشته باشید.

سورس کد این مقاله از طریق لینک بالای صفحه قابل دانلود می باشد.