Sitecore MVC Postbacks with URL Renderings

After adding a URL Rendering (which had a form on it) to the presentation details of the standard values of my template, I noticed that I could not get into the HttpPost action of my controller. I used dotPeek to decompile the Sitecore.Mvc.dll and looked at Sitecore.Mvc.Presentation.UrlRenderer and noticed that there is a call to WebUtil.ExecuteWebPage(url). This only does an HttpGet request. To submit a form with a URL Rendering I created two files:

GetUrlRenderer.cs:

using Sitecore.Data.Items;
using Sitecore.Mvc.Extensions;
using Sitecore.Mvc.Names;
using Sitecore.Mvc.Presentation;
using Sitecore.Mvc.Pipelines.Response.GetRenderer;

namespace Site.Web.Code
{
  public class GetUrlRenderer : Sitecore.Mvc.Pipelines.Response.GetRenderer.GetUrlRenderer
  {
    protected override Renderer GetRenderer(Rendering rendering, GetRendererArgs args)
    {
      string url = this.GetUrl(rendering, args);
      if (StringExtensions.IsWhiteSpaceOrNull(url))
        return (Renderer) null;
      return (Renderer) new Site.Web.Code.UrlRenderer()
      {
        Url = url
      };
    }
  }
}

and UrlRenderer.cs

using Sitecore.Mvc.Extensions;
using Sitecore.StringExtensions;
using Sitecore.Web;
using System.IO;
using Sitecore.Mvc.Presentation;
using System.Web;
using Sitecore.Diagnostics;
using System.Net;
using System.Collections.Specialized;
using System.Text;

namespace Site.Web.Code
{
    public class UrlRenderer : Sitecore.Mvc.Presentation.UrlRenderer
    {
        public override void Render(TextWriter writer)
        {
            string url = this.Url;
            if (Sitecore.Mvc.Extensions.StringExtensions.IsEmptyOrNull(url))
                return;

            string str = string.Empty;
            if (HttpContext.Current.Request.RequestType.ToLower() == "post")
            {
                str = ObjectExtensions.ToStringOrEmpty<string>(ExecuteWebPagePost(url, HttpContext.Current.Request.Form));
            }
            else
            {
                str = ObjectExtensions.ToStringOrEmpty<string>(WebUtil.ExecuteWebPage(url));
            }

            if (Sitecore.Mvc.Extensions.StringExtensions.IsEmptyOrNull(str))
                return;

            writer.Write(str);
        }

        public static string ExecuteWebPagePost(string url, NameValueCollection form)
        {
            Assert.ArgumentNotNull((object)url, "url");
            if (url.IndexOf("://") < 0)
                url = WebUtil.GetServerUrl() + url;
            WebClient webClient = new WebClient();

            byte[] responseBytes = webClient.UploadValues(url, "POST", form);
            string response = Encoding.UTF8.GetString(responseBytes);

            return response;
        }
    }
}

UrlRenderer has been changed to do a form submission by calling ExecuteWebPagePost instead of WebUtil.ExecuteWebPage and GetUrlRenderer has been changed to instantiate our custom UrlRenderer. To hook this up I needed to change /App_Config/Include/Sitecore.Mvc.config From:

<mvc.getRenderer>
<processor type=”Sitecore.Mvc.Pipelines.Response.GetRenderer.GetUrlRenderer, Sitecore.Mvc”/>
</mvc.getRenderer>

To:

<mvc.getRenderer>
<processor type="Site.Web.Code.GetUrlRenderer, Site.Web"/>
</mvc.getRenderer>

/Views/Search/Search.cshtml:

@inherits System.Web.Mvc.WebViewPage
@{
Layout = null;
}

Route in Globals.ascx:

routes.MapRoute(  "Search_UrlRendering",
"Search_UrlRendering",
new { controller = "Search", action = "Search" }  );

/Controllers/SearchController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Site.Web.Controllers
{
   public class SearchController 
   {
      [HttpGet]
      public ActionResult Search()
      {
         return View();
      }

      [HttpPost]
      public ActionResult Test(FormCollection form)
      {
         ViewData["search"] = form["search"];
         return View();
      }
   }
}

In Sitecore, I have created /Sitecore/Layouts/Renderings/Search_UrlRendering and set the URL field to /Search_UrlRendering which is the second parameter to my route in the Global.ascx file.

Now I am able to post back from a UrlRendering.