58

I'm having some strange behavior when I try to modify my headers with a EndRequest event handler in a nested HttpModule on MVC 5.2.2 and .NET 4.6.2. If I don't modify EndRequest in my top level HttpModule, it appears that the event handler in the nested HttpModule never fires, even though I know Init was called on the nested HttpModule.

My question is, what is occurring in my code below to prevent the "TestNested" header from appearing in response headers, unless I include the commented out code that adds an EndRequest event handler that does nothing?


Dynamically register my top level HttpModule

[assembly: PreApplicationStartMethod(typeof(PreApplicationStartClass), "Start")]
namespace MyNamespace
{
    public class PreApplicationStartClass
    {
        public static void Start()
        {
            DynamicModuleUtility.RegisterModule(typeof(TopHttpModule));
        }
    }
}

Call Init on all my other HttpModules from a single top level module

namespace MyNamespace
{
    public class TopHttpModule: IHttpModule
    {
        private readonly Lazy<IEnumerable<IHttpModule>> _modules = 
            new Lazy<IEnumerable<IHttpModule>>(RetrieveModules);

        private static IEnumerable<IHttpModule> RetrieveModules()
        {
            return DependencyResolver.Current.GetServices<IHttpModule>();
        }

        public void Init(HttpApplication context)
        {
            var modules = _modules.Value;
            foreach (var module in modules
                .Where(module => module.GetType() != typeof(TopHttpModule)))
            {
                module.Init(context);
            }

            context.BeginRequest += (sender, e) =>
            {
                var app = sender as HttpApplication;
                if (app != null)
                {
                    //This shows that NestedHttpModule was found
                    app.Context.Response.Headers.Add(
                        "TestModules",
                        string.Join(",", modules.Select(_ => _.GetType().ToString())));
                }
            };

            //Add this and the NestedHttpModule EndRequest handler works
            //context.EndRequest += (sender, e) =>
            //{
            //    //Do Nothing
            //};
        }

        public void Dispose()
        {
            var modules = _modules.Value;
            foreach (var disposable in modules
                .Where(disposable => disposable.GetType() != typeof(TopHttpModule)))
            {
                disposable.Dispose();
            }
        }
    }
}

Modify some header information in an EndRequest event handler

namespace MyNamespace
{
    public class NestedHttpModule: IHttpModule
    {
        public void Init(HttpApplication context)
        {
            //This gets called whether or not the TopHttpModule modifies context.EndRequest 
            MvcHandler.DisableMvcResponseHeader = true;

            context.EndRequest += Application_EndRequest;
        }

        public void Application_EndRequest(object sender, EventArgs e)
        {
            var app = sender as HttpApplication;
            if (app != null && app.Context != null)
            {
                //This doesn't appear to be called unless TopHttpModule modifies context.EndRequest
                app.Context.Response.Headers.Add("TestNested", "Found");
            }
        }

        public void Dispose()
        {
            //Do Nothing
        }
    }
}
Imantas
  • 1,621
  • 13
  • 19
