Simple Custom Server Control getting 'Value cannot be null. Parameter name: child' - asp.net

Building my first server control. Getting the "Value cannot be null. Parameter name: child" error in debug at the point indicated in my code. The textbox is created, then becomes nothing when the parent's page_load event is called. The page_load event fires between the OnInit and CreateChildControls in my control. Examples are hard to come by.
Public Class ContactForm
Inherits WebControl
#Region "Local variables"
Private _ShowFirstName As Boolean
Private tbFirstName As TextBox
#End Region
<Browsable(True), _
Bindable(True), _
Category("Appearance"), _
DefaultValue(True), _
Localizable(True), _
Description("Show First Name field")> _
Public Property ShowFirstName() As Boolean
Get
Return _ShowFirstName
End Get
Set(value As Boolean)
_ShowFirstName = value
End Set
End Property
Protected Overrides Sub OnInit(e As EventArgs)
If ShowFirstName = True Then
Dim tbFirstName As New TextBox
tbFirstName.ID = "tbFirstName"
tbFirstName.MaxLength = 30
tbFirstName.Text = "IM HERE"
End If
MyBase.OnInit(e)
End Sub
<<<<< --- NOTE: The Page_Load event of the parent fires here ->>>>>
Protected Overrides Sub CreateChildControls()
Me.Controls.Add(tbFirstName) <<---Error: Value cannot be null. Parameter name: child
If HasChildViewState Then
ClearChildViewState()
End If
MyBase.CreateChildControls()
End Sub
End Class

i haven't done this before but at the top you say, DefaultValue(True); does that mean it must have a default value? maybe if you set a default value OnInit (or make the prop false).

Okay, here is how I got it working.
I basically move the OnInit code into the CreateChildControls...duh!
I created a function (buildForm) that declares and adds all controls in one step like below.
Protected Overrides Sub CreateChildControls()
buildForm()
If HasChildViewState Then
ClearChildViewState()
End If
MyBase.CreateChildControls()
End Sub
SIDEBAR: Web User Control vs Custom Server Control
I spent the last 4-5 days comparing and experimenting with both controls. I've done a lot of User Controls over the years and just copied them into applications. Hated the maintenance that created.
I spent a full day trying to compile my current web controls into single dll that could be used kind of like server controls. I found it tedious and limited.
Many articles, including MSDN, had me scared of server controls saying it is much harder than a web control to create. I can say...so far...I am digging the Server Control. I wish I had done this in the beginning. True reuse. Don't fear the server control.

Related

Property Value gets cleared on PostBack

I have to Get and Set the BackColur of Current Page. So user can use this property from any other page
And this is my code
Private _BackgroundColour As System.Drawing.Color
Public Property MenuBackColour() As System.Drawing.Color
Get
Return _BackgroundColour
End Get
Set(ByVal value As System.Drawing.Color)
_BackgroundColour = value
End Set
End Property
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
Try
NavigationMenu.BackColor = MenuBackColour
Catch ex As Exception
End Try
End Sub
But the problem is Property value gets cleared on PostBack so I'm not able to get the BackColor value from other page
On post back only the input elements are posted.
So they are the same as before the post back, what they have before the post back, have and now. So actually you do not "get" this parameters on code behind, you only "set" them and on server controls with viewstate on, you can remember this parametres on post back - but you can not change them on client side and expect to read this change on server.
Here the workaround is. Either use the viewstate of the page to save some values and keep them on post back, ether use input hidden elements to have them on post back.
If your main purpose is to retain or share the backcolor of some control between different pages, there are many ways to do it in ASP.NET. You can keep the value in cookies or sessions, or cache.
As per request, if Property must be used, I create a Default.aspx as the following:
Public Class _Default
Inherits Page
Private Shared _BackgroundColour As System.Drawing.Color = Drawing.Color.Azure
Public Shared Property MenuBackColour() As System.Drawing.Color
Get
Return _BackgroundColour
End Get
Set(ByVal value As System.Drawing.Color)
_BackgroundColour = value
End Set
End Property
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
MenuBackColour = Drawing.Color.Red
Response.Redirect("Default1")
End Sub
End Class
It will automatically be redirected to Default1.aspx, where there is a Label control in that page. It will use _Default.MenuBackColour as the label's backcolor:
Public Class Default1
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Label1.BackColor = _Default.MenuBackColour
End Sub
End Class
Again, this approach is not recommended. If Default.aspx has never been run (at least once), MenuBackColour's value may not be what you think it is. I won't encourage people to retain any static variable or property in an .aspx page for sharing.
In web applications, variable values simply get erased. But it is very simple to persist these values. They may be persisted using the Viewstate object. Before the postback is invoked, the variable's value is saved in a viewstate object. In the recieving page, the viewstate's value may be retrieved back.
//Save the value in ViewState object before PostBack
ViewState("myColour")="Black";
//Retrive the value from ViewState after the PostBack
myColourProperty=ViewState("myColour").ToString();

