Properly handling model validation errors - c#

How do I get this simple form validation going?
I have an AccountVerificationController which originally had the following method:
public ActionResult Index(AccountVerificationModel model)
Problem is when view is initially loaded, there are validation errors since the model has required fields as follows:
public class AccountVerificationModel
{
[Required]
public string MerchantId { get; set; }
[Required]
public string AccountNumber { get; set; }
[Required, StringLength(9, MinimumLength = 9, ErrorMessage = "The Routing Number must be 9 digits")]
}
However, this is not desired behavior. I want validation to occur only after user clicks on a validate button, so I changed form to invoke the Verify method in the account controller.
View is as follows;
#using (Html.BeginForm("Verify", "AccountVerification", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h2>#ViewBag.Title</h2>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.SubMerchantId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.MerchantId, new { htmlAttributes = new { #class = "form-control", placeholder = "MID" } })
#Html.ValidationMessageFor(model => model.MerchantId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.RoutingNumber, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.RoutingNumber, new { htmlAttributes = new { #class = "form-control", placeholder = "9 Digit Routing Number" } })
#Html.ValidationMessageFor(model => model.RoutingNumber, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input name="validate" type="submit" value="Validate" class="btn btn-info"/>
</div>
</div>
</div>
}
Now the challenge is handling model validation errors. I have the controller structured as follows:
public class AccountVerificationController : BaseMvcController
{
public AccountVerificationController()
{
}
public ActionResult Index()
{
return View(new AccountVerificationModel());
}
public ActionResult Verify(AccountVerificationModel model)
{
// do the validation then re-direct......
if (!model.IsValid())
{
return RedirectToAction("Index", model);
}
// otherwise try to validate the account
if (!model.VerificationSuccessful)
{
// repopulate the view with this model...
return RedirectToAction("Index", model);
}
return Redirect("Index");
}
However, during re-direction, I am losing the entire context, model errors and all. Reading upon this whole model binding but if someone can quickly spot what I am doing wrong here, that would be appreciated.

Well, you can't redirect when there's errors. Period. Redirection is actually a two-step process, even though it seems to happen seemlessly. First, the server returns a 301 or 302 status code with a header value indicating the URL the client should go to instead. Then, the client issues a brand new request to that given URL. That's why no context is persisted. It's basically the same as the client requesting the page for the very first time.
The only way to maintain the context is to simply return a view. Which means your Verify action would need to return the same view as your Index action. However, that's not exactly ideal. I don't exactly understand what the original problem was that made you decide to add the Verify action in the first place, but that can likely be fixed if you open a new question to that regard. You should really just postback to your Index action.

Related

ResetPassword feature doesn't use code from link

I have an old MVC website I'm looking at. I don't understand the following code in my Account controller. (I believe this was code generated by ASP.NET.)
Controller
//
// GET: /Account/ResetPassword
[AllowAnonymous]
public ActionResult ResetPassword(string code)
{
return code == null ? View("Error") : View();
}
//
// POST: /Account/ResetPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
AddErrors(result);
return View();
}
View
#model OnBoard101.Models.ResetPasswordViewModel
#{
ViewBag.Title = "Reset password";
}
<h2>#ViewBag.Title</h2>
#using (Html.BeginForm("ResetPassword", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Reset your password</h4>
<hr />
#Html.ValidationSummary("", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Code)
<div class="form-group">
#Html.LabelFor(m => m.Email, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.Email, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Password, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.Password, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ConfirmPassword, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.PasswordFor(m => m.ConfirmPassword, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-success" value="Reset" />
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
These methods are called when the user uses the Forgot Password feature and then clicks the link that is sent to them in an email.
As best I can tell, the POST handler correctly detects an invalid code in the link (it produces an Invalid Token error when the code is not value). But I don't understand how. The GET handler seems to simply discard the code argument.
I don't get how this code works. How does model.Code ever get populated?
The ResetPassword feature does use code from the link.
Model binding retrieves data from various sources such as route data, form fields, and query strings.
It inspects the query string parameters for matching properties on the model used by the view
While it may appear like the code is being discarded by the Controller GET action, the code is still a part of the request and used by the view.
And since the view explicitly binds to a model
#model OnBoard101.Models.ResetPasswordViewModel
which has a matching public Code property (case-insensitive)
public string Code { get; set; }
it will bind it to the model in the view during the GET and then use it (the model) to populate a hidden form field (as shown in your View markup)
#Html.HiddenFor(model => model.Code)
So now when the form is submitted, the POST action will bind that field to the model posted back to the action and perform the validation
The same could have been achieved with
// GET: /Account/ResetPassword
[AllowAnonymous]
public ActionResult ResetPassword(string code) {
if (code == null) return View("Error");
var model = new ResetPasswordViewModel {
Code = code
};
return View(model);
}
But since the built-in model binding functionality would initialize a model if one was not provided, the above code really does not add anything additional to what the framework does out-of-the-box.

