Как стать автором
Обновить

Комментарии 42

Сделайте, что нибудь с пробелками в коде. Такие простыни кода смотреть не удобно.
Пытаюсь разобраться, почему лишние строки вставляются. Если кто-нибудь знает причину, подскажите, пожалуйста.
Парсер хабра не дружит с Source Code Highlighter. Это древняя проблема.
Все вопросы к администрации хабра.
<source lang='cs'>
Тут ваш код
</source>
Большое спасибо, уже занимаюсь переформатированием
return new SelectList(new List<short>{ 2011, 2012 }, DefYear);

А в 2013 году надо будет добавлять ещё один год?
Это лишь пример формирования списка и к реальной системе оно отношения не имеет.
При всем уважении к автору… Только у меня возникло ощущение, что некоторые части статьи переведены машиной? e.g. Имя модель фильтра очень мы можем выбрать из БД данные, удовлетворяющие значениям, хранящимся в этой модель

Пробелы между строками кода и вобще форматирование также наводит на мысли, что код выдран с какого-то сайта. Надеюсь я не прав:) В целом интересно.
Топик писал ночью, после достаточно тяжелого трудового дня. Статья не переводная, написана лично мной. За указание на ошибки большое спасибо.
А где, как и зачем вы потом используете фильтрующее выражение?

И второй вопрос: а вы знаете, какое значение имеет термин Filter в ASP.net MVC?
Используется оно при выборке данных из коллекций. В статье есть пример, в самом начале

var students = DbContext.Students
   .Where(Filter.FilterExpression)
   .OrderBy(s => s.Name)
   .ToList();


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

«Если Вы имеете ввиду фильтры действий, то да, знаю.»
И вас не смущает, что вы сейчас получили конфликт терминов?

(первое предположение о содержимом статье после взгляда на ее заголовое — это то, что вы будете писать фильтр, обрабатывающий предоставляемое пользователю содержимое, то есть post-execute)

«В статье я описываю фильтры контента»
А чем вам слово «данные» не угодило? Фильтруете вы данные.

«У Вас есть другие предложения как можно это назвать? „
Для начала я не уверен, что это вообще надо делать. Но если бы я это делал, базовым термином был бы DataFilter, а еще лучше DataFilteringExpression.
Извиняюсь, промазал. Ответил Вам ниже.
«Где это происходит? Можете описать полную последовательность происходящего на высоком уровне „

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

“И вас не смущает, что вы сейчас получили конфликт терминов?»
Ничуть. Фильтры действий, это фильтры действий.

«А чем вам слово «данные» не угодило? Фильтруете вы данные.»
Тут с Вами согласен, слово «данные» более уместно. Мой косяк.
«В действии контроллера или в любом другом месте, где Вы выбираете данные из БД или любой другой коллекции. Последовательность такова: пользователь заходит на страницу где видит список, ну например, товаров, сверху этого списка отображается форма фильтрации. Пользователь заполняет интересующие его поля и отправляет форму. После этого он видит уже только те товары, которые удовлетворяют указанным характеристикам.»
Тогда зачем для всей этой фильтрации городить отдельный механизм, когда он должен быть встроен в механизм отображения списка?

(и куда он обычно радостно встроен, смотрите на любые адекватные гриды)

Собственно, меня именно это изначально и расстраивает во всей этой идее — разрывается цепочка ответственности, и совершенно не видно, от чего и как зависит выбор данных. По сути, получается, что контроллер, отдающий данные, берет фильтр неизвестно откуда.
От чего же неизвестно от куда? В статье описано как собирается модель фильтра и где она хранится. От пользователя приходит запрос на служебный контроллер, из запроса собирается модель, модель кладется в специальное хранилище, объект которого находится в данных сессии и дальше идет редирект на ту страницу, от куда запрос пришел. Контроллер этой страницы получает уже сформированную модель фильтра из хранилища. Гриды для моего проекта без костылей были не подходящими по многим причинам, по этому пришлось придумывать нечто свое.

