ASP.Net MVC 5 - валидация при перемаппинге
От: Neco  
Дата: 31.05.16 11:49
Оценка:
Сперва опишу контекст. Кому многа-букф, можно сразу перейти к вопросу в конце поста.

Как всё случилось:
Был функционал по вводу данных. Данные хранились (и хранятся до сих пор) в нескольких таблицах, связанных либо 1-0..1, либо 1-0..*, по-разному. В связи с этим первоначально всё было реализовано так, что вначале вводим основную запись, сохраняем её в БД, потом вводим зависимые записи и сохраняем также отдельно со ссылкой на основную запись. Для примера давайте возьмём сущность пользователя с несколькими адресами. Сначала ввели пользователя, потом вводим его адреса.
Юзеры начали роптать, что это не стыкуется с их ощущением бизнеса — они хотят сразу всё ввести и сохранить одним махом. При этом сама вся вводимая сущность довольно объёмистая и на её ввод может уйти прилично времени (за которое может произойти всё что угодно) и автоматически встал вопрос о том, что в процессе ввода надо делать временные сохранения, чтобы в случае чего можно было вернуться к ранее создаваемому объекту.
Поскольку надо было делать довольно резво, решили обойтись флагом временности в основных таблицах. Т.е. теперь сущность вставляется в БД сразу вначале создания и все зависимые объекты сразу могут сослаться на неё. В конце редактирования (при нажатии на финальное "Сохранить"), флаг временности убирается и сущность становится полноценной.

Из негатива:
Пришлось убрать всю обязательность полей в БД и с атрибутов сущностей Entity Framework, поскольку временные сущности могут быть сколь угодно неполными. По факту серверную валидацию даже и не делаем (оставили только клиентскую), но об этом не принято говорить на людях.
Во всяких запросах пришлось понавтыкать проверку того, чтобы временные сущности не подхватывались.
Код сохранения сущности стал довольно сложным (если временное сохранение, то так, а если нет, то эдак).

Что хочу сделать:
Создать отдельную сущность (чтобы окончательно всё запутать) для временных объектов — по структуре, сильно упрощённую модель настоящих объектов. Все операции с временными сущностями будут происходить в своём мирке, а при переводе в постоянные таблицы будут выполняться все проверки (т.е. опять верну обязательность полей в БД и в модели EF).

Где вижу предстоящие сложности:
Валидация. Дело в том, что временная модель не должна будет содержать никаких атрибутов Required и т.п. Но контролллер и вью будут оперировать в первую очередь с временной моделью. При переводе временной модели в постоянную я могу задействовать проверки, но вся эта автоматическая asp.net mvc валидационная лабудень будет по умолчанию в ModelState писать названия свойств постоянной модели. И вью (работающее со временной моделью) не будет подкрашивать соответствующие поля.
С подобной проблемой вероятно должны были сталкиваться те, кто на постоянной основе разделяет ViewModel и DomainModel. Хотел узнать, есть ли какие-то наработки в том, чтобы связать эти слои не только от View к Domain, но и обратно? Т.е. чтобы валидационная информация из Domain'а нормально применялась в презентационном слое.

Вопрос вкратце:
Если вы разделяете ViewModel и DomainModel, то как при этом View получает и отображает ошибки валидации по полям Domain-модели?

Сейчас смотрю AutoMapper, но что-то как будто не умеет он этого...
всю ночь не ем, весь день не сплю — устаю
Re: ASP.Net MVC 5 - валидация при перемаппинге
От: Vladek Россия Github
Дата: 01.06.16 17:36
Оценка: 1 (1)
Здравствуйте, Neco, Вы писали:

N>Что хочу сделать:

N>Создать отдельную сущность (чтобы окончательно всё запутать) для временных объектов — по структуре, сильно упрощённую модель настоящих объектов.
Так и надо делать. Эти объекты — модель для пользователей, которые осуществляют ввод данных. Как им удобно, так эти объекты и должны принимать данные.

