پیاده سازی Unobtrusive Ajax در ASP.NET Core 1.0
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: هشت دقیقه

پیاده سازی Unobtrusive Ajax را در ASP.NET MVC 5.x، می‌توانید در مطلب «ASP.NET MVC #21» مطالعه کنید. HTML Helpers مرتبط با Ajax، به طور کامل از ASP.NET Core 1.0 حذف شده‌اند. اما این مورد به این معنا نیست که نمی‌توان Unobtrusive Ajax را در ASP.NET Core که تمرکزش بیشتر بر روی Tag Helpers جدید هست تا HTML Helpers قدیمی، پیاده سازی کرد.


Unobtrusive Ajax چیست؟

در حالت معمولی، با استفاده از متد ajax جی‌کوئری، کار ارسال غیرهمزمان اطلاعات، به سمت سرور صورت می‌گیرد. چون در این روش کدهای جی‌کوئری داخل صفحات برنامه‌های ما قرار می‌گیرند، به این روش، «روش چسبنده» می‌گویند. اما با استفاده از افزونه‌ی «jquery.unobtrusive-ajax.min.js» مایکروسافت، می‌توان این کدهای چسبنده را تبدیل به کدهای غیرچسنبده یا Unobtrusive کرد. در این حالت، پارامترهای متد ajax، به صورت ویژگی‌ها (attributes) به شکل data-ajax به المان‌های مختلف صفحه اضافه می‌شوند و به این ترتیب، افزونه‌ی یاد شده به صورت خودکار با یافتن مقادیر ویژگی‌های data-ajax، این المان‌ها را تبدیل به المان‌های ای‌جکسی می‌کند. در این حالت به کدهایی تمیزتر و عاری از متدهای چسبنده‌ی ajax قرار گرفته‌ی در داخل صفحات وب خواهیم رسید.
روش طراحی Unobtrusive را در کتابخانه‌های معروفی مانند بوت استرپ هم می‌توان مشاهده کرد.


پیشنیازهای فعال سازی Unobtrusive Ajax در ASP.NET Core 1.0

توزیع افزونه‌ی «jquery.unobtrusive-ajax.min.js» مایکروسافت، از طریق bower صورت می‌گیرد که پیشتر در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 14 - فعال سازی اعتبارسنجی ورودی‌های کاربران» با آن آشنا شدیم. در اینجا نیز برای دریافت آن، تنها کافی است فایل bower.json را به نحو ذیل تکمیل کرد:
{
  "name": "asp.net",
  "private": true,
  "dependencies": {
   "bootstrap": "3.3.6",
   "jquery": "2.2.0",
   "jquery-validation": "1.14.0",
   "jquery-validation-unobtrusive": "3.2.6",
   "jquery-ajax-unobtrusive": "3.2.4"
  }
}
و پس از آن فایل bundleconfig.json مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 21 - بررسی تغییرات Bundling و Minification» یک چنین شکلی را پیدا می‌کند:
[
  {
   "outputFileName": "wwwroot/css/site.min.css",
   "inputFiles": [
    "bower_components/bootstrap/dist/css/bootstrap.min.css",
    "content/site.css"
   ]
  },
  {
   "outputFileName": "wwwroot/js/site.min.js",
   "inputFiles": [
    "bower_components/jquery/dist/jquery.min.js",
    "bower_components/jquery-validation/dist/jquery.validate.min.js",
    "bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js",
    "bower_components/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.min.js",
    "bower_components/bootstrap/dist/js/bootstrap.min.js"
   ],
   "minify": {
    "enabled": true,
    "renameLocals": true
   },
   "sourceMap": false
  }
]
در اینجا فایل‌های css و اسکریپت مورد نیاز برنامه، به ترتیب اضافه شده و یکی خواهند شد. خروجی نهایی آن‌ها به شکل زیر در صفحات وب مورد استفاده قرار می‌گیرند:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    <link href="~/css/site.min.css" rel="stylesheet" />
</head>
<body>
    <div>
        <div>
                @RenderBody()
        </div>
    </div>

    <script src="~/js/site.min.js" type="text/javascript" asp-append-version="true"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>
