asp.net mvc post request + сервисный слой - лучший способ сделать это - PullRequest
3 голосов
/ 04 августа 2011

Допустим, я хотел сделать почтовый запрос на обновление статуса дома , в идеале эти данные должны быть на каком-то уровне обслуживания, обычно это включает

  1. проверить пользователя - они все еще активны или были выгнаны администратором?
  2. проверить houseid - действителен ли houseid / record?
  3. может ли пользователь просмотреть детали дома?
  4. обновить статус на «открытый» или «закрытый»

В реальном мире / сложном домене - большинство видов очень сложны, мы должны выбросить, может быть, количество домов в этом районе, сколькокомментарии по дому, подробности о доме и т. д., может быть, число нерешенных задач по дому ...

Короче говоря - весь приведенный выше код может быть внутри уровня обслуживания, однако, допустим, выдается исключениепользователь не может обновить статус дома - теперь, чтобы заполнить представление, вы должны сначала получить детали дома (снова), загрузить все остальные вещи, которые вы только что загрузили, внутри слоя обслуживания ВСЕ этого инсидКонтроллер или другое подключение к сервисному уровню, который загружает эти данные ...

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

Этот код находится внутри метода действия, он может легко находиться внутри уровня службы ...

// ПРИМЕЧАНИЕ: _repo - это простая абстракция над linq to sql ...

    [HttpGet]
    public ActionResult TaskDetail(int houseid, int taskid)
    {
        var loggedonuser = _repo.GetCurrentUser();

        var _house = _repo.Single<House>(x => x.HouseID == houseid && x.Handler == loggedonuser.CompanyID);

        if (_house == null)
            throw new NoAccessException();

        var summary = _house.ToSummaryDTO();

        var companies = _repo.All<Company>();
        var users = _repo.All<User>();

        var task = _repo.Single<HouseTask>
            (x => x.HouseID == _house.HouseID && x.TaskID == taskid && (x.CompanyID == loggedonuser.CompanyID));

        var dto = new TaskDTO
        {
            TaskID = task.TaskID,
            Title = task.Title,
            Description = task.Description,
            DateCreated = task.DateCreated,
            IsClosed = task.IsClosed,
            CompanyID = companies.Where(y => task.CompanyID == y.CompanyID).SingleOrDefault().Identifier,
        };

        if (task.DueDate.HasValue)
            dto.DueDate = task.DueDate.Value;

        var comments = _repo.All<HouseTaskComment>()
            .Where(x => x.TaskID == task.TaskID)
            .OrderByDescending(x => x.Timestamp)
            .Select(x => new TaskCommentDTO
            {
                Comment = x.Comment,
                Timestamp = x.Timestamp,
                CompanyID = companies.Where(y => x.CompanyID == y.CompanyID).SingleOrDefault().Identifier,
                UserID = users.Where(y => x.UserID == y.UserID).SingleOrDefault().Login,
                Type = EnumHelper.Convert<TaskCommentType>(x.Type)
            });

        dto.AllComments = comments;

        return View(new TaskViewModel
        {
            Summary = summary,
            TaskDetail = dto,
            NewComment = new TaskCommentDTO()
        });
    }

Вкратце - получите подробные сведения о доме для сводки, получите подробные сведения о задании (из нескольких доступных заданий), а также получите комментарии к заданию.Это простой взгляд ИМХО, ничего сложного.

В этот момент пользователь может: Добавить комментарий, Закрыть / Открыть задачу - если у него есть для этого полномочия (код был опущен для простоты), установить дату выполнения задачи или даже очистить дату выполнения задачи.

