passing attributes to ActionLink - asp.net-mvc

I have the following code for generate a tags:
<ul>
#foreach (var schedule in scheduleGroup)
{
<li>
#Html.ActionLink(string.Format("{0:HH\\:mm}", schedule.RecurrenceStart), "EventOverview", "BaseEvent", new { id = schedule.BaseEvent.OID, type = schedule.BaseEvent.XPObjectType}, new Dictionary<string, object>
{
{"session", schedule.SessionId},
{"hall",schedule.HallId},
{"client",schedule.BasePlace.PremieraClientId}
})
}
</ul>
But, html attributes shows not correctly in the a tag . This is generated markup:
20:15
Where is a error?
Thanks.
UPDATE:
I want the following:
<a client="1" hall="1" session="15" href="/BaseEvent/EventOverview?id=36&type=Film"> 20:15 </a>

Your code:
#Html.ActionLink(string.Format("{0:HH\\:mm}", schedule.RecurrenceStart), "EventOverview", "BaseEvent",
new { id = schedule.BaseEvent.OID, type = schedule.BaseEvent.XPObjectType},
new Dictionary<string, object>
{
{"session", schedule.SessionId},
{"hall",schedule.HallId},
{"client",schedule.BasePlace.PremieraClientId}
})
If your intention is to generate a link as #Tommy said that is 'session', 'hall', 'schedule' are as queryStirng parameter, then the code should be:
#Html.ActionLink(string.Format("{0:HH\\:mm}", "schedule.RecurrenceStart"), "EventOverview", "BaseEvent",
new { id = schedule.BaseEvent.OID, type = schedule.BaseEvent.XPObjectType,
session= schedule.SessionId, hall =schedule.HallId, client =schedule.BasePlace.PremieraClientId},
null)
Otherwise if you want the 'session', 'hall', 'schedule' as html attributes(
according to your given code) , there are two matching ActionLink method signatures:
public static MvcHtmlString ActionLink(
this HtmlHelper htmlHelper,
string linkText,
string actionName,
string controllerName,
Object routeValues,
Object htmlAttributes
)
And
public static MvcHtmlString ActionLink(
this HtmlHelper htmlHelper,
string linkText,
string actionName,
string controllerName,
RouteValueDictionary routeValues,
IDictionary<string, Object> htmlAttributes
)
You have to choose one of them. That is send both parameters 'routeValues' and 'htmlAttributes' as anonymous object (1st one) or as typed object, 'RouteValueDictionary' for 'routeValues' and 'IDictionary<string, Object>' for 'htmlAttributes'. Your given code matched the 1st one, that is why the 'htmlAttributes' of type IDictionary<string, Object> treat as Object and generating the incorrect tag.
Correct code should be:
#Html.ActionLink(linkText: string.Format("{0:HH\\:mm}", "schedule.RecurrenceStart"), actionName: "EventOverview", controllerName: "BaseEvent",
routeValues: new { id = schedule.BaseEvent.OID, type = schedule.BaseEvent.XPObjectType },
htmlAttributes: new
{
session = schedule.SessionId,
hall = schedule.HallId,
client = schedule.BasePlace.PremieraClientId
} )
Or
#Html.ActionLink(string.Format("{0:HH\\:mm}", "schedule.RecurrenceStart"), "EventOverview", "BaseEvent",
new RouteValueDictionary(new { id = schedule.BaseEvent.OID, type = schedule.BaseEvent.XPObjectType }),
new Dictionary<string, object>
{
{"session", schedule.SessionId},
{"hall", schedule.HallId},
{"client", schedule.BasePlace.PremieraClientId}
})

Related

Set disable attribute based on a condition for Html.TextBoxFor

