0

I have a dropdown that represents various "attribute" fields related to a document. For example, a document could have a Tags field, which would require a text-box to enter your tag(s) into. Or it might have a Location field, and that would have a pre-populated dropdown of various location types the user could pick. Or it could be a Deadline field, and we have a custom date-picker thing we use for picking a date.

My problem: I wanted to declare an HtmlHelper inside my controller action that fires when selecting the appropriate attribute field. I would switch against what the Attribute.Type is, and depending on the type, I would return in my view model the appropriate html to render in the view. If I did it this way I can unit test my switch statement.

I am reading all over that declaring an HtmlHelper in a controller is a bad idea. I can understand the sentiment-- is there a better way I can do this? I want to avoid logic in my view whenever possible, because I can't unit test that at all.

The other way I see of doing this is passing the Attribute.Type along in the view model, and doing the switch logic there on the view to determine which HtmlHelper method to call. Is that the way to go? Thanks in advance.

EDIT: I'm talking about wanting to do something like this in the code-behind. My unit test would assert against viewModel.FieldHtml. What I'm hearing is that I should put this in another class and have the controller call it. But here's what it would have looked like in the controller. It's just pseudo-code so it's not perfect but it's to give context to what I'm asking.

public ActionResult GetValueInput(Guid attributeFieldUid)
{
    //you have to pass some stuff into the constructor but essentially this
    HtmlHelper html = new HtmlHelper();
    AttributeField field = GetAttributeFieldFromDb(attributeFieldUid);

    AttributeViewModel viewModel = new AttributeViewModel();
    switch(field.type)
    {
        case Dropdown:
            viewModel.FieldHtml = html.DropDownList();
            break;
        case Text:
            viewModel.FieldHtml = html.TextBox();
            break;
        case Date:
            // our own extension method
            viewModel.FieldHtml = html.OurDatePicker();
            break;
    }
}

The other option I initially saw was to essentially do this switch statement on a razor view. But I think I like the idea of doing this in a separate class the controller calls the most.

This is really a design question-- I'm asking what way to implement this makes the most sense?

  • 2
    What do you mean, "declare an HtmlHelper" in your controller action? That doesn't make sense. – Andrew Barber Apr 25 '12 at 21:11
  • 1
    your HtmlHelper is a static method in a static class, not in the controller. you can create a separate folder named "Helpers", and just add this class there, but be sure to include the folder in the tag in Webconfig – Alex Peta Apr 25 '12 at 21:15
  • I have edited my question to help add context to what I'm asking. – Louis Basile Apr 25 '12 at 22:02
  • So... to add onto what others have said... pass the data you need to the view from the controller in the model. Then, in the view, call the helper to determine what type of control to render. This keeps logic out of the view (which you already identified as a problem) and allows you to build tests around the helper. – Trent Apr 25 '12 at 23:22

2 Answers2

1

You could write a custom HTML helper for this. So the controller action will be responsible to fill the view model and pass it to the view:

public ActionResult GetValueInput(Guid attributeFieldUid)
{
    AttributeField field = GetAttributeFieldFromDb(attributeFieldUid);
    AttributeViewModel viewModel = new AttributeViewModel();
    viewModel.FieldType = field.type;
    return View(viewModel);
}

and then write a helper:

public static class HtmlExtensions
{
    public static IHtmlString Field(this HtmlHelper html, FieldType type)
    {
        switch(field.type)
        {
            case Dropdown:
                return html.DropDownList(... you will probably need some more info on your view model to be passed here to generate a dropdown)
            case Text:
                return html.TextBox(...);
            case Date:
                return html.OurDatePicker();
        }

        return MvcHtmlString.Empty;
    }
}

This helper will be used in the view:

@model AttributeViewModel
@Html.Field(Model.FieldType)

Another possibility is to use editor templates. Here's an example.

Community
  • 1
  • 1
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
0

Hmm sounds like a job for AOP.

It's quite simple, create a custom attribute where you could specify the type of the field with any additional parameters required ex.

public class CustomAttribute : Attribute
{
    private readonly FieldType _fieldType;
    public CustomAttribute(FieldType fieldType)
    {
        _fieldType = fieldType;
    }
    public FieldType FieldType { get { return _fieldType; } }
}

And annotate properties on the viewmodel ex.

[Custom(FieldType.CheckBox)]
public int SomeField { get; set; }

Then write an html helper extension method ex.

public static class CustomHtmlHelperExtensions
{
    public MvcHtmlString CustomControlFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel,TProperty>> memberExpression)
    {
        var member = ((MemberExpression)model.Body).Member;
        var customAttribute = member.GetAttributes().OfType<CustomAttribute>().Cast<CustomAttribute>().SingleOrDefault();
        if(customAttribute == null)
        //Perform certain logic if the property doesn't have the attribute specified
        //ex. return null;
        switch(customAttribute.FieldType)
        {
            case FieldType.TextBox: 
                {
                    //Do something
                    return htmlHelper.TextBoxFor(memberExpression);
                }
            default: break;
        }
        return null;
    }
}

That should at least get you started :)

Shelakel
  • 1,070
  • 9
  • 16