views:

60

answers:

2

I've seen various controls in ASP.NET with "collections" of objects, which you can use markup to define. For example:

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <Triggers>
        <asp:PostBackTrigger ControlID="MyButton" />
    </Triggers>
...
</asp:UpdatePanel>

In the above example, you can add any number of triggers, and they can be of any type that extends UpdatePanelTrigger. I'd like to do something similar, but with only a single item instead of a collection:

<app:PortalLink ID="DrinkLink" Text="I am thirsty">
    <portal:ViewProductsRequest ProductCategory="Drinks" />
</app:PortalLink>
--or--
<app:PortalLink ID="DrinkLink">
    <Content>I am thirsty</Content>
    <Request>
        <portal:ViewProductsRequest ProductCategory="Drinks" />
    </Request>
</app:PortalLink>

The idea is that the Request could be given a single object of any type that inherits a particular interface or class. Is there any way to do this?

Update

To clarify, I want a PortalLink to be able to do accept anything that is a sub-class of a Request, which represents the parameters that a particular page accepts. For example:

public class AdminMenuRequest : Request
{} // No parameters for the main menu page.
public class ViewProductsRequest : Request
{
    public string ProductCategory {get;set;}
    public bool? ShowOutOfStock {get;set;}
}

And in markup:

<app:PortalLink runat="server" Text="I am an administrator">
    <portal:AdminMenuRequest />
</app:PortalLink>
<app:PortalLink runat="server" Text="I am thirsty">
    <portal:ViewProductsRequest ProductCategory="Drinks" />
</app:PortalLink>

So far I've figured out that I can either make the Requests controls unto themselves, or make PortalLinks accept a collection of requests (but not restricted to a single request). If I make the Requests controls, it sort of breaks the single-purpose rule: requests represent both a request on the server side and a link on the client side. Plus, since I want to have one request per "page", as it were, it would also end up polluting the intellisense list in most circumstances. If I make PortalLinks accept a collection of requests, then I can simply use the first item in the list and ignore the rest. That works, but it would be nice to be able to enforce that each PortalLink should have one (and only one) value in it. It would also be easier when creating or initializing links in the codebehind to be able to say:

((ViewProductsRequest)FoodOrDrinkLink.Request).ShowOutOfStock = OOSCheckBox.Checked;

This is something I can sort of fake with additional properties, but I was hoping for something just a tad more elegant.

+2  A: 

Take a look here: Web Control Collection Property Example

EDIT: For a "simple" property, you don't need anything else than to create a public property into your control class, but you must to use it later as an tag attribute (and not as an inner property):

public partial class UCSample : UserControl
{
    public string MyCustomProperty { get; set; }

    protected void Page_Load(object sender, EventArgs e)
    {
        Response.Write(this.MyCustomProperty);
    }
}

<%@ Page Language="C#"  %>
<%@ Register src="UCSample.ascx" tagname="UCSample" tagprefix="uc1"%>

<uc1:UCSample ID="UCSample1" runat="server" MyCustomProperty="StackOverflow" />
Rubens Farias
That was enough to get me started, but wasn't a really full answer. Am I to understand that I can do this sort of thing for *collections* of objects, but not for single objects?
StriplingWarrior
@StriplingWarrior, see edited answer
Rubens Farias
See my updated question.
StriplingWarrior
A: 

You can use a ControlBuilder to achieve this.

Here's the code I used:

public class PortalLinkBuilder : ControlBuilder {
  public override Type GetChildControlType(string tagName, IDictionary attribs) {
    // you need the full name of the types, suppose they're on namespace Portal
    return Type.GetType("Portal." + tagName, true);
  }
  public override void AppendLiteralString(string s) {
    // ignores literals between rows
  }
}

[ControlBuilder(typeof(PortalLinkBuilder))]
public class PortalLink : Control {

  protected override void AddParsedSubObject(object obj) {
    if (Request != null) throw new InvalidOperationException("Too many requests!");
    Request = (Request)obj; // will fail for non-Request objects
  }

  [Browsable(false)] 
  [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  public Request Request { get; private set; }

  ...

}

In the UI:

    <app:PortalLink runat="server" ID="lnk1">
        <AdminMenuRequest /> 
    </app:PortalLink>

    <app:PortalLink runat="server" ID="lnk2">
        <ViewProductsRequest ProductCategory="Drinks" />     
    </app:PortalLink>

I was able to make it work, but the designer surface complains about the child tags. Maybe your idea about creating Request controls would fix this, but you should also maintain your Semantic Model separate from the controls, and use the controls to build the model. You'd have these classes:

The model:

  • Request
  • AdminMenuRequest
  • ViewProductsRequest

The controls:

  • RequestControl
  • AdminMenuRequestControl
  • ViewProductsRequestControl
Jordão