English 中文(简体)
asp.net mvc 3 webgrid sorting remains ?sortdir=ASC
原标题:

I m experimenting a bit with several grids in asp.net mvc. Microsoft has a grid too know in the prerelease of mvc 3 so I thought I ll try that one out.

The basic functionality is quite easy to implement but when it comes to sorting, I m having some problems. The grid handles its sorting by the url. In the url you have the sort column and the sort direction in the following way: ?sortdir=ASC&sort=ABONNEMENT

Now you would expect that after you ve done the sorting on the certain column, the sortdir querystring on that column would change to ?sortdir=DESC but it doesn t. It stays ?sortdir=ASC . Does anybody knows if this is a bug or a feature, and how to solve this?

Another very annoying thing: if I click on a sort link, an httpget request is done. Because of this I loose my model. Because there is the possibility on the page to filter the grid (search functionality), I want to preserve this in the model. It would be a much easier & cleaner solution in my opinion to put this data in the model state, than to store it in the session state. Is it possible to change the behaviour of the sorting header links so a http post is executed?

Any ideas or thoughts on this? Tnx for the help.

greetz, Koen

The code of the view is the following:

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

<table>
    <tr>
        <td>
            <h1 class="normal">List of subscription</h1>
        </td>
        <td>

        </td>
    </tr>
