Issues with my MVC repository pattern and StructureMap

后端 未结 3 2090
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-30 18:26

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 StackOverflowExce

相关标签:
3条回答
  • 2020-12-30 18:47

    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

    0 讨论(0)
  • 2020-12-30 18:48

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

    0 讨论(0)
  • 2020-12-30 18:57

    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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题