Recommendations for good ways to display user messages in asp.net? - asp.net

I have an app full of pages and user controls, many of which have one or more label controls on them for displaying various different messages to the user.
Example: the AddCompany.ascx user control, normally used on the Company.aspx page (with App.Master MasterPage) has a label on it called "OutOfCreditLabel" with Text="Error: You cannot create a new company, as you are out of credit". The code checks the credit and shows the label if needed.
I want to get rid of all these labels all over the place and just have a method I can call from anywhere like
ShowUserMessage("Text goes here");
In previous projects I've implemented this as a single label on the master page and public method in the .master.cs - as long as the page has the line
<%# MasterType VirtualPath="~/App.master" %>
in it, this works - but I can't get it to work on user controls (.ascx.cs). Is there a trick to this?
Or... Is there a better way?
What would you recommend for a "global" user message method that works from anywhere in the site?
Any nice jQuery solutions, perhaps?
Update
RPM1984 has asked for further clarification, so I'm trying to ask this a different way:
I need a method I can call from the code-behind (of a page or a user control) which will then display whatever text I specify to the user, like how stackoverflow tells you about new answers to your question when you next visit the site. It can be up the top of the window (like SO) or somewhere on the page, doesn't matter. What matters is that it's a "global" approach, rather than having various javascript alerts and asp:label's littered over every other page and user control in the project.
Something like this scenario:
A user clicks the "Create Widget" button on my "widget manager" user control on my page. In the event handler is:
if (User.IsOutOfCredit)
{
ShowUserMessage("Sorry, you cannot create widgets; you are out of credit.");
}
This results in the user seeing "Sorry, you cannot create widgets; you are out of credit." Either in a pop-up or red text in the page somewhere or at the top like StackOverflow, anything is fine
Does that make sense?

Why not a simple HTML "old-school" extension method? (i.e static method)
namespace Web.Helpers
{
public class HtmlHelpers
{
public static string Label(string target)
{
var dynamicText = SomeWhere.GetSomeCoolText();
return String.Format("<label for='{0}'>{1}</label>", target, dynamicText);
}
}
}
Then in your HTML:
<%= HtmlHelpers.Label("sometarget") =>
In your main master page, just import the namespace:
<%# Import Namespace="Web.Helpers" %>
EDIT after question updated
Ok, i see what you're trying to do now. I would recommend doing it all client-side.
That is, in your main master page, create an initially hidden <div> with a unique id:
<div id="message" style="display: none"></div>
Then create a helper method somewhere in your web project, rendering out some script.
public void DisplayMessage()
{
string message = SomeWhere.GetSomeCoolText();
StringBuilder script = new StringBuilder();
script.AppendFormat("$('message').show().append("<span>{0}</span>")", message);
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), script.ToString(), true);
}
Of course, this is just a guideline. You dont have to use jQuery, you don't have to hardcode the script in the method. You could create a JavaScript function that accepts a string and modifies the HTML of the div, then just call the function.
It all depends on how complicated your "message" is, if you need special server controls, internationalization, etc.
But this is certainly the easiest way to accomplish what you want (IMHO).

Personally, I'd prefer using the HttpContext.Current.Items dictionary with a custom control. Similar to Keith Bluestone's approach, but doesn't require an arbitrarily named control on the page; it makes it very clear what's going on. You could package it into one class as a server control easily enough, but here it is to drop into a standard web project.
To register a message in your code behind:
SiteMessageUtility.Add("Test message");
To display in your page, or master page, or wherever, really:
<%# Register TagPrefix="custom" TagName="SiteMessage" Src="~/Controls/SiteMessage.ascx" %>
<custom:SiteMessage runat="server" />
Here's the files you'll need:
~\App_Code\SiteMessageUtility.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
public static class SiteMessageUtility
{
public static void Add(string message)
{
string currMessage = HttpContext.Current.Items["message"] as string;
if (currMessage == null)
{
HttpContext.Current.Items["message"] = message;
}
else
{
HttpContext.Current.Items["message"] = currMessage + "<br/>" + message;
}
}
}
~\Controls\SiteMessage.aspx
<%# Control Language="C#" AutoEventWireup="true" CodeFile="SiteMessage.ascx.cs" Inherits="Controls_SiteMessage" %>
<asp:Literal runat="server" ID="message" />
~\Controls\SiteMessage.aspx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class Controls_SiteMessage : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected override void OnPreRender(EventArgs e)
{
message.Text = (string)HttpContext.Current.Items["message"];
base.OnPreRender(e);
}
}

