RouteValueDictionary to HtmlAttributes - c#

I know I can add html attributes to my tag by doing something like:
var htmlAttributes = new RouteValueDictionary { { "data-foo", "bar" } };
var tag = new TagBuilder("div");
tag.MergeAttributes(htmlAttributes );
#tag
Output:
<div data-foo="bar"></div>
I wonder if I can add attributes in a similar way by using markup instead of a tag builder. Maybe something like:
var htmlAttributes = new RouteValueDictionary { { "data-foo", "bar" } };
<div #htmlAttributes.ToHtmlAttributes() ></div>
Expected output:
<div data-foo="bar"></div>
Clearly, I wouldn't be able to handle merge conflicts this way. However, I think it's worth it because the second way is so much more readable.

You can write your own extension method:
namespace SomeNamespace
{
public static class RouteValueDictionaryExtensions
{
public static IHtmlString ToHtmlAttributes(this RouteValueDictionary dictionary)
{
var sb = new StringBuilder();
foreach (var kvp in dictionary)
{
sb.Append(string.Format("{0}=\"{1}\" ", kvp.Key, kvp.Value));
}
return new HtmlString(sb.ToString());
}
}
}
which will be used exactly how you've described:
#using SomeNamespace
#{
var htmlAttributes = new RouteValueDictionary
{
{"data-foo", "bar"},
{"data-bar", "foo"}
};
}
<div #htmlAttributes.ToHtmlAttributes()> </div>
the result is:
<div data-foo="bar" data-bar="foo" > </div>
Edit:
If you want to use TagBuilder, you can alternatively write another extension which uses it internally:
public static IHtmlString Tag(this HtmlHelper helper,
RouteValueDictionary dictionary,
string tagName)
{
var tag = new TagBuilder(tagName);
tag.MergeAttributes(dictionary);
return new HtmlString(tag.ToString());
}
and the usage shown below below gives the same output html as previously:
#Html.Tag(htmlAttributes, "div")

Related

Passing IEnumerable property in RouteValues of ActionLink

Imagine an object defined like :
public class MyViewModel{
public List<string> MyList { get; set; }
}
In my view, i have this Action link :
#Ajax.ActionLink("<", "Index", new MyViewModel() { MyList = new List<string>() {"foo", "bar"}}, new AjaxOptions())
The html result of the ActionLink will be :
<a class="btn btn-default" data-ajax="true" href="/Index?MyList=System.Collections.Generic.List%601%5BSystem.String%5D"><</a>
My question is, how get this result rather :
<a class="btn btn-default" data-ajax="true" href="/Index?MyList=foo&MyList=bar"><</a>
You can try string.Join. Something like this
#Ajax.ActionLink(
"Your text", -- <
"ActionName", -- Index
new
{
MyList =string.Join(",", new List<string>() {"foo", "bar"}),
otherPropertiesIfyouwant = YourValue
}, -- rounteValues
new AjaxOptions { UpdateTargetId = "..." }, -- Your Ajax option --optional
new { #id = "back" } -- Your html attribute - optional
)
You cannot use #Html.ActionLink() to generate route values for a collection. Internally the method (and all the MVC methods that generate urls) uses the .ToString() method of the property to generate the route/query string value (hence your MyList=System.Collections.Generic.List%601%5BSystem.String%5D" result).
The method does not perform recursion on complex properties or collections for good reason - apart from the ugly query string, you could easily exceed the query string limit and throw an exception.
Its not clear why you want to do this (the normal way is to pass an the ID of the object, and then get the data again in the GET method based on the ID), but you can so this by creating a RouteValueDictionary with indexed property names, and use it in your#Ajax.ActionLink() method.
In the view
#{
var rvd = new RouteValueDictionary();
rvd.Add("MyList[0]", "foo");
rvd.Add("MyList[1]", "bar");
}
#Ajax.ActionLink("<", "Index", rvd, new AjaxOptions())
Which will make a GET to
public ActionResult Index(MyViewModel model)
However you must also make MyList a property (the DefaultModelBinder does not bind fields)
public class MyViewModel{
public List<string> MyList { get; set; } // add getter/setter
}
and then the value of model.MyList in the POST method will be ["foo", "bar"].
With Stephen's anwser, i have develop a helper extension method to do this.
Be careful of the URL query string limit : if the collection has too many values, the URL can be greater than 255 characters and throw an exception.
public static class AjaxHelperExtensions
{
public static MvcHtmlString ActionLinkUsingCollection(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 AjaxExtensions.ActionLink(ajaxHelper, linkText, actionName, rv, ajaxOptions, htmlAttributes);
}
}

