I have been experimenting with code that will clear all of the cookies in an HttpContext.Response.
Initially, I used this:
DateTime cookieExpires = DateTime.Now.AddDays(-1);
for (int i = 0; i < HttpContext.Request.Cookies.Count; i++)
{
HttpContext.Response.Cookies.Add(
new HttpCookie(HttpContext.Request.Cookies[i].Name, null) { Expires = cookieExpires });
}
However, this will error with an OutOfMemoryException because the for loop never exits - each time you add a cookie to the Response, it also gets added to the `Request.
The following approach works:
DateTime cookieExpires = DateTime.Now.AddDays(-1);
List<string> cookieNames = new List<string>();
for (int i = 0; i < HttpContext.Request.Cookies.Count; i++)
{
cookieNames.Add(HttpContext.Request.Cookies[i].Name);
}
foreach (string cookieName in cookieNames)
{
HttpContext.Response.Cookies.Add(
new HttpCookie(cookieName, null) { Expires = cookieExpires });
}
So, what exactly is the relationship between HttpContext.Request.Cookies and HttpContext.Response.Cookies?
Request.Cookies contains the complete set of cookies, both those that browser send to the server and those that you just created on the server.
Response.Cookies contains the cookies that the server will send back.
This collection starts out empty and should be changed to modify the browser's cookies.
The documentation states:
ASP.NET includes two intrinsic cookie
collections. The collection accessed
through the Cookies collection of
HttpRequest contains cookies
transmitted by the client to the
server in the Cookie header. The
collection accessed through the
Cookies collection of HttpResponse
contains new cookies created on the
server and transmitted to the client
in the Set-Cookie header.
After you add a cookie by using the
HttpResponse.Cookies collection, the
cookie is immediately available in the
HttpRequest.Cookies collection, even
if the response has not been sent to
the client.
Your first code sample should work if you make the for loop run backwards.
The new cookies will be added after the end, so the backwards loop would ignore them.
Related
I found this behaviour by accident, as I return the count of items in a session in an error message and found that some sessions had as many as 120 items in them (they should have 1!). On further investigation I found that every request seems to add an item into the session. They are all negative integers, like -710, -140 -528. I can't seem to see a pattern in what number comes up.
I have checked my code for any interactions with the Session object and as far as I can tell it is not me. I store one item in the session which is my own object which has a number of other properties on it. My session state is SQL server, and I am only serialising a certain set of values that need to be kept.
Has anyone seen anything like this or has any advice on where I can troubleshoot further?
Thank you in advance.
-- Edit, as requested - first where I count the items in the session - this is done in the page load event of my master page. I loop through so I could inspect using the debugger.
int itemCount = Session.Count;
for (int i = 0; i < itemCount; i++)
{
object o = Session[i];
}
-- here is where I add my custom object to the session. This is called at session start and in my master page. It runs on a "get, but if not there, create" principle.
HttpSessionState Session = HttpContext.Current.Session;
HttpRequest Request = HttpContext.Current.Request;
if (Session == null)
return null;
SessionData sessionData = (SessionData)Session[StaticNames.SESSION_NAME];
if (sessionData == null)
{
sessionData = new SessionData();
Session.Add(StaticNames.SESSION_NAME, sessionData);
}
I also have this to get the SessionData object from the session:
public SessionData(SerializationInfo info, StreamingContext ctxt)
{
this.IsManualLogin = (bool)info.GetValue("IsManualLogin", typeof(bool));
this.HasAskedUserForLocation = (bool)info.GetValue("HasAskedUserForLocation", typeof(bool));
// ... etc, more items for all users here
int? loginID = null;
try
{
loginID = info.GetInt32("LoginID");
}
catch
{
return;
}
this.LoginID = loginID.Value;
// ... etc, more items for logged in users only
}
There is also an equivalent method for adding this data to the SerializationInfo used for SqlSessionState.
Credit to the modest jadarnel27.
It turns out the Ajax Control Toolkit NoBot control adds an integer into your session on every request. My website has an auto 40 second refresh, similar to facebook, so this probably would have brought the whole thing crashing down at some point and I am lucky to find it now. Should anyone else consider using the NoBot control, be warned about this behaviour!
I create some cookies in logon.aspx.cscodebehind thatc read and contain user info from DB with data reader .
HttpCookie UID = new HttpCookie("ID");
Response.Cookies["UID"].Value = Recordset[0].ToString();
Response.Cookies.Add(UID);
HttpCookie UName = new HttpCookie("Username");
Response.Cookies["Username"].Value = Recordset[3].ToString();
Response.Cookies.Add(UName);
HttpCookie Pass = new HttpCookie("Pass");
Response.Cookies["Pass"].Value = Recordset[4].ToString();
Response.Cookies.Add(Pass);
HttpCookie Admins = new HttpCookie("Admin");
Response.Cookies["Admin"].Value = Recordset[12].ToString();
Response.Cookies.Add(Admins);
HttpCookie Mails = new HttpCookie("Emails");
Response.Cookies["Emails"].Value = Recordset[9].ToString();
Response.Cookies.Add(Mails);
Response.Redirect("../default.aspx");
when i trace the code every thing is good and data hold by cookies.
Now when i read these cookies in master page or other content page, i can't.
in other worlds the cookies not recognize by their names(or keys)
if (Request.Cookies["Username"] !=null)
{
lblWelcomeUser.Text = Server.HtmlEncode(Request.Cookies["Username"].Value);
pnlUsersNavigation.Visible = true;
LoginMenu.Visible = false;
RegisterMenu.Visible = false;
lblWelcomeUser.Text = Server.HtmlEncode(Request.Cookies["Username"].Value);
//lblWelcomeUser.Text = Request.Cookies["Username"].Value.ToString();
if (Request.Cookies["Admin"].Value.ToString()=="True")
{
lblWelcomeUser.Text = "WELCOME ADMIN";
// Show Menu that is only for Admin
}
where is the problem in this code?
It appears that you might be overwriting the cookie with a good value, with a new empty cookie.
// new cookie created - empty
HttpCookie UName = new HttpCookie("Username");
// new cookie created with a value
Response.Cookies["Username"].Value = Recordset[3].ToString();
// overwrite new cookie with value with new empty cookie
Response.Cookies.Add(UName);
Create the cookie, set the value, then add the cookie to the response.
HttpCookie UName = new HttpCookie("Username");
UName.Value = Recordset[3].ToString();
Response.Cookies.Add(UName);
Also note that as Paul Grimshaw pointed out, you can add multiple values to the same cookie.
Download Fiddler to check request/response to ensure your cookies contain the correct values and such... http://fiddler2.com/get-fiddler
Also be careful about Man-in-the-middle attacks. Storing usernames and passwords in plain text is not such a good idea to begin with.
This doesn't look like a very secure way of securing access to your application. Try looking at ASP.NET membership.
Otherwise try setting an expiry date. Also, as this example shows, you may want to store all the above info in one cookie:
HttpCookie myCookie = new HttpCookie("UserSettings");
myCookie["UID"] = Recordset[0].ToString();
myCookie["Username"] = Recordset[3].ToString();
//...etc...
myCookie.Expires = DateTime.Now.AddDays(1);
Response.Cookies.Add(myCookie);
Also, from MSDN:
By default, cookies are shared by all pages that are in the same
domain, but you can limit cookies to specific subfolders in a Web site
by setting their Path property. To allow a cookie to be retrieved by
all pages in all folders of your application, set it from a page that
is in the root folder of your application and do not set the Path
property. If you do not specify an expiration limit for the cookie,
the cookie is not persisted to the client computer and it expires when
the user session expires. Cookies can store values only of type
String. You must convert any non-string values to strings before you
can store them in a cookie. For many data types, calling the ToString
method is sufficient. For more information, see the ToString method
for the data type you wish to persist.
I have some asp.net pages that read and write cookie values. During the life cycle of a page it may update the cookie value and then need to read it again further in the code. What I've found is that it's not getting the latest value of the cookie until a page refresh. Is there a way around this? Here's the code I'm using to set and get the values.
public static string GetValue(SessionKey sessionKey)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies[cookiePrefix];
if (cookie == null)
return string.Empty;
return cookie[sessionKey.SessionKeyName] ?? string.Empty;
}
public static void SetValue(SessionKey sessionKey, string sessionValue)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies[cookiePrefix];
if (cookie == null)
cookie = new HttpCookie(cookiePrefix);
cookie.Values[sessionKey.SessionKeyName] = sessionValue;
cookie.Expires = DateTime.Now.AddHours(1);
HttpContext.Current.Response.Cookies.Set(cookie);
}
What you're missing is that when you update the cookie with SetValue you're writing to the Response.Cookies collection.
When you call GetValue you're reading from the Request.Cookies collection.
You need to store the transient information in a way that you access the current information, not just directly the request cookie.
One potential way to do this would be to writer a wrapper class that with rough psuedo code would be similar to
public CookieContainer(HttpContext context)
{
_bobValue = context.Request.Cookies["bob"];
}
public Value
{
get { return _bobValue; }
set {
_bobValue = value;
_context.Response.Cookies.Add(new Cookie("bob", value) { Expires = ? });
}
}
I ran into needing to do similar code just this week. The cookie handling model is very strange.
Start using Sessions to store your information, even if it's only temporary.
Cookies rely on a header being sent to the browser before the page has rendered. If you've already sent information to the client then proceed to set a cookie, you're going to see this "page refresh delay" you've described.
If it's necessary to have this value, use a session variable between the time you set the cookie and when you refresh the page. But, even then I would just recommend avoiding settings cookies so late in the processing step and try to set it as early as possible.
I have an Silverlight 4 RIA Services application with custom Forms Authentication. The custom authentication service works like a charm.
The problems is I want to serialize the user object in a cookie which is then sent with each subsequent request.
I create the cookie and add it to the response cookie collection but on the next request the only cookies in the cookie collection are ASPXAUT and ASPX_SESSIONId, of the custom cookie not a trace.
This is the cookie management class:
public class CookieManager:ISessionManager
{
public object this[string key]
{
get
{
var context = getCurrentContext();
var cookie = context.Request.Cookies[key];
if (cookie == null) return null;
return deserialize(cookie.Value);
}
set
{
var context = getCurrentContext();
string cookieValue = serialize(value);
HttpCookie cookie = new HttpCookie(key, cookieValue);
cookie.Expires = DateTime.Now.AddDays(10000);
cookie.HttpOnly = false;
context.Response.Cookies.Remove(key);
context.Response.Cookies.Add(cookie);
}
}
public void Abandon()
{
var context = getCurrentContext();
context.Response.Cookies.Clear();
}
public void Clear()
{
Abandon();
}
private HttpContext getCurrentContext()
{
return HttpContext.Current;
}
private string serialize(object value)
{
MemoryStream stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Context = new StreamingContext(StreamingContextStates.Clone);
formatter.Serialize(stream, value);
StreamReader reader = new StreamReader(stream);
stream.Position = 0;
string result = reader.ReadToEnd();
reader.Close();
stream.Close();
return HttpUtility.UrlEncodeUnicode(result);
}
public object deserialize(string value)
{
value = HttpUtility.UrlDecode(value);
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(value);
BinaryFormatter formatter = new BinaryFormatter();
return formatter.Deserialize(stream);
}
}
It reads and saves cookies.
Now my problem is this:
What I need to enable in silverlight or in the ASP.NET (WCF) application in order for extra cookies to be sent with each request along side the authentication cookie.
EDIT:
I've inspected the HTTP request/response stack and those extra cookies are sent from the server with the WCF RIA Services response but not returned by the next service call from the client.
If I understand your edit above correctly, you've already inspected the HTTP requests and found the desired cookie present in the HTTP Set-Cookie header of the response, but missing in the Cookie header of the next request. Is this correct? If not, please clarify.
If so, the problem sounds like one of three things:
the client is not successfully saving the cookie, due to many possible reasons including:
cookie not properly formatted (unlikley)
cookie is too long
there's a client- or server-side policy (e.g. P3P) preventing saving persistent cookies.
The client is saving the cookie OK, but is not sending it back, even without Silverlight. This could be caused by, for example, a security issue where the hostname of the first request is different from the second.
The client is saving the cookie and can send it back over regular HTML pages, but not via HTTP requests sent by Silverlight.
To see if #1 is the problem, look (using your browser's ability to view cookies) at the cookies saved by your browser for that site. Is the expected cookie saved? If it is, then you can eliminate #1 as the problem. If it's not saved, start looking
To see if #2 is the problem, try creating a server-side page with no silverlight on it-- just a simple HTML page. When you visit that page with your browser, is the cookie sent as expected? If yes, then #2 is not your problem.
If #1 and #2 are not the problem, that leaves #3. Silverlight's HTTP handling is complicated, not least because you have to choose between having HTTP client requests handled by the browser or by Silverlight. Read the Silverlight cookies documentation carefully and see if any of the info therein will help you figure out the problem. Consider trying to use the "Client HTTP" setting, or if you're already using this, consider switching back to the "browser HTTP" setting and see if your problem goes away. Note that the Client HTTP setting apparently has a problem with losing new cookies after an HTTP redirect. See this thread for more info. There's a workaround discussed in that thread: using CookieContainer.
BTW, could you edit your question to include all the HTTP headers of the request and the subsequent request? This may help diagnosis.
In order to support a legacy application that's in the field, I need my ASP.NET MVC app to return an empty response that also has a Content-Type. One of IIS, ASP.NET, or ASP.NET MVC is removing my Content-Type when I send back a null response. Is there any way around this?
(While not requiring an empty response with a set Content-Type would obviously be the ideal solution, the clients are already out there, and many of them cannot be upgraded.)
EDIT: Since there was a request for code: I'm proxying the request from the new web application to the one that older clients rely on. To do this, I have a subclass of ActionResult, called LegacyResult, that you can simply return for those methods that need to be handled by the old software. This is the relevant part of its code:
public override void ExecuteResult(ControllerContext context)
{
using (var legacyResponse = GetLegacyResponse(context))
{
var clientResponse = context.HttpContext.Response;
clientResponse.Buffer = false;
clientResponse.ContentType = legacyResponse.ContentType; /* Yes, I checked that legacyResponse.ContentType is never string.IsNullOrEmpty */
if (legacyResponse.ContentLength >= 0) clientResponse.AddHeader("Content-Length", legacyResponse.ContentLength.ToString());
var legacyInput = legacyResponse.GetResponseStream();
using (var clientOutput = clientResponse.OutputStream)
{
var rgb = new byte[32768];
int cb;
while ((cb = legacyInput.Read(rgb, 0, rgb.Length)) > 0)
{
clientOutput.Write(rgb, 0, cb);
}
clientOutput.Flush();
}
}
}
If legacyInput has data, then Content-Type is set appropriately. Otherwise, it's not. I can actually kluge the old backend to send an empty v. non-empty response for exactly the same request, and observe the difference in Fiddler.
EDIT 2: Poking around with Reflector reveals that, if headers have not been written at the time that HttpResponse.Flush is called, then Flush writes out the headers itself. The problem is that it only writes out a tiny subset of the headers. One of the missing ones is Content-Type. So it seems that, if I can force headers out to the stream, I can avoid this problem.
You have to trick the response into writing the headers, by falsely telling it there's content, then suppressing it:
/// [inside the writing block]
var didWrite = false;
while ((cb = legacyInput.Read(rgb, 0, rgb.Length)) > 0)
{
didWrite = true;
clientOutput.Write(rgb, 0, cb);
}
if (!didWrite)
{
// The stream needs a non-zero content length to write the correct headers, but...
clientResponse.AddHeader("Content-Length", "1");
// ...this actually writes a "Content-Length: 0" header with the other headers.
clientResponse.SuppressContent = true;
}