views:

170

answers:

2

I have an ASP.NET MVC 2 application which in part allows a user to filter data and view that data in a JQGrid.

Currently this consists of a controller which initialises my filter model and configures how I wish my grid to be displayed. This information is used by a view and a partial view to display the filter and the grid shell. I use an editor template to display my filter. The JQGrid makes use of a JsonResult controller action (GET) to retrieve the results of the filter (with the addition of the paging offered by the grid - only a single page of data is returned by the GET request. The Uri used by the grid to request data contains the filter model as a RouteValue - and currently contains a string representation of the current state of the filter. A custom IModelBinder is used to convert this representation back into an instance of the filter model class.

The user can change the filter and press a submit button to get different results - this is then picked up by an (HttpPost) ViewResult action which takes the filter model - reconstituted by a further model binder and causes the grid shell to be updated.

So I have:

FilterModel Represents the user's desired filtering characteristics

FilterModelEditorTemplateSubmissionBinder : DefaultModelBinder - used to convert the request information supplied from a user changing their filtering characteristics into the appropriate FilterModel instance.

FilterModelStringRepresentationBinder : IModelBinder - used to convert the encoded filter from the JQGrid GET request for data so the correct request is made of the service which is ultimately performing the query and returning the relevant data.

ViewResult Index() - constructs a default filter, configures the grid specification and returns the view to render the filter's editor template, and the grid shell.

[HttpPost]ViewResult Filter(FilterModel filter) - takes the new filter characteristics and returns the same view as Index(). Uses FilterModelEditorTemplateSubmissionBinder to bind the filter model.

JsonResult GetData(FilterModel filter, string sidx, string sord, int page, int rows) - called from the JQGrid in order to retrieve the data. Uses FilterModelStringRepresentationBinder to bind the filter model.

As a complication, my filter model contains a option to select a single value from a collection of items. This collection is retrieved from a service request and I don't want to keep querying for this data everytime I show the filter, currently I get it if the property is null, and then include the options hidden in the editor template and encoding in the string representation. These options are then reconstituted by the relevant model binder.

Although this approach works I can't help but feel that I am having to basically reinvent viewstate in order to maintain my filter and the included options. As I am new to ASP.NET MVC but am very happy with classic ASP and ASP.NET Web Forms I thought I'd throw this out there for comment and guidance as to find a way which more closely fits with the MVC pattern.

A: 

I made this once, very simple.

pseudo code:

Controller

[HttpGet]
public ActionResult getList(int? id){
    return PartialView("Index", new ListViewModel(id??0))
}

ViewModel

public class ListViewModel{
//ObjectAmountPerPage is the amount of object you want per page, you can modify this as //parameter so the user
//can choose the amount

public int ObjectAmountPerPage = 20 //you can make this into a variable of any sort, db/configfile/parameter

public List<YourObjectName> ObjectList;
public int CurrentPage;
    public ListViewModel(id){
        Currentpage = id;
        using (MyDataContext db = new MyDataContext()){
            ObjectList = db.YourObjectName.OrderBy(object=>object.somefield).getListFromStartIndexToEndIndex(id*ObjectAmountPerPage ,(id*ObjectAmountPerPage) +20).toList();
        }
    }
}

Now Create A RenderPartial:

PartialView

<@page inherit="IEnumerable<ListViewMode>">
<%foreach(YourObjectName object in Model.ObjectList){%>
 Create a table with your fields
<%}%>

And create a view that implements your Jquery, other components+your partialView

View

<javascript>
    $(function(){
        $("#nextpage").click(function(){
          (/controller/getlist/$("#nextpage").val(),function(data){$("#yourlist").html = data});
        });
    });
</javascript>
<div id="yourlist">
    <%=Html.RenderPartial("YourPartialView", new ListViewModel())%>
</div>
<something id="nextpage" value"<%=Model.CurentPage+1%>">next page</something>

I hope this helps, this is according to the MVC- mv-mv-c principle ;) Model-View -(modelview) - control

