Client-side validation for wrapped/multi model views - c#

I have these 2 model classes wrapped in one model. The strongly typed view and server side validation works well enough but I can't get the client-side validation working for this wrapped model class.
Here's my client-side code:
Sys.Mvc.ValidatorRegistry.validators.usernameEmail = function (rule) {
var emailProperty = rule.ValidationParameters.emailProperty;
var message = rule.ErrorMessage;
return function (value, context) {
if (!value || !value.length) {
return true;
}
var usernameField = context.fieldContext.elements[0];
var emailFieldId = $('input[name = "' + emailProperty + '"]').attr('id');
var emailField = $get(emailFieldId, usernameField.form);
//...validation stuff...
};
};
The problem is that emailProperty returns only the property name stated in its model ie. "EmailAddress" but the strongly typed view assigns the name as "Model1Name.EmailAddress" since it is called like:
<%: Html.TextBoxFor(m => m.Model1Name.EmailAddress)%>
Thus it returns null when looking for the property and I get an error. So I guess my questions here would be:
Is there a way to get the assigned model name ("Model1Name") so I
could append it with the property name?
Could I pass the assigned model name from the DataAnnotations/ModelValidationRule classes? If so, how?
Here are my model classes:
public class Model1
{
public string EmailAddress{ get; set; }
...
}
public class Model2
{
[UsernameEmail]
public string Username{ get; set; }
...
}
public class WrappedModel
{
public Model1 Model1Name{ get; set; }
public Model2 Model2Name { get; set; }
}
Side-note: The client side validation works well for single model views.
Edit
After much debugging, I found out that
viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId("EmailAddress");
in my DataAnnotationsModelValidator is returning only the parameter name "EmailAddress" and is leaving out its prefix "Model1Name" thus returning the wrong Id.
Any help would be appreciated. Thanks.

Your custom [UsenameEmail] attribute is a piece which is responsible for emitting correct client-side validation metadata. To be more precise, there should be validator adapter registered for that. You need to make sure adapter generates full property name, i.e. "Model1Name.EmailAddress", then "usernameEmail" client-side function will receive correct field name and will operate as expected.
See "DataAnnotationsModelValidatorProvider.RegisterAdapter(...)" for more details.
Here is implementation sample - http://weblogs.asp.net/jacqueseloff/archive/2009/12/17/asp-net-mvc-2-rc-validating-credit-card-numbers.aspx

Related

ASP.NET MVC Binding with Remote Validation

