I can easily understand how to use custom events in pure C# code, but how can I do pass in custom event arguments on asp:button click?
I've tried all sorts of things (defining new event args, new delegates, etc etc) but I have had no luck.
Is there a tutorial of how to do this with the standard asp controls?
As long as your EventArgs inherit from System.EventArgs you will be able to pass them. Then, once inside your event handler, you can cast the event to the proper subtype.
Here is an example:
using System;
class Program
{
static event EventHandler Foo;
static void Main()
{
// Here is cast the EventArgs to FooEventArgs
Foo += (o,e) => Console.WriteLine(((FooEventArgs)e).Number);
// Notice that I am passing a "FooEventArgs" instance
// even though the delegate signature for "EventHandler"
// specifies "EventArgs". Polymorphism in action. :)
Foo(null, new FooEventArgs { Number = 1 });
}
}
class FooEventArgs : EventArgs
{
public int Number { get; set; }
}
I know that you are working with existing control delegates so unfortunately this kind of casting is neccessary. Keep in mind though that there is a EventHandler<T> where T : EventArgs delegate in .NET 2.0 and greater that will allow you to do what I have done above without casting.
I don't believe there is a way to do this without creating your own controls. However, I sometimes use the commandagument and commandname properties on a button to provide additional information
I don't thinks you can. The Button itself will be calling the Click event with it's own EventArgs object, and unfortunately you can't hijack that call. You can however use closures:
protected void Page_Load(object sender, EventArgs e)
{
int number = 1;
button.Click += (o, args) => Response.Write("Expression:"+number++);
number = 10;
button.Click += delegate(object o, EventArgs args) { Response.Write("Anonymous:"+number); };
}
By the way the output for this is Expression:10Anonymous:11 Understanding why this is the output is a big step into understanding closures! Even though number is out of scope when Click Event is handled, it is not destroyed because both of the defined event handler's have references to the it. So it and it's value will be maintained until it is no longer needed. I know that's not the most technical explanation of closures, but should give you an idea of what they are.
Related
I've 2 user controls named UCCreateProfile.ascx (used for creating/editing profile data) and UCProfileList.ascx (used to display profile data in GridView). Now whenever a new profile created I want to update my UCProfileList control to show new entry.
The best solution against above problem I've to go for Observer Pattern. In my case UCCreatedProfile is a Subject/Observable and UCProfileList is a Observer and as per pattern definition when observer initialized it knows who is my Subject/Observable and add itself into Subject/Observable list. So whenever a change occurred in Subject/Observable it will be notified.
This pattern best fit my requirements but I'm getting few problems to implement this describe as follows.
I'm working under CMS (Umbraco) and I don't have any physical container page (.aspx). What I've to do is find UCCreateProfile (Subject/Observable) in UCProfileList (Observer) onLoad event using following code.
private Control FindCreateProfileControl()
{
Control control = null;
Control frm = GetFormInstance();
control = GetControlRecursive(frm.Controls);
return control;
}
where GetFormInstance() method is
private Control GetFormInstance()
{
Control ctrl = this.Parent;
while (true)
{
ctrl = ctrl.Parent;
if (ctrl is HtmlForm)
{
break;
}
}
return ctrl;
}
and GetControlRecursive() method is
private Control GetControlRecursive(ControlCollection ctrls)
{
Control result = null;
foreach (Control item in ctrls)
{
if (result != null) break;
if (item is UCCreateProfile)
{
result = item;
return result;
}
if (item.Controls != null)
result = GetControlRecursive(item.Controls);
}
return result;
}
this way I can find the UCCreateProfile (Subject/Observable) user control in UCProfileList (Observer) but the way to find out the (Subject/Observable) is not so fast. As you can see I need to loop through all controls and first find the HtmlForm control and then loop through all child controls under HtmlForm control and find the appropriate control we're looking for.
Secondly, placement of the user controls in container if very important my code will only work if UCCreatedProfile.ascx (Subject/Observable) placed before UCProfileList.ascx (Observer) because this way UCCreateProfile will load first and find in UCProfileList. But if someone changed the position of these 2 controls my code will not work.
So to get rid of these problems I need some solution which works faster and independent of the position of the controls.
I've figured out some solution as described below. Please do let me know if it is a good way of doing this. If there is an alternative, please let me know.
I've a session level variable (a dictionary with Dictionary<ISubject, List<Observer>>) . No matter which user control initialized/loaded first, User Control will add itself into this dictionary.
If Subject/Observable added first, the corresponding observers will be found in this dictionary.
If Observer added first it will added to the dictionary with a null entry. When the Subject added, the association is made.
Regards,
/Rizwan
The Observer pattern is best implemented in .NET via events and delegates. If you use events and delegates, the Dictionary you mention becomes completely unnecessary. See for example this code below (only important pieces shown):
public partial class UserProfile : System.Web.UI.UserControl
{
//This is the event handler for when a user is updated on the UserProfile Control
public event EventHandler<UserUpdatedEventArgs> UserUpdated;
protected void btnUpdate_Click(object sender, EventArgs e)
{
//Do whatever you need above and then see who's subscribed to this event
var userUpdated = UserUpdated;
if (userUpdated != null)
{
//Initialize UserUpdatedEventArgs as you want. You can, for example,
//pass a "User" object if you have one
userUpdated(this,new UserUpdatedEventArgs({....}));
}
}
}
public class UserUpdatedEventArgs : EventArgs
{
public User UserUpdated {get;set;}
public UserUpdatedEventArgs (User u)
{
UserUpdated=u;
}
}
Now subscribing to the UserUpdated event from the UserProfile control on the UserListControl is as easy as this:
public partial class UserList : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
//Find the UserProfile control in the page. It seems that you already have a
//recursive function that finds it. I wouldn't do that but that's for another topic...
UserProfile up = this.Parent.FindControl("UserProfile1") as UserProfile;
if(up!=null)
//Register for the event
up.UserUpdated += new EventHandler<UserUpdatedEventArgs>(up_UserUpdated);
}
//This will be called automatically every time a user is updated on the UserProfile control
protected void up_UserUpdated(object sender, UserUpdatedEventArgs e)
{
User u = e.UserUpdated;
//Do something with u...
}
}
Is there a way to force methods to be accessible only during certain events during the page life cycle. For example, I have a extension to System.Web.UI.Page that adds a PrependTitle method.
I also have a masterpage that embeds another masterpage. The first masterpage sets the base title (Google), the next masterpage prepends the title (Calendar), and a page also prepends the title (21 May 2011).
The result should be:
21 May 2011 :: Calendar :: Google
And this is the case when the PrependTitle is run during the Page_Init event. However, when the method is run during Page_Load the following the results:
Google
So, that brings me to the question: How can it be enforced that a method only be accessible during specified life cycle events?
// The Method Mentioned
public static class PageExtensions
{
public static void PrependTitle(this Page page, string newTitle)
{
page.Title = newTitle + " " + Global.TITLE_DELIMITER + " " + page.Title;
}
}
I think this can be done similar to the following. The general idea is declare the method as private, declare the ones that should have access to it as sealed
class AppsBasePage : Page
{
abstract void PrependTitle(string title);
}
class PageWithTitlePrepended : AppsBasePage
{
private void PrependTitle(string title)
{
Title = String.Format("{0} {1} {2}", newTitle, Global.TITLE_DELIMITER, Title);
}
protected sealed override void Page_Init(object sender, EventArgs e)
{
PrependTitle("This is a title")
}
}
class ActualPageInApp: PageWithTitlePrepended
{
override void Page_Load(object s, EventArgs e)
{
// can't access PrependTitle here
}
}
This solves your question in bold, but I'm not convinced this situation is what is causing your problem with PrependTitle specifically. I think more code / context would be needed to solve your actual problem
If you want to brute force ensure that the method is being called from Init, you can inspect the call stack. Something like this:
public static bool CalledFromInit()
{
//Grab the current Stack Trace and loop through each frame
foreach(var callFrame in new StackTrace().GetFrames())
{
//Get the method in which the frame is executing
var method = callFrame.GetMethod();
//Check if the method is Control.OnInit (or any other method you want to test for)
if(method.DeclaringType == typeof(Control) && method.Name == "OnInit")
//If so, return right away
return true;
}
//Otherwise, we didn't find the method in the callstack
return false;
}
Then you would use it like:
public static void PrependTitle(this Page page, string newTitle)
{
//If we aren't called from Init, do something
if (!CalledFromInit())
{
//We could either return to silently ignore the problem
return;
//Or we could throw an exception to let the developer know they
// did something wrong
throw new ApplicationException("Invalid call to PrependTitle");
}
//Do the normally processing
page.Title = newTitle + " " + Global.TITLE_DELIMITER + " " + page.Title;
}
However, I'd caution that the stack trace isn't the most reliable thing. In release, code could get optimized such that the Control.OnInit method is inlined so your code wouldn't be able to see it in the call stack. You could wrap this check in an #if DEBUG block so it only executes during development. Depending on your use case, it might be good enough to catch this problem while in DEBUG and not bother doing the check in RELEASE. But that's up to you.
Another option...building on Tommy Hinrichs answer, if all your pages inherit from a base class, you'll be able to do it a bit more reliably. I'd suggest something like this:
public abstract class BasePage : Page
{
private bool _executingInit;
protected internal override void OnPreInit(EventArgs e)
{
_executingInit = true;
base.OnPreInit(e);
}
protected internal override void OnInitComplete(EventArgs e)
{
base.OnInitComplete(e);
_executingInit = true;
}
public void PrependTitle(string newTitle)
{
if (!_executingInit)
throw new ApplicationException("Invalid call to PrependTitle.");
Title = newTitle + " " + Global.TITLE_DELIMITER + " " + Title;
}
}
That way, PrependTitle will throw an exception unless it's called between PreInit and InitComplete (which sounds like exactly what you want).
As one last option, you could be sneaky and use reflection to access the Control.ControlState property (which is a confusing name because it's not related to Control State - the thing similar to View State). That property tracks the Control as it goes throw its lifecycle - and it has the following values:
internal enum ControlState
{
Constructed,
FrameworkInitialized,
ChildrenInitialized,
Initialized,
ViewStateLoaded,
Loaded,
PreRendered
}
You'll notice that Enum is internal. So is the Control.ControlState property. But with Reflection, you could use that - and you could even use it from an extension method that is external to the Page.
Hope one of those ways will work for you!
Your best bet is probably to use the Handles Keyword to attach the method to the event.
You might have to create a subclass of System.Web.UI.Page to ensure this is enforced.
It seems that the issue is in the prependTitle method, it should append the text to the page title not replace it.
Just call the PrependTitle method in the page_load of each mashterpage and page and append the text to the title.
I have C# Web Application that has an aspx page hosting a user control (Review.ascx). Inside that user control there are 5 more user controls, one of which has a public event (Review_Summary.ascx). The problem is no matter what i do I cannot get the event wired up in the parent ascx control (Review.ascx).
Here is what I have in the child control (Review_Summary.ascx)
public event EventHandler forwardStatusChanged;
#region methods
protected void btnForward_Click(object sender, EventArgs e)
{
if (btnForward.Text == "Return")
{
if (forwardStatusChanged != null)
{
forwardStatusChanged(sender, e);
}
removeForward();
}
}
In the parent control (Review.ascx) I have this
public void initReview(string EmployeeNumber)
{
RevSummary.forwardStatusChanged += new EventHandler(RevSummary_forwardStatusChanged);
<more code here>
}
protected void RevSummary_forwardStatusChanged(object sender, EventArgs e)
{
lblReadOnly.Visible = false;
}
RevSummary is the ID of the child control in the parent control. InitReveiw is a method that is called by the aspx page in its Page_Load event.
I get no errors on compile or at runtime. But when I click the button the forwardStatusChanged event is null. The "removeForward()" method that is called after that executes properly. So that fact that the event is always null leads me to believe that the wire up in the parent control is not working. However, I am sure it is executing becasue all of the code after that executes.
How can I figure out why this event is not wiring up?
Where is initReview being called from? Are you sure it's being called because the only reason this happens is that the event handler wasn't truly setup. I've never found a reason other than this, the several times I did this myself.
HTH.
I need to use the mentioned 3rd party datepicker and it throws an exception when an invalid date it entered. The author only exposes one event, which is fired when a successful parse takes place. How, in ASP.NET could I catch this error and do something about it, like set a label's text?
There are a couple of approaches you can take here, personally I would replace the default event handler for the TextChanged event via inheritance.
The code assigns one via during the setup and unfortunately textbox is a private member
textBox.TextChanged += new EventHandler(OnSelectedDateChanged);
which is declared as
protected virtual void OnSelectedDateChanged(object sender, EventArgs e)
So we can inherit SlimeeLibrary.DatePicker
public class EnhancedDatePicker : SlimeeLibrary.DatePicker
and then override the EventHandler raising a new parse error event.
public event EventHandler OnDateParseError;
protected override void OnSelectedDateChanged(object sender, EventArgs e)
{
try
{
base.OnSelectedDateChanged(sender, e);
}
catch (FormatException fe)
{
OnDateParseError(sender, e);
}
}
Hope that helps. I haven't checked it but have examined the code for slimees control, but don't want to setup a code project account to download it sorry. You'll obviously need to change your ASP.NET usercontrol references to use the new class.
Lets imagine a class
class Foo: IDisposable
{
Dispose()
{
//Dispose of nonmanged resources.
}
}
Let image that a use case exist for putting it into HttpContext.Items. It doesn't automatically raise errors when you add an object that implement IDisposable (and who knows, maybe the answer is that it should)
What event(s) do I need to hook into to dispose of that item?
Lets also assume that using blocks are not available as the object gets used into two different method blocks.
Per #Jaroslav Jandek, I think hooking into Application_EndRequest in global.asax would work just fine. You can do a simple check to see if the item is preset in HttpContext.Items and if it is then dispose of it.
protected virtual void Application_BeginRequest (Object sender, EventArgs e)
{
HttpContext.Current.Items["test"] = new IDisposableObject();
}
protected virtual void Application_EndRequest (Object sender, EventArgs e)
{
if(HttpContext.Current.Items.Contains("test")) {
((IDisposable)HttpContext.Current.Items["test"]).Dispose();
}
}