در اینجا تنها دو فایل نهایی این عملیات، یعنی css/site.min.css و js/site.min.js به صفحه الحاق شده‌اند که حاوی تمام پیشنیازهای اسکریپتی و شیوه‌نامه‌های برنامه هستند و در این حالت دیگر نیاز به افزودن آن‌ها به دیگر صفحات سایت نیست.


استفاده از معادل‌های واقعی Unobtrusive Ajax در ASP.NET Core 1.0

واقعیت این است که HTML Helper ای‌جکسی حذف شده‌ی از ASP.NET Core 1.0، کاری بجز افزودن ویژگی‌های data-ajax را که توسط افزونه‌ی jquery.unobtrusive-ajax.min.js پردازش می‌شوند، انجام نمی‌دهد و این افزونه مستقل است از مباحث سمت سرور و به نگارش خاصی از ASP.NET گره نخورده است. بنابراین در اینجا تنها کاری را که باید انجام داد، استفاده از همان ویژگی‌های اصلی است که این افزونه قادر به شناسایی آن‌ها است.
خلاصه‌ی آن‌ها را جهت انتقال کدهای قدیمی و یا تهیه‌ی کدهای جدید، در جدول ذیل می‌توانید مشاهده کنید:

 HTML attribute   AjaxOptions 
 data-ajax-confirm   Confirm 
 data-ajax-method   HttpMethod 
 data-ajax-mode   InsertionMode 
 data-ajax-loading-duration   LoadingElementDuration 
 data-ajax-loading   LoadingElementId 
 data-ajax-begin   OnBegin 
 data-ajax-complete   OnComplete 
 data-ajax-failure   OnFailure 
 data-ajax-success   OnSuccess 
 data-ajax-update   UpdateTargetId 
 data-ajax-url   Url 
   
در ASP.NET Core 1.0، به علت حذف متدهای کمکی Ajax دیگر خبری از AjaxOptions نیست. اما اگر علاقمند به انتقال کدهای قدیمی به ASP.NET Core 1.0 هستید، معادل‌های اصلی این پارامترها را می‌توانید در ستون HTML attribute مشاهده کنید.

چند نکته:
- اگر قصد استفاده‌ی از این ویژگی‌ها را دارید، باید ویژگی "data-ajax="true را نیز حتما قید کنید تا سیستم Unobtrusive Ajax فعال شود.
- ویژگی data-ajax-mode تنها با ذکر data-ajax-update (و یا همان UpdateTargetId پیشین) معنا پیدا می‌کند.
- ویژگی data-ajax-loading-duration نیاز به ذکر data-ajax-loading (و یا همان LoadingElementId پیشین) را دارد.
- ویژگی data-ajax-mode مقادیر before، after و replace-with را می‌پذیرد. اگر قید نشود، کل المان با data دریافتی جایگزین می‌شود.
- سه callback قابل تعریف data-ajax-complete، data-ajax-failure و data-ajax-success، یک چنین پارامترهایی را از سمت سرور در اختیار کلاینت قرار می‌دهند:

parameters  
 Callback  
 xhr, status   data-ajax-complete 
 data, status, xhr   data-ajax-success 
 xhr, status, error   data-ajax-failure 

برای مثال می‌توان ویژگی data-ajax-success را به نحو ذیل در سمت کلاینت مقدار دهی کرد:
 data-ajax-success = "myJsMethod"
این متد جاوا اسکریپتی یک چنین امضایی را دارد:
  function myJsMethod(data, status, xhr) {
}
در این حالت در سمت سرور، پارامتر data در یک اکشن متد، به صورت ذیل مقدار دهی می‌شود:
 return Json(new { param1 = 1, param2 = 2, ... });
و در سمت کلاینت در متد myJsMethod این پارامترها را به صورت data.param1 می‌توان دریافت کرد.


مثال‌هایی از افزودن ویژگی‌های data-ajax به المان‌های مختلف

 در حالت استفاده از Form Tag Helpers که در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers» بررسی شدند، یک فرم ای‌جکسی، چنین تعاریفی را پیدا خواهد کرد:
با این ViewModel فرضی
using System.ComponentModel.DataAnnotations;
 
