Внедрить свойство контроллера ASP.NET MVC в зависимость уровня службы?

Я использую подход, аналогичный тому, который описан в этом руководстве по ASP.NET MVC где вы передаете оболочку вокруг коллекции ModelState контроллера в класс проверки, чтобы контроллер мог получить доступ к информации об ошибках.

Вот готовый пример:

interface IProductValidator {
   void Validate(Product item);
}

class ProductValidator {
   // constructor
   public ProductValidator(ModelStateWrapper validationDictionary) { }
}

interface IProductService {
   void AddProduct();
}

public class ProductService : IProductService {
   // constructor
   public ProductService(IProductValidator validator) { }
}

Используя контейнер Castle Windsor для IoC / DI, как мне создать IProductService? Обычно у меня было бы:

MvcApplication.IocContainer.Resolve<IProductService>()

но это не может ввести значение свойства ModelState Контроллера в конструктор для ProductValidator. Я мог бы подключить это, используя параметры конструктора, но это кажется действительно некрасивым.


person user403830    schedule 06.10.2010    source источник
comment
Вы проверяли: stackoverflow.com/questions / 2077055 /   -  person bzarah    schedule 06.10.2010
comment
Это очень похожий вопрос: в моем случае я даже не пытаюсь создать экземпляр валидатора - я хочу, чтобы контейнер разрешил его. В любом случае, вопрос, на который вы указали ссылку, не имеет для меня ответов. Я оставлю это в силе и надеюсь на дополнительную информацию.   -  person user403830    schedule 06.10.2010
comment
Я надеялся, что есть способ получить текущий ControllerContext статически, точно так же, как вы можете получить текущий HttpContext с помощью HttpContext.Current. К сожалению, найти не удалось. Еще одна идея - иметь дополнительный метод в IProductValidator, который принимал бы ModelStateWrapper и копировал бы туда ошибки проверки. Не так хорошо, как Dependency Injection, но должно работать довольно легко.   -  person PatrickSteele    schedule 07.10.2010


Ответы (1)


Я предполагаю, что вы хотите, чтобы состояние модели автоматически вводило любые ошибки в вашу модель? IMHO, ModelState должен оставаться там, где он есть, и вы вносите в него ошибки проверки. Вот как я обрабатываю ошибки в качестве примера. Я не говорю, что это лучший или единственный способ, но это один из способов, при котором ваш уровень проверки не должен знать, кто или что потребляет ошибки проверки.

Во-первых, в моем poco я использую System.ComponentModel.DataAnnotations для правил проверки. Вот, например, мой класс учетной записи.

public class Account : CoreObjectBase<Account>
{
    public virtual int AccountId { get; set; }

    [Required(ErrorMessage = "Email address is required.")]
    public virtual string EmailAddress { get; set; }

    [Required(ErrorMessage = "A password is required.")]
    public virtual string Password { get; set; }
}

Поскольку я хочу иметь возможность инициировать проверку самостоятельно (кроме MVC, выполняющей это самостоятельно), мне пришлось реализовать свой собственный валидатор.

public class Validator<T> where T : CoreObjectBase<T>
{
    public ValidationResponse Validate(T entity)
    {
        var validationResults = new List<ValidationResult>();
        var context = new ValidationContext(entity, null, null);
        var isValid = Validator.TryValidateObject(entity, context, validationResults);

        return new ValidationResponse(validationResults.ToArray());
    }
}

Вот результат проверки, который я передаю

[Serializable]
public class ValidationResponse
{
    public IList<ValidationResult> Violations { get; private set; }

    public IList<ErrorInfo> Errors { get; private set; }

    public bool HasViolations
    {
        get { return Violations.Count > 0; }
    }

    public ValidationResponse(params ValidationResult[] violations)
    {
        Violations = new List<ValidationResult>(violations);

        var errors = from v in Violations
                     from n in v.MemberNames
                     select new ErrorInfo(n, v.ErrorMessage);

        Errors = errors.ToList();
    }

}

ErrorInfo - это очень простой класс с информацией о моей ошибке

[Serializable]
public class ErrorInfo
{
    public string ErrorMessage { get; private set; }
    public object Object { get; private set; }
    public string PropertyName { get; private set; }

    public ErrorInfo(string propertyName, string errorMessage)
        : this(propertyName, errorMessage, null)
    {

    }

    public ErrorInfo(string propertyName, string errorMessage, object onObject)
    {
        PropertyName = propertyName;
        ErrorMessage = errorMessage;
        Object = onObject;
    } 
}

Чтобы завершить эту проверку всеми красивыми и аккуратными моими классами poco, я наследую от базового класса. Для проверки, чтобы сделать его универсальным, когда унаследованный дочерний элемент должен сообщить базовому классу свой тип. Это кажется круглым, но это работает.

[Serializable]
public class CoreObjectBase<T> : IValidatable where T : CoreObjectBase<T>  
{
    #region IValidatable Members

    public virtual bool IsValid
    {
        get
        {
            // First, check rules that always apply to this type
            var result = new Validator<T>().Validate((T)this);

            // return false if any violations occurred
            return !result.HasViolations;
        }
    }

    public virtual ValidationResponse ValidationResults
    {
        get
        {
            var result = new Validator<T>().Validate((T)this);
            return result;
        }
    }

    public virtual void Validate()
    {
        // First, check rules that always apply to this type
        var result = new Validator<T>().Validate((T)this);

        // throw error if any violations were detected
        if (result.HasViolations)
            throw new RulesException(result.Errors);
    }

    #endregion
}

И, наконец, как вы можете видеть, моя проверка выдает исключение RulesException. Этот класс - оболочка для всех ошибок.

[Serializable]
public class RulesException : Exception 
{
    public IEnumerable<ErrorInfo> Errors { get; private set; }

    public RulesException(IEnumerable<ErrorInfo> errors)
    {
        Errors = errors != null ? errors : new List<ErrorInfo>();
    }

    public RulesException(string propertyName, string errorMessage) : 
        this(propertyName, errorMessage, null)
    {

    }

    public RulesException(string propertyName, string errorMessage, object onObject) : 
        this (new ErrorInfo[] { new ErrorInfo(propertyName, errorMessage, onObject) } )
    {

    }
}

Итак, с учетом сказанного, моя проверка в моем контроллере больше похожа на это

public ActionResult MyAction()
{
   try
   {
      //call validation here
   }
   catch (RulesException ex)
   {
      ModelState.AddModelStateErrors(ex);
   }

   return View();
}

ModelState.AddModelStateErrors (ex); это метод расширения, который я написал. это очень просто.

    public static void AddModelStateErrors(this System.Web.Mvc.ModelStateDictionary modelState, RulesException exception)
    {
        foreach (ErrorInfo info in exception.Errors)
        {
            modelState.AddModelError(info.PropertyName, info.ErrorMessage);
        }
    }

Таким образом, я все еще могу использовать DI для своих сервисов / репозиториев и позволять им выдавать ошибку, когда моя модель недействительна. Затем я позволяю интерфейсу - будь то приложение MVC, веб-служба или приложение Windows - решать, что делать с этими ошибками.

Я считаю, что возвращение состояния контроллера / модели / представления MVC в модель / службы / репозитории / и т. Д. Является нарушением базового разделения между слоями.

person Josh    schedule 28.10.2010
comment
Мне нравится! Не знаете, почему ваш класс RulesException поддерживает сериализацию? Не могли бы вы объяснить? - person Haroon; 10.06.2011
comment
Я сделал его сериализуемым, чтобы исключения правил можно было использовать и представлять в приложении, вызывающем мой код через веб-службу. - person Josh; 10.06.2011