User Control Postback Events Not Firing

Topics: Controls
Apr 30, 2012 at 2:56 PM

I am using BlogEngine 2.6.0.0

I have created a simple user control with a single <asp:Button />. It is embedded in a BlogEngine Page (not post) using the [user control:] syntax. See the complete definition below.

The custom user control is in fact loaded correctly into the Page at runtime. Unfortunately the postback event for the button_click event is never called, and instead, when the button on the user control is clicked in the browser, the user is redirected to the home page.

Note that when the mouse is hovered over the button on the user control, I can see in the browser window that the button has been wired (by BE somehow) to request the correct Page (the one hosting the user control), but the link has a querystring parameter ?id=.... (e.g., http://localhost/BlogEngine.NET/page/my-page.aspx?id=ae81fc..._).

Upon closer inspection, it appears that the page is modified by some BE Core logic to over-write the normal button_click client-side behavior with the new URL that has the id querystring parameter.

This is interfering with the ability to process a simple button_click post back event. What can I do to have a user control hosted in a Page process a custom postback event?

Thanks in advance.

Here is the user control definition:

------------------
.ASCX Definition:
------------------
<%@ Control Language="C#" AutoEventWireup="false" CodeBehind="LopersEventCheckIn.ascx.cs" Inherits="BlogEngine.NET.User_controls.LopersEventCheckIn" %>
<p>Hello World</p>
<p>
    <asp:Button ID="CheckIn_Button" runat="server" Text="Check In" />
</p>

--------------------
.ascx.cs code behind
--------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace BlogEngine.NET.User_controls
{
    public partial class LopersEventCheckIn : System.Web.UI.UserControl
    {
        public LopersEventCheckIn()
        {
            this.Load += new EventHandler(LopersEventCheckIn_Load);

            this.CheckIn_Button = new Button();

            if (this.CheckIn_Button != null)
            {
                this.CheckIn_Button.Click += new EventHandler(CheckIn_Button_Click);
            }
        }

        void LopersEventCheckIn_Load(object sender, EventArgs e)
        {
            Response.Write("<br />LOADED"); // This wires up just fine and "LOADED" appears on the rendered page
        }


        protected void CheckIn_Button_Click(object sender, EventArgs e)
        {
            Response.Write("<br />CHECKED IN SUCCESSFULLY");  // This never happens because user is redirected to home page.
        }
    }
}

--------------------
Insertion syntax into Page
--------------------
[usercontrol:~/User Controls/LopersEventCheckIn.ascx]

May 6, 2012 at 9:43 PM

I found a way to enable PostBack for custom user controls in BlogEngine. It appears that PostBacks were disabled by virtue of a bug in BlogEngine that causes the current BlogID to be appended to the URL twice. In other discussions about postbacks being disabled in BlogEngine I have read that the disabling of PostBacks was by design. The rationalle being something like: "you should not want to have PostBacks in a custom user control, anyway, because the PostBack would cause the page to jump back to the top." That may be fine logic, but (1) the implementation is more of a bug than a feature; and (2) it should be me, the developer, who determines when some runtime behavior is desirable and when it is not desirable - particularly if the feature [being disabled] is a fundamental part of ASP.NET, itself (i.e, PostBacks).

So, for  those of you who want to enable PostBacks for your custom user controls in BlogEngine, here is where the logic/bug needs to be modified/fixed:

1. open page.aspx.cs (in the root of the BlogEngine.NET project) and find the OnInit event procedure. Here is the entire original/unmodified version as of BE version 2.6.0.0:

protected override void OnInit(EventArgs e)
{
   var queryString = this.Request.QueryString;
   var qsDeletePage = queryString["deletepage"];
   if (qsDeletePage != null && qsDeletePage.Length == 36)
   {
      this.DeletePage(new Guid(qsDeletePage));
   }

   var qsId = queryString["id"];
   if (qsId != null && qsId.Length == 36)
   {
      this.ServePage(new Guid(qsId));
      this.AddMetaTags();
   }
   else
   {
      this.Response.Redirect(Utils.RelativeWebRoot);
   }

   base.OnInit(e);
}

The Response.Redirect in the above method is what causes your PostBacks to result in the home page of the blog being displayed rather than your postback processing to be allowed to run it's natural course. What causes that 'else' condition above to run during a PostBack is that qs.Id.Length is greater than 36. The reason it is greather than 36 is that BlogEngine.net prepends the blog ID twice to the querystring during the PostBack operation. So, your solution is to either (1) prevent BlogEngine from appending the blog ID twice; or (2) modify the logic above to either strip the duplicate BlogID value before the 'if' test; or otherwise allow for the ServePage() and AddMetaTags() logic to run.

2. For those who want to prevent BlogEngine from appending the BlogID twice to the querystring (i.e. if you want to fix the bug in BE), here is what you can do:

Open this file: \BlogEngine.Core\Web\HttpModules\UrlRewrite.cs   and find the follwoing method:

private static void RewritePage(HttpContext context, string url)
{
   var slug = ExtractTitle(context, url);
   var page =
         Page.Pages.Find(
            p => slug.Equals(Utils.RemoveIllegalCharacters(p.Slug), StringComparison.OrdinalIgnoreCase));

   if (page != null) {
         context.RewritePath(string.Format("{0}page.aspx?id={1}{2}", Utils.ApplicationRelativeWebRoot, page.Id, GetQueryString(context)), false);
   }
}

 Notice in the above call to context.RewritePath() that the first querystring value is the value of page.Id.

Notice also that the querystring also gets whatever was already the value of the querystring. This is the problem. If the querystring already contained the pageID, then the pageID will be appended a second time. In the case of a PostBack from a user control, the result is that the pageId appears twice in the querystring. There is nothing here to prevent it from being appended more than twice.

This is what I specifically consider to be a bug: to automatically append whatever is already the value of the querystring without also ensuring that a duplicate querystring parameter name and value is not going to be the ultimate result of the operation.

So, to fix this - thereby indirectly enabling PostBacks from your user controls - add whatever logic you like that prevents duplicate querystring parameters from being appended to the URL in the above contextRewritePath call.

NOTE: Until the BE developers fix this issue (if they even consider it to be a problem at all - they may not), if you modify either method described above, you will effectively be creating your own branch of the source code. So don't do it unless you know exactly what you are doing and are willing to deal with the potential unintended consequences.

I hope this his helpful to those of you who have banged your head on the wall trying to figure out why a basic functionality of the underlying ASP.NET platform (PostBacks for user controls) is broken in BlogEngine.

- Jeff

Dec 16, 2013 at 4:48 PM
Edited Dec 16, 2013 at 4:56 PM
Great job Jeff;

I ran into this myself and you saved me a lot of debugging.

I noticed that you found this in 2.6. I have a number of implementation out there where the submission works with no issues running on 2.7. I just updated to 2.8 and em experiencing the problem again.

BTW in 2.8'; RewritePage function is in \BlogEngine.Core\Web\UrlRules.cs not in \BlogEngine.Core\Web\HttpModules\UrlRewrite.cs
Dec 16, 2013 at 6:28 PM
Edited Dec 16, 2013 at 6:29 PM
Great got it to work by replacing RewritePage with the following code:
        public static void RewritePage(HttpContext context, string url)
        {
            var slug = ExtractTitle(context, url);
            var page =
                Page.Pages.Find(
                    p => slug.Equals(Utils.RemoveIllegalCharacters(p.Slug), StringComparison.OrdinalIgnoreCase));

            if (page != null)
            {

                //jk-20131216: Added code to fix issue with firing event on page due to passing of duplicate page ID
                //https://blogengine.codeplex.com/discussions/354014

                var cleanQueryString = GetQueryString(context);
                NameValueCollection queryStringItems = HttpUtility.ParseQueryString(cleanQueryString);
                cleanQueryString = "";

                foreach (string queryStringItem in queryStringItems.AllKeys)
                {
                    if (queryStringItem != "id")
                    {
                        if (queryStringItem != null)
                        {
                            cleanQueryString += "&" + queryStringItem + "=" + queryStringItems[queryStringItem];
                        }
                    }
                }

                context.RewritePath(string.Format("{0}page.aspx?id={1}{2}", Utils.ApplicationRelativeWebRoot, page.Id, cleanQueryString), false);

            }
        }
One side effect of this is that the rewrite is no longer taking out the ID when it renders the page after the event fires

__i.e. shows www.sample.com/page?id=34759823745982347 rather than www.sample.com/page__

other than that it works like a charm.