asp.net MVC 2 - View Model / Model Validation - Is there a way to map validation attributes from model to ViewModel via AutoMapper? [duplicate] - model-view-controller

I use AutoMapper to map my domain objects to my view models. I have metadata in my domain layer, that I would like to carry over to the view layer and into ModelMetadata. (This metadata is not UI logic, but provides necessary information to my views).
Right now, my solution is to use a separate MetadataProvider (independently of ASP.NET MVC), and use conventions to apply the relevant metadata to the ModelMetadata object via an AssociatedMetadataProvider. The problem with this approach is that I have to test for the same conventions when binding the ModelMetadata from the domain as I do with my AutoMapping, and it seems like there should be a way to make this more orthogonal. Can anyone recommend a better way to accomplish this?

I use the approach below to automatically copy data annotations from my entities to my view model. This ensures that things like StringLength and Required values are always the same for entity/viewmodel.
It works using the Automapper configuration, so works if the properties are named differently on the viewmodel as long as AutoMapper is setup correctly.
You need to create a custom ModelValidatorProvider and custom ModelMetadataProvider to get this to work. My memory on why is a little foggy, but I believe it's so both server and client side validation work, as well as any other formatting you do based on the metadata (eg an asterix next to required fields).
Note: I have simplified my code slightly as I added it below, so there may be a few small issues.
Metadata Provider
public class MetadataProvider : DataAnnotationsModelMetadataProvider
{
private IConfigurationProvider _mapper;
public MetadataProvider(IConfigurationProvider mapper)
{
_mapper = mapper;
}
protected override System.Web.Mvc.ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
//Grab attributes from the entity columns and copy them to the view model
var mappedAttributes = _mapper.GetMappedAttributes(containerType, propertyName, attributes);
return base.CreateMetadata(mappedAttributes, containerType, modelAccessor, modelType, propertyName);
}
}
Validator Provivder
public class ValidatorProvider : DataAnnotationsModelValidatorProvider
{
private IConfigurationProvider _mapper;
public ValidatorProvider(IConfigurationProvider mapper)
{
_mapper = mapper;
}
protected override System.Collections.Generic.IEnumerable<ModelValidator> GetValidators(System.Web.Mvc.ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
var mappedAttributes = _mapper.GetMappedAttributes(metadata.ContainerType, metadata.PropertyName, attributes);
return base.GetValidators(metadata, context, mappedAttributes);
}
}
Helper Method Referenced in above 2 classes
public static IEnumerable<Attribute> GetMappedAttributes(this IConfigurationProvider mapper, Type sourceType, string propertyName, IEnumerable<Attribute> existingAttributes)
{
if (sourceType != null)
{
foreach (var typeMap in mapper.GetAllTypeMaps().Where(i => i.SourceType == sourceType))
{
foreach (var propertyMap in typeMap.GetPropertyMaps())
{
if (propertyMap.IsIgnored() || propertyMap.SourceMember == null)
continue;
if (propertyMap.SourceMember.Name == propertyName)
{
foreach (ValidationAttribute attribute in propertyMap.DestinationProperty.GetCustomAttributes(typeof(ValidationAttribute), true))
{
if (!existingAttributes.Any(i => i.GetType() == attribute.GetType()))
yield return attribute;
}
}
}
}
}
if (existingAttributes != null)
{
foreach (var attribute in existingAttributes)
{
yield return attribute;
}
}
}
Other Notes
If you're using dependency injection, make sure your container isn't already replacing the built in metadata provider or validator provider. In my case I was using the Ninject.MVC3 package which bound one of them after creating the kernel, I then had to rebind it afterwards so my class was actually used. I was getting exceptions about Required only being allowed to be added once, took most of a day to track it down.

if your metadata are provided with attributes define the attributes in MetaDataTypes, then apply the same MetaDataType to both your domain class and to your viewmodels. You can define all MetaDataTypes in a separate dll that is reference by both layers. There are some issues with this approach if your ViewModel classes have not some properties that is used in the MetaDataType, but this can be fixed with a custom Provider(I have the code if youlike this approach).

