ASP.NET dynamic data: Table is not shown anymore a

2019-06-24 02:59发布

问题:

Lately, I received a bug report for Ninject.Web that it is not working properly together with ASP.NET dynamic data. The problem is that on postback (e.g. when Inserting, Deleting, Editing a record) the table is not shown anymore.

Some debuging showed that the problem is caused by a IHttpModule that recursively iterates through all controls of a page after it is initialized. As soon as this module accesses the Controls property get accessor of FormView or GridView the problem occurs. If this type of controls is skiped everything is fine. The following code shows the module:

public class NinjectHttpModule : DisposableObject, IHttpModule
{
    private HttpApplication httpApplication;

    public void Init(HttpApplication context)
    {
        this.httpApplication = context;
        this.httpApplication.PreRequestHandlerExecute += this.OnPreRequestHandlerExecute;
    }

    private static void InjectUserControls(Control parent)
    {
        if (parent == null)
        {
            return;
        }

        foreach (Control control in parent.Controls)
        {
            if (control is UserControl)
            {
                // KernelContainer.Inject(control); This is irrelevant for the question.
            }

            InjectUserControls(control);
        }
    }

    private void OnPreRequestHandlerExecute(object sender, EventArgs e)
    {
        var page = this.httpApplication.Context.CurrentHandler as Page;

        if (page == null)
        {
            return;
        }

        KernelContainer.Inject(page);
        page.InitComplete += (src, args) => InjectUserControls(page);
    }
}

If this code is changed so that the iteration through the child controls of DataBoundControls is delayed to the DataBound event everything is fine. Shown by the next code snippet:

    private static void InjectUserControls(Control parent, bool skipDataBoundControls)
    {
        if (parent == null)
        {
            return;
        }

        if (skipDataBoundControls)
        {
            var dataBoundControl = parent as DataBoundControl;
            if (dataBoundControl != null)
            {
                dataBoundControl.DataBound += InjectDataBoundControl;
                return;
            }                
        }

        foreach (Control control in parent.Controls)
        {
            if (control is UserControl)
            {
                KernelContainer.Inject(control);
            }

            InjectUserControls(control, skipDataBoundControls);
        }
    }

    private static void InjectDataBoundControl(object sender, EventArgs e)
    {
        var dataBoundControl = sender as DataBoundControl;
        if (dataBoundControl != null)
        {
            dataBoundControl.DataBound -= InjectDataBoundControl;
            InjectUserControls(dataBoundControl, false);
        }
    }

Because I'm completely unfamiliar with System.Web.DynamicData I'd like to know some things to get a better feeling about how to fix this bug:

  • Why does this problem occur? I mean it's only a simple read access to the Controls property.
  • What side effects can the change above have?
  • Is it still early enough to inject the controls after the data bound event?
  • Do you think this is a valid bug fix for this problem?

回答1:

Certainly puzzling behavior, as can sometimes happen in WebForms with the many phases of execution.

Even though it's just a simple read access to the Controls property, this property can actually do a lot of work to return the child controls. In particular, it can't return the child controls unless they have been created, and that creation normally does not occur until later in the page life cycle. So by accessing it in InitComplete, the children end up getting created prematurely, before some important Dynamic Data hookups have happened, causing some controls to be missing. Yes, I realize that the end result behavior seems to make little sense, which is why some people favor the straightforwardness of MVC :)

As an alternate possible workaround, could you try moving your injection from InitComplete to PreLoad? e.g.

page.PreLoad += (src, args) => InjectUserControls(page);

I'm pretty sure that'll address the problem, though I'm less sure whether this will cause issues with your KernelContainer.Inject logic. Give it a try, since it's simpler than your workaround.

If that doesn't work, I think your workaround is ok, as it delays the enumeration until the children are created. As for 'Is it still early enough to inject the controls after the data bound event', I think that depends in exactly what KernelContainer.Inject does, and what expectations it has on the state of the control.