Cookie persistence and best practice - c#

In our application there are two types of user, let's call them Alpha and Beta users. Each of these users sees a different type of toolbar / menu.
We have decided to track this using cookies. The majority of our pages are either Alpha pages or Beta pages and then there are some common pages that Alpha and Beta users share. So in each view of our application where we know the user type (Alpha or Beta) we have the following code:
HttpCookie isAlphaCookie = new HttpCookie("IsAlpha", "false"); // or true
HttpCookie isBetaCookie = new HttpCookie("IsBeta", "true"); // or false
isAlphaCookie.Expires = DateTime.MaxValue;
isBetaCookie.Expires = DateTime.MaxValue;
Response.Cookies.Add(isAlphaCookie);
Response.Cookies.Add(isBetaCookie);
The idea is then, in common pages, we don't set any cookie and rely on the previously set cookie to determine which toolbar to load. So, these two cookies are set to true or false as above in our known pages before we read them in the controller method which loads our toolbar like so:
HeaderViewModel header = new HeaderViewModel
{
FirstName = UserProfile.CurrentUser.FirstName,
LastName = UserProfile.CurrentUser.LastName,
ImageUrl = null,
OrganisationName = UserProfile.CurrentUser.OrganisationName,
OrganisationUrl = UserProfile.CurrentUser.OrganisationUrl,
ShowAlphaToolbar = bool.Parse(Request.Cookies["IsAlpha"].Value),
ShowBetaToolbar = bool.Parse(Request.Cookies["IsBeta"].Value),
ShowPublicToolbar = false
};
return PartialView("Common/_Header", header);
From reading up on how to read / write cookies this seems to be the right approach; writing the cookie to the Response object and reading the cookie from the Request object.
The problem I'm having is that when i get to the controller method that loads the toolbar the values of the IsAlpha and IsBeta cookies are both empty strings and this breaks the application.
I have confirmed that the cookies are set in the Response before they are read in the Request.
I'm wondering whether I'm missing something fundamental here.

I only expect your assumption " in common pages, we don't set any cookie and rely on the previously set cookie to determine which toolbar to load" to work if you are calling these partial actions, which you referring to as "common pages" I guess, through Ajax Calls. If you are using
#Html.RenderAction('nameOfaAtion')
then I don't think what you have in place will work.
Reason is Both your main action and partial actions are executed within the same Http Request Cycle so the cookies you are trying to access from the Request Object in your common pages have not yet came as part of Request.
Edit
As I can see you are hard coding the cookies on each page so Guess you can also do something like below. Not originally the way you are trying to do but I think it will do the same thing you are trying.
In your pages change the partial view call to like this.
#Html.Action("LoadHeader", "Profile", new{IsAlpha=False, IsBeta=true});
Then Change the signature of the LoadHeader action to receive these two extra parameters
public ViewAction LoadHeader(bool IsAlpha, Bool IsBeta)
Then with the viewModel Initialization change two lines as below.
ShowAlphaToolbar = IsAlpha,
ShowBetaToolbar = IsBeta,

Related

Access cookie before View loaded in MVC5?

Let's say I have a cookie with value "OLD", then I update the cookie with following code :
var lang = new HttpCookie("lang");
lang.Value = "NEW";
lang.Expires = DateTime.UtcNow.AddDays(2);
HttpContext.Current.Response.SetCookie(lang); //set updated cookie value
var x = HttpContext.Current.Request.Cookies("lang");
x will still have "OLD" as value because it's not being sent to the client. Is it possible to get the updated value without it being sent to client first?
The Problem
you are setting the cookie in HttpContext.Current.Response and trying to find it in HttpContext.Current.Request even before the next request has come to server.
It is not going to work.
My Advice
For this case, use of cookie is incorrect. You have other options better fit for purpose, like -
ViewData
ViewBag
TempData
Session
ViewModel
The Answer
But if you still want to use cookie this way, search the cookie in HttpContext.Current.Response. I am sure you will get it.
Like this:
var cookieValue = HttpContext.Current.Response.Cookies["OLD"].Value
If you don't want to send value to client side and only want to use for reference you can use TempData for your purpose. Which might help.
I think, you should look for it in Response, not in Request, because request - it's that came from client.

HttpWebRequest: Add Cookie to CookieContainer -> ArgumentException (Parametername: cookie.Domain)

