How to call an Action from a helper and avoid the render? - asp.net-mvc

I'm working on ASP.NE MVC4 and I'm using LungoJS library for the development.
I'm developing a Helper to render LungoJS controls in the way I need.
For the Select inputs, I want to get a JSON that contains the data for each option.
I can get the needed JSON from an Action of a existing Controller.
But when I call the action from the helper, the view renders my html inside a <pre> tag :-(
View
<div class="form" id="address-data">
#Html.LungoInputFor(m => m.CaseServiceCaseModel.CaseAddressModel.AddressDataModel.TerritoryId, "text", #Labels.Territory, #htmlAttributesRO, true)
#Html.LungoSelectFor(m => m.CaseServiceCaseModel.CaseAddressModel.AddressDataModel.TerritoryId, #Labels.Territory, null, true, "GetTerritoryKendo", "Address")
Helper
public static MvcHtmlString LungoSelectFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string label = "", object htmlAttributes = null, bool fieldset = false, string action = "", string controller = "")
{
TagBuilder tag = new TagBuilder("select");
JavaScriptSerializer js = new JavaScriptSerializer();
MvcHtmlString data = ChildActionExtensions.Action(html, action, controller);
object[] json = js.Deserialize<object[]>(data.ToString());
foreach (Dictionary<string,object> item in json)
{
foreach (var property in item)
{
TagBuilder option = new TagBuilder("option");
switch (property.Key)
{
case "Selected":
if ((bool)property.Value)
{
option.MergeAttribute("selected","selected");
}
break;
case "Text":
option.InnerHtml = property.Value.ToString();
break;
case "Value":
option.MergeAttribute("value", property.Value.ToString());
break;
default:
break;
}
tag.InnerHtml += option.ToString(TagRenderMode.Normal);
}
}
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}
I don't understand why if I'm getting the htmlstring from the Action in a variable, and after I return only the Select input html, affects the whole page... If I comment ChildActionExtensions.Action(html, action, controller) the view renders good (but without the content I need...)

You should not be using child actions here (ChildActionExtensions.Action helper) because they write directly to the response. What you could do instead is to include a property on your view model that will contain the collection you need and directly pass it as argument to your helper. Alternatively (but this is not a good solution) would be to call your DAL in the helper to retrieve the list.

Related

Creating a custom countrydownlistfor - missing assemby reference on load

