I am a long time backend .Net developer who is new to web application development. I am using MVC 4, Razor, EF 5, and I have a basic understanding on how to make a routine DB driven MVC 4 site with these tools.
I need to create a custom form capability for a workflow scenario. I have the entire code-first schema designed and classes for different formfield types, and formvalue type.
The model that will be passed to a view will be a form class with a list of formvalues, which contain form field specifications. So the view will have to iterate through the form fields and dynamically choose what editors to use and so on.
My problem is that this prevents me from using any data annotation for client side validation. And every place I find advice on custom validation assumes (not surprisingly) that all the fields are members of a class. Whereas, in this case, all the fields are on a list.
My goal is to end up with validation messages on each field and a validation summary as you would normally have if the view was simply bound to an annotated class.
Any suggestions on how to approach this? Thanks in advance.
Imagine view logic that has the following:
#foreach (var fieldvalue in Model.FormFieldValues) {
// Logic that chooses the appropriate editor based on typeof(fieldvalue.FormField)
// Binds the editor to fieldvalue.Value
// Validation specs are contained in the Formfield obtained by fieldValue.FormField
// And my problem is setting up the validation without the aid of the normal method
// of data annotation or class level custom validation.
}
and the fieldvalue class looks like this:
public class FormFieldValue : EboEntity
{
public string Value { get; set; }
[Required]
public int FormFieldId { get; set; }
[Required]
public virtual FormField FormField { get; set; }
[Required]
public int FormId { get; set; }
[Required]
public virtual Form Form { get; set; }
}
And imagine that the FormField object has fields such as Required, Maxlength, and so on, depending on the kind of field it is (i. e. FormFieldText would be a subclass of FormField)
ANSWER:
Ok, you can tell I am new at MVC.
The answer to my question is that the specific editors take htmlAttributes which can control validation. So in the case where one of my formfields is a required textfield of stringlenth(10), I can invoke the editor as follows:
<div class="editor-field">
#Html.TextBoxFor(model => model.NoTexty, new {
required = true,
maxlength = 10,
placeholder = "Texty"})
#Html.ValidationMessageFor(model => model.NoTexty)
</div>
but in my case the htmlAddtributes won't be hard coded, but rather they will come from the fields in the formvalue.FormField object.
Sorry if I wasted anyone's time on this. But I will leave this here for any other newbies like me.
I have an EF entity Respondent that is automatically generated by EF from the database).
I had to expand this entity to add some validation rules and attributes to use within my View:
[MetadataType(typeof(RespondentMetadata))]
public partial class Respondent { }
public class RespondentMetadata
{
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
}
Now, in my controller, I need to check if this Respondent object has indeed value in FirstName (without checking the value explicitly, because I may have large number of such properties with various validation rules).
This check can be anywhere not necessary in [HttpPost] action (so, ModelState.IsValid N/A here).
How can I validate the entire entity anywhere in code?
Thanks.
The 'Text-Book-Way' is to add validation to your Model.
then you can make a check like
if (Respondent.IsValid())
{
}
You could use the Validator.ValidateObject method in order to perform validation anywhere in the code, but for that you need to stop using the MetadataTypeAttribute and explicitly associate the metadata class using the TypeDescriptor.AddProviderTransparent method.
Sample:
var respondent = new Respondent();
var provider = new AssociatedMetadataTypeTypeDescriptionProvider(
typeof (Respondent),
typeof (RespondentMetadata));
TypeDescriptor.AddProviderTransparent(provider, typeof (Respondent));
Validator.ValidateObject(
respondent,
new ValidationContext(respondent, null, null));
The Validator class does not seem to honor the attribute so this is the only workaround that I'm aware.
MVC Validation Fundamentals (with Entity Framework)
Scenario:
I have a model class as below (autogenerated via Entity Framework EF.x DbContext Generator).
(There is no view model at the moment).
public partial class Activity
{
public int Id { get; set; }
public byte Progress { get; set; }
public decimal ValueInContractCurrency { get; set; }
public System.DateTime ForecastStart { get; set; }
public System.DateTime ForecastEnd { get; set; }
public int DepartmentId { get; set; }
public int OwnerId { get; set; }
public int StageId { get; set; }
public int StatusId { get; set; }
public virtual Department Department { get; set; }
public virtual Owner Owner { get; set; }
public virtual Stage Stage { get; set; }
public virtual Status Status { get; set; }
}
When I submit a blank form on the strongly-typed view, I get these validation messages:
The Progress field is required.
The ValueInContractCurrency field is required.
The ForecastStart field is required.
The ForecastEnd field is required.
i.e. all the fields in the db table.
If I fill these in, and submit again, then the controller gets called. The controller then returns back to the view page due to IsValid being false.
The screen is then redisplayed with these validation messages:
The StageId field is required.
The DepartmentId field is required.
The StatusId field is required.
The OwnerId field is required.
i.e. all the foreign key fields in the db table (these are also all select boxes).
If I fill these in, the form then submits succesfully and is saved to the db.
Questions:
Where is the validation coming from, given that I have not used any [Required] attributes? Is this something to do with entity framework?
Why is the form not validating everything right away client-side, what's different about foreign keys (or select boxes) that they are only checked by IsValid() even though they are empty and therefore clearly invalid?
How do you make everything get validated in one step (for empty fields), so the user does not have to submit the form twice and all validation messages are shown at once? Do you have to turn off client side validation?
(I tried adding [Required] attributes to the foreign key fields, but that didn't seem to make any difference (presumably they only affect IsValid). I also tried calling Html.EnableClientValidation() but that didn't make any difference either).
4..Lastly, I've seen people using [MetadataType[MetadataType(typeof(...)]] for validation. Why would you do that if you have a viewmodel, or is it only if you don't?
Obviously I'm missing some fundamentals here, so in addition if anyone knows of a detailed tutorial on how exactly the MVC validation process works step-by-step including javascript/controller calls, rather than just another essay on attributes, then I could do with a link to that too :c)
More info for Mystere Man:
Solution setup as follows:
.NET4
MVC3
EF5
EF5.x Db Context Generator
"Add Code Generation Item" used on edmx design surface to associate EF.x Db Context Generator files (.tt files)
Controller looks like this:
// GET: /Activities/Create
public ActionResult Create()
{
ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerId = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractId = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageId = new SelectList(new List<string>());
ViewBag.StatusId = new SelectList(db.Status.Where(s => s.IsDefaultForNewActivity == true), "Id", "Name");
return View();
}
// POST: /Activities/Create
[HttpPost]
public ActionResult Create(Activity activity)
{
if (ModelState.IsValid)
{
db.Activities.Add(activity);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerId = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractId = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageId = new SelectList(db.Stages, "Id", "Number");
ViewBag.StatusId = new SelectList(db.Status, "Id", "Name");
return View(activity);
}
View is like this:
<!-- this refers to the EF.x DB Context class shown at the top of this post -->
#model RDMS.Activity
<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()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Activity</legend>
<div class="editor-label">
#Html.LabelFor(model => model.StageId, "Stage")
</div>
<div class="editor-field">
#Html.DropDownList("StageId", String.Empty)
#Html.ValidationMessageFor(model => model.StageId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Progress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Progress)
#Html.ValidationMessageFor(model => model.Progress)
</div>
<!-- ETC...-->
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
The reason why you get required validation is because the properties are value types (ie they can't be null). Since they can't be null, the framework requires you fill in values for them (otherwise it would have to throw some weird exception).
This problem manifests itself in several ways. I've seen this over and over and over here on Slashdot. I am not sure why so many people fall into this problem, but it's pretty common. Usually this results in a strange exception referring to no default constructor being thrown, but for some reason that did not happen here.
The problem stems from your use of ViewBag and naming the items in ViewBag the same as your model properties. When the page is submitted, the model binder gets confused by similarly named items.
Change these to add List at the end:
ViewBag.DepartmentList = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerList = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractList = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageList = new SelectList(new List<string>());
ViewBag.StatusList = new SelectList(db.Status
.Where(s => s.IsDefaultForNewActivity == true), "Id", "Name");
And change your view to use the strongly typed versions of DropDownListFor:
#Html.DropDownList(x => x.StageId, ViewBag.StageList, string.Empty)
... and so on
One other item of note. In the example above, I hope you're not using some kind of global data context or worse, a singleton. That would be disastrous and could cause data corruption.
If db is just a member of your controller that you new up in the constructor, that's ok, though not ideal. A better approach is to either create a new context in each action method, wrapped by a using statement (then the connection gets closed and destroyed right away) or implement IDisposable on the controller and call Dispose explicitly.
An even better approach is not doing any of this in your controller, but rather in a business layer, but that can wait until you're further along.
Where is the validation coming from, given that I have not used any [Required] attributes? Is this something to do with entity framework?
There is a default validation provider in MVC (not EF), checking two things :
the type of the provided value (a string in an int property) => (not sure, but something like) yyy is not valid for field xxx
a "check null" attribute for Value types (it will complain if you let an empty field corresponding to an int property, and would accept an empty field for an int? property). => The xxx field is required
This second behaviour can be desactivated in global.asax (the property name is rather clear) :
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
With client side validation enabled, these validations, and the one related to DataAnnotations (Required, StringLength...) will raise Validation errors on client side, before going to the Controller. It avoids a roundtrip on the server, so it's not useless. But of course, you can't rely on client validation only.
Why is the form not validating everything right away client-side, what's different about foreign keys (or select boxes) that they are only
checked by IsValid() even though they are empty and therefore clearly
invalid?
Hmmm, I must admit I ain't got a satisfying answer... So I let this one for a more competent one. They are taken as error in the ModelState.IsValid, because when the ClientSide Validation passed, you then go to ModelBinding (Model binding looks at your POSTed values, looks at the arguments of the corresponding HttpPost method (ActionResult Create for you), and try to bind the POSTed values with these arguments. In your case, the binding sees a Activity activity argument. And he doesn't get anything for StageId (for example) in your POSTed fields. As StageId is not nullable, he puts that as an error in the ModelState dictionary => ModelState is not valid anymore.
But I don't know why it's not catched by Client side validation, even with a Required attribute.
How do you make everything get validated in one step (for empty
fields), so the user does not have to submit the form twice and all
validation messages are shown at once? Do you have to turn off client
side validation?
Well, you'd have to turn off client validation, as you can't trust client validation only. But client validation, as said, is fine to avoid a useless roundtrip to the server.
Lastly, I've seen people using
[MetadataType(typeof(...)]] for validation. Why would you
do that if you have a viewmodel, or is it only if you don't?
It's only if you ain't got a ViewModel, but work on your Model class. It's only usefull when you work with Model First or Database First, as your entity classes are generated (with T4) each time your edmx changes. Then, if you put custom data annotations on your class, you would have to put it back manually after each class (file) generation, which would be stupid. So the [MetadataType(typeof()]] is a way to add annotations on a class, even if the "base class files" are re-generated.
Hope this helps a bit.
By the way, if you're interested in validation, take a look on FluentValidation.
This is a very nice... fluent validation (would you have guessed ?) library.
If the field is not nullable, EF think that it is required.
Cause foreign keys is not nullable, so navigation properties is required too.
To get all validation at once you need to use ViewModel that is transformed in entity mode in controller after validation.
More about attribute validation in mvc, you can read here.
I use a tiny_mce plugin to have a textarea with advanced editing (bold, italic, ...) see below. The attribute behind this textarea is required (validation).
#Html.TextAreaFor(m => m.Project.Content, new { style = "height:250px;width:100%;", #class = "mceEditor" })
#Html.ValidationMessageFor(m => m.Project.Content)
In my model, I defined the attribute as required.
[Required]
public string Content { get; set; }
The problem is that when I submit my form for the first time, I have a validation error on this control (even if something is typed in it). The second time the form is submitted I don't have any validation error.
Any idea?
Thanks.
Well, It's basically a bug in Asp.net MVC3 due to which the unobtrusive validations don't work for nested properties in TextAreaFor. You can read further at codeplex workitem.
I would suggest you to use the Editorfor in it's place and decorate the respective property with following code :-
[DataType(DataType.MultilineText)]
[Required]
public string Content { get; set; }
My project has viewmodels labeled like this:
public class locViewModel {
[Required]
public string City { get; set; }
}
If the view does not set a value then how can I detect this? Is this how the [required] works? Also what other kind of tags can I add to fields in a viewModel?
That means that for validation purposes you can do numerous things. For instance, in a View you can have client validation enabled and the form will not submit unless the control that is populating that property has data entered into it.
With a property with the Required attribute, and a Html.ValidationMessageFor(m => m.City, "City is required") you can notify the user on the client-side that it is a required field.
Here is a Great Resource on unobtrusive validation, and an in depth explanation of what you are looking for.