Betty's solution is excellent for "inheriting" data annotations. I have extended this idea to also include validation provided by IValidatableObject.
public class MappedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
private readonly IMapper _mapper;
public MappedModelValidatorProvider(IMapper mapper)
{
_mapper = mapper;
}
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
var mappedAttributes = _mapper.ConfigurationProvider.GetMappedAttributes(metadata.ContainerType, metadata.PropertyName, attributes);
foreach (var validator in base.GetValidators(metadata, context, mappedAttributes))
{
yield return validator;
}
foreach (var typeMap in _mapper.ConfigurationProvider.GetAllTypeMaps().Where(i => i.SourceType == metadata.ModelType))
{
if (typeof(IValidatableObject).IsAssignableFrom(typeMap.DestinationType))
{
var model = _mapper.Map(metadata.Model, typeMap.SourceType, typeMap.DestinationType);
var modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeMap.DestinationType);
yield return new ValidatableObjectAdapter(modelMetadata, context);
}
}
}
}
Then in Global.asax.cs:
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MappedModelValidatorProvider(Mapper.Instance));

Related

Constructor Dependency Injection WebApi Attributes

I have been looking around for a non Parameter injection option for the WebApi attributes.
My question is simply whether this is actually possible using Structuremap?
I have been googling around but keep coming up with either property injection (which I prefer not to use) or supposed implementations of constructor injection that I have thus far been unable to replicate.
My container of choice is Structuremap however any example of this will suffice as I am able to convert it.
Anyone ever managed this?
Yes, it is possible. You (like most people) are being thrown by Microsoft's marketing of Action Filter Attributes, which are conveniently put into a single class, but not at all DI-friendly.
The solution is to break the Action Filter Attribute into 2 parts as demonstrated in this post:
An attribute that contains no behavior to flag your controllers and action methods with.
A DI-friendly class that implements IActionFilter and contains the desired behavior.
The approach is to use the IActionFilter to test for the presence of the attribute, and then execute the desired behavior. The action filter can be supplied with all dependencies (through the constructor) and then injected when the application is composed.
IConfigProvider provider = new WebConfigProvider();
IActionFilter filter = new MaxLengthActionFilter(provider);
config.Filters.Add(filter);
NOTE: If you need any of the filter's dependencies to have a lifetime shorter than singleton, you will need to use a GlobalFilterProvider as in this answer.
To wire this up with StructureMap, you will need to return an instance of the container from your DI configuration module. The Application_Start method is still part of the composition root, so you can use the container anywhere within this method and it is still not considered a service locator pattern. Note that I don't show a complete WebApi setup here, because I am assuming you already have a working DI configuration with WebApi. If you need one, that is another question.
public class DIConfig()
{
public static IContainer Register()
{
// Create the DI container
var container = new Container();
// Setup configuration of DI
container.Configure(r => r.AddRegistry<SomeRegistry>());
// Add additional registries here...
#if DEBUG
container.AssertConfigurationIsValid();
#endif
// Return our DI container instance to the composition root
return container;
}
}
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// Hang on to the container instance so you can resolve
// instances while still in the composition root
IContainer container = DIConfig.Register();
AreaRegistration.RegisterAllAreas();
// Pass the container so we can resolve our IActionFilter
WebApiConfig.Register(GlobalConfiguration.Configuration, container);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
}
public static class WebApiConfig
{
// Add a parameter for IContainer
public static void Register(HttpConfiguration config, IContainer container)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// Add our action filter
config.Filters.Add(container.GetInstance<IMaxLengthActionFilter>());
// Add additional filters here that look for other attributes...
}
}
The implementation of MaxLengthActionFilter would look something like this:
// Used to uniquely identify the filter in StructureMap
public interface IMaxLengthActionFilter : System.Web.Http.Filters.IActionFilter
{
}
public class MaxLengthActionFitler : IMaxLengthActionFilter
{
public readonly IConfigProvider configProvider;
public MaxLengthActionFilter(IConfigProvider configProvider)
{
if (configProvider == null)
throw new ArgumentNullException("configProvider");
this.configProvider = configProvider;
}
public Task<HttpResponseMessage> ExecuteActionFilterAsync(
HttpActionContext actionContext,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);
if (attribute != null)
{
var maxLength = attribute.MaxLength;
// Execute your behavior here (before the continuation),
// and use the configProvider as needed
return continuation().ContinueWith(t =>
{
// Execute your behavior here (after the continuation),
// and use the configProvider as needed
return t.Result;
});
}
return continuation();
}
public bool AllowMultiple
{
get { return true; }
}
public MaxLengthAttribute GetMaxLengthAttribute(ActionDescriptor actionDescriptor)
{
MaxLengthAttribute result = null;
// Check if the attribute exists on the action method
result = (MaxLengthAttribute)actionDescriptor
.GetCustomAttributes(typeof(MaxLengthAttribute), false)
.SingleOrDefault();
if (result != null)
{
return result;
}
// Check if the attribute exists on the controller
result = (MaxLengthAttribute)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(typeof(MaxLengthAttribute), false)
.SingleOrDefault();
return result;
}
}
And, your attribute which should not contain any behavior should look something like this:
// This attribute should contain no behavior. No behavior, nothing needs to be injected.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MaxLengthAttribute : Attribute
{
public MaxLengthAttribute(int maxLength)
{
this.MaxLength = maxLength;
}
public int MaxLength { get; private set; }
}
I struggled with custom action filter providers, without getting it to work for my auth attributes. I also trying out various approaches with constructor and property injection, but did not find a solution that felt nice.
I finally ended up injecting functions into my attributes. That way, unit tests can inject a function that returns a fake or mock, while the application can inject a function that resolves the dependency with the IoC container.
I just wrote about this approach here: http://danielsaidi.com/blog/2015/09/11/asp-net-and-webapi-attributes-with-structuremap
It works really well in my project and solves all problems I had with the other approaches.

