I want to be able to globally grab a reference to the Page object that is currently being requested, so I've added a Global.asax file.
Is there any way to do this? I assume it has something to do with the Application_BeginRequest event.
You can access the current handler (the page) from global.asax, but not from any stage of the request life cycle. I.e. it is not possible in BeginRequest, but it is possible during PreRequestHandlerExecute:
void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
var page = (Context.Handler as System.Web.UI.Page);
}
Note that page might still be null, if the handler is not a page. Also, I'm not sure your approach is the correct one. Maybe you should explain in more detail what you want to attempt?
Create a class that is a subclass of Page that does what you want and use this subclass as the base type for all you pages.
public class MyPage : Page
{
//... override whatever you want, add functionality, whatever
}
All other pages:
public class Index : MyPage
{
// Automatically get new behaviour
}
You have to use http module to catch the every request for each page on your application and do whatever you want with the request.
var page = HttpContext.Current.Handler as Page
if(page != null) /// do something with page
PreRequestHandlerExecute should be fine for your purposes (if you don't fancy writing your own HttpModule, which is in fact very easy)
Related
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...
I have an authentication roles-based system: different roles are redirected to different folders and in each folder there is a web.config that allows the access only to a particular username.
Few roles have the default page in common with a gridview that react in different ways depending on the role(different columns are shown, events trigger different methods, etc.).
so my problem is that everytime I need to make minor changes to a page I need to copy/paste the same changes to all the others default pages in the other folders.
In terms of code I solved by creating a DefaultFather class which extends System.Web.UI.Page and every other Default class inherits from DefaultFather. In this way, if I dont declare a Page-life-method, the DefaultFather method will be triggered.
but what about the graphic part(html, javascript, asp components, etc...)??
I created a NestedMasterPage just for the Default pages but everytime I need to change the appearance/behaviour of controls(gridview, buttons, linkbuttons) I must use the FindControl() method.
there isnt really another way to solve this problem?
Im thinking of using the Page_Load() method to search for each control with FindControl() and save them into attributes for later usage but it doesnt really look like a good solution.
It would be nice if I could use the masterpage components as properties but I think that in order to do that I should create public properties and I dont know if it will cause some kind of security problem.
any suggestion?
btw, if masterpage is the solution, should I remove the DefaultFather class and place the code directly into the masterpage? or is it a good idea to have another class just for the code?
I'd say there's nothing wrong with having both a master page and a base class for your page. They serve different purposes. The master page is generally all about layout, and the base class would be about page functionality.
If you want to manipulate the markup on your master page, rather than accessing the fields directly, I'd say create a logical function which does what you need it to do, and let the master page do it.
// Site.Master.cs
public void HideSubmitButton()
{
btnSubmit.Visible = false;
}
// Default.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
((SiteMaster)Master).HideSubmitButton();
}
I'd probably wrap that cast so you can use it more easily - that is something that would belong in your base class:
// DefaultFather.cs
protected new SiteMaster Master { get { return (SiteMaster)base.Master; } }
// Default.aspx.cs
Master.HideSubmitButton();
EDIT
Per your comment about attaching event handlers - if you need to attach events to objects that live on the master (which may not be a good idea - ideally the event handler for something living on the master lives on the master - but if you really need it) you can expose methods to do that as well, like:
// Site.Master.cs
public void AttachEventHandlerToGoButton(EventHandler eventHandler)
{
btnGo.Click += eventHandler;
}
// Default.aspx.cs
Master.AttachEventHandlerToGoButton(DoMyThing);
private void DoMyThing(object sender, EventArgs e) { }
or if you want to get fancy, write a wrapper event:
// Site.Master
<asp:Button ID="btnGo" runat="server" OnClick="btnGo_Click" />
// Site.Master.cs
public event EventHandler GoButtonClick;
protected void btnGo_Click(object sender, EventArgs e) {
if (GoButtonClick != null) {
GoButtonClick(sender, e);
}
}
// Default.aspx.cs
Master.GoButtonClick += DoMyThing;
private void DoMyThing(object sender, EventArgs e) { }
Also see my edit on the Master wrapper - you need the base. there to avoid a stack overflow.
My aspx pages pages are drived from a base class, my base class creates the dynamic header of my website. I have a method called "genMenu" and i call in within pageLoad event of base class to create menu.
In some pages i need to override this method, however as we know page load of base class fires before the pageLoad of drived pages. So i cant really do that.
So how can i override the genMenu in aspx page and NoT fire it within base class. I know i can manually call the genMenu function in all drived pages, and not call it within pageLoad of base class, but there has to be a better way of handling it.
Create a virtual method called GenerateMenu or something like that, and always call it from the base page. Do the actual work there. Then your derived pages that need to intercept it can override that instead.
E.g., something like this:
// Base page
protected SomeType MenuVariable;
protected void Page_Load(object sender, EventArgs e)
{
this.GenerateMenu();
}
protected override void OnInit(EventArgs e) {
this.GetMenuVariable();
}
private void GetMenuVariable() {
// Some stuff with query string or the database
this.MenuVariable = FooBar();
}
protected virtual void GenerateMenu() {
// Generate the menu here
}
And:
// Derived page
protected override void GenerateMenu() {
// Calls from the base page Load event
// will be intercepted by this override
// Do the alternate work for this page
// here
}
Updated based on clarification:
In your base class, define a virtual method called AutoCallGenMenu(), which returns true by default. In your page load in the base class, check the result of that method, if it returns true, call genMenu().
In the pages you want to manually call genMenu(), override AutoCallGenMenu() to return false. Then, just manually call the method where you need it.
Instead of pageLoad, why don't you use pageInit or page_init to call your method, so that it occurs before page_load?
Refer to the. Net page life cycle for order of execution, you can then decide which method is best to use
http://msdn.microsoft.com/en-us/library/ms178472(v=vs.100).aspx
I may have misunderstood your question, but tou could also override your genmenu and ignore thr call to .base()
How can I create an attribute for an ASP.NET page that redirects to another page?
[MyAttribute()]
public partial class Default : System.Web.UI.Page
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
}
}
[AttributeUsage(AttributeTargets.All)]
public class MyAttribute: Attribute
{
public MyAttribute()
{
if (// something)
{
// I need to redirect to some page here
}
}
}
An attribute cannot redirect by itself - it is simply metadata that decorates the class/member you have defined it one.
That metadata can be used with reflection in order to perform a redirect, but you will need to put that code somewhere within the asp.net pipeline, so that the page can be inspected and a redirect can occur.
I agree with everyone else that using attributes to redirect is a weird experience.
If you really want to do this, you should look into the HttpApplication.BeginRequest event and you could probably write some code which checked each request for the page class being loaded to see if there is the attribute you want and then execute your redirection there.
Alternatively, you could probably write an abstract class which inherits from System.Web.UI.Page (all your pages in your site would need to inherit this too) and put some kind of check in the OnLoad for if the class is decorated with the attribute.
I use the following MSDN code on my master page to detect if the browser accepts cookies:
protected void Page_Load(object sender, EventArgs e)
{
if(!this.IsPostBack) {
if(Request.QueryString["CookieTest"] == null) {
Response.Cookies["TestCookie"].Value = "Test";
Response.Redirect("CookieCheck.aspx.redirect=" + Server.UrlEncode(Request.Url.ToString())));
}
else if((string)Request.QueryString["Test"] == "passed") {
// my stuff...
}
}
}
The CookieCheck.aspx contains the following:
protected void Page_Load(object sender, EventArgs e)
{
if(Request.Cookies["TestCookie"] == null)
Response.Redirect(Request.QueryString["redirect"] + "?Test=notPassed", true);
else
Response.Redirect(Request.QueryString["redirect"] + "?Test=passed", true);
}
Within the web.config i have defined the following:
<customErrors mode="On" defaultRedirect="Error.aspx" />
Now the recognizing of the cookies works well, but I have this problem: Whenever an error occurs on the page and I should be redirected to Error.aspx (and this worked before the whole cookie detection thing), the redirection seems stuck in an infinite loop and appends more and more "?Test=passed" to the URL. I should mention that the Errors.aspx also has the same masterpage and thus also performs the cookie check. However I have no clue why the redirection doesn't stop. Is there a way to solve this problem other than to exlude the Errors.aspx page from having the master page? Thank you very much.
If the CookieCheck.aspx page also uses the same Master page it will keep redirecting recursively, make sure that CookieCheck.aspx is not using the same MasterPage.
I'd rather recommend not using MasterPages for this, Master Pages by design are for Visual Inheritance not code Inheritance, if you wish to make some special type of pages that checks for the the browser ability to use cookies, you can have a new base class for these pages
public abstract class CookieEnabledPage : Page
{
}
and add your logic to this class, then whenever you need to make a new page with this behavior you inherit from this base class. I think this is a much cleaner way of doing what you want.
I guess the masterpage (or the combination masterpage-error.aspx) is raising an exception which triggers a redirect to error.aspx, which in turn causes the masterpage to restart its lifecycle and raise a new exception. The concatenation of "?Test=passed" is almost certainly a side effect of reinvoking the cookie test every time an error redirect occurs.
I suggest firing up the debugger and setting a breakpoint at Page_Load in Masterpage.aspx.cs and step through until you are redirected to the error page (the last line which gets execued is the one raising the exception).