I want to set disable attribute based on a condition for Html.TextBoxFor in asp.net MVC like below
#Html.TextBoxFor(model => model.ExpireDate, new { style = "width: 70px;", maxlength = "10", id = "expire-date" disabled = (Model.ExpireDate == null ? "disable" : "") })
This helper has two output disabled="disabled " or disabled="". both of theme make the textbox disable.
I want to disable the textbox if Model.ExpireDate == null else I want to enable it
The valid way is:
disabled="disabled"
Browsers also might accept disabled="" but I would recommend you the first approach.
Now this being said I would recommend you writing a custom HTML helper in order to encapsulate this disabling functionality into a reusable piece of code:
using System;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
public static class HtmlExtensions
{
public static IHtmlString MyTextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes,
bool disabled
)
{
var attributes = new RouteValueDictionary(htmlAttributes);
if (disabled)
{
attributes["disabled"] = "disabled";
}
return htmlHelper.TextBoxFor(expression, attributes);
}
}
which you could use like this:
#Html.MyTextBoxFor(
model => model.ExpireDate,
new {
style = "width: 70px;",
maxlength = "10",
id = "expire-date"
},
Model.ExpireDate == null
)
and you could bring even more intelligence into this helper:
public static class HtmlExtensions
{
public static IHtmlString MyTextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes
)
{
var attributes = new RouteValueDictionary(htmlAttributes);
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
if (metaData.Model == null)
{
attributes["disabled"] = "disabled";
}
return htmlHelper.TextBoxFor(expression, attributes);
}
}
so that now you no longer need to specify the disabled condition:
#Html.MyTextBoxFor(
model => model.ExpireDate,
new {
style = "width: 70px;",
maxlength = "10",
id = "expire-date"
}
)
Actually, the internal behavior is translating the anonymous object to a dictionary.
So what I do in these scenarios is go for a dictionary:
#{
var htmlAttributes = new Dictionary<string, object>
{
{ "class" , "form-control"},
{ "placeholder", "Why?" }
};
if (Model.IsDisabled)
{
htmlAttributes.Add("disabled", "disabled");
}
}
#Html.EditorFor(m => m.Description, new { htmlAttributes = htmlAttributes })
Or, as Stephen commented here:
#Html.EditorFor(m => m.Description,
Model.IsDisabled ? (object)new { disabled = "disabled" } : (object)new { })
I like Darin method. But quick way to solve this,
Html.TextBox("Expiry", null, new { style = "width: 70px;", maxlength = "10", id = "expire-date", disabled = "disabled" }).ToString().Replace("disabled=\"disabled\"", (1 == 2 ? "" : "disabled=\"disabled\""))
One simple approach I have used is conditional rendering:
#(Model.ExpireDate == null ?
#Html.TextBoxFor(m => m.ExpireDate, new { #disabled = "disabled" }) :
#Html.TextBoxFor(m => m.ExpireDate)
)
If you don't use html helpers you may use simple ternary expression like this:
<input name="Field"
value="#Model.Field" tabindex="0"
#(Model.IsDisabledField ? "disabled=\"disabled\"" : "")>
I achieved it using some extension methods
private const string endFieldPattern = "^(.*?)>";
public static MvcHtmlString IsDisabled(this MvcHtmlString htmlString, bool disabled)
{
string rawString = htmlString.ToString();
if (disabled)
{
rawString = Regex.Replace(rawString, endFieldPattern, "$1 disabled=\"disabled\">");
}
return new MvcHtmlString(rawString);
}
public static MvcHtmlString IsReadonly(this MvcHtmlString htmlString, bool #readonly)
{
string rawString = htmlString.ToString();
if (#readonly)
{
rawString = Regex.Replace(rawString, endFieldPattern, "$1 readonly=\"readonly\">");
}
return new MvcHtmlString(rawString);
}
and then....
#Html.TextBoxFor(model => model.Name, new { #class= "someclass"}).IsDisabled(Model.ExpireDate == null)
Is solved this using RouteValueDictionary (works fine as htmlAttributes as it's based on IDictionary) and an extension method:
public static RouteValueDictionary AddIf(this RouteValueDictionary dict, bool condition, string name, object value)
{
if (condition) dict.Add(name, value);
return dict;
}
Usage:
#Html.TextBoxFor(m => m.GovId, new RouteValueDictionary(new { #class = "form-control" })
.AddIf(Model.IsEntityFieldsLocked, "disabled", "disabled"))
Credit goes to https://stackoverflow.com/a/3481969/40939
This is late, but may be helpful to some people.
I have extended #DarinDimitrov's answer to allow for passing a second object that takes any number of boolean html attributes like disabled="disabled" checked="checked", selected="selected" etc.
It will render the attribute only if the property value is true, anything else and the attribute will not be rendered at all.
The custom reuseble HtmlHelper:
public static class HtmlExtensions
{
public static IHtmlString MyTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes,
object booleanHtmlAttributes)
{
var attributes = new RouteValueDictionary(htmlAttributes);
//Reflect over the properties of the newly added booleanHtmlAttributes object
foreach (var prop in booleanHtmlAttributes.GetType().GetProperties())
{
//Find only the properties that are true and inject into the main attributes.
//and discard the rest.
if (ValueIsTrue(prop.GetValue(booleanHtmlAttributes, null)))
{
attributes[prop.Name] = prop.Name;
}
}
return htmlHelper.TextBoxFor(expression, attributes);
}
private static bool ValueIsTrue(object obj)
{
bool res = false;
try
{
res = Convert.ToBoolean(obj);
}
catch (FormatException)
{
res = false;
}
catch(InvalidCastException)
{
res = false;
}
return res;
}
}
Which you can use like so:
#Html.MyTextBoxFor(m => Model.Employee.Name
, new { #class = "x-large" , placeholder = "Type something…" }
, new { disabled = true})
if you dont want to use Html Helpers
take look it my solution
disabled="#(your Expression that returns true or false")"
that it
#{
bool isManager = (Session["User"] as User).IsManager;
}
<textarea rows="4" name="LetterManagerNotes" disabled="#(!isManager)"></textarea>
and I think the better way to do it is to do that check in the controller and save it within a variable that is accessible inside the view(Razor engine) in order to make the view free from business logic
Yet another solution would be to create a Dictionary<string, object> before calling TextBoxFor and pass that dictionary. In the dictionary, add "disabled" key only if the textbox is to be diabled. Not the neatest solution but simple and straightforward.
Another approach is to disable the text box on the client side.
In your case you have only one textbox that you need to disable but consider the case where you have multiple input, select, and textarea fields that yout need to disable.
It is much easier to do it via jquery + (since we can not rely on data coming from the client) add some logic to the controller to prevent these fields from being saved.
Here is an example:
<input id="document_Status" name="document.Status" type="hidden" value="2" />
$(document).ready(function () {
disableAll();
}
function disableAll() {
var status = $('#document_Status').val();
if (status != 0) {
$("input").attr('disabled', true);
$("textarea").attr('disabled', true);
$("select").attr('disabled', true);
}
}
I like the extension method approach so you don't have to pass through all possible parameters.
However using Regular expressions can be quite tricky (and somewhat slower) so I used XDocument instead:
public static MvcHtmlString SetDisabled(this MvcHtmlString html, bool isDisabled)
{
var xDocument = XDocument.Parse(html.ToHtmlString());
if (!(xDocument.FirstNode is XElement element))
{
return html;
}
element.SetAttributeValue("disabled", isDisabled ? "disabled" : null);
return MvcHtmlString.Create(element.ToString());
}
Use the extension method like this:
#Html.EditorFor(m => m.MyProperty).SetDisabled(Model.ExpireDate == null)

How to encrypt URL parameters in MVC

I'm trying to encrypt the URL parameters by implementing an EncryptedActionLink that returns a link with an encrypted parameter "p" to a generic action "ResolveUrl". The controller should recieve the request and invoke the proper action, or redirect it to the actual action without showing later the unencrypted values at the address bar (RedirectToAction doesn't work because of this).
So far, I've done this extension method:
public static MvcHtmlString EncryptedActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes)
{
var RouteValueDictionary = new RouteValueDictionary(routeValues);
RouteValueDictionary.Add("actionName", actionName);
RouteValueDictionary.Add("noise", new Random().Next(5000,10000));
var routeValuesText = RouteTable.Routes.GetVirtualPath(null, RouteValueDictionary).VirtualPath;
var Encryption64 = new Encryption64();
var routeValuesTextCrypto = Encryption64.Encrypt(routeValuesText, "ABC123AB");
return htmlHelper.ActionLink(linkText, "ResolveUrl", controllerName, new { p = routeValuesTextCrypto }, htmlAttributes);
}
using this method, i get the following URL:
<%: Html.EncryptedActionLink("MyText", "MyAction", "MyContoller", new { Parameter1 = 123, Parameter2 = "String", Parameter3 = false }, null)%>
http://localhost:21536/MyContoller/ResolveUrl?p=iqo6yhy0Zl3jZXdMmnJ9KdvQhqCb5X6gg19%2FqZ8XUe19r5PJ6xO84plZr1GUHCHNY9h2SDO1o4CaF9W2DdmpywXooEQ1S0rNYjpnH4s3wb%2FqM8sGxoqAqyIoC%2F2nqW7U
Now, all my contollers inherits from ContollerBase. There I define the ResolveUrl Action as this:
public ActionResult ResolveUrl(String p)
{
var Encryption64 = new Encryption64();
var query = Encryption64.Decrypt(p, "ABC123AB");
if (query.Length > 2)
query = query.Substring(2);
var tokens = query.Split(new String [] { "&" }, StringSplitOptions.RemoveEmptyEntries);
var RouteValueDictionary = new RouteValueDictionary();
for (int i = 0; i < tokens.Count(); i++)
{
var centerPos = tokens[i].IndexOf("=");
RouteValueDictionary.Add(tokens[i].Substring(0,centerPos),tokens[i].Substring(centerPos+1));
}
Type thisType = this.GetType();
MethodInfo theMethod = thisType.GetMethod(RouteValueDictionary["actionName"].ToString());
var theParameters = theMethod.GetParameters();
var theParametersObject = new object[theParameters.Count()];
System.ComponentModel.TypeConverter converter = new System.ComponentModel.TypeConverter();
for (int i=0 ; i<theParameters.Count();i++)
{
theParametersObject[i] = converter.ConvertTo(RouteValueDictionary[theParameters[i].Name],theParameters[i].ParameterType);
}
return (ActionResult)theMethod.Invoke(this, theParametersObject);
}
the thing about that code is that the ResolveUrl doesn't work. First, when there are two implementatios for one action (POST/GET) then an exception is throwed. And the second thing that fails is the parameter type conversion (for exampte converting from string to an nullable type).
How can I encrypt the URL parameters? Is any of my code useful? What is the best way to do this?
if you are trying to encrypt url parameters (route values) you can use custom valuedataprovider that will automatically decrypt the value on action without showing unencrypted value in address bar.