MVC UI for ASP.NET CheckboxFor Issue

I have a big problem with MVC Telerik UI for ASP.NET
I am trying to get a checkbox up for a boolean field. I know we have two input fields to return the value false when the box is not touched.
When I do not touch the CBox, I get the value 'false' as expected. When I check the box, I get false too because the CBOx is returning a string = "true,false" which makes it impossible to convert directly to bool.
View
public class role
{
public string role_name { get; set; }
public bool add_school { get; set; }
}
Controller
public ActionResult test()
{
return View();
}
[HttpPost]
public async Task<ActionResult> test(Models.role role)
{
var z = Request["cb_addschool"];
var x = 1;
return RedirectToAction("Index");
}
View
#model Models.role
#using (Html.BeginForm("test", "Home", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h2>Add a New Role</h2>
<hr />
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.role_name, new { #class = "col-md-1 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.role_name, new { #class = "form-control form-control-big" })
</div>
</div>
<div class="form-group">
#Html.Kendo().CheckBoxFor(m=>m.add_school).Name("cb_addschool").Label("Add School")
</div>
<div class="form-group">
<div class="col-md-offset-3 col-md-9">
<input type="submit" class="btn btn-login" value="Register" />
</div>
</div>
}
Please, any help?
Remove this code from your action method:
var z = Request["cb_addschool"];
You have this value inside your role model. So this is pointless in this case.
Than remove this attribute from Kendo CheckBoxFor:
.Name("cb_addschool")
You don't have to need that (the property will be bound correctly without that).
Small hint: if you are using Kendo - use the Kendo().TextBoxFor method instead of #Html.TextBoxFor (or add "k-textbox" class to your TextBoxFor - it will use Kendo CSS styles).
Here is an example:
#(Html.Kendo().TextBoxFor(model => model.role_name)
.HtmlAttributes(new { placeholder = "Select role", #class = "form-control form-control-big" })
)

Hard coding dropdown list on the view page

I have a string input type and I want it to remain string. But I want the user to chose from a list of options when he inputs the information. So I want to add a drop down list but I don't know how to do it. I don't want to create any model or any list in the back end ... I want to be able to do this on the view only.
Here is my initial code:
<div class="form-group">
#Html.LabelFor(model => model.EmployeeType, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.EmployeeType, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EmployeeType, "", new { #class = "text-danger" })
</div>
</div>
If you do not want to use any viewmodels/Html helper methods for rendering your dropdown, you may simply use pure Html. Just keep names for your form elements and use that as parameters of your HttpPost action method.
#using(Html.Beginform())
{
<input type="text" name="userName"/>
<select name="state">
<option value="MI">Michigan<option>
<option value="OH">Ohio<option>
</select>
}
And your HttpPost action would be
[HttpPost]
public ActionResult Register (string userName,string state)
{
//check the parameter values now
// to do : Save and redirect.
}
If you are already having a ViewModel object as parameter for your HttpPost action method, You can add the SELECT element name as second param and it will still work.
[HttpPost]
public ActionResult Register (CreateUserVM model,string state)
{
//check the parameter values now
// to do : Save and redirect.
}
I am not quite sure about your exact use case, If you have more than 1 or 2 form elements you are posting, I recommend using a view model and rely on MVC Model binding. Adding the dropdown data to the viewmodel is not that hard.
public class CreateUserVM
{
public string UserName {set;get;}
public List<SelectListItem> EmployeeTypes {set;get;}
public int SelectedType {set;get;}
}
And in your GET action method, Load the EmployeeTypes collection and send it to the view
public ActionResult Create()
{
var vm= new CreateUserVM();
vm.EmployeeTypes = GetEmployeeTypes();
return View(vm);
}
public List<SelectListItem> GetEmployeeTypes()
{
return new List<SelectListItem>()
{
new SelectListItem
{
Value = "1",
Text = "PERM"
},
new SelectListItem
{
Value = "2",
Text = "Temporary"
}
};
}
And in your view,
#model CreateUserVM
#using(Html.BeginForm())
{
#Html.TextBoxFor(v=>v.UserName)
#Html.DropdownListFor(s=>sSelectedType,Model.EmployeeTypes,"Select one")
}
And in your HttpPost action, Read the SelectedType property to get the selected Item's value.
[HttpPost]
public ActionResult Register (CreateUserVM model)
{
//check the model.SelectedType
// to do : Save and redirect.
}
If you do not want to hard code the Employee Types in the server side code, Create a db table and store it there and read it in your GET action method. This will allow you to add a new employee type to the system without touching the code.
Take a look at this example: http://www.asp.net/mvc/overview/older-versions/working-with-the-dropdownlist-box-and-jquery/using-the-dropdownlist-helper-with-aspnet-mvc
Here is some basic code to create a dropdownlist in a view:
#{
var listitems = new List<SelectListItem>();
listitems.Add(new SelectListItem() { Text = "One", Value="1"});
listitems.Add(new SelectListItem() { Text = "Two", Value="2"});
}
#Html.DropDownList("DropDownListValue", listitems)
I'm not sure why you would want to fill the data in the View, but you want to use:
#Html.DropDownList
So your code would be something along the lines of....
<div class="form-group">
#Html.LabelFor(model => model.EmployeeType, "Employee Type", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("EmployeeType", new SelectList(new List<string> { ... }, "EmployeeType", "SelectListName"), htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.EmployeeType, "", new { #class = "text-danger" })
</div>
</div>
Obviously you need to actually put your strings into that list some how. Personally I would create or get the list in the controller and pass it in a ViewBag. Then you could just...
#Html.DropDownList("EmployeeType", new SelectList(ViewBag.EmployeeTypes, "EmployeeType", "SelectListName"), htmlAttributes: new { #class = "form-control" })

