"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