У меня в приложении примерно 20 страниц с различными данными, которые необходимо было фильтровать таким образом. С моим решением обеспечить фильтрами данных 20 разных сущностей весьма быстро и сопровождать потом просто.
«От пользователя приходит запрос на служебный контроллер, из запроса собирается модель, модель кладется в специальное хранилище, объект которого находится в данных сессии и дальше идет редирект на ту страницу, от куда запрос пришел. Контроллер этой страницы получает уже сформированную модель фильтра из хранилища. „
Вот это и называется — неизвестно откуда. У вас два контроллера взаимодействуют для выполнения _единой_ обязанности через некое отдельное хранилище. Неочевидное для разработчика. Зачем так делать, если намного проще передавать данные по фильтрации сразу в контроллер, обрабатывающий данные, без лишних редиректов и неявной передачи информации?
А зачем конечному разработчик узнать что и от куда приходит? Он знает как создать модель фильтра и он знает как из этой модели получит expression для выборки данных. Для выполнения фильтрации этого достаточно, зачем ему больше?

По поводу «некого хранилища». Организовано оно для того, что бы хранить данные фильтров в сессии, ну или где угодно. Зачем? Что бы когда пользователь вернулся к странице, он увидел тот же отфильтрованный набор данных, что видел и в момент ухода со страницы (требования моего проекта). Либо же для организации пейджинга, что бы на каждую страницу не таскать данные фильтров.

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

Я не претендую на идеальность архитектуры моего решения, но для моей задачи это было оптимальным решением.
«А зачем конечному разработчик узнать что и от куда приходит?»
Чтобы иметь возможность управлять происходящим. Потому что с точки зрения сценария использования фильтр — часть конкретного действия. А в коде это почему-то размазано на два слабосвязанных контроллера. Еще надо помнить, что обращение к этому контроллеру (замаскированное под хелпер) должно быть того же типа (по модели), что и получение выражения внутри основного контроллера — т.е. в реальности по поведению это сильно связанный код, но оттрассировать это нельзя.

«Зачем? Что бы когда пользователь вернулся к странице, он увидел тот же отфильтрованный набор данных, что видел и в момент ухода со страницы (требования моего проекта).»
Это, знаете ли, на редкость специфичное требование.

«Либо же для организации пейджинга, что бы на каждую страницу не таскать данные фильтров.»
Данные фильтров не настолько велики, чтобы это было проблемой. Зато падение сессии между страницами вас больше не волнует. Равно как и проблема, когда пользователь открыл одну страницу два раза, а потом сделал разный поиск.

«Допустим это можно сделать одной-двумя строчками, но про эту строчку надо еще не забыть! „
А так вам надо не забыть забрать фильтрующее выражение из хранилища. Ровно то же самое.

Понимаете ли, задача типовой обработки решается типовыми же средствами — базовым контроллером, фильтром на action и так далее. Зачем изобретать свой, странный и непонятный велосипед.

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

«Это, знаете ли, на редкость специфичное требование.»
Отнюдь нет. Я с ним сталкивался не однократно.

«Данные фильтров не настолько велики, чтобы это было проблемой»
А если будут велики? 100 различных параметров? GET-ом отправлять? Или формировать POST запрос через JS, который может быть отключен у пользователя? Или может быть заботится о том, что бы закрывающий тэг бы в самом низу, а номера страниц input'ами выводились?

«Зато падение сессии между страницами вас больше не волнует»
Волнует. Эту проблему я указал в топике.

«А так вам надо не забыть забрать фильтрующее выражение из хранилища. Ровно то же самое.»
Соглашусь, сборку фильтра можно положить в тот же action, где и происходит выборка данных. А со сбросом данных что делать: описывать в каждом контроллере действие Reset — много код, выносить в базовый контроллер — но тогда опять встает задача определения какой же фильтр мы сбрасываем.

«Понимаете ли, задача типовой обработки решается типовыми же средствами — базовым контроллером, фильтром на action и так далее. Зачем изобретать свой, странный и непонятный велосипед.»
Для уменьшения кол-во кода, необходимого для конечного использования этой задачи. Разве не для этого пишутся различные библиотеки и фрэймворки?