On the master page add the following controls:
the hfMsg hidden field will hold the message that will be displayed
and the hfCtrl will hold the name of the html control that will display this message which will be in this example lblMsg.
<label id="lblMsg" style="background-color:Yellow; border:solid 1px; width:200px;height:100px;display:none;"></label>
<asp:HiddenField ID="hfMsg" runat="server" />
<asp:HiddenField ID="hfCtrl" runat="server" />
NOTE:you can add another html control with a different id on an ASCX control and use it to display the message instead of the lblMsg.
and the following script:
<script language="javascript" type="text/javascript">
var msg = $('#<%= hfMsg.ClientID %>').attr("value");
var ctrl = $('#<%= hfCtrl.ClientID %>').attr("value");
if (msg != undefined && msg != "") {
$("#" + ctrl).html(msg);
$("#" + ctrl).show();
}
else {
$("#" + ctrl).hide();
}
</script>
NOTE:the script simply checks to see if the hfMsg control has a message to display or not.
and add the following two methods to the master page:
public void ShowMessage(string control, string message)
{
this.hfCtrl.Value = control;
this.hfMsg.Value = message;
}
public void ClearMessage()
{
this.hfMsg.Value = string.Empty;
}
finally you can call the ShowMessage method from any page like this to display a message on the master page:
Master.ShowMessage("lblMsg","hello world!");
and if you have a user control that holds an html label (e.g lblUserMsg) to display the message you can simply call ShowMessage and pass it the name of the label:
Master.ShowMessage("lblUserMsg","hello world!");

Having a SetStatus method in the MasterPage is always the way I've done this, to access the MasterPage from inside a user control, just create a private property in the usercontrol (or create a baseControl class that all user controls inherit from):
private TheNameSpaceOfMyMasterPage.MyMasterPage Master
{
get { return (TheNameSpaceOfMyMasterPage.MyMasterPage)Page.Master; }
}
protected void btnSave_OnClick(object sender, EventArgs e)
{
// Do your stuff...
// Set the Status
this.Master.ShowStatus("blah blah blah");
}
Also if you're using a control to hold the status message remember to disable ViewState on it, otherwise you'll end up with the same status message across postbacks which you don't want.

Some good answers, but...
To Robert W: the master page impl works, but it does require strong coupling (knowledge of) with the master page type. Master pages should generally affect style only, right? E.g. I should be able to change master pages without breaking my app.
SubPortal: yes, very clever, but... too clever for me. Also has master page issue.
RPM1984: again, too clever for me!
MGOwen didn't mention any special DHTML requirements (e.g. update from client side). So why not just implement a simple server-side helper method as he mentioned, find the target control which displays the message, and display it?
The following ShowMessage will find the target message control wherever it is on the page, as well as enable any page to override the default application's ShowMessage behavior if desired.
public static class ASPHelper {
// Defines something that shows a message
public interface IShowMessage
{
void ShowMessage(string msg);
}
// default implementation: finds a target message display control
// wherever it is on the page.
static void ShowMessage_Default(string msg)
{
const string SHOWMESSAGE_CONTROL_ID = "ctlShowMessage"; // or whatever
// find the control
Page currentPage = HttpContext.Current.CurrentHandler as Page;
Control ctlMessage = currentPage.FindControlRecursive(SHOWMESSAGE_CONTROL_ID);
if (ctlMessage == null)
throw new Exception("ShowMessage control not found!" );
Literal ctlMessageLiteral = ctlMessage as Literal;
if (ctlMessageLiteral == null)
throw new Exception("ShowMessage control must be a Literal!");
// Message control should not implement viewstate -- unneccesary
Debug.Assert(ctlMessageLiteral.EnableViewState == false);
// show it
ctlMessageLiteral.Text = msg;
ctlMessageLiteral.Visible = true;
}
// public version for use across app & in pages
static public void ShowMessage(string msg)
{
// Allow the current page to implement its own way of showing
// messages -- provides flexibility.
Page currentPage = HttpContext.Current.CurrentHandler as Page;
IShowMessage showMessageImpl = currentPage as IShowMessage;
if (showMessageImpl != null)
{
// Show using custom message displayer
showMessageImpl.ShowMessage(msg);
}
else
{
// static/global/default version
ShowMessage_Default(msg);
}
}
} // end ASPHelper
To use, simply place a literal control on the page, as in:
<div><asp:Literal
ID="ctlShowMessage" runat="server"
Visible="false"
EnableViewState="false"
/>
</div>
To use, simply call ShowMessage, as in ASPHelper.ShowMessage("insufficient credit"). Hope this is what you're looking for... Keith

