Unit Testing ASP.NET DataAnnotations validation - asp.net

I am using DataAnnotations for my model validation i.e.
[Required(ErrorMessage="Please enter a name")]
public string Name { get; set; }
In my controller, I am checking the value of ModelState. This is correctly returning false for invalid model data posted from my view.
However, when executing the unit test of my controller action, ModelState always returns true:
[TestMethod]
public void Submitting_Empty_Shipping_Details_Displays_Default_View_With_Error()
{
// Arrange
CartController controller = new CartController(null, null);
Cart cart = new Cart();
cart.AddItem(new Product(), 1);
// Act
var result = controller.CheckOut(cart, new ShippingDetails() { Name = "" });
// Assert
Assert.IsTrue(string.IsNullOrEmpty(result.ViewName));
Assert.IsFalse(result.ViewData.ModelState.IsValid);
}
Do I need to do anything extra to set up the model validation in my tests?

I posted this in my blog post:
using System.ComponentModel.DataAnnotations;
// model class
public class Fiz
{
[Required]
public string Name { get; set; }
[Required]
[RegularExpression(".+#..+")]
public string Email { get; set; }
}
// in test class
[TestMethod]
public void EmailRequired()
{
var fiz = new Fiz
{
Name = "asdf",
Email = null
};
Assert.IsTrue(ValidateModel(fiz).Any(
v => v.MemberNames.Contains("Email") &&
v.ErrorMessage.Contains("required")));
}
private IList<ValidationResult> ValidateModel(object model)
{
var validationResults = new List<ValidationResult>();
var ctx = new ValidationContext(model, null, null);
Validator.TryValidateObject(model, ctx, validationResults, true);
return validationResults;
}

Validation will be performed by the ModelBinder. In the example, you construct the ShippingDetails yourself, which will skip the ModelBinder and thus, validation entirely. Note the difference between input validation and model validation. Input validation is to make sure the user provided some data, given he had the chance to do so. If you provide a form without the associated field, the associated validator won't be invoked.
There have been changes in MVC2 on model validation vs. input validation, so the exact behaviour depends on the version you are using. See http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html for details on this regarding both MVC and MVC 2.
[EDIT] I guess the cleanest solution to this is to call UpdateModel on the Controller manually when testing by providing a custom mock ValueProvider. That should fire validation and set the ModelState correctly.

I was going through http://bradwilson.typepad.com/blog/2009/04/dataannotations-and-aspnet-mvc.html, in this post I didn't like the idea of putting the validation tests in controller test and somewhat manual checking in each test that if the validation attribute exists or not. So, below is the helper method and it's usage which I implemented, it works for both EDM (which has metadata attributes, because of the reason we can not apply attributes on auto generated EDM classes) and POCO objects which have ValidationAttributes applied to their properties.
The helper method does not parse into hierarchical objects, but validation can be tested on flat individual objects(Type-level)
class TestsHelper
{
internal static void ValidateObject<T>(T obj)
{
var type = typeof(T);
var meta = type.GetCustomAttributes(false).OfType<MetadataTypeAttribute>().FirstOrDefault();
if (meta != null)
{
type = meta.MetadataClassType;
}
var propertyInfo = type.GetProperties();
foreach (var info in propertyInfo)
{
var attributes = info.GetCustomAttributes(false).OfType<ValidationAttribute>();
foreach (var attribute in attributes)
{
var objPropInfo = obj.GetType().GetProperty(info.Name);
attribute.Validate(objPropInfo.GetValue(obj, null), info.Name);
}
}
}
}
/// <summary>
/// Link EDM class with meta data class
/// </summary>
[MetadataType(typeof(ServiceMetadata))]
public partial class Service
{
}
/// <summary>
/// Meta data class to hold validation attributes for each property
/// </summary>
public class ServiceMetadata
{
/// <summary>
/// Name
/// </summary>
[Required]
[StringLength(1000)]
public object Name { get; set; }
/// <summary>
/// Description
/// </summary>
[Required]
[StringLength(2000)]
public object Description { get; set; }
}
[TestFixture]
public class ServiceModelTests
{
[Test]
[ExpectedException(typeof(ValidationException), ExpectedMessage = "The Name field is required.")]
public void Name_Not_Present()
{
var serv = new Service{Name ="", Description="Test"};
TestsHelper.ValidateObject(serv);
}
[Test]
[ExpectedException(typeof(ValidationException), ExpectedMessage = "The Description field is required.")]
public void Description_Not_Present()
{
var serv = new Service { Name = "Test", Description = string.Empty};
TestsHelper.ValidateObject(serv);
}
}
this is another post http://johan.driessen.se/archive/2009/11/18/testing-dataannotation-based-validation-in-asp.net-mvc.aspx which talks about validating in .Net 4, but i think i am going to stick to my helper method which is valid in both 3.5 and 4