using control states in server control

I have a server control that I am trying to get to save properties as control states but for some reason the properties are not persisting across partial postbacks.
The psuedo code is as follows:
Public Class FileUpload
Inherits ScriptControl
Implements INamingContainer, IPostBackEventHandler
Public Property newFileExt() As String
Get
Dim foundList As String = DirectCast(ViewState(Me.UniqueID & "_fileExt"), String)
If foundList IsNot Nothing Then
Return foundList
Else
Return String.Empty
End If
End Get
Set(ByVal value As String)
ViewState(Me.UniqueID & "_fileExt") = value
End Set
End Property
Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
MyBase.OnInit(e)
Page.RegisterRequiresControlState(Me)
End Sub
Protected Overrides Function SaveControlState() As Object
Dim controlState(6) As Object
controlState(0) = MyBase.SaveControlState()
controlState(1) = newFileExt
Return controlState
End Function
Protected Overrides Sub LoadControlState(ByVal savedState As Object)
Dim controlState() As Object
controlState = CType(savedState, Object)
MyBase.LoadControlState(controlState(0))
newFileExt = CType(controlState(1), String)
End Sub
end class
On this control is an asyncFileUpload ajaxcontroltoolkit control and a button. I have an event for upload complete:
Protected Sub SaveUploadedFile(ByVal sender As Object, ByVal e As AjaxControlToolkit.AsyncFileUploadEventArgs) Handles asyncFileUpload.UploadedComplete
newFileExt= "Some Value"
end sub
Protected Sub bntSelectResults_click(ByVal sender As Object, ByVal e As EventArgs) Handles bntSelectResults.Click
If (newFileExt= "") Then
'this always returns as empty
End If
end sub
So, UploadedComplete is complete it should set the controls state. then, when the user click the button it should read it. Through debugging, I can see that it is set correctly in UploadedComplete event but null when read. Is this due to the cycle of the page or something?
Thanks
jason
EDIT
I traced out the path for how the page cycle is running:
User clicks the async file upload control's browse button and selects a file. This causes the upload process to start
a. OnInit gets called
b. LoadControlState gets called
c. OnLoad gets called
d. asyncFileUpload.UploadedComplete gets called and I set the newFileExt property
here.
e. SaveControlState gets called. newFileExt is set here properly
User clicks a button on the control that initiates another partial postback/update of the update panel
a. OnInit gets called
b. LoadControlState gets called. I can see that the newFileExt property is not set
c. OnLoad gets called
d. Buttons click event gets called and the property is read (which is no longer set)
e. SaveControlState gets called and cycle ends
So, as best as I can tell, the asyncFileUpload application has issues with ViewStates/ControlStates. I ended up just just using sessions.

Lost HttpContext in a custom event

I'm getting the 'Response is not available in this context error' calling the following function:
Private Sub ReloadPage(ByVal inNumber As Integer) Handles tempaux.Advertise
'Response.Redirect("tope.aspx?dep=" & CStr(inNumber))
Response.Write("<script>window.open('tope.aspx?dep= & CStr(inNumber)','topFrame');</script>")
End Sub
I've changed the line adding the System.Web.HttpContext.Current before Response.Write and I get 'Object reference not set to an instance of an object'.
To give some background: tope.aspx is, as you can see, opened in topframe. As soon as it loads it starts a CustomTimer object I've defined:
Public Class tope
Inherits System.Web.UI.Page
Public funciones As funciones = New funciones
Dim WithEvents tempaux As CustomTimer = Global.objCustomTimer
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim inUserProfile As Int64
Try
tempaux.StartTimer()
Catch ex As Exception
'bla bla
End Try
As you can see I've declared the CustomTimer in the Global.asax. The CustomTimer object raises an Advertise event every 5 seconds and passes 'inNumber' as a parameter for the tope.aspx page to refresh some label, a simple thing. CustomTimer is a class I made to manage the timer, it doesn't inherits any other class( For what I've learned in my search it has to inherit some httpthing but I'm not sure). I'm guessing that at some point the httpcontext is being lost (I've searched in google and I couldn't figure its lifecycle or whatever information that tells me why it 'dies). Can anyone help me to find out what is the problem?
thanks
Your timer exists outside of the tope page class, so it is possible that the timer event is firing after the response from the page is complete and there is no longer a HttpContext.Current instance.
It sounds like what you are trying to do is to change an advertising banner on a page every 5 seconds, once the page is loaded. You need to do that using a javascript timer, which would fire every 5 seconds and make a request back to your web server for a new advertisement.

ASP.NET BasePage Class Page_Load not Fired on Postback

I have the following BasePage class...
Public Class BasePage
Inherits System.Web.UI.Page
Private litError As Literal
Protected SO As Session
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
SO = Session.Item("SO")
If SO Is Nothing Then
Session.Abandon()
Response.Redirect("~/timeout.htm")
End If
litError = Page.FindControl("litError")
If litError IsNot Nothing Then
litError.Visible = False
End If
End Sub
Protected Sub ShowMessage(ByVal Message As String)
Show(Message, "message")
End Sub
Protected Sub ShowError(ByVal Message As String)
Show(Message, "error message")
End Sub
Protected Sub ShowSuccess(ByVal Message As String)
Show(Message, "success message")
End Sub
Private Sub Show(ByVal Message As String, ByVal CssClass As String)
If litError IsNot Nothing Then
litError.Text = String.Format("<span class=""{0}"">{1}</span>", CssClass, HttpUtility.HtmlEncode(Message))
litError.Visible = True
End If
End Sub
End Class
Every page in this application inherits this class. The SO variable represents a custom session class, that is very simple and just holds a couple of basic settings to be used throughout the application. The problem is, my Page_Load in this base class does not fire if a natural postback occurs (in this case, it is a gridview postback by sorting/paging). Then later in my code when I reference SO, I get a null reference exception because it hasn't been pulled from session.
Why doesn't the base Page_Load fire?
Try moving your code into the Page_Init event.
Microsoft has some info on each event in the lifecycle http://msdn.microsoft.com/en-us/library/ms178472.aspx. This MSDN page tells you what types of things you should handle in each event.
You might want to think about implementing SO as a property, where the Get does (not sure if this is correct VB...)
Dim so As Session = Session.Item("SO")
If so Is Nothing Then
Session.Abandon()
Response.Redirect("~/timeout.htm")
End If
return so
It could be that something else is happening in the Init events that is causing it to fail. So rather than it not being called it just hasn't been called yet.
It could be that the autoevent wireup isn't wiring it up correctly, tend to override the OnInit event and attach the events manually myself, I have also read somewhere that this improves perfomance by not requiring the framework to do heaps of reflection on every post.
But back to your problem... try making the SO object private and create a property accessor for it that first checks that if the private is set, if not set it, before returning the private variable. If it isn't set and can't be found then it can abort the same way you are doing in the Load. This means that to load the variable you won't be dependent on the Page_Load from firing and thus the SO object should be available for you during the init routines, if you need it.

"Re-Raising" Custom Events in .NET (ASP.NET in this case)

I'm working on an ASP.NET page, using VB.NET and I have this hierarchy:
Page A
- Web User Control 1
- Web User Control A
- Web User Control B
- Web User Control C
I need to raise an event from Web User Control B that Page A will receive (the event flow will be Web User Control B -> Web User Control 1 -> Page A).
My only approach so far has been this:
1) Add a custom event declaration to both Web User Control B and Web User Control 1 and simply RaiseEvent twice until it gets to Page A (this seems ugly and I don't particularly like it).
My other idea was to create a custom Event class that inhertis from some magical base Event class and create an instance of it in both Web User Control B and Web User Control 1, but that is proving fruitless because I can't find any event base classes (maybe b/c they're aren't any, since it appears to be a keyword, not a class name).
Any help would be appreciated! Thanks and happy coding!
You can use the BubbleEvent concept to do this. A BubbleEvent goes up the control hierarchy until someone handles it. The GridView and Repeater controls do this with their Row/ItemCommand events.
You could implement it into WebUserControl1, turning it into a standard event for the page (like the GridView does):
Class UserControl1 ' Parent
Protected Override Function OnBubbleEvent(sender as Object, e as EventArgs) as Boolean
Dim c as CommandEventArgs = TryCast(e, CommandEventArgs)
If c IsNot Nothing Then
RaiseEvent ItemEvent(sender, c)
Return True ' Cancel the bubbling, so it doesn't go up any further in the hierarchy
End If
Return False ' Couldn't handle, so let it bubble
End Function
Public Event ItemEvent as EventHandler(Of CommandEventArgs)
End Class
Class UserControlB ' Child
Protected Sub OnClicked(e as EventArgs)
' Raise a direct event for any handlers attached directly
RaiseEvent Clicked(Me, e)
' And raise a bubble event for parent control
RaiseBubbleEvent(Me, New CommandEventArgs("Clicked", Nothing))
End Sub
Protected Sub OnMoved(e as EventArgs)
' Raise a direct event for any handlers attached directly
RaiseEvent Moved(Me, e)
' And raise a bubble event for parent control
RaiseBubbleEvent(Me, New CommandEventArgs("Moved", Nothing))
End Sub
End Class
Class PageA
Sub UserControl1_ItemEvent(sender as Object, e as CommandEventArgs) Handles UserControl1.ItemEvent
Response.Write(sender.GetType().Name & " was " & e.CommandName)
End Sub
End Class
Or, do it directly in the page. UserControlB (Child) is the same as above, and UserControl1 (Parent) doesn't need to do anything special - OnBubbleEvent defaults to returning False, so the event bubbles up:
Class PageA
Protected Override Function OnBubbleEvent(sender as Object, e as EventArgs) as Boolean
If sender Is UserControlB Then
Dim c as CommandEventArgs = TryCast(e, CommandEventArgs)
If c IsNot Nothing Then
Response.Write(sender.GetType().Name & " was " & c.CommandName)
Else
Response.Write(sender.GetType().Name & " raised an event, with " & e.GetType().Name & " args)
End If
Return True ' Cancel the bubbling, so it doesn't go up any further in the hierarchy
End If
Return False ' Not handled
End Function
End Class
If your initial event is from a server control (like a Button.Click), then it will have been coded to already raise the bubble event - so UserControlB (Child) doesn't need to do anything to get that to the parent either. You just need to call RaiseBubbleEvent for any of your custom events, or if you want to transform the EventArgs in some way.
The real question here is is the actual action in Web UserControl B something that should notify both, OR, is WebUserControl1 responsible for some processing BEFORE notifying the page.
If each step of the chain has a specific action, your method of raising two events is proper. If it is in a manner where the event just needs to notify everyone you will want to look at different subscription methods to communicate.
Create a Assembly (or Namespace) that is referenced by everything.
Create a interface with the methods you need.
Create a class that manages objects that implemented the interface.
Have Page A implement the interface
Have Page A register itself with the manager class done in step #3
Now Web UserControl B can raise the event by retrieving the page from the manager and calling the method on the interface that raises the event you need.
You avoid tightly coupling the page to the webcontrol because you are using a interface.
Likely you will find that you will have a multiple interface for different areas of your project. For example in my CAM project I have a interface for the Setup Parameters UI, the Shape Entry UI, and the Cut Entry UI. On our website we have different product categories that uses different interfaces. (Software, Machinery, Services, etc).
You can create a public method in Page A which gets called from Web User Control B instead of raising an event up the entire control tree.
This would not be my first choice since this will cause tight coupling between those classes but I hope it solves your problem.
Sample Page:
Public Partial Class TestPage
Inherits Page
Public Sub PerformAction()
'Whatever needs to be done on Page A
End Sub
End Class
Sample User Control:
Public Partial Class TestControl
Inherits UserControl
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'This will call the Page, obviously this will only
'work when the control is on TestPage
CType(Page, TestPage).PerformAction()
End Sub
End Class

Resources