مقدمة الى Model Binding في ASP.NET Core

-

في تركيب MVC يوجد مجموعة من المجلدات منها مجلد باسم Models  حيث يحتوي على البيانات التي يتعامل التطبيق.و Models هي عبارة عن C# classes تستخدم لمليء البيانات من قاعدة بيانات أو اي مصدر آخر بواسطة Controllers. 

في هذا الدرس سنتطرق الى موضوعين مهمين في Models هما:

  • Model Binding :  في هذه العملية يتم استخراج البيانات من HTTP request وتقديمها ك arguments  إلى action method’s.
  • Model Validation : في هذه العملية يتم التحقق من صحة خصائص Model بحيث لا يتم إدخال إدخالات غير صالحة في قاعدة البيانات.
سنتابع العمل على المشروع السابق StudentsAcademy
نقاط مهمه للتذكير:
  • تأكد من وجود ملف Razor View Imports المسمى ViewImports.cshtml_، ومن ثم أضف الكود التالي لاستيراد namespaces و Tag Helpers.
@using StudentsAcademy
@using StudentsAcademy.Models
@using Microsoft.AspNetCore.Http
@addTagHelper StudentsAcademy.TagHelpers.*, StudentsAcademy
@inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
  • إعداد المشروع 
انتقل الى class Startup.cs.
ثم قم بإنشاء service  لفئة StudentRepository بحيث يمكن ل controller الوصول إلى هذه class باستخدام dependency injection.
اضف الكود التالي: 
services.AddSingleton<StudentRepository>();
تمام بعد ما تتأكد من اتمام الاعدادت السابقه بنكون جاهزين نبدأ.

ما هو Model Binding
Model Binding 
هي عملية في ASP.NET Core framework تستخدم لاستخراج البيانات من HTTP Requests ومن ثم يتم قراءتها واستقبالها ك arguments في Action Method.

لفهم ذلك بشكل أفضل سنعود الى AllStudent View السابق.
الكود الخاص بهذا view هو 

@model List<StudentsModel>
<link href="~/lib/bootstrap/dist/css/bootstrap.css.map" rel="stylesheet" />
<table class="table table-bordered table-sm table-striped">
    <thead>
        <tr>
            <th scope="col">Student No</th>
            <th scope="col">FullName</th>
            <th scope="col">Birthday</th>
            <th scope="col">Address</th>
            <th scope="col">UserName</th>
            <th scope="col">Email</th>
            <th scope="col">Mobile</th>
            <th scope="col">CreatedDate</th>
            <th scope="col">Update</th>
            <th scope="col">Del</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var student in Model)
        {
        <tr>
            <td>
                @student.StudentNo
            </td>
            <td>
                @student.FullName
            </td>
            <td>
                @Convert.ToDateTime(@student.Birthday).ToString("dd/MM/yyyy")
            </td>
            <td>
                @student.Address
            </td>
            <td>
                @student.UserName
            </td>
            <td>
                @student.Email
            </td>
            <td>
                @student.Mobile
            </td>
            <td>
                @string.Format("{0:dd MMMM yyyy}", @student.CreatedDate)
            </td>
            <td>
                <form asp-action="Delete" method="post" asp-route-id="@student.StudentID" >
                    <button background-color="danger">Delete</button>
                </form>
            </td>
            <td><a asp-action="Update" asp-route-id="@student.StudentID"  background-color="success">Update</a></td>
        </tr>
        }
    </tbody>
</table>
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }

لاحظ انه تم إضافة Tag Helper  للأزرار Delete و Update حيث ارسلنا القيمة danger الى الزر Delete والقيمة success الى الزر Update
قم بتشغيل المشروع بالانتقال الى الرابط: 

https://localhost:44382/students/allstudent

لاحظ ان امر update سيكون بالشكل التالي: 

<a href="/Students/Update/1/" class="btn btn-success">Update</a>
حيث قمنا باستدعاء ال action المسمى Update في StudentsController وقمنا بإرسال رقم ال Student الى هذا action باستخدام id parameter 
هنا يتم تشغيل عملية Model Binding  لأن عنوان URL الذي طلبته يحتوي على قيمة id  ل Student 
وفقًا ل url routes الواردة في Startup.cs، فإن الجزء الثالث من عنوان URL مخصص ل Id segment.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

