Answered by:
Accessing a singleton custom control via a static method

Question
-
User-570742420 posted
Hello, I developed a WebControl that can be placed only once in a page.
Id like to have a static property on this control that allows to access the singleton instance.In a few words, if I put the control on the page...
<aspTag:MyControl runat="server"/>
...I'd like to access it from everywhere on the page C# code (and from other custom controls) using a static property:
MyControl.Current
Using the ViewState is not an acceptable solution, because the ViewState is accessible only after the control Load event, but I'd like to access the Current variable also at earlier lifecycle stages (page Init for example).
Wednesday, January 9, 2019 4:29 PM
Answers
-
User-570742420 posted
I found the solution and it works!
I persist the control reference on the session.
First of all I created the following class:internal class PageStorage { #region Operations public static void Add(string key, Page page, object storedObject) { var session = HttpContext.Current.Session; var storage = (Dictionary<Page, object>)session[key]; if (storage == null) { storage = new Dictionary<Page, object>(); session[key] = storage; } storage.Add(page, storedObject); } public static object GetValue(string key, Page page) { object retvalue = null; var session = HttpContext.Current.Session; var storage = (Dictionary<Page, object>)session[key]; if (storage != null) storage.TryGetValue(page, out retvalue); return retvalue; } public static void Remove(string key, Page page) { var session = HttpContext.Current.Session; var storage = (Dictionary<Page, object>)session[key]; if (storage != null) storage.Remove(page); } #endregion }
Then into the MyControl sources I put:
public class MyControl : WebControl { protected override void OnInit(EventArgs e) { base.OnInit(e); PageStorage.Add("MyKey", Page, this); ... } public static MyControl GetCurrent(Page page) { return (MyControl)PageStorage.GetValue("MyKey", page); } protected override void OnUnload(EventArgs e) { base.OnUnload(e); PageStorage.Remove("MyKey", Page); } }
In this way, from everywhere in the page or inside other controls, with no need to overwrite master pages or pages, I can call the static method MyControl.GetCurrent(this) to have an immediate reference.
- Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
Thursday, January 10, 2019 11:04 AM
All replies
-
User475983607 posted
A singleton is a design pattern where one instance of a type exists in memory. User controls and server controls in general are instance types. A user control would access the singleton.
Wednesday, January 9, 2019 4:46 PM -
User-570742420 posted
I used a wrong term, I did not mean singleton in the C#way, I mean that I put a control (and only one) in the page.
So only one instance of the control can exist.
In this case I'd like to access it using a static property MyControl.CurrentWednesday, January 9, 2019 4:49 PM -
User475983607 posted
IgorDR
I mean that I put a control (and only one) in the page. So only one instance of the control can exist.Again, user controls are instance types. AFAIK, there is no way to stop someone from adding two user controls to a page.
IgorDR
In this case I'd like to access it using a static property MyControl.CurrentA static property can exist in any class. If you decide to place the static property in a user control then the static property is accessed from the user control class name. IMHO, it does not make sense to add a static property the a user control but there is nothing stopping you from doing so.
Can you explain the problem you are trying to solve? Perhaps post source code so we can reproduce the issue?
Wednesday, January 9, 2019 5:01 PM -
User-570742420 posted
The control I created is GoogleAnalyticsTracker, a control that istantiates all the scripts for Google Analytics,
I know that there's no way to block the user to put 2 controls of the same type, but this is not the problem, let's assume the user (me) is smart enough to not put 2 GoogleAnalyticsTrackers on the same page.The problem I'd like to find a solution is to have the property GoogleAnalyticsTrackers.Current so that other controls created by me can access the GoogleAnalyticsTrackers instance.
The property is static, but obviously every session in every page has to get different values (not a static value for the whole application).
A sort of static property that has only the page scope.I tried to use [ThreadStatic] but sometimes the page rendering is performed by different threads, so I abandoned this path...
Wednesday, January 9, 2019 5:08 PM -
User753101303 posted
Hi,
It could be a static property that would return the instance of this control that is inserted in the page. You insert this control in each and every page ? It seems it could be done once for all at the master page level.
What will be used by other controls ? Could ie be rather some kind of common page property that can be consumed by all controls. I feel there is a better way to do that but I'm not sure why other controls needs to know about this one.
Wednesday, January 9, 2019 5:21 PM -
User-570742420 posted
All the controls are inside a library and are used among multiple different applications, they are not aware of master pages.
Wednesday, January 9, 2019 5:24 PM -
User753101303 posted
AFAIK they can indirectly. If adding this Google Analytics control on each and every page on my site and I'm using master pages, I would first see if it couldn't be added to the master page so that I'm done with that once for all.
Now a control should be able to access its parent page which knows about a possible master page. I would perhaps use a base class or an extension method to be able to expose what I need (and just what I need from the control rather than the full control).
Then I would start to play with that and refine as needed while I better grasp what needs really to be done. A good advice I see once is even to write "consuming code" agains a "do nothing" implementation to put first how you'll finally consume this "service" rather than what this service does (which is usually not the real problem).
My Web Forms and even more GA is likely rusty but if you need further help you'll likely have to explain which kind of interaction you need between those controls.
Wednesday, January 9, 2019 6:17 PM -
User475983607 posted
IgorDR
The property is static, but obviously every session in every page has to get different values (not a static value for the whole application).
A sort of static property that has only the page scope.As stated that's simply a property or field. Create a base class and inherit from the base class.
http://www.4guysfromrolla.com/articles/041305-1.aspx
Or place the code in a master page as that's what a master page is for...
Wednesday, January 9, 2019 7:08 PM -
User-893317190 posted
Hi IgorDr,
Markup put in aspx like <aspTag:MyControl runat="server"/> will be recreated every time the page object is created and the page object will be created in every request.
So if you put it in aspx, it could not be singleton.
Even you use Page.LoadControl to dynamically add the user control , its lifetime is still with the Page object.
So it is hard to control control's lifetime in webform.
But to control their lifetime is not necessary, because what it is important is the data in the control instead of the control itself.
You want a single webcontrol only to use the data in it. How about save the data of the usercontrol in Session and visit the data of data through session?
If you want to maintain the data only through a page, you could use Ispostback to control the data, If !Ispostback , then recreate the data and save the new data in session.
Best regards,
Ackerly Xu
Thursday, January 10, 2019 2:45 AM -
User-570742420 posted
I found the solution and it works!
I persist the control reference on the session.
First of all I created the following class:internal class PageStorage { #region Operations public static void Add(string key, Page page, object storedObject) { var session = HttpContext.Current.Session; var storage = (Dictionary<Page, object>)session[key]; if (storage == null) { storage = new Dictionary<Page, object>(); session[key] = storage; } storage.Add(page, storedObject); } public static object GetValue(string key, Page page) { object retvalue = null; var session = HttpContext.Current.Session; var storage = (Dictionary<Page, object>)session[key]; if (storage != null) storage.TryGetValue(page, out retvalue); return retvalue; } public static void Remove(string key, Page page) { var session = HttpContext.Current.Session; var storage = (Dictionary<Page, object>)session[key]; if (storage != null) storage.Remove(page); } #endregion }
Then into the MyControl sources I put:
public class MyControl : WebControl { protected override void OnInit(EventArgs e) { base.OnInit(e); PageStorage.Add("MyKey", Page, this); ... } public static MyControl GetCurrent(Page page) { return (MyControl)PageStorage.GetValue("MyKey", page); } protected override void OnUnload(EventArgs e) { base.OnUnload(e); PageStorage.Remove("MyKey", Page); } }
In this way, from everywhere in the page or inside other controls, with no need to overwrite master pages or pages, I can call the static method MyControl.GetCurrent(this) to have an immediate reference.
- Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
Thursday, January 10, 2019 11:04 AM -
User753101303 posted
IMO it could be accessed using from a control using :
this.Page.MasterPage.GAControl
Depending on which kind of interaction you need between those controls I would have maybe exposed stricly what is required rather than the full control (wrapping that in a method that really expose just what other control needs).
Thursday, January 10, 2019 11:46 AM