I have created a custom html helper for an mvc5 application I am working on to create a dropdownlist for countries
public static MvcHtmlString CountryDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, string css, bool displayPleaseSelect = true)
{
var countries = GetCountries();
var list = countries.Select(c => new SelectListItem()
{
Selected = false,
Text = c.Name,
Value = c.Id
}).ToList();
if (displayPleaseSelect)
{
list.Insert(0, new SelectListItem() { Selected = true, Text = "- Please Select -", Value = "0" });
}
return htmlHelper.DropDownListFor(expression, list, new { #class = css });
}
I think my helper looks ok however when adding it to an editor template view like
#Html.CountryDropDownListFor(m=>m.CountryId,"")
I get an error
'System.Web.Mvc.HtmlHelper' does not contain a definition for 'CountryDropDownListFor' and no extension method 'CountryDropDownListFor' accepting a first argument of type 'System.Web.Mvc.HtmlHelper' could be found (are you missing a using directive or an assembly reference?)
Can anyone see what I am doing wrong? The helper is in the same project

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)

Conditionally disable Html.DropDownList

How can I change this DropDownList declaration so that the disabled attribute is enable/disabled conditionally?
<%= Html.DropDownList("Quantity", new SelectList(...), new{#disabled="disabled"} %>
non-working example:
<%= Html.DropDownList("Quantity", new SelectList(...), new{#disabled=Model.CanEdit?"false":"disabled"} %>
p.s. adding an if condition around the entire statement is not a desired approach :)
EDIT: based on this extension method from another question I came up with the following extension:
public static IDictionary<string, object> Disabled (this object obj, bool disabled)
{
return disabled ? obj.AddProperty ("disabled", "disabled") : obj.ToDictionary ();
}
which can then be used as
<%= Html.DropDownList("Quantity", new SelectList(...), new{id="quantity"}.Disabled(Model.CanEdit) %>
There is no need to add helper methods, you can just use
<%= Html.DropDownList("Quantity", new SelectList(...), IsEditable == true ? new { #disabled = "disabled" } as object : new {} as object %>
If you were to remove the as object entries this wouldn't work because by default new {} is a dynamic object compiled at runtime, therefore the two possible objects must have the same properties. But the Html attributes parameter is actually just an object, so these dynamics can be cast as objects to get around this.
This solution even allows you to use multiple HTML attributes where one is optional and another is not, i.e class='whatever' is not optional but disabled is so you put class='whatever' in both the objects, but the optional one only in the first. Dimitrov's answer does not support any custom attributes other than disabled.
Please don't write spaghetti code. Html helpers are there for this purpose:
public static MvcHtmlString DropDownList(this HtmlHelper html, string name, SelectList values, bool canEdit)
{
if (canEdit)
{
return html.DropDownList(name, values);
}
return html.DropDownList(name, values, new { disabled = "disabled" });
}
And then:
<%= Html.DropDownList("Quantity", new SelectList(...), Model.CanEdit) %>
Or maybe you could come up with something even better (if the model contains the options):
<%= Html.DropDownList("Quantity", Model) %>
You will also get the bonus of having more unit testable code.
One option is creating a custom version of Html.DropDownList that takes an extra parameter and does what you want... but then you would have to make a new one for every type of helper - TextBoxFor, TextAreaFor, CheckBoxFor, etc... and you still have to figure out how to make the guts of it work.
I opted, instead, to create an Html Helper to replace the normal anonymous HtmlAttributes object since then it would be compatible with all of the Helpers that use HtmlAttributes without any special work. This solution also lets you pass through additional Attributes like Class, Name, or whatever you want. It doesn't lock you down to only disabled.
I created the following Helper - it takes a boolean and an anonymous object. If disabled is true, it adds the disabled attribute to the anonymous object (which is actually a Dictionary) with the value "disabled", otherwise it doesn't add the property at all.
public static RouteValueDictionary ConditionalDisable(
bool disabled,
object htmlAttributes = null)
{
var dictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (disabled)
dictionary.Add("disabled", "disabled");
return dictionary;
}
An example of it in action:
#Html.TextBoxFor(m => m.SomeProperty,
HtmlHelpers.ConditionalDisable(true, new { #class = "someClass"))
One huge advantage to this approach for me was that it works with virtually all of the MVC HtmlHelpers since they all have Overloads that accept a RouteValueDictionary instead of an anonymous object.
Caveats:
HtmlHelper.AnonymousObjectToHtmlAttributes() uses some fancy code ninja work to get things done. I'm not entirely sure how performant it is... but it's been sufficient for what I use it for. Your mileage may vary.
I don't especially like the name of it - but I couldn't come up with anything better. Renaming is easy.
I also don't love the usage syntax - but again I couldn't come up with anything better. It shouldn't be difficult to change. An extension method on object is one idea... you'd end up with new { #class = "someClass" }.ConditionalDisable(true) but then if you only want the disable attribute and don't have anything additional to add you end up with something gross like new {}.ConditionalDisable(true); and you also end up with an extension method that shows up for all objects... which is probably not desirable.
#bool IsEditable=true;
#if (IsEditable)
{
Html.DropDownListFor(m => m, selectList);
}
else
{
Html.DropDownListFor(m => m, selectList, new { disabled = "disabled" })
}
Strongly typed verison:
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
string optionText, bool canEdit)
{
if (canEdit)
{
return html.DropDownListFor(expression, selectList, optionText);
}
return html.DropDownListFor(expression, selectList, optionText, new { disabled = "disabled" });
}
For completeness here is one that preservers all parameters and it would post select value to the server:
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, object htmlAttributes, bool enabled)
{
if (enabled)
{
return SelectExtensions.DropDownListFor<TModel, TProperty>(html, expression, selectList, htmlAttributes);
}
var htmlAttributesAsDict = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
htmlAttributesAsDict.Add("disabled", "disabled");
string selectClientId = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression));
htmlAttributesAsDict.Add("id", selectClientId + "_disabled");
var hiddenFieldMarkup = html.HiddenFor<TModel, TProperty>(expression);
var selectMarkup = SelectExtensions.DropDownListFor<TModel, TProperty>(html, expression, selectList, htmlAttributesAsDict);
return MvcHtmlString.Create(selectMarkup.ToString() + Environment.NewLine + hiddenFieldMarkup.ToString());
}
Usage example, disable drop down if there is only one item in list, that one value is still posted to server with correct client id:
#Html.DropDownListFor(m => m.SomeValue, Model.SomeList, new { #class = "some-class" }, Model.SomeList > 1)
You can do:
var dropDownEditDisable = new { disabled = "disabled" };
var dropDownEditEnable = new { };
object enableOrDisable = Model.CanEdit ?
(object)dropDownEditEnable : (object)dropDownEditDisable;
#Html.DropDownList("Quantity", new SelectList(...), enableOrDisable)
Html.DropDownListFor() can be long, so doing that, there is no need to repeat it.
I don't know if ASP.NET offers a more succinct special-case approach, but presumably you could do:
<%= Html.DropDownList("Quantity", new SelectList(...), Model.CanEdit? new{#class="quantity"} : new{#class="quantity", #disabled:"disabled"}) %>

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.

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