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.
Related
What's the "skeleton" code for using CsQuery in the code-behind of a MasterPage in order to modify the HTML output? I need to be able to modify everything in the <body> of the HTML?
I'm hoping to use CsQuery to "touch-up" the HTML output of a Dynamic Data website without rewriting / messing with the default code.
Just looking for sample code specific to MasterPage code-behind? Thanks.
There is an example in the CsQuery project that shows how to do this (which I just made sure was working right!) in the CsQuery.WebFormsApp project.
The core of the usage looks like this. You must override the Render method in a class that inherits Page, and use this instead of Page as the base class for the codebehind in an aspx page:
public class CsQueryPage: System.Web.UI.Page
{
public CQ Doc { get; protected set; }
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
// most of the work is done for you with the
// `CsQuery.WebForms.CreateFromRender` method
var csqContext = WebForms.CreateFromRender(this, base.Render, writer);
// if you are using update panels, this lets you also manipulate that
// HTML, otherwise you don't need the IsAsync part
if (csqContext.IsAsync)
{
foreach (var item in csqContext.AsyncPostbackData) {
Cq_RenderUpdatePanel(item.Dom,item.ID);
}
}
else
{
Doc = csqContext.Dom;
Cq_Render();
}
// writes the altered content to the base HtmlTextWriter
csqContext.Render();
}
protected virtual void Cq_Render()
{ }
protected virtual void Cq_RenderUpdatePanel(CQ doc, string updatePanelId)
{ }
}
The two virtual methods are where you can alter the dom, which is populated in the Doc property of the CsQueryPage object - the intent of leaving them unimplemented here is that each aspx page that inherits CsQueryPage can optionally override them and make changes to the DOM.
To see how this works in practice just pull down the CsQuery code from github and run the example.
The same technique can be used for a UserControl which is also shown in the example. I don't actually show how to do it with MasterPage but it's very much the same-- MasterPage derives from UserControl, you just override it's Render method same as the other situations.
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()
I have a really weird problem using ASP.NET/C# 4.0 that I haven't been able to find an answer to. I have a custom base master page for my child master pages, and a custom base page class for my pages. My problem is that if I access the Master member from any of my pages, the page content itself doesn't get rendered, only the master pages.
So, to recap in a different way:
"Master Page" inherits System.Web.UI.MasterPage
"Nested Master" using "Master Page" inherits System.Web.UI.MasterPage
"WebForm" using "Nested Master", WebForm is inheriting CustomPageBase which inherits System.Web.UI.Page
When CustomBaseMaster accesses this.Master it will not render the webform's content, only the "Nested Master" and "Master Page" content. I have tried it with only reading a member, writing a member, and both reading and writing, and not even accessing a member, just calling Debug.WriteLine(this.Master)
If it helps any, I am using a Page Decorator on "WebForm" that needs to update the controls on the "Master Page"
Unfortunately, my google searching queries show up with results on how to access public members of the Master Page. But nothing I could find would help explain why, when accessing the master page, it results in the page's content not displaying.
Has anybody seen anything like that and can offer any suggestions? Google hasn't been much help, been searching for a couple of hours now.
Ok, turns out when using the Page decorator at the top of the class it assigns the properties of the page class before PreInit completes. I used ILSpy to look at what happens when you access the Master property of the page, if it is accessed before the Master page has been created (internally null) and before all of the PreInit methods are called, it will create a new master page and uses that content. Kind of annoying. Anyways, my solution was to not make the properties bubble up the values to the parent master pages on getting and setting them, but overloaded the OnLoad event in my base page and base master classes, and set the properties on the parents there.
So, instead of doing this:
namespace MyWebsite
{
public abstract class MyPageBase : Page
{
public int PropertyName
{
get
{
return ((MyMaster)this.Master).RatePageId;
}
set
{
((MyMaster)this.Master).RatePageId;
}
}
}
}
I did this:
namespace MyWebsite
{
public abstract class MyPageBase : Page
{
public int? PropertyName
{
get;
set;
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (PropertyName != null)
{
((MyMaster)this.Master).PropertyName = PropertyName.Value;
}
}
}
}
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)