I like to test the data attributes on my models and view models outside the context of the controller. I've done this by writing my own version of TryUpdateModel that doesn't need a controller and can be used to populate a ModelState dictionary.
Here is my TryUpdateModel method (mostly taken from the .NET MVC Controller source code):
private static ModelStateDictionary TryUpdateModel<TModel>(TModel model,
IValueProvider valueProvider) where TModel : class
{
var modelState = new ModelStateDictionary();
var controllerContext = new ControllerContext();
var binder = ModelBinders.Binders.GetBinder(typeof(TModel));
var bindingContext = new ModelBindingContext()
{
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
() => model, typeof(TModel)),
ModelState = modelState,
ValueProvider = valueProvider
};
binder.BindModel(controllerContext, bindingContext);
return modelState;
}
This can then be easily used in a unit test like this:
// Arrange
var viewModel = new AddressViewModel();
var addressValues = new FormCollection
{
{"CustomerName", "Richard"}
};
// Act
var modelState = TryUpdateModel(viewModel, addressValues);
// Assert
Assert.False(modelState.IsValid);

I had an issue where TestsHelper worked most of the time but not for validation methods defined by the IValidatableObject interface. The CompareAttribute also gave me some problems. That is why the try/catch is in there. The following code seems to validate all cases:
public static void ValidateUsingReflection<T>(T obj, Controller controller)
{
ValidationContext validationContext = new ValidationContext(obj, null, null);
Type type = typeof(T);
MetadataTypeAttribute meta = type.GetCustomAttributes(false).OfType<MetadataTypeAttribute>().FirstOrDefault();
if (meta != null)
{
type = meta.MetadataClassType;
}
PropertyInfo[] propertyInfo = type.GetProperties();
foreach (PropertyInfo info in propertyInfo)
{
IEnumerable<ValidationAttribute> attributes = info.GetCustomAttributes(false).OfType<ValidationAttribute>();
foreach (ValidationAttribute attribute in attributes)
{
PropertyInfo objPropInfo = obj.GetType().GetProperty(info.Name);
try
{
validationContext.DisplayName = info.Name;
attribute.Validate(objPropInfo.GetValue(obj, null), validationContext);
}
catch (Exception ex)
{
controller.ModelState.AddModelError(info.Name, ex.Message);
}
}
}
IValidatableObject valObj = obj as IValidatableObject;
if (null != valObj)
{
IEnumerable<ValidationResult> results = valObj.Validate(validationContext);
foreach (ValidationResult result in results)
{
string key = result.MemberNames.FirstOrDefault() ?? string.Empty;
controller.ModelState.AddModelError(key, result.ErrorMessage);
}
}
}

Related

Ormlite: Setting model attributes in c# no longer works