How can I change the input element name attribute value in a razor view model using a custom attribute in a model?

I have the following:
#model Pharma.ViewModels.SearchBoxViewModel
<div class="smart-search">
#using (Html.BeginForm("Index", "Search", FormMethod.Get, new { #class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<div class="hidden-xs- col-sm-1 col-md-1 col-lg-1 text-right">
#Html.LabelFor(m => m.SearchPhrase, new { #class = "control-label" })
</div>
<div class="col-xs-8 col-sm-8 col-md-9 col-lg-10">
#Html.TextBoxFor(m => m.SearchPhrase, new { #class = "form-control" })
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default" />
</div>
</div>
}
</div>
As you can see this is creating an input element.
The view model passed to the view contains the following:
public class SearchBoxViewModel
{
[Required]
[Display(Name = "Search")]
public string SearchPhrase { get; set; }
}
At the moment the input element contains a name attribute with the value of "SearchPhrase" but I would like the value to be just "q" without renaming the property.
I would prefer an extension which allows me to call TextBoxFor but without the need of having to supply the Name property, so that the custom attribute somehow sets the value of the Name property automatically to the value specified in the custom attribute.
The following is an example of what I mean:
public class SearchBoxViewModel
{
[Required]
[Display(Name = "Search")]
[Input(Name = "q")]
public string SearchPhrase { get; set; }
}
Combined with:
#model Pharma.ViewModels.SearchBoxViewModel
<div class="smart-search">
#using (Html.BeginForm("Index", "Search", FormMethod.Get, new { #class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<div class="hidden-xs- col-sm-1 col-md-1 col-lg-1 text-right">
#Html.LabelFor(m => m.SearchPhrase, new { #class = "control-label" })
</div>
<div class="col-xs-8 col-sm-8 col-md-9 col-lg-10">
#Html.TextBoxFor(m => m.SearchPhrase, new { #class = "form-control" })
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default" />
</div>
</div>
}
</div>
Which would then produce something similar to the following:
<div class="smart-search">
<form action="/Search/Index" method="get" class="form-horizontal" role="form">
<div class="form-group">
<div class="hidden-xs- col-sm-1 col-md-1 col-lg-1 text-right">
<label for="Search" class="control-label">Search</label>
</div>
<div class="col-xs-8 col-sm-8 col-md-9 col-lg-10">
<input type="text" name="q" id="Search" value="" class="form-control" />
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default" />
</div>
</div>
</form>
</div>
I would like this custom attribute to take effect whenever the SearchBoxViewModel is used regardless of what template is used to prevent errors, with the intention of being clear to programmers, while creating a user-friendly query string for the user.
Is it possible to do this using a custom attribute on the SearchPhrase property in a similar fashion to how the display name is changed?
I wrote something simple but can be a start to write the complete solution.
First I wrote a simple Attribute with the name you provided:
public class InputAttribute : Attribute
{
public string Name { get; set; }
}
Then I wrote a Html helper that wraps default TextBoxFor and searches for Input attribute and if any, It will replace name attribute of generated HtmlString from TextBoxFor:
public static MvcHtmlString MyTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
{
var memberExpression = expression.Body as MemberExpression;
var attr = memberExpression.Member.GetCustomAttribute(typeof (InputAttribute)) as InputAttribute;
var result = htmlHelper.TextBoxFor(expression, htmlAttributes);
if (attr != null)
{
var resultStr = result.ToString();
var match = Regex.Match(resultStr, "name=\\\"\\w+\\\"");
return new MvcHtmlString(resultStr.Replace(match.Value, "name=\"" + attr.Name + "\""));
}
return result;
}
Then use this html helper in razor views:
#Html.MyTextBoxFor(m => m.SearchPhrase, new { #class = "form-control" })
Also your model is as follows:
public class SearchBoxViewModel
{
[Required]
[Display(Name = "Search")]
[Input(Name = "q")]
public string SearchPhrase { get; set; }
}
This is a way to complete solution:
You have to implement all of the overloads of TextBoxFor
If you try to send form data to an action with parameter of type SearchBoxViewModel you will get a 404 because ModelBinder can not bind request parameters to this ViewModel. So you will have to write a ModelBinder to solve this problem.
You have to write LabelFor accordingly to match for attribute correctly.
EDIT: In case of your problem you don't have to deal with case 2 because you send a GET request and you will get the form parameters in query string. So you may write your action signature like:
public ActionResult Search(string q)
{
// use q to search
}
The problem occurs when you have a non-primitive type in your action parameters. In this case ModelBinder tries to match query string items (Or request payload) with properties of type of action parameter. For example:
public ActionResult Search(SearchBoxViewModel vm)
{
// ...
}
In this case, query string (or request payload) has your search query in a parameter named q (because name of input is q and html form sends request in form of key-values consist of input name and input value). So MVC can not bind q to SearchPhrase in LoginViewModel and you will get a 404.
I know this isn't what you are explicitly asking for but I feel that having a different ViewModel name from the actual form name undermines one of the core conventions in MVC and may be misleading.
As an alternative, you can just add a new property to the VM that mirrors SearchPhrase and has the proper name:
public class SearchBoxViewModel
{
[Required]
[Display(Name = "Search")]
public string SearchPhrase { get; set; }
[Display(Name = "Search")]
public string q
{
get { return SearchPhrase; }
set { SearchPhrase = value; }
}
}
Change your view to use these:
#model Pharma.ViewModels.SearchBoxViewModel
<div class="smart-search">
#using (Html.BeginForm("Index", "Search", FormMethod.Get, new { #class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<div class="hidden-xs- col-sm-1 col-md-1 col-lg-1 text-right">
#Html.LabelFor(m => m.q, new { #class = "control-label" })
</div>
<div class="col-xs-8 col-sm-8 col-md-9 col-lg-10">
#Html.TextBoxFor(m => m.q, new { #class = "form-control" })
</div>
<div class="col-xs-4 col-sm-3 col-md-2 col-lg-1">
<input type="submit" value="Search" class="btn btn-default" />
</div>
</div>
}
</div>
This would allow you to keep your code in the back end referencing SearchPhrase instead of q to make it easier on the programmers. Hopefully this view isn't spread out everywhere and you only have a single EditorTemplate or partial.

