MVC 3 validation messages not shown due ModelState lost after PartialView - c#

I have an MVC 3 project where I have 1 view LoginRegister which contains 2 views with forms for login and pre-register. The problem is after incorrectly completing the pre-register form and using PartialView("LoginRegister", loginRegisterViewModel) validation messages aren't displayed due the ModelState being lost. Before reading the next paragraph it's probably best to jump to the CODE.
Debugging the PartialView("LoginRegister", loginRegisterViewModel) and stepping into the PreRegister view to the following #Html.ErrorMessageFor(model => model.Email). Where the ModelState does not contain the Email key (see below) and hence doesn't display an error message.
private static MvcHtmlString ErrorMessageHelper(this HtmlHelper htmlHelper, ModelMetadata modelMetadata, string expression, string validationMessage, IDictionary<string, object> htmlAttributes)
{
string modelName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expression);
if (!htmlHelper.ViewData.ModelState.ContainsKey(modelName)) // ModelState contains no keys, e.g. Email not found and a null is returned
{
return null;
}
// code continues here to render error message
}
CODE
ViewModels
LoginRegisterViewModel
namespace Site.ViewModels.Account
{
public class LoginRegisterViewModel
{
public bool IsDialog { get; set; }
public PreRegisterViewModel PreRegister { get; set; }
public QuickLoginViewModel QuickLogin { get; set; }
}
}
PreRegisterViewModel
using FluentValidation.Attributes;
using Site.ViewModels.Validators;
namespace Site.ViewModels.Account
{
[Validator(typeof(PreRegisterViewModelValidator))]
public class PreRegisterViewModel
{
public string Email { get; set; }
public string ConfirmEmail { get; set; }
}
}
Views
LoginRegister
#model Site.ViewModels.Account.LoginRegisterViewModel
#{
Layout = ViewBag.Layout;
}
<div id="loginReg" class="mod-loginReg">
<h2>Login and Registration</h2>
#{Html.RenderPartial("PreRegister", Model.PreRegister, new ViewDataDictionary());}
#{Html.RenderPartial("QuickLogin", Model.QuickLogin, new ViewDataDictionary());}
</div>
PreRegister
#using Site.Classes.MVC.Extensions;
#model Site.ViewModels.Account.PreRegisterViewModel
#using (Html.BeginForm("PreRegister", "Account", FormMethod.Post, new { #class = "form-errorContainer" }))
{
<div class="mod-loginReg-register">
<h3>Register</h3>
<p>Please enter your email address to register.<br /><br /></p>
<div class="mod-loginReg-form">
<div class="field-item">
#Html.LabelFor(model => model.Email)
#Html.TextBoxFor(model => model.Email, new { #maxlength = "255", #class = "Textbox required email" })
#Html.ErrorMessageFor(model => model.Email)
</div>
<div class="field-item">
#Html.LabelFor(model => model.ConfirmEmail)
#Html.TextBoxFor(model => model.ConfirmEmail, new { #maxlength = "255", #class = "Textbox required email" })
#Html.ErrorMessageFor(model => model.ConfirmEmail)
</div>
<div class="button-group">
#Html.Button("Register", "Register")
</div>
</div>
</div>
}
AccountController
[RequireHttps]
public ActionResult LoginRegister(int showDialog)
{
var loginRegisterViewModel = new LoginRegisterViewModel();
if (showDialog == 1)
{
ViewBag.Layout = "~/layouts/structure/dialog.cshtml";
loginRegisterViewModel.IsDialog = true;
}
else
{
ViewBag.Layout = "~/layouts/structure/2column.cshtml";
}
return PartialView(loginRegisterViewModel);
}
[RequireHttps]
public ActionResult PreRegister()
{
return PartialView();
}
[HttpPost]
[RequireHttps]
public ActionResult PreRegister(PreRegisterViewModel model)
{
if (ModelState.IsValid)
{
return PartialView("PreRegisterComplete");
}
var loginRegisterViewModel = new LoginRegisterViewModel { PreRegister = model, QuickLogin = new QuickLoginViewModel() };
return PartialView("LoginRegister", loginRegisterViewModel);
}