My HtmlHelper is losing Html Encoding

I am trying to use a custom HtmlHelper in my view to display a link, but it's getting HTML Encoded.
In my view, I'm calling my helper like this:
<td>
#Html.Urls(item.TaskUrl)
</td>
And my helper looks like this:
public static class MkpHelpers
{
public static string Urls(this HtmlHelper helper, string value)
{
var items = value.Split(';'); // use your delimiter
var sb = new StringBuilder();
foreach (var i in items)
{
var linkBuilder = new TagBuilder("a");
linkBuilder.MergeAttribute("href",i);
linkBuilder.InnerHtml = i;
sb.Append(linkBuilder.ToString());
}
return sb.ToString();
}
}
Rendered out, it looks like this:
<a href="http://localhost:63595/project/reviewresource/99ddb0d8-238a-e511-8172-00215e466552">
http://localhost:63595/project/reviewresource/99ddb0d8-238a-e511-8172-00215e466552
</a>
I'm guessing I'm doing something wrong that should be pretty simple/obvious.
Be careful here of Injection attacks. That being said, you need to return an HtmlString:
public static class MkpHelpers
{
public static HtmlString Urls(this HtmlHelper helper, string value)
{
var items = value.Split(';'); // use your delimiter
var sb = new StringBuilder();
foreach (var i in items)
{
var linkBuilder = new TagBuilder("a");
linkBuilder.MergeAttribute("href",i);
linkBuilder.InnerHtml = i;
sb.Append(linkBuilder.ToString());
}
return new HtmlString(sb.ToString());
}
}
HtmlString derrives from IHtmlString:
Represents an HTML-encoded string that should not be encoded again.

How can I capture the #Html.DropDownListFor selected value?

I am using MVC5, Razor, Entity Framework, C#. I am trying to pass a value of a dorpdown list using a link.
my model is
public class TestVM
{
public string TheID { get; set; }
}
I am loading an enum into a IEnumerable<SelectListItem>.
My enum is
public enum DiscountENUM
{
SaleCustomer,
SaleCustomerCategory,
SaleProduct,
SaleProductCategory,
SaleCustomerAndProduct,
SaleCustomerAndProductCategory,
SaleCustomerCategoryAndProductCategory,
PurchaseVendor,
PurchaseVendorAndProduct,
PurchaseVendorAndProductCategory,
PurchaseProduct,
PurchaseProductCategory,
Unknown
}
I am using the index method of the home controller
public ActionResult Index()
{
ViewBag.ListOfDiscounts = SelectListDiscountENUM();
TestVM d = new TestVM();
return View(d);
}
Where I load the ListOfDiscounts using:
private IEnumerable<SelectListItem> SelectListDiscountENUM()
{
List<SelectListItem> selectList = new List<SelectListItem>();
var listOfEnumValues = Enum.GetValues(typeof(DiscountENUM));
if (listOfEnumValues != null)
if (listOfEnumValues.Length > 0)
{
foreach (var item in listOfEnumValues)
{
SelectListItem sVM = new SelectListItem();
sVM.Value = item.ToString();
sVM.Text = Enum.GetName(typeof(DiscountENUM), item).ToString();
selectList.Add(sVM);
}
}
return selectList.OrderBy(x => x.Text).AsEnumerable();
}
My create method which is called from the view is
public ActionResult Create(TestVM d, string TheID)
{
return View();
}
My Index view is
#model ModelsClassLibrary.Models.DiscountNS.TestVM
<div>#Html.ActionLink("Create New", "Create", new { TheID = Model.TheID})</div>
<div>
#Html.DropDownListFor(x => x.TheID, #ViewBag.ListOfDiscounts as IEnumerable<SelectListItem>, "--- Select Discount Type ---", new { #class = "form-control" })
</div>
The problem is in the following line in the View
<div>#Html.ActionLink("Create New", "Create", new { TheID = Model.TheID })</div>
I have tried adding a model with the name of the field as "TheID"... no luck. Also, added a string field in the parameter, no luck. I looked at the FormControl object, and there was nothing in it either! I suspect something has to be added at the Route level in the helper, but I don't know what.
Model.TheID is always null. Even when I select an item in the DropDownListFor.
Does anyone have an idea how I can capture the select value of the DropDownListFor and send it into the Html.ActionLink TheID?

How to set a textbox readonly property true or false

I need your help in creating a textbox readonly property true or false based on a condition.
I tried however was unsuccessful.
Below is my sample code:
string property= "";
if(x=true)
{
property="true"
}
#Html.TextBoxFor(model => model.Name, new { #readonly = property})
My question is: Even though the condition is false I am unable to write or edit the textbox?
This is because the readonly attribute in HTML is designed so that it's mere presence indicates a readonly textbox.
I believe that the values true|false are completely ignored by the attribute and infact the recomended value is readonly="readonly".
To re-enable the textbox you'll need to get rid of the readonly attribute altogether.
Given that the htmlAttributes property of TextBoxFor is an IDictionary, you could simply build the object based on your requirements.
IDictionary customHTMLAttributes = new Dictionary<string, object>();
if(x == true)
// Notice here that i'm using == not =.
// This is because I'm testing the value of x, not setting the value of x.
// You could also simplfy this with if(x).
{
customHTMLAttributes.Add("readonly","readonly");
}
#Html.TextBoxFor(model => model.Name, customHTMLAttributes)
A shorthand way to add the custom attrbute could be:
var customHTMLAttributes = (x)? new Dictionary<string,object>{{"readonly","readonly"}}
: null;
or simply:
#Html.TextBoxFor(model => model.Name, (x)? new {"readonly","readonly"} : null);
I achieved it using some extension methods
public static MvcHtmlString IsDisabled(this MvcHtmlString htmlString, bool disabled)
{
string rawstring = htmlString.ToString();
if (disabled)
{
rawstring = rawstring.Insert(rawstring.Length - 2, "disabled=\"disabled\"");
}
return new MvcHtmlString(rawstring);
}
public static MvcHtmlString IsReadonly(this MvcHtmlString htmlString, bool #readonly)
{
string rawstring = htmlString.ToString();
if (#readonly)
{
rawstring = rawstring.Insert(rawstring.Length - 2, "readonly=\"readonly\"");
}
return new MvcHtmlString(rawstring);
}
and then....
#Html.TextBoxFor(model => model.Name, new { #class= "someclass"}).IsReadonly(x)
You probably need to refactor your code to be something along the lines of
if(x)
{
#Html.TextBoxFor(model => model.Name, new { #readonly = "readonly"})
}
else
{
#Html.TextBoxFor(model => model.Name)
}

MVC3 ActionLink with images (but without MvcFutures)?

I was wondering if anyone knows if it possible to use any of the "out of the box" ASP.NET MVC3 helpers to generate a "link button"...I currently use following:
<a class="button" title="My Action" href="#Url.Action("MyAction", "MyController", new { id = item.Id })">
<img alt="My Action" src="#Url.Content("~/Content/Images/MyLinkImage.png")" />
</a>
I am trying to avoid using MvcFutures, but even if I was able to use them, I don't think there is a extension method it there that will accomplish this either. (I believe solution in this case would be to roll custom helper as seen here)
Finally, this post also has a good idea to handle this via CSS, but that is not what I am asking...
I am using the following to generate action links:
using System;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
using Fasterflect;
namespace StackOverflow.Mvc.Extensions
{
public static class HtmlExtensions
{
#region ActionImage
// href image link
public static string ActionImage( this HtmlHelper helper, string href, string linkText, object htmlAttributes,
string alternateText, string imageSrc, object imageAttributes )
{
var sb = new StringBuilder();
const string format = "<a href=\"{0}\"{1}>{2}</a>";
string image = helper.Image( imageSrc, alternateText, imageAttributes ).ToString();
string content = string.IsNullOrWhiteSpace( linkText ) ? image : image + linkText;
sb.AppendFormat( format, href, GetAttributeString( htmlAttributes ), content );
return sb.ToString();
}
// controller/action image link
public static string ActionImage( this HtmlHelper helper, string controller, string action, string linkText, object htmlAttributes,
string alternateText, string imageSrc, object imageAttributes )
{
bool isDefaultAction = string.IsNullOrEmpty( action ) || action == "Index";
string href = "/" + (controller ?? "Home") + (isDefaultAction ? string.Empty : "/" + action);
return ActionImage( helper, href, linkText, htmlAttributes, alternateText, imageSrc, imageAttributes );
}
// T4MVC ActionResult image link
public static string ActionImage( this HtmlHelper helper, ActionResult actionResult, string linkText, object htmlAttributes,
string alternateText, string imageSrc, object imageAttributes )
{
var controller = (string) actionResult.GetPropertyValue( "Controller" );
var action = (string) actionResult.GetPropertyValue( "Action" );
return ActionImage( helper, controller, action, linkText, htmlAttributes, alternateText, imageSrc, imageAttributes );
}
#endregion
#region Helpers
private static string GetAttributeString( object htmlAttributes )
{
if( htmlAttributes == null )
{
return string.Empty;
}
const string format = " {0}=\"{1}\"";
var sb = new StringBuilder();
htmlAttributes.GetType().Properties().ForEach( p => sb.AppendFormat( format, p.Name, p.Get( htmlAttributes ) ) );
return sb.ToString();
}
#endregion
}
}
Note that the GetAttributeString method relies on the Fasterflect library to make reflection tasks easier, but you can replace that with regular reflection if you prefer not to take the additional dependency.
The Image helper extension used to be part of MvcContrib but appears to have been removed, most likely because the functionality is now built in to MVC. Regardless, I've included it below for completeness:
public static class ImageExtensions {
public static MvcHtmlString Image(this HtmlHelper helper, string imageRelativeUrl, string alt, object htmlAttributes) {
return Image(helper, imageRelativeUrl, alt, new RouteValueDictionary(htmlAttributes));
}
public static MvcHtmlString Image(this HtmlHelper helper, string imageRelativeUrl, string alt, IDictionary<string, object> htmlAttributes) {
if (String.IsNullOrEmpty(imageRelativeUrl)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "imageRelativeUrl");
}
string imageUrl = UrlHelper.GenerateContentUrl(imageRelativeUrl, helper.ViewContext.HttpContext);
return MvcHtmlString.Create(Image(imageUrl, alt, htmlAttributes).ToString(TagRenderMode.SelfClosing));
}
public static TagBuilder Image(string imageUrl, string alt, IDictionary<string, object> htmlAttributes) {
if (String.IsNullOrEmpty(imageUrl)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "imageUrl");
}
TagBuilder imageTag = new TagBuilder("img");
if (!String.IsNullOrEmpty(imageUrl)) {
imageTag.MergeAttribute("src", imageUrl);
}
if (!String.IsNullOrEmpty(alt)) {
imageTag.MergeAttribute("alt", alt);
}
imageTag.MergeAttributes(htmlAttributes, true);
if (imageTag.Attributes.ContainsKey("alt") && !imageTag.Attributes.ContainsKey("title")) {
imageTag.MergeAttribute("title", (imageTag.Attributes["alt"] ?? "").ToString());
}
return imageTag;
}
}
The snippet you have looks quite good. You should wrap it in a general-purpose html helper and call it a day. I'm sure there are other more interesting aspects to your application than nit picking about UI helpers :)
Check at the bottom of this blog post for an example with HTML extension methods from Stephen Walther

Categories

Resources