I have a model ModelA with a member toBeRemoteChecked and a model MapToA with a member valueToMap. Whenever I create an instance of ModelA, I also need an instance of MapToA, so I have a model CreateModelA which includes a member modelA and a member valueToMap. When the form is submitted, I add the modelA to the database table ModelA and create and add an instance to MapToA which consists of an id of modelA and the valueToMap. In Terms of code
public class ModelA
{
[Key]
public int ID { get; set; }
[Required, Remote("isValid", "MyController", ErrorMessage = "not valid")]
public string toBeRemoteChecked { get; set; }
}
public class MapToA
{
[Key]
public int Map_ID { get; set; }
[Required]
public int modelAID { get; set; }
[Required]
public int valueToMap { get; set; }
}
public class CreateModelA
{
public ModelA modelA { get; set; };
public int valueToMap { get; set; };
}
When I edit an instance of ModelA, values in MapToA don't matter (and in most cases there's more than one instance of mapToA with the same modelA id), but the remote validation of toBeRemoteChecked remains important.
My Problem: binding for the validation method:
public ActionResult isValid(string toBeRemoteChecked) { ... }
If I leave it as it is, it is working when editing a ModelA, but not when I'm creating a ModelA via CreateModelA (I always get null value in toBeRemoteChecked). When I use the BindPrefix
public ActionResult isValid([Bind(Prefix = "modelA.toBeRemoteChecked")] string toBeRemoteChecked) { ... }
it is working when I create a ModelA, but not when I'm editing it.
When I try to change the "name" in the Create.cshtml by adding a ... #Name = "toBeRemoteChecked" ... (instead of the modelA.toBeRemoteChecked that's created by the HTML helper) in the htmlAttributes of the #Html.TextBoxFor, then validation is working, but the binding of the value to the table get's lost and I get the error when the values are saved to the database (null value).
So, how do I achieve the different binding for creating and editing?
So far, my workaround is to make ModelA and CreateModelA : IValidatableObject and check the member toBeRemoteChecked in my public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) method. But that one displays the error messages on top of the form and not at the place of the TextFor box.
So: best solution: how to do the binding that the remote validation works in both cases?
Second best: how to display the error messages of IValidatableObject near the object where it belongs to (and get the error messages right at hand, not after submitting)
Different ideas or solutions: welcome.
Thanks.
An interesting issue, and similar to this question, which as an result I reported a issue at Codeplex, but it has not been resolved yet. The link includes a suggested modification to the jquery.validate.js file which would solve this (it strips the prefix) but that means you would need to maintain it whenever you update the script so not really desirable.
One option would be to change CreateModelA to inherit from ModelA and just add the int valueToMap property so that you never have a prefix - your always using #Html.TextBoxFor(m => m.toBeRemoteChecked) instead of #Html.TextBoxFor(m => m.modelA.toBeRemoteChecked)
Also, [Remote] is client side only validation, which means you still need to perform the validation in the server when you post. So you could just accept that you don't have client side validation for the property, and instead add a ModelState error in the POST methods(s) for the property and return the view so that its displayed in the associated ValidationMessageFor() element
Side note: The fact your model has a [Key] attribute suggests this is a data model, not a view model, and [Remote] is a view specific attribute. You should be using view models, especially when editing data. (refer What is ViewModel in MVC?)
I found a solution without inheritance (and without view models) that solves my binding problem with just little change to my code.
There's two ways of binding for remote validation, you can either just pass the member that has to be remote checked
public ActionResult isValid(string toBeRemoteChecked) { ... }
or you can pass the instance of the class of that member.
public ActionResult isValid(ModelA modelA) { ... }
Inside the second variant, of course, you have to replace toBeRemoteChecked with modelA.toBeRemoteChecked. On this second version the binding works in both cases - when editing and also when creating my instance of ModelA in the context above. In order to make the binding work, it's crucial that the parameter name of the remote validation method matches the member name in the CreateModelA, i.e. modelA in my case.
In case you have a very complex model, you can just initialize the parameter modelA with the members you want to use by using bind/include, i.e. in my case I'd use
public ActionResult isValid([Bind(Include = "toBeRemoteChecked")] ModelA modelA) { ... }
By default (without Include), all other members will remain null or have a default value - so you need to use Include only if you need other members for validation as well - in my case, I would have the same when omitting the Include)

ASP.NET MVC Modify view model property before validation

I've been googling like crazy without result, maybe I'm just missing the correct keywords.
I have a class with a custom validation attribute on a property. I want to "clean" the value before validation, removing the white-space and special characters that we accept but that we don't want to save to the database.
public class PersonViewModel
{
[SocialSecurityNumberLuhn(ErrorMessage = "Incorrect social security number")]
public string SocialSecurityNumber { get; set; }
}
I would want to do something like this:
public class PersonViewModel
{
[CleanWhiteSpace]
[SocialSecurityNumberLuhn(ErrorMessage = "Incorrect social security number")]
public string SocialSecurityNumber { get; set; }
}
For example 1985-03-15-1234 should be saved and validated as 19850315-1234.
Any suggestions? What's the neatest approach?
If you change the auto-implemented property into a manual-implemented property then you can perform the "cleaning" step when the value is set, so it can only be stored in the model in a "clean" state. Something like this:
public class PersonViewModel
{
private string _socialSecurityNumber;
[SocialSecurityNumberLuhn(ErrorMessage = "Incorrect social security number")]
public string SocialSecurityNumber
{
get { return _socialSecurityNumber; }
set
{
_socialSecurityNumber = CleanSocialSecurityNumber(value);
}
}
}
The recommended approach here is to use a service layer. View models should not include any logic. With a service layer Your controller would call a method on its associated service and this method would return your view model with the clean SSN.
In this tutorial you will see how the service layer can be structured and you could adapt the pattern to clean your data before validation.
http://www.asp.net/mvc/tutorials/older-versions/models-(data)/validating-with-a-service-layer-cs
The use of a ValidationAttribute in your example would be an incorrect usage also.
Controller where you would inject / instantiate the service class
public ActionResult GetPerson(int PersonId){
return _personService.GetPerson(personId);
}
The service method
public PersonViewModel GetPerson(int Id){
// get the data (maybe from DAL) and clean returning view model
return new PersonViewModel(){SocialSecurityNumber = Clean(...)};
}
Hope this gives you some direction.

Asp.net MVC 3 - Data Validation

