Где должна быть проверка в сценарии ASP.Net MVC с репозиторием, сервисным уровнем и использованием связывателя модели? - PullRequest
5 голосов
/ 24 апреля 2009

Связано: Каков наилучший способ проверки полей с использованием ASP.NET MVC?

Предположим решение со следующими проектами:

Foo; // the MVC web project
Foo.Models;
Foo.Repositories;
Foo.Services;

Foo.Models является доменом приложения со всеми сущностями, не имеет значения, используете ли вы EF, NH, POCO или что-то еще. Вот пример:

public class User
{
    public string Username { get; set; }

    public string Email { get; set; }

    public string Password { get; set; }
}

В Foo.Repositories есть UserRepository, а в Foo.Services есть UserService.

В веб-приложении рассмотрим подшивку модели следующим образом:

public class UserBinder : DefaultModelBinder
{
    //...
}

Я вижу три разных варианта того, где поставить проверку:

  • В Foo.Models как показано ниже:

    public class User
    {
        public string Username { get; set; }
    
        public string Email { get; set; }
    
        public string Password { get; set; }
    
        public ICollection<KeyValuePair<string, string>> ValidateErrors()
        {
            //Validate if Username, Email and Password has been passed
        }
    }
    
  • В Foo.Services как:

    public class UserService
    {
        public ICollection<KeyValuePair<string, string>> ValidateErrors()
        {
            //Validate if Username, Email and Password has been passed
        }
    }
    
  • В Foo внутри подшивки модели:

    public class UserBinder : DefaultModelBinder
    {
        protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var user = (User)bindingContext.Model;
    
            // validate everything here
    
            base.OnModelUpdated(controllerContext, bindingContext);
        }
    }
    

Еще одна вещь, на которую следует обратить внимание, это то, что, учитывая первые 2 варианта [Модель и Сервис], необходимо принять другое решение: ValidateErrors метод может быть вызван непосредственно на контроллере или внутри Binder.

У меня есть 2 вопроса по сценарию:

  1. Если проверка будет:

    • В модели, вызываемой из контроллера?
    • В модели, вызываемой из переплета?
    • В Сервисе вызывается с контроллера?
    • В Службу вызывается из переплета?
    • Прямо в Биндер?
    • Есть еще идеи?
  2. Все вышеприведенные сценарии обсуждают о создании пользователя. Но как насчет входа пользователя? Допустим, пользователь использует имя пользователя и пароль для входа в приложение, поэтому ему не нужно проверять электронную почту. Где должна быть эта проверка?

    • В модели, вызываемой из контроллера?
    • В Сервисе, вызываемом с контроллера?
    • Есть еще идеи?

Ответы [ 6 ]

1 голос
/ 24 апреля 2009

Я большой поклонник вызова вызова проверки из контроллеров и того, чтобы подпрограмма проверки возвращала ActionResult, чтобы контроллер мог знать, что делать с результатом.

1 голос
/ 24 апреля 2009

Ознакомьтесь с примером приложения ASP.NET MVC Contact Manager. У него очень хорошая архитектура, на мой взгляд

http://www.asp.net/learn/mvc/tutorial-26-cs.aspx'> http://www.asp.net/learn/mvc/tutorial-26-cs.aspx

0 голосов
/ 25 сентября 2009

Я использую атрибуты DataAnnotations в сочетании со связывателем модели MVC, чтобы выполнить мою проверку и это довольно круто. Так как я рассматриваю ввод пользователя как модели командного представления, это самый чистый способ сохранить домен чистым от внешних проблем.

http://bradwilson.typepad.com/blog/2009/04/dataannotations-and-aspnet-mvc.html

Это также позволяет мне использовать AutoForm от LosTechies.com:

.

http://www.lostechies.com/blogs/hex/archive/2009/06/17/opinionated-input-builders-part-8-the-auto-form.aspx

И я ожидаю, что средства проверки на стороне клиента в MVC 2, VS 2010 также воспользуются этими атрибутами.