ValidationMessage - Handle multiple errors for the same property

I'm using ValidationMessage control in MVC. When validating each property, it may have more than one error message to show, but the ValidationMessage only displays the first error message in the list.
Here is an example:
ModelState["Key"] = new ModelState();
ModelState["Key"].Errors.Add("Error 1");
ModelState["Key"].Errors.Add("Error 2");
and in the html I have: <%= Html.ValidationMessage("Key")%>
which displays: "Error 1"
I want to see all error messages on the page which will be "Error 1 Error 2"
Any idea how to do it?
I had exactly the same problem, so I created an extension method for HtmlHelper as replacement for the MVC ValidationMessage method.
The benefit of this over ValidationSummary method is that it displays error message per field so you can place it right next to each field (same as ValidationMessage method).
public static string AllValidationMessage(this HtmlHelper helper, string modelName)
{
StringBuilder builder = new StringBuilder();
TagBuilder ulTag = new TagBuilder("ul");
ulTag.AddCssClass("u-error-list");
builder.Append(ulTag.ToString(TagRenderMode.StartTag));
if (helper.ViewData.ModelState.ContainsKey(modelName) &&
helper.ViewData.ModelState[modelName].Errors.Count > 0)
{
foreach (var err in helper.ViewData.ModelState[modelName].Errors)
{
TagBuilder liTag = new TagBuilder("li") { InnerHtml = HttpUtility.HtmlEncode(err.ErrorMessage) };
liTag.AddCssClass("u-error-item");
builder.Append(liTag.ToString());
}
}
builder.Append(ulTag.ToString(TagRenderMode.EndTag));
var msgSpan = helper.ValidationMessage(modelName, "{placeholder}");
if (msgSpan == null)
return string.Empty;
return msgSpan.ToHtmlString().Replace("{placeholder}", builder.ToString());
}
public static string AllValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
return HtmlHelperExtensions.AllValidationMessage(helper, ExpressionHelper.GetExpressionText(expression));
}
Edit: added AllValidationMessageFor method
Edit: added a null check on msgSpan
With just out-of-the-box MVC, you'll have to add a ValidationSummary:
<%= Html.ValidationSummary() %>
That will show all ModelErrors.
Based on the solutions presented here and in How to display multiple validation errors with #Html.ValidationMessageFor?, I created my own multiline validation message for a property. It behaves somewhat like ValidationSummary but can be used per field. I use it present a validation message for a collection field of a model. This allows me to present a summary message for the collection and only the collection.
public static MvcHtmlString MultilineValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null)
{
var propertyName = ExpressionHelper.GetExpressionText(expression);
var modelState = htmlHelper.ViewData.ModelState;
// If we have multiple (server-side) validation errors, collect and present them.
if (modelState.ContainsKey(propertyName) && modelState[propertyName].Errors.Count > 1)
{
var msgs = new StringBuilder();
foreach (ModelError error in modelState[propertyName].Errors)
{
msgs.AppendLine(error.ErrorMessage + "<br />");
}
// Return standard ValidationMessageFor, overriding the message with our concatenated list of messages.
var msgSpan = htmlHelper.ValidationMessageFor(expression, "{0}", htmlAttributes as IDictionary<string, object> ?? htmlAttributes);
var msgDiv = msgSpan.ToHtmlString().Replace("span", "div");
return new MvcHtmlString(string.Format(msgDiv, msgs.ToString()));
}
// Revert to default behaviour.
return htmlHelper.ValidationMessageFor(expression, null, htmlAttributes as IDictionary<string, object> ?? htmlAttributes);
}
A more straight to the point approach:
Controller:
ModelState.AddModelError("other", "error 1");
ModelState.AddModelError("other", "error 2");
ModelState.AddModelError("other", "error 3");
View:
<ul>
#foreach (var error in Html.ViewData.ModelState["other"].Errors)
{
<li>#error.ErrorMessage</li>
}
</ul>
As ModelState follows a dictionary pattern for errors, it seems ultimately we need to concatenate all the errors into the single ModelState key:
ModelState["Key"].Errors.Add("Error 1. " + "Error 2");
If you use the IValidatableObject convention to perform custom validations, you can convert the validation result failures to ModelState entries as follows:
var resultsGroupedByMembers = validationResults
.SelectMany(_ => _.MemberNames.Select(
x => new {MemberName = x ?? "",
Error = _.ErrorMessage}))
.GroupBy(_ => _.MemberName);
foreach (var member in resultsGroupedByMembers)
{
ModelState.AddModelError(
member.Key,
string.Join(". ", member.Select(_ => _.Error)));
}
The cross join is needed noting there may be more than one MemberName per Validation Result. Unbound results are bound to "" and should be available to the ValidationSummary.
Also in your Controller Action you can check the
ModelState.IsValid
and if its false, just return the View and the ValidationSumary will be populated.