namespace Core1RtmEmptyTest.ViewModels.Account
{
    public class RegisterViewModel
    {
        [Required]
        [EmailAddress]
        [Display(Name = "Email")]
        public string Email { get; set; }
    }
}
که در View متناظر Ajax ایی ذیل استفاده شده‌است:
@using Core1RtmEmptyTest.ViewModels.Account
@model RegisterViewModel
@{
}
 
<form method="post"
      asp-controller="TestAjax"
      asp-action="Index"
      asp-route-returnurl="@ViewBag.ReturnUrl"
      class="form-horizontal"
      role="form"
      data-ajax="true"
      data-ajax-loading="#Progress"
      data-ajax-success="myJsMethod">
 
    <input asp-for="Email" class="form-control" />
    <span asp-validation-for="Email" class="text-danger"></span>
 
    <button type="submit">ارسال</button>
 
    <div id="Progress" style="display: none">
        <img src="images/loading.gif" alt="loading..." />
    </div>
</form>
 
@section scripts{
    <script type="text/javascript">
        function myJsMethod(data, status, xhr) {
            alert(data.param1);
        }
    </script>
}
در اینجا تمام تعاریف مانند قبل است؛ تنها سه ویژگی data-ajax جهت فعال سازی jquery-ajax-unobtrusive به فرم اضافه شده‌اند. همچنین یک callback دریافت پیام موفقیت آمیز بودن عملیات Ajax ایی نیز تعریف شده‌است.

این View از کنترلر ذیل استفاده می‌کند:
using Core1RtmEmptyTest.ViewModels.Account;
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class TestAjaxController : Controller
    {
 
        public IActionResult Index()
        {
            return View();
        }
 
        [HttpPost]
        public IActionResult Index([FromForm]RegisterViewModel vm)
        {
            var ajax = isAjax();
            if (ajax)
            {
                // it's an ajax post
            }
 
 
            if (ModelState.IsValid)
            {
                //todo: save data
 
                return Json(new { param1 = 1, param2 = 2 });
            }
            return View();
        }
 
        private bool isAjax()
        {
            return Request?.Headers != null && Request.Headers["X-Requested-With"] == "XMLHttpRequest";
        }
    }
}
به ASP.NET Core 1.0، متد کمکی IsAjax اضافه نشده‌است؛ اما تعریف آن‌را در این کنترلر مشاهده می‌کنید. در مورد قید FromForm در ادامه توضیح داده خواهد شد (هرچند در این مورد خاص، حالت پیش فرض است و الزامی به قید آن نیست).

و یا Action Link ای‌جکسی نیز به صورت خلاصه به این نحو قابل تعریف است:
<div id="EmployeeInfo">
<a 
 asp-controller="MyController" asp-action="MyAction"
 data-ajax="true" 
 data-ajax-loading="#Progress" 
 data-ajax-method="POST" 
 data-ajax-mode="replace" 
 data-ajax-update="#EmployeeInfo">
 Get Employee-1 info
</a>

  <div id="Progress" style="display: none">
    <img src="images/loading.gif" alt="loading..."  />
  </div>
</div>


نکته‌ای در مورد اکشن متدهای ای‌جکسی در ASP.NET Core 1.0