Javascript safe popup. Works with Ajax/jQuery.
Namespace Utilities
''' <summary>
''' Utility class for injecting JavaScript into pages. Used primarily for throwing a JavaScript alert box, with correct focus.
''' </summary>
''' <remarks></remarks>
Public Class WriteJava
Region "Public Subs"
Public Shared Sub Script(ByVal script As String, ByRef p As Page, Optional ByVal scriptName As String = "")
If String.IsNullOrEmpty(scriptName) Then
scriptName = GetScriptName(p)
End If
If Not p.ClientScript.IsStartupScriptRegistered(scriptName) Then
ToolkitScriptManager.RegisterStartupScript(p, p.GetType, scriptName, script, True)
End If
End Sub
Public Overloads Shared Sub Alert(ByVal MyAlert As String, ByRef p As Page)
Script("alert('" & Format(MyAlert) & "');", p)
End Sub
Private Shared Function Format(ByVal value As String) As String
Return value.Replace("'", "\'").Replace(Chr(13), "\r").Replace(Chr(10), "\n")
End Function
Private Shared Function GetScriptName(ByVal p As Page) As String
Dim i As Integer = p.AppRelativeVirtualPath.LastIndexOf("/")
Dim pageName As String = p.AppRelativeVirtualPath.Substring(i + 1)
Return pageName
End Function
End Region
End Class
End Namespace
Use by calling:
Utilities.WriteJava.Alert("DANGER WILL ROBINSON", Me.Page)

I was looking for a site-wide way to display message to the user. I found jnotifica. It's similar to stackoverflow's bar at the top of the page.
The author's site appears to be down at the moment.

Raise an event in the user control, subscribe to the event from the page. The event handler updates your custom page property. Seems like the least amount of moving parts.