N>Все операции с временными сущностями будут происходить в своём мирке, а при переводе в постоянные таблицы будут выполняться все проверки (т.е. опять верну обязательность полей в БД и в модели EF).

Проверки должны выполняться раньше — сразу после ввода данных. Тогда приложение будет доверять этим данным на 100%, потому что мусор не прошёл фильтр.

N>Где вижу предстоящие сложности:

N>Валидация. Дело в том, что временная модель не должна будет содержать никаких атрибутов Required и т.п. Но контролллер и вью будут оперировать в первую очередь с временной моделью. При переводе временной модели в постоянную я могу задействовать проверки, но вся эта автоматическая asp.net mvc валидационная лабудень будет по умолчанию в ModelState писать названия свойств постоянной модели. И вью (работающее со временной моделью) не будет подкрашивать соответствующие поля.
Вот именно. Если сразу видна корявость этого подхода, зачем на него тратить время? Пользовательские структуры данных должны содержать необходимую валидацию — тогда, если данные прошли валидацию, приложение может работать с этими структурами дальше.

N>С подобной проблемой вероятно должны были сталкиваться те, кто на постоянной основе разделяет ViewModel и DomainModel. Хотел узнать, есть ли какие-то наработки в том, чтобы связать эти слои не только от View к Domain, но и обратно? Т.е. чтобы валидационная информация из Domain'а нормально применялась в презентационном слое.

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

N>Вопрос вкратце:

N>Если вы разделяете ViewModel и DomainModel, то как при этом View получает и отображает ошибки валидации по полям Domain-модели?

ViewModel отвергается до тех пор, пока из неё нельзя получить корректную DomainModel.
Re: ASP.Net MVC 5 - валидация при перемаппинге
От: B7_Ruslan  
Дата: 02.06.16 05:50
Оценка:
AutoMapper не нужен в маппинге Domain<->ViewModel