I like to keep my data models clean (and not dependent on any Servicestack DLLs) by defining any attributes just in the database layer. However since upgrading to ver 5.0, my application fails to correctly recognise attributes set in c# using AddAttributes().
The code below shows a minimal reproducable example.
using ServiceStack;
using ServiceStack.DataAnnotations;
using ServiceStack.OrmLite;
namespace OrmliteAttributeTest
{
class Program
{
static void Main(string[] args)
{
var type = typeof(DataItem2);
type.AddAttributes(new AliasAttribute("DataItem2Table"));
var prop = type.GetProperty(nameof(DataItem2.ItemKey));
if (prop != null)
prop.AddAttributes(new PrimaryKeyAttribute());
prop = type.GetProperty(nameof(DataItem2.ItemDescription));
if (prop != null)
prop.AddAttributes(new StringLengthAttribute(100));
SqlServerDialect.Provider.GetStringConverter().UseUnicode = true;
var connectionString = #"Data Source=localhost\sqlexpress; Initial Catalog=OrmLiteTest; Integrated Security=True;";
var connectionFactory = new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider);
using (var db = connectionFactory.OpenDbConnection())
{
db.CreateTableIfNotExists<DataItem>();
db.CreateTableIfNotExists<DataItem2>();
}
}
}
[Alias("DataItemTable")]
public class DataItem
{
[PrimaryKey]
public int ItemKey { get; set; }
[StringLength(100)]
public string ItemDescription { get; set; }
}
public class DataItem2
{
public int ItemKey { get; set; }
public string ItemDescription { get; set; }
}
}
The table for DataItem is created correctly using the attributes as specified. The table for DataItem2 fails to use any of the attibubes defined in the code.
The issue is that the static constructor of JsConfig.InitStatics() needs to be initialized once on Startup which reinitializes the static configuration (and dynamic attributes added) in ServiceStack Serializers.
This is implicitly called in ServiceStack libraries like OrmLiteConnectionFactory which because it hasn't been called before will reinitialize that ServiceStack.Text static configuration. To avoid resetting the dynamic attributes you can initialize the OrmLiteConnectionFactory before adding the attributes:
var connectionFactory = new OrmLiteConnectionFactory(connStr, SqlServerDialect.Provider);
var type = typeof(DataItem2);
type.AddAttributes(new AliasAttribute("DataItem2Table"));
var prop = type.GetProperty(nameof(DataItem2.ItemKey));
if (prop != null)
prop.AddAttributes(new PrimaryKeyAttribute());
prop = type.GetProperty(nameof(DataItem2.ItemDescription));
if (prop != null)
prop.AddAttributes(new StringLengthAttribute(100));
SqlServerDialect.Provider.GetStringConverter().UseUnicode = true;
Or if preferred you can explicitly call InitStatics() before adding any attributes, e.g:
JsConfig.InitStatics();
var type = typeof(DataItem2);
type.AddAttributes(new AliasAttribute("DataItem2Table"));
//...

Integer value model validation

I have a regular Integer (Not nullable) in my model:
[Required]
[Range(0, Int32.MaxValue - 1)]
public int PersonId
{
get;
set;
}
In my WebApi action, I accept an object that has that propery.
public IHttpActionResult Create([FromBody] Person person)
{
if (!ModelState.IsValid)
{
return BadRequest("Some error message.");
}
//Do some stuff with person...
}
Now, altough there is a Required attribute on PersonId, when a person is posted to this action, the ModelState.IsValid property is true.
I guess this is because Person is created with default value, which is 0, I want to throw an error if there is no PersonId field in the incoming JSON / query string request.
I can set PersonId to be Nullable, but that doesn't make sense.
Is there any easy way to validate the field exists and the integer is larger than 0 ? (without custom validators for that simple requirement)
Setting the [Required] attribute doesn't do anything on an int, as far as I know. All [Required] does is make sure the value is not null.
You can set [Range(1, Int32.MaxValue)] to make sure that a correct value is added.
If you don't already do this, it might be a good idea to make a different model for your view and make the data annotations on this model. I use view models to make sure I don't pollute my "real" models with stuff that is not relevant to the whole domain. This way your PersonId can be nullable in your view model only, where it makes sense.
BindRequiredAttribute can be used to
Quoting from this nice blog post about [Required] and [BindRequired]
It works the same way as RequiredAttribute, except it mandates that
the value comes from the request – so it not only rejects null values,
but also default (or “unbound”) values.
So this would reject unbound integer values:
[BindRequired]
[Range(0, Int32.MaxValue - 1)]
public int PersonId
{
get;
set;
}
I tend to use int? (nullable int) in this case and then mark those as required. I then use myInt.Value throughout the code and assume it's safe to use because it wouldn't have passed validation otherwise.
and like #andreas said, I do make sure to use "view models" in times like this so I'm not polluting my view model as a business or data layer model.
Actually for missing not nullable integer parameters model validation doesn't work. There is JSON parsing exception which is thrown by Newtonsoft.Json.
You can have a following workaround to parse and include exceptions in model validations.
Create the custom validation attribute as following and register in WebApiConfig.cs.
public class ValidateModelAttribute : ActionFilterAttribute {
public override void OnActionExecuting(HttpActionContext actionContext) {
// Check if model state is valid
if (actionContext.ModelState.IsValid == false) {
// Return model validations object
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest,
new ValidationResultModel(100001, actionContext.ModelState));
}
}
public class ValidationError {
public string Field { get; }
public string Message { get; }
public ValidationError(string field, string message) {
Field = field != string.Empty ? field : null;
Message = message;
}
}
public class ValidationResultModel {
public int Code { get; set; }
public string Message { get; }
public IDictionary<string, IEnumerable<string>> ModelState { get; private set; }
public ValidationResultModel(int messageCode, ModelStateDictionary modelState) {
Code = messageCode;
Message = "Validation Failed";
ModelState = new Dictionary<string, IEnumerable<string>>();
foreach (var keyModelStatePair in modelState) {
var key = string.Empty;
key = keyModelStatePair.Key;
var errors = keyModelStatePair.Value.Errors;
var errorsToAdd = new List<string>();
if (errors != null && errors.Count > 0) {
foreach (var error in errors) {
string errorMessageToAdd = error.ErrorMessage;
if (string.IsNullOrEmpty(error.ErrorMessage)) {
if (key == "model") {
Match match = Regex.Match(error.Exception.Message, #"'([^']*)");
if (match.Success)
key = key + "." + match.Groups[1].Value;
errorMessageToAdd = error.Exception.Message;
} else {
errorMessageToAdd = error.Exception.Message;
}
}
errorsToAdd.Add(errorMessageToAdd);
}
ModelState.Add(key, errorsToAdd);
}
}
}
}
}
//Register in WebApiConfig.cs
// Model validation
config.Filters.Add(new ValidateModelAttribute());