Validating deserialised object with Postsharp Contracts

[This is kind of an obvious question but I couldn't find anything about it - if someone could reference me, it'll be grand.]
In a WebAPI project:
public class MyObject
{
[PostSharp.Patterns.Contract.Required]
public string Name {get;set;}
}
public class MyController : ApiController
{
public HttpResponseMessage Post([FromBody]MyObject obj)
{
/// ...
}
}
During compilation, I guess PostSharp's validations put themselves in the setter of the property, so when obj is deserialised from the request's body, its fields aren't validated.
So, what's the best/clean way to validate that object?
Cheers
There is currently no clean way to achieve such validation as it is presumed that once-serialized object is already valid.
In order to force the validation logic, one would need to use ISerializationCallback interface's OnDeserialized method, go through properties and forcibly set them to their current value in order to enforce validation.
This can be done by a PostSharp aspect, but it would be certainly non-trivial. Other possibility is to use reflection/expression trees to achieve the same.
If you think that this would be a nice feature of PostSharp, you can vote PostSharp's UserVoice page.
As Daniel Balas wrote, there's no simple solution to trigger PostSharp's validations after deserializing an object, except implementing OnDeserialized method of ISerializationCallback interface. So I post an Aspect I wrote that deep copies public properties of objects one by one through reflection, and hence activating the validations in the setters.
[Serializable]
public sealed class ArgsValidationAspect : MethodInterceptionAspect
{
public override bool CompileTimeValidate(MethodBase method)
{
if (!method.GetParameters().Any(p => p.ParameterType.IsClass))
{
Message.Write(method, SeverityType.Error, "MY001", "Cannot apply HttpObjectValidationAspect to method '{0}'.", method);
return false;
}
return true;
}
public override void OnInvoke(MethodInterceptionArgs args)
{
foreach (var arg in args.Arguments)
{
try
{
RecursiveCopyInstance(arg);
}
catch (Exception e)
{
throw e.InnerException ?? e;
}
}
base.OnInvoke(args);
}
private static object RecursiveCopyInstance(object origin)
{
var type = origin.GetType();
var instance = Activator.CreateInstance(type);
foreach (var prop in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
{
var val = prop.GetValue(origin);
if (val != null && !prop.PropertyType.IsPrimitive && !prop.PropertyType.Equals(typeof(string)))
{
val = RecursiveCopyInstance(val);
}
prop.SetValue(instance, val);
}
return instance;
}
}

DbGeometry serialization issue in Asp.net web api

I'm building an OData v3 Web API with Entity Framework 6.0 Code First.
Everything works well and I can execute CRUD operations back to the api server.
However I'm using Spatial Types and some of my entities have DbGeometry properties. When I try to update/post an entity with a DbGeometry type from a client application (just a console application for tests) I get this DataServiceRequestException:
No parameterless constructor defined for this object.
It took me a while but I identified the DbGeometry type as the responsible. I already looked at this topic here and made a custom JsonConverter, where I applied to the property:
[Required]
[JsonConverter(typeof(DbGeometryConverter))]
[Column("geometria")]
public DbGeometry Geometria { get; set; }
That didn't worked. The object is not deserialized on the web api server unless I remove the DbGeometry property.
I also tried to change the Global json serializer behavior
var formatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new DbGeometryConverter());
Also futile. I really need the DbGeometry properties. What else can I do to work around this issue?
A little late, but for those who'll seek an answer:
I've managed to do it with the exact same code on a controller level. The idea was taken from this SO Question&Answer.
So, here is the code including the DbGeometryConverter.
DbGeometryConverter.cs:
public class DbGeometryConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsAssignableFrom(typeof(DbGeometry));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var location = JObject.Load(reader);
var token = location["Geometry"]["WellKnownText"];
string geom = token.ToString();
token = location["Geometry"]["CoordinateSystemId"];
int srid = token != null ? int.Parse(token.ToString()) : 0;
var converted = DbGeometry.FromText(geom, srid);
return converted;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanWrite => false;
}
CustomJsonAttribute.cs:
public class CustomJsonAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
var formatter = controllerSettings.Formatters.JsonFormatter;
formatter.SerializerSettings.Converters.Add(new DbGeometryConverter());
}
}
And [CustomJson] attribute on a controller that uses DbGeometry.

