in

ASP.NET Weblogs

Andy Smith's Blog

Page.RegisterStartupScript('Andy', 'MetaBuilders_WebControls_GainKnowledge();');

August 2004 - Posts

  • Composite controls that do their own databinding

    Most databound controls are written from the perspective that they will be databound to something, but the control writer doesn’t know what that would be. It is assumed that the control’s container will set the datasource and databind it. Therefore the same container knows about and handles the datasource, databinding, viewstate usages, event handling, etc. This works great.

    But then application developers come along and want self contained controls, like, say… A composite control which contains a DropDownList that always contains items databound from a database, and a button that performs magic on those items. The app developer of course calls on his local control guru to make him this control.

    This gives us 3 different people with 3 very different perspectives. There is the app developer, the custom control developer, and the asp.net team control developer who made the DropDownList control. The asp.net team guy does the Right Thing in exposing a DataSource property which DataBind uses, and an EnableViewState property which says whether or not the items created should be serialized to ViewState. He’s thinking, “If a consumer of my control does want to use viewstate, they can just bind on every request.” This is a reasonable position. Then on the other end we have the app developer who is using the custom control. He had the custom control made exactly BECAUSE he doesn’t want to think about datasources and databinding and such. However, the trickiness presents itself because he does still want to be in control of ViewState usage. Maybe some specific page is hit so often that ViewState should be disabled to save on bandwidth. “My custom control should just get the data every hit, I can cache at the data-access level to make up for it.” This is also a reasonable position.

    So now the custom control developer is in a bit of a pickle. The break in the chain is that the lower levels assume that one person would be both databinding and deciding on ViewState policy. That isn’t the case here.

    Ignoring the ViewState issue for a second, the straightforward solution to this is that the composite control does a “if not postback then databind” thing during Load, and override its DataBind method to databind its child DropDownList to the database. But then what happens if the app developer sets EnableViewState to false? Obviously the control is broken. So the control dev needs to databind during load in both first-request and disabled-viewstate states. This mostly solves the problem; however there is one last problem that I just don’t see a good solution to. Think of the case where an app dev sets the EnableViewState property to false during PreRender. In this case the Load-time DataBind never runs on subsequent requests even though there won’t be ViewState to load and thus the control is broken. Now don’t think that I imagine this is a common scenario, but it’s unfortunate. The only thing I can think is that you’d have to push the responsibility of DataBind() onto the app dev in this case. It’s not something I particularly like, because I think the app dev shouldn’t have to worry about the datasources of controls contained by it’s composite control children. However, it’s the best I’ve come up with so far.

    Any other ideas?

  • SP2 IE and about:mozilla

    So one change in IE that came with sp2, that I haven't seen anybody comment on, is that about:mozilla is gone. Before I installed sp2, i had my homepage set to about:mozilla because it gave a nice colored window as the default. But now all it does it show the word "mozilla" on a white background. If you are annoyed that your nice default html is no longer around, there is a little trick that not many people know, which is that you can put any html you want in an about url, and IE will show it. So just write a simple html page that displays any color/design you want and set that text as your homepage.
  • The Death of DynamicImage

    Of all the features cut from asp.net v2, the only one that I'm really going to miss is the loss of DyanmicImage and the Image Generation Service.  Not so much because it's a little harder to do dynamic images in my app without them, but because it's much harder to do dynamic images in my 3rd party server controls without them. The fact that you will be able progmatticly register custom IHttpHandlers in web.config mitigates the situation a bit, but not enough.
  • MetaBuilders and Telligent

    It's not often that a contractor says, "wow, I’m so incredibly happy about my latest client", but I see myself in that position. And seeing that I have a voice, I just need to tell y’all.

    I recently signed up to help out Telligent with their Community Server: Forums project.

    I can’t express how big the smile on my face was when I started working with Rob, Jason, Scott, and Terry Denham.

    You know, when I started MetaBuilders as a business, I had plans that that selling server control products would be my main business, but the bottom line has lead me to selling my services as an asp.net/control guru, helping others get their business done. It’s worked out pretty well for me, but I’ve never been more excited about work than I am right now.

    You’ll have to excuse me for the complete lack of useful technical information in this post… I know that’s what regular readers are used to seeing… but this is really such a high point in my recent history that I really need to publish it.

  • Web Control Developer Macro Goodness

    I've had these control dev macros sitting around for a while, and I thought I'd share them finally. First I looked for a wiki I could post them to, but I was surprised to not find one for vstudio addins/macros/whatnot. The closest I found was the gotdotnet developer powertoy site, but the whole submission/wait-for-approval thing just bores me. So, I decided to post the macro file here. I hope you find them useful:

    Imports EnvDTE
    Imports System
    Imports System.Diagnostics
    Imports System.IO
    Imports System.Text
    
    Public Module CodeGen
    
        ' Creates A Public Style Property
        ' Write the following line into the code area:
        ' <StyleType> <PropertyName>
        ' Select the text, run this macro
        Public Sub CreateStyleProperty()
            CheckLanguage()
            Dim ts As TextSelection = DTE.ActiveDocument.Selection
            Dim selection As String = ts.Text.Replace(vbTab, " ").Trim(" ")
            Dim words() As String = selection.Split(" ")
            If words.Length <> 2 Then
                Return
            End If
            Dim typeName As String = words(0)
            Dim propertyName As String = words(1)
            Dim memberName As String = "_" & propertyName.Substring(0, 1).ToLower() & propertyName.Substring(1)
            Dim code As New StringBuilder
            Dim codeWriter As New StringWriter(code)
            Try
                codeWriter.WriteLine("[")
                codeWriter.WriteLine("Category( ""Style"" ),")
                codeWriter.WriteLine("Description( """" ),")
                codeWriter.WriteLine("DefaultValue( null ),")
                codeWriter.WriteLine("DesignerSerializationVisibility(DesignerSerializationVisibility.Content),")
                codeWriter.WriteLine("PersistenceMode(PersistenceMode.InnerProperty),")
                codeWriter.WriteLine("]")
                codeWriter.WriteLine("public {0} {1} {{", typeName, propertyName)
                codeWriter.WriteLine("get {")
                codeWriter.WriteLine("if ( {0} == null ) {{", memberName)
                codeWriter.WriteLine("{0} = new {1}();", memberName, typeName)
                codeWriter.WriteLine("if ( IsTrackingViewState ) {")
                codeWriter.WriteLine("((IStateManager){0}).TrackViewState();", memberName)
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("return {0};", memberName)
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("private {0} {1};", typeName, memberName)
                codeWriter.Flush()
            Finally
                If Not codeWriter Is Nothing Then
                    DirectCast(codeWriter, IDisposable).Dispose()
                End If
            End Try
            ReplaceCode(code.ToString(), "CreateStyleProperty" & propertyName)
        End Sub
    
        ' Creates a Public ITemplate Property
        ' Write the following line into the code area:
        ' <PropertyName> [<TemplateContainerType>]
        ' Select the text, run this macro
        Public Sub CreateTemplateProperty()
            CheckLanguage()
            Dim ts As TextSelection = DTE.ActiveDocument.Selection
            Dim selection As String = ts.Text.Replace(vbTab, " ").Trim(" ")
            Dim words() As String = selection.Split(" ")
            If 1 > words.Length OrElse words.Length > 2 Then
                Return
            End If
            Dim propertyName As String = words(0)
            Dim templateContainer As String = ""
            If words.Length = 2 Then
                templateContainer = words(1)
            End If
            Dim memberName As String = "_" & propertyName.Substring(0, 1).ToLower() & propertyName.Substring(1)
            Dim code As New StringBuilder
            Dim codeWriter As New StringWriter(code)
            Try
                codeWriter.WriteLine("[")
                codeWriter.WriteLine("Browsable( false ),")
                codeWriter.WriteLine("DefaultValue( null ),")
                codeWriter.WriteLine("Description( """" ),")
                codeWriter.WriteLine("PersistenceMode(PersistenceMode.InnerProperty),")
                If templateContainer <> "" Then
                    codeWriter.WriteLine("TemplateContainer( typeof( {0} ) ),", templateContainer)
                End If
                codeWriter.WriteLine("]")
                codeWriter.WriteLine("public ITemplate {0} {{", propertyName)
                codeWriter.WriteLine("get {")
                codeWriter.WriteLine("return {0};", memberName)
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("set {")
                codeWriter.WriteLine("{0} = value;", memberName)
                codeWriter.WriteLine("ChildControlsCreated = false;")
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("private ITemplate {0};", memberName)
                codeWriter.Flush()
            Finally
                If Not codeWriter Is Nothing Then
                    DirectCast(codeWriter, IDisposable).Dispose()
                End If
            End Try
            ReplaceCode(code.ToString(), "CreateStyleProperty" & propertyName)
        End Sub
    
        ' Creates A Public ViewState-Backed Property
        ' Write the following line into the code area:
        ' <PropertyType> <PropertyName> = <DefaultValue>;
        ' Select the text, run this macro
        Public Sub CreateViewStateProperty()
            CheckLanguage()
    
            Dim ts As TextSelection = DTE.ActiveDocument.Selection
            Dim selection As String = ts.Text.Replace(vbTab, " ").Trim(" ")
            Dim words() As String = selection.Split(" ")
            If words.Length < 3 Then
                Return
            End If
    
            Dim typeName As String
            Dim propertyName As String
            Dim defaultValue As String
            typeName = words(0)
            propertyName = words(1)
            defaultValue = selection.Substring(selection.IndexOf("=") + 2)
            defaultValue = defaultValue.Substring(0, defaultValue.Length - 1)
            Dim code As New StringBuilder
            Dim codeWriter As New StringWriter(code)
            Try
                codeWriter.WriteLine("[")
                codeWriter.WriteLine("Bindable(true),")
                codeWriter.WriteLine("Category( """" ),")
                codeWriter.WriteLine("Description( """" ),")
                codeWriter.WriteLine("DefaultValue( {0} ),", defaultValue)
                codeWriter.WriteLine("]")
                codeWriter.WriteLine("public virtual " & typeName & " " & propertyName & " {")
                codeWriter.WriteLine("get {")
                codeWriter.WriteLine("Object state = ViewState[""{0}""];", propertyName)
                codeWriter.WriteLine("if ( state != null ) {")
                codeWriter.WriteLine("return ({0})state;", typeName)
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("return {0};", defaultValue)
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("set {")
                codeWriter.WriteLine("ViewState[""{0}""] = value;", propertyName)
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("}")
                codeWriter.Flush()
            Finally
                If Not codeWriter Is Nothing Then
                    DirectCast(codeWriter, IDisposable).Dispose()
                End If
            End Try
            ReplaceCode(code.ToString(), "CreateViewStateProperty" & propertyName)
        End Sub
    
        ' Creates A Public Event and Protected OnEvent Method
        ' Write the following line into the code area:
        ' <EventName> [<DelegateType> <EventArgsType>]
        ' Select the text, run this macro
        Public Sub CreateEvent()
            CheckLanguage()
            Dim ts As TextSelection = DTE.ActiveDocument.Selection
            Dim selection As String = ts.Text.Replace(vbTab, " ").Trim(" ")
            Dim words() As String = selection.Split(" ")
            If words.Length <> 1 AndAlso words.Length <> 3 Then
                Return
            End If
            Dim eventName As String = words(0)
            Dim delegateTypeName As String = "EventHandler"
            Dim eventArgsTypeName As String = "EventArgs"
            If words.Length = 3 Then
                delegateTypeName = words(1)
                eventArgsTypeName = words(2)
            End If
    
            Dim raiserName As String = "On" & eventName
            Dim eventObjectName As String = "event" & eventName
            Dim code As New StringBuilder
            Dim codeWriter As New StringWriter(code)
            Try
                codeWriter.WriteLine("#region {0} Event", eventName)
                codeWriter.WriteLine()
                codeWriter.WriteLine("public event {0} {1} {{", delegateTypeName, eventName)
                codeWriter.WriteLine("add {")
                codeWriter.WriteLine("Events.AddHandler( {0}, value );", eventObjectName)
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("remove {")
                codeWriter.WriteLine("Events.RemoveHandler( {0}, value );", eventObjectName)
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("}")
                codeWriter.WriteLine()
                codeWriter.WriteLine("protected void {0}( {1} e ) {{", raiserName, eventArgsTypeName)
                codeWriter.WriteLine("{0} handler = Events[ {1} ] as {0};", delegateTypeName, eventObjectName)
                codeWriter.WriteLine("if ( handler != null ) {")
                codeWriter.WriteLine("handler( this, e );")
                codeWriter.WriteLine("}")
                codeWriter.WriteLine("}")
                codeWriter.WriteLine()
                codeWriter.WriteLine("private static readonly Object {0} = new Object();", eventObjectName)
                codeWriter.WriteLine()
                codeWriter.WriteLine("#endregion")
                codeWriter.Flush()
            Finally
                If Not codeWriter Is Nothing Then
                    DirectCast(codeWriter, IDisposable).Dispose()
                End If
            End Try
            ReplaceCode(code.ToString(), "CreateEvent" & eventName)
        End Sub
    
        ' Surrounds the selected code with a try/catch/finally block
        Public Sub InsertTryCatchFinally()
            Dim endCode As New StringBuilder
            Dim writer As New StringWriter(endCode)
            Try
                writer.WriteLine("")
                writer.WriteLine("} catch( Exception ex ) {")
                writer.WriteLine("")
                writer.WriteLine("} finally {")
                writer.WriteLine("")
                writer.WriteLine("}")
                writer.Flush()
            Finally
                If Not writer Is Nothing Then
                    DirectCast(writer, IDisposable).Dispose()
                End If
            End Try
            SurroundSelectionWithCode("try {" & Environment.NewLine, endCode.ToString(), "InsertTryCatchFinally")
        End Sub
    
        ' Replaces the selected code with the given new code
        Private Sub ReplaceCode(ByVal newCode As String, ByVal undoContextName As String)
            Dim selection As TextSelection = DTE.ActiveDocument.Selection
            Dim undoContextOpened As Boolean = False
            Try
                If Not DTE.UndoContext.IsOpen Then
                    Call DTE.UndoContext.Open(undoContextName, False)
                    undoContextOpened = True
                End If
                ' Replace the text and SmartFormat it
                selection.Delete()
                selection.Insert(newCode, vsInsertFlags.vsInsertFlagsInsertAtStart)
                selection.TopPoint.CreateEditPoint.SmartFormat(selection.BottomPoint)
            Catch ex As Exception
                MsgBox("Unable To Perform Code Alteration:" & Environment.NewLine & ex.Message, MsgBoxStyle.OKOnly, "Problem Running Macro")
            Finally
                If undoContextOpened Then
                    Call DTE.UndoContext.Close()
                End If
            End Try
        End Sub
    
        ' Surrounds the selected code with the given prefix and suffix code
        Private Sub SurroundSelectionWithCode(ByVal startCode As String, ByVal endCode As String, ByVal undoContextName As String)
            Dim selection As TextSelection = DTE.ActiveDocument.Selection
            Dim undoContextOpened As Boolean = False
            Try
                If Not DTE.UndoContext.IsOpen Then
                    Call DTE.UndoContext.Open(undoContextName, False)
                    undoContextOpened = True
                End If
                Dim tp As EditPoint = selection.TopPoint.CreateEditPoint()
                selection.TopPoint.CreateEditPoint().Insert(startCode)
                Dim bp As EditPoint = selection.BottomPoint.CreateEditPoint()
                bp.Insert(endCode)
                tp.SmartFormat(bp)
            Catch ex As Exception
                MsgBox("Unable To Perform Code Alteration:" & Environment.NewLine & ex.Message, MsgBoxStyle.OKOnly, "Problem Running Macro")
            Finally
                If undoContextOpened Then
                    Call DTE.UndoContext.Close()
                End If
            End Try
        End Sub
    
        ' Ensures that the macro is modifying only c# documents
        Private Sub CheckLanguage()
            If DTE.ActiveDocument.Language <> "CSharp" Then
                Throw New System.Exception("This Macro only works on C# code")
            End If
        End Sub
    End Module
    
More Posts