How do I skip the "Control Events" portion of the ASP.NET Page Life Cycle without skipping re-rendering in a Postback?

36 views Asked by At

Background

I've been tasked with retrofitting a large-scale legacy application with a quick and dirty little check to ensure that Controls which have been Disabled when the page was created, have not been re-enabled on the clientside prior to the postback.

Obviously, it would be preferable for the application to have originally been written such that every postback handler was thoroughly implementing validation and only processing using the data inputs it actually needs, but these are not the circumstances I find myself in.
The second best preference would be for the powers that be to consider spending all the money and effort of making all the postbacks work that way to be worth it, but alas.

So here we are, with an enormous application in it's wind-down period but not quite at the point of being replaced, with a need to ensure security compliance by definitively preventing the possibility for a disabled input (typically included on the page because either in some circumstances it's not disabled, or because it's using a reusable component to 'display' that data, where sometimes the component is used to update it as well) being re-enabled and modified in the browser, causing subsequent postbacks which assume it to be unchanged, to take and use it's improperly modified data in some way.

Edit: I've got an implementation I'm happy with for the "Check the controls and make sure they haven't been tampered with" part of my problem, what I'm struggling with is where and how to insert this check into the ASP Page Lifecycle so that it works within my feature constraints ("Control Event Handlers" prevented from running, but modifications to the page like showing a div and putting an error message in it that happen during the page_load (or earlier) event, still do run and get sent back to the browser)

Question

Having read this article: https://web.archive.org/web/20101224113858/http://www.c6software.com/codesolutions/dotnet/threadabortexception.aspx
I am aware that what I could do is insert an override for RaisePostBackEvent on every page in the application, which hooks into a variable that I could conditionally set in the Page_Load from one of the .master pages when detecting the control issue, which determines whether to follow-through with the EventHandlers from the PostBack or not.
However, given there are 5 .master pages and 540 'regular' pages, and since the check is applicable to all of them, I'd ideally love to be able to implement something at the .master level when detecting this particular issue instead.
Something that would effectively let me do this:

Looking at that article as the closest I've come to an answer, I basically want to do exactly this variation of it:

/* MainPage.master */

    protected void Page_Load(object sender, EventArgs e)
    {
        if (ControlManipulationDetected)
        {
            ToggleErrorDivVisible(true);
            ErrorMessage = "Don't do that, that's naughty!";
            HttpContext.Current.ApplicationInstance.CompleteRequest();

            void RaisePostBackEvent_Stop(IPostBackEventHandler sourceControl, string eventArgument)
            {
                // No-Op!
            }
            Page.RaisePostBackEvent = RaisePostBackEvent_Stop;

            // remember to end the method here if
            // there is more code in it
            return;
        }
    }

Except that it looks like Page doesn't let anyone outside Page modify that callback directly, so does anyone know of some other kind of shenanigan I can use to accomplish this, or something similar?

Worst case scenario, if no-one can think of any better ideas, I'll just have to settle down with a coffee and do a bunch of copy-pasting to implement it at the page level.
Though I'd really prefer to avoid doing that because it then means that in addition to taking up a bunch of my time, it will both risk my doing it incorrectly somewhere and breaking something during the initial implementation, and it will put an onus on all future page development to ensure this override is implemented for the page to be properly protected (which is more fail-deadly than fail-safe).

What I've tried

I've tried using Response.End(), but that is a hard stop for any and all processing and doesn't allow for rendering of any changes to the page that I did want to make (as the design for the application calls for errors to be displayed in an emulated pop-up via div toggling, rather than a redirect anything like that)
Or at least, it doesn't with any of the ways I've tried using it. If there's some way to explicitly trigger the Rendering portions of the pipeline early, and then End the response, that could also work...

I've tried using HttpContext.Current.ApplicationInstance.CompleteRequest(); by itself, but that doesn't actually skip the "Control Events" portion, and the Postback-causing Event is still handled on the page (which eventually led me to the above article explaining why).

I've tried using Response.Redirect and having a dedicated error page for that eventuality, but the rest of the application doesn't work like that, it always puts the error at the top of the page, so that non-standard reaction is not particularly well liked by the application owners.

1

There are 1 answers

1
Albert D. Kallal On

But what criteria for what controls? And where do you plan to save or control such information?

You do realize that if you set a controls visible property = false, then that markup is NEVER sent to the client side browser, and thus it can't be re-enabled or messed with client side anyway.

So, I guess the question is where and how are you going to decide what control to hide and not be rendered in the markup? I fail to see how intercepting some post-back or messing with the page life cycle going to help here, since you STILL as a developer have to pick and choose what controls you don't want being rendered in the client side browser.

Since you going to have to look at, read and see and choose what controls in question, then it would seem at that point in time would the VERY same time that you see the control you want protected would be the VERY same instant that you simple set that control's visible = false

Hence this markup:

So, say I have this markup, and don't want the TextBox control to be touched client side:

        <h3>Test stuff</h3>

        <asp:TextBox ID="TextBox1" runat="server"
        >
        </asp:TextBox>

Ok, then add this:

        <h3>Test stuff</h3>

        <asp:TextBox ID="TextBox1" runat="server"
            Visible="false">
        </asp:TextBox>

And now when the page is run, the markup will not have the TextBox, and not even JavaScript or any client side code can do anything at all.

So, when I run the above page, hit f12 for browser dev tools, I see this:

enter image description here

So, any control with the property set visible=false will not be sent nor rendered in the browser.

The issue here is where, when, how are you going to decide what controls are to be hidden and where to you plan to save or write such code? No matter what approach you use, you still need a list of controls, and a list for each and every page. And you have to plow thought each and every page to get the control id used.

If you have say this markup:

        <h3>Test stuff</h3>

        <asp:TextBox ID="TextBox1" runat="server">
        </asp:TextBox>

And in on page load do this:

    protected void Page_Load(object sender, EventArgs e)
    {
        TextBox1.Visible = false;

    }

Once again, the control will thus never be sent client side, nor rendered, nor appear in the client side markup.

check to ensure that Controls which have been Disabled when the page was created, have not been re-enabled on the client side prior to the post back.

Why should it matter if some disabled control was re-enabled? I mean, anyone always been able to hit f12, and modify any text on a web page, but I fail to see how that can be provided, but worse, I'm failing how that could or would be some security issue?

It is not like some disabled controls on a page are going to be used for input, and I can't recall me having used such code anyway. And certainly one should never say display some primary key value in a control, that is THEN used against a database to determine say the record loaded. Since enabled, or disabled, such controls always were open to modifications by people messing around with their browser debug tools. I fail to see how this issue applies to webforms more or less then any other technology stack.

Now, as you note, it's possible that some code exists that takes such user data, but then again, verification of input is a vast different issue then that of allowing or using values from some disabled control.

It not clear if you talking about disabled controls, or controls that are to be hidden?

Since your faced with having to read and view the markup in each page, then at that point, I fail to see some advantage of introducing some vast different post model compared to the built in one that exists already.

Values of disabled controls should not matter to the code behind (apparently it seems to in your case).

If values of disabled controls are not to be changed, then the code behind should not use such values. How is the code you want going to verify that the disabled control has what value it supposed to have without additional code?

The only practical solution here is to hide the controls, so they don't appear in the markup EVER and thus not subject to client side tampering in the first place.

But, if such controls are used in code behind, then the amount of code to verify such values written should belong in that given page, since each and every page is going to be different anyway, and thus no advantage exists in attempting to write or place that code someplace else.

I suppose if you had a "list" controls you don't want changed, then better would be re-run the code code that loaded and set their values as such again, and thus it would not matter if they been changed.

So, I might be missing some big goal here, but I'm not really aware why a control used for display, and one that is disabled would matter much in near any of those 500+ pages.

It might I suppose matter in a few pages, but not all of them.

However, I certainly think that some code in the master page could be adopted here. When a post-back occurs on a child (of master), then the sequence is this:

child page load event fires.
master page load event fires.
child page event code stub runs.

So, yes, it would seem that the master page load event is a location in which you can check or modify those disabled controls, and I guess what? (restore their values???? - it was never clear how your code plans to do that).

You can of course also in the master page load event check/see which child page is being processed like:

        string pageName = MainContent.Page.GetType().FullName;
        Debug.Print("master page load");

        if (pageName == "ASP.default_aspx")
        {
            // get text box 1 on child page.

            //TextBox tbox1 = (TextBox)MainContent.FindControl("TextBox1");
            //tbox1.Text = "Hello world";

        }

So, if such disabled controls can be hidden (visible = false), then of course that's the best solution, since any controls not visible are never rendered client side.

However, assuming that such client controls are used for code behind, but not to be changed, then the load event of the master page could check which page, and then verify the disabled controls have their correct values (not 100% sure how that code going to know what the correct values are supposed to be, but at least that's a good location for such code to do such checking).