Validation a public List<KeyValuePair<int, string>> in MVC3 - asp.net-mvc-3

I have the following property in my Model :
[StringLength(100, ErrorMessage = "Must be less than 100 Chars", MinimumLength = 3)]
public List<KeyValuePair<int, string>> Authors { get; set; }
How can I Validate each string into the above list with DataAnnotation Validation attribute in MVC3 ?
Is it possible at all ?

Custom validation to the rescue! You need to do the following:
Implement the IValidatableObject interface
Implement the IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
Implement your logic to determine that each string has less than 100 characters
Here's the code
public class YourModel : IValidatableObject
{
public List<KeyValuePair<int, string>> Authors { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
foreach(KeyValuePair<int, string> myKvp in Authors)
{
if(myKvp.Value.Length >= 100)
{
yield return new ValidationResult("Must be less than 100 characters");
}
}
}
}
That way you can do a call to if(Model.IsValid) in your controller action, and return any errors that are reported. If your KeyValuePair entries are referring to a specific entity you can even do something like:
yield return new ValidationResult("Must be less than 100 characters", new string[] { myKvp.Key.ToString() });
You'd need to tailor it to fit the ID of the attribute on your page. This way, the error message could be specific to an input on your page.
I override IValidatableObject in many places as there's many cases where I do validation that's dependent on the state of my object. Your case is a bit different, but it's certainly do-able as can be seen from the above example. (All that's off the top of my head, however, so may not be perfect!)

Related

Custom Validation rule not working .NET 5 MVC

I'm trying to apply a custom validation rule to my MVC model, to ensure that the value in FieldA will not be less than the value in FieldB when both fields are not null. They are nullable in my model.
Custom Validation code, "NotLessThan"
public class NotLessThan : ValidationAttribute
{
private readonly string testedValue;
public NotLessThan(string testedValue)
{
this.testedValue = testedValue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var compareObject = context.ObjectType.GetProperty(this.testedValue);
var compareValue = compareObject.GetValue(context.ObjectInstance, null);
if ((decimal)value >= (decimal)compareValue)
{
return ValidationResult.Success;
}
return new ValidationResult(FormatErrorMessage(context.DisplayName));
}
}
Model Code
[NotLessThan("FieldB", ErrorMessage = "FieldA cannot be less than FieldB")]
public decimal? FieldA { get; set; }
public decimal? FieldB { get; set; }
View Code
#Html.TextBoxFor(t => t.FieldA)
#Html.ValidationMessageFor(t => t.FieldA)
I have other validation on other fields in this model, standard stuff like Required fields and so on. All that other validation triggers and returns the expected validation messages. However, even if I satisfy all my other required fields, my "NotLessThan" custom attribute does not fire, even with failing input.
What am I doing wrong?

Asp.Net Mvc 2 Custom Validation Issue

My question is regarding MVC 2 custom validation. I'm stuck on a particular issue and I'm unsure how to get around it. I'm fairly sure it's more figuring out how to do it logically and then implementing it in code.
So what we have is a metadata class for a product. Each product has a product product ID which is the PK, and obviously unique. Each product also has a product code which is also unique. Customers enter the product code however, but the nature of the code ensures that only one code is attached to one product so it will be unique.
Here is a snippet from the metadata class:
public partial class ProductMetadata
{
[DisplayName("Product Name")]
[Required(ErrorMessage = "Product Name is required.")]
public string ProductName { get; set; }
[DisplayName("Product Code")]
[Required(ErrorMessage = "Product Code is required.")]
[ProductCodeAlreadyExistsValidator(ErrorMessage = "This Product code is in use.")]
public string ProductCode { get; set; }
}
The 'ProductCodeAlreadyExistsValidator' works perfectly when creating a new product. The problem lies in editing an existing product as the validation is being performed on this attribute again, and it is finding itself in the database. This results in the validation failing.
Here is a snippet from the custom validator:
public class ProductCodeAlreadyExistsValidator : ValidationAttribute
{
private readonly object typeId = new object();
private const string defaultErrorMessage = "Product Code {0} is already present in the system.";
public ProductCodeAlreadyExistsValidator()
: base(defaultErrorMessage)
{
}
public override object TypeId
{
get
{
return typeId;
}
}
public string CustomerType { get; set; }
public string CustomerFriendlyType { get; set; }
public override string FormatErrorMessage(string roleName)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, roleName);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (!IsValid(value))
{
string errorMessage = string.Format(defaultErrorMessage, validationContext.MemberName, value as string);
return new ValidationResult(errorMessage, new string[] { validationContext.MemberName });
}
return null;
}
public override bool IsValid(object value)
{
bool alreadyPresent = false;
string ProductCode = value as string;
using (ModelContainer ctn = new ModelContainer())
{
alreadyPresent = ctn.Products.Where(t => t.ProductCode == ProductCode).Count() > 0;
}
return !alreadyPresent;
}
}
It might be a relatively simple fix however I seem to have hit a brick wall with it. Can anyone offer any advice?
Code seems ok to me. I think you need to identify if you are doing an insert or an update so the validation can ignore the checking when updating the field. You could check if the ID of the item editted is the same ID found by the code, it would be identified as a Editting.

Is there any way to force a model to be revalidated in an MVC3 controller?

I have a Model which contains an Address and Person twice, once for the "main" contact, and once for the "invoice" contact, and a boolean value called InvoiceContactSameAsMain - a clumsy name, but descriptive. The getter of the property checks to see if the Address and Contact objects for "main" and "invoice" are the same, and returns true if they are. The setter checks to see if the value is true, and if so, copies the main Person over the invoice Person , and the main Address over the invoice Address.
In my View, the boolean value is represented by a check box (as you'd expect). Attached to this is a small JS function which, if the check box is checked, hides the invoice fields and "switches off" the client-side validation by setting the data-val HTML attribute to false and forcing a re-parse of the unobtrusive validation attributes in the form. Un-checking the box naturally shows the fields and turns the validation back on.
All of this works fine, until I get to my Controller.
Despite the Model being "valid" and containing the correct fields (thanks to my InvoiceContactSameAsMain setter), ModelState.IsValid remains resolutely false, and I can't seem to find any way to revalidate the model. If I clear the ModelState, any and all errors disappear. I'd very much rather avoid digging through the fields in the ModelState by name, as the Person and Address objects are used throughout the project and may need to change or be extended at some point.
Is there something obvious I've missed here that will allow me to revalidate the ModelState? I've tried TryUpdateModel and TryValidateModel, but they both appear to use the cached ModelState values. I've even tried recursively calling my Action again, passing in the "fixed" model. I'm almost thankful that one didn't work.
Please let me know if any more detail or examples will help.
Edit: Obviously, if this is completely the wrong way to approach the problem, just let me know.
Edit 2: Added code samples as per Ron Sijm's suggestion.
The model is as follows:
public class Details
{
public int? UserID { get; set; }
public Company Company { get; set; }
public Address CompanyAddress { get; set; }
public Person MainPerson { get; set; }
public Address InvoiceAddress { get; set; }
public Person InvoiceContact { get; set; }
[Display(Name = "Promotional code")]
[StringLength(20, ErrorMessage = "Promotional code should not exceed 20 characters")]
public string PromotionalCode { get; set; }
[Display(Name = "Invoice contact same as main")]
public bool InvoiceContactSameasMain
{
get { return InvoiceContact.Equals(MainPerson); }
set
{
if (value)
{
InvoiceContact = MainPerson.Copy();
InvoiceAddress = CompanyAddress.Copy();
}
}
}
[_Common.MustAccept]
[Display(Name = "I agree with the Privacy Policy")]
public bool PrivacyFlag { get; set; }
[Display(Name = "Please subscribe to Sodexo News Letter")]
public bool MarketingOption { get; set; }
[Display(Name = "Contract number")]
public int? ContractNumber { get; set; }
public Details()
{
Company = new Company();
CompanyAddress = new Address();
MainPerson = new Person();
InvoiceAddress = new Address();
InvoiceContact = new Person();
}
}
This is wrapped in a ViewModel as there are a number of SelectLists involved in the page:
public class DetailsViewModel
{
public Details Details { get; set; }
public SelectList MainContactTitles { get; set; }
public SelectList InvoiceContactTitles { get; set; }
public SelectList SICCodes { get; set; }
public SelectList TypesOfBusiness { get; set; }
public SelectList NumbersOfEmployees { get; set; }
public DetailsViewModel()
{
}
}
The Controller's two relevant actions are as follows:
public class DetailsController : _ClientController
{
[Authorize]
public ActionResult Index()
{
DetailsViewModel viewModel = new DetailsViewModel();
if (Client == null)
{
viewModel.Details = DetailsFunctions.GetClient((int)UserId, null);
}
else
{
viewModel.Details = DetailsFunctions.GetClient((int)UserId, Client.ContractNumber);
}
viewModel.MainContactTitles = DetailsFunctions.GetTitles((int)UserId, viewModel.Details.MainPerson.title);
viewModel.InvoiceContactTitles = DetailsFunctions.GetTitles((int)UserId, viewModel.Details.InvoiceContact.title);
viewModel.SICCodes = DetailsFunctions.GetSICCodes(viewModel.Details.Company.sic_code);
viewModel.NumbersOfEmployees = DetailsFunctions.GetNumbersOfEmployees(viewModel.Details.Company.number_of_employees);
viewModel.TypesOfBusiness = DetailsFunctions.GetTypesOfBusiness(viewModel.Details.Company.public_private);
return View(viewModel);
}
[Authorize]
[HttpPost]
public ActionResult Index(DetailsViewModel ViewModel)
{
if (ModelState.IsValid)
{
//go to main page for now
DetailsFunctions.SetClient((int)UserId, ViewModel.Details);
return RedirectToAction("Index", "Home");
}
else
{
ViewModel.MainContactTitles = DetailsFunctions.GetTitles((int)UserId, ViewModel.Details.MainPerson.title);
ViewModel.InvoiceContactTitles = DetailsFunctions.GetTitles((int)UserId, ViewModel.Details.InvoiceContact.title);
ViewModel.SICCodes = DetailsFunctions.GetSICCodes(ViewModel.Details.Company.sic_code);
ViewModel.NumbersOfEmployees = DetailsFunctions.GetNumbersOfEmployees(ViewModel.Details.Company.number_of_employees);
ViewModel.TypesOfBusiness = DetailsFunctions.GetTypesOfBusiness(ViewModel.Details.Company.public_private);
return View(ViewModel);
}
}
}
I can provide the view and JS if needs be, but as the Model binding is all working just fine, I'm not sure how much help that is.
It's a moderately crap hack, but I've ended up just clearing the ModelState errors for the relevant fields in the controller before checking ModelState.IsValid:
if(ViewModel.Details.InvoiceContactSameasMain)
{
//iterate all ModelState values, grabbing the keys we want to clear errors from
foreach (string Key in ModelState.Keys)
{
if (Key.StartsWith("Details.InvoiceContact") || Key.Startwith("Details.InvoiceAddress"))
{
ModelState[Key].Errors.Clear();
}
}
}
The only upside is, if the Person or Address objects change, this code won't need to be altered.

MVC3 correct usage of model

While looking at MVC3 examples of models. Most people tend to use model classes to create class definitions for business objects to hold data with very little or no logic. The sole purpose of model then is to be passed around. For example:
namespace MvcMusicStore.Models
{
public class Cart
{
[Key]
public int RecordId { get; set; }
public string CartId { get; set; }
public int AlbumId { get; set; }
public int Count { get; set; }
public System.DateTime DateCreated { get; set; }
public virtual Album Album { get; set; }
}
}
Is this how models classes should typically be used in MVC? Sometimes I see logic but are very specific to manipulating data. For exmaple:
public partial class ShoppingCart
{
MusicStoreEntities storeDB = new MusicStoreEntities();
string ShoppingCartId { get; set; }
public const string CartSessionKey = "CartId";
public static ShoppingCart GetCart(HttpContextBase context)
{
var cart = new ShoppingCart();
cart.ShoppingCartId = cart.GetCartId(context);
return cart;
}
// Helper method to simplify shopping cart calls
public static ShoppingCart GetCart(Controller controller)
{
return GetCart(controller.HttpContext);
}
public void AddToCart(Album album)
{
// Get the matching cart and album instances
var cartItem = storeDB.Carts.SingleOrDefault(
c => c.CartId == ShoppingCartId
&& c.AlbumId == album.AlbumId);
if (cartItem == null)
{
// Create a new cart item if no cart item exists
cartItem = new Cart
{
AlbumId = album.AlbumId,
CartId = ShoppingCartId,
Count = 1,
DateCreated = DateTime.Now
};
storeDB.Carts.Add(cartItem);
}
else
{
// If the item does exist in the cart, then add one to the quantity
cartItem.Count++;
}
// Save changes
storeDB.SaveChanges();
}
}
What is the correct way of using the Model? In classic MVC definition, model is where the intelligence of the application should be. However looking at MVC3 samples, a lot of logic is in controller or another layer for say Data Access. What is the advantage of this?
Thanks
The short answer is it provides for separation of model definitions and data access, which are conceptually different. When you separate your Data Access to its own layer (not as part of either controllers or models) you achieve far greater De-coupling.
That said there are a lot of different ways developers are using MVC, and model as data accessors is definitely one of them - the framework even supports models based on entity framework; go straight from the database to a usable model.
There's always the "fat controller" pattern of course; that is, stick all your handling logic inside the controller. I wouldn't recommend that because it will very quickly spiral into unmaintainable code.

Using Data Annotations Validation Manually and Object Graphs

Let's assume that I have two simple classes:
public class CustomerDetails
{
[Required]
public string Address
{
get;
set;
}
}
public class Customer
{
public Customer()
{
Details = new CustomerDetails();
}
[Required]
public string Name
{
get;
set;
}
public CustomerDetails Details
{
get;
private set;
}
}
When I try to manually validate Customer class in a Console application in this way:
var customer = new Customer() { Name = "Conrad" };
var context = new ValidationContext(customer, null, null);
var results = new List<ValidationResult>();
Validator.TryValidateObject(customer, context, true);
Then -even though I chose to validate all properties of the customer instance- Validator just validates the Name property of the customer instance, but not the Address property of the Details.
Is this by design or am I missing something here? Moreover, if this is by design then is there a robust way to manually validate the full object graph decorated with validation attributes, including nested types instead of using validator for the whole object graph manually?
Please note that this is tested within a Console application and not an ASP.NET MVC application.
Kind regards.
I had almost the same problem but with the collection of nested objects. I was able to resolve it by implementing IValidatableObject on a container class. In your case it's slightly easier. Something like this:
public class Customer : IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var context = new ValidationContext(this.Details, validationContext.ServiceContainer, validationContext.Items);
var results = new List<ValidationResult>();
Validator.TryValidateObject(this.Details, context, results);
return results;
}
}
Hope this helps.

Resources