This is caused by the viewData being cleared out when new ViewDataDictionary() is called.
#{Html.RenderPartial("PreRegister", Model.PreRegister, new ViewDataDictionary());}
#{Html.RenderPartial("QuickLogin", Model.QuickLogin, new ViewDataDictionary());}
According to MSDN ViewDataDictionary is used for:
Represents a container that is used to pass data between a controller
and a view.
I assume you have a good reason for this otherwise you would not have taken the trouble to add the additional parameter.
If you change it to:
#Html.RenderPartial("PreRegister", Model.PreRegister)
#Html.RenderPartial("QuickLogin", Model.QuickLogin)
the ModelState built up in the controller should be available on your views.

Related

C# razorview DropDownListFor 'Value cannot be null'

I am new to ASP.NET MVC and I'm working on my first project just for fun.
I get this ArgumentNullException and I cannot figure out what's wrong.
This is my model:
public class SpeciesLabel
{
[Key]
[Required]
public string Name { get; set; }
[Required]
public CustomGroup CustomGroup { get; set; }
[Required]
public Family Family { get; set; }
[Required]
public Genus Genus { get; set; }
[Required]
public Species Species { get; set; }
}
public class SpeciesLabelDbContext : DbContext
{
public SpeciesLabelDbContext()
: base("DefaultConnection")
{
}
public DbSet<SpeciesLabel> SpeciesLabel { get; set; }
}
This is the controller:
public ActionResult Create()
{
List<SelectListItem> customGroups = new List<SelectListItem>();
IQueryable<string> customGroupsQuery = from g in customGroupsDb.CustomGroup
select g.Name;
foreach (var element in customGroupsQuery)
{
customGroups.Add(new SelectListItem()
{
Value = element,
Text = element
});
}
ViewBag.CustomGroup = customGroups;
This is the controller POST request:
public ActionResult Create([Bind(Include = "CustomGroup,Family,Genus,Species")] SpeciesLabel speciesLabel)
{
if (ModelState.IsValid)
{
db.SpeciesLabel.Add(speciesLabel);
db.SaveChanges();
return RedirectToAction("Create");
}
return View();
}
And this is the view:
<pre>
#model PlantM.Models.PlantModels.SpeciesLabel
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Species label</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.CustomGroup, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.CustomGroup, new SelectList(ViewBag.CustomGroupList, "Value", "Text"), "Please select...", new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CustomGroup, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
</pre>
I have inputs for all properties in the view but I cut them as they are similar to this one and the exception would be the same. Only property Name is not returned from the view as it will be designed in the controller (concatenation of the other properties).
This is the exception I get when I submit the form:
ArgumentNullException
Edit:
After adding the ViewBag initialization in the POST Create method the problem with the ArgumentNullException is resolved but I still receive Null value arguments and the object cannot be created due to this and the Create view is recalled again and again!? Can anyone advise why these #Html.DropDownListFor do not post any value to the controller?
From the comment, it sound like you see the view on first visit, but a null exception happen after you post.
If above assumption is correct, then I think your problem is because when you post back, your model did not pass the validation (for example, maybe a required input field did not post back value), which means ModelState.IsValid is false, so return View() was called
Here is the problem, you are not setting the ViewBag.CustomGroup = customGroups; in before return, hence ViewBag.CustomGroup is null, that is why you are seeing the exception.
init the ViewBag like how you did it on get then you should be able to see the page.

other inputs validation interfering with input that should allow null

Hi everyone so I am trying to create an application using asp.net mvc with a code first database that allows the users to be able to create a blog post with as many images as they wish. The application works perfect as long as I have the head,body and image fields full or if I take validation off the head and body.The issue is if I have validation on the head and the body the program crashs with this validation error:
DbEntityValidationException
An exception of type 'System.Data.Entity.Validation.DbEntityValidationException' occurred in Crud.dll but was not handled in user code
Additional information: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details. The validation errors are: Heading is Required; Body is Required
Thanks for your help with this issue.
View
#model Crud.Models.PostModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm("Edit", "Home", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<div class="editor-field">
#Html.LabelFor(model => model.Heading)
#Html.TextBoxFor(model => model.Heading)
#Html.ValidationMessageFor(model => model.Heading)
</div>
<div>
#Html.LabelFor(model => model.PostBody)
#Html.TextBoxFor(model => model.PostBody)
#Html.ValidationMessageFor(model => model.PostBody)
</div>
<div class="editor-label">
#*Temporary way to upload*#
#Html.LabelFor(model => model.ImagePath)
#Html.TextBoxFor(model => model.ImagePath, new { type = "file", multiple = "multiple", NAME = "files" })
</div>
<div class="editor-label">
#*Future Upload links*#
#*#Html.LabelFor(model => model.Images)
#Html.TextBoxFor(model => model.Images, new { type = "file", multiple = "multiple" })
#Html.ValidationMessageFor(model => model.Images)*#
</div>
<p><input type="submit" value="Create" /></p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Repository
public void Save(PostModel Post)
{
try
{
if (Post.PostID == 0)
{
context.Posts.Add(Post);
}
else
{
PostModel dbEntry = context.Posts.Find(Post.PostID);
if (dbEntry != null)
{
dbEntry.Heading = Post.Heading;
dbEntry.PostBody = Post.PostBody;
}
}
context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
// Throw a new DbEntityValidationException with the improved exception message.
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
Model
public partial class PostModel
{
[Key]
[HiddenInput(DisplayValue = false)]
public int PostID { get; set; }
[Required(ErrorMessage = "Heading is Required")]
[Display(Name = "Heading")]
public string Heading { get; set; }
[Required(ErrorMessage = "Body is Required")]
[DataType(DataType.MultilineText)]
[Display(Name = "Body")]
public string PostBody { get; set; }
public string ImageDisplayName { get; set; }
public string ImagePath { get; set; } //Temporarly here until I can get the ImageModel Method Working
//public virtual ICollection<ImageModel> Images { get; set; }
}
Controller
public ViewResult Create()
{
return View("Edit", new PostModel());
}
public ViewResult Edit(int PostID)
{
PostModel editedItem = repository.Posts
.FirstOrDefault(p => p.PostID == PostID);
return View(editedItem);
}
[HttpPost]
public ActionResult Edit(PostModel Post, IEnumerable<HttpPostedFileBase> files)
{
if (ModelState.IsValid)
{
foreach (var file in files)
{
PostModel post = new PostModel();
if (file != null && file.ContentLength > 0)
{
string displayName = file.FileName;
string fileExtension = Path.GetExtension(displayName);
string fileName = string.Format("{0}.{1}", Guid.NewGuid(), fileExtension);
string path = Path.Combine(Server.MapPath("~/Img/"), fileName);
file.SaveAs(path);
post.ImageDisplayName = displayName;
post.ImagePath = fileName;
post.PostBody = Post.PostBody;
post.Heading = Post.Heading;
}
repository.Save(post);
}
}
return RedirectToAction("display");
}
Why do you want to add the [Required] attribute if the fields are not actually required?Please see here for how required attribute is handled for strings - Allow empty strings for fields marked with PhoneAttribute or UrlAttribute

Duplicate Rules appearing on MVC Home Page

I have been following a article on Visual Studio Magazine and have found that for some reason I am getting duplicates for Rules in the drop down on my page. Could someone please explain what I am missing here? Also I would like to know how I could fix this issue.
Upon closer inspection I believe the issue lies with the Validators line as it appears to be reading in 4 objects rather than 2 (a Phone validator and an Email validator). The Email validator code is essentially the same as the Phone but using a different regex.
Controller code:
[ImportMany]
public IEnumerable<Lazy<BusinessRules.IValidate<string>, BusinessRules.IValidateMetaData>> Validators { get; private set; }
[HttpGet]
public ActionResult Index()
{
var vm = new ValidationFormModel();
vm.Rules = new List<SelectListItem>(from v in Validators
select new SelectListItem()
{ Text = v.Metadata.Name,
Value = v.Metadata.Name });
return View(vm);
}
View Model code:
public class ValidationFormModel
{
public string Input { get; set; }
public List<SelectListItem> Rules { get; set; }
public string Rule { get; set; }
public string StatusLabel { get; set; }
}
Business Rules Code
[Export(typeof(IValidate<string>))]
[ExportMetadata("Name", "Phone")]
public class ValidatePhone : IValidate<string>
{
const string PHONE_PATTERN = #"^((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}$";
public ValidationResult Validate(string input)
{
var result = new ValidationResult();
if (input == null || !Regex.IsMatch(input, PHONE_PATTERN))
{
result.ErrorMessage = string.Format("{0} is not a valid phone number");
}
else
{
result.IsValid = true;
}
return result;
}
}
The View:
#model ValidationExample.ViewModels.ValidationFormModel
#{
ViewBag.Title = "MEF Demo";
}
#using (Html.BeginForm())
{
<strong>#Html.DisplayFor(m => m.StatusLabel)</strong>
#Html.ValidationSummary(false)
<fieldset>
<legend>Validation Demo</legend>
#Html.LabelFor(m => m.Input)
#Html.TextBoxFor(m => m.Input)
#Html.LabelFor(m => m.Rule)
#Html.DropDownListFor(m => m.Rule, Model.Rules)
</fieldset>
<input type="submit" />
}
From the view, try using IEnumerable
#model IEnumerable<ValidationExample.ViewModels.ValidationFormModel>
and try changing your dropdown list to a normal <select>
<fieldset>
<legend>Validation Demo</legend>
#Html.LabelFor(m => m.Input)
#Html.TextBoxFor(m => m.Input)
#Html.LabelFor(m => m.Rule)
foreach(var item in Model)
{
<select>#item.Rule </select>
}
</fieldset>

Unable to bind MVC model property to input element

I'm having trouble getting my model to bind to an input box created by a custom helper method. I wrapped a jquery ajax-driven autocomplete box with a helper method called "Html.AutoCompleteBoxAjax." This helper basically just creates an element with javascript autocomplete functionality.
The property in the model is a string called "formatName". I have verified that, in the generated html for the view, both the name and id of the input element are "formatName", and that there are no other elements with those identities. I've also checked that the model has a default constructor, and that the "formatName" property is publicly accessible. Lastly, I've verified that when the Channel model is passed into the view, Channel.formatName has the correct value. Yet despite all this, I can't get the value to bind to the element, and the input box remains blank. There is also no binding when going the other way, from view to controller, and Channel.formatName remains blank.
What am I missing? Is it somehow because I'm using a custom helper method?
Model:
namespace WebApp.Models
{
public class ChannelModel
{
XYZ.DataAccess.ODS.DB db = Config.DB();
public string id { get; set; }
// Parent Project
[Display(Name = "Project")]
public string projectID { get; set; }
// Format Name
[Required(ErrorMessage = "Format is required.")]
[RegularExpression(Constants.username_regex, ErrorMessage = Constants.username_regexErrorMsg)]
[Display(Name = "Format")]
public string formatName { get; set; }
// Channel Name
[Required(ErrorMessage="Channel name is required.")]
[StringLength(100, ErrorMessage = Constants.minLengthErrorMsg, MinimumLength = Constants.username_minLength)]
[RegularExpression(Constants.username_regex, ErrorMessage = Constants.username_regexErrorMsg)]
[Display(Name = "Channel name")]
public string name { get; set; }
// Sequence
[Display(Name = "Sequence")]
public string sequenceID { get; set; }
public ChannelModel()
{
id = Guid.NewGuid().ToString();
}
public ChannelModel(XYZ.DataAccess.ODS.Channel channel_db)
{
id = channel_db.id;
projectID = channel_db.project_id;
name = channel_db.name;
formatName = channel_db.format_name;
sequenceID = channel_db.sequence_id;
}
public XYZ.DataAccess.ODS.Channel buildDBObject()
{
XYZ.DataAccess.ODS.Channel channel = new XYZ.DataAccess.ODS.Channel();
channel.id = id;
channel.project_id = projectID;
channel.name = name;
channel.format_name = formatName;
channel.sequence_id = sequenceID;
return channel;
}
}
}
View
#model WebApp.Models.ChannelModel
#using HelperMethods.Infrastructure
#{
ViewBag.Title = "Edit";
var sequences = ViewData["sequences"] as List<SelectListItem>;
}
<h2>Edit</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#section header {
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Channel</legend>
#Html.HiddenFor(model => model.id)
#Html.HiddenFor(model => model.projectID)
#Html.HiddenFor(model => model.name)
<div class="editor-field">
#Html.LabelFor(model => model.name):
#Html.DisplayFor(model => model.name)
</div>
<div class="editor-label">
#Html.Label("Format")
</div>
<div class="editor-field">
#Html.AutoCompleteBoxAjax("formatName", Url.Action("GetFormatsBeginningWith"))
#Html.ValidationMessageFor(model => model.formatName)
</div>
<!-- SEQUENCE -->
<div class="editor-label">
#Html.Label("Sequence")
</div>
<div class="editor-field">
#Html.SlaveDropdownList("sequenceID", "groupID", Url.Action("GetSequencesInGroup"), WebApp.Util.makeSelectList(sequences, Model.sequenceID))
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Controller
namespace WebApp.Controllers
{
public class ChannelController : Infrastructure.NoCacheController
{
XYZ.DataAccess.ODS.DB db = Config.DB();
-- stuff --
[HttpGet]
public ActionResult GetFormatsBeginningWith(string term)
{
var formats = db.getFormatsBeginningWith(term);
List<CustomHelpers.AutocompleteItem> items = new List<CustomHelpers.AutocompleteItem>();
foreach (var format in formats)
items.Add(new CustomHelpers.AutocompleteItem { value = format.name, label = format.name });
var j = Json(items, JsonRequestBehavior.AllowGet);
return j;
}
public ActionResult Edit(string id)
{
ChannelModel channelModel = new ChannelModel(db.getChannel(id));
string groupID = db.getProject(channelModel.projectID).group_id;
var sequences = db.getSequencesInGroup(groupID);
ViewData["sequences"] = makeSelectListItems(sequences);
return View(channelModel);
}
//
// POST: /Channel/Edit/5
[HttpPost]
public ActionResult Edit(ChannelModel model)
{
if (ModelState.IsValid)
{
db.updateChannel(model.buildDBObject());
return RedirectToAction("Index");
}
string groupID = db.getProject(model.projectID).group_id;
var sequences = db.getSequencesInGroup(groupID);
ViewData["sequences"] = makeSelectListItems(sequences);
return View(model);
}
-- more stuff --
}
}
Helper method for AutoCompleteBox
public static MvcHtmlString AutoCompleteBoxAjax(this HtmlHelper html, string id, string actionUrl)
{
TagBuilder input = new TagBuilder("input");
input.Attributes.Add("id", id);
input.Attributes.Add("name", id);
input.Attributes.Add("type", "text");
input.AddCssClass("autocomplete_ajax");
input.Attributes.Add("value", "");
input.Attributes.Add("action", actionUrl);
var variables = new Dictionary<string, string>() {
{"AUTOCOMPLETE_ID", id}
};
var script = populateScriptTemplate("TEMPLATE_autocomplete_ajax.js", variables);
StringBuilder s = new StringBuilder();
s.AppendLine(input.ToString(TagRenderMode.SelfClosing));
s.AppendLine(script);
return new MvcHtmlString(s.ToString());
}
Javascript for the autocomplete
$('#AUTOCOMPLETE_ID').autocomplete({
source: $('#AUTOCOMPLETE_ID').attr('action')
});
Relevant portion of the view's html
<div class="editor-field">
<input action="/Channel/GetFormatsBeginningWith" class="autocomplete_ajax" id="formatName" name="formatName" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="formatName" data-valmsg-replace="true"></span>
</div>
Answer turned out to be simple with input from #Jakub. The helper itself has to populate the value; there's no automatic binding that takes place for html helpers. Once I took care of this, the auto binding of posts back to the controller worked as expected.

asp.net MVC 4 multiple post via different forms

Right now I understand
if (IsPost){ //do stuff }
checks all post methods on that page. However, I have 2 different forms posting 2 different information. These are a login form and a register form.
Is there a way I can check IsPost based on which form? For example,
if(Login.IsPost){ //do stuff }
but how would I define the Login variable? My form looks like:
<form id="Login" method = "POST">
I have tried:
var Login = Form.["Login"]
it did not work.
I will appreciate any help.
Thanks.
In an MVC view, you can have as many forms with as many fields as you need. To keep it simple, use a single view model with all the properties you need on the page for every form. Keep in mind that you will only have access to the form field data from the form that you submit. So, if you have a login form and registration form on the same page you would do it like this:
LoginRegisterViewModel.cs
public class LoginRegisterViewModel {
public string LoginUsername { get; set; }
public string LoginPassword { get; set; }
public string RegisterUsername { get; set; }
public string RegisterPassword { get; set; }
public string RegisterFirstName { get; set; }
public string RegisterLastName { get; set; }
}
YourViewName.cshtml
#model LoginRegisterViewModel
#using (Html.BeginForm("Login", "Member", FormMethod.Post, new {})) {
#Html.LabelFor(m => m.LoginUsername)
#Html.TextBoxFor(m => m.LoginUsername)
#Html.LabelFor(m => m.LoginPassword)
#Html.TextBoxFor(m => m.LoginPassword)
<input type='Submit' value='Login' />
}
#using (Html.BeginForm("Register", "Member", FormMethod.Post, new {})) {
#Html.LabelFor(m => m.RegisterFirstName)
#Html.TextBoxFor(m => m.RegisterFirstName)
#Html.LabelFor(m => m.RegisterLastName)
#Html.TextBoxFor(m => m.RegisterLastName)
#Html.LabelFor(m => m.RegisterUsername)
#Html.TextBoxFor(m => m.RegisterUsername)
#Html.LabelFor(m => m.RegisterPassword)
#Html.TextBoxFor(m => m.RegisterPassword)
<input type='Submit' value='Register' />
}
MemberController.cs
[HttpGet]
public ActionResult LoginRegister() {
LoginRegisterViewModel model = new LoginRegisterViewModel();
return view("LoginRegister", model);
}
[HttpPost]
public ActionResult Login(LoginRegisterViewModel model) {
//do your login code here
}
[HttpPost]
public ActionResult Register(LoginRegisterViewModel model) {
//do your registration code here
}
Do not forget, when calling BeginForm, you pass the Controller name without "Controller" attached:
#using (Html.BeginForm("Login", "Member", FormMethod.Post, new {}))
instead of:
#using (Html.BeginForm("Login", "MemberController", FormMethod.Post, new {}))
I'd just load a partial view (containing a form) for each needed form, giving each partial a different viewmodel:
Multiple-form-on-a-page-requirement: satisfied.
Javascript unobtrusive validation on each form: accomplished.
Instead of doing a form Submit, we can do a ajax post on click of corresponding submit button.
#using (Html.BeginForm("ActionName", "ControllerName", FormMethod.Post, new { #Id = "Form1" }))
{
}
#using (Html.BeginForm("ActionName", "ControllerName", FormMethod.Post, new { #Id = "Form2" }))
{
}
Once you allocate different id attribute to each of the form in your page, use code like this:
$(document).ready( function() {
var form = $('#Form1');
$('#1stButton').click(function (event) {
$.ajax( {
type: "POST",
url: form.attr( 'action' ),
data: form.serialize(),
success: function( response ) {
console.log( response );
}
} );
} );
}
Repeat the same thing on 2nd Button click event to invoke the ajax post for the 2nd form.
This code uses .serialize() to pull out the relevant data from the form.
For future reference, the jQuery docs are very, very good.
NB : The button you are using to trigger the event that causes the submit of the form via ajax post should not be of type submit! Else this will always fail.
You should make each <form> point to a separate action with its own model parameters.
Use this example to prevent unnecessary/unintended validation checks while using multiple Form Post with different Controller Actions.
What to look for
Essentially this code utilizes a Boolean inside of the Model to flag which Controller Action was called in the Form Post.
Notice the use of the nest model and the [Is Action Login] and [Is Action Register] Boolean properties that I set using the method helper IsActionLogin() and IsActionRegister(). Only one will be called in the respective Controller Action.
Notice the sReturnURL property in the model. This property stores previous navigation url and is shared with the both Login and Register Controller Actions. This will allow us to return to the page the user left off prior to having to log in.
/* Begin Model logic */
public class LoginRegisterModel
{
public LoginModel LoginModel { get; set; }
public RegisterModel RegisterModel { get; set; }
public string sReturnURL { get; set; }
public bool bIsActionLogin { get; set; }
public bool bIsActionRegister { get; set; }
public void IsActionLogin()
{
bIsActionLogin = true;
bIsActionRegister = false;
}
public void IsActionRegister()
{
bIsActionLogin = false;
bIsActionRegister = true;
}
}
public class LoginRegisterModel
{
public LoginModel LoginModel { get; set; }
public RegisterModel RegisterModel { get; set; }
public string sReturnURL { get; set; }
public bool bIsActionLogin { get; set; }
public bool bIsActionRegister { get; set; }
public void IsActionLogin()
{
bIsActionLogin = true;
bIsActionRegister = false;
}
public void IsActionRegister()
{
bIsActionLogin = false;
bIsActionRegister = true;
}
}
public class RegisterModel
{
[Required]
[Display(Name = "Email")]
[RegularExpression("^[a-zA-Z0-9_\\.-]+#([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "Email is not valid.")]
public string UserName { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Confirm Password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
/*End Model logic*/
/*Begin Controller Logic*/
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginRegisterModel model, string sReturnURL)
{
model.IsActionLogin(); //flags that you are using Login Action
//process your login logic here
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(LoginRegisterModel model, string sReturnURL)
{
model.IsActionRegister(); //flag Action Register action
//process your register logic here
}
/*End Controller Logic*/
/*Begin View Logic*/
#model eCommerce.Models.LoginRegisterModel
#{
/*Place this view logic in both the Login.cshtml and Register.cshtml. Now use the last Action called as a Boolean check against your validation messages, so unnecessary validation messages don't show up.*/
bool bLoginCallBack = Model.bIsActionLogin;
bool bRegisterCallBack = Model.bIsActionRegister;
MvcHtmlString htmlIcoWarn = new MvcHtmlString(" font awesome icon here ");
MvcHtmlString htmlIcoHand = new MvcHtmlString(" font awesome icon here ");
}
#using (Html.BeginForm("Login", "Account", new { sReturnURL = Model.sReturnURL }))
{
#Html.AntiForgeryToken()
if (bLoginCallBack)
{
MvcHtmlString htmlLoginSummary = Html.ValidationSummary(true);
if (!htmlLoginSummary.ToHtmlString().Contains("display:none"))
{
#:#(htmlIcoWarn)#(htmlLoginSummary)
}
}
#Html.LabelFor(m => m.LoginModel.UserName)
#Html.TextBoxFor(m => m.LoginModel.UserName, new { #placeholder = "Email" })
#if (bLoginCallBack)
{
MvcHtmlString htmlLoginUsername = Html.ValidationMessageFor(m => m.LoginModel.UserName);
if (!htmlLoginUsername.ToHtmlString().Contains("field-validation-valid"))
{
#:#(htmlIcoHand) #(htmlLoginUsername)
}
}
#Html.LabelFor(m => m.LoginModel.Password)
#Html.PasswordFor(m => m.LoginModel.Password, new { #placeholder = "Password" })
#if (bLoginCallBack)
{
MvcHtmlString htmlLoginPassword = Html.ValidationMessageFor(m => m.LoginModel.Password);
if (!htmlLoginPassword.ToHtmlString().Contains("field-validation-valid"))
{
#:#(htmlIcoHand) #(htmlLoginPassword)
}
}
#Html.CheckBoxFor(m => m.LoginModel.RememberMe)
#Html.LabelFor(m => m.LoginModel.RememberMe)
<button type="submit" class="btn btn-default">Login</button>
}
#using (Html.BeginForm("Register", "Account", new { sReturnURL = Model.sReturnURL }))
{
#Html.AntiForgeryToken()
if (bRegisterCallBack)
{
MvcHtmlString htmlRegisterSummary = Html.ValidationSummary(true);
if (!htmlRegisterSummary.ToHtmlString().Contains("display:none"))
{
#:#(htmlIcoWarn)#(htmlRegisterSummary)
}
}
#Html.LabelFor(m => m.RegisterModel.UserName)
#Html.TextBoxFor(m => m.RegisterModel.UserName, new { #placeholder = "Email" })
#if (bRegisterCallBack)
{
MvcHtmlString htmlRegisterUsername = Html.ValidationMessageFor(m => m.RegisterModel.UserName);
if (!htmlRegisterUsername.ToHtmlString().Contains("field-validation-valid"))
{
#:#(htmlIcoHand) #(htmlRegisterUsername)
}
}
#Html.LabelFor(m => m.RegisterModel.Password)
#Html.PasswordFor(m => m.RegisterModel.Password, new { #placeholder = "Password" })
#if (bRegisterCallBack)
{
MvcHtmlString htmlRegisterPassword = Html.ValidationMessageFor(m => m.RegisterModel.Password);
if (!htmlRegisterPassword.ToHtmlString().Contains("field-validation-valid"))
{
#:#(htmlIcoHand) #(htmlRegisterPassword)
}
}
#Html.LabelFor(m => m.RegisterModel.ConfirmPassword) #Html.PasswordFor(m => m.RegisterModel.ConfirmPassword, new { #placeholder = "Confirm Password" })
#if (bRegisterCallBack)
{
MvcHtmlString htmlRegisterConfirmPassword = Html.ValidationMessageFor(m => m.RegisterModel.ConfirmPassword);
if (!htmlRegisterConfirmPassword.ToHtmlString().Contains("field-validation-valid"))
{
#:#(htmlIcoHand) #(htmlRegisterConfirmPassword)
}
<button type="submit" class="btn btn-default">Signup</button>
}
}
/*End View Logic*/

Categories