I am having a problem where a cookie I am setting is being lost directly after RedirectToAction() is called. Is something going on behind the scenes that invalidates the current request and creates a new one that causes the cookie to be lost before it has been persisted to disk?
I understand that if you want data to be available after the redirect you need to use TempData, but is that the same for cookies? If so, doesn't that seem ugly to have to store the cookie value in TempData and then write the cookie later?
Update:
I just realized that the cookie is lost at the end of the request, it doesn't matter if I call RedirectToAction(). So now the question is why wont the cookie persist accros two requests? (I update the code below to show what I am doing now)
public ActionResult DoSomething()
{
Response.Cookies["SomeCookie"].Value = "Jarified";
Response.Cookies["SomeCookie"].Expires = DateTime.UtcNow.AddDays(3);
return View("SomeView");
}
Update
I created a new MVC project using the default template. I modified the HomeController/Index action to have the code below. The first time I hit the view "Cookie Not Found" is printed as expected. However, every subsequent time the same message is printed. If I remove the line that sets the expiration date then everything works just fine. I guess the real question here is why does making the cookie persistent cause the browser to throw it away? Is there a trick to making cookies persistent in MVC?
public ActionResult Index()
{
HttpCookie cookie = Request.Cookies["temp"];
if (cookie == null)
{
ViewData["Message"] = "Cookie Not Found";
Response.Cookies["temp"].Value = "Welcome to ASP.NET MVC!";
Response.Cookies["temp"].Expires = DateTime.UtcNow;
}
else
{
ViewData["Message"] = cookie.Value;
}
return View();
}
The reason that
Response.Cookies["temp"].Expires = DateTime.UtcNow;
is not working is that it sets a cookie with an expires in the past (at least for me).
It works when I change it to
Response.Cookies["temp"].Expires = DateTime.UtcNow.AddDays(3);
Setting a cookie expires property to the past will delete the cookie.
This code works for me:
public ActionResult Index() {
HttpCookie cookie = Request.Cookies["temp"];
if (cookie == null) {
ViewData["Message"] = "Cookie Not Found";
Response.Cookies["temp"].Value = "This is a cookie: Welcome to ASP.NET MVC!";
Response.Cookies["temp"].Expires = DateTime.UtcNow.AddDays(3);
} else {
return RedirectToAction("Something");
}
return View();
}
public ActionResult Something() {
HttpCookie cookie = Request.Cookies["temp"];
ViewData["Message"] = cookie.Value;
return View();
}
Take a look at this article. It should address your issue!
http://stephenwalther.com/blog/archive/2008/07/08/asp-net-mvc-tip-15-pass-browser-cookies-and-server-variables-as-action-parameters.aspx
var newCookie = new HttpCookie("myCookie", cookieValue);
newCookie.Expires = DateTime.Now.AddDays(10);
Response.AppendCookie(newCookie);
return RedirectToAction("Index");
Here's a one liner for setting a cookie and expiration date
Response.Cookies.Add(new HttpCookie("myCookie", "cookie value")
{ Expires = DateTime.Now.AddDays(1)});
Related
I have an ASP.NET MVC application with ActionFilters for Authentication and no Forms Authentication. "SegurancaAction" is the attribute responsible for validating authentication and exists in every controller endpoint except in the login ones (as expected).
I'm facing a problem in which sometimes I try to access one of my controllers and the GET request goes to my login endpoint. In the method Application_BeginRequest at Global.asax, I can see the very first attempt is at 'security/login' (the route to my login endpoint) instead of the one I want. I can also see this endpoint being called in debugging apps such as Fiddler, or ASP.NET Trace or Glimpse MVC5.
Besides calling the wrong action, once I login again this issue keeps happening for the same endpoint I was trying to access, redirecting my site to the login page over and over.
SegurancaAction:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Autenticacoes autenticacao = _authApp.IsAutenticado(filterContext.HttpContext.Session.SessionID);
if (autenticacao == null)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
filterContext.Result = new HttpStatusCodeResult(System.Net.HttpStatusCode.Unauthorized);
else
{
filterContext.HttpContext.Response.RedirectPermanent("/security/login");
return;
}
}
else
{
// other stuff
}
}
SecurityController:
[HttpPost]
[ConfigAction]
public ActionResult Login(vm_Login login)
{
if (ModelState.IsValid)
{
if (!String.IsNullOrEmpty(login.Login) && !String.IsNullOrEmpty(login.Senha))
{
Entidades entidade = _entidadeApp.GetByUsuarioSenha(login.Login, login.Senha);
if (entidade == null)
{
ViewBag.FalhaAutenticacao = "As credenciais informadas não conferem!";
return View("Login");
}
else
{
string encryptionKey = System.Configuration.ConfigurationManager.AppSettings["EncryptionKey"];
var a = _autenticacaoApp.Autenticar(entidade.Id, encryptionKey, login.Senha, HttpContext.Session.SessionID);
}
Response.RedirectPermanent("~/principal/index");
}
}
else
{
ViewBag.FalhaAutenticacao = "É necessário informar o usuario e a senha!";
}
return View();
}
All _autenticacaoApp.Autenticar(...) method does is to create an authentication entry on the database, it's a completely custom code.
Does anyone know why this issue happens? Sometimes I can reproduce it by deleting the cookies that contain ASP.NET_Session ID and RequestVerificationToken. So far I know those cookies are automatically generated and I notice that sometimes when I login again they are not re-generated.
I figured out the issue. It was this "RedirectPermanent" method being used here:
filterContext.HttpContext.Response.RedirectPermanent("/security/login");
It tells the browser that the resource I'm trying to access is no longer available and is now located at this new Url. The browser records this information and always redirects to the new resource.
I just changed it to use "Redirect" instead.
I have an ASP.NET MVC website which I am developing, and I recently deployed it on a test server so the client could give some initial feedback.
I have noticed that on the live server there is a considerable delay in loading the login form, of around 20-30 seconds. Once you are logged in, the system functions just fine, and is responsive.
If you log out, and return to the login page, it is once again slow.
It doesn't appear to be an issue with the apppool spooling up, as it happens every time on the page, not just once, and all items appear to be loading.
Any suggestions on how to debug this?
Below is the BaseController from which all controllers enherit, plus the account login controller.
protected override void ExecuteCore()
{
if (User.Identity.IsAuthenticated)
{
try
{
AccountDataContext = new AccountDAL.DataContext(ConfigurationManager.AppSettings["Server"]);
// set the current user.
CurrentUser = AccountDataContext.Users.FirstOrDefault(x => x.Email == User.Identity.Name);
AccountDataContext.CurrentAccount = CurrentUser.Account;
ViewBag.CurrentUser = CurrentUser;
ViewBag.Account = CurrentUser.Account;
SystemDataContext = new SystemDAL.DataContext(ConfigurationManager.AppSettings["Server"], CurrentUser.Account.Database);
// setup the account based on the users settings
ViewBag.Theme = "Default"; // hard coded for now
}
catch (Exception)
{
// if the previous threw an exception, then the logged in user has been deleted
// log them out
FormsAuthentication.SignOut();
Session.Abandon();
// clear the authentication cookie
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, "");
cookie.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(cookie);
FormsAuthentication.RedirectToLoginPage();
}
}
base.ExecuteCore();
}
and the Accounts controller:
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if(AccountDataContext == null)
AccountDataContext = new AccountDAL.DataContext(ConfigurationManager.AppSettings["Server"]);
var user = AccountDataContext.Users.FirstOrDefault(x => x.Email == model.UserName && x.Password == model.Password);
if (user != null)
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Couple of things will improve performance:
First and foremost : Deploy your site in Release mode if you care anything at all about performance. Here a great article from Dave Ward about it.
<compilation targetFramework="your framework version" debug="false">
If you are not using webforms view engine (Which i assume you are not) jus disable it and use Razor only and to take it a little further, allow just chtml files
ViewEngines.Engines.Clear();
IViewEngine RazorEngine = new RazorViewEngine() { FileExtensions = new string[] { "cshtml" } };
ViewEngines.Engines.Add(RazorEngine);
Configure Idle Time-out Settings for an Application Pool (IIS 7) Here's the link
EDIT1:
Based on your last comment mentioning that the app is running fine in your local IIS. I would recommend that you start focusing on analyzing the requests on your remote IIS , here's a link to a tool that you can use.
In order to trace successful requests as well (And you should do that in your case) set the status to 200 here's a tutorial on that as well.
As the slowdown only seems to affect the login pages, I took a closer look at the Account controller which they both use (no other account functions implemented at this time).
I found the following code at the top of the controller:
public AccountController()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
Removing this declaration cured the issue. I'm not sure what it is or where it came from, I think it's from the original MS default implementation authentication. I don't have an ApplicationDbContext, so I think it was waiting on this request to time out before proceeding.
Comment out your data access code and run it. Does that speed things up? Or add Debug.WriteLine(System.DateTime.Now) and see where the long gap is. There might be a delay connecting to the database
In application pool setting There is setting called Load User profile. Set it to true it will help you to make your site faster.
I am setting an error Message in the viewbag to show to the user after each postback how ever I do not want to show this error message on page reload or refresh. I have tried to set it to null but it still is shown because the json parameter still contains the values of the first submit.
[HttpPost]
[CustomAuthorize(Definitions.RoleAnalystManager, Definitions.RoleProjectManager)]
public ActionResult ExportReportAllMediaDetailsCsv(string json)
{
ViewBag.Error = null;
var serializer = new JavaScriptSerializer();
var reportParams = serializer.Deserialize<ReportAllMediaDetailsParams>(json);
var rows = uow.Items.Report_AllMediaDetails(reportParams.profileID, reportParams.organisationID, reportParams.startDate, reportParams.endDate).ToList();
if (rows.Count <= 1)
{
ViewBag.Error = "There were no results for the provided parameters, please adjust the parameters and try again.";
return View("AllMediaDetails");
}
This is because refreshing the page actually resubmits the previous request, so your POST request is resubmitted by the browser. Can't test this at the minute; however, perhaps try setting a bool - TempData["FirstLoad"] - to true on the GET request and then do something like this in the POST method:
if (TempData["FirstLoad"] != null && TempData["FirstLoad"] == true)
{
if (rows.Count <= 1)
{
ViewBag.Error = "...";
}
}
else
{
ViewBag.Error = null;
return View();
}
TempData["FirstLoad"] = false;
return View("AllMediaDetails");
I'm basing this on the fact that TempData is stored in the session, so its values should be retained on a refresh (rather than resubmitted as they were originally as if the value was passed as, for example, a hidden field).
I have 2 websites, actually they are the same. The purpose is in that one of them is for internet users and the second one is for local use.
Now they are hosted on the same IIS server on my localhost machine.
When I open this two websites and trying to get action result which is marked with [ValidateAntiForgeryToken] I have an issue that in my cookies i have cookies for my localhost site and there is a cookie with name "RequestVerificationToken_Lw" which is anti-forgery protection key.
And the problem is in that both sites are using the same cookie to store this key. And so if did smth on one web site I get anti-forgery error when I'm trying to do smth on the other.
How can I change cookie domain or any other solutions to split cookies?
Thank You!
Well, lets see what the ValidateAntiforgeryTokenAttribute does (Reflector/ILSpy is your friend) :
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
string antiForgeryTokenName2 = AntiForgeryData.GetAntiForgeryTokenName(filterContext.HttpContext.Request.ApplicationPath);
HttpCookie httpCookie = filterContext.HttpContext.Request.Cookies[antiForgeryTokenName2];
if (httpCookie == null || string.IsNullOrEmpty(httpCookie.Value))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
AntiForgeryData antiForgeryData = this.Serializer.Deserialize(httpCookie.Value);
string text = filterContext.HttpContext.Request.Form[antiForgeryTokenName];
if (string.IsNullOrEmpty(text))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
AntiForgeryData antiForgeryData2 = this.Serializer.Deserialize(text);
if (!string.Equals(antiForgeryData.Value, antiForgeryData2.Value, StringComparison.Ordinal))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
string username = AntiForgeryData.GetUsername(filterContext.HttpContext.User);
if (!string.Equals(antiForgeryData2.Username, username, StringComparison.OrdinalIgnoreCase))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
if (!this.ValidateFormToken(antiForgeryData2))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
}
Okay, it is obvious, that the cookie name for the token is made from application path:
string antiForgeryTokenName2 = AntiForgeryData.GetAntiForgeryTokenName(filterContext.HttpContext.Request.ApplicationPath);
HttpCookie httpCookie = filterContext.HttpContext.Request.Cookies[antiForgeryTokenName2];
So, you create your own filter, just copy-paste this code, and change that to respect also port (or whatever by what you distinguish your applications):
string antiForgeryTokenName2 = AntiForgeryData.GetAntiForgeryTokenName(filterContext.HttpContext.Request.ApplicationPath + filterContext.HttpContext.Request.Url.Port);
HttpCookie httpCookie = filterContext.HttpContext.Request.Cookies[antiForgeryTokenName2];
This way the cookie name ("RequestVerificationToken_Lw") will vary by port too.
And of course we cant forget to change this cookie name while creating the token too. Unfortunately, you will need to copy-paste "re-implement" 2 things here - first the AntiForgeryToken extension method to call your own AntiForgeryWorker, and then the AntiForgeryWorker itself - just override the method GetAntiForgeryTokenAndSetCookie, it is the same stuff as before :
string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(httpContext.Request.ApplicationPath);
Well, it seems a mess, and its definitely not a DRY solution, but if you really really want this, you can have it done in few minutes. Just use reflector and copy-paste :)
I am using the following code to set a cookie in my asp.net mvc(C#) application:
public static void SetValue(string key, string value, DateTime expires)
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_request = httpContext.Request;
_response = httpContext.Response;
HttpCookie cookie = new HttpCookie(key, value) { Expires = expires };
_response.Cookies.Set(cookie);
}
I need to delete the cookies when the user clicks logout. The set cookie is not removing/deleting with Clear/Remove. The code is as below:
public static void Clear()
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_request = httpContext.Request;
_response = httpContext.Response;
_request.Cookies.Clear();
_response.Cookies.Clear();
}
public static void Remove(string key)
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_request = httpContext.Request;
_response = httpContext.Response;
if (_request.Cookies[key] != null)
{
_request.Cookies.Remove(key);
}
if (_response.Cookies[key] != null)
{
_response.Cookies.Remove(key);
}
}
I have tried both the above functions, but still the cookie exists when i try to check exist.
public static bool Exists(string key)
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_request = httpContext.Request;
_response = httpContext.Response;
return _request.Cookies[key] != null;
}
What may be problem here? or whats the thing i need to do to remove/delete the cookie?
Clearing the cookies of the response doesn't instruct the browser to clear the cookie, it merely does not send the cookie back to the browser. To instruct the browser to clear the cookie you need to tell it the cookie has expired, e.g.
public static void Clear(string key)
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_response = httpContext.Response;
HttpCookie cookie = new HttpCookie(key)
{
Expires = DateTime.Now.AddDays(-1) // or any other time in the past
};
_response.Cookies.Set(cookie);
}
The Cookies collection in the Request and Response objects aren't proxies for the cookies in the browser, they're a set of what cookies the browser sends you and you send back. If you remove a cookie from the request it's entirely server side, and if you have no cookies in the response you're just not going to send any thing back to the client, which won't change the set of cookies in the browser at all.
To delete a cookie, make sure that it is in the response cookie collection, but has an expiration time in the past.
Just to add something else I also pass the value back as null e.g.
public static void RemoveCookie(string cookieName)
{
if (HttpContext.Current.Response.Cookies[cookieName] != null)
{
HttpContext.Current.Response.Cookies[cookieName].Value = null;
HttpContext.Current.Response.Cookies[cookieName].Expires = DateTime.Now.AddMonths(-1);
}
}
The best way to implement this is to use a tool like Reflector and see how the System.Web.Security.FormsAuthentication.SignOut method implements removing the authentication cookie.
In Reflector, open up System.Web and navigate to the FormsAuthentication object and find the SignOut method. Right click on it and select "Disassemble" (Choose your language from the menu).
VB.NET
Public Shared Sub SignOut()
FormsAuthentication.Initialize
Dim current As HttpContext = HttpContext.Current
Dim flag As Boolean = current.CookielessHelper.DoesCookieValueExistInOriginal("F"c)
current.CookielessHelper.SetCookieValue("F"c, Nothing)
If (Not CookielessHelperClass.UseCookieless(current, False, FormsAuthentication.CookieMode) OrElse current.Request.Browser.Cookies) Then
Dim str As String = String.Empty
If (current.Request.Browser.Item("supportsEmptyStringInCookieValue") = "false") Then
str = "NoCookie"
End If
Dim cookie As New HttpCookie(FormsAuthentication.FormsCookieName, str)
cookie.HttpOnly = True
cookie.Path = FormsAuthentication._FormsCookiePath
cookie.Expires = New DateTime(&H7CF, 10, 12)
cookie.Secure = FormsAuthentication._RequireSSL
If (Not FormsAuthentication._CookieDomain Is Nothing) Then
cookie.Domain = FormsAuthentication._CookieDomain
End If
current.Response.Cookies.RemoveCookie(FormsAuthentication.FormsCookieName)
current.Response.Cookies.Add(cookie)
End If
If flag Then
current.Response.Redirect(FormsAuthentication.GetLoginPage(Nothing), False)
End If
End Sub
With the above as an example, I was able to create a common method called RemoveCookie() in a shared assembly, code is below:
VB.NET
''' <summary>
''' Method to remove a cookie
''' </summary>
''' <param name="key">Key</param>
''' <remarks></remarks>
Public Shared Sub RemoveCookie(ByVal key As String)
' Encode key for retrieval and remove cookie
With HttpContext.Current
Dim cookie As New HttpCookie(.Server.UrlEncode(key))
If Not IsNothing(cookie) Then
With cookie
.HttpOnly = True
.Expires = New DateTime(&H7CF, 10, 12)
End With
' Remove from server (has no effect on client)
.Response.Cookies.Remove(.Server.UrlEncode(key))
' Add expired cookie to client, effectively removing it
.Response.Cookies.Add(cookie)
End If
End With
End Sub
Having tested this using FireBug and the Cookie add-in for FireBug (in FireFox), I can attest that the cookie immediately gets removed.
Any questions, feel free to message me.
After playing around with this for some time and trying all of the other answers here I discovered that none of the answers here are totally correct.
The part that is correct is that you have to send an expired cookie to effect the deletion. The part that nobody else picked up on (but is demonstrated in the Microsoft code posted by Ed DeGagne) is that the cookie options for the deletion must match exactly the cookie options that were used to set the cookie in the first place.
For example if you originally created the cookie with the HttpOnly option then you must also set this option when deleting the cookie. I expect the exact behavior will vary across browsers and probably over time, so the only safe option that will work long-term is to make sure that all of the cookie options in the deletion response match exactly the cookie options used to create the cookie originally.
Response.Cookies["key"].Expires= DateTime.Now;