0

I am trying to implement this example:

Stack Overflow

But my ASP.NET MVC4 app keeps throwing Filtering is not allowed when it executes anything after the first Action exception messages on the line of code filterContext.RequestContext.HttpContext.Response.Filter = new CdnResponseFilter(filterContext.RequestContext.HttpContext.Response.Filter);.

Based on some searching I've tried moving the filter to another event function but none of them have worked. I have also tried checking if the filter is already applied and if so don't add a new one.

The first action is properly filtered, it's on the second action being executed that it throws the exception.

Code

public class CDNUrlFilter : IActionFilter, IResultFilter
{
    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        filterContext.RequestContext.HttpContext.Response.Filter = new CdnResponseFilter(filterContext.RequestContext.HttpContext.Response.Filter);
    }

    public void OnResultExecuting(ResultExecutingContext filterContext)
    { 
    }

    public void OnResultExecuted(ResultExecutedContext filterContext)
    { 
    }
}

public class CdnResponseFilter : MemoryStream
{
    private Stream Stream { get; set; }

    public CdnResponseFilter(Stream stream)
    {
        Stream = stream;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {

        var data = new byte[count];
        Buffer.BlockCopy(buffer, offset, data, 0, count);
        string html = Encoding.Default.GetString(buffer);

        html = Regex.Replace(html, "src=\"/Content/([^\"]+)\"", FixUrl, RegexOptions.IgnoreCase);
        html = Regex.Replace(html, "href=\"/Content/([^\"]+)\"", FixUrl, RegexOptions.IgnoreCase);

        html = Regex.Replace(html, "src=\"/Images/([^\"]+)\"", FixUrl, RegexOptions.IgnoreCase);
        html = Regex.Replace(html, "href=\"/Images/([^\"]+)\"", FixUrl, RegexOptions.IgnoreCase);

        byte[] outData = Encoding.Default.GetBytes(html);
        Stream.Write(outData, 0, outData.GetLength(0));
    }

    private static string FixUrl(Match match)
    {
        if (match.ToString().Contains("src"))
        {
            return String.Format("{0}", match.ToString());
        }
        else if (match.ToString().Contains("href"))
        {
            return String.Format("href=\"{0}content{1}", Settings.Default.CDNDomain, match.ToString().Replace("href=\"", ""));
        }
        return match.ToString();
    }
}

As requested here is the RegisterGlobalFilters which is called from Global.asax.cs:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        filters.Add(new Filters.InitializeSimpleMembershipAttribute());
        filters.Add(new Filters.RequestTimingFilter());
        if (Settings.Default.CDNEnable)
        {
            filters.Add(new Filters.CDNUrlFilter());
        }
    }
}

Another edit to add the complete stack trace:

Stack Trace