«Вот только вашу задачу — конкретную — вы не указали.»
Описание моей задачи заняло бы намного больший объем, чем сама статья. Я предлагаю вариант решения, а решать подходит он для Ваших задач или нет — только Вам.
«Вот об этом как раз и заботится и хелпер и отдельный контроллер. Конечному программисту помнить это не нужно.»
Только вот хелперу надо указать тип.

«Отнюдь нет. Я с ним сталкивался не однократно.»
Именно в формулировке, которая требует сессии (т.е. — закрытие и открытие страницы, например)?

«100 различных параметров?»
Не бывает. С такими фильтрами неудобно работать (за редкими исключениями).

«Или может быть заботится о том, что бы закрывающий тэг бы в самом низу, а номера страниц input'ами выводились? „
А это проблема какая-то, что ли?

(а вас не смущает, что если у пользователя отключен js, то у него и куки могут быть отключены, а это означает отсутствие сессии?)

“выносить в базовый контроллер — но тогда опять встает задача определения какой же фильтр мы сбрасываем»
Эээ… вообще-то это типичная задача на базовый класс, когда функциональность находится в нем, а конкретизация — в наследнике. Вплоть до использования дженерика.

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

«А это проблема какая-то, что ли?»
Да, это проблема. Я ни разу не припомню, что бы кто то номера страниц инпутами проставлял.

«а вас не смущает, что если у пользователя отключен js, то у него и куки могут быть отключены»
а еще и браузер отсутствовать, кхе-кхе. Как не странно, js намного чаще отключают, нежели куки, по крайней мере в тех организациях, с которыми я сталкивался.

«Эээ… вообще-то это типичная задача на базовый класс, когда функциональность находится в нем, а конкретизация — в наследнике. Вплоть до использования дженерика.»
Зачем наследовать, когда можно реализовать все одним методом, как у меня и сделано? Это находится не в базовом контроллере, потому что я не хочу, что бы все мои контроллеры наследовали этот метод. Делать другой базовый? А если я захочу еще чего нибудь специфичное в базовых контроллерах? Третий? А если я захочу функционал второго и третьего одновременно — четвертый?

«Я хочу сказать, что количество порожденного вами кода существенно больше, чем надо для решения вашей задачи.»
Для решения моей задачи — нет, не больше, а меньше. Если у вас 2-5 наборов данных, которые нужно фильтровать, согласен, заморачиваться смысла нет. А когда их 20 и со временем будет еще 20?

«Да, это проблема. Я ни разу не припомню, что бы кто то номера страниц инпутами проставлял.»
Если хотят совместимости с js-disabled-браузерами — проставляют и стилизуют. Другое дело, что…

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

«А когда их 20 и со временем будет еще 20?»
В этом случае пишут контрол для отображения данных (раз уж что-то не позволяет взять чужой). Это же ответ на вопрос, что делать с наследованием в контроллерах.

Потому что, повторюсь, фильтрация должна быть неразрывна связана с тем механизмом отображения данных, которыми она используется.
«За последние семь лет работы с корпоративными приложениями я ни разу не видел такой ситуации.»
За те же 7 лет встречал ее достаточно часто.

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

Спасибо за дискуссию, думаю дальше развивать холливар смысла нет, наши позиции ясны. Хотя, конечно, если Вы другого мнения, буду рад ответить на Ваши замечания.
А вот меня смущает. Как, думаю, и многих других чуваков, работающих с MVC. Дело отнюдь не в том, что в подходе MVC уже есть паннерн «Intercepting Filter» (реализованный и в ASP.NET MVC), а в том, что у этого уже есть название.

То, что описано у вас, реализуется с помощью паттернов Specification и/или QueryObject, незачем называть их фильтрами.
Я не буду биться за название «фильтры», тут уж как Вам больше нравится.