How do I test response data using nUnit?

Django has a very handy test client/dummy web browser that one can use in test cases to verify the correctness of HTTP responses (e.g., status codes, context/model data). It does not require you to have the web server running, as it deals directly with the framework to simulate the calls.
I'd really love an nUnit (or similar) equivalent that we can slip right into our test suites. We're working in MVC3 and 4, and want to check things like successful 301 redirects, that model validation is correct, and that ViewModel data is correct in the views.
What's the best solution for this?
ViewModel Data should be easy to check with the following:
public T GetViewModelFromResult<T>(ActionResult result) where T : class
{
Assert.IsInstanceOf<ViewResult>(result);
var model = ((ViewResult)result).Model;
Assert.IsInstanceOf<T>(model);
return model as T;
}
[Test]
public void TheModelHasTheOrder()
{
var controller = new MyController();
var result = controller.MyActionMethod();
var model = GetViewModelFromResult<MyModel>();
Assert.That(model, Is.SameAs(???));
}
As for the model validation, if you are using the out of the box .net property attributes like [Required] etc, you can be pretty sure they will work fine, and won't need testing.
To explicitly test the [Required] etc attributes on your object you will have extract the built in .net validation into another class. Then use that class in your controllers to validate your objects, instead of the Model.IsValid property on your controller.
The model validator class:
public class ModelValidator : IModelValidator
{
public bool IsValid(object entity)
{
return Validate(entity, new List<ValidationResult>());
}
public IEnumerable<ValidationResult> Validate(object entity)
{
var validationResults = new List<ValidationResult>();
Validate(entity, validationResults);
return validationResults;
}
private static bool Validate(object entity, ICollection<ValidationResult> validationResults)
{
if (entity != null)
{
var validationContext = new ValidationContext(entity, null, null);
return Validator.TryValidateObject(entity, validationContext, validationResults);
}
return false;
}
}
This could be verifiable in unit tests with the following:
public class MySampleEntity
{
[Required]
public string X { get; set; }
[Required]
public int Y { get; set; }
}
[TestFixture]
public class ModelValidatorTests
{
[Test]
public void GivenThePropertiesArePopulatedTheModelIsValid()
{
// arrange
var _validator = new ModelValidator();
var _entity = new MySampleEntity { X = "ABC", Y = 50 };
// act
var _result = _validator.IsValid(_entity);
// assert
Assert.That(_result, Is.True);
}
}

Unit test controller - membership error

I want to create a Unit test for the following controller but it got fail in the Membership class:
public class AccountController:BaseController
{
public IFormsAuthenticationService FormsService { get; set; }
public IMembershipService MembershipService { get; set; }
protected override void Initialize(RequestContext requestContext)
{
if(FormsService == null) { FormsService = new FormsAuthenticationService(); }
if(MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
}
public ActionResult LogOn()
{
return View("LogOn");
}
[HttpPost]
public ActionResult LogOnFromUser(LappLogonModel model, string returnUrl)
{
if(ModelState.IsValid)
{
string UserName = Membership.GetUserNameByEmail(model.Email);
if(MembershipService.ValidateUser(model.Email, model.Password))
{
FormsService.SignIn(UserName, true);
var service = new AuthenticateServicePack();
service.Authenticate(model.Email, model.Password);
return RedirectToAction("Home");
}
}
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View("LogOn", model);
}
}
Unit test code:
[TestClass]
public class AccountControllerTest
{
[TestMethod]
public void LogOnPostTest()
{
var mockRequest = MockRepository.GenerateMock();
var target = new AccountController_Accessor();
target.Initialize(mockRequest);
var model = new LogonModel() { UserName = "test", Password = "1234" };
string returnUrl = string.Empty;
ActionResult expected = null;
ActionResult actual = target.LogOn(model, returnUrl);
if (actual == null)
Assert.Fail("should have redirected");
}
}
When I googled, I got the following code but I don't know how to pass the membership to the accountcontroller
var httpContext = MockRepository.GenerateMock();
var httpRequest = MockRepository.GenerateMock();
httpContext.Stub(x => x.Request).Return(httpRequest);
httpRequest.Stub(x => x.HttpMethod).Return("POST");
//create a mock MembershipProvider & set expectation
var membershipProvider = MockRepository.GenerateMock();
membershipProvider.Expect(x => x.ValidateUser(username, password)).Return(false);
//create a stub IFormsAuthentication
var formsAuth = MockRepository.GenerateStub();
/*But what to do here???{...............
........................................
........................................}*/
controller.LogOnFromUser(model, returnUrl);
Please help me to get this code working.
It appears as though you are using concrete instances of the IMembershipServive and IFormsAuthenticationService because you are using the Accessor to initialize them. When you use concrete classes you are not really testing this class in isolation, which explains the problems you are seeing.
What you really want to do is test the logic of the controller, not the functionalities of the other services.
Fortunately, it's an easy fix because the MembershipService and FormsService are public members of the controller and can be replaced with mock implementations.
// moq syntax:
var membershipMock = new Mock<IMembershipService>();
var formsMock = new Mock<IFormsAuthenticationService>();
target.FormsService = formsMock.Object;
target.MembershipService = membershipService.Object;
Now you can test several scenarios for your controller:
What happens when the MembershipService doesn't find the user?
The password is invalid?
The user and password is is valid?
Note that your AuthenticationServicePack is also going to cause problems if it has additional services or dependencies. You might want to consider moving that to a property of the controller or if it needs to be a single instance per authentication, consider using a factory or other service to encapsuate this logic.

Asp.Net MVC Validation - dependent fields

I'm currently trying to work through MVC validation, and am coming up against some problems where a field is required depending on the value of another field. An example is below (that I haven't figured out yet) - If the PaymentMethod == "Cheque", then the ChequeName should be required, otherwise it can be let through.
[Required(ErrorMessage = "Payment Method must be selected")]
public override string PaymentMethod
{ get; set; }
[Required(ErrorMessage = "ChequeName is required")]
public override string ChequeName
{ get; set; }
I'm using the System.ComponentModel.DataAnnotations for the [Required], and have also extended a ValidationAttribute to try and get this working, but I can't pass a variable through to do the validation (extension below)
public class JEPaymentDetailRequired : ValidationAttribute
{
public string PaymentSelected { get; set; }
public string PaymentType { get; set; }
public override bool IsValid(object value)
{
if (PaymentSelected != PaymentType)
return true;
var stringDetail = (string) value;
if (stringDetail.Length == 0)
return false;
return true;
}
}
Implementation:
[JEPaymentDetailRequired(PaymentSelected = PaymentMethod, PaymentType = "Cheque", ErrorMessage = "Cheque name must be completed when payment type of cheque")]
Has anyone had experience with this sort of validation? Would it just be better to write it into the controller?
Thanks for your help.
If you want client side validation in addition to model validation on the server, I think the best way to go is a custom validation attribute (like Jaroslaw suggested). I'm including the source here of the one I use.
Custom attribute:
public class RequiredIfAttribute : DependentPropertyAttribute
{
private readonly RequiredAttribute innerAttribute = new RequiredAttribute();
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue) : base(dependentProperty)
{
TargetValue = targetValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get a reference to the property this validation depends upon
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(DependentProperty);
if (field != null)
{
// get the value of the dependent property
var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);
// compare the value against the target value
if ((dependentvalue == null && TargetValue == null) ||
(dependentvalue != null && dependentvalue.Equals(TargetValue)))
{
// match => means we should try validating this field
if (!innerAttribute.IsValid(value))
// validation failed - return an error
return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif"
};
var depProp = BuildDependentPropertyId(DependentProperty, metadata, context as ViewContext);
// find the value on the control we depend on;
// if it's a bool, format it javascript style
// (the default is True or False!)
var targetValue = (TargetValue ?? "").ToString();
if (TargetValue != null)
if (TargetValue is bool)
targetValue = targetValue.ToLower();
rule.ValidationParameters.Add("dependentproperty", depProp);
rule.ValidationParameters.Add("targetvalue", targetValue);
yield return rule;
}
}
Jquery validation extension:
$.validator.unobtrusive.adapters.add('requiredif', ['dependentproperty', 'targetvalue'], function (options) {
options.rules['requiredif'] = {
dependentproperty: options.params['dependentproperty'],
targetvalue: options.params['targetvalue']
};
options.messages['requiredif'] = options.message;
});
$.validator.addMethod('requiredif',
function (value, element, parameters) {
var id = '#' + parameters['dependentproperty'];
// get the target value (as a string,
// as that's what actual value will be)
var targetvalue = parameters['targetvalue'];
targetvalue = (targetvalue == null ? '' : targetvalue).toString();
// get the actual value of the target control
var actualvalue = getControlValue(id);
// if the condition is true, reuse the existing
// required field validator functionality
if (targetvalue === actualvalue) {
return $.validator.methods.required.call(this, value, element, parameters);
}
return true;
}
);
Decorating a property with the attribute:
[Required]
public bool IsEmailGiftCertificate { get; set; }
[RequiredIf("IsEmailGiftCertificate", true, ErrorMessage = "Please provide Your Email.")]
public string YourEmail { get; set; }
Just use the Foolproof validation library that is available on Codeplex:
https://foolproof.codeplex.com/
It supports the following "requiredif" validation attributes / decorations:
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
To get started is easy:
Download the package from the provided link
Add a reference to the included .dll file
Import the included javascript files
Ensure that your views references the included javascript files from within its HTML for unobtrusive javascript and jquery validation.
I would write the validation logic in the model, not the controller. The controller should only handle interaction between the view and the model. Since it's the model that requires validation, I think it's widely regarded as the place for validation logic.
For validation that depends on the value of another property or field, I (unfortunately) don't see how to completely avoid writing some code for that in the model, such as shown in the Wrox ASP.NET MVC book, sort of like:
public bool IsValid
{
get
{
SetRuleViolations();
return (RuleViolations.Count == 0);
}
}
public void SetRuleViolations()
{
if (this.PaymentMethod == "Cheque" && String.IsNullOrEmpty(this.ChequeName))
{
RuleViolations.Add("Cheque name is required", "ChequeName");
}
}
Doing all validation declaratively would be great. I'm sure you could make a RequiredDependentAttribute, but that would only handle this one type of logic. Stuff that is even slightly more complex would require yet another pretty specific attribute, etc. which gets crazy quickly.
Your problem can be solved relatively simply by the usage of conditional validation attribute e.g.
[RequiredIf("PaymentMethod == 'Cheque'")]
public string ChequeName { get; set; }

Resources