همانطور که در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 18 - کار با ASP.NET Web API»، قسمت «تغییرات Model binding پیش فرض، برای پشتیبانی از ASP.NET MVC و ASP.NET Web API» نیز ذکر شد:
public IActionResult Index([FromBody] MyViewModel vm)
{
   return View();
}
ذکر ویژگی FromBody در اینجا الزامی است. از این جهت که اطلاعات با فرمت JSON، از قسمت body درخواست استخراج و به MyViewModel بایند خواهند شد (در حالت dataType: json). و اگر dataType : application/x-www-form-urlencoded; charset=utf-8 بود (مانند حالت پیش فرض Unobtrusive Ajax)، باید از ویژگی FromForm استفاده شود. در غیر اینصورت در سمت سرور نال دریافت خواهیم کرد.
  • #
    ‫۷ سال و ۹ ماه قبل، دوشنبه ۲۲ آذر ۱۳۹۵، ساعت ۱۵:۳۷
    یک نکته‌ی تکمیلی
    متد IsAjaxRequest و ویژگی AjaxOnly در ASP.NET Core، یک چنین تعاریفی را پیدا می‌کنند:
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc.Abstractions;
    using Microsoft.AspNetCore.Mvc.ActionConstraints;
    using Microsoft.AspNetCore.Routing;
    
    namespace WebToolkit
    {
        public static class AjaxExtensions
        {
            private const string RequestedWithHeader = "X-Requested-With";
            private const string XmlHttpRequest = "XMLHttpRequest";
    
            public static bool IsAjaxRequest(this HttpRequest request)
            {
                return request?.Headers != null && request.Headers[RequestedWithHeader] == XmlHttpRequest;
            }
        }
    
        public class AjaxOnlyAttribute : ActionMethodSelectorAttribute
        {
            public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
            {
                return routeContext.HttpContext.Request.IsAjaxRequest();
            }
        }
    }
  • #
    ‫۷ سال و ۹ ماه قبل، یکشنبه ۵ دی ۱۳۹۵، ساعت ۱۵:۴۰
    فعالسازی نمایش خطاهای سمت سرور به کاربر، پس از پایان عملیات ای‌جکسی
    سمت سرور:
    return BadRequest(error: "کاربر مورد نظر یافت نشد");
    سمت کاربر:
    - تغییرات فرم:
    <form
    ...
    ...
    data-ajax-failure="dataAjaxFailure">
    - متد جاوا اسکریپتی متناظر:
    function dataAjaxFailure(xhr, status, error) {
        alert(xhr.responseText);
    }
  • #
    ‫۷ سال و ۷ ماه قبل، شنبه ۳۰ بهمن ۱۳۹۵، ساعت ۱۶:۴۰
    برای عدم نمایش لینک اصلی Action Link ای‌جکسی در Status bar مرورگر
    href را به # و ویژگی data-ajax-url را به href اصلی تنظیم کنید. در کدهای اصلی، ابتدا مقدار ویژگی data-ajax-url بررسی می‌شود و سپس در صورت عدم یافتن مقداری برای آن، از مقدار href استفاده خواهد شد.
  • #
    ‫۶ سال و ۸ ماه قبل، جمعه ۲۲ دی ۱۳۹۶، ساعت ۱۴:۳۲
    یک نکته‌ی تکمیلی: نحوه‌ی ارسال Anti forgery token توسط Action Link ای‌جکسی 

    برای اینکار نیاز است متد Ajax begin آن‌را تکمیل کرد:
    <a data-ajax="true" data-ajax-begin="onBegin"
    در این حالت امضای متد onBegin به صورت ذیل خواهد بود:
    <script type=text/javascript>
        function onBegin(xhr, settings) {
            var token = $('input[name=__RequestVerificationToken]').val();
            settings.data = settings.data + '&__RequestVerificationToken=' + token;
        }
    </script>
  • #
    ‫۶ سال و ۳ ماه قبل، سه‌شنبه ۸ خرداد ۱۳۹۷، ساعت ۱۴:۵۳
    سلام و تشکر؛ در حالتیکه فرم ما درview به شکل enctype="multipart/form-data"  بود هم باید ویژگی FromBody ذکر گردد؟ چون داخل فرم ما یه input برای گرفتن عکس وجود داره که با بقیه اطلاعات فرم ذخیره میشه بعد از ارسال فرم. البته این نکته رو هم عرض کنم که در اکشن متد ما در کنترلر این فایل ورودی رو توسط IFormFile دریافتش میکنیم.
     public async Task<IActionResult> CreateFunction(MyViewModel vm, IFormFile attachFile)

      • #
        ‫۶ سال و ۳ ماه قبل، چهارشنبه ۹ خرداد ۱۳۹۷، ساعت ۱۶:۱۰
        ممنون از راهنمایی شما
        ولی متاسفانه بعد از دیباگ شی attachFile رو null میفرسته سمت سرور( اکشن متد داخل کنترلر ) و فقط بقیه input‌ها رو در قالب مدل ارسال میکنه که کل عملیات با شکست مواجه میشه چون تصویری برای ذخیره شدن موجود نیست و NullRefrenceException اعلام میشه.
        تو بخش client : 
        <form asp-controller="Admin" asp-action="CreateBlog" enctype="multipart/form-data" data-ajax="true" data-ajax-method="post" data-ajax-loading="#Progress" data-ajax-complete="onComplete"
              data-ajax-failure="onFailed" data-ajax-success="onSuccess">
         در سمت سرور :
        public async Task<IActionResult> CreateBlog(MyViewModel vm, IFormFile attachFile)

        یکی از دوستان در نظرات این لینک مقاله شما رو پیشنهاد کرده بودن. منتها گفتم شاید در دات نت core بشه بدون افزونه خاصی متد ارسال فایل رو با بقیه اطلاعات فرم بصورت UnObtrusive پیاده سازی کرد.
        • #
          ‫۶ سال و ۳ ماه قبل، چهارشنبه ۹ خرداد ۱۳۹۷، ساعت ۱۶:۳۸
          خیر. از این لحاظ (سمت کلاینت آن) هیچ تفاوتی با قبل وجود ندارد؛ چون این‌ها مباحث مرتبط با مرورگرها هستند.
  • #
    ‫۶ سال و ۲ ماه قبل، دوشنبه ۱۸ تیر ۱۳۹۷، ساعت ۰۵:۳۸
    سلام، من پکیج Microsoft.jQuery.Unobtrusive.Validation  رو از nuget دریافت کردم، ولی فایل‌های js  رو برای من اضافه نکرده.
    آیا نیاز به پکیج دیگری هست؟
      • #
        ‫۶ سال و ۲ ماه قبل، دوشنبه ۱۸ تیر ۱۳۹۷، ساعت ۰۵:۵۳
        بله اون مطلب رو مطالعه کردم
        محتویات فایل package.json به این صورت هست
        {
          "name": "dntcommon.web.core.testwebapp",
          "version": "1.0.0",
          "description": "",
          "scripts": {},
          "author": "",
          "license": "ISC",
          "dependencies": {
            "bootstrap": "^3.3.7",
            "bootstrap-rtl": "^3.3.4",
            "components-font-awesome": "5.0.6",
            "jquery": "^3.3.1",
            "jquery-ajax-unobtrusive": "^3.2.4",
            "jquery-validation": "^1.17.0",
            "jquery-validation-unobtrusive": "^3.2.8",
            "samim-font": "1.0.2"
          }
        }

        بعد از اجرای npm install  پاسخ زیر رو می‌دهد
        C:\Projects\ZagrosCore\src\Zagros>npm install
        npm WARN dntcommon.web.core.testwebapp@1.0.0 No description
        npm WARN dntcommon.web.core.testwebapp@1.0.0 No repository field.
        
        up to date in 0.181s
        احتمالا ساختار فایل package.json اشتباه نیست؟ 
        • #
          ‫۶ سال و ۲ ماه قبل، دوشنبه ۱۸ تیر ۱۳۹۷، ساعت ۰۵:۵۶
          خیر. warnها مهم نیستند (اخطار هستند؛ نه خطا). عنوان می‌کند بهتر است این فیلدها هم در ساختار فایل json آن وجود داشته باشند که مهم نیست. مسیر نهایی تشکیل شده‌ی پوشه‌ی node_modules در ریشه‌ی پروژه مهم است.
  • #
    ‫۶ سال و ۲ ماه قبل، سه‌شنبه ۱۹ تیر ۱۳۹۷، ساعت ۲۰:۴۶
    با سلام؛ بنده این ساختار رو پیاده سازی کردم و مشکلی باهاش ندارم. فقط مشکلی که دارم اینه که فرم دوبار و گاهی سه بار به سمت کنترلر ارسال می‌گردد. وقتی site.min.js رو برمی دارم همه چیز درسته ولی وقتی می‌ذارمش این مشکل بوجود میاد.
    <form asp-controller="Home" asp-action="SaveForm" 
          asp-antiforgery="true" id="TagDetailForm"
          data-ajax="true" data-ajax-begin="onBegin" 
          data-ajax-complete="onComplete" data-ajax-failure="onFailed" 
          data-ajax-success="onSuccess" data-ajax-method="POST">
    مشکل از کجا می‌تونه باشه.
    • #
      ‫۶ سال و ۲ ماه قبل، چهارشنبه ۲۰ تیر ۱۳۹۷، ساعت ۰۰:۲۱
      اگر فایل jquery-ajax-unobtrusive.js سه بار در صفحه بارگذاری شود، سه بار درخواست را به سمت سرور ارسال می‌کند. بنابراین بررسی کنید چندبار اسکریپت‌ها را به صفحه اضافه کرده‌اید و کدام قسمت‌ها در حال تزریق اسکریپت‌های اضافی به صفحه هستند؟
  • #
    ‫۵ سال و ۵ ماه قبل، دوشنبه ۱۲ فروردین ۱۳۹۸، ساعت ۰۳:۱۷
    یک نکته تکمیلی: ارسال آرگومان‌های سفارشی به متدهای متناظر با begin، ‏failure، ‏complete و success

    مثال اول: قصد داریم شناسه فرم جاری را نیز به عنوان آرگومان به متد handleFormFaild ارسال کنیم. برای این منظور می‌بایست به شکل زیر عمل کنید:
    function handleFormFailed(xhr, status, error, formId) {
    };
    
    data-ajax-failure="handleFormFailed(xhr, status, error, 'role-form')"

    مثال دوم: قصد داریم عملیات حذف یک رکورد را با استفاده از یک لینک Ajaxای با متد POST انجام دهیم. برای این منظور ‌می‌بایست شناسه رکورد مورد نظر را به شکل زیر به data ارسالی به سرور اضافه کنیم:
    function handleDeleteLinkBegin(xhr, id, settings)
    {
        var token = $('input[name=__RequestVerificationToken]').val();
        settings.data =
          settings.data + '&Id=' + id + '&__RequestVerificationToken=' + token;
    }
    
    <a href="#" data-ajax="true" data-ajax-method="POST" 
       data-ajax-begin="handleDeleteLinkBegin(xhr, '1', arguments[1])"
       data-ajax-url="/Roles/Delete"> 
       Delete
    </a>


  • #
    ‫۵ سال و ۱ ماه قبل، دوشنبه ۲۱ مرداد ۱۳۹۸، ساعت ۱۴:۳۶
    حالتی که بعضی از پراپرتی‌های مدل متصل به ویو ما بصورت معمولی ارسال میشن و یکی از پراپرتی‌ها بصورت Ajax هم باید کل فرم رو بصورت ajax ارسال کرد. سناریویی رو در نظر بگیرید که مدل ما در حقیقت یه complex model هستش که تشکیل شده از 2 مدل دیگر. مدل دوم باید بصورت Ajax اطلاعاتش(پراپرتی‌های مدل) به سمت سرور ارسال بشه. ولی مدل اول بصورت معمولی هم میتونه ارسال بشه.
  • #
    ‫۴ سال و ۸ ماه قبل، جمعه ۲۰ دی ۱۳۹۸، ساعت ۱۲:۰۲
    با سلام؛ گزارش خطاهای سطح مدل (مثلا فرمت اشتباه کد ملی یا شماره تماس ) که قبلا به صورت 
    this.ModelState.AddModelError("","کد ملی را درست وارد نمایید")
    return View(model)
    اتفاق می‌افتاد، به صورت غیر اسکریپتی و به شکل معمول امکان پذیر نمی‌باشد، 
    • #
      ‫۴ سال و ۸ ماه قبل، جمعه ۲۰ دی ۱۳۹۸، ساعت ۱۲:۴۰
      "asp-validation-summary="ModelOnly را در مطلب «قسمت 14 - فعال سازی اعتبارسنجی ورودی‌های کاربران » مطالعه کنید.
      @if (ViewData.ModelState.Any(keyValuePair => keyValuePair.Value.Errors.Any()))
      {
          <div class="alert alert-danger">
              <a href="#" class="close" data-dismiss="alert">×</a>
              <h4>خطاهای اعتبارسنجی</h4>
              <div asp-validation-summary="ModelOnly"></div>
          </div>
      }