I would like to enforce field validations on my Views in the MVC app that I am working on. For example -
Limit the length of the field to 40
Ensure only alphanumeric and special characters ##$%&*()-_+][';:?.,! can be entered.
I used the following to restrict the field length:
<div>
<%= Html.TextBoxFor(c => c.CompanyName, new { style = "width:300px", maxlength = "40" })%></div>
How do I ensure that only alphanumeric and special characters can be entered in the textboxes?
EDIT:
I changed the property in my model to
[DataMember(EmitDefaultValue = false)]
[Required(ErrorMessage="CompanyName is Required")]
[StringLength(40, ErrorMessage = "Must be under 40 characters")]
public string CompanyName { get; set; }
To test I tried saving a blank CompanyName hoping to get a Server Error since it is Required. However, it saves the blank Company Name. Any ideas what might be missing?
This is MVC 2.0 but works just as well for 3.0
http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
Just look into Data Annotations, and do some Model Validation
EDIT:
your controller action will need something like
if(ModelState.IsValid)
{
//success logic
}
//failure logic
//return to view
you will also need
#Html.ErrorMessageFor(model => model.YourProperty)
in order to see the error messages being thrown.
Read the article it does a better job explaining this then anyone else will.
Just create a ViewModel object like this:
class Company
{
[Required]
[StringLength(40)]
[RegularExpression(#"someregexhere")]
public string CompanyName { get; set; }
}
And bind your View to that model. In this way you'll have both serverside and clientside validation. It's really easy.
#model Company
#using (Html.BeginForm()) {
Html.EditorFor(x => x.CompanyName)
<input type="submit" value="Save" />
}
Oh, this example uses Razor (MVC3), btw MVC2 works pretty much the same as far as I know.
Then just validate the incoming ViewModel in your controller by checking ModelState.IsValid.
Related
I am using a Dictionary<string, T> data structure to store phone numbers in a view model. The user can add or remove them client side before posting them back to the server.
Back story: I am using a Dictionary, because using a List<T> data structure with ASP.NET MVC 5 requires the names of form fields to contain sequential indexes starting with zero, and it becomes a real pain for JavaScript to add or remove those fields on screen without re-sequencing the index values. I found using a dictionary made it very easy. Now I'm doing a proof of concept task to enable dependency injection that allows us to use our NHibernate session to query the database during validations, and use the same session that the controllers and view models use instead of the "singleton" pattern that FluentValidation uses with MVC 5.
When using the [Validator(typeof(T))] attribute above view models, messages appear by the fields just fine, but the validator instances are singletons in the AppDomain, and the NHibernate session used by the validators is not the same one used by the controllers. This causes data to become out of sync during data validations. Validations that check the database start returning unexpected results, because NHibernate is caching so much data on the server, and it effectively has 2 separate caches.
Project setup
ASP.NET MVC 5
.NET Framework 4.5.1 (but we could upgrade)
FluentValidation v8.5.0
FluentValidation.Mvc5 v8.5.0
FluentValidation.ValidatorAttribute v8.5.0
View models
public class PersonForm
{
public PhoneFieldsCollection Phones { get; set; }
}
public class PhoneFieldsCollection
{
public Dictionary<string, PhoneNumberFields> Items { get; set; }
}
public class PhoneNumberFields
{
[Display(Name="Country Code")]
[DataType(DataType.PhoneNumber)]
public string CountryCode { get; set; }
[Display(Name="Phone Number")]
[DataType(DataType.PhoneNumber)]
public string PhoneNumber { get; set; }
[DataType(DataType.PhoneNumber)]
public string Extension { get; set; }
[Display(Name="Type")]
public string TypeCode { get; set; }
}
View model validators
public class PersonFormValidator : AbstractValidator<PersonForm>
{
private readonly IPersonRepository repository;
public PersonFormValidator(IPersonRepository repository)
{
// Later on in proof of concept I will need to query the database
this.repository = repository;
RuleForEach(model => model.Phones)
.SetValidator(new PhoneNumberFieldsValidator());
}
}
public class PhoneNumberFieldsValidator : AbstractValidator<PhoneNumberFields>
{
public PhoneNumberFieldsValidator()
{
RuleFor(model => model.PhoneNumber)
.NotEmpty();
}
}
Controller code to validate the view models:
private bool IsModelStateValid(PersonForm model)
{
// The `repository` field is an IPersonRepository object from the DI container
var validator = new PersonFormValidator(repository);
var results = validator.Validate(model);
if (results.IsValid)
return true;
results.AddToModelState(ModelState, "");
return false;
}
Razor template code to render the page
Page level template
#model PersonForm
#Html.EditorFor(model => model.Phones)
PhoneFieldCollection editor template
#model PhoneFieldsCollection
<fieldset class="form-group form-group-phones">
<legend class="control-label col-md-3 required">
Phone Numbers:
</legend>
<div class="col-md-9">
#Html.ValidationMessageFor(model => model, "", new { role = "alert", #class = "alert alert-danger", #for = Html.IdFor(model => model) + "-addButton" })
<ol class="list-unstyled">
#foreach (var item in Model.Items)
{
if (item.Value.IsClientSideTemplate)
{
<script type="text/html">
#Html.EditorFor(model => model.Items[item.Key])
</script>
}
else
{
#Html.EditorFor(model => model.Items[item.Key])
}
}
</ol>
<hr />
<p>
<button type="button" class="btn btn-default" id="#Html.IdFor(model => model)-addButton"
data-dynamiclist-action="add"
data-dynamiclist="fieldset.form-group-phones ol">
<span class="glyphicon glyphicon-plus"></span>
Add another phone number
</button>
</p>
</div>
</fieldset>
PhoneNumberFields editor template
#model PhoneNumberFields
#Html.EditorFor(model => model.PhoneNumber)
#Html.ValidationMessageFor(model => model.PhoneNumber)
Required field message not showing up
When I POST the form back to the server with the phone number field empty, I get a validation summary message at the top of the page saying "Phone number field is required", which is what I expect. However, the call to ValidationMessageFor(model => model.PhoneNumber) in the editor template is not causing the validation message to appear by the form field.
When running the application in debug mode I get Phones[0].PhoneNumber for the name of the field that has the validation message, but the name of the field in the view model is Phones.Items[123].PhoneNumber (where 123 is a database Id, or a timestamp generated by new Date().getTime() in JavaScript).
So I know why the validation message isn't showing up next to the field. The challenge is, how can I do this?
How can I validate a Dictionary with FluentValidation so the error messages appear by the form fields when using **ValidationMessageFor(model => model.PhoneNumber) in the editor template?
Update: Looks like there is a GitHub issue from 2017 related to this: Support for IDictionary Validation. The person found a workaround, but the maintainer for FluentValidation basically said supporting this is a monster pain and would require a major refactoring job. I might try fiddling with this myself and posting an answer if I can get something to work.
I've been searching around and I'm not able to find an answer on what seems like a simple requirement:
With MVC Data Annotation validation, can you show the validation message ('must be a string with a maximum length of 5') in the validation summary or next to field, but clear the value of the text box (when validation fails).
I've tried to use ModelState.Clear() and ModelState.Remove("CompanyName"), but this clears both the value and validation message (validation state).
I'm asking this because recently we had a penetration test and one of the recommendations was to not pre-populate secure values (credit card number etc) if validation fails. This is obviously a minor issue, but the recommendation was to not send the value back across the internet (from the server) if we didn't have to.
Here is the code I'm working with:
public ActionResult Edit()
{
return View();
}
[HttpPost]
public ActionResult Edit(CompanyInput input)
{
if (ModelState.IsValid)
{
return View("Success");
}
//ModelState.Clear // clears both the value and validation message
//ModelState.Remove("CompanyName") // same result
return View(new CompanyInput());
}
And the view model:
public class CompanyInput
{
[Required]
[StringLength(5)]
public string CompanyName { get; set; }
[DataType(DataType.EmailAddress)]
public string EmailAddress { get; set; }
}
And the view:
#model Test.Models.CompanyInput
<h2>Edit</h2>
#using (Html.BeginForm("Edit", "Company"))
{
#Html.EditorForModel()
<button type="submit">Submit</button>
}
The ModelState of each field holds more than just the value, so removing it from the collection outright removed your error message as expected. I believe you should be able to clear just the value however, by doing something like.
ModelState["CompanyName"].Value = null;
EDIT: Upon closer inspection I found that the Value property is of type ValueProviderResult, simply nulling it doesn't give the desired result, and because the properties of this class appear to be getters only you have to replace the instance with your own. I've tested the following and it works for me.
ModelState["CompanyName"].Value = new ValueProviderResult(string.Empty, string.Empty, ModelState["CompanyName"].Value.Culture);
Because the ModelState isn't valid, you will either have to create a custom validator or a jQuery ajax/json call to determine if the data needs to be cleared or not.
Just changing the model property to string.Empty or something like that won't do the trick because the entire view gets re-rendered with the previous successful posted model but with the ModelState validation errors.
Yes you can add error message like this
[Required(ErrorMessage = "must be a string with a maximum length of 5")]
Update after clarity from OP:
To clear e.g. Input.Field = string.Empty;
You can create a custom validation class which is inherited from ValidationAttribute class
The following link gives a clear idea about how to implement custom validation class suitable for your problem.
Custom Data Annotation
I have a new MVC 4 Application with a fairly basic View/Controller. The associated Model contains a couple properties that I've mapped to Hidden form fields. When the Page renders the first time (e.g. via the HttpGet Action) it all looks fine. But when the form is Post'ed by selecting the Submit button the resulting Model presented to the Action no longer has the Hidden field values set. Here is a walkthrough of the particulars.
Here is a sample of the Model:
public class Application
{
public bool ShowSideBars { get; set; }
}
Here is the initial Controller *Action* (which seems to work fine):
[HttpGet]
public ActionResult Application()
{
var model = Request.ParseFromQueryString<Application>();
model.ShowSideBars = true;
return View(model);
}
This maps to the View as follows:
<fieldset>
#Html.HiddenFor(m => m.ShowSideBars)
...
</fieldset>
This results in the following mark-up to be rendered inside the fieldset:
<input data-val="true" data-val-required="The ShowSideBars field is required." id="ShowSideBars" name="ShowSideBars" type="hidden" value="True" />
Note: I sure wish I knew why MVC has decided to add the '... field is required' content when I didn't flag it as required, but that's for another question
Here is the Action that is called when the form is submitted. At this point the aforementioned property will no longer be set to 'true'.
[HttpPost]
public ActionResult Application(Application application)
{
// Other work done here
return View(application);
}
At present, there are no custom Model Binders. Also, I've tested some other data types and I'm seeing the same thing.
Can someone explain why hidden form values are not being returned? Am I just doing this all wrong?
If you have the property in your model decorated with a ReadOnlyAttribute the value will not be populated back into the model for you. After all, it is read only.
I just had the same problem. The form didn't submitted the hidden property because the model class didn't had a proper getter and setter for that property.
I know that is not the issue you had, just figured it might help other people that will lend in this page.
I cannot reproduce the issue (ASP.NET MVC 4 Beta running on VS 2010 .NET 4.0).
Model:
public class Application
{
public bool ShowSideBars { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Application()
{
var model = new Application();
model.ShowSideBars = true;
return View(model);
}
[HttpPost]
public ActionResult Application(Application application)
{
return Content(application.ShowSideBars.ToString());
}
}
View:
#model Application
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.ShowSideBars)
<button type="submit">OK</button>
}
When I submit the form, the model binder correctly assigns the ShowSideBars property in the POST action to true.
Note: I sure wish I knew why MVC has decided to add the '... field is
required' content when I didn't flag it as required, but that's for
another question
That's because non-nullable types such as booleans are always required. You could stop ASP.NET MVC helpers from emitting HTML5 data-* client side validation attributes for them by putting the following line in Application_Start:
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
I think the fields MUST be within the form html tags for the hidden ones to be posted back and not ignored
try this:
public class Model
{
[ScaffoldColumn(false)]
public bool InvisibleProperty { get; set; }
}
more info here (ScaffoldColumn(bool value) vs HiddenInput(DisplayValue = bool value) in MVC)
In my case it was because I had declared a field instead of a property:
public BaseController.Modes Mode;
doesn't work. But:
public BaseController.Modes Mode { get; set; }
works. The default model binder only works with properties.
I kid you not, this is another reason it could happen.
My form had the same field in it twice. The other field was actually not in the form, but that doesn't matter.
Run this jQuery in the developer console to see how many elements come back:
$("[id$=PropertyName]"); // Search for ids ending with property name.
Example:
For me, in Core 6 the solution was removing [Editable(false)] attribute from the model class Id property which I wanted to tunnel through get/post as a hidden form field. In .Net 4.8 it was not a problem.
I just successfully set up a many-to-many relationship between BlogPosts and Topics in Entity Framework code first approach. So there are a list of topics ("CSS", "HTML", "ASP.NET") that a BlogPost can have many of and vice versa. So currently I had EF create 3 tables, the middle table being the id of both the BlogPost and the Topic itself.
Now I am in the Razor view of my homepage.
#model MvcBlog.Models.MyModel
#foreach (var post in Model.Posts)
{
<div class="blogpost">
<h2>#Html.DisplayFor(modelItem => post.Title)</h2>
<div class="post_info">#Html.DisplayFor(modelItem => post.DateCreated)<span class="right">Blog</span></div>
<p>#Html.Raw(post.Content)</p>
<div class="post_close">
<span class="left">
***********************
</span>
<span class="right"><img src="Content/images/comment.jpg" alt="" /> 0 comments</span>
</div>
</div>
}
All of the above works just fine, but I want to replace the * with the topics associated with this particular post. I can't seem to figure this out. Do I have to pass the model differently from the controller? Currently I am passing the entire DB to this page as it will be using various info from different tables. I am just lost on this one. Any help would be really appreciated! (Obviously I want to do something similar with comments)
Thanks!
No, no, no, do NOT pass the entire database to the view. You need to be abstracting your view data from your database. Create a view model containing just the data you need for this view in the format best suited for the view to consume it. Use your controller or model code, depending on whether you believe in fat or thin controllers, to transform the data from the database into the view model. The whole point of MVC is separation of concerns and if you let your data model leak into the view code, you will be losing this basic idea and lose the benefits of loose coupling between the various layers.
To get you started on the recommended course of action. Your view model will be a normal class:
public class PostViewModel
{
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public List<Topic> Topics { get; set; }
public List<Comment> Comments { get; set; }
}
In your controller, you populate what you need for the view
public ActionResult Index()
{
// assuming entity framework
List<PostViewModel> posts = (from p in context.Set<Post>()
select new PostViewModel {
Title = p.Title,
DateCreated = p.DateCreated,
Topics = p.Topics
}).ToList();
return View(posts);
}
And in your view
#model List<PostViewModel>
#foreach(Post post in Model)
{
#Html.DisplayFor(m=>m.Title)
#foreach(Topic topic in post.Topics)
{
}
}
Do I have to pass the model differently from the controller?
Yes. Make a model specifically for the needs of your view (a view model). Fill in the correct object graphs there (which blog goes to which topic). Then pass that instantiated object into the view and reference those objects in your view.
I've read a LOT on Stack and read a few articles else where on how to design a proper form. I ended up adopting the PRG method. article here
The problem I'm having is actually getting it to work in IE 8. It sort of works in Firefox. I just want to know what I'm doing wrong.
Update
The Form validation is now fixed thanks to trial and error. Validation scripts with IE DO NOT work with Jquery 1.7. They do work with Jquery 1.5 as they are suppose to.
Edit
The form validations also don't work in IE with this current setup. Any light into this would be appreciated.
Problems
1.The Create View is actually loaded as a partial view which is inside the Index view. Picture a popup to add a new Subscriber.
So when the form submits and it's invalid, it goes to the partial view Create which now looks ridiculous because the partial view was meant to be a popup not a page.
How can I fix this so the pop stays up if it's invalid, instead of posting back to the Create View?
Should I just forget about popups and do a new page for forms? What's the best practice and most practical solution to PRG and forms?
The structure I have setup looks like this:
Model
public class Subscribers
{
[Required(ErrorMessage="Name is required")]
[Display(Name = "Subscriber Name: ")]
public string Name { get; set; }
[Required(ErrorMessage = " URI is required")]
[Display(Name = "URI (email or url): ")]
public string URI { get; set; }
[Display(Name = "Channel: ")]
[Required(ErrorMessage = " Channel is required")]
public int SelectedChannelID { get; set; }
[Display(Name = "Subscriber Type: ")]
[Required(ErrorMessage = " Type is required")]
public int? SelectedSubscriberTypeID { get; set; }
public List<Models.Subscriber> getSubscribers()
{
Models.SwitchboardEntities db = new Models.SwitchboardEntities();
List<Models.Subscriber> subscriberList = db.Subscribers.ToList();
return subscriberList;
}
View
Create.cshmtl
#model Switchboard.Models.Subscribers
<script src="#Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script>
<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>
#using (#Html.BeginForm("Create", "Subscriber", Model, FormMethod.Post, new { id = "addForm" }))
{
<fieldset>
<legend><h3>Create</h3></legend>
<br />
#Html.ValidationSummary(true)
<div class="editor-fields">
#Html.LabelFor(xModels => xModels.Name)
#Html.EditorFor(xModels => xModels.Name)
#Html.ValidationMessageFor(xModels => xModels.Name)
</div>
<div class="editor-fields">
#Html.LabelFor(xModels => xModels.URI)
#Html.EditorFor(xModels => xModels.URI)
#Html.ValidationMessageFor(xModels => xModels.URI)
</div>
}
...
Controller
[HttpPost]
public ActionResult Create(Models.Subscribers model)
{
try
{
// TODO: Add insert logic here
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
else
{
return View(model);
}
}
catch
{
return View(model);
}
}
There are many things you're not doing quite right. First, jQuery should be loaded in your layout file (in the head section), not in your partial view (I assume Create.cshtml is partial). jQuery needs to be loaded as early as possible (and should be the first script you load), and it should be loaded before the page is rendered. By putting it so far in, you are just asking for trouble.
I would also load the jquery.validation and jquery.validation.unobtrusive in the layout as well.
If you can do so, i'd download the MVC4 beta, and generate a default internet application. The MVC4 default app uses a popup login dialog, that includes validation and does not go away if the validation fails.. exactly what you're looking for. You should be able to adapt their code to your own needs.
Also, make sure you have the latest versions of jQuery.validation, and jQuery.validation.unobtrusive. Use NuGet to retrieve the latest versions of everything.
You would need to store the data in the TempData object available to both the controller and the view (if needed) and then extract it and use it if its there on the next request.
Controller
public ActionResult Create(Models.Subscribers model) {
// .. snip ..
if (ModelState.IsValid) {
return RedirectToAction("Index");
}
// .. snip ..
this.TempData["SubscribersTemp"] = model;
this.RedirectToAction("Index");
}
This stores the model in the TempData object for the next request and sends the user back to the Index action. This action can do its normal routine and once it hits the #Html.RenderAction("RenderCreate") line, the new logic will grab the temp data and send it to the view.
public PartialViewResult RenderCreate() { // use name of partial view here
// The next line will use the model from the TempData if it exists or
// create a new, empty model if it does not.
// Using the create method above as an example, if the model is valid,
// the next line will create a new subscribers object to pass to the partial
// view. If it was invalid, it'll use the object stored in TempData instead.
Models.Subscribers model =
this.TempData["SubscribersTemp"] as Model.Subscribers ??
new Model.Subscribers();
return this.View(model);
}
The TempData object will persist the data for the next request only. Once that request is complete, the TempData object is cleaned.