اجرای این نوع صفحات کار سختی نیست؛ با کمی جستجو در اینترنت مثلا در اینجا میتوانید چیزهای خوبی پیدا کنید. اما متاسفانه اکثر مثالها چیزی
شبیه قرار دادن پارشال "ورود اعضا" در کنار پارشال "ثبت نام"
هستند. حتما متوجه شدهاید که معمولا این دو صفحه پس از PostBack به صفحهای
جدید هدایت میشوند و یا در بهترین حالت به کمک Ajax ، پس از انجام عملیات، پیامی به کاربر نمایش میدهیم.
در این مقاله سعی شده روشی برای ایجاد چند فرم در یک View توضیح داده
شود با این شرط که:
اولا : از Ajax یا هلپر ایجکسی استفاده
نکنیم.
ثانیا : پس از post-back، عملیات Redirect را انجام ندهیم و صفحه
جاری را حفظ کنیم؛ چه قرار باشد همه چیز درست انجام شده باشد و چه مشکلی پیش آمده باشد
و پیام خطایی در کنار فیلدها نمایش داده شود.
در این روش به این نکته توجه شده که هر مدل پس از Post-back حفظ شود و مستقل از دیگری رفتار کند. مثلا
اگر یکی از فرمها ناقص پر شد و دکمهی ارسال آن فشرده شد، پس از Post-back، فقط و فقط اجزای همین فرم Validate شود و فرم دوم بدون تغییر باقی بماند.
ویوی زیر را در نظر بگیرید. در layout، دو پارشال، به
کمک اکشنمتد فراخوانی شدهاند:
ViewModelهای مرتبط با این دو بخش به شکل زیر هستند :
ContactVM .cs
public class ContactVM
{
[Display(Name = "نام")]
[Required(ErrorMessage = "لطفا {0} را وارد کنید")]
public string Name { get; set; }
[EmailAddress(ErrorMessage = "آدرس ایمیل صحیح نیست")]
[DataType(DataType.EmailAddress)]
[Display(Name = "آدرس ایمیل")]
[Required(ErrorMessage = "لطفا {0} را وارد کنید")]
public string EmailAddress { get; set; }
[Display(Name = "متن پیام")]
[Required(ErrorMessage = "حرفی برای گفتن ندارید؟")]
public string Description { get; set; }
[Required(ErrorMessage = "لطفا {0} را وارد کنید")]
[Display(Name = "حاصل جمع")]
public string Captcha { get; set; }
}
SubscriberVM .cs
public class SubscriberVM
{
/*[RegularExpression("^[a-zA-Z0-9_\\.-]+@([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "آدرس ایمیل صحیح نیست")]*/
[EmailAddress(ErrorMessage = "آدرس ایمیل صحیح نیست")] /*.Net4.5*/
[Display(Name = "ایمیل")]
[Required(ErrorMessage = "لطفا {0} را وارد کنید")]
public string Email { get; set; }
[Display(Name = "وضعیت")]
public bool IsActive { get; set; }
}
در Layout، دو اکشن متد صدا زده شدهاند که وظیفه ارسال ویوهای هر کدام به Layout را به عهده دارند :
<div class="row footerclass">
<div class="col-md--6">
@Html.Action("Subscribers", "Home")
</div>
<div class="col-md-6">
@Html.Action("Contact", "Home")
</div>
</div>
اکشن متدهای این دو پارشال به شکل زیر هستند :
public ActionResult Contact()
{
return PartialView("_Contact", model);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Contact(ContactVM model)
{
if (ModelState.IsValid)
{
//Do Something
}
return PartialView("_Contact", model);
}
public ActionResult Subscribers()
{
return PartialView("_Subscribers");
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Subscribers(SubscriberVM model)
{
if (ModelState.IsValid)
{
//Do Something
}
}
return PartialView("_Subscribers",model);
}
و اما ویوهایی که قرار است نمایش داده شوند:
Contact.Cshtml _
@model IrsaShop.Models.ViewModel.ContactVM
<span></span><span>تماس با ما</span>
<hr />
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<div>
@Html.TextBoxFor(m => m.Name, new { @class = "form-control", @id = "name", @name = "name", placeholder = "نام" })
@Html.ValidationMessageFor(m => m.Name)
</div>
<div>
@Html.TextBoxFor(m => m.EmailAddress, new { @class = "form-control", @id = "email", @name = "email", placeholder = "ایمیل", @style = "direction: ltr" })
@Html.ValidationMessageFor(m => m.EmailAddress)
</div>
<div>
@Html.TextAreaFor(model => model.Description, new { @class = "form-control", @id = "message", @name = "message", placeholder = "پیام", @style = "max-width: 100%;height: 90px;" })
</div>
<div>
<input type="button" value="" id="refresh" />
<img alt="Captcha" id="imgcpatcha" src="@Url.Action("CaptchaImage","Captcha")" />
</div>
<div>
@Html.TextBoxFor(model => model.Captcha, new { @class = "form-control", placeholder = "حاصل جمع؟" })
@Html.ValidationMessageFor(model => model.Captcha)
</div>
<div>
<input type="submit" value="ارسال" name="submitValue" />
</div>
}
_Subscriber.Csh tml
@model IrsaShop.Models.SubscriberVM
<span></span><span>خبرنامه</span>
<hr/>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<div>
<div>
@Html.TextBoxFor(m => m.Email, new { @class = "form-control right-buffer top-buffer pull-right", @id = "email", @name = "email", placeholder = "ایمیل", @style = "direction: ltr;width: 50%", @required = "required" })
@* <button type="submit" name="submitValue">ثبت ایمیل</button>*@
<input type="submit" value="ثبت ایمیل" name="submitValue" />
</div>
</div>
@Html.ValidationMessageFor(m => m.Email,"",new { @class = "right-buffer pull-right"})
}
نکته اول : هیچ نوع ورودی برای Html.BeginForm در نظر گرفته نشده است.
اگر اکشن متدی را برای صدا زدن در این بخش در نظر بگیرید، هنگام Postback به مشکل
برخورد خواهید کرد؛ چون آدرس آن اکشن متد به شکل صریح در آدرس مرورگر فراخوانی میشود و
پارشال ما پس از Post-back به تنهایی
و بدون Layout نمایش داده خواهد شد. اسم بردن از اکشن متد وقتی کارساز است که آن اکشن متد
قرار باشد یک Redirect انجام دهد ولی هدف ما این
است که صفحه را از دست ندهیم و پیامهای خطای ModelState را در همان صفحه قبل و پس
از Post-back ببینیم و همچنین پس از انجام عملیات (مثلا
ارسال پیام) همین صفحه نمایش داده شود.
نکته دوم : نکته اول یک مشکل دارد! اگر به شکل صریح اکشن متد مربوط به Post-back مشخص نشود، بطور اتوماتیک تمامی اکشن متدهایی که ویژگی [HttpPost] دارند اجرا خواهند شد.
این یعنی هر دو اکشن متد Contact و Subscriber اجرا میشوند و بنابر
آنچه در اکشن متدها نوشتهایم هر دو ModelState بررسی میشود که این هدف
ما نیست. مثلا فرم سمت چپ را تکمیل کرده ایم و دکمه "ثبت ایمیل" را فشار
دادهایم و صفحه Postback میشود و با اینکه ایمیل
در بانک ثبت شده اما فرم سمت راستی با خطا ظاهر میشود که چرا فیلدها خالی هستند!؟
برای حل این مشکل کافیست خاصیت name مربوط به دکمهها را به
شکل یک ورودی برای اکشن متدها بفرستیم و بر اساس وضعیت آن تنها state مدل مورد نظر خودمان را
بررسی کنیم. پس اصلاح زیر را برای اکشن متدهای دارای ویژگی [HttpPost] انجام میدهیم.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Contact(ContactVM model, , string submitValue)
{
if (submitValue == "ارسال")
{
if (ModelState.IsValid)
{
//Do Something
}
} else
{
ModelState.Clear();
}
return PartialView("_Subscribers", model);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Subscribers(SubscriberVM model, string submitValue)
{
if (submitValue == "ثبت ایمیل")
{
if (ModelState.IsValid)
{
//Do Something
}
}
else
{
ModelState.Clear();
}
return PartialView("_Subscribers");
}
نکته سوم : در این روش سعی کنید از ViewModel استفاده کنید و سعی کنید ویو مدلها پراپرتیهای با نام یکسان نداشته باشند. مثلا پراپرتی Email در ویو مدلها نامهای متفاوتی داشته باشند (مثل EmailAddress ، Email ، ContactMail و ...). با اینکار در زمان Postback احتمال اینکه فیلدهای مشترک اتوماتیک پر شده به ما نمایش داده باشند صفر خواهد شد.
نکته چهارم : حواستان باشد پس از انجام عملیات مرتبط با هر فرم در اکشن متد مربوط به آن (مثلا ارسال ایمیل، ثبت در بانک یا ...) در صورتی که عملیات با موفقیت انجام شد حتما ModelState را clear کنید. با اینکار پس از Post-back فیلدهای پارشالها خالی میشوند.
نکته پنجم : میتوانید به سادگی مدیریت خطا را به کمک جی کوئری انجام دهید؛ مثلا فرض کنید میخواهیم اگر ایمیل کاربر برای دریافت خبرنامه با موفقیت ثبت شد، پیامی مبنی بر موفقیت برای وی بفرستیم؛ اکشن متد HttPost مربوط به Subscriber را به شکل زیر تکمیل میکنیم :
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Subscribers(SubscriberVM model, string submitValue)
{
if (submitValue == "ثبت ایمیل")
{
if (ModelState.IsValid)
{
Subscriber mail = new Subscriber() { Email = model.EmailSubscriber, IsActive = true };
context.Subscribers.Add(mail);
context.SaveChanges();
ViewBag.info = "ایمیل شما با موفقیت ثبت شد.";
ViewBag.color = "alert-success";
ModelState.Clear();
}
}
else
{
ModelState.Clear();
}
return PartialView("_Subscribers ");
}
در انتهای پارشال _Subscriber هم چند خط کد زیر را
مینویسیم :
@if (!String.IsNullOrEmpty(ViewBag.info))
{
<div id="info" style="position: fixed; bottom: 0; right: 0; margin-right: 1%;">
<div class="alert @ViewBag.color alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<strong> @ViewBag.info</strong>
</div>
</div>
<script>
$(function () {
$("#info").fadeOut(15000);
});
</script>
}
نتیجه این خواهد
بود که پس از PostBack در صورت موفقیت تصویر زیر را
خواهیم دید و 15 ثانیه المان سبزرنگ بوت استرپِ زیر نمایش داده خواهد شد.
این روش نوعی مدیریت میان اکشن متدهای دارای ویژگی HttpPost است و همانطور که گفتیم به علت اینکه پس از Post-Back نیاز به ساختار به هم نخوردهی صفحهی قبلی
داریم، نمیتوانستیم به شکل صریح، اکشن متد برای Html.BeginForm تعریف کنیم تا این دردسرها را نداشته باشیم.
حین نوشتن این مقاله به علت وجود if های تو در تو، امیدوار
بودم که روشهای بهتری برای اینکار موجود باشند و هنوز هم امیدوارم نظرات شما چنین
چیزی را نشان دهد.