ASP.NET MVC - Pass array object as a route value within Html.ActionLink(...)

I have a method that returns an array (string[]) and I'm trying to pass this array of strings into an Action Link so that it will create a query string similar to:
/Controller/Action?str=val1&str=val2&str=val3...etc
But when I pass new { str = GetStringArray() } I get the following url:
/Controller/Action?str=System.String%5B%5D
So basically it's taking my string[] and running .ToString() on it to get the value.
Any ideas? Thanks!
Try creating a RouteValueDictionary holding your values. You'll have to give each entry a different key.
<% var rv = new RouteValueDictionary();
var strings = GetStringArray();
for (int i = 0; i < strings.Length; ++i)
{
rv["str[" + i + "]"] = strings[i];
}
%>
<%= Html.ActionLink( "Link", "Action", "Controller", rv, null ) %>
will give you a link like
<a href='/Controller/Action?str=val0&str=val1&...'>Link</a>
EDIT: MVC2 changed the ValueProvider interface to make my original answer obsolete. You should use a model with an array of strings as a property.
public class Model
{
public string Str[] { get; set; }
}
Then the model binder will populate your model with the values that you pass in the URL.
public ActionResult Action( Model model )
{
var str0 = model.Str[0];
}
This really annoyed me so with inspiration from Scott Hanselman I wrote the following (fluent) extension method:
public static RedirectToRouteResult WithRouteValue(
this RedirectToRouteResult result,
string key,
object value)
{
if (value == null)
throw new ArgumentException("value cannot be null");
result.RouteValues.Add(key, value);
return result;
}
public static RedirectToRouteResult WithRouteValue<T>(
this RedirectToRouteResult result,
string key,
IEnumerable<T> values)
{
if (result.RouteValues.Keys.Any(k => k.StartsWith(key + "[")))
throw new ArgumentException("Key already exists in collection");
if (values == null)
throw new ArgumentNullException("values cannot be null");
var valuesList = values.ToList();
for (int i = 0; i < valuesList.Count; i++)
{
result.RouteValues.Add(String.Format("{0}[{1}]", key, i), valuesList[i]);
}
return result;
}
Call like so:
return this.RedirectToAction("Index", "Home")
.WithRouteValue("id", 1)
.WithRouteValue("list", new[] { 1, 2, 3 });
Another solution that just came to my mind:
string url = "/Controller/Action?iVal=5&str=" + string.Join("&str=", strArray);
This is dirty and you should test it before using it, but it should work nevertheless. Hope this helps.
There is a library called Unbinder, which you can use to insert complex objects into routes/urls.
It works like this:
using Unbound;
Unbinder u = new Unbinder();
string url = Url.RouteUrl("routeName", new RouteValueDictionary(u.Unbind(YourComplexObject)));
This is a HelperExtension solving array and IEnumerable properties troubles :
public static class AjaxHelperExtensions
{
public static MvcHtmlString ActionLinkWithCollectionModel(this AjaxHelper ajaxHelper, string linkText, string actionName, object model, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
{
var rv = new RouteValueDictionary();
foreach (var property in model.GetType().GetProperties())
{
if (typeof(ICollection).IsAssignableFrom(property.PropertyType))
{
var s = ((IEnumerable<object>)property.GetValue(model));
if (s != null && s.Any())
{
var values = s.Select(p => p.ToString()).Where(p => !string.IsNullOrEmpty(p)).ToList();
for (var i = 0; i < values.Count(); i++)
rv.Add(string.Concat(property.Name, "[", i, "]"), values[i]);
}
}
else
{
var value = property.GetGetMethod().Invoke(model, null) == null ? "" : property.GetGetMethod().Invoke(model, null).ToString();
if (!string.IsNullOrEmpty(value))
rv.Add(property.Name, value);
}
}
return System.Web.Mvc.Ajax.AjaxExtensions.ActionLink(ajaxHelper, linkText, actionName, rv, ajaxOptions, htmlAttributes);
}
}
I'd use POST for an array. Aside from being ugly and an abuse of GET, you risk running out of URL space (believe it or not).
Assuming a 2000 byte limit. The query string overhead (&str=) reduces you to ~300 bytes of actual data (assuming the rest of the url is 0 bytes).

How do I access query string parameters in asp.net mvc?

I want to have different sorting and filtering applied on my view
I figured that I'll be passing sorting and filtering params through query string:
#Html.ActionLink("Name", "Index", new { SortBy= "Name"})
This simple construction allows me to sort. View comes back with this in query string:
?SortBy=Name
Now I want to add filtering and i want my query string to end up with
?SortBy=Name&Filter=Something
How can I add another parameter to list of already existing ones in ActionLink? for Example:
user requests /Index/
view has
#Html.ActionLink("Name", "Index", new { SortBy= "Name"})
and
#Html.ActionLink("Name", "Index", new { FilterBy= "Name"})
Links: The first one looks like /Index/?SortBy=Name and The second is /Index/?FilterBy=Name
I want when user pressed sorting link after he applied some filtering - filtering is not lost, so i need a way to combine my params.
My guess is there should be a way to not parse query string, but get collection of parameters from some MVC object.
so far the best way I figured out is to create a copy of ViewContext.RouteData.Values
and inject QueryString values into it.
and then modify it before every ActionLink usage.
still trying to figure out how to use .Union() instead of modifying a dictionary all the time.
<% RouteValueDictionary tRVD = new RouteValueDictionary(ViewContext.RouteData.Values); %>
<% foreach (string key in Request.QueryString.Keys )
{
tRVD[key]=Request.QueryString[key].ToString();
} %>
<%tRVD["SortBy"] = "Name"; %>
<%= Html.ActionLink("Name", "Index", tRVD)%>
My solution is similar to qwerty1000's. I created an extension method, ActionQueryLink, that takes in the same basic parameters as the standard ActionLink. It loops through Request.QueryString and adds any parameters found to the RouteValues dictionary that are not already present (so we can overwrite the original query string if needed).
To preserve the existing string but not add any keys the usage would be:
<%= Html.ActionQueryLink("Click Me!","SomeAction") %>
To preserve the existing string and add new keys the user would be:
<%= Html.ActionQueryLink("Click Me!","SomeAction", new{Param1="value1", Param2="value2"} %>
The code below is for the two usages, but it should be pretty easy to add other overloads to match the other ActionLink extensions as needed.
public static string ActionQueryLink(this HtmlHelper htmlHelper,
string linkText, string action)
{
return ActionQueryLink(htmlHelper, linkText, action, null);
}
public static string ActionQueryLink(this HtmlHelper htmlHelper,
string linkText, string action, object routeValues)
{
var queryString =
htmlHelper.ViewContext.HttpContext.Request.QueryString;
var newRoute = routeValues == null
? htmlHelper.ViewContext.RouteData.Values
: new RouteValueDictionary(routeValues);
foreach (string key in queryString.Keys)
{
if (!newRoute.ContainsKey(key))
newRoute.Add(key, queryString[key]);
}
return HtmlHelper.GenerateLink(htmlHelper.ViewContext.RequestContext,
htmlHelper.RouteCollection, linkText, null /* routeName */,
action, null, newRoute, null);
}
<%= Html.ActionLink("Name", "Index", new { SortBy= "Name", Filter="Something"}) %>
To preserve the querystring you can:
<%= Html.ActionLink("Name", "Index",
String.IsNullOrEmpty(Request.QueryString["SortBy"]) ?
new { Filter = "Something" } :
new { SortBy=Request.QueryString["SortBy"], Filter="Something"}) %>
Or if you have more parameters, you could build the link manually by using taking Request.QueryString into account.
Use ActionLinkCombined instead of ActionLink
public static string ActionLinkCombined(this HtmlHelper htmlHelper, string linkText, string actionName,
object routeValues)
{
var dictionary = new RouteValueDictionary();
foreach (var pair in htmlHelper.ViewContext.Controller.ValueProvider)
dictionary[pair.Key] = pair.Value.AttemptedValue;
if (routeValues != null)
{
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(routeValues))
{
object o = descriptor.GetValue(routeValues);
dictionary[descriptor.Name] = o;
}
}
return htmlHelper.ActionLink(linkText, actionName, dictionary);
}
MVC4
#Html.ActionLink("link text","action",new { #id = 5, #name = "textName", #abc = "abc" })
OR
#Html.ActionLink("link text", "action", "controller", new { #id = 5, #name = "textName", #abc = "abc" }, new { #class = "cssClass" })
querystring would be like:
yourDomainRout/action/5?name=textName&abc=abc
it would have class="cssClass"

Resources