Skip to content
Leonid Gordo edited this page May 5, 2012 · 2 revisions

#Action Filters

The MvcBlanketLib provides support for the following action filters, that you can use to add specific behavoir to your action methods.

  • Navigated
  • SelectFilter
  • SortMapping

##Navigated action filter

This filters can be used to provide built-in support for processing paged lists, ie. the lists of the entities that are splitted onto a number of pages the user can move backward and forward and also can change the number of entities displayed onto the single page.

To use this functionality you should mark your action method in the controller with [Navigated] attribute.

Action method example:

[Navigated]
public ActionResult Index()
{
        var pagedViewModel = PagedViewModelFactory.Create<User>(ControllerContext, "Login")
                .Apply(f => UsersRepository.GetUsers(f as UsersListFiltersModel)).Setup();
        var viewModel = ViewModelsResolver.Resolve<UserListViewModel>();
        viewModel.Users = pagedViewModel;
        return View(viewModel);
}

The action method above renders the users list with page navigation support. The information about current page number and page size selected put into the PagedViewModel<> and then can be accessed from the view. In the example above the data queried from the storage is put into the Users property of the view model, which has the type PagedViewModel<User>.

View model example:

public class UserListViewModel 
{
    public PagedViewModel<User> Users { get; set; }        
}

Finally, the view can render the view model using the following code:

@Html.Partial("Pager", Model.Users.PagedList)
@Html.Grid(Model.Users.PagedList).AutoGenerateColumns()

The pager shared partial view can render the pager control by reading the properties of PagedList property of the PagedViewModel<> which has the type IPagination with most important properties like these:

  • PageNumber - the current page number
  • PageSize - the number of entries are displayed onto the single page
  • TotalItems - the total number of entities are queried from the storage

##SelectFilter action filter

This filter can be used to provide built-in support for processing pages with filtering of displayed items. Let's say we display the users list and provide a number of filters user can use to display only users with specific emails, or in specific status, etc.

MvcBlanketLib provides the following types of filters:

  • Text filter - a simple text box, user can enter either string or numeric values
  • Date filter - a text box decorated with datepicker, user can select the date from the calendar
  • Drop down list - a drop down list, user can select one value from it
  • Multiple select list - a select list with multiple selection enabled, user can select one or more values from it.

Text filter and date filter can provide special behavior, named Range when user should enter not single value, but a range of values (lower bound value and upper bound value, say a range of dates or numbers).

To use this functionality you should add special filters model to your web application.

Filters model example:

public class UsersListFiltersModel : IPageFiltersModel
{
    [Alias(Name="log")]
    public PageFilter<string> Login { get; set; }

    public PageFilter<string> Email { get; set; }

    [NotSelectedValue(NotSelectedValue = FilterModelConstants.NotSelectedValue)]
    public PageFilter<string> Role { get; set; }

    [NotSelectedValue(NotSelectedValue = FilterModelConstants.NotSelectedValue)]
    public PageFilter<IEnumerable<bool>> Status { get; set; }

    [Locale("ru"), Alias(Name="last")]
    public PageFilter<IRange<DateTime>> LastLogon { get; set; }
}

The filter model should inherits from the IPageFilterModel mark interface. The model can contains any number of properties of type PageFilter<> which describe the filters user can apply to the query. Each property can be annotated with one of three attributes:

  • Alias - provides alias will be used as key in the query string for the filter value. If alias not provided the key is just a name of property in the lower case.
  • NotSelectedValue - provides special not selected value for the drop down list filters, which means that user does not select any meaningful value for the filter.
  • Locale - provides information about culture should be used when rendering filters controls onto the page and when parsing values entered by the user. Contains two properties:
  • LocaleName - the name of explicit culture to use (for example, en-US)
  • UseClientLocale - the boolean property, when set to true means that culture determined by the client browser will be used.

To render the filter controls onto the page use can use HtmlHelper extension methods:

  • FilterTextBox
  • FilterDateBox
  • FilterDropDownList
  • FilterList

And additional extension methods to simplify creation of the filters form:

  • FilterFormButtons
  • BeginFilterForm
  • EndFilterForm

View example:

@Html.BeginFilterForm()
<ul>
    <li>@tml.FilterTextBox(m => m.Login, Resources.Labels.Login)</li>
    <li>@Html.FilterTextBox(m => m.Email, Resources.Labels.Email)</li>        
    <li>@Html.FilterDropDownList(m => m.Role, FilterModelConstants.NotSelectedValue, Resources.Labels.UnsetValueText, Model.Roles, r => r.Name, r => r.DisplayName, Resources.Labels.UserRole)</li>
    <li>@Html.FilterDropDownList(m => m.Status, FilterModelConstants.NotSelectedValue, Resources.Labels.UnsetValueText, Model.Statuses, r => r.Value, r => r.Text, Resources.Labels.UserStatus)</li>
    <li>@Html.FilterList(m => m.Status, Model.Statuses, r => r.Value, r => r.Text, Resources.Labels.UserStatus)</li>
    <li>@Html.FilterDateBox(m => m.LastLogon, Resources.Labels.LastLogon)</li>
