Комментарии 39
Это работает. Показываешь. Фидбек же будет такой — а как же мои атрибутики модел биндинга?
Есть и правильные вопросы, но из-за атрибутов Asp это такой box — что когда надо out of the box дверь находят немногие. В общем я против атрибутов, слишком часто их выставляют каким-то сверхзнанием, слишком многие люди воспринимают фокус за достижение. А «на самом деле» (простите) атрибут это наведение тумана.
Замену
if (!ModelState.IsValid)
return View(param);
этим
[ValidationFilter]
не считаю достижением, а считаю шагом в сторону. Шагом вперед считал бы поиск регулярного в коде и составление всего метдоа Action динамически (при первом вызове) чему атрибуты не просто мешают — делают не возможным.
public IActionResult Post(Model model)
{
ThrowIfModelIsInvalid(model);
return View();
}
Таким образом у нас метод становиться статическим, и не надо шаманить с аттрибутами.
Скажите, а почему бы не вынести это в базовый контроллер? Одна строчка аттрибута или одна строчка в начале метода? В обоих случая если забудете написать, то валидация работать не будет.
В обоих случая если забудете написать, то валидация работать не будет.
Атрибут можно бросить на весь контроллер, на базовый контроллер или даже зарегистрировать глобально. Также атрибут можно навесить динамически по соглашениям и проверять различные условия. Всё это выглядит правильно и намного выгоднее, чем ThrowIfModelIsInvalid(model) — это крайне кривой и отвратительный костыль, тем более он бросает исключения, что противоречит всем best practics и рекомендациям. Обычно исключения, это то, что сломалось и это надо чинить. Тут же обычная рядовая валидация.
Я не вижу здесь пространства для холивара, вообще.
binding property list задается только статически (там есть вроде как теоретический путь создать его и динамически но мой вывод 1) только поверх системного кеширования, 2) массивным переписыванием кода, в общем, будем считать что нет методы, тем более что ее действительно нет как реализации кем-то раз написаной). Последствия: хочешь создавать контроллер из модели не через T4 а через run time генерирование (с исп. Execution trees) — вынужден отказываться от Model Binding'ов, брать HttpRequest и по нему сгенерированной функцией строить класс.
А можно подробнее? В вашем комментарии есть несколько ключевых слов «t4, Execution trees» которые вызывают интерес. Но суть я не уловил. По этому вопросы:
> binding property list
Что это?
>хочешь создавать контроллер из модели не через T4 а через run time генерирование
почему не реализовать свой controller selector и свои контроллеры (со своими динамическими экшонами итд) по динамической модели данных?
почему не реализовать свой controller selector и свои контроллеры (со своими динамическими экшонами итд) по динамической модели данных?
На сколько я понимаю IHttpControllerSelector в Core нет, есть другое. Но вообще мне (пока) не нужна его фунциональность. Я готов сжится с таким способом (на большие перестройки не хватает времени).
public class DashboardLine: Controller{
public static meta = new Meta<DashboardLine>(....);
private static Task<IActionResult> Edit();
private static Task<IActionResult> EditConfirmed();
public DashboardLine(){
Edit=meta.GenerateEdit(this);
EditConfirmed=meta.GenerateEditСonfirmed(this);
}
public async Task<IActionResult> Edit()
{
return await Edit();
}
[HttpPost, ActionName(nameof(Edit)), ValidateAntiForgeryToken]
public async Task<IActionResult> EditFormData()
{
return await EditConfirmed();
}
}
Тут есть аттрибуты роутинга, пусть остаются. Но уже нет model binding. К этому есть два умных вопроса 1) как такой контроллер тестировать 2) как тут обратится к ASP IoC. Вопросы правильные на них есть ответы но я хочу просто показать что типичный T4 контроллер может быть заменен и далее развит подобным способом.
Вы свою собственную ссылку не читали?
Прочитаю статью — выношу решение.
Поправляю.
public class DashboardLine: Controller{
public static meta = new Meta<DashboardLine>(..здесь можно обратится к EF модели..);
private Func<Task<IActionResult>> Edit;
private Func<Task<IActionResult>> EditConfirmed;
public DashboardLine(){
Edit=meta.GenerateEdit(this);
EditConfirmed=meta.GenerateEditСonfirmed(this);
}
public async Task<IActionResult> Edit()
{
return await Edit();
}
[HttpPost, ActionName(nameof(Edit)), ValidateAntiForgeryToken]
public async Task<IActionResult> EditFormData()
{
return await EditConfirmed();
}
}
Те куски кода которые нужно закешировать и потом использовать уже скомпилированными компилирются при инициализации static meta.
btw. там реально async/await не нужен, можно прямо вернуть Task.
А тут собственно дело обстоит так.
До return await Edit();
будет поток исполнения ASP.NET MVC (шедуллер от типа хостинга зависит).
После await будет тот поток исполнения который предоставит Awaitable объект. В случае Task это будет тот же поток в котором завершилась работа, либо ThreadPool в случае TASK_STATE_THREAD_WAS_ABORTED
или Thread.CurrentThread.ThreadState == ThreadState.AbortRequested
или TaskCreationOptions.RunContinuationsAsynchronously
.
Если заменить это на просто return Edit() для вызывающего метода ничего не изменится (т.е. поток/контекст исполнения для него будет хаотичным и неизвестным). Скорее всего внутри он сам чекает на нужный контекст исполнения и переключается на ожидаемый.
То что я написал вверху относится к .ConfigureAwait(false).
С его отсутствием или .ConfigureAwait(true) будет шедулинг в текущем SynchronizationContext либо в текущем TaskScheduler либо в дефолтном TaskScheduler(ThreadPool).
Получается 'await Edit();' возвращает исполнение в тот контекст в которм был до await. Вот тут я не уверен, но ASP.NET MVC на это пофиг.
Прочитав стало яснее, вероятности какого-нибудь супер хитрого поведения TaskSchedule теперь не опасаюсь.
использовать HandleUnknownAction
. Чем этот вариант не подходит?Однако у генерирования ран тайм и T4 разные предназначения.
Ран тайм — можно улучшать поведение десятка однотипных контроллеров без труда.
T4 — одноразовая генерация (практика показывает только так), получили бойлерплейт остальные изменения только руками.
Есть и различия.
В ран-тайм нет возни со сгенерированными файами исходного кода, нет возни с файлами шаблонов, нет T4 — другого языка.
С T4 все быстрее, цель «сгенерировать один раз», не накладывает больших обязательств на гибкость, читаемость и конфигурироемость кода. Нет притензии на новый уровень абстракции (а у программиста есть травмирующий опыт от новых уровней абстракций).
public class Param
{
public int SomeEntityId { get;set; }
// нужно проверить, что сущность с таким id существует
}
Добавить в атрибут параметр — метод, заполняющий модель для вьюхи?
Видел еще такую альтернативу: можно <select>'ы грузить ajax'ом с фронта. В момент рендеринга View просто <input type=«hidden» class=«dropdown»> делать, а потом на фронте заменять.
Автор, конечно, молодец! НО! Не забывайте, что еще один экземпляр, в данном случае, — фильтр затормозит обработку запроса (поковыряйте исходники MVC). Я фильтры, вообще, не использую. Проще в Вашем случае, раз уж Вы пошли путем упрощения кода, сделать базовый контроллер, где определить метод (пример):
protected TResult IsModelStateIsValid<TModel, TResult>(Func<TModel, TResult> ifModelIsValidFunc)
where TResult : IActionResult
// Execute the filter factory to determine which static filters can be cached.
var filters = CreateUncachedFiltersCore(filterProviders, actionContext, allFilterItems);
// Cache the filter items based on the following criteria
// 1. Are created statically (ex: via filter attributes, added to global filter list etc.)
// 2. Are re-usable
for (var i = 0; i < staticFilterItems.Length; i++)
{
var item = staticFilterItems[i];
if (!item.IsReusable)
{
item.Filter = null;
}
}
return new FilterFactoryResult(staticFilterItems, filters);
Если хочется перформанса тогда можно выкинуть MVC из пайплайна, заменить на Handler / Middleware. В MVC в любом случае будет осуществляться проверка фильтров. Можете поделиться профайлингом на сколько дополнительный фильтр замедляет выполнение?
Дело, даже не в оптимизации, а дополнительном коде (вызовы и экземпляры). Я для себя открыл AspRazorPages! MVC забыт! :)
Насчет, бенчмарка. Тут смысла мерить нету. Разница будет в десяток-другой вызовов и нескольких экземплярах. Но т.к. мы говорим о веб-приложении, то (моя позиция) необходимо минимизировать любые расходы… И когда вся совокупность будет учтена, получится разница в производительности. А, вообще, пустой запрос в ASP.NET MVC (Release режим) занимает 5 мс, тогда как в AspRazorPages или при обработке через Handler / Middleware, естественно < 1 мс. Таким образом, избавившись от MVC, мы сэкономим 5 мс на каждом запросе. Но, не будем, забывать, что если остальной код не оптимизирован, то и разговаривать не о чем...
Если хочется перформанса тогда можно выкинуть MVC из пайплайна, заменить на Handler / Middleware
Вы пишите:
AspRazorPages или при обработке через Handler / Middleware, естественно < 1 мс.
Здесь мы согласны.
Но т.к. мы говорим о веб-приложении, то (моя позиция) необходимо минимизировать любые расходы…
Здесь позволю не согласиться. Перформанс — действительно важный критерий качества ПО, но есть еще удобство сопровождения и стоимость разработки / поддержки. Возможно вы и бизнес по разному оцениваете стоимость этих 4мс. Кроме того кроме tfb нужно еще смотреть на throughput. По этой логике нужно срочно все переписать на Netty :) Я бы не был так категоричен в высказываниях.
В свое время смотрел эти бенчмарки, и Netty смотрел тоже! :) Вы не менее категорично восприняли. :)
но есть еще удобство сопровождения и стоимость разработки / поддержки
Тут, к сожалению, любые новые технологии или подходы проиграют… И я со своими наработками проиграю. Но и ладно! Когда-нибудь же эти технологии и патерны зайдут в продакшн.
Тут, к сожалению, любые новые технологии или подходы проиграют… Но и ладно!Вот у меня «ладно» и не получается, поскольку хотя я и работаю один (фрилансер, а если бы приходилось через консервативных «архитектов», «сеньеров» и «тим лидов» пробиваться было бы еще нервней) то все равно выходить к людям с новыми идеями и подходами хочется и конструктива хочется, а тебе говорят «перехочется, ты проиграл, минус».
Но мой опыт говорит, что «бизнес патриотизм» прибежище черти знает кого. Да и бесконечно длинна очередь желающих доказать что они сильнее всех любят бизнес на словах. Люби бизнес на деле — будь фрилансером :)
Избавляемся от boilerplate для валидации в ASP.NET MVC