На счет паттернов спасибо, почитаю про них. Но я же не паттерн проетирования описал, я описал конкретное решение и почему его нужно обзывать именами использованных/неиспользованных паттернов? Мы же не называем проект * MVC *, потому что он написан с использованием паттерна MVC, мы называем его по его тематике, и здесь точно так же — название «фильтры» мне показалось более удачным, потому что для пользователя сайта это именно фильтры данных. Но, повторюсь еще раз, я не буду говорить, что это название идеально, если Вас оно смущает, я готов обозвать это как угодно.
Не принимайте близко к сердцу :) Хозяин — барин, называйте, как вам больше нравится, это мое личное мнение, что фильтры не вполне соответствуют «семантике» подхода. Я просто указал на паттерны, которые применимы именно в этой ситуации.
Нет нет, все в порядке. Если бы мне было не важно чужое мнение — я бы не стал публиковать статью на хабре :) А за паттерны еще раз большое спасибо, обязательно их освою!
Прошу прощения, но никогда так не делайте. Если в коллекции ничего нет, а AddNew сломается, то приложение упадет с ошибкой переполнения стека и сломает приложение. На небольшом коде это не страшно, так как быстро ловится. Но при больших системах такая конструкция может сыграть злую шутку.

    public TFilter Find<TFilter>() where TFilter : FilterModel
    {
        try
        {
            return (TFilter)Collection.Single(f => f.GetType().FullName == typeof(TFilter).FullName);
        }
        catch
        {
            AddNew<TFilter>();

            return Find<TFilter>();
        }
    }
Спасибо за замечание. Согласен с Вами, не очень удачно.
public abstract class FilterModel<ModelType> : FilterModel
{
    public abstract Func<ModelType, bool> FilterExpression { get; }
}


А точно не Expression<Func<Student, bool>>?
.Where принимает аргумент типа Func<ModelType, bool>
принимает, но Expression<Func<Student, bool>> она тоже принимает
и, если я не ошибаюсь, разница будет в том, что Expression конвертируется в часть запроса к БД, а при передаче Func выбирутся все данные и фильтрация будет уже на сервере приложения, а не в БД
Не могу ручаться, но на сколько мне известно, Func тоже преобразуется в SQL запрос.

Во время написания моделей фильтров к своему приложению несколько раз в FilterExpression ошибался и это приводило к SqlException, от сюда полагаю что все таки преобразуется. Как будет время, посмотрю профайлером на конечный запрос к серверу, там будет видно, включены условия выборки фильтра в него или нет
интернет говорит, что все-таки Func не передается

The parameter is now a Func<> instead of an Expression<Func<>>. The reason this makes a difference is that a predicate that's in the form of an Expression is passed to SQL server, but a predicate that's passed as a Func is not.
Да, похоже Вы правы, профайлер тоже это говорит. Спасибо за ценное замечание, впредь буду внимательней к таким моментам.
Func не может при всем желании, ведь это просто ссылка на скомпилированный код, информация о том, что внутри «свойство А сравнивается со значением Б» уже утрачена (ну, сильно трасформирована, точнее) — на этапе компиляции проекта.

А Expression — это кусочек исходного дерева кода, в котором собственно ровно и написано, что «свойство А сравнивается со значением Б». Его можно легко преобразовать в Func, вызвав Compile, а можно и непосредственно как дерево обработать, например, преобразовать в SQL «поле А сравнить со значением Б».

Поиграйтесь с ними побольше, они клёвые :)
Да, я уже почитал более подробно о различиях, спасибо!

Меня просто смутили SqlException при ошибках в Func, но видимо они были вызваны другими вещами.
НЛО прилетело и опубликовало эту надпись здесь
А зачем из Response html выгружать? Вы имели ввиду из запроса(Request)?

И на сколько я помню, в MVC 3 [ValidateInput(false)] у меня отлично работал по назначению.
Для решения задачи пользовательских фильтров, советую присмотреться к EFExtension

в этой библиотеке есть метод расширения:
IQueryable<T> Where<T>(this IQueryable<T> source, string predicate, params object[] values)

в результате можно сгенерировать строку выражения и добавить ее к запросу.
var q = query.Where("Group==@SelectedGroup && Name.Contains(@Name)", selectedGroup, name); 

Хорошим тоном считается использование таких выражении вместе с Specification Pattern
Мне лично не очень удобно составлять запрос в строковом виде, лямбдами куда приятнее. Да и опять же как лямбду, так и эту строку надо от куда то брать для каждого набора данных, либо руками, либо придумывать способ автоматической генерации, что я пытался сделать в своем решении.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории