Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

"Server cannot modify cookies" error in ASP.NET MVC

This post falls into the "Google-able errors" category and won’t be of much use to anyone who isn’t get the specific error.

Here’s the error I was getting: Server cannot modify cookies after http headers have been sent

This is in my ASP.NET MVC application and the offending code was the last line of the following method:

public void SignIn( User user )
{
    FormsAuthentication.SignOut( );

    var issued = DateTime.Now;
    var expires = issued.AddDays( 1 );
    var roles = "Administrator";

    var ticket = new FormsAuthenticationTicket( 1, user.UserName, issued, expires, false, roles );
    var encryptedTicket = FormsAuthentication.Encrypt( ticket );
    var authCookie = new HttpCookie( FormsAuthentication.FormsCookieName, encryptedTicket )
        {
            Expires = ticket.Expiration
        };

    _httpContext.Response.Cookies.Add( authCookie );
}

This is in a class called Authentication Service which is called from my LoginController. The authentication service is wired up to the LoginController using Dependency Injection via Windsor by way of MvcContrib. Consider that foreshadowing.

The key to the error is the _httpContext variable. It is set in the constructor of the AuthenticationService as follows:

public AuthenticationService( ) : this( new HttpContextWrapper2( HttpContext.Current ) )
{
}

public AuthenticationService( HttpContextBase httpContext )
{
    _httpContext = httpContext;
}

Some of you will recognize this as poor man’s dependency injection, used primarily because I didn’t feel like wrapping the HttpContextWrapper2 class (which is itself a wrapper) in an interface. Some of you will also note that even though I’m injecting the HttpContext into this class, it is still largely untestable because of the FormsAuthentication stuff. Frankly, I’m good with that which is why I didn’t bother with the interface.

During my debugging, one of the things I did was replace _httpContext with HttpContext.Current and lo! That works fine. So clearly, my poor man’s dependency injection had issues. My mentor application, CodeCampServer, went the extra step and created an IHttpContextProvider interface around HttpContextWrapper2 and used Windsor to wire it in:

public AuthenticationService(IClock clock, IHttpContextProvider httpContextProvider)
{
    _clock = clock;
    _httpContextProvider = httpContextProvider;	        
}

IHttpContextProvider has a pretty simply interface and implementation:

public class HttpContextProvider : IHttpContextProvider
{
    public HttpContextBase GetCurrentHttpContext()
    {
        return new HttpContextWrapper2(HttpContext.Current);
    }
}

So while I originally poo-poohed the extra layer of abstraction, CodeCampServer had a key feature that my application did not in that it’s login facility worked.

So I introduced an IHttpContextProvider interface and replaced my existing _httpContext with it and lo! I am able to log in successfully. Here is the updated AuthenticationService:

public class AuthenticationService : IAuthenticationService
{
    private readonly IHttpContextProvider _httpContext;

    public AuthenticationService( IHttpContextProvider httpContext )
    {
        _httpContext = httpContext;
    }

    public void SignIn( User user )
    {
        FormsAuthentication.SignOut( );

        var issued = DateTime.Now;
        var expires = issued.AddDays( 1 );
        var roles = "Administrator";

        var ticket = new FormsAuthenticationTicket( 1, user.UserName, issued, expires, false, roles );
        var encryptedTicket = FormsAuthentication.Encrypt( ticket );
        var authCookie = new HttpCookie( FormsAuthentication.FormsCookieName, encryptedTicket )
             {
                 Expires = ticket.Expiration
             };

        _httpContext.GetCurrentHttpContext( ).Response.Cookies.Add( authCookie );
    }
}

One of the nice things that came out of this was that, thanks to my auto-registration code, I didn’t need to do anything special to wire in the new IHttpContextProvider class. Just created it and it just worked. Who knew?

As for the cause of the error, I can only speculate. My guess is that it was using the wrong HttpContext. During my debugging, the constructor for AuthenticationService would get called during the first call to the Login page. That is, when the Login page was first rendered. So the class was storing a reference to *that* page’s HTTP context. When the user logged in, it would use that context and try to add the authentication cookie to it. Since that context had already been used and the headers written out, the error occurred. So it wasn’t actually getting the *current* HTTP context. It was using the previous request’s context.

Further proof of this theory: when I would see that error, my workaround was to go back to Visual Studio and do a full re-compile of the application. This clears out the cached version of the class, and more importantly, it’s reference to the previous request’s HTTP context. When I’d refresh the page, it would have to create a new instance of AuthenticationService and use the current request’s HTTP context, which hasn’t yet completed.

In the new version, every call to GetCurrentHttpContext will create a new HttpContextWrapper2 object based on the current request’s HTTP context. Ergo, we will always have the current context.

Whatever, as long as it works.

Kyle the Theoretical

This entry was posted in ASP.NET MVC. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Paul

    Just SAVED my life. Thanks!

  • Kyle Baley

    @Jason: I had not seen that. Looks interesting though perhaps a little bit of overkill for my requirements, especially since I’ve already got the authentication working. Will take a look when my requirements get more complex though.

  • http://huntjason.spaces.live.com JH

    Kyle,
    Not certain if you’ve already come across it already, but the MVC Membership Starter Kit has give us a good headstart on authentication stuff on our project.

    http://www.codeplex.com/MvcMembership

  • Kyle Baley

    As I mentioned, managing the cookie yourself is a possibility. But that’s what FormsAuthentication is there for, to do it for you. To check if someone is authenticated, you need only call: HttpContext.Current.User.Identity.IsAuthenticated.

    FormsAuthentication is an entirely separate concept from WebForms and MVC. It doesn’t use the ASP.NET Lifecycle, which is the typical target for people moving away from WebForms. You could just as easily manage the cookies yourself in WebForms.

  • Joe

    I just don’t know why there’s a need for FormsAuthentication. You can set the cookie yourself and validate it’s existence before each action call (via some inherited controller).

  • Kyle Baley

    @Andy: My original implementation didn’t use Windsor at all. It used poor man’s dependency injection. But yes, it was stateful. Really, I could have fixed this by not creating an HttpContextWrapper2 object in the constructor but in the SignIn method itself. When I switched to using Windsor, it worked because my auto-registration code adds objects as transient.

  • Andy Hitchman

    Your original implementation of the AuthenticationService was stateful.
    I’d guess that you need to mark the component as transient so Windsor constructs a new instance per resolution. By default (IIRC), it caches component instances which effectively act as singletons.

  • Kyle Baley

    Oh, and the concept of forms *does* exist in MVC. It’s WebForms, the abstraction over HTTP forms, that MVC tosses out.

  • Kyle Baley

    MVC obviates the need for a lot of things but authentication is not one of them. I still have the need to store a user’s identity across a session. And FormsAuthentication is still useful in this regard.

    Note that FormsAuthentication doesn’t make use of ViewState, the chief target of MVC-proponents. It’s an abstraction over cookies. Without it, I’d have to manage the cookies used for authentication myself.

    I sound a lot more confident than I really am in this regard. In reality, I stole the code from CodeCampServer

  • Joe

    Ok…but what’s that got to do with FormsAuthentication? I’m just curious as in why you’re using it if the whole concept of forms doesn’t even exist in MVC.

  • Kyle Baley

    @Joe: Although the app is meant to be used internally, the company is small enough that they don’t have the infrastructure in place to host it internally. Hence, it will be hosted externally.

  • Joe

    Why are you using FormsAuthentication?