بالانتقال الى Update action في StudentsController ستلاحظ انه تم إضافة argument باسم id كما في الكود في الأسفل: 

public IActionResult Update(int id)
        {
            string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
            StudentsModel studentsModel = new StudentsModel();
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                string sql = $"Select * From Students Where StudentId={id}";
                SqlCommand command = new SqlCommand(sql, connection);
                connection.Open();
                using (SqlDataReader dataReader = command.ExecuteReader())
                {
                    while (dataReader.Read())
                    {
         studentsModel.FullName = Convert.ToString(dataReader["FullName"]);
         studentsModel.StudentNo = Convert.ToString(dataReader["StudentNo"]);
         studentsModel.Birthday = Convert.ToDateTime(dataReader["Birthday"]);
         studentsModel.Address = Convert.ToString(dataReader["Address"]);
         studentsModel.UserName = Convert.ToString(dataReader["UserName"]);
         studentsModel.Email = Convert.ToString(dataReader["Email"]);
         studentsModel.Mobile = Convert.ToString(dataReader["Mobile"]);
         studentsModel.CreatedDate = Convert.ToDateTime(dataReader["CreatedDate"]);
                    }
                }
                connection.Close();
            }
            return View(studentsModel);
        } 
هنا يعمل ASP.NET Core على استخراج قيمة id من عنوان URL، ومن ثم يقدمها إلى id argument  في  action method. 
يبحث framework  عن قيمة argument  في  action method’s في الأماكن الثلاثة التالية:
Form data values
Routing variables
Query strings
يعمل ASP.NET Core  على فحص القيم في كل مكان بدءًا من قيم Form data values  ، ثم  Routing variable وأخيرًا  Query String. 
في المثال السابق 
اذا تم فتح الرابط التالي:
https://localhost:44382/Students/Update/1/


في هذه الحالة يبدا framework بالبحث عن قيمة id في قيم Form data ، وبما انه لا يوجد أي قيم هنا في Form data ينتقل بعدها لفحص routing variables  حيث تم ارسال قيمة Id هنا في Route، هنا يكون البحث قد انتهى، بدون إجراء بحث في Query String.
ترتيب البحث مهم في ASP.NET Core.
إذا قمت بفتح عنوان
https://localhost:44382/Students/Update/1?id=2/ 
فسترى سجل id الطالب رقم 1 ، كما هو موضح في الصورة أدناه:

في المثال السابق ارسلنا الرقم 2 كقيمة ل Id أيضا عن طريق Query String ولكن ولأنه تم ارسال قيمة Id في Route أيضا فسيتم تطبيق الأولوية في استخدم القيم 

إذا قمت بفتح عنوان
https://localhost:44382/Students/Update?id=2
فستكون النتيجة:

هنا ارسلنا قيمة Id في Query String لذا لن يجد framework قيمة id في Form Data و URL segment  ، لذلك ينتقل للبحث في query string حيث سيجد قيمة id  تساوي 2 وسيتم عرض البيانات بناء على ذلك.
القيم الافتراضية ل Binding
في حال لم يجد ASP.NET Core framework قيم ل argument   فيaction method’s  في أي من المواقع الثلاثة  Form data values, Routing variables & Query strings. 
في هذه الحالة، يوفر ASP.NET Core مجموعة من القيم الافتراضية بناءً على نوع argument في action method’s:
الجدول التالي يوضح ذلك: 

نوع argument
القيمة الافتراضية
int
0
string
""
DateTime
01-01-0001 00:00:00
float
0
لاختبار default binding value ، 
قم بالانتقال الى الرابط:
https://localhost:44382/Students/Update
حيث لم نقم بإرسال أي قيمة لمتغير Id 
الآن ضع breakpoint على action method Update في StudentsController  ثم قم بتشغيل التطبيق الخاص بك. 
ضع مؤشر الماوس فوق متغير id  للتحقق من قيمته. سترى القيمة 0، كما هو موضح في الصورة أدناه:

نظرًا لعدم وجود طالب يحمل id بقيمة 0 فستكون نتيجة التنفيذ كما في الصورة:

لاحظ أن القيم الافتراضية لأنواع nullable هي null.
للحصول على قيمة nullable ل argument  قم بإضافة العلامة ? قبل اسم argument  . 
لتطبيق ذلك انتقل الى Update action في StudentsController   ثم عدل الكود الى الاتي: 