Nealv
It is alot of code, but that's mvc, it works like a charm, it's a matter of knowing how
Nealv
Nealv - I can see that would allow me to get the pages of data I want, which is groovy but I can't see how it improves upon the solution I've cobbled together above - namely it doesn't seem to support the filtering elements I am trying to achieve. The solution I've come up with, which is currently working okay, is also a lot of code but it feels like it's too much code.
Stephen Newman
This is the way mvc is supposed to work. I know ot is alot of code as far as I know there is no way around it. Much is generated tough (right klick on a controller action -> generate view etc). I don't understand why you want to use a filter. explain that please
Nealv
Essentially the dataset is showing transactional information, and I want my users to be able to restrict what they are seeing using some checkboxes, selects, radio buttons etc. So they can view combinations of filters that I can't anticipate and wire into routes. e.g. all transactions of a given state(s), for a given purpose(s), for a particular location. So I don't retrieve all the data and make the site slow I want a GET to return the JSON for the grid, but this obviously needs the filter object. I thought about TempData but decided against it as it felt wrong to keep adding it every time
Stephen Newman
I would store that information in the session, and use the session onformation in your ViewModel queries
Nealv
A: 

It seems to me that the best way in to divide some actions which provide pure data for the jqGrid from other controller action. Such jqGrid-oriented actions can have prototype like:

JsonResult GetData(string filter, string sidx, string sord, int page, int rows)

I personally prefer to implement this part as WCF service and to have this WCF service as a part of the same ASP.NET site. In general it's much more the matter of taste and depends on your other project requirements.

This part of you ASP.NET site could implement users authentication which you need and can be tested with unit tests exactly like other actions of your controllers.

The views of the ASP.NET MVC site can have empty data for jqGrids, and have only correct URLs and probably generate the HTML code depends on the users permission in the site. Every page will fill the data of jqGrids with respect of the corresponds requests to the server (request to the corresponding GetData action).

You can use HTTP GET for the data for the best data caching. The caching of data is the subject of a separate discussion. If you do this, you should use prmNames: { nd:null } in the definition of jqGrid to remove unique nd parameter with the timestamp added per default to every GET request. To have full control of the data caching on the server side you can for example add in HTTP headers of the server responses both "Cache-Control" set to "max-age=0" and "ETag" header with the value calculated based of the data returned in the response. You should test whether the request from the client has "If-None-Match" HTTP header with the value of "ETag" coresponds the data cached on the client. Then you should verify whether the current data on the server (in the database) are changed and, if there are not changed, generate a response with an empty body (set SuppressEntityBody to true) and return "304 Not Modified" status code (HttpStatusCode.NotModified) instead of default "200 OK". A more detail explanation is much more longer.

If you don't want optimize you site for caching of HTTP GET data for jqGrids you can either use HTTP POST or don't use prmNames: { nd:null } parameter.

The code inside of JsonResult GetData(string filter, string sidx, string sord, int page, int rows) is not very short of cause. You should deserialise JSON data from the filter string and then construct the request to the data model depends on the method of the data access which you use (LINQ to SQL, Entity Model or SqlCommand with SqlDataReader). Because you have this part already implemented it has no sense to discuss this part.

Probably the main part of my suggestion is the usage of clear separation of controller actions which provide the data for all your jqGrids and the usage of MVC views with empty data (having only <table id="list"></table><div id="pager"></div>). You should also has no doubt with having a relative long code for analyzing of filters which come from the Advance Searching feature of the jqGrid and generating or the corresponding requests to your data model. Just implement it one time. In my implementation the code in also relatively complex, but it is already written one time, it works and it can be used for all new jqGrids.

Oleg
That's pretty much exactly how I have split the code up - not done anything with permissions at this point, it's a good point I'll look at that today. I think that using the model binders is the best way to pass the filter between requests, thanks for this - esp. the caching piece - could be useful.
Stephen Newman