views:

195

answers:

5

Is is possible to apply an attribute to a collection, and then detect this when iterating through the collection members using ViewData.ModelMetadata.Properties ?

I would like to apply an attribute to the collection to specify whether items in the collection should be displayed in their entirety or not. I want to then detect this in the Object.ascx (that deals with the display of objects of unknown type) to decide what level of detail to display.

(Please see brad wilson's post for background on this generic templating approach)

For Example:

public class Parent
{
   [SomeAttributeIWantToDetect]        
   public IList<Child> Children{ get; set; }
}


public class Child
{
    public string Name { get; set; }
    public string Details { get; set; }
}

object.ascx: (Note, this code is from the ASP.NET MVC team, not mine)

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<% if (Model == null) { %>
    <%= ViewData.ModelMetadata.NullDisplayText %>
<% } else { %>
    <table cellpadding="0" cellspacing="0" border="0">
    <% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm))) { %>
        <% if (prop.HideSurroundingHtml) { %>
            <%= Html.Display(prop.PropertyName) %>
        <% } else { %>
            <tr>
                <td>
                    <div class="display-label" style="text-align: right;">
                        <%= prop.GetDisplayName() %>
                    </div>
                </td>
                <td>
                    <div class="display-field">
                    <!-- *********** HERE ***************-->
                    <% if (prop.AdditionalValues.ContainsKey(SomeAttributeIWantToDetectAttribute)) 
                        //Do something else.....
                        else
                     %>
                        <%= Html.Display(prop.PropertyName) %>
                    <% } %>
                    </div>
                </td>
            </tr>
        <% } %>
    <% } %>
    </table>
<% } %>
A: 

Could you not use reflection like so: http://msdn.microsoft.com/en-us/library/z919e8tw.aspx ?

Nik
Thanks, I'll take a look at that. I'd like to avoid using reflection if possible, but maybe it's necessary.
UpTheCreek
+1  A: 

This is not MVC, it's closer to classic ASP templates

Choose which camp you want to be in, and stay there

To use MVC you need to make a ViewModel which expresses the Model in terms of a particular render destination

Your ViewModel should be built using just the logic commands from above. i.e.

if (Model == null)
{
  x = ViewData.ModelMetadata.NullDisplayText
}
else
{
  foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm)))
  {
    if (prop.HideSurroundingHtml)
    {
      x.Items1.Add(prop.PropertyName));
    }
    else
    {
      prop.GetDisplayName()
      if (prop.AdditionalValues.ContainsKey(SomeAttributeIWantToDetectAttribute)) 
      {
        x.Items2.Add( { Text = prop.zzz, Highlight = true} ));
      }
      else
      {
        x.Items2.Add( { Text = prop.PropertyName } ));
      }
    }
  }
}

The code above is obviously wrong, I am just try to show that you should take the complex code and logic and use it to build a ViewModel, it should never be in the view

The ViewModel has a simple construct relating to the rendering technology you are using (like html attributes etc)

The view should just contain simple iterators and layout selectors, fed from the ViewModel, never the actual Model

TFD
ViewModels are meaningless in a generic template! Whether or not it is strictly 'right' to do generic templating in MVC is another matter entirely - I'm aware that it's not elegant. However, this approach is actually advocated by the ASP.NET MVC team. See brad wilson's post: http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html
UpTheCreek
Thanks for the retalitory -1!
UpTheCreek
Just where did you say this was generic?. Anyway generic or not, the conclusion still stands, make a ViewModel. Any code required to process attributes or do reflection should NOT be in the view
TFD
Ok, I'll update the question to make it clearer that I'm talking about generic templating.
UpTheCreek
@TFD - I would think "generic templating" would be implied as that is exactly what these ASP.NET features were built for. Nothing says MVC and Generic Templating can't play together nicely. The first version of RoR used generic templating.
jfar
MVC and generic templating play very well together. You still need to turn the complex model attributes/reflection into a simple data structure that you can layout a View without more code than simple iterators
TFD
+2  A: 

@UpTheCreek, you're coming across as awfully confrontational for wanting people to help you, but I'll give my 2 cents anyway.

My understanding is that you want to be able to have a child collection on a model. On the child model you're going to be including some [ScaffoldColumn("false")] attributes, but you also want to be able to put an attribute on the parent model that would cause the renderer to ignore the ScaffoldColumn attributes and just show everything. If my understanding is correct, I think you are going about this the wrong way. You should be creating separate view models for cases where you want different properties to be shown.

Also, I'm note quite clear if this is your intent because in your code sample you would only be hiding the input field and not the label itself. Perhaps you are trying to put some sort of notice that the field is hidden?

Ryan
Hi Ryan, this is a generic template and so I can't create separate view models for each case. See the linked article for more info.
UpTheCreek
+1  A: 

You've linked http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html several times with the comment "I can't use ViewModels because this is generic templating".

I don't understand why you believe this. TFD and Ryan have it exactly right. Create two different ViewModels to wrap your model, and put the ScaffoldColumn attributes on your ViewModel (or better, omit those fields entirely).

Object.ascx then detects the attribute (or of course the presence or absence of the field) on your ViewModel and displays (or doesn't) the field appropriately.

In fact, the author of the post you linked suggests doing exactly that:-

http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html#comment-6a00e54fbd8c4988340120a6396c7a970b

"Personally, my recommendation for people who want strict SoC (like I do) is to use ViewModels and only place the annotations on the view model. There are other issues with directly model binding to things like LINQ to SQL or LINQ to Entities (for example, if you're not careful, you can destroy your associations or inadvertently let a bad guy bind data into something that wasn't originally shown in the editor), so I generally always recommend view models anyway."

So:-

public class Parent
{
  public IList<Child> Children{ get; set; }
}

public class Child
{
  public String Name { get; set; }
  public String Details { get; set; }
}

// Pass this one to your "Admin" view.
public class ParentAdminViewModel
{
  private Parent _parent;
  public ParentAdminViewModel(Parent parent) { this._parent = parent; }

  public IEnumerable<Child> Children
  {
    get
    {
      return _parent.Children.Select(x => new ChildAdminViewModel(x));
    }
  }
}

public class ChildAdminViewModel
{
  private Child _child;
  public ChildAdminViewModel(Child child) { this._child = child; }

  public String Name { get { return _child.Name; } }
  public String Details { get { return _child.Details; } }
}

// Pass this one to your "User" view.
public class ParentUserViewModel
{
  private Parent _parent;
  public ParentUserViewModel(Parent parent) { this._parent = parent; }

  public IEnumerable<Child> Children
  {
    get
    {
      return _parent.Children.Select(x => new ChildUserViewModel(x));
    }
  }
}

public class ChildUserViewModel
{
  private Child _child;
  public ChildAdminViewModel(Child child) { this._child = child; }

  public String Name { get { return _child.Name; } }
  // ChildUserViewModel doesn't have a Details property,
  // so Object.ascx won't render a field for it.
}

Obviously you'll need to wire up setters as well if you want to edit.

Iain Galloway
A: 

Here's a blog post that you might find useful.

Omu