Теперь UpdateTaskStatus - если невозможно обновить статус, должен возвращать вышеуказанный вид, аналогичный комментарию, если вы не можете комментировать, пожалуйста, верните подробный вид - комментарии могут быть закрыты.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult TaskDueDate(int houseid, int taskid)
{

    var duedate = Request.Form["duedate"];
    var duetime = Request.Form["duetime"];
    try
    {
        if (ModelState.IsValid)
        {

            var newduedate = DateHelper.GoodDate(duedate, duetime);
            _service.SetTaskDueDate(houseid, taskid, newduedate);

            return RedirectToAction("TaskDetail");
        }
    }
    catch (RulesException ex)
    {
        ex.CopyTo(ModelState);
    }

    var loggedonuser = _repo.GetCurrentUser();

    var _house = _repo.Single<House>(x => x.InstructionID == houseid && x.HandlerID == loggedonuser.CompanyID);

    if (_house == null)
        throw new NoAccessException();

    var summary = _house.ToSummaryDTO();

    var companies = _repo.All<Company>();
    var users = _repo.All<User>();

    var task = _repo.Single<HouseTask>
        (x => x.InstructionID == _house.HouseID && x.CompanyID == loggedonuser.CompanyID && x.TaskID == taskid);

    var dto = new TaskDTO
    {
        TaskID = task.TaskID,
        Title = task.Title,
        Description = task.Description,
        DateCreated = task.DateCreated,
        IsClosed = task.IsClosed,
        CompanyID = companies.Where(y => task.CompanyID == y.CompanyID).SingleOrDefault().Identifier
    };

    if (task.DueDate.HasValue)
        dto.DueDate = task.DueDate.Value;

    var comments = _repo.All<HouseTaskComment>()
        .Where(x => x.TaskID == task.TaskID)
        .OrderByDescending(x => x.Timestamp)
        .Select(x => new TaskCommentDTO
        {
            Comment = x.Comment,
            Timestamp = x.Timestamp,
            CompanyID = companies.Where(y => x.CompanyID == y.CompanyID).SingleOrDefault().Identifier,
            UserID = users.Where(y => x.UserID == y.UserID).SingleOrDefault().Login
        });

    dto.AllComments = comments;

    return View("TaskDetail", new TaskViewModel
    {
        Summary = summary,
        TaskDetail = dto,
        NewComment = new TaskCommentDTO()
    });
}

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

  1. Я оставляю весь код только для чтения внутри действий, потому что каждое представление может быть различным,Я не хочу, чтобы здесь вмешивался служебный уровень
  2. Я хочу «защитить» свои обновления / изменения и сохранить это в своем служебном слое или базовом проекте (отдельно c # class lib) или даже в доменном слое, как бы я написал этопроверка дескриптора кода (что я и делаю внутри служебного вызова) выполняет фактическое сохранение?

Я слышал о CommandHandler apProach, это хороший подход?В идеале я хочу сохранить свою валидацию + упорство, используя простой подход внутри моего домена, а не действия контроллера ....

Ответы [ 2 ]

7 голосов
/ 04 августа 2011

Может быть, вы немного обдумываете это

Как я понимаю ваш процесс, так оно и должно быть

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

    • все ваши классы контроллеров (не действия)

    • имеет базовый класс контроллера и помещает в него фильтр

  2. Ваши шаги 2,3 и 4 действительно связаны, потому что

    • в первую очередь вы должны получить данные о доме (включая разрешение)

    • если идентификатор дома недействителен, вы ничего не получите от БД

    • если вы вернули данные, проверьте права доступа

    • установить статус соответственно, когда это разрешено

Примечание о добавлении

В сложных моделях доменов обычно нет необходимости иметь сложные представления. Возможно, у вас их больше, а навигация более сложная для удобства использования. Пользователи также будут рады работать с простыми представлениями, а не со сложными.

Проверка модели

Я бы сохранил статическую проверку , поскольку Asp.net MVC реализует - используя аннотации данных на вашем уровне объектов POCO - потому что он встроен и сократит ваш код (меньше строк = меньше ошибка поверхности) и это сделает его более надежным, потому что вы случайно не забудете проверить некоторые вещи.

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

Чтобы избежать утомительного (и повторяющегося кода) блоков кода try / catch, вы можете просто написать собственный фильтр исключений, который будет передавать значения в состояние модели и возвращать любое представление, необходимое для отображения ошибок состояния модели. Реализация такого фильтра исключений полностью на вас. Затем просто установите его на свой родительский контроллер (поскольку вы не используете MVC 3 с глобальными фильтрами) и забудьте об этом. Это также сократит повторяющийся код.

Если вы поместите проверку динамической модели в сервисный уровень, вам придется передать ему необходимые детали пользователя, потому что он опирается на них.

Выбор пользователя

Я вижу, что вы читаете пользовательские данные из хранилища данных каждый раз, что нежелательно. В большинстве случаев вам, вероятно, просто нужен идентификатор пользователя (и, очевидно, также идентификатор компании). Эти небольшие данные могут сохраняться другим способом (обычно в безопасном файле cookie, но вы можете выбрать свою собственную стратегию), поэтому вы экономите время. Аннулирование этих кэшированных данных довольно тривиально, потому что пользователь, вероятно, может изменить только свои собственные данные, чтобы вы могли сделать недействительными в этот момент и перечитать. Вы можете связать его с дополнительными данными, которые вам часто нужны (например, отображаемое имя пользователя или логин). Но это оно. Большую часть времени ваш код будет использовать идентификатор пользователя.

Подтверждение модели / разрешения

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

[RequirePermission(Permission.UpdateTaskDueDate)]
public ActionResult TaskDueDate(...) { ... }

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

[RequirePermission(Permission.UpdateTaskDueDate, typeof(TaskValidator), "paramName")]
public ActionResult TaskDueDate(...) { ... }

Этот метод валидатора Validate будет принимать значение перечисления разрешения и проверять, что от него требуется.

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

Наличие пользовательских валидаторов, которые ваш фильтр едва вызывает, также сохраняет вашу валидацию на уровне сервиса, а не на презентации. Таким образом, в вашей доменной логике проверяются те, которые поддерживают независимость пользовательского интерфейса от основного кода. В случае, если что-то изменится, должны совпадать только ваши типы ввода и вывода, но весь код уровня пользовательского интерфейса должен практически остаться таким, как есть.

Чтение запроса данных

Вместо чтения ваших данных с использованием Request.Form["duedate"] вы должны просто поместить их в качестве параметров действия. Они будут заселены MVC для вас. Потому что в вашем коде:

try
{
    if (ModelState.IsValid)
    {

        var newduedate = DateHelper.GoodDate(duedate, duetime);
        _service.SetTaskDueDate(houseid, taskid, newduedate);

        return RedirectToAction("TaskDetail");
    }
}
catch (RulesException ex)
{
    ex.CopyTo(ModelState);
}

if оператор полностью избыточен. Ваши целочисленные параметры всегда будут действительными (они не устанавливаются как обнуляемые) в теле действия. Независимо от того, проверяет ли ваш DateHelper.GoodDate другую пару статически, они могут лучше содержаться в пользовательском классе (также с TaskId), и вы можете поместить в него аннотацию данных. И валидация модели на самом деле может быть недействительной (так что выражение if будет иметь смысл).

Но вместо того, чтобы повторять тот же код в вашем TaskDueDate действии, вы можете просто сделать это следующим образом:

[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.UpdateTaskDueDate)]
public ActionResult TaskDueDate(int houseid, TaskDue taskDueData)
{
    if (!this.ModelState.IsValid)
    {
        // don't repeat code and just call another action within this controller
        return this.TaskDetail(houseid, taskDueData.TaskId);
    }
    _service.SetTaskDueDate(houseid, taskid, newduedate);
    return RedirectToAction("TaskDetail");
}

Потому что вы делаете одно и то же в обоих действиях. Никто не говорил, что вы не можете вызывать другие действия внутри действий. Все они возвращаются ActionResult в любом случае.

Я думаю, что мы оба согласны, что это действие намного проще.

Код метода действия

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

Эпилог

Надеюсь, я ответил, по крайней мере, на некоторые ваши вопросы / проблемы / проблемы, потому что мне довольно сложно расшифровать, какова реальная проблема (или лучше сказать, что именно вы спрашиваете). Вы написали свой вопрос довольно запутанно, поэтому не получили ответа на свой вопрос. Люди не понимают, в чем твоя проблема.

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

0 голосов
/ 04 августа 2011

Можете ли вы унаследовать от стандартного исключения FaultException и добавить свойство HouseDetails для if типа HouseDTO?

...