开发者

ASP.NET MVC Generic templating and collections

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.AdditionalValu开发者_如何学编程es.ContainsKey(SomeAttributeIWantToDetectAttribute)) 
                        //Do something else.....
                        else
                     %>
                        <%= Html.Display(prop.PropertyName) %>
                    <% } %>
                    </div>
                </td>
            </tr>
        <% } %>
    <% } %>
    </table>
<% } %>


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


@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?


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