Can someone be kind and explain why I should write strings in this way:
public static MvcHtmlString Render(this HtmlHelper htmlHelper, MenuItem menuItem)
{
if (!menuItem.IsVisible)
return MvcHtmlString.Empty;
var li = new TagBuilder("li");
var a = new TagBuilder("a");
a.MergeAttribute("href", menuItem.Uri);
a.SetInnerText(menuItem.Title);
li.InnerHtml = a.ToString();
return MvcHtmlString.Create(li.ToString());
}
When this is so much cleaner:
public static MvcHtmlString Render(this HtmlHelper htmlHelper, MenuItem item)
{
if (!item.IsVisible)
return MvcHtmlString.Empty;
var str = string.Format(#"<li>{1}</li>", item.Uri, item.Title);
return MvcHtmlString.Create(str.ToString());
}
What are the benefits?
There isn't a great reason. I would say speed, since TagBuilder is essentially a specialized StringBuilder, which is known to be faster than string.Format, but I'd need to see some benchmarks, since it seems like the whole HTML structure thing might slow things down.
One example of why you might want to use it is that it makes conditional logic easier. I think for example something like
var theClass = item.Title.Length > 5 ? " class=\"Long\"" : "";
var str = string.Format(#"<li><a href=""{0}""{1}>{2}</a></li>", item.Uri, theClass, item.Title);
is not very clear or clean, whereas
if (item.Title.Length > 5)
{
a.AddCssClass("Long");
}
// And then all that other stuff.
is a pretty nice solution.
TagBuilder just ensures your HTML is well formed. I tend to use tagbuilder for anything I would use multiple lines for in HTML like an span within a href within an div for the extra protection from typos.
Related
I've looked into this question and found questions such as:
SO-Link1
SO-Link2
So the problem seems rather common - but i really don't like the solution.
Question:
I've started digging into sources but couldn't find a convenient solution yet. Is there a built in solution to this by now using attributes?
Having to use dynamic parameters in place instead of using attributes on the model is very inconvenient.
Guess i'll see what i can achieve by modifying the ModelBinder in the meantime - there has to be some way i guess.
Update:
Why do i want to do this?
I want to reduce network traffic because properties in code may be long + nested. Imagine 200 Checkboxes x 40 bytes generated names.
I can already make my modelbinder work with aliases - however in order to fully automate it, i need the TextBoxFor etc methods to use alias names instead of the actual property names.
Personally I don't think you should be totally concerned with the size of the the name attribute's values. As long as you're using compression through something like IIS then you aren't going to be saving that much.
You can however, achieve what you're after by creating custom HTML helpers which will create the HTML markup you desire.
Example Model
public class UserModel
{
public string FullName { get; set; }
}
Helper
public static MvcHtmlString CustomTextBoxFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string name)
{
var fieldName = ExpressionHelper.GetExpressionText(expression);
//
// Pass in alias or call method to get alias here
//
var fullBindingName = String.IsNullOrWhiteSpace(name) ? html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName) : name;
var fieldId = TagBuilder.CreateSanitizedId(fullBindingName);
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var value = metadata.Model;
var tag = new TagBuilder("input");
tag.Attributes.Add("name", fullBindingName);
tag.Attributes.Add("id", fieldId);
tag.Attributes.Add("type", "text");
tag.Attributes.Add("value", value == null ? "" : value.ToString());
var validationAttributes = html.GetUnobtrusiveValidationAttributes(fullBindingName, metadata);
foreach (var key in validationAttributes.Keys)
{
tag.Attributes.Add(key, validationAttributes[key].ToString());
}
return new MvcHtmlString(tag.ToString(TagRenderMode.SelfClosing));
}
Call the Method in your View
#Html.TextBoxFor(x => x.FullName)
#Html.CustomTextBoxFor(x => x.FullName, "FName")
Output
<input id="FullName" type="text" value="Heymega" name="FullName">
<input id="FName" type="text" value="Heymega" name="FName">
Is there a way to do something like the following:
public static IHtmlString TableFor(this HtmlHelper helper, IEnumerable<MaterialGroup> groups, Func<HtmlHelper, MaterialGroup, int, string> tableContentsFunc)
{
return MvcHtmlString.Create("#Html.TextBoxFor(x => this.Model.Something)");
}
Obviously this is a trivial example, but when ever I try something of the sort it renders the Helpers i.e. "#Html.TextBoxFor(x => this.Model.Something)" as text on the page instead of processing them as helpers.
Is there a way to achieve this?
public static MvcHtmlString TableFor(this HtmlHelper<IEnumerable<MaterialGroup>> helper, IEnumerable<MaterialGroup> groups, Func<HtmlHelper<MaterialGroup>, MaterialGroup, int, string> tableContentsFunc)
{
String html = "<table class='materials joists'>";
String endHtml = "</table>";
for (int i = 0; i < groups.Count(); ++i)
{
HtmlHelper<MaterialGroup> groupHelper = new HtmlHelper<MaterialGroup>(helper.ViewContext, helper.ViewDataContainer); // Crashes here with cannot convert IEnumerable<MaterialGroup> to MaterialGroup.
html += TbodyFor(groupHelper , groups.ElementAt(i), i);
html += tableContentsFunc(groupHelper , groups.ElementAt(i), i);
}
return MvcHtmlString.Create(html + endHtml);
}
public static string TbodyForJoists(this HtmlHelper<MaterialGroup> helper, MaterialGroup group, int index)
{
string html = string.Empty;
MvcHtmlString markTextbox = InputExtensions.TextBoxFor<MaterialGroup, String>(helper, x => group.Joists.ElementAt(i).Mark, new { Name = "MaterialGroups[" + index + "].Joists[" + i + "].Mark", Class = "auto-size first-column" });
html += martTextbox;
.
.
.
return html;
}
When I attempt the above I get issues with the HtmlHelper<> Types.
If I leave it with just HtmlHelper I get an error telling me to explicity state since it doesn't know what I doing with it. If I explicitly state is have conversion? issues I guess you could say.
How can I simply just use the TextBoxFor in this way?
Because that is very literally what you are telling it to do. The output of the helper itself is not processed by Razor. Whatever you return is what's going on the page.
However, you could always do:
MvcHtmlString textBox = Html.TextBoxFor(expression);
And, you'd have to feed the helper the expression to use. It perhaps would be easier in this situation to use Html.TextBox instead, but then you're going to have to do more work to try to figure out the right names and such for the fields.
It's going to be far easier and less convoluted to just use editor templates for this type of thing.
I'm writing an HTML Helper for an editor. The idea is to get properties from the Model with attributes AutoGenerateField and build a table, each line of which contains a name of a field (also from the attribute) and a TextBox or a CheckBox containing actual value of the field.
I have a problem with HTMLHelper. Since I send the whole model to the helper and not one value, I cannot use methods such as TextBoxFor, as they need parameter, such as
"Expression<Func<TModel, TValue>> expression".
I'm using reflexion and I tried to send the property instead, still VisualStudio considers this as incorrect usage.
Below is simplified method for my HtmlHelper:
public static MvcHtmlString GenerateEditor<TModel>(this HtmlHelper<TModel> htmlHelper)
{
var model = htmlHelper.ViewData.Model;
var result = String.Empty;
//generating container, etc ...
foreach (var property in model.GetType().GetProperties())
{
var attr = property.GetCustomAttributes(typeof (DisplayAttribute), true).FirstOrDefault();
if (attr == null) continue;
var autoGenerate = ((DisplayAttribute)attr).AutoGenerateField;
if(autoGenerate)
{
//here I'm building the html string
//My problem is in the line below:
var r = htmlHelper.TextBoxFor(property);
}
}
return MvcHtmlString.Create(result);
}
Any ideas?
How about just using the non-lambda overloads. : InputExtensions.TextBox()
if(autoGenerate)
{
//here I'm building the html string
//My problem is in the line below:
var r = htmlHelper.TextBox(property.Name);
}
//not sure what you do with r from here...
If I'm not mistaken the name attribute of the form element is set to the property name even when you use the lambda version of the function so this should do the same thing.
I will try and verify what the lambda function does, you might be able to do the same since you have the TModel with you.
Update
From a quick look of things inside the source code of InputExtensions.cs, TextBoxFor calls eventually calls InputHelper() which eventually calls ExpressionHelper.GetExpressionText(LambdaExpression expression) inside ExpressionHelper.cs which from the cursory looks of things gets the member.Name for the name html attribute on the input element.
I can't quite verify it right now because I'm not on windows but I think the non-lambda function should suit your need. Do tell me how it goes?
I have an extension method that needs to return an HtmlString. The method has a loop which will build the HtmlString, however the HtmlString object has no Append method and does not allow concatenation using the + operator so I am not sure how I would build the HtmlString.
I'd like to use the StringBuilder but it doesn't have a ToHtmlString method...
Any solutions or patterns for this?
Why not just build the string in a stringbuilder and then
return MvcHtmlString.Create(sb.ToString());
I think you want to use TagBuilder. See also Using the TagBuilder Class to Build HTML Helpers like this:
// Create tag builder
var builder = new TagBuilder("img");
// Create valid id
builder.GenerateId(id);
// Add attributes
builder.MergeAttribute("src", url);
builder.MergeAttribute("alt", alternateText);
builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
// Render tag
return builder.ToString(TagRenderMode.SelfClosing);
You could have a look at the fubu spin-off for creating HTML Tags. Here is a SO question that talks a bit about its usage.
You could write the ToHtmlString() method yourself as a extension method on StringBuilder.
There are several solutions to this including using the TagBuilder, but using Html.Raw() worked very well for me:
public static IHtmlString HtmlMethod(this HtmlHelper htmlhelper, Object object)
{
var sb = new StringBuilder();
foreach (var item in object)
{
sb.Append(object.outputStr)
}
return htmlHelper.Raw(sb.ToString());
}
I'm writing an ASP.NET MVC Html Helper which basically takes 2 HTML Helpers that return IHtmlStrings and combines them together and also returns them as an IHtmlString like so:
//this doesn't work
public static IHtmlString CompositeHelper(this HtmlHelper helper, string data)
{
//GetOutput returns an IHtmlString
var output1 = new Component1(data).GetOutput();
var output2 = new Component2(data).GetOutput();
return output1 + output2
}
Now I know this isn't going to work because IHtmlString is an interface with an implementation that is a complex type, but if I go
return output1.ToHtmlString() + output2.ToHtmlString()
I just get a normal string which gets HtmlEncoded when I return it from my HtmlHelper.
So my question is, how can I take the output form two IHtmlStrings and combine them into a single IHtmlString?
Like this:
return new HtmlString(output1.ToString() + output2.ToString());