views:

954

answers:

2

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.

+7  A: 

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 :)

Sergey
A: 

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

The syntax coloring is a little off on StackOverflow :) TEntity is a generic type, not a class object. It is simply black in Visual Studio. I use TEntity to simply pass the type of object of i want to validate.I agree with you on validation in the controller. I couldn't find any better way to validate that would give me a good amount of flexibility. Doing it this way I can use any validation framework i want within my entity validator.
Sergey
Now, i haven't gave it much thought, but what could work is using the adapter pattern to write an adapter for ModelState. So essentially you would validate your entity, get ValidationState object and use the adapter to convert it to ModelState. If you abstract it enough, you could use StructureMap to inject adapter of your choice. I'm not sure how well it would tie in with your controller, but i'm sure you could decouple it fairly nicely. This way, your validation service could probably be used within your business models/entities or the service layer. I'll see if i can do something with this.
Sergey
I was looking at following this, http://www.asp.net/Learn/mvc/tutorial-38-cs.aspx. Originally I discounted the option as I considered that it would couple too tightly my service layer to MVC but at the minute, for the sake of getting my project moving along, it looks like the best of a bad bunch. I'm very keen to find a looser alternative though. If you come up with anything drop me an email at lloyd phillips all one word at xtra dot co dot nz.RegardsLloyd