Так что сейчас я в бешеном темпе выкрикиваю модели, команды и представления пользовательского ввода и связываю их не только с функциональностью AutoForm, но и с моими собственными шаблонами пользовательского интерфейса, чтобы получать AutoGrid и AutoOutput из этих атрибутов.

Нет ничего лучше, чем сказать:

Html.AutoForm(Model);

Или

Html.AutoGrid(Model.Products);

И получение валидации и генерации html очень СУХОГО и ортогонального способа. Мои контроллеры легки, мой домен нетронут, и мое время не занято, когда я пишу один и тот же метод if (string.IsNullOrEmpty ()) для каждого объекта со свойством FirstName.

Для меня этот подход был не таким «философским», как писали другие. Я пытаюсь быть очень прагматичным в разработке MVC и получаю массу удовольствия от этих битов.

0 голосов
/ 25 сентября 2009

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

Однако как насчет проверки, если выбранное имя пользователя уникально? Должен ли этот код находиться внутри модели User, или внутри класса UserService, или в классе UserRepository?

Если проверка уникальности должна проводиться внутри модели User, то модель User должна иметь доступ либо к классу UserService, либо к классу UserRepository. Это нормально, или это против какой-либо модели «лучшей практики»?

Например:

class User
{
  string Username { get; set; }
  string Email { get; set; }
  string Password { get; set; } // hashed and salted of course :)

  IEnumerable<RuleViolation> Validate()
  {
    List<RuleViolation> violations = new List<RuleViolation>();
    IUserService service = MyApplicationService.UserService; // MyApplicationService is a singleton class, especialy designed so that the User model can access application services

    // Username is required
    if ( string.IsNullOrEmpty(Username) )
       violations.Add(new RuleViolation("Username", "Username is required"));

    // Username must be unique: Should uniqueness be validated here?
    else if( !service.IsUsernameAvailable(Username)
       violations.Add(new RuleViolation("Username", "Username is already taken!"));

    // Validate email etc...

    return violations;
  }
}

interface IUserRepository
{
  void Save(User item);
}

interface IUserService
{
  IUserRepository UserRepository { get; }
  void Save(User item);
}

class UserService : IUserService
{
  public UserService(IUserRepository userRepository)
  {
     this.UserRepository = userRepository;
  }

  IUserRepository UserRepository { get; private set}

  public void Save(User user)
  {
     IEnumerable<RuleViolation> violations = user.Validate();

     if(violations.Count() > 0)
         throw new RuleViolationException(violations); // this will be catched by the Controller, which will copy the violations to the ModelState errors collection. But the question is, should we validat the user here, or in the UserRepository class?

      UserRepository.Save(user);
  }
}

class UserRepository : IUserRepository
{
   void Save(User item)
   {
      IEnumerable<RuleViolation> violations = user.Validate();

     if(violations.Count() > 0)
         throw new RuleViolationException(violations); // this will be catched by the Controller, which will copy the violations to the ModelState errors collection. But the question is, should we validate the user here, or in the UserService class?

      UserRepository.Save(user);

   }
}

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

Самый важный для меня вопрос: должна ли модель User знать об интерфейсах IUserService / IUserRepository, чтобы она могла проверить уникальность имени пользователя? Или служба IUserService должна проверять уникальность?

Мне интересно ваше мнение об этом!

0 голосов
/ 01 мая 2009

После долгих исследований я думаю, что получил ответы на свой вопрос, поэтому я решил поделиться.

Код подтверждения должен быть на модели. Согласно идее «тонкий контроллер, толстая модель» И учитывая, что модель будет знать, что ей нужно проверить или нет.

Например, допустим, я решил использовать Foo.Models в другом решении, но я решил НЕ использовать какой-либо другой проект, а проверка выполняется в другом проекте. В этом случае мне придется перекодировать всю валидацию, что является пустой тратой времени, верно?

OK. Код проверки должен быть в модели, но где он должен называться?

Эта проверка должна вызываться там, где вы сохраняете ее в своей базе данных или файле. Как и в предлагаемом сценарии, я рассматриваю хранилище в качестве домена, поэтому нам следует рассмотреть вопрос о том, чтобы поставить проверку непосредственно перед сохранением изменений [в этом примере я использую Entity Framework, но это не обязательно, это просто показать]:


public class UserRepository : IRepository<User>
{
    public void Create(User user)
    {
        user.Validate();

        var db = dbFooEntities();

        db.AddToUsers(user);
        db.SaveChanges();
    }
}

В соответствии с рекомендацией MS, проверка модели должна вызывать исключение, и контроллер должен заполнить ModelState найденными ошибками [я постараюсь обновить этот ответ с помощью образца кода, как только я закончу свое приложение].

С этим у нас есть ответ на вопрос № 1.

А как насчет вопроса № 2 относительно проверки логина?

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

Итак, ответы на вопрос:

  1. В модели, вызываемой из Хранилище [которая вызывается контроллером]

  2. В службе, вызываемой из контроллера

0 голосов
/ 24 апреля 2009

Для чего это стоит, вот что я нашел в моем текущем проекте:

У меня есть Models, Repositories (вы можете назвать их Services, если хотите) и ViewModels. Я стараюсь избегать написания пользовательских связывателей моделей, потому что (а) это скучно и (б) странное место для проверки, ИМХО. Для меня модель связующего просто берет элементы из запроса и помещает их в объект. Например, PHP не выполняет никакой проверки при вставке элементов из заголовка в массив $ _POST; это вещь, в которую мы подключаем массив, который заботится о его содержимом.

Мои Model объекты обычно никогда не позволяют себе войти в недопустимое состояние. Это означает, что обязательные параметры передаются во время конструктора, и свойства будут генерировать исключения, если их попытаться установить с недопустимыми значениями. И, в общем, я пытаюсь сделать мои Model объекты неизменяемыми. Например, у меня есть объект Address для почтовых адресов, созданный с помощью объекта AddressBuilder, в котором рассматриваются требования к полям для данной страны путем проверки AddressScheme, который можно получить из AddressSchemeRepository. Уф. Но я думаю, что это хороший пример, потому что он берет что-то концептуально простое («проверить почтовый адрес») и усложняет его в реальном мире («мы принимаем адреса из более чем 30 стран, и эти правила форматирования находятся в базе данных, а не в моем коде ").

Поскольку создание этого Model объекта является чем-то вроде боли - так и должно быть, так как он довольно специфичен в отношении данных, которые загружаются в него - у меня есть, скажем, InputAddressViewModel объект, который мой вид привязывается к. InputAddressViewModel реализует IDataErrorInfo, так что я получаю ASP.NET MVC DefaultModelBinder для автоматического добавления ошибок в ModelState. Для простых процедур проверки, которые я знаю заранее (форматирование номера телефона, имя, требуемый формат, адрес электронной почты), я могу реализовать их прямо в InputAddressViewModel.

Другое преимущество наличия модели представления состоит в том, что, поскольку она бесстыдно адаптирована к конкретному виду, ваша реальная модель более пригодна для повторного использования, поскольку ей не нужно идти на какие-то странные уступки, чтобы сделать ее подходящей для отображения пользовательского интерфейса (например, необходимо реализовать INotifyPropertyChanged или Serializable или любое из этого беспорядка).

Другие ошибки проверки адреса, о которых я не узнаю, пока не взаимодействую с моим AddressScheme в моем фактическом Model. Эти ошибки будут там, где контроллер выполняет оркестровку в ModelState. Что-то вроде:


public ActionResult InputAddress(InputAddressViewModel model)
{
    if (ModelState.IsValid)
    {
        // "Front-line" validation passed; let's execute the save operation
        // in the our view model
        var result = model.Execute();

        // The view model returns a status code to help the 
        // controller decide where to redirect the user next
        switch (result.Status)
        {
            case InputAddressViewModelExecuteResult.Saved:
                return RedirectToAction("my-work-is-done-here");

            case InputAddressViewModelExecuteResult.UserCorrectableError:
                // Something went wrong after we interacted with the 
                // datastore,  like a bogus Canadian postal code or 
                // something. Our view model will have updated the 
                // Error property, but we need to call TryUpdateModel() 
                // to get these new errors to get added to
                // the ModelState, since they were just added and the
                // model binder ran before this method even got called.
                TryUpdateModel(model);
                break;
        }

        // Redisplay the input form to the user, using that nifty
        // Html.ValidationMessage to convey model state errors
        return View(model);
    }
}

switch может показаться отталкивающим, но я думаю, что это имеет смысл: модель представления является просто старым классом и не имеет никакого знания о Request или HttpContext. Это делает логику модели представления легкой для тестирования в изоляции, не прибегая к насмешкам, и оставляет код контроллера оставленным, ну, в общем, для управления , интерпретируя результат модели таким образом, который имеет смысл на веб-сайте. - может перенаправлять, может устанавливать файлы cookie и т. д.

И InputAddressViewModel методы Execute() выглядят примерно так (некоторые люди настаивают на том, чтобы поместить этот код в объект Service, который будет вызывать контроллер, но для меня модель представления будет сильно влиять на данные чтобы он соответствовал реальной модели, которую имеет смысл поместить здесь):


public InputAddressViewModelExecuteResult Execute()
{
    InputAddressViewModelExecuteResult result;

    if (this.errors.Count > 0)
    {
        throw new InvalidOperationException(
            "Don't call me when I have errors");
    }

    // This is just my abstraction for clearly demarcating when
    // I have an open connection to a highly contentious resource,
    // like a database connection or a network share
    using (ConnectionScope cs = new ConnectionScope())
    {
        var scheme = new AddressSchemeRepository().Load(this.Country);
        var builder = new AddressBuilder(scheme)
             .WithCityAs(this.City)
             .WithStateOrProvinceAs(this.StateOrProvince);

        if (!builder.CanBuild())
        {
            this.errors.Add("Blah", builder.Error);
            result = new InputAddressViewModelExecuteResult()
            {
                Status = InputAddressViewModelExecuteStatus
                    .UserCorrectableError
            };
        }
        else
        {
            var address = builder.Build();
            // save the address or something...
            result = new InputAddressViewModelExecuteResult()
            {
                Status = InputAddressViewModelExecuteStatus.Success,
                Address = address
            };
        }        
    }

    return result;
}

Имеет ли это смысл? Это лучшая практика? Я понятия не имею; это, конечно, многословно; это то, что я только что придумал за последние две недели, подумав об этой проблеме. Я думаю, что у вас будет некоторое дублирование проверки - ваш пользовательский интерфейс не может быть полным имбецилом и не знать, какие поля являются обязательными или нет, прежде чем отправлять их в вашу модель / репозитории / службы / что угодно - иначе форма может просто сгенерировать себя.

Я должен добавить, что стимулом для этого является то, что я всегда ненавидел менталитет Microsoft «установить одно свойство -> проверить одно свойство», потому что в действительности ничто не работает так. И вы всегда в конечном итоге получаете недействительный объект, потому что кто-то забыл вызвать IsValid или что-то подобное по пути в хранилище данных. Итак, еще одна причина наличия модели представления заключается в том, что она адаптируется к этой уступке, поэтому мы получаем большую CRUD-работу по извлечению элементов из запроса, ошибкам проверки в состоянии модели и т. Д. без необходимости поставить под угрозу целостность самой нашей модели. Если у меня в руке есть объект Address, я знаю, что это хорошо. Если у меня в руке есть объект InputAddressViewModel, я знаю, что мне нужно вызвать его метод Execute(), чтобы получить этот золотой Address объект.

Я с нетерпением жду, чтобы прочитать некоторые другие ответы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...