Можно делать модальные диалоги на JS прямо на странице, и сохранять JSON в hidden field через JSON.stringify
Декодер JSON для ViewModel:
using System;
using System.ComponentModel;
using System.Linq;
using System.Web.Mvc;
using Newtonsoft.Json;
namespace MyProject
{
    public class JsonPropBinder : DefaultModelBinder
    {
        protected override object GetPropertyValue(ControllerContext controllerContext, 
            ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
        {
            if (propertyDescriptor.Attributes.OfType<Attribute>().Any(x => (x is JsonBindableAttribute)))
            {
                var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;
                var settings = new JsonSerializerSettings { DateTimeZoneHandling = DateTimeZoneHandling.Unspecified };
                return JsonConvert.DeserializeObject(value, propertyDescriptor.PropertyType, settings);
            }
            return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
        }
    }
}

Атрибут для JSON поля ViewModel
public class JsonBindableAttribute : Attribute
{
}


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

ПС: на JS+JQuery в создании сложных форм далеко не уедешь.
Re[2]: ASP.Net MVC 5 - валидация при перемаппинге
От: Neco  
Дата: 02.06.16 09:49
Оценка:
Здравствуйте, Vladek, Вы писали:

N>>Что хочу сделать:

N>>Создать отдельную сущность (чтобы окончательно всё запутать) для временных объектов — по структуре, сильно упрощённую модель настоящих объектов.
V>Так и надо делать. Эти объекты — модель для пользователей, которые осуществляют ввод данных. Как им удобно, так эти объекты и должны принимать данные.
Ok

N>>Все операции с временными сущностями будут происходить в своём мирке, а при переводе в постоянные таблицы будут выполняться все проверки (т.е. опять верну обязательность полей в БД и в модели EF).

V>Проверки должны выполняться раньше — сразу после ввода данных. Тогда приложение будет доверять этим данным на 100%, потому что мусор не прошёл фильтр.
Ммм...
Вопрос где начинается то приложение.
Если проверки делать ViewModel'и, то получаются две сложности:
1. Проверки должны быть отключаемыми, поскольку моя ViewModel будет одновременно являться моделью временной сущности, которая будет храниться в БД. Пока вижу, что EF при сохранении автоматом прогоняет проверку атрибутов (может можно и опустить проверку в EF, я не искал пока).
2. Поскольку мы всецело доверяем слою ViewModel'и, в доменной модели проверки уже нужно не делать (чтобы избежать дублирования). Но дело в том, что есть и другие способы создания сущностей — автоматом, без UI. Такие сущности получается будут обходить проверку, либо надо будет делать так чтобы они тоже использовали ViewModel'и, что уже кажется избыточными махинациями.
Мой опыт подсказывает, что проверки (валидация) наоборот должны выполняться как можно ближе к хранилищу (в идеале в БД). Фокус в том, чтобы их протащить к пользователю и показать как можно раньше — это должно решаться метаданными. Но рубеж должен быть либо в БД, либо чуть раньше.

N>>С подобной проблемой вероятно должны были сталкиваться те, кто на постоянной основе разделяет ViewModel и DomainModel. Хотел узнать, есть ли какие-то наработки в том, чтобы связать эти слои не только от View к Domain, но и обратно? Т.е. чтобы валидационная информация из Domain'а нормально применялась в презентационном слое.

V>Смотри выше. Приложение должно доверять самому себе и не содержать мусорной логики на каждом шагу. Проверки на корректность и безопасность — забота других слоёв, внешних по отношению к ядру приложения.
Если говорить о слоях, то в общем случае их три, в данном случае рассматриваем два — презентационный и бизнес-логики. Корректность сущностей во избежании дублирования должна проверяться в слое бизнес-логики, а ViewModel это само по себе презентационная фишка — как может устраивать тот факт, что бизнес-логика проверяется исключительно в презентационном слое?

N>>Если вы разделяете ViewModel и DomainModel, то как при этом View получает и отображает ошибки валидации по полям Domain-модели?

V>ViewModel отвергается до тех пор, пока из неё нельзя получить корректную DomainModel.
Тут согласен — но вопрос в механике. Как я это вижу: ViewModel конвертируется в DomainModel, DomainModel проверяется на корректность, ошибки переводятся в термины ViewModel'и.
Как я понимаю то, что описываешь ты: ViewModel проверяется на корректность, потом конвертируется в DomainModel и она без последующих проверок сохраняется в БД.
Но это даже в общем случае невозможно — во ViewModel'и может быть даже недостаточно данных для полноценной валидации.
Прошу пояснить мысль.
всю ночь не ем, весь день не сплю — устаю
Re[2]: ASP.Net MVC 5 - валидация при перемаппинге
От: Neco  
Дата: 02.06.16 10:01
Оценка:
Здравствуйте, B7_Ruslan, Вы писали:

B_R>AutoMapper не нужен в маппинге Domain<->ViewModel

B_R>Можно делать модальные диалоги на JS прямо на странице, и сохранять JSON в hidden field через JSON.stringify
B_R>Во viewModel можно подгрузить необходимые данные до валидации с помощью своих Binder, тогда в FluentValidation можно проверить вообще все.
Несколько раз прочитал пост, но так и не понял при чём здесь JS, JSON и jQuery. Вопрос был целиком про серверную часть — о том, что прилетает в контроллер и что сохраняется в БД (и что при этом валидируется и как ошибка отображается пользователю).
FluentValidation не используем, но не вижу как это может помочь, если модели две и надо решить как и где делать проверки.
всю ночь не ем, весь день не сплю — устаю
Re[3]: ASP.Net MVC 5 - валидация при перемаппинге
От: Vladek Россия Github
Дата: 02.06.16 12:38
Оценка:
Здравствуйте, Neco, Вы писали:

N>Вопрос где начинается то приложение.

N>Если проверки делать ViewModel'и, то получаются две сложности:
N>1. Проверки должны быть отключаемыми, поскольку моя ViewModel будет одновременно являться моделью временной сущности, которая будет храниться в БД. Пока вижу, что EF при сохранении автоматом прогоняет проверку атрибутов (может можно и опустить проверку в EF, я не искал пока).
Эти структуры моделируют не объекты предметной области, а ввод пользователя. Пользователь что-то же вводит в приложение — вот мы получаем его ввод в виде этих структур данных. Ввод от пользователя мы должны проверять всегда и вообще относиться к нему с недоверием.

Если их не тащить в ядро приложения, а использовать на входе, а потом выбросить — работать с ними проще.

N>2. Поскольку мы всецело доверяем слою ViewModel'и, в доменной модели проверки уже нужно не делать (чтобы избежать дублирования). Но дело в том, что есть и другие способы создания сущностей — автоматом, без UI. Такие сущности получается будут обходить проверку, либо надо будет делать так чтобы они тоже использовали ViewModel'и, что уже кажется избыточными махинациями.


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

Простой пример: ввод от пользователя — имя файла, модель приложения — документ из этого файла. От пользователя получено только имя файла, с проверками "файл существует" и "формат поддерживается", документ загружается внутри приложения — объект документа создаётся самим приложением на основе данных из файла. Тут уже будут проверки другого рода — корректность содержимого документа, наличие всех необходимых секций например — вот это бизнес-логика, а "формат поддерживается" — внешний фильтр.

N>Мой опыт подсказывает, что проверки (валидация) наоборот должны выполняться как можно ближе к хранилищу (в идеале в БД). Фокус в том, чтобы их протащить к пользователю и показать как можно раньше — это должно решаться метаданными. Но рубеж должен быть либо в БД, либо чуть раньше.


Немного философии.

Проверки есть на входе и на выходе ядра приложения ("бизнес-логики"). Вход у него — ввод от пользователя. Выход? Ну, видимо, та самая БД. И наоборот, кстати. Ядро приложения должно уметь на входе получать данные от пользователя и от БД, на выходе выдавать данные и пользователю, и БД. Сколько кластеров объектов тут будет использоваться? Я предлагаю три, ты хочешь обойтись одним. Любые способы допустимы, всё зависит от задачи.

Вот эти кластеры объектов:

  1. Данные для пользователя, объекты с именами вроде Request/Response.
  2. Объекты бизнес-логики. Приложение должно делать некую работу, а не просто сразу кидать данные в БД — эти объекты ею и занимаются.
  3. Объекты БД. Ядру надо куда-то сохранять своё состояние, оно распихивает данные по этим объектам. Это объекты для EF. Внимание — они внешние по отношению к ядру приложения!

Кластер №1 и №3 — это простые структуры данных с необходимой валидацией. На основе аттрибутов или ещё чего. Указал ли пользователь эту строку, заполнен ли вот этот столбец с таблице БД. К бизнес-логике это не имеет отношения, это проверки ввода-вывода. Пользователь — это ввод-вывод, БД — это тоже ввод-вывод.

Кластер №2 отвечает на вопрос — а что собственно приложение делает. Иногда приложение ничего не делает! Оно берёт какие-то данные от пользователя и складывает их в БД, берёт данные из БД и показывает их пользователю. Всё. В этом случае в приложении нет никакой бизнес-логики и это нормальная ситуация для многих приложений, которые мы пилим на работе.

Хорошо, допустим, мы решили обойтись вообще одним кластером объектов — мы сразу начинаем терпеть неудобства! У нас один и тот же объект теперь может быть заполнен человеком, и ORM-библиотекой! Это получается понятно и непротиворечиво в самых простых случаях. Именно этот случай показывают в коде во всяких книгах и статьях — проектируют какой-нибудь анемичный блог, где одни и те же объекты Post летают между пользователем и БД.

N>Если говорить о слоях, то в общем случае их три, в данном случае рассматриваем два — презентационный и бизнес-логики. Корректность сущностей во избежании дублирования должна проверяться в слое бизнес-логики, а ViewModel это само по себе презентационная фишка — как может устраивать тот факт, что бизнес-логика проверяется исключительно в презентационном слое?


Как я написал выше, в данном случае никакой бизнес-логики, видимо, нет — задача стоит сохранить пользовательские данные в БД и наоборот — отдать их пользователю. Да, тогда двух слоёв достаточно. Представление и хранение. И в обоих слоях будет своя отдельная валидация, ведь назначение у них разное.

N>Тут согласен — но вопрос в механике. Как я это вижу: ViewModel конвертируется в DomainModel, DomainModel проверяется на корректность, ошибки переводятся в термины ViewModel'и.

N>Как я понимаю то, что описываешь ты: ViewModel проверяется на корректность, потом конвертируется в DomainModel и она без последующих проверок сохраняется в БД.
N>Но это даже в общем случае невозможно — во ViewModel'и может быть даже недостаточно данных для полноценной валидации.
N>Прошу пояснить мысль.

Ну пользователь заполняет какую-то огромную форму, мы на основе этой формы создаём и валидируем объект или дерево объектов. Все проверки пройдены, пользователь указал все данные, мы можем создать объект для сохранения в БД — какой информации нам будет не хватать?
Re: ASP.Net MVC 5 - валидация при перемаппинге
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 09.06.16 19:33
Оценка: +1
Здравствуйте, Neco, Вы писали:

N>Сперва опишу контекст. Кому многа-букф, можно сразу перейти к вопросу в конце поста.


N>Как всё случилось:

N>Был функционал по вводу данных. Данные хранились (и хранятся до сих пор) в нескольких таблицах, связанных либо 1-0..1, либо 1-0..*, по-разному. В связи с этим первоначально всё было реализовано так, что вначале вводим основную запись, сохраняем её в БД, потом вводим зависимые записи и сохраняем также отдельно со ссылкой на основную запись. Для примера давайте возьмём сущность пользователя с несколькими адресами. Сначала ввели пользователя, потом вводим его адреса.
N>Юзеры начали роптать, что это не стыкуется с их ощущением бизнеса — они хотят сразу всё ввести и сохранить одним махом. При этом сама вся вводимая сущность довольно объёмистая и на её ввод может уйти прилично времени (за которое может произойти всё что угодно) и автоматически встал вопрос о том, что в процессе ввода надо делать временные сохранения, чтобы в случае чего можно было вернуться к ранее создаваемому объекту.

Я бы сделал на клиенте форму, в которой все вводится, потом одной большой пачкой отправляется на сервер, а временные сохранения делал бы localStorage в браузере.
А на сервере также осталась бы model binding, валидация и сохранение с помощью EF.
Re: ASP.Net MVC 5 - валидация при перемаппинге
От: bnk СССР http://unmanagedvisio.com/
Дата: 17.06.16 17:16
Оценка:
Здравствуйте, Neco, Вы писали:

N>Вопрос вкратце:

N>Если вы разделяете ViewModel и DomainModel, то как при этом View получает и отображает ошибки валидации по полям Domain-модели?

Вся валидация во ViewModel и на входе в контроллере. В бизнес-объектах пользовательской валидации нет. Если бизнес-объекты это тупо объекты EF, то только required/foreign key/etc (т.е. проверки уровня базы).
Если наехали на этом уровне (базы) на проблему — exception -> это ошибка приложения -> error 500 -> "звоните солу".

Сохранение временных "корявых" объекты в базе — imho ошибка дизайна. Лучше их, как посоветовали выше, в localStorage пихать а не на сервер постить, чтоб базу не ломать.
На сервер отправлять, когда уже все готово.

N>Сейчас смотрю AutoMapper, но что-то как будто не умеет он этого...

Он и не предназначен для валидации. Просто для маппинга одного на другое, чтобы тонны тупого кода по присваиванию полей не писать. Т.е. валидация — это не его дело.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.