2

I work on a ASP.NET MVC project that needs to be able to filter data in an extensible way. I've decided to use a class to represent a single filter criterion (I am aware that it could be a Dictionary at this point, but I might need to add other properties later):

public class FilterCriterion
{
    public string FilterName { get; set; }

    public string FilterValue { get; set; }
}

And this is the controller method signature I had in mind that will return filtered data:

public JsonResult GetMultipleShowDetailsByFilter(IEnumerable<FilterCriterion> filters, int pageNumber, int itemsPerPage)

This method will be called from the web page (using JavaScript) and will return a JSON containing a set of entities that will be then displayed on the page.

I assume I will need some kind of converter that will accept the arguments in a raw state and create and enumerable object implementing IEnumerable<FilterCriterion>. Maybe I could use a ModelBinder, but I am not really sure how.

Can you nudge me in the right direction? Thanks.

EDIT: I not pass an array as suggested. When I step into the controller, there is a correct number of items inside filters, but their properties (both FilterName and FilterValue) are null. Below I post the complete code as well as the contents of filter object on the client side. What am I doing wrong? Javscript code:

function filterChanged() {
    var activeFilters = $(':checked');
    var filters = new Array();
    $.each(activeFilters, function (i, val) {
        var newItem = new Object();
        newItem.FilterName = $(val).attr('data-filter-type');
        newItem.FilterValue = $(val).attr('data-filter-value');
        filters[i] = newItem;
    });

    $.getJSON('../DatabaseApi/GetMultipleShowDetailsByFilter',
        {
            'filters': filters,
            'pageNumber': 1,
            'itemsPerPage': 10
        },
        function(data) {
            fill(data);
        });
}

How do the created object look (proof that there is data, taken from VS Immediate Window):

?filters
[[object Object],[object Object]]
    [0]: {...}
    [1]: {...}
    [prototype]: []
?filters[0]
{...}
    [prototype]: {...}
    FilterName: "Genre"
    FilterValue: "Animation"
Jan Kratochvil
  • 2,307
  • 1
  • 21
  • 39
  • 1
    if you are already using the class FilterCriterion why not add int pageNumber, int itemsPerPage as a variable in a class it would be a lot easier for you to parse it later – COLD TOLD Jul 28 '12 at 19:52
  • Good idea, plus it will simplify my method signature, which is always a good thing :-) – Jan Kratochvil Jul 28 '12 at 19:56
  • @COLDTOLD in fact, that's *bad* idea. There may be several `FilterCriterion`s, and how should code decide which pageNumber to use? – Sergei Rogovtcev Jul 28 '12 at 20:12
  • @Serg Rogovtsevthe page number is part of the object and will stay or change based on filter but passing an array is a really bad idea in this specific situation since array maintenance will a lot harder – COLD TOLD Jul 29 '12 at 01:30
  • @COLDTOLD part of which object? And what "array maintenance" will be a lot harder? – Sergei Rogovtcev Jul 29 '12 at 06:53
  • @Serg Rogovtsev part of the FilterCriterion object the variable FilterName may be equal to a, b, c but the variable pageNumber may be the same or change depending on the situation the array are harder to maintain since you constantly need to keep track of n as comparing to stack – COLD TOLD Jul 29 '12 at 18:03
  • @COLDTOLD It seems to me that you don't undestand the scenario. It's common "grid with paging and filtering (and sorting)" one. When you filter and page a grid, you may have *several* filters (because you have several columns), but you have only one page descriptor. So you're stuck with *multiple* `FilterCriterion`s anyway, but there's no reason to implement paging on `FilterCriterion` because paging has nothing to do with filtering, that's separate concerns. – Sergei Rogovtcev Jul 29 '12 at 18:07
  • @Serg Rogovtsev you seem to be confused about the concept of how to handle the FilterCriterion I will repeat the page variable may stay the same while the public string FilterName, string FilterValue may change you still will be sending the same values for page every time you send the filter – COLD TOLD Jul 29 '12 at 18:13
  • @COLDTOLD the thing you propose violates filter semantics. – Sergei Rogovtcev Jul 29 '12 at 18:14
  • @Serg Rogovtsev nope does not violates anything at all because the variable FilterName, FilterValue are treated separatly – COLD TOLD Jul 29 '12 at 18:16
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/14592/discussion-between-serg-rogovtsev-and-cold-told) – Sergei Rogovtcev Jul 29 '12 at 18:16

2 Answers2

3
  1. Change your controller signature to

    public JsonResult GetMultipleShowDetailsByFilter(FilterCriterion[] filters, int pageNumber, int itemsPerPage)
    
  2. use following POST structure:

    filters[0].FilterName = 'aaa'
    filters[0].FilterValue = 'bbbb'
    filters[1].FilterName = 'ccc'
    filters[1].FilterValue = 'ddd'
    ...
    filters[n].FilterName = 'xxx'
    filters[n].FilterValue = 'yyy'
    

    (you can easily do that with jQuery)

One caveat: your index numbering should be contiguous, i.e. have no gaps.

Update to topic starter's update: you have to be very careful with POST structure. Nested JavaScript objects won't do. Try using this one:

function filterChanged() {
    var activeFilters = $(':checked');
    var data = {
        'pageNumber': 1,
        'itemsPerPage': 10
    }
    $.each(activeFilters, function (i, val) {
        data['filters[' + i + '].FilterName'] = $(val).attr('data-filter-type');
        data['filters[' + i + '].FilterValue'] = $(val).attr('data-filter-value');
    });

    $.getJSON('../DatabaseApi/GetMultipleShowDetailsByFilter',
        data,
        function(data) {
            fill(data);
        });
}
Sergei Rogovtcev
  • 5,804
  • 2
  • 22
  • 35
  • Thanks, could you take look at my modified question and tell me what am I doing wrong? – Jan Kratochvil Jul 28 '12 at 20:31
  • Your `POST` structure is wrong. It has to be *exactly* as specified. I'll try to write an example in update to my post. – Sergei Rogovtcev Jul 28 '12 at 21:00
  • Thanks, this works. Could you maybe include some link or brief explanation of why the structure has to be precisely like this? (I'm not an experienced JavaScript dev and the structure of 'data' seems a bit bizarre to me :-) ) – Jan Kratochvil Jul 28 '12 at 21:12
  • FYI I've tried it and IEnumerable in the signature works fine (since arrays in .NET implement IEnumerable). – Jan Kratochvil Jul 28 '12 at 21:16
  • 1
    http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx It has nothing to do with JavaScript, it is MVC's default model binder requirement. – Sergei Rogovtcev Jul 28 '12 at 21:23
2

see this link, here you can see the plugin ToDictionary works great and solves these problems, allowing us to have the code much cleaner:

JS:

function filterChanged() {

    var filters = [];
    $(':checked').each(function (i, el) {
        filters.push({
            'FilterName': $(this).data('filter-type'),
            'FilterValue': $(this).data('filter-value')
        });
    });

    var data = $.toDictionary({
        'filters': filters,
        'pageNumber': 1,
        'itemsPerPage': 10
    });

    var response = function (data) {
        fill(data);
    };

    $.getJSON('../DatabaseApi/GetMultipleShowDetailsByFilter', data, response);
}

based on the response: link

Community
  • 1
  • 1
andres descalzo
  • 14,887
  • 13
  • 64
  • 115