</table>
<% using (Html.BeginForm("List", "Subscription", FormMethod.Post)) { %>

<table border="0" cellpadding="0" cellspacing="5">
    <tr>
        <td>
            Search By
        </td>
        <td>
            <%: Html.DropDownListFor(m => m.SearchByColumn, Ogone.FrontEnd.Web.Controllers.SubscriptionController.SubscriptionSearchList) %>
        </td>
        <td>
            <%: Html.TextBoxFor(m => m.SearchByText) %>
        </td>
        <td>
            <input name="button" type="submit" value="Search" />
        </td>
    </tr>
</table>

<div>
<%  
var grid = new System.Web.Helpers.WebGrid(Model.SubscriptionList,  
              columnNames: new List<string>(){"Title"},  
              canPage:false);  
 %> 
<%= grid.GetHtml(columns: grid.Columns(
             grid.Column(format: (item) => Html.ActionLink("Edit", "Edit", new { id = item.ID })),
             grid.Column("ID"),
             grid.Column("ISP"),
             grid.Column("ABONNEMENT"),
             grid.Column("MODE"),
             grid.Column("SETUPFEE"),
             grid.Column("MONTHLYFEE"))
             ) %>
</div>

问题回答

This happens because grid column names must correspond to your fields or properties. Method which generates url in grid header compares "sort" from url query string with bounded columns and with grid column name. The three of them must be the same. If definition of your column name is not configured properly, the url will not be generated correctly.

Anyway .. here is some examples of properly defined column names.

Here we have sample domain class to display

public class Person
{
    public string FirstName;
    public string LastName;
    public Address LivesIn;
}

public class Address
{
    public string Country;
    public string City;
}

Now lets display List

Use your column names as fields

grid.Column("FirstName"),
grid.Column("LastName"),
grid.Column("LivesIn.City"),
grid.Column("LivesIn.Country")

... all columns has correct sorting url

If you make a typo in column name you got exception

grid.Column("FirstName"),
grid.Column("MyLastName"), <-- this throws exception
grid.Column("LivesIn.City"),
grid.Column("LivesIn.Country")

But you can use format and there will be no exception thrown

grid.Column("FirstName"),
grid.Column("MyLastName", format: item => item.LastName</text>),
grid.Column("LivesIn.City"),
grid.Column("LivesIn.Country")

... but sorting url for column MyLastName will be BAD !!! all the time sortDir=ASC

You need to use good column name to has proper sorting url and custom format so ...

grid.Column("FirstName"),
grid.Column("LastName", format: item => item.LastName),
grid.Column("LivesIn.City"),
grid.Column("LivesIn.Country")

... everything is ok

How about complex type ?

grid.Column("FirstName"),
grid.Column("LastName"),
grid.Column("LivesIn.MyNonExistingField", format: item => item.LivesIn.City),
grid.Column("LivesIn.Country")

.... wow ... everything is ok .. that s kid of bug .. column "LivesIn.MyNonExistingField" has proper sorting url.

Ok ... What if we don t want to expose our domain structure. Then we need to add list of column names during binding

var grid = new WebGrid(persons, columnNames: new [] { "Foo" });
-- or --
grid.Bind(persons, columnNames: new [] { "Foo" });

grid.Column("Foo", format: item => item.FirstName),
grid.Column("LastName"),
grid.Column("LivesIn.MyNonExistingField", format: item => item.LivesIn.City),
grid.Column("LivesIn.Country")

.. now Foo column has proper sorting url

But careful !!! There is another bug.

If we add manual column names to binding then all column will be skipped until manual column is found. Example :

var grid = new WebGrid(persons, columnNames: new [] { "Foo" });
-- or --
grid.Bind(persons, columnNames: new [] { "Foo" });

grid.Column("FirstName"),
grid.Column("Foo", format: item => item.LastName),
grid.Column("LivesIn.MyNonExistingField", format: item => item.LivesIn.City),
grid.Column("LivesIn.Country")

... sorting url for column "FirstName" will not be generated correctly ... sortDir=ASC all the time ...to fix this add also a "FirstName" as column name like this:

var grid = new WebGrid(persons, columnNames: new [] { "FirstName", "Foo" });
-- or --
grid.Bind(persons, columnNames: new [] { "FirstName", "Foo" });

grid.Column("FirstName"),
grid.Column("Foo", format: item => item.LastName),
grid.Column("LivesIn.MyNonExistingField", format: item => item.LivesIn.City),
grid.Column("LivesIn.Country")

@Kohen

Remove columnNames in your code here

var grid = new System.Web.Helpers.WebGrid(Model.SubscriptionList,  
          columnNames: new List<string>(){"Title"},  
          canPage:false);

... or add there all your column names like "ID", "ISP", etc

I ve been thinking about a workaround for the problem, and I ve found one.

I inject an extra parameter into the querystring collection. That way I can put the search filter into the querystring.

Normally the querystring collection is readonly, but I ve found some code to fix this.

Code to add parameter to querystring:

            public static void Add(string name, string value)
            {
                    NameValueCollection qs = System.Web.HttpContext.Current.Request.QueryString;
                    qs = (NameValueCollection)System.Web.HttpContext.Current.Request.GetType().GetField("_queryString", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(System.Web.HttpContext.Current.Request);
                    PropertyInfo readOnlyInfo = qs.GetType().GetProperty("IsReadOnly", BindingFlags.NonPublic | BindingFlags.Instance);
                    readOnlyInfo.SetValue(qs, false, null);
                    qs[name] = value;
                    readOnlyInfo.SetValue(qs, true, null);
            }

New controller code:

            public ActionResult Index(string SearchFilter)
            {
                    // check querystring search
                    if (string.IsNullOrEmpty(SearchFilter) && !String.IsNullOrEmpty(Request.QueryString["search"]))
                            SearchFilter = Request.QueryString["search"];

                    var model = new Models.SubscriptionListModel { SearchFilter = SearchFilter };

                    if (string.IsNullOrEmpty(SearchFilter))
                    {
                            model.SubscriptionList = _subscriptionHandler.ReadWhereIdLessThanThousand();
                    }
                    else
                    {
                            // add search filter to the querystring
                            Common.QueryString.Add("search", SearchFilter);
                            model.SubscriptionList = _subscriptionHandler.ReadWhereContains(SearchFilter);
                    }

                    if (Request.IsAjaxRequest())
                    {
                            return View("SubscriptionList", model);
                    }
                    else
                    {
                            return View("Index", model);
                    }
            }

If anyone has a cleaner solution to fix this, suggestions are still welcome :-)

I m not sure why DESC isn t switchign to ASC. I have a similar example and it works just fine. Ttry using the ajax container(ajaxUpdateContainerId). This could help, if nothing else it would solve the issue you are having with the httpget request, maintaining your search result. Here is what I have: (I m using razor, but should be easy enough to convert).

Simply add the new property: ajaxUpdateContainerId: "div_name"

Wrap the grid with a div having an ID of div_name

@{
var grid = new System.Web.Helpers.WebGrid(Model.SubscriptionList, canPage:false, ajaxUpdateContainerId: "grid");  
}
<div id="grid">
    @grid.GetHtml(columns: grid.Columns(  
    grid.Column(format:(item) => Html.ActionLink("Edit", "Edit", new { id=item.Id })),
    grid.Column("ISP"),
    grid.Column("ABONNEMENT"))) 
</div>

Best of luck, hope it helps!

I fixed this issue in MVC 4 by setting the SortColumn prop. of the grid to the value of "sort" Query String param.:

grid = new WebGrid(...
grid.Bind(Model...

grid.SortColumn = this.Request.QueryString["sort"];

@grid.GetHtml(columns:...

I noticed that for Date columns in my model SortColumn prop. was set to the name of default sort column regardless of the value of "sort" Query String param...





相关问题
WebForms and ASP.NET MVC co-existence

I am trying to make a WebForms project and ASP.NET MVC per this question. One of the things I ve done to make that happen is that I added a namespaces node to the WebForms web.config: <pages ...

Post back complex object from client side

I m using ASP.NET MVC and Entity Framework. I m going to pass a complex entity to the client side and allow the user to modify it, and post it back to the controller. But I don t know how to do that ...

Create an incremental placeholder in NHaml

What I want to reach is a way to add a script and style placeholder in my master. They will include my initial site.css and jquery.js files. Each haml page or partial can then add their own required ...

asp.net mvc automapper parsing

let s say we have something like this public class Person { public string Name {get; set;} public Country Country {get; set;} } public class PersonViewModel { public Person Person {get; ...

structureMap mocks stub help

I have an BLL that does validation on user input then inserts a parent(PorEO) and then inserts children(PorBoxEO). So there are two calls to the same InsertJCDC. One like this=>InsertJCDC(fakePor)...

ASP.NET MVC: How should it work with subversion?

So, I have an asp.net mvc app that is being worked on by multiple developers in differing capacities. This is our first time working on a mvc app and my first time working with .NET. Our app does not ...

System.Web.Mvc.Controller Initialize

i have the following base controller... public class BaseController : Controller { protected override void Initialize(System.Web.Routing.RequestContext requestContext) { if (...

热门标签