In this recipe, we are going to take a look at how you can create your own HTML helpers by using extension methods. This will allow you to quickly add all sorts of functionality that will be right at your fingertips when knocking together forms. We will specifically build an HTML helper to display a What You See Is What You Get (WYSIWYG) editor.
In order for us to see how an HTML helper is created to display a WYSIWYG, we first need to go grab a WYSIWYG editor to be displayed. I will be working with the CKEditor—you can probably get just about any WYSIWYG to work. Go to http://ckeditor.com/download to download the CKEditor. There is also a copy in the dependencies
directory!
We're going to carry on where we left off with the first chapter, so if you've not already done so, please create the project as described or load it up from the source code.
ckeditor
, and place that folder in the root of your MVC application. ckeditor
folder and open the _samples
directory. Locate the index.html
file and browse to the file to verify that your installation is working (right-click on the index.html
file and "View in browser"). Click on one of the examples to be sure that your WYSIWYG editor is working. Site.Master
file (in the Views/Shared
directory) and drag the ckeditor/ckeditor.js
file into the head of your master page (this should create the script include we need).Views/Shared/Site.Master:
<script src="../../ckeditor/ckeditor.js" type="text/javascript">
</script>
You have probably already come across this, but the tooling support for ASP.NET MVC is a bit patchy in places. Dragging a script of style reference into your page will often result in something like../.. being prepended to the file path. The file reference will work just fine until you start messing around with routing. You're far better off making every static file reference begin at the root of the site.
Extensions
into a new directory called Helpers
. This class will hold our HTML helper extension method and some functions to make building extension methods easier for future use. Whatever the name of the folder you put the class in, if it's newly created, you'll have to reference it in Web.config
.<system.web> ... <pages> <namespaces> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Routing" /> <add namespace="Forms.Helpers"/> </namespaces> </pages> ... </system.web>
Extensions
class, we will define a new extension method called WysiwygFor
to allow us to display the CKEditor easily. For the sake of consistency, we will make this a generic method that accepts a lambda expression.Our method will return a MvcHtmlString
, which is essentially an indication that the resulting string will be HTML and should not be encoded again. MvcHtmlString
was built specifically for ASP.NET MVC, but was replaced in .NET 4 with HtmlString
. We will continue using MvcHtmlString
to ensure backward compatibility with .NET 3.5, but I'd recommend using HtmlString
in .NET 4 projects.
Models/Extensions.cs:
public static class Extensions {
public static MvcHtmlString WysiwygFor<TModel, TProperty>( this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) {
...
}
StringBuilder
, or in our case a TagBuilder
. With the TagBuilder
we can quickly knock up our text area with the appropriate classname (for CKEditor to just work), column specification, and tag name. We will then return the tag builder through the creation of an MvcHtmlString
.var builder = new TagBuilder("textarea"); builder.AddCssClass("ckeditor"); builder.MergeAttribute("cols", "80"); return MvcHtmlString.Create(builder.ToString());
MemberName
that will extend our expression. We will then grab the body of the expression, from which we can get the property name.private static string MemberName<T, V>( this Expression<Func<T, V>> expression) { var memberExpression = expression.Body as MemberExpression; if (memberExpression == null) throw new NullReferenceException("Expression must be a member expression"); return memberExpression.Member.Name; }
MemberName
method.var builder = new TagBuilder("textarea"); builder.GenerateId(expression.MemberName()); builder.AddCssClass("ckeditor"); builder.MergeAttribute("cols", "80"); builder.MergeAttribute("name", expression.MemberName());
ModelMetadata.FromLambdaExpression()
.Models/Extensions.cs:
var meta = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData); var value = meta.Model; if (value != null) builder.SetInnerText(value.ToString());
TagBuilder
. Here is the complete listing.public static MvcHtmlString WysiwygFor<TModel, TProperty>( this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
var builder = new TagBuilder("textarea");
builder.GenerateId(expression.MemberName());
builder.AddCssClass("ckeditor");
builder.MergeAttribute("cols", "80");
builder.MergeAttribute("name", expression.MemberName());
var meta = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var value = meta.Model;
if (value != null)
builder.SetInnerText(value.ToString());
return MvcHtmlString.Create(builder.ToString());
}
HtmlHelper
extension method working, we can plug this into the view. Open up Article.aspx
and change the Html.TextBoxFor
to a Html.WysiwygFor
method call.Views/Home/Article.aspx: <div class="editor-label"> <%: Html.LabelFor(model => model.Description) %> </div> <div class="editor-field"> <%: Html.WysiwygFor(model => model.Description) %> <%: Html.ValidationMessageFor(model => model.Description) %> </div>
home/article
, and you should see the CKEditor! Web.config
to allow the code to succeed.// Controller [ValidateInput(false)] public ActionResult Article() { ... <!-- Web.config --> <system.web> ... <httpRuntime requestValidationMode="2.0" /> </system.web>
The changes mentioned in the previous step switch off the default safeguards for our Article
action. If you're building a form that will be freely accessible on the Internet, you're potentially leaving yourself open to script injection attacks. So you'll need to put in your own safeguards for dealing with malicious scripts.
Apart from creating a reusable way to add a WYSIWYG editor to our forms, this recipe demonstrated the use of lambda expressions in method signatures.
In order for us to populate the ID for our WYSIWYG element, we needed to get the property name from the passed-in lambda expression. To do this, we cast the expression body to a MemberExpression
, which then gives us access to the Name
property.
We were able to get the property value by using the ModelMetadata.FromLambdaExpression()
method, which takes the lambda expression, as well as the ViewData
that comes along with the HTML helper that we are extending. This method essentially whittles down the full model of the page to just the property that is captured by the expression. This value is then exposed by the Model
property, which is now the property that is passed in via the expression.
If you take a look at the code download inside the Extensions
class, you will notice that there are a few other helper methods in there, which are not discussed here in the text. While researching bits for this recipe, I came across several helpful functions for building helper extensions that I thought would be useful for any MVC developer's toolbox. Thanks to Chris Patterson for these found here: http://stackoverflow.com/questions/1559800/get-custom-attributes-from-lambda-property-expression#1560950.
The solution I initially worked up for this recipe used some not-so-fancy reflection of the helper.ViewData.Model
to determine the value of the property that was being passed in. This would be appropriate in MVC 1 (as ModelMetadata
came about with MVC 2). I thought there might be a better solution though, which prompted me to post a query to StackOverflow
to get some added insight. Darin Dimitrov provided me with a better way to get at this data (using the new ModelMetadata
). Thanks Darin! See my query here: http://stackoverflow.com/questions/2846778/what-is-the-easiest-way-to-get-the-property-value-from-a-passed-lambda-expression#2847712.
With regards to CKEditor itself, like a lot of similar editors, CKEditor is packed with lots of features, of which we've scratched only the surface. Another good WYSIWYG editor worth taking a look at is TinyMCE (http://tinymce.moxiecode.com/).