Tuesday 14 January 2014

Solution for MVC Ajax.BeginForm throwing SecurityPermission due to medium trust Level

I've written a very simple MVC 5 application and deployed it to my 1and1 hosting provider. Immediately I was struck by an error. I've forgotten that 1and1 like many other hosting providers enforce medium trust level.

I've gone to my local project and modified the config to simulate this problem locally. Line of code that threw an error was:
@using (Ajax.BeginForm("GetInTouch", "Home", new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "makeContact"}))
{

}

Stack trace:
 
Request for the permission of type 'System.Security.Permissions.SecurityPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed

at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet)
    at System.Security.CodeAccessSecurityEngine.Check(CodeAccessPermission cap, StackCrawlMark& stackMark)
    at System.Security.CodeAccessPermission.Demand()
    at System.Web.HttpContext.System.IServiceProvider.GetService(Type service)
    at System.Web.HttpContextWrapper.GetService(Type serviceType)
    at System.Web.WebPages.UrlRewriterHelper.IsUrlRewriterTurnedOn(HttpContextBase httpContext)
    at System.Web.WebPages.UrlRewriterHelper.WasRequestRewritten(HttpContextBase httpContext)
    at System.Web.WebPages.UrlUtil.GenerateClientUrlInternal(HttpContextBase httpContext, String contentPath)
    at System.Web.WebPages.UrlUtil.GenerateClientUrl(HttpContextBase httpContext, String contentPath)
    at System.Web.Mvc.UrlHelper.GenerateUrl(String routeName, String actionName, String controllerName, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, Boolean includeImplicitMvcValues)
    at System.Web.Mvc.Ajax.AjaxExtensions.BeginForm(AjaxHelper ajaxHelper, String actionName, String controllerName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary`2 htmlAttributes)

I was shocked 1and1 hosting provider said that they support MVC 3 and 4, .NET Framework 3.5 and 4. All I did was build a super simple website with a contact form. What's happening?

Line System.Web.HttpContext.System.IServiceProvider.GetService(Type service) indicates that it's using some kind of a internal dependency injection. Dependency injection normally uses reflection to build objects, reflection is not supported in medium trust environments.

Taking a closer look at the stack trace we see that it's using System.Web.WebPages.UrlUtil.GenerateClientUrl(HttpContextBase httpContext, String contentPath). It's using Html.BeginForm(method, controller) to build the URL for the form.

Now, we identified the problem, where and why it's happening. So what's the solution? Not to use helpers? Create your own helpers? These are all possible solutions. Luckily, I am using Ajax helper so I can get away with the following for now:

@using (Ajax.BeginForm(new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "makeContact", Url = "Home/GetInTouch"}))
{

}

As URL is specified manually (Url = "Home/GetInTouch") it's not going to try and generate it. This still leaves us with other problems.

You can't use Html.BeginForm(action, controller) as it's going to try and resolve these URL's. If you go with Ajax solution above and client has no JavaScript nothing will happen, form action will point to "/". This is not great, at least now you have an answer and a partial solution.

If you have found a better way around this, please do comment.