views:

90

answers:

2

I am trying to develop a control that'll allow the developer to populate a collection through markup (e.g. Properties decorated with the attribute PersistenceMode(PersistenceMode.InnerProperty). I have an example from Brian Chavez that more or less does what I want but I want to have the control inherit from UserControl and not Control.

Here is the code I have in vb.net:

AggregateFeeds.ascx.vb

Imports Microsoft.VisualBasic

<ParseChildren(True)>
<PersistChildren(False)>
Public Class AggregateFeeds
    Inherits UserControl

    Public Sub New()
        MyBase.New()
        Me.Feeds = New RssFeedCollection()
        Me.Settings = New AggregateSettings()
    End Sub

    Private _Feeds As RssFeedCollection
    Private _Settings As AggregateSettings

    <PersistenceMode(PersistenceMode.InnerProperty)>
    Public Property Feeds As RssFeedCollection
        Get
            Return _Feeds
        End Get
        Private Set(ByVal value As RssFeedCollection)
            _Feeds = value
        End Set
    End Property

    <PersistenceMode(PersistenceMode.InnerProperty)>
    Public Property Settings As AggregateSettings
        Get
            Return _Settings
        End Get
        Private Set(ByVal value As AggregateSettings)
            _Settings = value
        End Set
    End Property
End Class

Public Class AggregateSettings
    Public Property TimeOut As Integer
    Public Property CacheResults As Boolean

    Public Sub New()
        TimeOut = 100
        CacheResults = True
    End Sub

End Class

Public Class RssFeedCollection
    Inherits List(Of RssResource)
End Class

Public Class RssResource
    Public Property Url As String = String.Empty
End Class

The ascx file just looks like:

<%@ Control Language="VB" AutoEventWireup="false" CodeFile="ucAdminTabControl.ascx.vb" Inherits="AggregateFeeds" %>

My test page is real simple and looks like:

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Test.aspx.vb" Inherits="Test" %>
<%@ Register Src="~/Survey/Controls/ucAdminTabControl.ascx" TagName="AggregateFeeds" TagPrefix="uc" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;

<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <uc:AggregateFeeds runat="server" ID="af">
            <Settings CacheResults="False" TimeOut="250" />   
            <Feeds>
                <uc:RssResource Url="http://test.com" />
            </Feeds>        
        </uc:AggregateFeeds>        
    </div>
    </form>
</body>
</html>

As it is now when I try to compile I get a compiler error that simply says object instance not set to an instance of an object on the line above. If I take it out, the page loads just fine and the Settings object reflects the values in the markup. Why am I having trouble getting the collection to populate correctly?

A: 

You have to implement your own Parser if you want to parse multiple child entries. You need to make your child class a user control and override "AddParsedSubObject". Here is a new version of your RssFeedCollection that will accept multiple strings separated by Carriage Returns instead of RssResource objects. You can make the parser more advanced and parse XML instead of strings if you still need it to do that.

<ParseChildren(False)>
<PersistChildren(False)>
Public Class RssFeedCollection
    Inherits Web.UI.Control

    Private _URLs As String()
    Public ReadOnly Property URLs As String()
        Get
            Return _URLs
        End Get
    End Property

    Protected Overrides Sub AddParsedSubObject(ByVal obj As Object)
        Dim s = DirectCast(obj, LiteralControl).Text.Trim.Replace(" ", "") 'remove spaces
        Dim separator = New Char() {Microsoft.VisualBasic.vbCr(0)}

        _URLs = s.Split(separator, System.StringSplitOptions.RemoveEmptyEntries)

        MyBase.AddParsedSubObject(obj)
    End Sub
End Class
Carter
Thanks for your response. This may work but I know the example on Brian Chavez's site works as expected and doesn't involve this and I'd like to avoid making the RssFeedCollection a UserControl when it really is better suited as a simple collection of simple objects
Dustin Hodges
@EDIT: It only has to inherit from Control, my mistake.
Carter
I added a new solution. The old one is still useful to highlight the use of the "AddParsedSubObject" method, but the new one allows you to use the attributes properly.
Carter
A: 

Here is a simpler solution. I removed your AggregateSettings for this example; that code should work the same as before.

Here is the ASPX I am using.

    <uc:AggregateFeeds runat="server" ID="af">
        <Feeds>
            <RssResource Url="Test1" />
            <RssResource Url="Test2" />
            <RssResource Url="Test3" />
        </Feeds>
    </uc:AggregateFeeds>

Here is the User control code.

Partial Class AggregateFeeds
    Inherits System.Web.UI.UserControl

    Private _Feeds As New MyFeedsClass(Me)

    <PersistenceMode(PersistenceMode.InnerProperty)>
    Public ReadOnly Property Feeds As MyFeedsClass
        Get
            Return _Feeds
        End Get
    End Property
End Class

Public Class MyFeedsClass
    Inherits ControlCollection

    Sub New(ByVal owner As Control)
        MyBase.New(owner)
    End Sub

    Public Overrides Sub Add(ByVal child As System.Web.UI.Control)
        MyBase.Add(New Feed(child))
    End Sub
End Class


Public Class Feed
    Inherits HtmlGenericControl

    Sub New(ByVal GenericControl As HtmlGenericControl)
        MyBase.New()
        Me.Url = GenericControl.Attributes("Url")
    End Sub

    Public Property Url As String = String.Empty

    Public Overrides Function ToString() As String
        Return Me.Url
    End Function
End Class

It works assuming every child control is an html generic control. Do not use <uc in the child control so it won't attempt match an actual class.

Carter