If I have a master 'composite custom server control', with several child custom controls, is it possible, for the master control to share its viewstate with the child controls, which are also custom composite server controls,(all of the controls are composite custom server controls)?
To expand a little more, say I have a Person control, Address control, Phone control and Notes control. Address, Phone and Notes can exist as either independant controls, or as part of the Person control. Since each control is responsible for its own viewstate, and stores required data in it, so it can render/postback etc, it ends up that there is a lage duplication in the viewstate, since the Person control stores all the data, and then each child control stores its own data again. Also, to further complicate things, the Person Control adds the child controls dynamically, so its possible to add a 2nd address/phone number/note etc,which can cause yet a larger viewstate(up to 1MB).
In the case of when all the Address/Phone/etc controls are children of the the Person Control, is it possible for me to somehow share common viewstate data, so I dont have 2/3/4 copies of some stuff in the viewstate, and when they are not components, just act as normal?
I do have one idea for a solution, but its fairly nasty imo, where I could modify the Person control, expose its viewstate or the data, and then in the child control, would check the control hierarchy, and if the child control is part of a Person, dont use to its own viewstate, use the exposed one. This would require me to rework alot of the existing code, which I'd rather avoid if possible.
The first question I'd ask is, "How much of this data do you really need to keep on the ViewState?" For example, is there enough information in a typical page request to be able to rebuild all of the information that is in some of these controls?
I find that ASP.NET typically uses ViewState far more than I really need it to, and if you're getting a 1MB viewstate, I suspect the same is probably true for you. I highly recommend reading this article to get a more full understanding of how ViewState works, what causes values to get saved to ViewState, and how to avoid using it where it's not necessary. You may be able to solve the bulk of your problem simply by doing more of your work in Page_Init instead of Page_Load, for example.
You can "share" a ViewState between any number of objects by passing it around as a StateBag type, or by using delegate functions that return the ViewState that needs to be shared. The usage of this, however; should be limited to very specific circumstances, because typically controls use Properties to expose their ViewState data to other objects (See the link #StriplingWarrior posted here). That being said, here is some example code:
User Control: SharedState.ascx
<%# Control Language="vb" AutoEventWireup="false" CodeBehind="SharedState.ascx.vb" Inherits="TestSite.SharedState" %>
User Control: SharedState.ascx.vb
Public Class SharedState
Inherits System.Web.UI.UserControl
Public Delegate Function GetStateDelegate() As StateBag
Public Const SharedValueKey = "The key used to access the ViewState dictionary"
Public Property GetState() As GetStateDelegate
' Different ways to get values
Public Function GetTypedValue(Of TValue)() As TValue
Return CTypeDynamic(GetValue(), GetType(TValue))
End Function
Public Function GetValue() As Object
' Use the delegate to get the view state from the parent
Return GetState.Invoke()(SharedValueKey)
End Function
Public Function GetValue(state As StateBag) As Object
Return state(SharedValueKey)
End Function
' Different ways to set values
Public Sub SetTypedValue(Of TValue)(value As TValue)
Me.SetValue(value)
End Sub
Public Sub SetValue(value As Object)
GetState.Invoke()(SharedValueKey) = value
End Sub
Public Sub SetValue(state As StateBag, value As Object)
state(SharedValueKey) = value
End Sub
' Set ViewState value using a key and the delegate
Public Sub SetStateWithKey(key As String, value As Object)
GetState.Invoke()(key) = value
End Sub
' Get ViewState value using a key and the delegate
Public Function GetStateWithKey(key As String) As Object
Return GetState.Invoke()(key)
End Function
End Class
Page: SharedStatePage.aspx
<%# Page Language="vb" AutoEventWireup="false" CodeBehind="SharedStatePage.aspx.vb" Inherits="TestSite.SharedStatePage" %>
<%# Register src="SharedState.ascx" tagname="SharedState" tagprefix="uc1" %>
<uc1:SharedState ID="SharedState1" runat="server" />
<uc1:SharedState ID="SharedState2" runat="server" />
Page: SharedStatePage.aspx.vb
Public Class SharedStatePage
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
' Set up SharedState 1 & 2 to use the delegate '
SharedState1.GetState = AddressOf GetState
SharedState2.GetState = AddressOf GetState
' Set the view state from the page '
ViewState(SharedState.SharedValueKey) = 23
' Read the view state from the user controls '
Dim sharedInt As Integer
sharedInt = SharedState1.GetTypedValue(Of Integer)()
sharedInt = SharedState1.GetValue()
sharedInt = SharedState1.GetValue(ViewState)
sharedInt = SharedState2.GetValue()
' Set the view state from one of the user controls '
SharedState2.SetTypedValue(Of String)("Hello")
Dim sharedStr As String = ViewState(SharedState.SharedValueKey)
sharedStr = SharedState1.GetTypedValue(Of String)()
sharedStr = SharedState2.GetValue()
' Use a different key to set and get view state data '
ViewState("MyKey") = New With {.Name = "Some Object", .Value = 46}
Dim myObj = SharedState1.GetStateWithKey("MyKey")
SharedState2.SetStateWithKey("MyKey", "New Value")
myObj = ViewState("MyKey")
End Sub
Private Function GetState() As StateBag
Return ViewState
End Function
End Class
Use with caution, or don't use with abandon.
Related
I have a web grid, pick your favorite because I really don't think it changes the answer to this question - Microsoft, Telerik, Dev Express - and I have it set to use paging.
I'm using Telerik but I'm not putting that in the tags because I don't think the problem is in the grid itself, I think it's in how I'm locating the databinding code.
I have a class that inherits from WebConrol and when I page the grid this code runs again, which rebinds the grid. My issue is hitting the database (and waiting) multiple unnecessary times.
I'll take an answer for ~any~ type of web data grid. I think I can translate it if necessary to my type of grid.
Imports System.Data
Imports System.Web.UI.WebControls
Imports Telerik.Web.UI
Imports System.Web.UI
Imports System.Drawing
<ToolboxData("<{0}:ReportGrid runat=server></{0}:ReportGrid>")> _
<ToolboxBitmap(GetType(ReportGrid), "MyCompany.Web.UI.WebControls.ReportGrid.bmp")> _
Public Class ReportGrid
Inherits CompositeControl
Private _Grid As RadGrid
Private ReadOnly Property Grid As RadGrid
Get
If _Grid Is Nothing Then
_Grid = New RadGrid
_Grid.ClientSettings.Scrolling.UseStaticHeaders = True
'_Grid.Skin = "Black"
_Grid.AllowFilteringByColumn = True
_Grid.EnableAjaxSkinRendering = True
_Grid.AllowPaging = True
_Grid.AllowSorting = True
_Grid.ViewStateMode = System.Web.UI.ViewStateMode.Enabled
_Grid.GridLines = GridLines.Both
End If
Return _Grid
End Get
End Property
Protected Overrides Sub CreateChildControls()
Controls.Add(Grid)
'If Not Page.IsPostBack Then
'This code runs on every page load
Dim DataTable As DataTable = MyCompany.Library.Database.GetDataTable("My Connection String", "My Query")
Grid.DataSource = DataTable
Grid.DataBind()
'End If
End Sub
End Class
As a Telerik user myself, I'd recommend using the RadGrid's OnNeedDataSource to handle paged data. More info on how that works here:
http://demos.telerik.com/aspnet-ajax/grid/examples/data-binding/simple-vs-advanced/defaultcs.aspx
If you have hundreds of thousands or millions of records to display in your grid, you'd want to look at using a custom paging solution - i.e. only pull back the records from the datasource that are required to display on that current grid page, instead of pulling in all records every time a postback occurs.
Take a look at this example on how to implement custom paging in the Telerik RadGrid:
http://demos.telerik.com/aspnet-ajax/grid/examples/functionality/paging/custom-paging/defaultcs.aspx
I have a web page (aspx)- Purchasing page, with a ascx toolbar - Export Toolbar, that is used to export the data (either .xls or .csv).
I need to grab the Name of the Supplier from the Purchasing page and insert that value into the name of the export file on the ascx toolbar.
On the Purchasing page there is a ddl where the user can select the supplier and a grid that will display all the data. Above the grid there is the tool bar with an export button. I need to be able to grab the text of the dropdown list and utilize that on the ExportToolbar.ascx.vb page so I can take that text and insert it into the name.
I was trying to use a public property get and set method but it was not working. How would I go about grabbing that selected text from the Supplier ddl?
Conventional thinking goes like this: an ascx can be hosted on any aspx page. So usually it is bad form for an ascx to access properties of its host page. It is much more proper for the ascx to have a public property and the aspx will push the value into the ascx (as needed).
However, if you really want to go this route, the .Page property (of the ascx) referrs to the host page. If you cast it to the (stronger) type(name) of the host, you can get to the hosts properties. Like this:
'if your host page is called HostPage (and the class name is the same)
Dim host as HostPage = CType(me.Page, HostPage)
'now refer to the controls on the host (aspx) page
dim example as string
example = host.txtExample.Text
Keep in mind, this will cause errors if your ascx is hosted on several pages.
You can use an event form this purpose. Define the event on the UserControl like this:
Public BeforeExportEventArgs
Inherits EventArgs
Public Property FileName As String
End Class
Public Class ToolbarControl
Inherits UserControl
Public Event BeforeExport As EventHandler(Of BeforeExportEventArgs)
Public Sub btnExport_Click(sender As Object, e As EventArgs) Handles btnExport.Click
' Retrieve File Name
Dim beforeExpEventArgs As New BeforeExportEventArgs()
RaiseEvent BeforeExport(Me, beforeExpEventArgs)
' Set default filename if not provided by an event handler
If String.IsNullOrEmpty(beforeExpEventArgs.FileName) Then
beforeExpEventArgs.FileName = "DefaultFileName.csv"
End If
' Export data
End Class
Add an event handler to the form that hosts the UserControl:
Public Class WebForm1
Inherits Page
' ...
Public Sub expToolbar_BeforeExport(sender As Object, e As BeforeExportEventArgs) Handles expToolbar.BeforeExport
e.FileName = ddlSupplier.Text + ".csv"
End Sub
' ...
End Class
This way, you avoid tight coupling between the UserControl and the Page. The pages that host the UserControl can set a specific filename, but don't have to.
What I ended up doing was this-
On the ascx page I created a public property-
Public Property SupplierSelection As String
Get
Return Convert.ToString(ViewState.Item("SupplierSelection"))
End Get
Set(ByVal value As String)
ViewState.Add("SupplierSelection", value)
End Set
End Property
And then on the aspx page I added this on the load grid event-
SupergridToolbar1.SupplierSelection = ddlStrategy.SelectedItem.Text.ToString()
I was then able to use the Supplier Selection on the ascx page. Thanks for the help!
Asp.Net 4.0
Is it possible to call a function in a user control from the parent page in code behind?
The user controls are created by other programmers, however each will have a common public function that i look for called "Output" which returns values in need for the main page. The main page has a main menu, so only one user control will display based upon the main menu selection.
Folder in WebApp with user controls:
> UserControls
ProductA.ascx
ProductB.ascx
ProductC.ascx
ProductD.ascx
etc...
Code behind when user clicks a menu button:
Dim product As string = Session("MenuProduct")
Dim uc As UserControl
uc = LoadControl("~/UserControls/" & product & ".ascx")
InputPanel.Controls.Add(uc)
Function in user control I would like to access. This will be a common Function.
Public Function Output(ByVal ParamArray expr() As Object) As Object
...code
End Function
You can call functions in a userControl from a parent page if they have the right access modifier. Public I believe
Dim product As string = Session("MenuProduct")
Dim uc As UserControl
uc = LoadControl("~/UserControls/" & product & ".ascx")
InputPanel.Controls.Add(uc)
if (uc is TypeWithMethod )
{
Dim ucTyped As TypeWithMethod
ucTyped = uc As TypeWithMethod
ucTyped.Method()
}
Vb syntax is rusty, kept putting ; after every line.
Here are little details to what andrew is talking about...
Public Interface IGroupable
Function GetGroupId() As Int32
End Interface
Class MyUserControl
Inherits UserControl
Implements IGroupable
Public Function GetGroupId() As Integer Implements IGroupable.GetGroupId
Return 1
End Function
End Class
Class MyPage
Inherits Page
Dim id As Int32 = DirectCast(myUserControl, IGroupable).GetGroupId()
End Class
So basicly create a Interface, have all of your user controls implement that interface, and then when you load any of your user controls within webpage...simply CAST them to the the type of your interface name....your interface will have the access to the function.
I have the following steps in my webpage
1) User Logs in and I set the following session variables
Session("userName") = reader_login("useremail").ToString()
Session("userId") = reader_login("user_ID").ToString()
Session("firstName") = reader_login("firstName").ToString()
2) Now on my logged in VB.NET templates I reference a MasterPage called LoggedIn.Master. In Which I added the following method to check for the above null session variables. And if they null to redirect back to login page.
Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
'#Check that User is Logged in, if not redirect to login page
If (Session("userId") Is Nothing) Or (Session("userName") Is Nothing) Or (Session("firstName") Is Nothing) Then
Response.Redirect(ConfigurationManager.AppSettings("site_base_url").ToString & "login/", False)
End If
3) Now my question is if I want to use any above 3 Session variables in different .net templates or usercontrols referencing the above master page do i need to AGAIN add the check
If (Session("userId") Is Nothing) Or (Session("userName") Is Nothing) Or (Session("firstName") Is Nothing) Then
Response.Redirect(ConfigurationManager.AppSettings("site_base_url").ToString & "login/", False)
End If
In the respective pages or will the check in master page do. Because at the moment i.e. if in a usercontrol I attempt to do i.e.
customerName.Text = Session("userName").ToString()
or
Response.Write(Session("userName").ToString())
I am getting the error
Object reference not set to an instance of an object.
customerName.Text = Session("userName").ToString()
You can write a wrapper around the Session to handle null values and just call the wrapper when you access the items:
Public Class SessionWrapper
Public Shared ReadOnly Property Item()
'Access session here and check for nothing
End Property
End Class
And use it like this
SessionWrapper.Item("itemName")
In answer to your question - as long as the masterpage checks the session and redirects before all your controls and page code make a reference to Session, you should be fine.
You were using OnInit() which seems reasonable, but see this article for a good understanding of the timing of events.
Incidentally, I strongly discourage the use of ad-hoc calls to Session in your page and control code. Instead, I recommend you create a static SessionManager class that does the Session referencing for you. That way, you get to benefit from strong typing, and won't be able to accidentally make hard-to-debug 'session key' typos in your code like Session["FiirstName"]. Also, you can incorporate your null-session check right into the call for the session value:
EXAMPLE (in C#, sorry!)
public static class SessionManager
{
private static void EnsureUserId()
{
if (Session["userId"] == null)
{
Response.Redirect("YourLogin.aspx", false);
}
}
public static string FirstName
{
get
{
EnsureUserId();
if (Session["firstName"] == null)
Session["firstName"] = "";
return (string)Session["firstName"];
}
set
{
Session["firstName"] = value;
}
}
}
You can create an http module that asks about the session objects and if they are null, it will redirect to the login page and by developing this http module, in each page request the module will do the check and then you can use it normally without checking.
A better way to handle this would be to add a base class for all controls that require this session variables to be present. You can then add properties to wrap access to the session and other cool stuff and the check will work even if the controls are used with a different master page.
I have an aspx page with three web controls: one to control the List Users page, one to control the Edit Users page, and one to control the Add User page. I have discovered a method for accessing these elements, but it seems to be limited. Here is what I have done:
Protected Sub editUser(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewEditEventArgs)
'set selected user from gridview.
Dim index As Integer = e.NewEditIndex
Dim userId As Integer = gvListUsers.DataKeys(index).Value
Session.Item("SelectedUserId") = userId
'show edit page, hide list and add page.
With Page.Form.Controls(1)
.Controls(getControlId("loadAddUser")).Visible = False
.Controls(getControlId("loadEditUser")).Visible = True
.Controls(getControlId("loadListUser")).Visible = False
End With
End Sub
The getControlId function looks like this:
Public Function getControlId(ByVal control As String) As Integer
Dim enumer As System.Collections.IEnumerator = Page.Form.Controls.Item(1).Controls.GetEnumerator
Dim i As Integer
For i = 0 To (Page.Form.Controls.Item(1).Controls.Count - 1)
If Page.Form.Controls(1).Controls.Item(i).ID = control Then
Return i
End If
Next
Return Nothing
End Function
This works in most cases. However, I am unable to access the "enabled" attribute of these web controls. Why is this, and how might I access that attribute?
Thanks :)
You could raise events from your UserControls which you subscribe to in the parent ASPX page. In the parent page event action you could enable/disable your controls,
Here's an example of events in UserControls: http://codebetter.com/blogs/brendan.tompkins/archive/2004/10/06/Easily-Raise-Events-From-ASP.NET-ASCX-User-Controls.aspx
Something else to think about: are you getting any benefit from moving this code into usercontrols? Would any of the individual controls be re-usable on their own? Creating tightly coupled controls that rely on each other being present doesn't give you much re-usability of the individual controls.
Visible is a property provided by the System.Web.UI.Control class, which is why you can access it directly. Enabled is not a property on this class, so you need to map the control object to a variable of the type of your custom control class if you want to access the Enabled property.
Dim myControl As TheAddUserControl
With Page.Form.Controls(1)
myControl = .Controls(getControlId("loadAddUser"))
myControl.Enabled = False
.Controls(getControlId("loadEditUser")).Visible = True
.Controls(getControlId("loadListUser")).Visible = False
End With
To expose an Enabled property in you user control:
Public Property Enabled As Boolean
Get
Return (Child1.Enabled And Child2.Enabled And Child3.Enabled)
End Get
Set(ByVal value As Boolean)
Child1.Enabled = value
Child2.Enabled = value
Child3.Enabled = value
End Set
End Poperty