I'm new at ASP.NET MVC web framework. My database is compound of a single model class("Movie"). I need to validate user's from entering existing data, for instance, a database row could be:
Title - "Indiana Jones and the lost Arc"
Price - $10.00
If another user tries to insert into the database the same data above, provide an error message and prevent from submitting the form collection.
First of all, I think that your question need a "program as answer" but I'll try to suggest you from where to start for working with validation. Suppose you have this model:
class Movie {
public Guid Id { get; set; }
[Required(ErrorMessage="Title is required.")]
[Remote("UniqueTitle", "Validation")]
public String Title { get; set; }
[Required(ErrorMessage="Price is required.")]
public float Price { get; set; }
}
You can decorate it for "simple validation" using Data Annotation. I've used a specific attribute, called Remote.
This attribute allow you to define a custom, server-side, logic to validate the model.
Now, you can create a validation controller where check that provided value is not already in use:
class ValidationController : Controller {
private IDbContext db = ...;
public ActionResult UniqueTitle(String title) {
var item = db.Movies.FirstOrDefault(m => m.Title.Equals(title));
return Json(item == null, JsonRequestBehavior.AllowGet);
}
}
Now you are ready to validate your model.
I hope this can help.

An object used as a model in a partial view creates another in the controller?

There is something weird going on in my app. It's not dammageable, but it's a curious behavior and I'm reaching out to you to understand what's happening.
I was working on some partial view based on a model, and it worked. I figured out I had to replace a lot of stuff with the correct input.
So here's a snippet of my old model:
public class SearchObjInfo
{
public string m_ObjName { get; set; }
public string m_ObjType { get; set; }
public decimal? m_ObjNumber { get; set; }
public string m_ObjSymbol { get; set; }
public string m_ObjPower { get; set; }
}
And here's the same snippet with the new class I made to construct this partial view:
public class SearchObjInfoPartial
{
public string m_ObjName { get; set; }
public IEnumerable<SelectListItem> m_ObjType { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:0}")]
public int m_ObjNumber { get; set; }
public IEnumerable<SelectListItem> m_ObjPower { get; set; }
public IEnumerable<SelectListItem> m_ObjSymbol { get; set; }
}
Now the way the render is made is actually quite identical, and not. I used lots of stuff like these before:
<label>
Text: Write a name, part of a name, or a word.
</label>
Object Name: #Html.TextBox("_objectName") <br/>
Object Number: <input type="number" min="0" max="9999" name="_objNumber" value="decimal" style="width: 70px"/><br/>
Type: #Html.DropDownList("_objType", "All") <br/>
Power: #Html.DropDownList("_objSymbol", "=") #Html.DropDownList("_objValue", String.Empty)<br/>
But now I render my partial view this way:
#model MyApp.Utilities.SearchObjInfoPartial
Object Name: #Html.TextBoxFor(item => item.m_ObjName, Model.m_ObjName, String.Empty) <br/>
Object Number: #Html.EditorFor(item => item.m_ObjNumber)<br />
Power: #Html.DropDownListFor(item => item.m_ObjPower, Model.m_ObjPower, String.Empty) #Html.DropDownListFor(item => item.m_ObjSymbol, Model.m_ObjSymbol, String.Empty)
Type: #Html.DropDownListFor(item => item.m_ObjType, Model.m_ObjType, String.Empty) <br/>
Before rendering I deal with the SelectLists, no problems here.
Now here's where it gets interesting:
In my controllers I used to have methods receiving huge amounts of data (see here: How to deal with many possible values to make a query?)
But now I made something else. Without thinking, I tried to add the old search model in the controller method like this:
public ActionResult BrowseObjectList(SearchObjInfo searchObj, string _objName, (...))
And I just found out that it works even if the receiving object is not the same as the one used in my partial view model. How is that even possible? I mean, the proper fields will fill up and I can "safely" deal with my searchObj item, though I do not find this secure after all...
Thats what MVC framework does for you man.
Browser simply sends the form collection to server as Name Value Collection. As the request hits server, MVC framework will match the values with parameter in the Action method.
Form collection values are mapped to Model object properties. This is done by doing a match with Property Name and Name of the value in Form collection. Just check the client side code by view source, you can see that the input tags will have an attribute 'name' which matches with the property name of model.
QueryString values will also be mapped to parameters in Action method based on name.
Even you add a hidden field and specify a parameter with same name in action method...tada you will get the value of hidden field in that variable on post back
In your case though the model is different, its property name are same m_ObjName, m_ObjType, m_ObjNumber, m_ObjSymbol, m_ObjPower. So MVC do a match for you.
Try with different property name and see the results ;-)

MVC3 format nullable DateTime in TextBoxFor without changing model

I am new to MVC3 and am still trying to pick up on the good programming practices. I had a heck of a time trying to format how a DateTime? was being displayed in my MVC3 project that doesn't have an explicit ModelName.cs file associated with the class the date was coming from.
We had a database already in place and use a .edmx (ours is called Pooling.edmx) that we get our models from. I obviously didn't want to edit the designer file to fit this widely accepted solution: Date only from TextBoxFor().
I then tried another solution which I found here: Using Html.TextBoxFor with class and custom property (MVC)
which uses:
#Html.TextBoxFor(m => m.Name, new { data_bind="value: Name", #class = "title width-7" })
This worked as I was able to use custom attributes, add class names, and set a Value all at once.
I transformed this:
#Html.TextBoxFor(m => Model.PrePoolOwner.OglDateEffective, new Dictionary<string, object> { { "class", "check-dirty input-small datePicker" }, { "data-original-value", #Model.PrePoolOwner.OglDateEffective } })
into this (which seems really ugly...and leads to me to the question):
#Html.TextBoxFor(m => Model.PrePoolOwner.OglDateEffective, new { data_original_value = Model.PrePoolOwner.OglDateEffective.HasValue ? Model.PrePoolOwner.OglDateEffective.Value.ToString("MM/dd/yyyy") : null, #class = "datePicker check-dirty", #Value = Model.PrePoolOwner.OglDateEffective.HasValue ? Model.PrePoolOwner.OglDateEffective.Value.ToString("MM/dd/yyyy") : null })
Is it better to find and use these other ways (like underscores will translate into dashes, etc) to display the information, or should I be having a ModelName.cs file to change how it is displayed at the Model level?
For some reason I feel having a huge Pooling.edmx file, that maps out our database, is limiting us now and will in the future on how we access/present/change data as the website evolves.
To get a "PrePoolOwner" object which is called up above by Model.PrePoolOwner.OglDateEffective, we have a PrePoolOwnerRow.cs file that does:
namespace OCC_Tracker.Models
{
public class PrePoolOwnerRow
{
public bool Dirty { get; set; }
public bool Delete { get; set; }
public PrePoolOwner PrePoolOwner { get; set; }
public PrePoolOwnerRow(PrePoolOwner owner)
{
this.Dirty = false;
this.Delete = false;
this.PrePoolOwner = owner;
}
public PrePoolOwnerRow()
{ }
}
}
We then call at the top of our .cshtml file
#model OCC_Tracker.Models.PrePoolOwnerRow
Ok, so a few suggestions.
First, in your example, PrePoolOwnerRow is your view model. This, in itself, is fine. But the code smell is where you expose PrePoolOwner -- a domain entity -- through your view model, PrePoolOwnerRow.
So first thing I would suggest is to update your view model to something more like this:
public class PrePoolOwnerModel
{
public bool Dirty { get; set; }
public bool Delete { get; set; }
public DateTime? OglDateEffective { get; set; }
public String OglDateEffective { get; set; }
// Other public properties here that map to properties on your PrePoolOwner entity.
}
All I did here was drop the reference to the domain model, and replace it with (a placehold comment to) the properties from your model, needed by your view.
In your controller, fetch your PrePoolOwner model, and map it to your view model using AutoMapper (this is a hypothetical example, as I don't know what your view is doing):
public ViewResult Index(int id)
{
PrePoolOwner entity = myservice.GetPrePoolOwner(id);
PrePoolOwnerModel model = Mapper.Map<PrePoolOwnerModel>(entity);
return View(model);
}
Now, to address the issue w/ the DateTime textbox, you should look at using MVC Editor Templates (this is another subject altogether, but Google it to find many topics covering the subject). This gives you more flexibility and re-usability over rendering elements of like types (i.e. DateTime).
But, aside from using that, you can add another property to your model, and use AutoMapper to set the DateTime appropriately. So, something like this in your controller, execpt you would set up a mapping in AutoMapper to handle this:
public class PrePoolOwnerModel
{
....
public String OglDateEffectiveValue { get; set; }
....
}
public ViewResult Index(int id)
{
....
model.OglDateEffectiveValue = model.OglDateEffective.HasValue ?
model.OglDateEffective.Value.ToString("MM/dd/yyyy") :
String.Empty;
....
}
Once that is set up, you can just use this new model property (OglDateEffectiveValue) for your attributes on your textbox.
I know there's a lot I covered there, but dig in and experiment with modeling your view models like this, and using AutoMapper to map the data to your view model exactly like you need it to be on your view.
Keep your view logic very simple. Avoid using anything crazy beyond the occasion loop, and maybe an if conditional here or there.

Categories

Resources