views:

2696

answers:

6

Asp.Net MVC 2.0 preview builds provide helpers like

Html.EditorFor(c => c.propertyname)

If the property name is string, the above code renders a texbox.

What if I want to pass in MaxLength and Size properties to the text box or my own css class property?

Do I need to create one template for each size and length combinations in my application? If so, that doesn't make the default templates that usable.

+1  A: 

Hi chandmk

You can define attributes for your properties.

[StringLength(100)]
public string Body { get; set; }

This is known as System.ComponentModel.DataAnnotations. If you can't find the ValidationAttribute that you need you can allways define custom attributes.

Best Regards, Carlos

Carlos Fernandes
He talks about Html-attributes like maxlength not validation.
Malcolm Frexner
I would *hope* the view engine would obey the DataAnnotations also. I have not messed around with MVC2 yet.
mxmissile
+2  A: 

The problem is, your template can contain several HTML elements, so MVC won't know to which one to apply your size/class. You'll have to define it yourself.

Make your template derive from your own class called TextBoxViewModel:

public class TextBoxViewModel
{
  public string Value { get; set; }
  IDictionary<string, object> moreAttributes;
  public TextBoxViewModel(string value, IDictionary<string, object> moreAttributes)
  {
    // set class properties here
  }
  public string GetAttributesString()
  {
     return string.Join(" ", moreAttributes.Select(x => x.Key + "='" + x.Value + "'").ToArray()); // don't forget to encode
  }

}

In the template you can do this:

<input value="<%= Model.Value %>" <%= Model.GetAttributesString() %> />

In your view you do:

<%= Html.EditorFor(x => x.StringValue) %>
or
<%= Html.EditorFor(x => new TextBoxViewModel(x.StringValue, new IDictionary<string, object> { {'class', 'myclass'}, {'size', 15}}) %>

The first form will render default template for string. The second form will render the custom template.

Alternative syntax use fluent interface:

public class TextBoxViewModel
{
  public string Value { get; set; }
  IDictionary<string, object> moreAttributes;
  public TextBoxViewModel(string value, IDictionary<string, object> moreAttributes)
  {
    // set class properties here
    moreAttributes = new Dictionary<string, object>();
  }

  public TextBoxViewModel Attr(string name, object value)
  {
     moreAttributes[name] = value;
     return this;
  }

}

   // and in the view
   <%= Html.EditorFor(x => new TextBoxViewModel(x.StringValue).Attr("class", "myclass").Attr("size", 15) %>

Notice that instead of doing this in the view, you may also do this in controller, or much better in the ViewModel:

public ActionResult Action()
{
  // now you can Html.EditorFor(x => x.StringValue) and it will pick attributes
  return View(new { StringValue = new TextBoxViewModel(x.StringValue).Attr("class", "myclass").Attr("size", 15) });
}

Also notice that you can make base TemplateViewModel class - a common ground for all your view templates - which will contain basic support for attributes/etc.

But in general I think MVC v2 needs a better solution. It's still Beta - go ask for it ;-)

queen3
I think a better way of dealing with this is as I mentioned here http://stackoverflow.com/questions/1647609/asp-net-mvc-v2-styling-templates... In short doing something similar to the way that WPF handles the problem... Arbitrary styles elements (on in this case attributes) get passed to the template and the template decides which internal element it will apply the style to...
vdhant
A: 

UPDATE: hm, obviously this won't work because model is passed by value so attributes are not preserved; but I leave this answer as an idea.

Another solution, I think, would be to add your own TextBox/etc helpers, that will check for your own attributes on model.

public class ViewModel
{
  [MyAddAttribute("class", "myclass")]
  public string StringValue { get; set; }
}

public class MyExtensions
{
  public static IDictionary<string, object> GetMyAttributes(object model)
  {
     // kind of prototype code...
     return model.GetType().GetCustomAttributes(typeof(MyAddAttribute)).OfType<MyAddAttribute>().ToDictionary(
          x => x.Name, x => x.Value);
  }
}

<!-- in the template -->
<%= Html.TextBox("Name", Model, MyExtensions.GetMyAttributes(Model)) %>

This one is easier but not as convinient/flexible.

queen3
+7  A: 

May want to look at Kiran Chand's Blog post, he uses custom metadata on the view model such as:

[HtmlProperties(Size = 5, MaxLength = 10)]
public string Title { get; set; }

This is combined with custom templates that make use of the metadata. A clean and simple approach in my opinion but I would like to see this common use case built-in to mvc.

tj
A: 

This may not be the slickest solution, but it is straightforward. You can write an extension to the HtmlHelper.EditorFor class. In that extension, you can supply an options parameter that will write the options to the ViewData for the helper. Here's some code:

First, the extension method:

public static MvcHtmlString EditorFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, TemplateOptions options)
{
    return helper.EditorFor(expression, options.TemplateName, new
    {
        cssClass = options.CssClass
    });
}

Next, the options object:

public class TemplateOptions
{
    public string TemplateName { get; set; }
    public string CssClass { get; set; }
    // other properties for info you'd like to pass to your templates,
    // and by using an options object, you avoid method overload bloat.
}

And finally, here's the line from the String.ascx template:

<%= Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { @class = ViewData["cssClass"] ?? "" }) %>

Frankly, I think this is straightforward and clear to the poor soul who has to maintain your code down the road. And it is easy to extend for various other bits of info you'd like to pass to your templates. It's working well so far for me in a project where I'm trying to wrap as much as I can in a set of template to help standardize the surrounding html, a la http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-5-master-page-templates.html.

spot
+3  A: 

I solved this by creating an EditorTemplate named String.ascx in my /Views/Shared/EditorTemplates folder:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<string>" %>
<% int size = 10;
   int maxLength = 100;
   if (ViewData["size"] != null)
   {
       size = (int)ViewData["size"];
   }
   if (ViewData["maxLength"] != null)
   {
       maxLength = (int)ViewData["maxLength"];
   }
%>
<%= Html.TextBox("", Model, new { Size=size, MaxLength=maxLength }) %>

In my view, I use

<%= Html.EditorFor(model => model.SomeStringToBeEdited, new { size = 15, maxLength = 10 }) %>

Works like a charm for me!

tjeerdhans
brilliant - I had a datepicker DateTime drop down that I'd already templated, but passing extra attributes to it was proving painful - this solved it for me, thanks!
Terry_Brown