public IActionResult Update(int? id)

///// your code here
}
قم بتشغيل التطبيق الخاص بك وانتقل إلى
https://localhost:44382/Students/Update
 وستجد أن قيمة id هي Null. كما في الصورة: 




استخدام Model Binding في الأنواع البسيطة
الأنواع البسيطة هي string, int, bool, float, datetime, decimal الخ
استخدمنا بعض هذه الأنواع في المثال السابق حيث ارسلنا argument من نوع int  كما في الكود في الأسفل:
public IActionResult Update(int? id)
{
///// your code here
}
مثال ذلك الرابط 
https://localhost:44382/Students/Update/1/
يقوم ASP.NET Core framework تلقائيا بتحويل هذا id التي تساوي 1 الى قيمة int وتمريرها الى action ومن ثم تنفيذ العميلة. 
مثال آخر الرابط 
https://localhost:44382/Students/Update/hatem/

تم ارسال هنا argument من نوع string لذا سيحاول ASP.NET Core framework تحويل هذه القيمة الى int ، لكن النتيجة ستكون خطأ بالتأكيد 

استخدام Model Binding في الأنواع المعقدة
يمكن أيضا استخدام Model Binding مع الأنواع المعقدة مثل class object، حيث يتم ارسال model class ك argument الى action، في هذه الحالة فإن عملية Model Binding  تحصل على جميع الخصائص العامة للنوع المعقد من model class الذي تم استخدامه.
تبحث عملية Model Binding  عن قيم هذه الخصائص العامة في الأماكن الثلاثة التالية:
Form data values
Routing variables
Query strings
قمنا باستخدام هذه الخاصية سابقا في انشاء ملف طالب جديد. للمزيد سنقوم بمراجعة ذلك.
انتقل الى StudentsController وتأكد من وجود action  باسم NewStudent من نوع [HttpPost]  وآخر من نوع   [HttpGet]كما في الكود في الأسفل :

       // HTTP GET VERSION
        public IActionResult NewStudent()
        {
            return View();
        }
        [HttpPost]
        public IActionResult NewStudent(StudentsModel studentsModel)
        {
            string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                string sql = "Add_New_Student";
                using (SqlCommand command = new SqlCommand(sql, connection))
                {
                    command.CommandType = CommandType.StoredProcedure;
                    // adding parameters
                    SqlParameter parameter = new SqlParameter
                    {
                        ParameterName = "@FullName",
                        Value = studentsModel.FullName,
                        SqlDbType = SqlDbType.NVarChar,
                        Size = 200
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Birthday",
                        Value = studentsModel.Birthday,
                        SqlDbType = SqlDbType.DateTime
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Gender",
                        Value = studentsModel.Gender,
                        SqlDbType = SqlDbType.Int
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Nationality",
                        Value = studentsModel.Nationality,
                        SqlDbType = SqlDbType.Int
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Address",
                        Value = studentsModel.Address,
                        SqlDbType = SqlDbType.NVarChar,
                        Size = 500
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@UserName",
                        Value = studentsModel.UserName,
                        SqlDbType = SqlDbType.VarChar,
                        Size = 10
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Password",
                        Value = studentsModel.Password,
                        SqlDbType = SqlDbType.VarChar,
                        Size = 200
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Email",
                        Value = studentsModel.Email,
                        SqlDbType = SqlDbType.VarChar,
                        Size = 50
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Mobile",
                        Value = studentsModel.Mobile,
                        SqlDbType = SqlDbType.VarChar,
                        Size = 10
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Note",
                        Value = studentsModel.Note,
                        SqlDbType = SqlDbType.VarChar
                    };
                    command.Parameters.Add(parameter);
                    parameter = new SqlParameter
                    {
                        ParameterName = "@Result",
                        SqlDbType = SqlDbType.VarChar,
                        Size = 50,
                        Direction = ParameterDirection.Output
                    };
                    command.Parameters.Add(parameter);
                    connection.Open();
                    command.ExecuteNonQuery();
                    string result = Convert.ToString(command.Parameters["@Result"].Value);
                    ViewBag.Result = result;
                    connection.Close();
                }
            }
            ViewBag.Result = "Success";
            return View("AllStudent");
        }
في النوع HTTP GET يتم تحديد الطريقة طريقة NewStudent من default View ولم يمرر أي model إليها.
في النوع HTTP POST يتم تمرير argument من نوع StudentsModel class (وهو نوع معقد).
يتم تمرير model من نوع StudentsModel الى هذا action عن طريق HTTP Request حيث تقوم ميزة Model Binding باستخراج الخصائص من هذا StudentsModel من HTTP Request ويتم استقبالها في action على شكل model بنفس الخصائص ومن ثم يتم عرضها في view المطلوب 
بالانتقال الى view الخاص بإنشاء سجل طالب NewStudent جديد سيكون الكود:

@model StudentsModel
@{
    Layout = null;
}
<meta name="viewport" content="width=device-width" />
<title>Index</title>
<link href="~/lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
@*Welcome ,@TempData["UserName"]*@
    Welcome,@HttpContextAccessor.HttpContext.Session.GetString("UserName")
    <hr />
    <div class="container">
        <form id="external-account" asp-page="" asp-route-returnUrl="" method="post" class="form-horizontal">
            <div class="form-group">
                <label>FullName:</label>
                <input class="form-control" asp-for="FullName" />
                <span asp-validation-for="FullName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label>Birthday:</label>
                <input class="form-control" asp-for="Birthday" />
                <span asp-validation-for="Birthday" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label>Gender:</label>
                <select class="form-control" asp-for="Gender">
                    <option value="M">Male</option>
                    <option value="F">Female</option>
                </select>
                <span asp-validation-for="Gender" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label>Nationality:</label>
                <select class="form-control" asp-for="Nationality">
                    <option value="M">Jordan</option>
                    <option value="F">Saudi Aribae</option>
                    <option value="F">UAE</option>
                </select>
                <span asp-validation-for="Nationality" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label>Address:</label>
                <input class="form-control" asp-for="Address" />
                <span asp-validation-for="Address" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label>UserName:</label>
                <input class="form-control" asp-for="UserName" />
                <span asp-validation-for="UserName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label>Password:</label>
                <input class="form-control" asp-for="Password" />
                <span asp-validation-for="Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label>Email:</label>
                <input class="form-control" asp-for="Email" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label>Mobile:</label>
                <input class="form-control" asp-for="Mobile" />
                <span asp-validation-for="Mobile" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label>Note:</label>
                <input class="form-control" asp-for="Note" />
                <span asp-validation-for="Note" class="text-danger"></span>
            </div>
            <button class="btn btn-primary" type="submit">Submit</button>
        </form>
    </div>
    @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
في بداية هذا view استخدمنا model StudentsModel@ التي تعمل على ربط خصائص هذا model مع view، حيث سيتعرف View بشكل تلقائي على الخصائص الموجودة داخل هذا View 
في هذا View قمنا بإضافة form بحيث يعمل على ربط الخصائص العامة ل StudentsModel.
عند النقر فوق زر إرسال التابع ل form، 
الكود: 
<button class="btn btn-primary" type="submit">Submit</button>
 يتم ترحيل القيم التي تم إدخالها في form وتم ربطها تلقائيا مع خصائص StudentsModel الى  NewStudent  action من النوع   HTTP POST.
لاحظ في الكود هنا اننا لم تقم بتحديد controller  و action وذلك لان هذا View يحتوي على نفس اسم action في نفس controller 
يتم حفظ القيم هنا داخل form data باستخدام asp-for tag helper التي تم استخدامها مع input elements ، مثال ذلك 
الكود التالي: 

<div class="form-group">
  <label>FullName:</label>
  <input class="form-control" asp-for="FullName" />
  <span asp-validation-for="FullName" class="text-danger"></span>
</div>

يعمل على ريط input element مع اسم الطالب، وبنفس الطريقة يتم ربط بقية الخصائص.
بالرجوع الى NewStudent action ستجد انه يستقبل parameter من نوع StudentsModel، وقمنا قبل قليل بتعبئة هذا model من form. 
حسب ترتيب عمليات البحث تبدأ عملية Model Binding بالبحث أولاً عن القيم في  form dataالتي تم تعبئتها هنا. 
قم بتشغيل التطبيق بالانتقال الى الرابط: 
https://localhost:44382/Students/NewStudent
ثم قم بتعبئة الصفحة بالبيانات ومن ثم انقر فوق Submit

