I have a simple tag builder that looks like this:
public static MvcHtmlString Tag(this HtmlHelper helper, string tag, string content)
{
var tagBuilder = new TagBuilder(tag){InnerHtml = content};
return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.NormalTag));
}
And, I can use it like this:
#Html.Tag("em", Model.Title)
which produces:
<em>The Title</em>
How can this be written to use a Fluent Syntax so it's use would look like this:
#Html.Tag("em").Content(Model.Title)
You have to define a builder interface and implementation. I hope my example can provide some guidance:
public static class MyHtmlExtensions
{
public static IMyTagBuilder Tag(this HtmlHelper helper, string tag)
{
return new MyTagBuilder(tag);
}
}
Then you define your builder interface and implementation:
public interface IMyTagBuilder : IHtmlString
{
IHtmlString Content(string content);
}
public class MyTagBuilder : IMyTagBuilder
{
private readonly TagBuilder _tagBuilder;
public MyTagBuilder(string tag)
{
_tagBuilder = new TagBuilder(tag);
}
public IHtmlString Content(string content)
{
_tagBuilder.InnerHtml = content;
return this;
}
public string ToHtmlString()
{
return _tagBuilder.ToString(TagRenderMode.NormalTag);
}
}
Since IMyTagBuilder implements IHtmlString, it can be used either with or without calling .Content() afterwards.
A great trick to use when implementing fluent interfaces it to use a IFluentInterface to hide object members (ToString, Equals, GetHashCode and GetType) from IntelliSense, it removes some noise.
EDIT: A great resource for building fluent APIs is Daniel Cazzulino's screencast from building Funq here
Related
I created custom HtmlHelper like this:
public static class HtmlHelper
{
public static MvcHtmlString CreateHr(this HtmlHelper helper)
{
return MvcHtmlString.Create("<div class='line'></div>");
}
}
but when I build project I get this Error:
static types cannot be used as parameters
I searched Google, but I can't find similar question.
How can I do this?
Call your class something other than HtmlHelper, like HtmlHelperExtensions.
Not only are you not allowed to have a static class as a method parameter type (because it would make no sense), you would be shadowing the very class you're trying to extend.
Do not name your helper HtmlHelper because this means you try to shadow base HtmlHelper from MVC framework. Instead try:
using System;
using System.Web.Mvc;
namespace MvcApplication1.Helpers
{
public static class HrExtensions
{
public static string CreateHr(this HtmlHelper helper)
{
return MvcHtmlString.Create("<div class='line'></div>");
}
}
}
more details here: http://www.asp.net/mvc/overview/older-versions-1/views/creating-custom-html-helpers-cs
Is it possible to automatically sort all dropdownLists in ASP.net MVC project?
I don't want to go one by one and sort them explicitly. Is there a way to do this automatically on all dropdownLists in the project?
Create a HtmlHelperExtensions class that has an extension method that does what you want. Something like this:
public static class HtmlHelperExtensions
{
public static MvcHtmlString SortedDropDownList(this HtmlHelper htmlHelper, string name, IEnumerable<SelectListItem> selectList)
{
return htmlHelper.DropDownList(name, selectList.OrderBy(x => x.Text));
}
}
Whatever namespace you stick the helper in, make sure it's added to configuration\system.web.webPages.razor\pages\namespaces in the web.config found in your \Views folder so that you can use it in your view.
You could create HtmlHelperExtension class as friism sugested, or you could create extension method on SelectList like this:
public static class SortedList
{
public static void SortList(this SelectList selectList, SortDirection direction)
{
//sort content of selectList
}
}
and then use it like this:
var sel = new SelectList(new List<string> {"john", "mary", "peter"});
sel.SortList(SortDirection.Ascending);
Either way, you are going to change every line of code where you want to sort those lists.
I have the following classes:
public class Note
{
public string Text { get; set; }
public RowInfo RowInfo { get; set; }
}
public class RowInfo
{
[DisplayName("Created")]
public DateTime Created { get; set; }
[DisplayName("Modified")]
public DateTime Modified { get; set; }
}
In my view I have the following which creates HTML with the correct name and value:
Html.HiddenFor(model => model.Note.Created)
Now what I am trying to do is to create an extension method that will include the above and that I can call in each view. I have tried doing the following. I think I am on the right track but I don't know how to do the equivalent of "model => model.Note.Created" Can someone give me some advice on how I can do this and what I would need to replace the text inside the parenthesis with. I don't have a model but I can do this some other way so the hidden field will go look at my class to get the correct DisplayName just like it does above?
namespace ST.WebUx.Helpers.Html
{
using System.Web.Mvc;
using System.Web.Mvc.Html
using System.Linq;
public static class StatusExtensions
{
public static MvcHtmlString StatusBox(this HtmlHelper helper, RowInfo RowInfo )
{
return new MvcHtmlString(
"Some things here ... " +
System.Web.Mvc.Html.InputExtensions.Hidden( for created field ) +
System.Web.Mvc.Html.InputExtensions.Hidden( for modified field ) );
}
}
You could write a strongly typed helper taking a λ-expression:
public static class StatusExtensions
{
public static IHtmlString StatusBox<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> ex
)
{
return new HtmlString(
"Some things here ... " +
helper.HiddenFor(ex));
}
}
and then:
#Html.StatusBox(model => model.RowInfo.Created)
UPDATE:
As requested in the comments section here's a revised version of the helper:
public static class StatusExtensions
{
public static IHtmlString StatusBox<TModel>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, RowInfo>> ex
)
{
var createdEx =
Expression.Lambda<Func<TModel, DateTime>>(
Expression.Property(ex.Body, "Created"),
ex.Parameters
);
var modifiedEx =
Expression.Lambda<Func<TModel, DateTime>>(
Expression.Property(ex.Body, "Modified"),
ex.Parameters
);
return new HtmlString(
"Some things here ..." +
helper.HiddenFor(createdEx) +
helper.HiddenFor(modifiedEx)
);
}
}
and then:
#Html.StatusBox(model => model.RowInfo)
Needless to say that custom HTML helpers should be used to generate small portions of HTML. Complexity could grow quickly and in this case I would recommend you using an editor template for the RowInfo type.
I'm trying to create a templated control with Asp.Net MVC. By templated control, I mean a control that accepts markup as input like so:
<% Html.PanelWithHeader()
.HeaderTitle("My Header")
.Content(() =>
{ %>
<!-- ul used for no particular reason -->
<ul>
<li>A sample</li>
<li>A second item</li>
</ul>
<% }).Render(); %>
Note: Yes, this is very similar to how Telerik creates its MVC controls, I like the syntax.
Here's my PanelWithHeader code:
// Extend the HtmlHelper
public static PanelWithHeaderControl PanelWithHeader(this HtmlHelper helper)
{
return new PanelWithHeaderControl();
}
public class PanelWithHeaderControl
{
private string headerTitle;
private Action getContentTemplateHandler;
public PanelWithHeaderControl HeaderTitle(string headerTitle)
{
this.headerTitle = headerTitle;
return this;
}
public PanelWithHeaderControl Content(Action getContentTemplateHandler)
{
this.getContentTemplateHandler = getContentTemplateHandler;
return this;
}
public void Render()
{
// display headerTitle as <div class="header">headerTitle</div>
getContentTemplateHandler();
}
}
This displays the ul, but I have no idea how to display custom code within my Render method.
I have tried using the HtmlHelper with no success. I have also tried overriding the ToString method to be able to use the <%=Html.PanelWithHeader()... syntax, but I kept having syntax errors.
How can I do this?
public void Render()
{
Response.Write(getContentTemplateHandler());
}
It turns out that the Telerik MVC extensions are open-source and available at CodePlex so I took a quick look at the source code.
They create an HtmlTextWriter from the ViewContext of the HtmlHelper instance. When they write to it, it writes to the page.
The code becomes:
// Extend the HtmlHelper
public static PanelWithHeaderControl PanelWithHeader(this HtmlHelper helper)
{
HtmlTextWriter writer = helper.ViewContext.HttpContext.Request.Browser.CreateHtmlTextWriter(helper.ViewContext.HttpContext.Response.Output);
return new PanelWithHeaderControl(writer);
}
public class PanelWithHeaderControl
{
private HtmlTextWriter writer;
private string headerTitle;
private Action getContentTemplateHandler;
public PanelWithHeaderControl(HtmlTextWriter writer)
{
this.writer = writer;
}
public PanelWithHeaderControl HeaderTitle(string headerTitle)
{
this.headerTitle = headerTitle;
return this;
}
public PanelWithHeaderControl Content(Action getContentTemplateHandler)
{
this.getContentTemplateHandler = getContentTemplateHandler;
return this;
}
public void Render()
{
writer.Write("<div class=\"panel-with-header\"><div class=\"header\">" + headerTitle + "</div><div class=\"content-template\">");
getContentTemplateHandler();
writer.Write("</div></div>");
}
}
*I know, the code is a mess
You might want to do something like Html.BeginPanel() / Html.EndPanel(), similar to how forms are created with Html.BeginForm() / Html.EndForm(). This way you can wrap the contained content rather than need to pass it as a parameter.
I am writing my own HtmlHelper extenstion for ASP.NET MVC:
public static string CreateDialogLink (this HtmlHelper htmlHelper, string linkText,
string contentPath)
{
// fix up content path if the user supplied a path beginning with '~'
contentPath = Url.Content(contentPath); // doesn't work (see below for why)
// create the link and return it
// .....
};
Where I am having trouble is tryin to access UrlHelper from within my HtmlHelper's definition. The problem is that the way you normally access HtmlHelper (via Html.MethodName(...) ) is via a property on the View. This isn't available to me obviously from with my own extension class.
This is the actual MVC source code for ViewMasterPage (as of Beta) - which defines Html and Url.
public class ViewMasterPage : MasterPage
{
public ViewMasterPage();
public AjaxHelper Ajax { get; }
public HtmlHelper Html { get; }
public object Model { get; }
public TempDataDictionary TempData { get; }
public UrlHelper Url { get; }
public ViewContext ViewContext { get; }
public ViewDataDictionary ViewData { get; }
public HtmlTextWriter Writer { get; }
}
I want to be able to access these properties inside an HtmlHelper.
The best I've come up with is this (insert at beginning of CreateDialogLink method)
HtmlHelper Html = new HtmlHelper(htmlHelper.ViewContext, htmlHelper.ViewDataContainer);
UrlHelper Url = new UrlHelper(htmlHelper.ViewContext.RequestContext);
Am I missing some other way to access the existing HtmlHelper and UrlHelper instances - or do i really need to create a new one? I'm sure there isn't much overhead but I'd prefer to use the preexisting ones if I can.
Before asking this question I had looked at some of the MVC source code, but evidently I missed this, which is how they do it for the Image helper.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "The return value is not a regular URL since it may contain ~/ ASP.NET-specific characters")]
public static string Image(this HtmlHelper helper, string imageRelativeUrl, string alt, IDictionary<string, object> htmlAttributes) {
if (String.IsNullOrEmpty(imageRelativeUrl)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "imageRelativeUrl");
}
UrlHelper url = new UrlHelper(helper.ViewContext);
string imageUrl = url.Content(imageRelativeUrl);
return Image(imageUrl, alt, htmlAttributes).ToString(TagRenderMode.SelfClosing);
}
Looks like instantiating a new UrlHelper is the correct approach after all. Thats good enough for me.
Update: RTM code from ASP.NET MVC v1.0 Source Code is slightly different as pointed out in the comments.
File: MVC\src\MvcFutures\Mvc\ImageExtensions.cs
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Justification = "The return value is not a regular URL since it may contain ~/ ASP.NET-specific characters")]
public static string Image(this HtmlHelper helper, string imageRelativeUrl, string alt, IDictionary<string, object> htmlAttributes) {
if (String.IsNullOrEmpty(imageRelativeUrl)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "imageRelativeUrl");
}
UrlHelper url = new UrlHelper(helper.ViewContext.RequestContext);
string imageUrl = url.Content(imageRelativeUrl);
return Image(imageUrl, alt, htmlAttributes).ToString(TagRenderMode.SelfClosing);
}
I faced a similar issue and decided that it would be easier to just call the UrlHelper in the view and pass the output to my HtmlHelper extension. In your case it would look like:
<%= Html.CreateDialogLink( "text", Url.Content( "~/...path.to.content" ) ) %>
If you want to access the extension methods on the existing HtmlHelper that is passed into your class, you should only need to import System.Web.Mvc.Html in your source code file and you will get access to them (that's where the extension classes are defined). If you want a UrlHelper, you'll need to instantiate that as the HtmlHelper you are getting doesn't have a handle for the ViewPage that it's coming from.
If you need to create a UrlHelper in a utility class you can do the following :
string url = "~/content/images/foo.jpg";
var urlHelper = new UrlHelper(new RequestContext(
new HttpContextWrapper(HttpContext.Current),
new RouteData()), RouteTable.Routes);
string absoluteUrl = urlHelper.Content(url);
This allows you to use routing or '~ expansion' away from an MVC context.
Well, you can always pass the instance of the page to the extension method. I think that is a much better way of doing this than creating new instances in your method.
You could also define this method on a class that derives from MasterPage/ViewMasterPage and then derive the page from that. This way, you have access to all the properties of the instance and don't have to pass them around.