I have an object containing searching, sorting and paging parameters as well as an id of a record to be edited.
I'd like to pass this object into Html.ActionLink() as a route value object, so that the resulting query string will be correctly mapped by the default model binder into the Edit action's parameter, which is an EditViewModel.
The idea is that after the Edit action completes, it can redirect back to the Index and maintain the same paging/sorting position, in the same data set, and filtered by the same search string.
Edit View Model:
public class EditViewModel
{
public SearchSortPageViewModel SearchSortPageParams { get; set; }
public int Id { get; set; }
public EditViewModel()
{
SearchSortPageParams = new SearchSortPageViewModel();
Id = 0;
}
public EditViewModel(SearchSortPageViewModel searchSortPageParams, int id)
{
SearchSortPageParams = searchSortPageParams;
Id = id;
}
}
public class SearchSortPageViewModel
{
public string SearchString { get; set; }
public string SortCol { get; set; }
public string SortOrder { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
}
Edit action:
public ActionResult Edit(EditViewModel evm)
{
/* ... */
}
When I do this in the view:
@model MyApp.Areas.Books.ViewModels.Books.IndexViewModel
...
@{EditViewModel evm = new EditViewModel(Model.SearchSortPageParams, item.ID);}
@Html.ActionLink("Edit", "Edit", evm)
I get this:
http://localhost:63816/Books/Books/Edit/4?SearchSortPageParams=MyApp.Areas.Base.ViewModels.SearchSortPageViewModel
But I want this:
http://localhost:63816/Books/Books/Edit/4?SearchSortPageParams.SearchString=abc&SearchSortPageParams.SortCol=name&SearchSortPageParams.SortOrder=asc&SearchSortPageParams.Page=1&SearchSortPageParams.PageSize=3
The only way I have been able to pass the object so far has been to manually prepare the query string, like this:
@{string theQueryString = "?SearchSortPageParams.SearchString=" + @evm.SearchSortPageParams.SearchString + "&SearchSortPageParams.SortCol=" + @evm.SearchSortPageParams.SortCol + "&SearchSortPageParams.SortOrder=" + @evm.SearchSortPageParams.SortOrder + "&SearchSortPageParams.Page=" + @evm.SearchSortPageParams.Page + "&SearchSortPageParams.PageSize=" + @evm.SearchSortPageParams.PageSize;}
<a href="@Url.Action("Edit", new { evm.Id })@(theQueryString)">Edit</a>
I thought of writing a custom model binder, but it seems silly given that the default model binder already handles nested objects if formatted as a query string in the way it expects.
I also thought of writing a custom object serializer which outputs a serial format which the default model binder expects, but haven't yet gone down that route.
Finally, I thought of flattening out the EditViewModel so there is nothing nested, just have all the properties listed out flatly. But, it's not ideal.
So, what is the best way to go?
As far as I know, you can't pass the complex object directly, but you can avoid having to build the query string yourself by passing a RouteValueDictionary
:
@Html.ActionLink("Edit", "Edit", new RouteValueDictionary {
{"SearchSortPageParams.SortOrder", evm.SearchSortPageParams.SortOrder },
{ /* etc... */ }
})
This should generate the query string as you need it.
The only other alternative would be use reflection to iterate over the properties of the model and generate this dictionary that way but that would, in my opinion, be over-engineered.
Of course, I would generally suggest in this situation that you just have your action method take separate parameters:
public ActionResult Search(string searchString, SortOrder sortOrder, ...)
I'd generally consider this to be a more appropriate way to pass GET parameters to a method (of course, this could get unwieldy if you have a lot of parameters). Then you can just do the following, which is much tidier:
@Html.ActionLink("Edit", "Edit",
new { sortOrder = evm.SearchSortPageParams.SortOrder, ... })