Sitecore Sublayouts rendering on a Sitecore MVC Page (Part 2)


Continued

In my post Sitecore Sublayouts rendering on a Sitecore MVC Page (Part 1) I talked about how to get a very simple sublayout to render on a Sitecore MVC page. We did this by simply swapping out the sublayout with a predefined razor view rendering in the mvc.buildPageDefinition pipeline. The view rendering then executed the logic to execute the sublayout and write the output to the page. I also mentioned that the way it was written would not support viewstate. For a static site that is great because viewstate is ugly but you will find that as soon as you add an asp:button to the page you get a big fat exception:

Control 'ctl01_Button1' of type 'Button' must be placed inside a form tag with runat=server.

Well that's not good! Let's see how we can fix that.

Enabling ViewState and PostBacks

In order to fix this we need to think about the following:
  1. We need a ViewState provider which is implemented as a hidden input field by the name of "__VIEWSTATE".
  2. We need a way to execute an HTTP POST back to the page.
  3. The execution of the sublayout/UserControl need access to the request context of the POST so it can process the postback data.
It turns out that the first two points can be solved by simply adding a webcontrol of type System.Web.UI.HtmlControls.HtmlForm between the Page and the sublayout control.
    private static void AddSublayoutToPage(TemplateControl page, string path, string datasource,
        IEnumerable<KeyValuePair<string, string>> parameters)
    {
        var dict = ToDict(parameters);
        var sublayout = CreateSublayout(page, path, datasource, dict);
        var form = CreateForm();
        form.Controls.Add(sublayout);
        page.Controls.Add(form);
    }

    private static HtmlForm CreateForm()
    {
        var formName = GetNextFormName();
        var form = new HtmlForm
        {
            Method = "POST",
            ID = formName,
            Name = formName
        };
        return form;
    }

    private static string GetNextFormName()
    {
        var formNo = 0;
        int countParse;
        if (HttpContext.Current.Items.Contains(CONTEXT_KEY) &&
                int.TryParse(HttpContext.Current.Items[CONTEXT_KEY].ToString(), out countParse))
            formNo = countParse;
        var formName = string.Concat("Form", formNo);
        HttpContext.Current.Items[CONTEXT_KEY] = ++formNo;
        return formName;
    }
By adding the form you will see that page will load successfully again. If you look at the markup that is rendered you will see the form tag along with the ViewState. Even though everything looks like it should work, if you click the button the User control will still not process this as a PostBack. Now we will have to execute the page in a way that has access to the current context but does not write directly to the response object. Here is what that can look like:
public static IHtmlString RenderSitecoreSubLayout(this HtmlHelper html, string path, string datasource = null, RenderingParameters parameters = null)
{
    var page = new DummyPage { ViewStateEncryptionMode = ViewStateEncryptionMode.Never };
    AddSublayoutToPage(page, path, datasource, parameters);
    var result = ExecutePage(page);
    return html.Raw(result);
}

private static string ExecutePage(DummyPage page)
{
    page.ProcessRequest(HttpContext.Current);
    return page.Result;
}

private class DummyPage : Page
{
    public string Result { get; private set; }

    protected override void Render(HtmlTextWriter writer)
    {
        var buffer = new StringWriter();
        var stringWriter = new HtmlTextWriter(buffer);
        base.Render(stringWriter);
        Result = buffer.ToString();
    }
}
You can now see how each Sublayout we add to the presentation details will create it's own form with it's own ViewState. This solution will not support any sc:placeholder controls on the user sublayout controls to work correctly but it will allow you start using some (not all will work I'm sure) of the legacy controls that we wrote or found in the marketplace.
If you would like to look at the source for this and play with it I am making available @https://github.com/BissTalk/SitecoreSynergy
No comments

No comments :

Post a Comment