استخدام Model Binding  للأنواع Complex Type التي تحتوي على أنواع معقده أخرى Complex Type
سنعمل على جعل StudentsModel من النوع Complex.
لذا انتقل الى الملف StudentsModel class ثم أضف public property جديده باسم HomeAddress  كما في الكود في الأسفل: 

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace StudentsAcademy.Models
{
    public class StudentsModel
    {
        public int StudentID { get; set; }
        public string StudentNo { get; set; }
        [Required(ErrorMessage = "Please enter name")]
        [StringLength(150)]
        public string FullName { get; set; }
        [DataType(DataType.Date)]
        public DateTime Birthday { get; set; }
        public int Gender { get; set; }
        public int Nationality { get; set; }
        public string Address { get; set; }
        [Required(ErrorMessage = "Please enter User Name")]
        [StringLength(20)] 
        public string UserName { get; set; }
        [Required(ErrorMessage = "Please enter Password")]
        [StringLength(20)]
        [DataType(DataType.Password)]
        public string Password { get; set; }
        [Required(ErrorMessage = "Confirm Password is required")]
        [DataType(DataType.Password)]
        [Compare("Password")]
        public string ConfirmPassword { get; set;}
        [Required(ErrorMessage = "Please enter Email")]
        [RegularExpression("^[a-z0-9_\\+-]+(\\.[a-z0-9_\\+-]+)*@[a-z0-9-]+(\\.[a-z0-9]+)*\\.([a-z]{2,4})$", ErrorMessage = "Invalid email format.")]
        [StringLength(20)]
        public string Email { get; set; }
        [Required(ErrorMessage = "Mobile is required")]
        [RegularExpression(@"\d{10}", ErrorMessage = "Please enter 10 digit Mobile No.")]
        public string Mobile { get; set; }
        public string Note { get; set; }
        public DateTime CreatedDate { get; set; }
        public DateTime CurrentDate { get; set; }
        public PaymentModel Payment { get; set; }
        public Address StudentAddress { get; set; }
    }
    public class Address
    {
        public string HouseNumber { get; set; }
        public string Street { get; set; }
        public string City { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
    }
}
لاحظ انه تم ربط كل من PaymentModel و Address في StudentsModel حيث ان كلاهما عبارة عن public class.
الكود الخاص ب PaymentModel تم اضافته في نفس StudentsModel
الكود : 

    public class Address
    {
        public string HouseNumber { get; set; }
        public string Street { get; set; }
        public string City { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
    }
الكود الخاص ب Address تم اضافته في مجلد Models 
الكود:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace StudentsAcademy.Models
{
    public class PaymentModel
    {
        #region   Full Payment View
        [Required(ErrorMessage = "Card Number Mandatory")]
        public string CardNumber { get; set; }
        [Required(ErrorMessage = "Card Number Mandatory")]
        public string Number { get; set; }
        [Required(ErrorMessage = "Name on Card Mandatory")]
        public string CardHolderName { get; set; }
        [Required(ErrorMessage = "CVV Mandatory")]
        public string SecurityCode { get; set; }
        [Required(ErrorMessage = "Expiry Date Year Mandatory")]
        public string ExpiryDateYear { get; set; }
        [Required(ErrorMessage = "Expiry Date Month Mandatory")]
        public string ExpiryDateMonth { get; set; }
        [Required(ErrorMessage = "Card Expiry Date Mandatory")]
        public string CardExpiryDate { get; set; }
        [Required(ErrorMessage = "Expiry Date Mandatory")]
        public string ExpiryDate { get; set; }
        #endregion
    }
}
بهذه الطريقة تم ربط خصائص PaymentModel و Address مع StudentsModel بحيث يتم الوصول الى جميع الخصائص في كل منهم 
عند ربط خاصية HomeAddress ، تقوم عملية Model Binding بنفس الشيء كما كان من قبل:
  • يكتشف جميع public properties ل HomeAddress (i.e HouseNumber, Street, City, PostalCode, Country)).
  • ابحث عن قيم هذه public properties في form data. لاحظ أن routing & query sting لا يمكن أن تحتوي على complex types.
لذلك لجعل عملية Model Binding لاستخراج قيمة خاصية HouseNumber ، ستستخدم tag helper التالي
 asp-for="HomeAddress.HouseNumber" 
في view.
في الكود المحدث أدناه لـ Create View ، نقوم بربط جميع الخصائص الخمس لنوع HomeAddress. انظر الخطوط المميزة.

@model Employee
@{
    Layout = "_Layout";
    ViewData["Title"] = "Create Employee";
}
<h2>Create Employee</h2>
<form asp-action="Create" method="post">
    <div class="form-group">
        <label asp-for="Id"></label>
        <input asp-for="Id" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="DOB"></label>
        <input asp-for="DOB" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="Role"></label>
        <select asp-for="Role" class="form-control"
                asp-items="@new SelectList(Enum.GetNames(typeof(Role)))"></select>
    </div>
    <div class="form-group">
        <label asp-for="HomeAddress.HouseNumber"></label>
        <input asp-for="HomeAddress.HouseNumber" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="HomeAddress.City"></label>
        <input asp-for="HomeAddress.City" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="HomeAddress.Street"></label>
        <input asp-for="HomeAddress.Street" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="HomeAddress.PostalCode"></label>
        <input asp-for="HomeAddress.PostalCode" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="HomeAddress.Country"></label>
        <input asp-for="HomeAddress.Country" class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

بعد ذلك ، قم بتعديل Index View  لعرض خصائص HomeAddress كما هو موضح في الكود أدناه:

@model Employee
@{
    Layout = "_Layout";
    ViewData["Title"] = "Index";
}
 
<h2>Employee</h2>
 
<table class="table table-sm table-bordered table-striped">
    <tr><th>Id:</th><td>@Model.Id</td></tr>
    <tr><th>Name:</th><td>@Model.Name</td></tr>
    <tr><th>Date of Birth:</th><td>@Model.DOB.ToShortDateString()</td></tr>
    <tr><th>Role:</th><td>@Model.Role</td></tr>
    <tr><th>House No:</th><td>@Model.HomeAddress?.HouseNumber</td></tr>
    <tr><th>Street:</th><td>@Model.HomeAddress?.Street</td></tr>
    <tr><th>City:</th><td>@Model.HomeAddress?.City</td></tr>
    <tr><th>Postal Code:</th><td>@Model.HomeAddress?.PostalCode</td></tr>
    <tr><th>Country:</th><td>@Model.HomeAddress?.Country</td></tr>
</table>
الآن قم بتشغيل التطبيق الخاص بك وانتقل إلى 
URL - /Home/Create
جرب النتيجة وراسلنا اذا عندك ملاحظات او استفسارات.
لاحظ الكود المصدري html الذي تم إنشاؤه لخاصية HouseNumber هذه في Create View ، والذي يرد أدناه:
<div class="form-group">
    <label for="HomeAddress_HouseNumber">HouseNumber</label>
    <input class="form-control" type="text" id="HomeAddress_HouseNumber" name="HomeAddress.HouseNumber" value="" />
</div>
<div class="form-group">
    <label for="HomeAddress_City">City</label>
    <input class="form-control" type="text" id="HomeAddress_City" name="HomeAddress.City" value="" />
</div>
<div class="form-group">
    <label for="HomeAddress_Street">Street</label>
    <input class="form-control" type="text" id="HomeAddress_Street" name="HomeAddress.Street" value="" />
</div>
<div class="form-group">
    <label for="HomeAddress_PostalCode">PostalCode</label>
    <input class="form-control" type="text" id="HomeAddress_PostalCode" name="HomeAddress.PostalCode" value="" />
</div>
<div class="form-group">
    <label for="HomeAddress_Country">Country</label>
    <input class="form-control" type="text" id="HomeAddress_Country" name="HomeAddress.Country" value="" />
يحصل عنصر تحكم الإدخال HouseNumber على قيمة الاسم كـ HomeAddress.HouseNumber بينما تصبح قيمة المعرف HomeAddress_HouseNumber. بالطريقة نفسها ، يحصل عنصر تحكم إدخال المدينة على قيمة الاسم مثل HomeAddress.City بينما تصبح قيمة المعرف HomeAddress_City ، وهكذا بالنسبة لعناصر التحكم الأخرى.

يمكنك أيضًا الحصول على قيمة HouseNumber في action method باستخدام طريقة Request.Form كما هو موضح أدناه:

string houseNo = Request.Form["HomeAddress.HouseNumber"];
وهذا كله ينطبق على القيم الأخرى.
إذا كنت تريد الربط بين different object. في هذه الحالة سيكون عليك استخدام
[Bind(Prefix)] Attribute.
قم بإنشاء new Model class تسمى PersonAddress.cs بالرمز التالي:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ModelBindingValidation.Models
{
    public class PersonAddress
    {
        public string City { get; set; }
        public string Country { get; set; }
    }
}
بعد ذلك ، قم بإضافة action جديد يسمى DisplayPerson إلى فئة HomeController.

public IActionResult DisplayPerson(PersonAddress personAddress)
{
    return View(personAddress);
}

بعد ذلك ، قم بتغيير قيمة asp-action tag helper في Create View لاستهداف إجراء DisplayPerson كما هو موضح أدناه:

@model Employee
@{
    Layout = "_Layout";
    ViewData["Title"] = "Create Employee";
}
<h2>Create Employee</h2> 
<form asp-action="DisplayPerson" method="post">
    ...
</form>
يعني هذا الآن أنه عند ملء النموذج وإرساله في " Create View  " ، سيتم نشر البيانات في DisplayPerson action.
قم أيضًا بإنشاء DisplayPerson view داخل مجلد Views ➤ Home. يحتوي هذا   View على model من النوع PersonAddress ويعرض City and Country المعينين في Model object.

@model PersonAddress
@{
    Layout = "_Layout";
    ViewData["Title"] = "Person";
}
<h2>Person</h2> 
<table class="table table-sm table-bordered table-striped">
    <tr><th>City:</th><td>@Model.City</td></tr>
    <tr><th>Country:</th><td>@Model.Country</td></tr>
</table>
الآن قم بتشغيل التطبيق الخاص بك وانتقل إلى
 URL – /Home/Create
املأ النموذج كاملاً وانقر على زر الإرسال.

بعناصر التحكم إدخال city and country  بها string تسمى HomeAddress مسبوقة كما هو موضح في الكود أدناه.

<input class="form-control" type="text" id="HomeAddress_City" name="HomeAddress.City" value="">
<input class="form-control" type="text" id="HomeAddress_Country" name="HomeAddress.Country" value="">

ربط النموذج model binding process عن هذه الأسماء (HomeAddress.City و HomeAddress.Country) في form data. إنه يبحث في الواقع عن أسماء المدن والبلد فقط في form data.
لإصلاح هذه المشكلة ، قم بتطبيق [Bind(Prefix)] Attribute في action method’s. يحدد هذا البادئة التي يجب استخدامها أثناء ربط النموذج.

public IActionResult DisplayPerson([Bind(Prefix = nameof(Employee.HomeAddress))] PersonAddress personAddress)
{
    return View(personAddress);
}
الآن قم بإرسال form مرة أخرى وهذه المرة ستجد قيم المدينة والبلد معروضة على المتصفح 

إذا كانت هناك بعض الخصائص الحساسة التي يجب "عدم" ربطها ، فاستخدم argument الأولى لطريقة Bind لتحديد جميع الخصائص التي يجب ربطها ، أي أنك تتجاهل جميع الخصائص الحساسة التي لا تريد إظهارها.

أعط argument الأولى للطريقة ()Bind  جميع أسماء الخصائص (في قائمة مفصولة ب comma) التي يجب تضمينها فقط في عملية ربط النموذج model binding process. لذلك فإن تلك الخصائص المتبقية لن يتم ربطها على الإطلاق.

public IActionResult DisplayPerson([Bind(nameof(PersonAddress.City), Prefix = nameof(Employee.HomeAddress))] PersonAddress personAddress)
{
    return View(personAddress);
}
اختبرها عن طريق ملء النموذج وإرساله ، سترى أنه يتم عرض قيمة المدينة فقط 

يمكن إعادة إنشاء نفس الكود عن طريق تزيين خاصية البلد لفئة PersonAddress بالسمة [BindNever]. تحدد BindNever الخاصية التي يجب ألا تربطها عملية ربط النموذج على الإطلاق.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
  
namespace ModelBindingValidation.Models
{
    public class PersonAddress
    {
        public string City { get; set; }
  
        [BindNever]
        public string Country { get; set; }
    }
}