</ul>
@Html.FilterFormButtons(Resources.Labels.Apply, Resources.Labels.Reset)
@Html.EndFilterForm()

For the drop down list and multiple select list use should provide the list of type IEnumerable<> of possible options and two lambda expressions to select value and text from the list accordingly. Also for the drop down list your should point to the not selected value and not selected text which will be displayed above all possible options to select.

To date filter properly operate you should add the following javascript:

(function ($, undefined) {
$(function () {        
    $(".single-datepicker").datepicker($.datepicker.regional["ru"]);

    $("#sform").submit(function () {
        $(".single-datepicker").each(function () {
            var tb = $(this);
            var date = tb.datepicker("getDate");
            if (date != null) {
                date = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
                tb.next(":hidden").val(date);
            }
        });
    });
});
})(jQuery);

In the regional settings of datepicker you can set any of supported cultures, or live this setting empty to use default culture.

To process user input you should annotate your action method with SelectFilter action attribute and call the repository method passing the filters model automatically created and filled for you by the library.

Action method example:

[SelectFilter(FiltersModel = typeof(UsersListFiltersModel))]
public ActionResult Index()
{
        var pagedViewModel = PagedViewModelFactory.Create<User>(ControllerContext, "Login")
               .Apply(f => UsersRepository.GetUsers(f as UsersListFiltersModel)).Setup();
        var viewModel = ViewModelsResolver.Resolve<UserListViewModel>();
        viewModel.Users = pagedViewModel;
        return View(viewModel);
}

Of course, you can combine the SelectFilter and Navigated attributes together to get navigated and filtered paged list.

Repository can contain the following code to query the storage and filters the output.

public IQueryable<User> GetUsers(UsersListFiltersModel filters)
{
        var c = Context.Users;
        if (filters == null) return c;
        return c
           .Where(filters.Login, x => x.Login.StartsWith(filters.Login.Value))
           .Where(filters.Email, x => x.EMail.StartsWith(filters.Email.Value))
           .Where(filters.Role, x => x.Roles.Select(r => r.Name).Any(s => s == filters.Role.Value))
           .Where(filters.LastLogon, x => x.LastLogon >= filters.LastLogon.Value.LowerBound && x.LastLogon <= filters.LastLogon.Value.UpperBound)
           .Where(filters.Status, x => filters.Status.Value.Contains(x.IsActive));
}

This code uses special overloaded extension method Where which expands the standard implementation of Where method by adding one extra parameter of type PageFilter<>. This method first checks the page filter passed to see if this filter is actually used. If filter was not set or contains special not selected value, so it is considered to be not used and therefore skipped. You can manually check the filter by accessing the following properties:

  • Selected - boolean property signals that filter is actually used
  • Value - the actual filter value
  • FormatException - the exception that could occur while reading and converting filter value from the string. If this property contains the not null, so Selected property will be automatically set to false.

The page filter can be of simple type, like string, int, DateTime, etc., or one of the composite types:

  • IEnumerable<> - used for multiple select lists, to provide the capability to store all selected values

  • IRange<> - special type to provide the capability to store range of values (say, range of dates or numbers). This type provides the following properties you can examine:

  • LowerBound - the lower value user entered

  • UpperBound - the upper value user entered

##SortMapping action filter

This filter can be used to provide special behavior for the sorting feature. If you have the grid with columns, and has at least one column which contains complex data, you may wish to expect that the sorting for this column will be dependable of several columns at once.

View example:

@Html.Grid(Model.Tracks.PagedList).Columns(
        column =>
        {
            column.For(x => x.Title);
            column.For(x => x.VolumeNumber.ToString(CultureInfo.InvariantCulture) + "/" + x.TrackNumber.ToString(CultureInfo.InvariantCulture)).Named("Volume number / Track number").Sortable(true).SortColumnName("tracknum");
        }
).Sort(Model.Tracks.GridSortOptions)

The example above show the grid for the tracks list. The second column contains the complex data: volume number and track number inside the volume. The sorting over the second column should be over both columns in the database, first by volume number, then by track number. This column has special alias for sorting assigned with SortColumnName method call.

To provide the required functionality you should annotate you action method with SortMapping action filter attribute.

Action method example:

[SelectFilter(FiltersModel = typeof(TracksListFiltersModel)), Navigated]
[SortMapping(Mapping="tracknum=VolumeNumber,TrackNumber")]
public ActionResult Tracks(int? id)
{
    var tracks = PagedViewModelFactory.Create<Track>(ControllerContext, "tracknum")
            .Apply(f => Repository.GetTracks(f as TracksListFiltersModel)).SetupByNames();

    return View(tracks);
}

In the example above, the SortMapping attribute defines the mapping between the alias and the set of columns in the database over which the sorting will occur.

Clone this wiki locally