MVC HtmlHelper vs FluentValidation 3.1: Troubles getting ModelMetadata IsRequired

I created a HtmlHelper for Label that puts a star after the name of that Label if associated field is required:
public static MvcHtmlString LabelForR<TModel, TValue>(
this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
return LabelHelper(
html,
ModelMetadata.FromLambdaExpression(expression, html.ViewData),
ExpressionHelper.GetExpressionText(expression),
null);
}
private static MvcHtmlString LabelHelper(HtmlHelper helper, ModelMetadata metadata, string htmlFieldName, string text)
{
... //check metadata.IsRequired here
... // if Required show the star
}
If I use DataAnnotations and slap [Required] on the property in my ViewModel, metadata.IsRequired in my private LabelHelper will be equal to True and everything will work as intended.
However, if I use FluentValidation 3.1 and add a simple rule like that:
public class CheckEmailViewModelValidator : AbstractValidator<CheckEmailViewModel>
{
public CheckEmailViewModelValidator()
{
RuleFor(m => m.Email)
.NotNull()
.EmailAddress();
}
}
... in my LabelHelper metadata.IsRequired will be incorrectly set to false. (The validator works though: you can't submit empty field and it needs to be an Email like).
The rest of the metadata looks correct (Ex: metadata.DisplayName = "Email").
In theory, FluentValidator slaps RequiredAttribute on property if Rule .NotNull() is used.
For references:
My ViewModel:
[Validator(typeof(CheckEmailViewModelValidator))]
public class CheckEmailViewModel
{
//[Required]
[Display(Name = "Email")]
public string Email { get; set; }
}
My Controller:
public class MemberController : Controller
{
[HttpGet]
public ActionResult CheckEmail()
{
var model = new CheckEmailViewModel();
return View(model);
}
}
Any help is appreciated.
By default, MVC uses the DataAnnotations attributes for two separate purposes - metadata and validation.
When you enable FluentValidation in an MVC application, FluentValidation hooks into the validation infrastructure but not metadata - MVC will continue to use attributes for metadata. If you want to use FluentValidation for metadata as well as validation then you'd need to write a custom implementation of MVC's ModelMetadataProvider that knows how to interrogate the validator classes - this isn't something that FluentValidation supports out of the box.
I have a custom ModelMetadataProvider that enhances the default DataAnnotations one giving the following:
populates "DisplayName" from propertyname splitting string from Camel Case, if none is specified through DisplayAttribute.
If the ModelMetadata.IsRequired is set to false it checks if there are any fluent validator rules present (of type NotNull or NotEmpty).
I definitely checked out the source code that Jeremy has prepared but I was not ready for a total overhaul so I mixed and matched in order not to lose the default behavior. You can find it here
Here is the code with some additional goodness taken from this post.
public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
readonly IValidatorFactory factory;
public CustomModelMetadataProvider(IValidatorFactory factory)
: base() {
this.factory = factory;
}
// Uppercase followed by lowercase but not on existing word boundary (eg. the start)
Regex _camelCaseRegex = new Regex(#"\B\p{Lu}\p{Ll}", RegexOptions.Compiled);
// Creates a nice DisplayName from the model’s property name if one hasn't been specified
protected override ModelMetadata GetMetadataForProperty(
Func<object> modelAccessor,
Type containerType,
PropertyDescriptor propertyDescriptor) {
ModelMetadata metadata = base.GetMetadataForProperty(modelAccessor, containerType, propertyDescriptor);
metadata.IsRequired = metadata.IsRequired || IsNotEmpty(containerType, propertyDescriptor.Name);
if (metadata.DisplayName == null)
metadata.DisplayName = displayNameFromCamelCase(metadata.GetDisplayName());
if (string.IsNullOrWhiteSpace(metadata.DisplayFormatString) &&
(propertyDescriptor.PropertyType == typeof(DateTime) || propertyDescriptor.PropertyType == typeof(DateTime?))) {
metadata.DisplayFormatString = "{0:d}";
}
return metadata;
}
string displayNameFromCamelCase(string name) {
name = _camelCaseRegex.Replace(name, " $0");
if (name.EndsWith(" Id"))
name = name.Substring(0, name.Length - 3);
return name;
}
bool IsNotEmpty(Type type, string name) {
bool notEmpty = false;
var validator = factory.GetValidator(type);
if (validator == null)
return false;
IEnumerable<IPropertyValidator> validators = validator.CreateDescriptor().GetValidatorsForMember(name);
notEmpty = validators.OfType<INotNullValidator>().Cast<IPropertyValidator>()
.Concat(validators.OfType<INotEmptyValidator>().Cast<IPropertyValidator>()).Count() > 0;
return notEmpty;
}
}

Issues with my MVC repository pattern and StructureMap

I have a repository pattern i created on top of the ado.net entity framework. When i tried to implement StructureMap to decouple my objects, i kept getting StackOverflowException (infinite loop?). Here is what the pattern looks like:
IEntityRepository where TEntity : class
Defines basic CRUD members
MyEntityRepository : IEntityRepository
Implements CRUD members
IEntityService where TEntity : class
Defines CRUD members which return common types for each member.
MyEntityService : IEntityService
Uses the repository to retrieve data and return a common type as a result (IList, bool and etc)
The problem appears to be with my Service layer. More specifically with the constructors.
public PostService(IValidationDictionary validationDictionary)
: this(validationDictionary, new PostRepository())
{ }
public PostService(IValidationDictionary validationDictionary, IEntityRepository<Post> repository)
{
_validationDictionary = validationDictionary;
_repository = repository;
}
From the controller, i pass an object that implements IValidationDictionary. And i am explicitly calling the second constructor to initialize the repository.
This is what the controller constructors look like (the first one creates an instance of the validation object):
public PostController()
{
_service = new PostService(new ModelStateWrapper(this.ModelState));
}
public PostController(IEntityService<Post> service)
{
_service = service;
}
Everything works if i don't pass my IValidationDictionary object reference, in which case the first controller constructor would be removed and the service object would only have one constructor which accepts the repository interface as the parameter.
I appreciate any help with this :) Thanks.
It looks like the circular reference had to do with the fact that the service layer was dependent on the Controller's ModelState and the Controller dependent on the Service layer.
I had to rewrite my validation layer to get this to work. Here is what i did.
Define generic validator interface like below:
public interface IValidator<TEntity>
{
ValidationState Validate(TEntity entity);
}
We want to be able to return an instance of ValidationState which, obviously, defines the state of validation.
public class ValidationState
{
private readonly ValidationErrorCollection _errors;
public ValidationErrorCollection Errors
{
get
{
return _errors;
}
}
public bool IsValid
{
get
{
return Errors.Count == 0;
}
}
public ValidationState()
{
_errors = new ValidationErrorCollection();
}
}
Notice that we have an strongly typed error collection which we need to define as well. The collection is going to consist of ValidationError objects containing the property name of the entity we're validating and the error message associated with it. This just follows the standard ModelState interface.
public class ValidationErrorCollection : Collection<ValidationError>
{
public void Add(string property, string message)
{
Add(new ValidationError(property, message));
}
}
And here is what the ValidationError looks like:
public class ValidationError
{
private string _property;
private string _message;
public string Property
{
get
{
return _property;
}
private set
{
_property = value;
}
}
public string Message
{
get
{
return _message;
}
private set
{
_message = value;
}
}
public ValidationError(string property, string message)
{
Property = property;
Message = message;
}
}
The rest of this is StructureMap magic. We need to create validation service layer which will locate validation objects and validate our entity. I'd like to define an interface for this, since i want anyone using validation service to be completely unaware of the StructureMap presence. Besides, i think sprinkling ObjectFactory.GetInstance() anywhere besides the bootstrapper logic a bad idea. Keeping it centralized is a good way to insure good maintainability. Anyway, i use the decorator pattern here:
public interface IValidationService
{
ValidationState Validate<TEntity>(TEntity entity);
}
And we finally implement it:
public class ValidationService : IValidationService
{
#region IValidationService Members
public IValidator<TEntity> GetValidatorFor<TEntity>(TEntity entity)
{
return ObjectFactory.GetInstance<IValidator<TEntity>>();
}
public ValidationState Validate<TEntity>(TEntity entity)
{
IValidator<TEntity> validator = GetValidatorFor(entity);
if (validator == null)
{
throw new Exception("Cannot locate validator");
}
return validator.Validate(entity);
}
#endregion
}
I'm going to be using validation service in my controller. We could move it to the service layer and have StructureMap use property injection to inject an instance of controller's ModelState to the service layer, but i don't want the service layer to be coupled with ModelState. What if we decide to use another validation technique? This is why i'd rather put it in the controller. Here is what my controller looks like:
public class PostController : Controller
{
private IEntityService<Post> _service = null;
private IValidationService _validationService = null;
public PostController(IEntityService<Post> service, IValidationService validationService)
{
_service = service;
_validationService = validationService;
}
}
Here i am injecting my service layer and validaton service instances using StructureMap. So, we need to register both in StructureMap registry:
ForRequestedType<IValidationService>()
.TheDefaultIsConcreteType<ValidationService>();
ForRequestedType<IValidator<Post>>()
.TheDefaultIsConcreteType<PostValidator>();
That's it. I don't show how i implement my PostValidator, but it's simply implementing IValidator interface and defining validation logic in the Validate() method. All that's left to do is call your validation service instance to retrieve the validator, call the validate method on your entity and write any errors to ModelState.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "PostId")] Post post)
{
ValidationState vst = _validationService.Validate<Post>(post);
if (!vst.IsValid)
{
foreach (ValidationError error in vst.Errors)
{
this.ModelState.AddModelError(error.Property, error.Message);
}
return View(post);
}
...
}
Hope i helped somebody out with this :)
I used a similar solution involving a generic implementor of IValidationDictionary uses a StringDictionary and then copied the errors from this back into the model state in the controller.
Interface for validationdictionary
public interface IValidationDictionary
{
bool IsValid{get;}
void AddError(string Key, string errorMessage);
StringDictionary errors { get; }
}
Implementation of validation dictionary with no reference to model state or anything else so structuremap can create it easily
public class ValidationDictionary : IValidationDictionary
{
private StringDictionary _errors = new StringDictionary();
#region IValidationDictionary Members
public void AddError(string key, string errorMessage)
{
_errors.Add(key, errorMessage);
}
public bool IsValid
{
get { return (_errors.Count == 0); }
}
public StringDictionary errors
{
get { return _errors; }
}
#endregion
}
Code in the controller to copy the errors from the dictionary into the model state. This would probably be best as an extension function of Controller.
protected void copyValidationDictionaryToModelState()
{
// this copies the errors into viewstate
foreach (DictionaryEntry error in _service.validationdictionary.errors)
{
ModelState.AddModelError((string)error.Key, (string)error.Value);
}
}
thus bootstrapping code is like this
public static void BootstrapStructureMap()
{
// Initialize the static ObjectFactory container
ObjectFactory.Initialize(x =>
{
x.For<IContactRepository>().Use<EntityContactManagerRepository>();
x.For<IValidationDictionary>().Use<ValidationDictionary>();
x.For<IContactManagerService>().Use<ContactManagerService>();
});
}
and code to create controllers is like this
public class IocControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
return (Controller)ObjectFactory.GetInstance(controllerType);
}
}
Just a quick query on this. It's helped me out quite a lot so thanks for putting the answer up, but I wondered which namespace TEntity exists in? I see Colletion(TEntity) needs System.Collections.ObjectModel. My file compiles without anything further but I see your TEntity reference highlighted in Blue which suggests it has a class type, mine is Black in Visual Studio. Hope you can help. I'm pretty keen to get this working.
Have you found any way to seperate validation into the service layer at all? My gut tells me that validating in the Controller is a bit smelly but I've looked high and low to find a way to pass validation error messages back to the controller without tightly coupling the service layer to the controller and can't find anything. :(
Again, thanks for the great post!
Lloyd

Resources