Reputation: 181
I am currently writing my own paged list for ASP.NET Core MVC.
I struggle while creating the following:
I want to create a UrlHelper extension method: Url.PageLink(int page, int pageSize)
Within that extension method I want to return a link which reuses ALL current values for controller, action, querystring and in addition it should add/update the page, pageSize values in the querystring as well.
Questions: From where do I get the current controller and action in the UrlHelper object? What is the best way to rebuild the querystring? I can see it here url.ActionContext.HttpContext.Request.QueryString... but do I really have to manually rebuild it? Or does something like AddQueryStringValue(int key, object value) exist?
Thanks so much in advance!!! Simon
Upvotes: 5
Views: 4038
Reputation: 3587
The question asks for Asp.Net Core. I was trying to use shturm's answer on MVC 5 and could not get it to work. I took his same concepts and changed it to work with MVC 5. If anyone needs this verison, I'm posting it here. I also added the ability to change the action and controller.
/// <summary>
/// Get URL with substituted values while preserving query string.
/// </summary>
/// <param name="helper">The url helper object we are extending.</param>
/// <param name="action">The action we want to direct to.</param>
/// <param name="controller">The controller we want to direct to.</param>
/// <param name="substitutes">Query string parameters or route data paremers. E.g. new { action="Index", sort = "asc"}</param>
/// <returns>The route string.</returns>
public static String ActionWithOriginalQueryString(this UrlHelper helper, String action, String controller, object substitutes)
{
RouteValueDictionary routeData = new RouteValueDictionary(helper.RequestContext.RouteData.Values);
NameValueCollection queryString = helper.RequestContext.HttpContext.Request.QueryString;
//add query string parameters to the route data
foreach (var param in queryString.AllKeys)
{
if (!string.IsNullOrEmpty(queryString[param]))
{
//rd[param.Key] = qs[param.Value]; // does not assign the values!
routeData.Add(param, queryString[param]);
}
}
// override parameters we're changing in the route data
//The unmatched parameters will be added as query string.
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(substitutes.GetType()))
{
var value = property.GetValue(substitutes);
if (string.IsNullOrEmpty(value?.ToString()))
{
routeData.Remove(property.Name);
}
else
{
routeData[property.Name] = value;
}
}
//Set the controller and the action.
routeData["controller"] = controller;
routeData["action"] = action;
//Return the route.
return helper.RouteUrl(routeData);
}
Upvotes: 2
Reputation: 867
Improvement of Simon's answer:
It can be called as @Url.Current(new {page=42, id=5, foo=bar})
It will preserve query string and update/add provided values.
public static class UrlHelperExtensions
{
/// <summary>
/// Get current URL with substituted values while preserving query string
/// </summary>
/// <param name="helper"></param>
/// <param name="substitutes">Query string parameters or route data paremers. E.g. new { action="Index", sort = "asc"}</param>
/// <returns></returns>
public static string Current(this IUrlHelper helper, object substitutes)
{
RouteValueDictionary routeData = new RouteValueDictionary(helper.ActionContext.RouteData.Values);
IQueryCollection queryString = helper.ActionContext.HttpContext.Request.Query;
//add query string parameters to the route data
foreach (var param in queryString)
{
if (!string.IsNullOrEmpty(queryString[param.Key]))
{
//rd[param.Key] = qs[param.Value]; // does not assign the values!
routeData.Add(param.Key, param.Value);
}
}
// override parameters we're changing in the route data
//The unmatched parameters will be added as query string.
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(substitutes.GetType()))
{
var value = property.GetValue(substitutes);
if (string.IsNullOrEmpty(value.ToString()))
{
routeData.Remove(property.Name);
}
else
{
routeData[property.Name] = value;
}
}
string url = helper.RouteUrl(routeData);
return url;
}
}
Upvotes: 2
Reputation: 181
Have done it now like this:
public static class UrlHelperExtensions
{
public static string Page(this IUrlHelper url, int page, int pageSize)
{
//Reuse existing route values
RouteValueDictionary resultRouteValues = new RouteValueDictionary(url.ActionContext.RouteData.Values);
//Add existing values from query string
foreach (var queryValue in url.ActionContext.HttpContext.Request.Query)
{
if(resultRouteValues.ContainsKey(queryValue.Key))
continue;
resultRouteValues.Add(queryValue.Key, queryValue.Value);
}
//Set or add values for PagedList input model
resultRouteValues[nameof(PagedListInputModel.Page)] = page;
resultRouteValues[nameof(PagedListInputModel.PageSize)] = pageSize;
return url.RouteUrl(resultRouteValues);
}
}
And created a separate input model "PagedListInputModel" for just the pagedlist values... This way I can ensure that I can reuse it in all places and don't have to make sure that page and pageSize properties are included in all needed viewmodels with the correct name.
Any feedback is welcome.
Upvotes: 1