0
votes

I am having trouble wrapping my head around creating a custom html helper that utilizes built-in helpers for rendering specific sections. (Think composite helper.) In addition, the helper is only useful for a specific class.

Given the following class excerpt

public class Notification {
   [Display("Notice")]
   public string Content { get; set; }
   public DateTime Created { get; set; }
   public bool RequiresAcknowledgement { get; set; }
}

I would like to render the following sample output

<div class="notification">
   <!-- Notice that the DateTime property only displays the Date portion (i.e. Created.Date) -->
   <div class="notification-timestamp">Monday, October 07, 2014</div>
   <div class="notification-content">
      <h1>Notice</h1>
      This is a sample notice
   </div>
   <!-- This div is optional and only rendered if RequiresAcknowledgement == true -->
   <div class="notification-acknowledgement">
      <input type="checkbox">By clicking this you acknowledge that you have read the notice
   </div>
</div>

I figured that my extension method would have a signature similar to

public static System.Web.Mvc.MvcHtmlString DisplayNotificationFor<TModel, TValue>(this System.Web.Mvc.HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TValue>> expression)

but I am not sure if I need TModel and/or TValue since I know that the method is only good for Notification objects.

That signature also lead to confusion when I tried to do the following

// is this the correct way to get my model??
var valueGetter = expression.Compile();
var model = valueGetter(helper.ViewData.Model) as Notification;

var timestamp = new System.Web.Mvc.TagBuilder("div");
timestamp.AddCssClass("notification-timestamp");

timestamp.InnerHtml = helper.DisplayFor(?????)      // What is the best way to output model.Created.Date?

// how about
// does not work/compile but shown as possible approach
timestamp.InnerHtml = new System.Web.Mvc.Html.DisplayExtensions.DisplayFor<Notification, TValue>(helper, n => n.Created.Date);

I understand that the sample output shown above is mostly just rendering read-only text, and thus I could just render the properties of the Notification object directly instead of using built-in helper methods, but the question I am asking can be applied to editable forms too. For example, the form that allows the creation and/or modification of notices.

The reason why I would like to re-use the existing helpers is that they already have support for rendering different types of inputs depending on the data type (string vs bool vs DateTime vs email), they support validation attributes, they support Display attribute for labels, etc.

Of course I would want to extend my simple example to allow for custom attributes to be passed in like specific id's or additional css classes.

Am I way off base with how I should be using/creating custom html helpers?

1
If its for a specific class, then you can just pass the instance to a parameter as in public static MvcHtmlString DisplayNotificationFor<TModel, TValue>(HtmlHelper<TModel> helper, Notification notification) but why not just create a DisplayTemplate for Notification.cs? - user3559349
@StephenMuecke -- I cannot use a DisplayTemplate because I am trying to create a library project that can be used across projects (i.e. multiple MVC applications). As far as I know, Display/Editor Templates (like partial views) are only available within the MVC app that defines them. Am I wrong? - Jason
Not sure that copying the template to the Views/Shared/DisplayTemplates is more difficult than adding a reference to your library and updating you web.config files, but I'll post an answer using a html helper shortly - user3559349
If the library just contained the helper extension then yes...the effort would be the same as creating a Display template and then copying it from web project to web project. However, the library also contains the classes that contain the Notification object along with the interfaces and service implementations for reading/writing the data to its source. In addition, the library is also turned into a NuGet package that is made available on an inhouse NuGet repository. For all those reasons, the helper extension is the better choice. - Jason

1 Answers

0
votes

Turns out that restricting the class for the expression is easier than I thought.

public static System.Web.Mvc.MvcHtmlString DisplayNotificationFor<TModel, TValue>(this System.Web.Mvc.HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TValue>> expression)
   where TValue: Notification {

The key is the generics constraint appended to the end.

That allows me to do

@model Common.Notifications.Notification

@Html.DisplayNotificationFor(model => model)

// or

@model SomeViewModel

@Html.DisplayNotificationFor(model => model.ImportantNotification)

whereas

@Html.DisplayNotificationFor(model => model.SomeStringProperty)

shows as design-time error.