Building a custom HTML helper to display a WYSIWYG

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.

Getting started

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.

How to do it...

  1. Extract the downloaded CKEditor files into a folder called ckeditor, and place that folder in the root of your MVC application.
  2. Go into the 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.
  3. Next, we will add the CKEditor script to our master page so that things will magically work later. Open the 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>
    

    Note

    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.

  4. Now we can add a new class called 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>
    
  5. In the 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.

    Note

    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) {
    ...
    }
    
  6. With the method signature out of the way, we then need to look towards the body of the method. There are a few ways with which we can render HTML. We could use a string, a 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());
    
  7. We are missing two important pieces at this point though. We need to be able to map the property name that is passed into our helper to the ID of the textarea. We also need to be able to populate the WYSIWYG editor with the content of the property that is passed in. To get the property name we will create a new helper method called 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;
    }
    
  8. Now we can add the ID and name to our text area by making a call to our new MemberName method.
    var builder = new TagBuilder("textarea");
    builder.GenerateId(expression.MemberName());
    builder.AddCssClass("ckeditor");
    builder.MergeAttribute("cols", "80");
    builder.MergeAttribute("name", expression.MemberName());
    
  9. All that is left is for us to do is to grab the data out of the passed-in property and push it into our new WYSIWYG editor. We can do this using the ModelMetadata.FromLambdaExpression().

    Models/Extensions.cs:

    var meta = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
    var value = meta.Model;
    if (value != null)
    builder.SetInnerText(value.ToString());
    
  10. With the value in hand, we can then push it into our 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());
    }
    
    How to do it...
  11. Now that we have our 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>
    
  12. Now you can hit F5, browse to home/article, and you should see the CKEditor!
    How to do it...
  13. Click on the Submit button though; you'll see we're not looking so hot. By default, ASP.NET checks every form post for the existence of potentially harmful script. Make the following changes to the home controller and Web.config to allow the code to succeed.
    // Controller
    [ValidateInput(false)]
    public ActionResult Article() {
    ...
    <!-- Web.config -->
    <system.web>
    ...
    <httpRuntime requestValidationMode="2.0" />
    </system.web>
    

    Note

    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.

How it works...

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.

There's more...

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/).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset