54

I am writing a dirty little extension method for HtmlHelper so that I can say something like HtmlHelper.WysiwygFor(lambda) and display the CKEditor.

I have this working currently but it seems a bit more cumbersome than I would prefer. I am hoping that there is a more straight forward way of doing this.

Here is what I have so far.

public static MvcHtmlString WysiwygFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
    return MvcHtmlString.Create(string.Concat("<textarea class=\"ckeditor\" cols=\"80\" id=\"",
                                        expression.MemberName(), "\" name=\"editor1\" rows=\"10\">", 
                                        GetValue(helper, expression),
                                        "</textarea>"));
}

private static string GetValue<TModel, TProperty>(HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
    MemberExpression body = (MemberExpression)expression.Body;
    string propertyName = body.Member.Name;
    TModel model = helper.ViewData.Model;
    string value = typeof(TModel).GetProperty(propertyName).GetValue(model, null).ToString();
    return value;
}

private static string MemberName<T, V>(this Expression<Func<T, V>> expression)
{
    var memberExpression = expression.Body as MemberExpression;
    if (memberExpression == null)
            throw new InvalidOperationException("Expression must be a member expression");

    return memberExpression.Member.Name;
}

Thanks!

Andrew Siemer
  • 10,166
  • 3
  • 41
  • 61
  • I tested your Getvalue method and got good resuts when the helper is a selectlist. The other methods here failed in this case. – calterras Oct 20 '17 at 06:23

6 Answers6

85

Try like this:

public static MvcHtmlString Try<TModel, TProperty>(
    this HtmlHelper<TModel> htmlHelper, 
    Expression<Func<TModel, TProperty>> expression
)
{
    var builder = new TagBuilder("textarea");
    builder.AddCssClass("ckeditor");
    builder.MergeAttribute("cols", "80");
    builder.MergeAttribute("name", "editor1");
    builder.MergeAttribute("id", expression.Name); // not sure about the id - verify
    var value = ModelMetadata.FromLambdaExpression(
        expression, htmlHelper.ViewData
    ).Model;
    builder.SetInnerText(value.ToString());
    return MvcHtmlString.Create(builder.ToString());
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 6
    I was mostly interested in this line from the above - var value = ModelMetadata.FromLambdaExpression( expression, htmlHelper.ViewData ).Model; which worked great! I did find that your expression.Name returns null. I swapped that to point to my MemberName method to get that working for the ID. Also, I found that there is a TagBuilder.GenerateId() for adding the ID attribute. Thanks. I will be sure to mention you in my upcoming book (ASP.NET MVC Cookbook - http://groups.google.com/group/aspnet-mvc-2-cookbook-review) – Andrew Siemer May 17 '10 at 15:42
  • Hey darin, can you please explain to me this line? `var value = ModelMetadata.FromLambdaExpression( expression, htmlHelper.ViewData ).Model;` – Carlos Miguel Colanta Jul 30 '16 at 10:17
26
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
Object value = metadata.Model;
String name = metadata.PropertyName;
Peter Hedberg
  • 3,487
  • 2
  • 28
  • 36
  • This is working for me and correctly gets the property name via PropertyName where expression.Name returns null. Edit to add - I'm running MVC5 in case that matters – Nick Van Brunt Oct 25 '16 at 14:03
10

I Know this is an old thread but just in case if someone is looking for it, the way to generate id / name attribute is also:

System.Web.Mvc.ExpressionHelper.GetExpressionText(expression);

I'm using this in my extensions and never had any issues with it. It also works great with nested properties.

Ales Potocnik Hahonina
  • 2,977
  • 2
  • 26
  • 32
5

Simplest way is to wrap it all up in an extension method:

public static class ExtensionMethods
{   

    public static object Value<TModel, TProperty>(this Expression<Func<TModel, TProperty>> expression, ViewDataDictionary<TModel> viewData)
    {
        return ModelMetadata.FromLambdaExpression(expression, viewData).Model;
    }  

}

So the calling syntax is:

expression.Value(htmlHelper.ViewData)
BigMomma
  • 338
  • 2
  • 14
2

ASP.NET MVC 3 Futures includes a helper for that.

user571646
  • 665
  • 5
  • 10
1

This isn't addressed by either Peter or BigMomma's answer, but it combines both. If you're calling this from a controller method, where you don't have access to an HtmlHelper instance, just create a base controller method like this:

public ModelMetadata GetModelMetadata<TModel, TProperty>( TModel model, Expression<Func<TModel, TProperty>> expression )
{
    ViewData.Model = model; //model is null in Controller; you must set it here (or earlier) in order to extract values from the returned ModelMetadata.
    return ModelMetadata.FromLambdaExpression( expression, new ViewDataDictionary<TModel>( ViewData ) );
}

Then you can read what you need from the model metadata as usual;

var mm = GetModelMetaData( model, m => m.SomeProperty );
string name = mm.PropertyName;
object value = mm.Model;
Triynko
  • 18,766
  • 21
  • 107
  • 173