I would like to suggest to use a class for custom page(Inherited from System.Web.UI.Page) inside that add follwing as
protected void AlertError(string ErrorString)
{
StringBuilder scriptBuidler =
new StringBuilder(#"<script type='text/javascript' language='javascript'>");
scriptBuidler.Append(#"alert('");
scriptBuidler.Append(ErrorString);
scriptBuidler.Append(#"');");
scriptBuidler.Append(#"</script>");
AjaxControlToolkit.ToolkitScriptManager.RegisterClientScriptBlock
(this, typeof(Page),
"Patient Detail Not Found",
scriptBuidler.ToString(),
false
);
}
and inherit all your pages from this class instead of inherinting from system.web.ui.page
and use this function to to show your messages to your clients

Related

Get output cache to work in ASP.net with webusercontrol

I have a webusercontrol with a public int property SelectedCatID. I use this control on other pages and in other controls as follows:
<NewStore:LeftMenuLinks runat="server" SelectedCatID="<%#CatIDToSelect%>" />
How do I output cache this control based on SelectedCatID? Everything I've tried fails.
The closest I've gotten is getting it to cache, but it doesn't vary on SelectedCatID leaving the same menu item selected until the cache expires. Without caching, the control works as expected.
I figured out why the VaryByControls approach you used initially does not work. Sadly you edited it out of your question, so my research for this will just have to go into a blog post. Update: the blog post in question: http://tabeokatech.blogspot.be/2014/09/outputcache-on-user-controls.html .
The long and short of it though is that VaryByControls is kinda shorthand for VaryByParams, and does nothing for properties: it only looks at POST values. The fact that it ever worked for properties with a static value appears to be a bug - any string whatsoever in the VaryByControls would have made that part work. The accepted answer to this question is wrong: Vary by control properties using PartialCaching in ASP.NET .
There is no built-in way to vary by control property values.
That wouldn't make sense anyway, because user controls need to be created to have property values, and you want to avoid creating them, instead caching their rendered markup - cached user controls fields are null in code-behind if cached markup is served for them.
This works by injecting a PartialCachingControl into the page instead of the actual user control. This PartialCachingControl checks the cache, and only creates the control if no cached version exists.
As for making it work, I see two options:
If you only have 1 usercontrol per page, you could use the VaryByCustom approach. To make things easy you could write an interface that returns your property value for that page, and implement it on every page that hosts the user control, e.g.:
interface INumberProvider
{
int GetNumber();
}
// and the page:
public partial class _Default : Page, INumberProvider
{
public int GetNumber()
{
return this.SomeNumberPropertyOrWhatever;
}
...
In your Global.asax you cast the current handler to INumberProvider and get the number:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (custom == "INumberProvider")
{
var page = context.CurrentHandler as INumberProvider;
if (page != null)
{
return page.GetNumber().ToString();
}
}
return base.GetVaryByCustomString(context, custom);
}
And in your control you obviously add:
OutputCache Duration="180" VaryByCustom="INumberProvider" VaryByParam="None" Shared="true"
That's if you only have one user control per page, and should be pretty straightforward. If you need more than one user control per page you're out of luck:
Build your own wrapper around your user control by writing a custom WebControl. Add the properties you need, capture the output of the rendered user control, and insert it into HttpContext.Current.Cache with a key that includes the SelectedCatID. Basically write your own custom PartialCachingControl.
There's also option 3:
Decide caching is not that important after all
<%# OutputCache Duration="60" VaryByParam="SelectedCatID" %>
Now store youre <%#CatIDToSelect%> as an a parameter ex ?SelectedCatID=12
Now you're Page or UserControl depending on what you want to cache will output the cache depending on what the Request.Param["SelectedCatID"] is equal to.
You can also do something like this (although not the easiest way)
This goes on the page/usercontrol you want cached:
<%# OutputCache duration="120" varybyparam="None" varybycustom="SelectedCatID" %>
This goes into the Gloabal.asax file:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if(custom == "SelectedCatID")
{
return CatIDToSelect;
}
return String.Empty;
}
I'm late to the party here what with an accepted answer and a 500 point bounty awarded. Still wanted to give my few cents on how this could be achieved.
It can be made to work in the control itself. You can have the control store it's own output in the cache and use the cached version in the Render method if found. I have made a really simple UserControl to test with. The markup looks like this:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="TestUC.ascx.cs"
Inherits="Webforms_Test.UserControls.TestUC" %>
<div>
<asp:Label ID="curTime" runat="server"></asp:Label>
</div>
It just contains a label that is set to DateTime.Now when it is initialized. The code behind looks like this:
public partial class TestUC : System.Web.UI.UserControl
{
private string cachedOutput = null;
public bool RenderFromCache = true; // set to false in containing page if this control needs to be re-rendered
protected void Page_Load(object sender, EventArgs e)
{
cachedOutput = HttpContext.Current.Cache["key"] as string;
if (cachedOutput == null)
{
// not found in cache, do the heavy lifting here to setup the control
curTime.Text = "UC:" + DateTime.Now.ToString("yy-MM-dd hh:mm:ss");
}
}
protected void Page_PreRender(object sender, EventArgs e)
{
if (cachedOutput == null || !RenderFromCache)
{
RenderFromCache = false;
StringBuilder b = new StringBuilder();
HtmlTextWriter h = new HtmlTextWriter(new StringWriter(b));
this.RenderControl(h);
cachedOutput = b.ToString();
HttpContext.Current.Cache.Insert("key", cachedOutput, null, DateTime.UtcNow.AddSeconds(10), TimeSpan.Zero);
RenderFromCache = true;
}
}
protected override void Render(HtmlTextWriter writer)
{
if (!RenderFromCache)
base.Render(writer);
else
writer.Write(cachedOutput);
}
}
In this sample, the control itself checks if its output is found in the cache, and if so the Render method will just write the cached output. If it is not found in the cache, the PreRender method will run the Render method normally and capture the output and store it in the cache.
In your case you would of course need a bit more logic which would check the relevant property on the control and use that to check if a cached version exists.
Disclaimer: This is an extremely simple test control. I have not tried to figure out how to make all of this work with controls that contain event handlers etc. So take it for what it's worth...

Adding custom valiadtion to ASP.NET controls

We're trying to build a simple asp control for some clients where they can just drop in a single block -
i.e.
<captcha:CaptchaControl ID="CaptchaControl1"
runat="server"
Server="http://localhost:51947/"
/>
and have it render the control. The catch is that I can't get this to include custom validation. Right now I'm using the RenderContents function to display the layout of the control itself as well as hook it up the to Javascript. The problem is that I don't know how to get custom validation to fire when used as part of a control.
protected override void RenderContents(HtmlTextWriter output)
{
output.Write(#"
<script type=""text/javascript"" src=""http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js""></script>
<link rel=""stylesheet"" type=""text/css"" href=""/Layout/CaptchaLayout.css"" />
//etc
<asp:Textbox id=""text1"" runat=""server"" text=""""></asp:Textbox>
<asp:CustomValidator id=""CustomValidator2"" runat=""server""
ControlToValidate = ""text1""
ErrorMessage = ""You must enter at least 8 characters!""
ClientValidationFunction=""validateLength"" >
</asp:CustomValidator>"
);
}
Any suggestions for a better way to do this?
Oogh, I would definitely not recommend your approach. It's very brittle and difficult to maintain, and depending on how your control is used, I'm not even sure that you can output more asp tags and have them processed properly.
Why don't you just inherit your custom control from Panel, and then in the Init or Load event handlers, add the textbox and custom validator to it? Roughly:
public class MyControl : Panel
{
public MyControl()
{
}
protected override void OnInit(EventArgs e)
{
ScriptManager.RegisterScript( ... Google script, CSS, etc. ... );
TextBox txt = new TextBox();
txt.ID = "text1";
this.Controls.Add(txt);
CustomValidator vld = new CustomValidator();
vld.ControlToValidatre = "text1";
vld.ID = "validator1";
this.Controls.Add(vld);
}
}
Your CustomValidator doesn't work because ASP.NET has no idea it's there. You are basically just dumping that output to the response... ASP.NET is not interpreting it.
It seems to me that this is a perfect situation for a User Control rather than a Custom Control. Just drop that output string in its own .ASCX file.

How can I re-instantiate dynamic ASP.NET user controls without using a database?

I'm currently working with a part of my application that uses Dynamic Web User Controls and I'm having a bit of trouble figuring out the best way to re-instantiate the controls on postback by using ViewState or some other method that doesn't require me to query a database on each postback.
Basically what I have on my page is a user control that contains a panel for holding a variable amount of child user controls and a button that says "Add Control" whose function is pretty self explanatory.
The child user control is pretty simple; it's just a delete button, a drop down, and a time picker control arranged in a row. Whenever the user clicks the Add Control button on the parent control, a new 'row' is added to the panel containing the child controls.
What I would like to do is to be able to add and remove controls to this collection, modify values, and perform whatever operations I need to do 'in-memory' without having to do any calls to a database. When I am done adding controls and populating their values, I'd like to click "save" to save/update all the data from the controls to a database at once. Currently the only solution I have found is to simply save the data in the database each post back and then use the rows stored in the db to re-instantiate the controls on postback. Obviously, this forces the user to save changes to the DB against their will and in the event that they want to cancel working with the controls without saving their data, extra work must be done to ensure that the rows previously committed are deleted.
From what I've learned about using dynamic controls, I know it's best to add the controls to the page during the Init stage of the lifecycle and then populate their values in the load stage. I've also learned that the only way to make sure you can persist the control's viewstate is to make sure you give each dynamic control a unique ID and be sure to assign it the exact same ID when re instantiating the control. I've also learned that the ViewState doesn't actually get loaded until after the Init stage in the life cycle. This is where my problem lies. How do I store and retrieve the names of these controls if I am unable to use the viewstate and I do not want to perform any calls to a database? Is this sort of in-memory manipulation / batch saving of values even possible using ASP.net?
Any help is greatly appreciated,
Mike
You could store the bare minimum of what you need to know to recreate the controls in a collection held in session. Session is available during the init phases of the page.
Here is an example for you. It consists of:
Default.aspx, cs
- panel to store user controls
- "Add Control Button" which will add a user control each time it is clicked
TimeTeller.ascx, cs
- has a method called SetTime which sets a label on the control to a specified time.
Default.aspx
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="DynamicControlTest._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Panel ID="pnlDynamicControls" runat="server">
</asp:Panel>
<br />
<asp:Button ID="btnAddControl" runat="server" Text="Add User Control"
onclick="btnAddControl_Click" />
</div>
</form>
</body>
</html>
Default.aspx.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace DynamicControlTest
{
public partial class _Default : System.Web.UI.Page
{
Dictionary<string, string> myControlList; // ID, Control ascx path
protected void Page_Load(object sender, EventArgs e)
{
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (!IsPostBack)
{
myControlList = new Dictionary<string, string>();
Session["myControlList"] = myControlList;
}
else
{
myControlList = (Dictionary<string, string>)Session["myControlList"];
foreach (var registeredControlID in myControlList.Keys)
{
UserControl controlToAdd = new UserControl();
controlToAdd = (UserControl)controlToAdd.LoadControl(myControlList[registeredControlID]);
controlToAdd.ID = registeredControlID;
pnlDynamicControls.Controls.Add(controlToAdd);
}
}
}
protected void btnAddControl_Click(object sender, EventArgs e)
{
UserControl controlToAdd = new UserControl();
controlToAdd = (UserControl)controlToAdd.LoadControl("TimeTeller.ascx");
// Set a value to prove viewstate is working
((TimeTeller)controlToAdd).SetTime(DateTime.Now);
controlToAdd.ID = Guid.NewGuid().ToString(); // does not have to be a guid, just something unique to avoid name collision.
pnlDynamicControls.Controls.Add(controlToAdd);
myControlList.Add(controlToAdd.ID, controlToAdd.AppRelativeVirtualPath);
}
}
}
TimeTeller.ascx
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="TimeTeller.ascx.cs" Inherits="DynamicControlTest.TimeTeller" %>
<asp:Label ID="lblTime" runat="server"/>
TimeTeller.ascx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace DynamicControlTest
{
public partial class TimeTeller : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
public void SetTime(DateTime time)
{
lblTime.Text = time.ToString();
}
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
lblTime.Text = (string)ViewState["lblTime"];
}
protected override object SaveViewState()
{
ViewState["lblTime"] = lblTime.Text;
return base.SaveViewState();
}
}
}
As you can see, I still have to manage the internal viewstate of my user control, but the viewstate bag is being saved to the page and handed back to the control on postback. I think it is important to note that my solution is very close to David's. The only major difference in my example is that it's using session instead of viewstate to store the control info. This allows things to happen during the initialization phase. It is important to note that this solution takes up more server resources, therefore it may not be appropriate in some situations depending on your scaling strategy.
I have done this in the past. I have not had to do this since the days of .NET 1.1, but the principal removes the same.
I did it on Page_Load not Init have to reload the controls that you created on the last page cycle.
First you need to keep track of the controls you have created on each page cycle. This includes type, name etc. . .
Then on each page load you need to rebuild them.
You do that by re-creating the control, assinging it the exact same id, add it to the sampe place on the page and finally in the ViewState["LoadedControl"] to the control type.
Here is the code I used, I only did this with User Controls that I created. I have not tried this with an ASP.NET control, but I think it would work the same.
In this case I have an ArrayList of Triplets (keep in mind this is .NET 1.1) adn the first item was a PageView ID. You might not need that for your application.
protected void Page_Load(object sender, System.EventArgs e)
{
//**********************************************************
//* dynCtlArray will hold a triplet with the PageViewID, *
//* ControlID, and the Control Name *
//**********************************************************
ArrayList dynCtlArray = (ArrayList)this.ViewState["dynCtlArray"];
if (dynCtlArray != null)
{
foreach (object obj in dynCtlArray)
{
Triplet ctrlInfo = (Triplet)obj;
DynamicLoadControl(ctrlInfo);
}
}
}
private void DynamicLoadControl(Triplet ctrlInfo)
{
// ERROR HANDLING REMOVED FOR ANSWER BECAUSE IT IS NOT IMPORTANT YOU SHOULD HANDLE ERRORS IN THIS METHOD
Control ctrl = this.LoadControl(Request.ApplicationPath
+ "/UC/" + (string)ctrlInfo.Third);
ctrl.ID = (string)ctrlInfo.Second;
// Create New PageView Item
Telerik.WebControls.PageView pvItem = this.RadMultiPage1.PageViews[(int)ctrlInfo.First];
pvItem.Controls.Add(ctrl);
/******************************************************
* The ControlName must be preserved to track the *
* currently loaded control *
* ****************************************************/
ViewState["LoadedControl"] = (string)ctrlInfo.Third;
}
private void RegisterDynControl(Triplet trip)
{
ArrayList dynCtlArray = (ArrayList)this.ViewState["dynCtlArray"];
if (dynCtlArray == null)
{
dynCtlArray = new ArrayList();
this.ViewState.Add("dynCtlArray", dynCtlArray);
}
dynCtlArray.Add(trip);
}
In some method on your page
// Create new Control
Control ctrl = Page.LoadControl("../UC/MyUserControl.ascx");
// . . . snip .. .
// Create Triplet
Triplet ctrlInfo = new Triplet(0, ctrl.ID, "MyUserControl.ascx");
// RegisterDynControl to ViewState
RegisterDynControl(ctrlInfo);
// . . . snip .. .
To access the controls to save there information you will have to do a this.Page.FindControl('');
I implemented a page very similar to Daniel's example, but could not use Session due to technical constraints. However, I found that by using an field to the page, I could post back and retrieve its value during the Page_Init event.

Access a content control in C# when using Master Pages

Good day everyone,
I am building a page in ASP.NET, and using Master Pages in the process.
I have a Content Place Holder name "cphBody" in my Master Page, which will contain the body of each Page for which that Master Page is the Master Page.
In the ASP.NET Web page, I have a Content tag (referencing "cphBody") which also contains some controls (buttons, Infragistics controls, etc.), and I want to access these controls in the CodeBehind file. However, I can't do that directly (this.myControl ...), since they are nested in the Content tag.
I found a workaround with the FindControl method.
ContentPlaceHolder contentPlaceHolder = (ContentPlaceHolder) Master.FindControl("cphBody");
ControlType myControl = (ControlType) contentPlaceHolder.FindControl("ControlName");
That works just fine. However, I am suspecting that it's not a very good design. Do you guys know a more elegant way to do so?
Thank you!
Guillaume Gervais.
I try and avoid FindControl unless there is no alternative, and there's usually a neater way.
How about including the path to your master page at the top of your child page
<%# MasterType VirtualPath="~/MasterPages/PublicUI.Master" %>
Which will allow you to directly call code from your master page code behind.
Then from your master page code behind you could make a property return your control, or make a method on the master page get your control etc.
public Label SomethingLabel
{
get { return lblSomething; }
}
//or
public string SomethingText
{
get { return lblSomething.Text; }
set { lblSomething.Text = value; }
}
Refers to a label on the master page
<asp:Label ID="lblSomething" runat="server" />
Usage:
Master.SomethingLabel.Text = "some text";
//or
Master.SomethingText = "some text";
Rick Strahl has a good explanation (and sample code) here - http://www.west-wind.com/Weblog/posts/5127.aspx
Nothing to do different. Just write this code on child page to access the master page label control.
Label lblMessage = new Label();
lblMessage = (Label)Master.FindControl("lblTest");
lblMessage.Text = DropDownList1.SelectedItem.Text;
I use this code for acess to files recursively:
/// <summary>
/// Recursively iterate through the controls collection to find the child controls of the given control
/// including controls inside child controls. Return all the IDs of controls of the given type
/// </summary>
/// <param name="control"></param>
/// <param name="controlType"></param>
/// <returns></returns>
public static List<string> GetChildControlsId(Control control, Type controlType)
{
List<string> FoundControlsIds = new List<string>();
GetChildControlsIdRecursive(FoundControlsIds, control, controlType);
// return the result as a generic list of Controls
return FoundControlsIds;
}
public static List<string> GetChildControlsIdRecursive(List<string> foundControlsIds, Control control, Type controlType)
{
foreach (Control c in control.Controls)
{
if (controlType == null || controlType.IsAssignableFrom(c.GetType()))
{
// check if the control is already in the collection
String FoundControl = foundControlsIds.Find(delegate(string ctrlId) { return ctrlId == c.ID; });
if (String.IsNullOrEmpty(FoundControl))
{
// add this control and all its nested controls
foundControlsIds.Add(c.ID);
}
}
if (c.HasControls())
{
GetChildControlsIdRecursive(foundControlsIds, c, controlType);
}
}
Hi just thought i'd share my solution, found this works for accessing a 'Control' that is inside an < asp:Panel> which is on a 'ContentPage', but from C# code-behind of the 'MasterPage'. Hope it helps some.
add an < asp:Panel> with an ID="PanelWithLabel" and runat="server" to your ContentPage.
inside the Panel, add an < asp:Label> control with ID="MyLabel".
write (or copy / paste the below) a function in your MasterPage Code-behind as follows: (this accesses the label control, inside the Panel, which are both on the ContentPage, from the Master page code-behind and changes its text to be that of a TextBox on the Master page :)
protected void onButton1_click(object sender, EventArgs e)
{
// find a Panel on Content Page and access its controls (Labels, TextBoxes, etc.) from my master page code behind //
System.Web.UI.WebControls.Panel pnl1;
pnl1 = (System.Web.UI.WebControls.Panel)MainContent.FindControl("PanelWithLabel");
if (pnl1 != null)
{
System.Web.UI.WebControls.Label lbl = (System.Web.UI.WebControls.Label)pnl1.FindControl("MyLabel");
lbl.Text = MyMasterPageTextBox.Text;
}
}

Message Box in ASP.NET

How to display the message Box within the Content Page..?
After updating profile..I want to display a Message Box in content page..
Please give your suggestions.. Thanks in advance.
You could use the Page.RegisterStartupScript method.
if (UpdateProfile())
Page.RegisterStartupScript("startup", "<script>alert('your profile has been updated..');</script>");
Assuming of course that UpdateProfile() does the work and returns a boolean indicating success :)
Alternatively (because that method is obsolete), you could use the ClientScriptManager.RegisterStartupScript method instead.
if (UpdateProfile())
Page.ClientScript.RegisterStartupScript(this.GetType(), "startup", "<script>alert('your profile has been updated..');</script>", false);
write this method first
public void MsgBox(String ex, Page pg,Object obj)
{
string s = "<SCRIPT language='javascript'>alert('" + ex.Replace("\r\n", "\\n").Replace("'", "") + "'); </SCRIPT>";
Type cstype = obj.GetType();
ClientScriptManager cs = pg.ClientScript;
cs.RegisterClientScriptBlock(cstype, s, s.ToString());
}
after whenever you need message box just follow this line
MsgBox("Your Message!!!", this.Page, this);
The error you are seeing is caused by your content page somehow trying to inject the javascript to create the alert box outside of the Content control.
One way of doing this that should work is to inject the javascript at the master page level.
To do this expose a method in you master page code behing like the following:
public void ShowAlertMessage(String message)
{
string alertScript = String.Format("<Script Language='javascript'> alert('{0}');</script>", message);
Page.ClientScript.RegisterStartupScript(this.GetType(), "Key", alertScript, false);
}
Then, from the content page you can call this method on the Master object:
protected void UpdateProfile_Click(object sender, EventArgs e)
{
YourMasterPage master = (YourMasterPage) Master;
master.ShowMessage("Profile updated.");
}
This method also has the nice benefit of encapsulating your MessageBox logic for all your content pages.
One caveat on the above is that I can't for the life of me reproduce the error you are seeing, I've tried every combination of master/content markup I can think of and can't get the error. Any of the other examples provided here in the other answers work happily for me.
Response.Write("[script] alert('message here');[/script]");
stackoverflow won't allow the real tags replace the [ with < and the ] with >

Resources