views:

388

answers:

2

I have a user control that I use on multiple pages to show information about an object. Whenever I need to use this usercontrol, I call a method called Display (on the usercontrol) and pass in some values.

public void Display(string name, List<Items> items)
{
    // Set a few protected properties so I can display values from the aspx page
}

This works fine, but now I need to use this control inside a foreach loop in an aspx page.

<% foreach (var c in Categories) { %>
      <uc:ItemsControl runat="server"/>
<% } %>

The categories object has the methods and properties that I would otherwise pass into the Display method. So how would I properly set the values?

I tried making the properties on the user control public and just setting them in this way:

<uc:ItemsControl Items="<%# c.Items %>" 
                 OtherProperty="<%# c.GetProperty() %>" 
                 runat="server"/>

This doesn't work, because the properties getting sent in are empty. If I use <%= then it doesn't work and actually throws an error because it doesn't recognize 'c' (unless I take off runat="server" then it will at least compile, it still won't work though.

What is the proper way to do this?

Edit: Also, a repeater control might make this easier for databinding, but I'd prefer to avoid the use of any .NET controls.

+4  A: 

In a WebForms page, the way this is usually handled by using DataBinding. Instead of a foreach loop, you can use a Repeater control, set its DataSource property to <%#Categories%>, and then use the data-binding syntax in your latter example above, except instead of c, you'll need to cast Container.DataItem to the proper type in your control. Like this:

<%@ Page Language="C#" %>
<script runat="server">
protected void  Page_Load(object sender, EventArgs e)
{
    CategoriesList.DataBind();
}
class Category
{
    public string CategoryName { get; set; }
    public string GetProperty() { return CategoryName; }
    public string[] Items { get { return new string[] {CategoryName + " 1", CategoryName + " 2"}; } }
}
Category[] Categories = new Category[]
{
    new Category { CategoryName = "Shorts" },
    new Category { CategoryName = "Socks" },
    new Category { CategoryName = "Shirts" },
};
</script>
<html>
<body>
<asp:Repeater runat="server" ID="CategoriesList" DataSource="<%# Categories %>">
  <ItemTemplate>
    <uc:ItemsControl 
       Items="<%# ((Category)(Container.DataItem)).Items %>" 
       OtherProperty="<%# ((Category)(Container.DataItem)).GetProperty() %>" 
       runat="server"/>
  </ItemTemplate>       
</asp:Repeater>
</body>
</html>

The only caveat is you'll need to call the DataBind() method of the Repeater somwhere, typically in the Page_Load method.

Personally, I have always found data-binding to be a pain-- both the casting and the need to call data-bind feel much less natural to me than the foreach method you tried above. That's probably the thing I like best about ASP.NET MVC-- your MVC Views can much more easily use the foreach model of iteration which is much more straightforward, IMHO. That said, if you have to stick with WebForms, databinding-with-casting is the path of least resistance.

BTW, your approaches above didn't work because:

  1. runat=server ASP.NET control properties cannot be filled using <%= %> syntax-- that's only useful for plain-text output, not for filling in object properties.

  2. using data-binding syntax ( <%# %>) can definitely be used to fill in control properties, but you need to wrap the control in a data-binding container (like a Repeater) and call DataBind() in order to get the replacement to happen.

Justin Grant
A repeater would probably be the proper way, but we try to avoid use of .NET controls wherever possible and prefer to go with generating HTML.
Brandon
Yep, me too-- I also find the loop-and-emit-HTML approach much more straightforward. That's why I like ASP.NET MVC. But with Web Forms, if you already are using your own custom controls and need to populate their properties, especially in a loop, using data binding is the easiest way to go. Out of curiosity, why avoid asp.net's built-in controls? Is this a performance-related concern or something else? The repeater control is *very* bare-bones and fast-- and causes no viewstate or other crud like that. The repeater can act just like a foreach loop masquerading as a control.
Justin Grant
It's more of a consistency/performance concern. My boss originally moved away from having .NET controls in the project because the controls/AjaxToolKit was just too heavy for what we needed, so a pure HTML/jQuery approach was taken. If all else fails I could throw in a single repeater, I was just wondering if there was any way to do this using the path we've already started down.
Brandon
there's a way to do it, but it will require more code, will be hard to maintain, and will likely be slower too. :-) There are certainly good reasons to shy away from the complex built-in controls (e.g. DataGrid), but Repeater is really, really minimal. BTW, I'd definitely recommend looking at ASP.NET MVC-- if you like plain HTML and jquery, you'll be in heaven with MVC. :-)
Justin Grant
@Justin, I have plyaed around with it before, and you're right, I do prefer MVC to Web Forms. I find it to be so much cleaner and easier to work with. Hopefully I can use it in a new project in the near future. Also, +1 to your answer. While this would probably work, we went with just using the Render method of the UC to give the markup.
Brandon
A: 

While Justin Grant's answer would likely hav worked just fine, we decided to go with just loading the user control and calling the DisplayResults method.

<% ((UserControlType)LoadControl("~/pathToUserControl.ascx"))
       .DisplayResults(ItemName, ItemList)); %>

That is not the complete code, but it is what was needed to solve the issue I addressed in the question. By loading the user control this way, we could set the properties we needed and display the uesr control with the correct information.

Brandon