Thomas Langston
  • 3,743
  • 1
  • 25
  • 41
  • 13
    I guess the obvious question is - why are you using an HttpModule to change headers instead of using MVC APIs such as [filters](https://msdn.microsoft.com/en-us/library/gg416513(VS.98).aspx) to do this? Why use complex legacy ASP.NET technology for something that is already [readily modifiable in MVC](http://stackoverflow.com/a/15165248/181087)? – NightOwl888 Feb 18 '17 at 10:12
  • 1
    Which version of Asp.Net MVC and .NET framework are you using? The NestedHttpModule.EndRequest fires for me. – Praveen Reddy Feb 19 '17 at 04:17
  • @CodingDawg MVC 5.2.2 and .NET 4.6.2 – Thomas Langston Feb 21 '17 at 15:09
  • @NightOwl888 I want this change to occur on every request, not just for MVC actions. HttpModule or Global.asac appears to be the correct choice according to the question here:http://stackoverflow.com/questions/11507496/http-module-vs-action-filter-in-asp-net-mvc – Thomas Langston Feb 21 '17 at 19:35
  • @ThomasLangston - You are right, if you want the header to be present in image requests, css files, IIS errors, ASP.NET pages, etc. it is usually better to go with one of these options (rather than using `runAllManagedModulesForAllRequests`). I was just checking to see whether it is really worth the effort to go down this rabbit hole if your requirements don't warrant it. – NightOwl888 Feb 21 '17 at 19:44
  • @NightOwl888 Yeah, I've already got a functioning work around here, and I actually abandoned HttpModule for Global.asax for my project. This question is just to satisfy my curiousity about how HttpModule actually works in this scenario. It is very odd behavior to me. – Thomas Langston Feb 21 '17 at 19:51
  • @ThomasLangston Do you have a sample you can put online somewhere? I can't find where this is reproducible. – Ashley Lee Feb 28 '17 at 14:18
  • 1
    @ThomasLangston, is this still active? Can you please answer your own question with what you did? – PSGuy Apr 14 '17 at 20:43
  • @PSGuy not an active area of concern for me practically. I went with my workaround by making my header changes directly in the Global.asax instead. – Thomas Langston Apr 17 '17 at 12:51
  • 5
    @ThomasLangston: Can you answer your question with the solution you used? – garfbradaz Jul 20 '17 at 15:16
  • 2
    You might want to look at `Context.ApplicationInstance.CompleteRequest()`. I ran into a situation a couple years ago where the app was taking the request, doing something else, and redirecting without letting the request finish. I believe this was what I used to handle that. – Bardicer Mar 01 '18 at 16:31
  • 1
    I've put together a Solution for this and it works completely fine for me, as it should. I'm not sure what problem you were having, but it's not related to the code you've shown here. – cbp Apr 11 '18 at 00:50

1 Answers1

1

I also wanted to modify my headers, but i needed to hide as much as possible. It is the same for Add or Remove or both, it is just headers.

1) You can set MvcHandler.DisableMvcResponseHeader = true; in the global.asax

        protected void Application_Start()
        {
            MvcHandler.DisableMvcResponseHeader = true;
        }

and

        protected void Application_PreSendRequestHeaders()
        {
            Response.Headers.Remove("Server");
            Response.Headers.Remove("X-AspNet-Version");
        }

2) You should not really use diff module for almost the same job, instead create a HeadersModule that only handles header modification, and use the PreSendRequestHeaders to add or remove any headers that you want. You can always inject some service with list of headers to add or remove.

    public class HeadersModule : IHttpModule
    {
        public void Init(HttpApplication context)
        {
            context.PreSendRequestHeaders += OnPreSendRequestHeaders;
        }

        public void Dispose() {

        }

        void OnPreSendRequestHeaders(object sender, EventArgs e)
        {

            var r = sender as HttpApplication;
            r.Response.Headers.Remove("Server");
            r.Response.Headers.Remove("X-AspNetMvc-Version");
            r.Response.Headers.Remove("X-AspNet-Version");
            r.Response.Headers.Remove("X-Powered-By");
        }
    }

3) To be extra sure, that some headers show, or "not" show up you can add this to your config file

  <system.webServer>
    <modules>
      <add name="HeadersModule " type="MyNamespace.Modules.HeadersModule " />
    </modules>
    <httpProtocol>
      <customHeaders>
        <remove name="X-Powered-By" />
        <remove name="Server" />
        <remove name="X-AspNet-Version" />
        <remove name="X-AspNetMvc-Version" />
      </customHeaders>
      <redirectHeaders>
        <clear />
      </redirectHeaders>
    </httpProtocol>
  </system.webServer>

4) Test all pages, aka 404, error pages, weird path names, cause they can leak certain headers or show headers that you did not expect.

Thomas Langston
  • 3,743
  • 1
  • 25
  • 41
JohnnBlade
  • 4,261
  • 1
  • 21
  • 22