I'm trying to login to a website via my application.
What I did:
First I figured out how the browser does the authorization process with Fiddler.
I examined how the POST request is built and I tried to reconstruct it.
The browser sends 4 cookies (Google Analytics) and I tried to set them:
CookieContainer gaCookies = new CookieContainer();
gaCookies.Add(new Cookie("__utma", "#########.###########.##########.##########.##########.#"));
gaCookies.Add(new Cookie("__utmb", "#########.#.##.##########"));
gaCookies.Add(new Cookie("__utmc", "#########"));
gaCookies.Add(new Cookie("__utmz", "#########.##########.#.#.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)"));
(just replaced the original cookie data with #)
Then I went through the code with the debugger and as soon as the first gaCookies.Add is executed, the application stops with an
System.ArgumentException: The parameter '{0}' cannot be an empty string. Parameter name: cookie.Domain
I would like to know why this happens. The constructor of Cookie doesn't require a domain and I don't know where I can get this value?
Would be very great if someone of you could help me with this.
I'm not a webdeveloper or an expert in web stuff so I don't know much about it.
Is there maybe a great source where I can learn about this if there is no "short and quick answer"?
CookieContainers can hold multiple cookies for different websites, therefor a label (the Domain) has to be provided to bind each cookie to each website. The Domain can be set when instantiating the individual cookies like so:
Cookie chocolateChip = new Cookie("CookieName", "CookieValue") { Domain = "DomainName" };
An easy way to grab the domain to is to make a Uri (if you aren't using one already) that contains your target url and set the cookie's domain using the Uri.Host property.
CookieContainer gaCookies = new CookieContainer();
Uri target = new Uri("http://www.google.com/");
gaCookies.Add(new Cookie("__utmc", "#########") { Domain = target.Host });

Difference between HttpResponse: SetCookie, AppendCookie, Cookies.Add

there are some different ways to create multi value cookies in ASP.NET:
var cookie = new HttpCookie("MyCookie");
cookie["Information 1"] = "value 1";
cookie["Information 2"] = "value 2";
// first way
Response.Cookies.Add(cookie);
// second way
Response.AppendCookie(cookie);
// third way
Response.SetCookie(cookie);
When should I use which way? I've read that SetCookie method updates the cookie, if it already exits. Doesn't the other ways update the existing cookie as well?
And is the following code best practice for writing single value cookies?
Response.Cookies["MyCookie"].Value = "value";
If I remember correctly both
Response.Cookies.Add(..)
and
Response.AppendCookie(..)
will allow multiple cookies of the same name to be appended to the response.
On the other hand
Response.SetCookie(..)
and
Response.Cookies[key].Value = value;
will always overwrite previous cookies of the same name.
When should I use which way?
It's depends on what Cookie operation you want to do.
Note that Add and AppendCookie are doing the same functionality except the fact that with AppendCookie you're not referencing the Cookies property of the Response class and it's doing it for you.
Response.Cookies.Add - Adds the specified cookie to the cookie
collection.
Response.AppendCookie - Adds an HTTP cookie to the
intrinsic cookie collection
Response.SetCookie - Updates an existing cookie in the cookie
collection.
Exceptions will not be thrown when duplicates cookies are added or when attempting to update not-exist cookie.
The main exception of these methods is: HttpException (A cookie is appended after the HTTP headers have been sent.)
The Add method allows duplicate cookies in the cookie collection. Use the Set method to ensure the uniqueness of cookies in the cookie collection.
Thanks for MSDN!
To piggyback on tne's comment in Wiktor's reply, AppendCookie and SetCookie shouldn't be used - they're for internal use by the .NET framework. They shouldn't be public but they are, my guess would be as a hack for the IIS pipeline somewhere else.
So you should do your cookie setting this way (or write an extension method for setting multiple cookies):
string cookieName = "SomeCookie";
string cookieValue = "2017";
if (Response.Cookies[cookieName] == null)
{
Response.Cookies.Add(new HttpCookie(cookieName, cookieValue));
}
else
{
Response.Cookies[cookieName].Value = cookieValue;
}

How do I redirect to my parent action in MVC site?

I have been looking at several pages on here already such as:
How do I redirect to the previous action in ASP.NET MVC?
How can I redirect my action to the root of the web site?
Along with several hours of searching google.
No where seems to have an answer to my problem and I am sure it should be possible within MVC somehow hence the reason I am now here to ask the question.
So the problem I am facing is that I want to allow the user to change the language of the page by choosing a new language from a drop down menu which is in its own partial view hence the problem, I need to redirect to the parent action and not the child. This all works fine as long as i send the user back to the root of the site. Using the following code:
[HttpPost]
public ActionResult RegionSelect(RegionSelectionModel model)
{
var currentUser = Session.GetCurrentUser();
var currentDbUser = Session.GetUserEntity(_dataAccessLayer);
if (!ModelState.IsValid)
{
model.AvailableRegions = CacheHelpers.GetAvailableRegions<RegionView>(_dataAccessLayer, _cache).ToList();
return PartialView("_RegionSelect", model);
}
var selectedRegion = UsersControllerHelpers.SetSelectedRegion(model, _dataAccessLayer, _cache, _website.Client);
var uri = model.OriginalUrl;
var routeInfo = new RouteHelpers(uri, HttpContext.Request.ApplicationPath);
// Route Data
var routeData = routeInfo.RouteData;
routeData.Values.Remove("language");
var defaultClientLanguageCode = _website.Client.LanguagesSupported.FirstOrDefault().Code;
if (currentDbUser.Language.CountryCode != selectedRegion.PrimaryLanguage.CountryCode)
{
//TODO: Decide where to redirect or whether to refresh the whole page...
if ((defaultClientLanguageCode == selectedRegion.PrimaryLanguage.CountryCode) || (model.SelectedRegionId == 0))
{
UsersControllerHelpers.UpdateUsersRegions(currentUser, selectedRegion, _website.Client, _cache, _dataAccessLayer,
Session);
return RedirectToRoute(routeData.Values);
}
routeData.Values.Add("language",selectedRegion.PrimaryLanguage.CountryCode);
return RedirectToRoute(routeData.Values);
}
return RedirectToRoute(routeData.Values);
}
Two of my return statements return to the root page and one returns to the root but with a language so it would be "http://mysite/en-En/" but what if the user is on a page other than the root site? I want to somehow redirect them back to this same action but with the correct language string at the start.
How can i do this?
I have thought of several "hacky" ways of doing this, such as splitting the URL and swapping the language codes over. But ideally I am looking to do this as clean as possible.
Can anyone give me any idea's? Or is it just not possible?
It seems like it should be really simple but apparently not.
Thanks in advance for any help that you can provide.
EDITED
Added new code that is using code from suggested answer below.
I am now having two new problems.
I am getting this error message, if there are any things in the URL such as ?page=1:
A potentially dangerous Request.Path value was detected from the client (?)
If i try and remove the language completely using .Remove(). It removes it fine but when i try and redirect to the page in the default language it adds language?=language to the end of the URI.
Any ideas how i can resolve these two issues?
This option is definitely my answer. Leave me a comment if you need me to drop some code, and I can do that, but the examples on the linked website should get you started.
Use this method to change Request.UrlReferrer into Route data, then merge your language into that, then do a RedirectToRoute with the modified Route data.
Just use RouteData.Values.Add, RouteData.Values.Remove, and RouteData.values["whatever"], then pass that modified RouteData.Values object to RedirectToRoute()

Grabbing Cookies in Web Browser Control - WP7

In order to log into a certain part of a website the users of my application require their cookie. To do this I need to grab it and pass it to url.
Does anyone know how to grab a certain websites cookie from the browser control?
I saw this method but wasn't quite clear.
Thanks, TP.
As of WP 7.1 Mango "release", if one may call it, please see the WebBrowser Control Overview for Windows Phone. It has been recently updated a little bit, and it turns out that they actually have added some support for cookie-retrieval from the WebBrowser. On the bottom of the page you will find a tiny link GetCookies(WebBrowser) pointing to description of a new class: WebBrowserExtensions with this very handy method. Yes, this class has only that one single member. It's an extension method, I suppose no explanations needed on that.
I have not played with this method much, but it seems that this will allow you to access the very same thing as the JS trick: the cookieset for the current URL. It probably will not allow to set anything, nor to peek cookies for other URLs. Maybe if you play hard with the CookieContainer you will receive, but I doubt.
On the 7.0 release, I've been struggling quite hard to achieve "cookie transparency" for my App. Long story short, my app was doing some background HTTP requests, and also had a WebBrowser to show some online content -- and "it would be great" if both sources of connections would emit the same cookies to the server.. And guess what, my application had to make the first request, then let the browser navigate. With such requirements, there was virtually is no way to achieve consistency of the cookies - bah, even with the current new and glorious GetCookie method, I suppose it would be damn hard. So, to the point - it was possible, but needed to use some hidden API, that is present publicitly on the Phone, but is hidden in the SDK. The API is available the (public) class System.Net.Browser.WebRequestCreator, freely available. The quirk is: in the SDK this class has a single public static property "IWebRequestCreate ClientHttp" with a method "Create" that you can use to "factory" your "raw http" connections - in case you dont want to use the WebClient for some reason. On the phone, and on the emulator, there is a second public static property called "IWebRequestCreate BrowserHttp", easily returned by Reflection:
PropertyInfo brwhttp = typeof(System.Net.Browser.WebRequestCreator)
.GetProperty("BrowserHttp")
with this property, you will be able to obtain a "special" internal instance of IWebRequestCreate that is used internally by the WebBrowser. By opening your background HTTP requests with this class, you will get your cookies automatically set as if they were created/sent by the WebBrowser control, but in turn - you will NOT be able to modify http headers, userprovide http user authentication and neither do a few lowlevel things - because all that settings will be synced with the WebBrowser's data stored for current 'system user instance', if I'm allowed to call it as such on the single-user Phone device heh. The interoperation between connections and the WebBrowser works both ways - if your HTTP connection (created with use of the 'hidden property') receives any settings/cookies/etc -- then the WebBrowser will instantly notice them and update its own cache. No cookie/session loss on neither of the sides!
If you need to passively get cookies for your subsequent connections after some first WebBrowser navigation - please use the GetCookie or the JS way.
But if you need your code to be first, and then pass authz to the WebBrowser -- you will probably have to dig deeper and use the above.. It's been hidden, so please resort to the other means first!
..and don't ask me how did I found it or how much time it took :P
have a nice fun with it
//edit: I've just found out, that the BrowserHttp property is a normal Silverlight's way to access the Browser's connection factory, please see BrowserHttp. It seems that it is only has been hidden in the 'miniSilverlight' written for the WP7 platform!
The approach being described in the post you linked is to use the WebBrowser control's InvokeScript method to run some javascript. However the post appears to use a "cookies" collection which doesn't actually exist.
string cookie = myWebBrowser.InvokeScript("document.cookie") as string;
Now for the hard part the string you get contains all pertinent cookie name/value pairs for the page with the values still being Url encoded. You will need to parse the returned string for the value you need.
See document.cookie property documentation.
Edit:
Looking at it fresh instead of relying on the post, InvokeScript invokes named function on the window of the host browser. Hence the page being displayed in the WebBrowser would itself need to include a function like:-
function getCookie() { return document.cookie; }
Then the InvokeScript would look like:-
string cookie = myWebBrowser.InvokeScript("getCookie");
As #quetzalcoatl already suggested, you can use internal instance of WebRequestCreator to share cookies between browser instances and instances of WebRequest. You don't get to access the cookies directly though, I think that's just a security measure by Microsoft.
This code below creates a WebReqeust object, connected to CookieContainer of WebBrowser instance. It then posts to a url to log in the user and store cookies in the container.
After it's done, all browser instances within the app instance will have required set of cookies.
var browser = new WebBrowser();
var brwhttp = typeof (WebRequestCreator).GetProperty("BrowserHttp");
var requestFactory = brwhttp.GetValue(browser, null) as IWebRequestCreate;
var uri = new Uri("https://www.login.com/login-handler");
var req = requestFactory.Create(uri);
req.Method = "POST";
var postParams = new Dictionary<string, string> {
{"username", "turtlepower"},
{"password": "ZoMgPaSSw0Rd1"}
};
req.BeginGetRequestStream(aReq => {
var webRequest = (HttpWebRequest)aReq.AsyncState;
using (var postStream = webRequest.EndGetRequestStream(aReq)) {
// Build your POST request here
var postDataBuilder = new StringBuilder();
foreach (var pair in paramsDict) {
if (postDataBuilder.Length != 0) {
postDataBuilder.Append("&");
}
postDataBuilder.AppendFormat("{0}={1}", pair.Key, HttpUtility.UrlEncode(pair.Value));
}
var bytes = Encoding.UTF8.GetBytes(postDataBuilder.ToString());
postStream.Write(bytes, 0, bytes.Length);
}
// Receive response
webRequest.BeginGetResponse(aResp => {
var webRequest2 = (HttpWebRequest) aResp.AsyncState;
webRequest = (HttpWebRequest)aResp.AsyncState;
string resp;
using (var response = (HttpWebResponse)webRequest2.EndGetResponse(aResp)) {
using (var streamResponse = response.GetResponseStream()) {
using (var streamReader = new System.IO.StreamReader(streamResponse)) {
resp = streamReader.ReadToEnd();
}
}
}
}, webRequest);
}, req);
One of the issues I couldn't solve though was exceptions thrown when server returns 302 - it seems to throw WebException error with "Not found" description.
// Ensure this is set to true BEFORE navigating to the page
webBrowser1.IsScriptEnabled = true;
// Once the page has loaded, you can read the cookie string
string cookieString = webBrowser1.InvokeScript("eval", new string[] { "document.cookie;" }) as string;
The cookieString variable will contain the full cookie for the document. You can then parse the string.
There is an WebBrowser Extension class which is exactly developed for this:
CookieCollection tempCookies = Microsoft.Phone.Controls.WebBrowserExtensions.GetCookies(this.BrowserControl);

Categories