views:

256

answers:

3

How can I provide some form of Attribute against my Business Objects in my ASP.Net MVC application so that I can pick up the help texts in the View and have them appear as pop-up hover help texts like this:

<%= Html.TextBox("PostalCode", Model.PostalCode, new { 
    title = "Please enter your postal code." })%>

I want to be able to get the 'title' value from my ViewModel which will have picked up the text from an attribute on the Business Object.

In summary, how can I apply help text attributes / metadata on my Business Objects?

+1  A: 

Update:

You could just create a new class property for each of your object named "Title" or "Hint" and add the appropriate string value to them. Then get that property with MyObject.Title


Interesting question. I would like to see an answer to this using attributes, but here are two methods I can think of:

Add an extension method to your objects This would require alot of repetitive code.

public static string GetTitle(this YourObject obj)
{
     return "Title for object";
}

Html Helper extension method

You would store the object titles in this helper method.

public static string GetObjectTitle(this HtmlHelper html, string type)
{
     switch(type)
     {
          case "Object1":
          return "Title for object 1";
          break;

          case "Object2":
          return "Title for object 2";
          break;

          default:
          return "No titled specified for this object type";
          break;
     }
}

To call this method:

<%= Html.GetObjectTitle(Model.GetType()) %>

Or in your example:

<%= Html.TextBox("PostalCode", Model.PostalCode, new { 
watermark = "Postal Code", 
title = Html.GetObjectTitle(Model.GetType()) })%>

I prefer the 2nd method because you have a place to store all the titles and you would need to write less code.

However, I think adding an attribute to the class and creating a means to get that attribute would work a bit better.

Baddie
Why would you use extension methods when you can just put the method right into the class, and call it directly?
Robert Harvey
The case statement is worse. It breaks encapsulation by requiring you to define the message somewhere outside of the business object.
Robert Harvey
Either method would break "insert pedantic program design term" to a certain extent. I prefer the Case statement because it would place all the titles in one place, making future editing easier. Theres definitely better ways to do this, I just can't think of them. Thinking about it now, you could specify a "Title" property for each object, and depending on the way your app is setup, it might be the best thing.
Baddie
I just realized my main issue. For some reason I thought he was dealing with alot of generated code, i.e Linq to SQL .dbml files. Rather than modifying the generated class code, I create extension methods to the objects so I didn't have to touch the generated code, in fear of breaking it. Thanks for the insight Robert.
Baddie
In Linq to SQL, you can put the method in the partial class of the model object. This protects it in the event the dbml classes are regenerated.
Robert Harvey
Thanks yet again.
Baddie
Actually, curiously my idea came from using code generation with NetTiers before where database fields had comments associated with them at design time and the code generation filtered up from there to the business objects. However, in this case I am using more of a model-first design approach and I have all of my EF stuff in an IRepository with a view to pluggable repositories (including moving from EF to Azure). As I think this through this is really UI oriented but I want it reusable so that I can reuse the metadata when I use an RIAServices front-end later.
Redeemed1
+1  A: 

I don't think there is a way to do it straight out of the box.

What you could do though is create a generic "model value" class that encapsulated that information in it thus keeping your strongly typed view. ie:

ModelValue<string> postalCode = new ModelValue<string>("poscode value", "Please enter your postal code.")

You could then build up your model classes to contain properties of type ModelValue.

Your code above would then look something like:

<%= Html.TextBox("PostalCode", Model.PostalCode.Value, new {     watermark = "Postal Code",     title = Model.PostalCode.Title })%>

The down side to doing this is that I don't think mvc is going to do automatic binding for you, so you'll have to do all the mapping in the view yourself like in this example but also on Post you will have to do manual binding if you aren't already. You would probably also instantiate all your ModelValue properties in the constructor of the model and pull in all the title values from whever they are stored then because you wouldn't rebind them on Post (I am thinking about validation errors here causing the form to be redisplayed).

If you were super keen you would put attributes on your model properties and then somehow have them parsed when you render the page, but I have no idea where you would start if you wanted to go this way.

Dean Johnston
actually, its the idea of attributes on the model properties that I am looking for. I think there may be something in EnterpriseLibrary but I would be just as happy knowing I could define even custom attributes and simply apply them to the model
Redeemed1
A: 

Here is how I did it:

  1. Created new Attribute as follows:

    public class HelpPromptAttribute : Attribute
    

    { public HelpPromptAttribute(string text) { myproperty = text; } protected string myproperty;

    public string HelpTextKey
    {
        get { return myproperty; }
        set { myproperty = value; }
    }
    

    }

  2. Added attribute to entity property as follows:

    [HelpPrompt("ProjectDescription")]
    [Required(ErrorMessageResourceName = "ProjectDescriptionRequired", ErrorMessageResourceType = typeof(EntityValidationMessages))]
    [StringLength(50, ErrorMessageResourceName = "ProjectDescriptionTooLong", ErrorMessageResourceType = typeof(EntityValidationMessages))]
    public string ProjectDescription { get; set; }
    
  3. Added Extension Method to Entities as follows:

    public static class EntityBOExtensions { public static string PropertyHelp(this object objectBO, string PropertyName) { Type type = objectBO.GetType();

        foreach (PropertyInfo pInfo in type.GetProperties())
        {
            if (string.Compare(pInfo.Name, PropertyName) == 0)
            {
                foreach (Attribute attr in Attribute.GetCustomAttributes(pInfo))
                {
                    if (attr.GetType() == typeof(HelpPromptAttribute))
                    {
                        string key = ((HelpPromptAttribute)attr).HelpTextKey;
                        if (!string.IsNullOrEmpty(key))
                            return EntityBOHelp.ResourceManager.GetString(key);
                    }
                }
            }
        }
        return null;
    }
    

    }

  4. Added a HtmlHelper (simple) as follows:

    public static string LocalisedTextBoxWithHelp(this HtmlHelper helper, string name, object value, string helptext)
    {
        string textbox = helper.TextBox(name, value, new { helpprompt = helptext });
        return textbox;
    }
    
  5. And finally used the folowing markup in the View:

     <%= Html.LocalisedTextBoxWithHelp("project.ProjectDescription", Model.ProjectDescription, Model.PropertyHelp("ProjectDescription"))%>
    

This does the job although it needs refinement. ;)

Redeemed1