at System.Web.HttpResponse.set_Filter(Stream value) 
at POSGuys.Filters.CDNUrlFilter.OnActionExecuted(ActionExecutedContext filterContext) in c:\Users\skatir\Documents\BitBucket\posguys\Filters\CDNUrlFilter.cs:line 20 
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.b__49() 
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.b__49() 
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.b__49() 
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.b__36(IAsyncResult asyncResult) 
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass25.<>c__DisplayClass2a.b__20()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass25.b__22(IAsyncResult asyncResult) 
at System.Web.Mvc.Controller.<>c__DisplayClass1d.b__18(IAsyncResult asyncResult) 
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass4.b__3(IAsyncResult ar) 
at System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) 
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass4.b__3(IAsyncResult ar) 
at System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) 
at System.Web.Mvc.MvcHandler.<>c__DisplayClass8.b__3(IAsyncResult asyncResult) 
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass4.b__3(IAsyncResult ar) 
at System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) 
at System.Web.Mvc.HttpHandlerUtil.ServerExecuteHttpHandlerWrapper.<>c__DisplayClass4.b__3() 
at System.Web.Mvc.HttpHandlerUtil.ServerExecuteHttpHandlerWrapper.Wrap[TResult](Func`1 func) 
at System.Web.HttpServerUtility.ExecuteInternal(IHttpHandler handler, TextWriter writer, Boolean preserveForm, Boolean setPreviousPage, VirtualPath path, VirtualPath filePath, String physPath, Exception error, String queryStringOverride)


Target Site

Void set_Filter(System.IO.Stream)

While trying to solve this I've stumbled upon something very weird which is if I wrap the setting of the Response.Filter in OnActionExecute in a try{}catch{} block the page renders using the filter properly. This is making me think that some other part of the system is try to be filtered that shouldn't be. I'm going to do some work in the debugger and see if I can't narrow it down from here.

Community
  • 1
  • 1
siva.k
  • 1,344
  • 14
  • 24
  • How are you using `CDNUrlFilter`? Are you decorating a controller with an attribute `[CDNUrlFilter]`? – Jasen Jun 27 '13 at 18:24
  • No, it's wired into the Global Filters. – siva.k Jun 27 '13 at 19:51
  • Sounds like Filters are possibly not being registered. What does your `Global.ascx.cs` file look like? – technicallyjosh Jun 27 '13 at 21:03
  • @joshthecoder Added my code that registers the filters. The other filters are running perfectly. – siva.k Jun 27 '13 at 21:08
  • That is interesting. I implemented this on my test MVC app and it works flawlessly. I'm doing more testing to dig in. @mr.smors any way to provide the exception details rather than just the "title" - Filtering is not allowed – technicallyjosh Jun 27 '13 at 21:18
  • @joshthecoder I added the stack trace and target site, that's the rest of the info from the exception (the rest is null). I also added a note at the bottom about using a try catch block. Basically on HTML it's working just fine, but something else is being passed to it that it doesn't work with. I'm trying to narrow it down now. – siva.k Jun 27 '13 at 21:36
  • It looks like the exception is thrown after the `InitializeSimpleMembershipAttribute` filter is applied to an action. – siva.k Jun 27 '13 at 21:46
  • And problem solved, it has to be registered **before** the SimpleMembership. I'm not sure why but it's definitely working now and so does the SimpleMembership. – siva.k Jun 27 '13 at 22:04
  • @mr.smors Glad you could figure it out. :) – technicallyjosh Jun 27 '13 at 22:12

2 Answers2

2

I solved this by moving the filter to the top of the list during the registering of the filters. If any one knows why this works I'd love to know but here's my solution:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        if (Settings.Default.CDNEnable)
        {
            filters.Add(new Filters.CDNUrlFilter());
        }
        filters.Add(new HandleErrorAttribute());
        filters.Add(new Filters.InitializeSimpleMembershipAttribute());
        filters.Add(new Filters.RequestTimingFilter());
    }
}

The conflict occurs immediately after the first Action that initializes the SimpleMembership. For some reason once that happens no other filters can be applied. The RequestTimingFilter worked because it doesn't apply a filter to the request context, it just is a timing event that is stored outside of each action.

siva.k
  • 1,344
  • 14
  • 24
2

I'm pretty sure that your CDN filter is technically changing your routing on the site. The SimpleMembership needs the route(s) of the Account controller to be correct by the time that it initializes.

This article is what leads me to that conclusion.

by default the WebSecurity.InitializeDatabaseConnection() call is within the InitializeSimpleMembershipAttribute class, and by default the AccountController is adored with this attribute. The trouble with that is that a route associated with the AccountController must first be invoked for the SimpleMembershipProvider to be initialized...

Source.

technicallyjosh
  • 3,511
  • 15
  • 17
  • This is good to know, but I actually have this initializer moved out of there because I need the User object before it gets to that point in the page build process. The initializer is inside of Aplication_Start auto event in the Global.asax.cs file. Though this makes me think it wouldn't work if the CDN filter ran first, but what is working is running the CDN filter and then the Membership filter. Or do they run in reverse order from how they are registered? – siva.k Jun 27 '13 at 22:35
  • It all depends on where the `FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);` call is happening in relation to the SimpleMembership call. Actually it makes more and more sense as to why it would work when it's before the SimpleMembership filter init the more I think about it. Happy coding! – technicallyjosh Jun 27 '13 at 22:46