Пользовательские маршруты с ASP.NET MVC для многогранного поиска [от QueryString до Route]

Я реализую функцию многогранного поиска, где пользователь может фильтровать и детализировать 4 свойства моей модели: City, Type, Purpose и Value.

У меня есть раздел просмотра с такими гранями:

введите здесь описание изображения

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

Я делаю это со строками запроса, которые я передаю с помощью специального вспомогательного метода ActionLink:

 @Html.ActionLinkWithQueryString(linkText, "Filter",
                                 new { facet2 = Model.Types.Key, value2 = fv.Range });

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

http://leniel-pc:8083/realty/filter?facet1=City&value1=Volta%20Redonda&
facet2=Type&value2=6&facet3=Purpose&value3=3

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

http://leniel-pc:8083/realty/filter // returns ALL rows

http://leniel-pc:8083/realty/filter/city/rio-de-janeiro/type/6/value/50000-100000

http://leniel-pc:8083/realty/filter/city/volta-redonda/type/6/purpose/3

http://leniel-pc:8083/realty/filter/type/7/purpose/1

http://leniel-pc:8083/realty/filter/purpose/3/type/4

http://leniel-pc:8083/realty/filter/type/8/city/carangola

Это возможно? Любые идеи?


person Leniel Maccaferri    schedule 25.02.2012    source источник


Ответы (2)


Это возможно? Любые идеи?

Я бы сохранил параметры строки запроса для фильтрации.

Но если вы хотите получить URL-адреса, которые вы просили в своем вопросе, я расскажу о двух возможных методах.

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

public class FilterViewModel
{
    public string Key { get; set; }
    public string Value { get; set; }
}

и контроллер:

public class RealtyController : Controller
{
    public ActionResult Filter(IEnumerable<FilterViewModel> filters)
    {
        ... do the filtering ...
    }
}

Первый вариант — написать пользовательскую привязку модели, которая будет связана с типом IEnumerable<FilterViewModel>:

public class FilterViewModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var filtersValue = bindingContext.ValueProvider.GetValue("pathInfo");
        if (filtersValue == null || string.IsNullOrEmpty(filtersValue.AttemptedValue))
        {
            return Enumerable.Empty<FilterViewModel>();
        }

        var filters = filtersValue.AttemptedValue;
        var tokens = filters.Split('/');
        if (tokens.Length % 2 != 0)
        {
            throw new Exception("Invalid filter format");
        }

        var result = new List<FilterViewModel>();
        for (int i = 0; i < tokens.Length - 1; i += 2)
        {
            var key = tokens[i];
            var value = tokens[i + 1];
            result.Add(new FilterViewModel
            {
                Key = tokens[i],
                Value = tokens[i + 1]
            });
        }

        return result;
    }
}

который будет зарегистрирован в Application_Start:

ModelBinders.Binders.Add(typeof(IEnumerable<FilterViewModel>), new FilterViewModelBinder());

и у вас также будет маршрут фильтра:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "Filter",
        "realty/filter/{*pathInfo}",
        new { controller = "Realty", action = "Filter" }
    );

    routes.MapRoute(
        "Default",
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

Вторая возможность - написать собственный маршрут

public class FilterRoute : Route
{
    public FilterRoute()
        : base(
            "realty/filter/{*pathInfo}", 
            new RouteValueDictionary(new 
            { 
                controller = "realty", action = "filter" 
            }), 
            new MvcRouteHandler()
        )
    {
    }

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var rd = base.GetRouteData(httpContext);
        if (rd == null)
        {
            return null;
        }

        var filters = rd.Values["pathInfo"] as string;
        if (string.IsNullOrEmpty(filters))
        {
            return rd;
        }

        var tokens = filters.Split('/');
        if (tokens.Length % 2 != 0)
        {
            throw new Exception("Invalid filter format");
        }

        var index = 0;
        for (int i = 0; i < tokens.Length - 1; i += 2)
        {
            var key = tokens[i];
            var value = tokens[i + 1];
            rd.Values[string.Format("filters[{0}].key", index)] = key;
            rd.Values[string.Format("filters[{0}].value", index)] = value;
            index++;
        }

        return rd;
    }
}

который будет зарегистрирован в вашем методе RegisterRoutes:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.Add("Filter", new FilterRoute());

    routes.MapRoute(
        "Default",
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}
person Darin Dimitrov    schedule 26.02.2012
comment
ВАУ! Фантастика, мой друг... всегда выдающиеся ответы. Я думаю, что пока я оставлю подход QueryString, поскольку он работает, как и ожидалось, с использованием манипуляции с QueryString, о которой я упоминаю в вопросе. Ваш ответ, я уверен, будет использован в будущем. Отличная ссылка для нас, простых смертных... - person Leniel Maccaferri; 26.02.2012

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

Ваши URL-адреса не имеют особого смысла с точки зрения логической иерархии ресурсов.

Однако я бы, вероятно, переименовал метод «фильтр» в «поиск», при этом фильтры были бы переменными строки запроса. Кроме того, необходимо ли определять фасеты в строке запроса - разве вы не можете добиться того же результата, назвав фасет явным образом, например ?city=Volta&type=6&purpose=3 ?

person Mike Simmons    schedule 25.02.2012
comment
Да Майк. Я был так сконцентрирован на этом, что совершенно забыл, что могу просто передавать простые параметры. Нет необходимости в facet1/value1 и т. д. Я уже рефакторил это. Спасибо, что добавили свой ответ. Это очень помогает иметь другую точку зрения. - person Leniel Maccaferri; 25.02.2012