How do I prevent calling IValidatableObject.Validate for properties and call it for the top level model only?
public abstract class Foo, IValidatableObject
{
public virtual Foo Related { get; set; }
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// This is first called for the 'Related' property
// and then for the model itself
// I want this to be called for the top level model only
}
}
I am not sure if there is a more elegant way, but what about creating a new class overriding the Validate method of Foo?
Something like:
public class FooNoValidation : Foo
{
public override IEnumerable Validate(ValidationContext validationContext)
{
yield break;
}
}
Of course this would require the Validate method on Foo to be virtual and you to refer to FooNoValidation on the Foo parent class, but might work.
I know its a bit of a hack, but if you just need to get this to work this should make that happen.
Related
In my applicantion, I browse to the URL by supplying the parameters through query string. Based on the URI, the respective controller's action is triggered, and the parameters supplied are auto-mapped to my model.
URL: http://{host}:{port}/{website}/{controller}/{action}?{querystring}
URI:
/{controller}/{Action}?{QueryString}
My URI: Employee/Add?EmployeeCode=Code3&EmployeeId=103
EmployeeModel
public class EmployeeModel
{
public Employee()
{
}
public string EmployeeId { get; set; }
public string EmployeeCode { get; set; }
//Some more properties here
}
EmployeeController
[HttpGet]
[Route("Add")]
public IActionResult Add([FromUri] EmployeeModel model)
{
//Some code here
}
While this all works fabulous, when I browse through, below is the order in which break-points hit,
Add method of EmployeeController
Default constructor of EmployeeModel
set method of EmployeeId property of EmployeeModel
set method of EmployeeCode property of EmployeeModel
I suspect the order in which the properties get initialized is based on the order they are declared in the class.
But, to create an instance and initialize the properties the framework must be using reflection. And as per the MSDN documentation for Type.GetProperties the order is not guarateed.
The GetProperties method does not return properties in a particular
order, such as alphabetical or declaration order. Your code must not
depend on the order in which properties are returned, because that
order varies.
I basically want the initialization to take place in a specific order, is this possible?
You can't get the model binding mechanism to do things in a specific order, but you can make sure that the order is applied where it has to be.
Presumably, EmployeeModel is a domain model object on which the order actually matters, and you're now model binding directly to this type. Instead, introduce an edit model1 which you model bind to, and then map that to your model type:
public class EmployeeEditModel
{
public string EmployeeId { get; set; }
public string EmployeeCode { get; set; }
}
// and change your action signature to this:
[HttpGet]
[Route("Add")]
public IActionResult Add([FromUri] EmployeeEditModel model)
1 For an explanation of what an edit model is, see the final remarks on this old answer of mine.
To perform the mapping you have numerous alternatives, some better than others. Pick one that suits you - however, since the reason the order matters is probably something inherent in the domain model object, I'd advice you to put the logic inside it (e.g. in a constructor), to make it easier to remember to change it if the requirements change.
Map via a constructor on the model object
public class EmployeeModel
{
public EmployeeModel(string employeeId, string employeeCode /* , ... */)
{
// do stuff in whatever order you need
EmployeeId = employeeId;
EmployeeCode = employeeCode;
}
// Now your properties can be get-only
public string EmployeeId { get; }
public string EmployeeCode { get; }
}
Map via an extension method that does everything in the right order
public static class EmployeeEditModelExtensions
{
public EmployeeModel AsDomainModel(this EmployeeEditModel editModel)
{
// do stuff in whatever order you need
var model = new EmployeeModel();
model.EmployeeId = editModel.EmployeeId;
model.EmployeeCode = editModel.EmployeeCode;
// ...
}
// Now your properties can be get-only
public string EmployeeId { get; }
public string EmployeeCode { get; }
}
Use an external framework such as AutoMapper, with custom configuration to make sure that the ordering is correct
Do something else. The only purpose is to get you from an EmployeeEditModel instance to an EmployeeModel instance, assigning to the properties of the EmployeeModel in the correct order. Since you write this code yourself, you can do what you want.
I have an object class generated from a T4, with a partial SafeClass to do validation, which looks like this:
public partial class Address : IValidatableObject
This class has a Validate method like so:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//ValidationResponse is a custom struct that holds a few properties.
ValidationResponse r = this.IsValidAddress(); //a method that does some checks
if (!r.IsValid)
{
yield return new ValidationResult(r.Message, new string[] { "Address1" });
}
}
In my Controller's HttpPost event, I have the line:
if (!TryUpdateModel(_policy))
return View(_policy);
Note that the Policy object contains a Person object, which contains an Address object (pieces of all 3 are rendered in the same view; may be relevant, I don't know).
When TryUpdateModel() executes, the Address's Validate method gets called 3 times. I verified it's not triggering for other addresses on the policy. I have also verified that the Controller's HttpPost method is only being called once. It's the single execution of TryUpdateModel() that fires off 3 Validates.
Has anybody else run into this sort of thing? Any ides what's going on?
I had encoutered similar issue running this code
if (isPoorSpecimen)
{
errors.Add(new ValidationResult("Your are reporting poor specimen condition, please specify what is the reason"
, new string[] { "SpecimenCondition", "QtyOk", "DocumentedOk", "ColdChainOk", "PackagingOK", "IsProcessable" }));
}
It will show the error message 6 times in Html.ValidationSummary() .
The solution is to highligt a single control per error.
if (isPoorSpecimen)
{
errors.Add(new ValidationResult("Your are reporting poor specimen condition, please specify what is the reason"
, new string[] { "SpecimenCondition" }));
}
It is called 3 times, because the Address instance is validated first as a standalone entity, then as a member of a standalone Person entity, and finally as a member of the Person entity being a member of the Policy entity.
I would suggest the following solutions:
1) Remove IValidatableObject from all the classes but Policy and validate its members manually:
public class Policy : IValidatableObject
{
public Person PersonInstance { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// validate Policy...
// then validate members explicitly
var results = PersonInstance.Validate(validationContext);
}
}
public class Person
{
public Address AddressInstance { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// validate Person...
// then validate members explicitly
var results = AddressInstance.Validate(validationContext);
}
}
public class Address
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// validate Address...
}
}
2) Or add a flag to each class to validate only once, since the instance across the calls is the same:
private bool validated = false;
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!validated)
{
validated = true;
// do validation
}
}
Hope this helps.
I have read many posts on Session-scoped data in MVC, but I am still unclear where is the right place to include a custom Session wrapper into the solution.
I want to get the Username of the current user from the IPrincipal, load additional information about that User and store it in the Session. Then I want to access that User data from the Controller and the View.
None of the following approaches seem to fit what I want to do.
Option 1 : Access the Session collection directly
Everyone seems to agree this is a bad idea, but honestly it seems like the simplest thing that works. However, it doesn't make the User available to the view.
public class ControllerBase : Controller {
public ControllerBase() : this(new UserRepository()) {}
public ControllerBase(IUserRepository userRepository) {
_userRepository = userRepository;
}
protected IUserRepository _userRepository = null;
protected const string _userSessionKey = "ControllerBase_UserSessionKey";
protected User {
get {
var user = HttpContext.Current.Session[_userSessionKey] as User;
if (user == null) {
var principal = this.HttpContext.User;
if (principal != null) {
user = _userRepository.LoadByName(principal.Identity.Name);
HttpContext.Current.Session[_userSessionKey] = user;
}
}
return user;
}
}
}
Option 2: Injecting the Session into the class constructor forum post
This option seems pretty good, but I am still not sure how to attach it to the Controller and the View. I could new-it-up in the Controller, but shouldn't it be injected as a dependency?
public class UserContext {
public UserContext()
: this(new HttpSessionStateWrapper(HttpContext.Current.Session),
new UserRepository()) { }
public UserContext(HttpSessionStateBase sessionWrapper, IUserRepository userRepository) {
Session = sessionWrapper;
UserRepository = userRepository;
}
private HttpSessionStateBase Session { get; set; }
private IUserRepository UserRepository{ get; set; }
public User Current {
get {
//see same code as option one
}
}
}
Option 3 : Use Brad Wilson's StatefulStorage class
In his presentation Brad Wilson features his StatefulStorage class. It is a clever and useful set of classes which include interfaces and uses constructor injection. However, it seems to lead me down the same path as Option 2. It uses interfaces, but I couldn't use the Container to inject it because it relies on a static factory. Even if I could inject it, how does it get passed to the View. Does every ViewModel have to have a base class with a setable User property?
Option 4 : Use something similar to the Hanselman IPrincipal ModelBinder
I could add the User as a parameter to the Action method and use a ModelBinder to hydrate it from the Session. This seems like a lot of overhead to add it everywhere it is needed. Plus I would still have to add it to the ViewModel to make it available to the View.
public ActionResult Edit(int id,
[ModelBinder(typeof(IPrincipalModelBinder))] IPrincipal user)
{ ... }
I feel like I am overthinking this, but it also seems like there should be an obvious place to do this sort of thing. What am I missing?
My approach to Session:
Cover Session with interface:
public interface ISessionWrapper
{
int SomeInteger { get; set; }
}
Implement interface using HttpContext.Current.Session:
public class HttpContextSessionWrapper : ISessionWrapper
{
private T GetFromSession<T>(string key)
{
return (T) HttpContext.Current.Session[key];
}
private void SetInSession(string key, object value)
{
HttpContext.Current.Session[key] = value;
}
public int SomeInteger
{
get { return GetFromSession<int>("SomeInteger"); }
set { SetInSession("SomeInteger", value); }
}
}
Inject into Controller:
public class BaseController : Controller
{
public ISessionWrapper SessionWrapper { get; set; }
public BaseController(ISessionWrapper sessionWrapper)
{
SessionWrapper = sessionWrapper;
}
}
Ninject dependency:
Bind<ISessionWrapper>().To<HttpContextSessionWrapper>()
You can pass some commonly used information using ViewData when you want to use it in master page and using view model in specific views.
I would strongly recommend passing anything you need in the view down via the controller. That way, the decision on exactly what data the view should render stays with the controller. In order to make that as easy as possible, creating an abstract ViewModelWithUserBase class that has a settable User property really isn't a bad idea. An option is to create an interface IViewModelWithUser, and re-implement the User property every time (or combine with the base class, but you would have the option to re-implement instead of inheriting the base class if that makes things easier in some corner cases).
As far as populating this property, it can probably be done easily with an action filter. Utilizing the OnActionExecuted method you can test if the model passed to the view implements your base class (or interface), and then fill the property with the correct IPrincipal object if appropriate. This has the advantage that since action filters aren't executed in unit tests, you can use the HttpContext.Current.Session dependent code from your option 1 in your action filter, and still have a testable interface on the controller.
Say I have a model like so:
public class MyViewModel {
//some properties
public string MyString {get;set;}
public Dictionary<string,string> CustomProperties {get;set;}
}
And I am presenting the dictionary property like this:
<%= Html.EditorFor(m => m.CustomProperties["someproperty"]) %>
All is working well, however I have implemented a custom validator to validate the properties of this dictionary, but when returning a ModelValidationResult I can not get the member name referenced correctly (which chould be CustomProperties[someproperty] I believe). All the items in the list which are properties are bound correctly to their errors (I want the error class in the text box so I can highlight it).
Here is my code for the custom validator so far
public class CustomValidator : ModelValidator
{
public Custom(ModelMetadata metadata, ControllerContext controllerContext) : base(metadata, controllerContext)
{
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
if (Metadata.PropertyName.Equals("mystring", StringComparison.OrdinalIgnoreCase))
{
yield return new ModelValidationResult() {Message = "normal property validator works!!"};
}
else if (Metadata.PropertyName.Equals("customproperties", StringComparison.OrdinalIgnoreCase))
{
yield return new ModelValidationResult() { MemberName = "CustomProperties[someproperty]", Message = "nope!" };
}
}
}
It appears like something is filling in the MemberName property further up, and ignoring what I put in there
Cheers,
Amar
It appears to me that you are making validation more difficult than it needs to be. Have you taken a look at DataAnnotations which are built into the framework? Scott Gu's blog talks about this. It's a really nice (and easy) way to do validation of models.
I know this is probably not possible but let's say I have a model with two properties.
I write a ValidationAttribute for one of the properties. Can that VA look at the other property and make a decision?
So;
public class QuickQuote
{
public String state { get; set; }
[MyRequiredValidator(ErrorMessage = "Error msg")]
public String familyType { get; set; }
So in the above example, can the validator test to see what's in the "state" property and take that into consideration when validating "familyType"?
I know I can probably save the object to the session but would like to avoid any saving of state if possible.
Your custom validation could be applied to the class directly, take a look at PropertiesMustMatch attribute in the AccountModels class that is created by default as a part of the MVC project template in VS2008.
Another way to achieve this kind of validation is to have your model implement IDataErrorInfo. That way you can do whole viewmodel validation.
This page has some information about iplementing the IDataErrorInfo Interface, about 2/3 of the way down under the heading "mplementing the IDataErrorInfo Interface"
Use ValidationContext to get your model:
public class MyRequiredValidator: RequiredAttribute
{
public override bool RequiresValidationContext
{
get {return true;} //it needs another propertie in model
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
QuickQuote model = (QuickQuote)validationContext.ObjectInstance;
if (model.state == "single")
return null;
else
return base.IsValid(value, validationContext);//familyType is require for married
}
}