Fill value by default form in .NET

I have a IdentityUser Model
I have a Manage View with 3 partials (each one have one viewmodel and controller) I want to enter on this view and see the forms with data filled of the model.
ApplicationUser : IdentityUser (Model of my user)
using Microsoft.AspNet.Identity.EntityFramework;
using System;
namespace MTGWeb.Models
{
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser
{
public String Pais;
public String Email;
public DateTime UltimoLogin;
public DateTime FechaRegistro;
public String Tipo;
public Boolean Activado;
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
}
}
Manage (Main View)
#using MTGWeb.Models;
#using Microsoft.AspNet.Identity;
#{
ViewBag.Title = "Administrar cuenta";
}
<h2>#ViewBag.Title.</h2>
<p class="text-success">#ViewBag.StatusMessage</p>
<div class="row">
<div class="col-md-12">
<p>Ha iniciado sesión como <strong>#User.Identity.GetUserName()</strong>.</p>
#Html.Partial("_ChangePasswordPartial")
#Html.Partial("_ChangeEmailPartial")
#Html.Partial("_OtherFieldsPartial")
</div>
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
_ChangeEmailPartial
#using Microsoft.AspNet.Identity
#model MTGWeb.Models.ManageUserViewModelEmailChange
#using (Html.BeginForm("ManageEmail", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<h4>Formulario para cambiar email</h4>
<hr />
#Html.ValidationSummary()
<div class="form-group">
#Html.LabelFor(m => m.OldEmail, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.OldEmail, new { #class = "form-control", Value = Model.OldEmail})
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.NewEmail, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.NewEmail, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ConfirmEmail, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.ConfirmEmail, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Cambiar email" class="btn btn-default" />
</div>
</div>
}
Controller - ManageEmail
// Cambia el email
// POST: /Account/ManageEmail
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ManageEmail(ManageUserViewModelEmailChange model)
{
ViewBag.ReturnUrl = Url.Action("Manage");
var user = await UserManager.FindByNameAsync(User.Identity.Name);
if (ModelState.IsValid)//Si no hay errores en la validación que hace la clase (Datatype, length, required, etc..)
{
if (model.OldEmail.Equals(model.NewEmail) && model.OldEmail.Equals(user.Email))
{
user.Email = model.NewEmail;
IdentityResult success = await UserManager.UpdateAsync(user);
if (success.Succeeded)
{
return RedirectToAction("Manage", new { Message = ManageMessageId.ChangeEmailSuccess });
}
}
}
//Si el modelo no es válido o no el cambio no ha tenido exito
return View(model);
}
I have 2 more controllers for others partials, but this is usefull for the example. These Model.OldEmail is null and causes a Nullreference error, Where I have to fill it? I guess that this have to be filled in AccountController/Manage, but.. How can I send it to the partials?
I am new on MVC and .NET, I used to work with Java, and I am stucked in this (Is a testing project purposes)
pass the model you want the partials to show into the partials as an argument
you will need to add a viewmodel containing the models you want to show to the host view.
eg
#Html.Partial("_ChangePasswordPartial",Model.ChangePAsswordViewModel)
Model is a property on Controller (which your manage controller will inherit from)
You pass the viewmodel into the manage view from the controller in return View(YourViewModelInstance) from your Manage controller method.
you also need to add a reference to that model in your manage form like you have in your partials
Eg
#model MTGWeb.Models.ManageViewModel
your manage viewmodel might look something like
public class ManageViewModel
{
public ChangePasswordViewModel ChangePasswordViewModel{get;set;}
public NextViewModel NextViewModel{get;set;}
public AnotherViewModel NextViewModel{get;set;}
}

Categories

Resources