I want to develop a new custom session state provider or use an existing (distributed caching, sql ...). Our main website renders more than 10 000 000 visits per day.It is really important for us to provide an easy rollback/switch in case of an error or a performance hit. Change web.config is not optimal because we have more than 20 front end servers. Our idea is to switch between session provider (from our custom to InProc) with a simple config in database.
Is it possible to have multiple session state providers or easily switch between providers ?
i found here http://netpl.blogspot.fr/2007/06/wrapped-inprocsessionstatestore.html, a solution for having a generic wrapper, but it does not seems quite robust.
Thanks,
Just for future references, it is not possible to dynamically change the session state provider.
it is not.. totally true.. you can do it like this using Reflection:
var privateFieldFlags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
//Get session state section
var sessionStateSection = ConfigurationManager.GetSection("system.web/sessionState") as SessionStateSection;
var values = typeof(ConfigurationElement).GetField("_values", privateFieldFlags).GetValue(sessionStateSection);
var entriesArray = values.GetType().BaseType.GetField("_entriesArray", privateFieldFlags).GetValue(values);
//Get "Mode" entry (index: 2)
var modeEntry = (entriesArray as System.Collections.ArrayList)[2];
var entryValue = modeEntry.GetType().GetField("Value", privateFieldFlags).GetValue(modeEntry);
//Change entry value to InProc
entryValue.GetType()
.GetField("Value", privateFieldFlags)
.SetValue(entryValue, System.Web.SessionState.SessionStateMode.InProc);
references:
Dynamic session state provider
http://www.answerandquestion.net/questions/4447903/dynamic-session-state-provider
Related
I'm working with the SAFE stack (https://safe-stack.github.io/) and through the example dojo. It's great so far.
I'd like to extend the example to include a button to login/auth via Google. So I looked at an example on the Google website (https://developers.google.com/identity/sign-in/web/build-button). And then I had a look how to do authentication using ASP.NET (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-2.1&tabs=aspnetcore2x) As a result I ended up confused as to how to integrate this into a SAFE project. Can someone tell me what they would do? SHould I be trying to use ASP.NET Identity or should I be using the JWT approach? I don't even know if they are the same since I'm very new to web frameworks.....
The other question I have is how would one inject raw Javascript into the client side of a SAFE project. The google example above shows raw JS/CSS/HTML code? Should I be injecting that as is or should I look in React for some button that does this and map that idea back through Fable?
Setting up OAuth
The easiest way to use Google OAuth is to wait until the next release of Saturn, at which point Saturn will include the use_google_oauth feature that I just added. :-) See the source code if you're interested in how it works, though I'm afraid you can't implement this yourself with use_custom_oauth because you'll run into a type error (the underlying ASP.NET code has a GoogleOptions class, and use_custom_oauth wants an OAuthOptions class, and they aren't compatible).
To use it, add the following to your application CE:
use_google_oauth googleClientId googleClientSecret "/oauth_callback_google" []
The last parameter should be a sequence of string * string pairs that represent keys and values: you could use a list of tuples, or a Map passed through Map.toSeq, or whatever. The keys of that sequence are keys in the JSON structure that Google returns for the "get more details about this person" API call, and the values are the claim types that those keys should be mapped to in ASP.NET's claims system. The default mapping that use_google_oauth already does is:
id → ClaimTypes.NameIdentifier
displayName → ClaimTypes.Name
emails[] (see note) → ClaimTypes.Email
Those three are automatically mapped by ASP.NET. I added a fourth mapping:
avatar.url → `"urn:google:avatar:url"
There's no standard ClaimTypes name for this one, so I picked an arbitrary URN. Caution: this feature hasn't been released yet, and it's possible (though unlikely) that this string might change between now and when the feature is released in the next version of Saturn.
With those four claim types mapped automatically, I found that I didn't need to specify any additional claims, so I left the final parameter to use_google_oauth as an empty list in my demo app. But if you want more (say you want to get the user's preferred language to use in your localization) then just add them to that list, e.g.:
use_google_oauth googleClientId googleClientSecret "/oauth_callback_google" ["language", "urn:google:language"]
And then once someone has logged in, look in the User.Claims seq for a claim of type "urn:google:language".
Note re: the emails[] list in the JSON: I haven't tested this with a Google account that has multiple emails, so I don't know how ASP.NET picks an email to put in the ClaimTypes.Email claim. It might just pick the first email in the list, or it might pick the one with a type of account; I just don't know. Some experimentation might be needed.
Also note that third-party OAuth, including GitHub and Google, has been split into a new Saturn.Extensions.Authorization package. It will be released on NuGet at the same time that Saturn's next version (probably 0.7.0) is released.
Making the button
Once you have the use_google_oauth call in your application, create something like the following:
let googleUserIdForRmunn = "106310971773596475579"
let matchUpUsers : HttpHandler = fun next ctx ->
// A real implementation would match up user identities with something stored in a database, not hardcoded in Users.fs like this example
let isRmunn =
ctx.User.Claims |> Seq.exists (fun claim ->
claim.Issuer = "Google" && claim.Type = ClaimTypes.NameIdentifier && claim.Value = googleUserIdForRmunn)
if isRmunn then
printfn "User rmunn is an admin of this demo app, adding admin role to user claims"
ctx.User.AddIdentity(new ClaimsIdentity([Claim(ClaimTypes.Role, "Admin", ClaimValueTypes.String, "MyApplication")]))
next ctx
let loggedIn = pipeline {
requires_authentication (Giraffe.Auth.challenge "Google")
plug matchUpUsers
}
let isAdmin = pipeline {
plug loggedIn
requires_role "Admin" (RequestErrors.forbidden (text "Must be admin"))
}
And now in your scope (NOTE: "scope" will probably be renamed to "router" in Saturn 0.7.0), do something like this:
let loggedInView = scope {
pipe_through loggedIn
get "/" (htmlView Index.layout)
get "/index.html" (redirectTo false "/")
get "/default.html" (redirectTo false "/")
get "/admin" (isAdmin >=> htmlView AdminPage.layout)
}
And finally, let your main router have a URL that passes things to the loggedInView router:
let browserRouter = scope {
not_found_handler (htmlView NotFound.layout) //Use the default 404 webpage
pipe_through browser //Use the default browser pipeline
forward "" defaultView //Use the default view
forward "/members-only" loggedInView
}
Then your login button can just go to the /members-only route and you'll be fine.
Note that if you want multiple OAuth buttons (Google, GitHub, Facebook, etc) you'll probably need to tweak that a bit, but this answer is long enough already. When you get to the point of wanting multiple OAuth buttons, go ahead and ask another question.
Basically I need user data to be available on all pages in the site.
I need to display some user properties in the layout, like:
[Username], [FirstName], [LastName], [Email], [IsEmailVerified],
[Phone], [LastLoginDate], and 4 more properties...
But, these aspects need to be considered:
No use of Session (I disabled it in my application).
No database interaction on each request in order to get those user properties - I want to get them properties once and store them somewhere available.
I use Forms Authentication.
Now, from a little search I made, I found this article from 2008, posted by Microsoft, about Forms Authentication: Storing Additional User Data in the Ticket, and Using a Custom Principal.
I think this is the closest approach, because now I use User.Identity.Name to get only the [Username] stored in the authentication ticket, and I love using it because it is quick, available and simple.
But I also have a few questions that bother me with this approach:
This was posted in 2008, is it still relevant for 2017? is there something better and new today?
As you can see above, I have quite a bit user properties that need to go into the auth cookie, and as I read - a lot of user data in the auth cookie is not reccomended...
Thanks for the helpers.
Yes, it's still valid. Although people will tell you that newer approaches exists that involve claims and you should possibly considef the new Identity subsystem, the old good forms authentication is still one of viable options.
As for "lot of data", it's just one of shortcommings of the forms module, it doesn't handle multiple cookies, thus, you are constrained by the 4kb limit of a single cookie. Considering the encryption and signing, this makes much less data available in the custom data section of the cookie. But still, it should still be enough to have a simple serialization of like 10 attributes.
If you want to make a half of a step forward, you can just switch to the session authentication module, a benefit is that it is based on claims plus it supports large user data (as it automatically splits the data into chunks). I've blogged on that some time ago
http://www.wiktorzychla.com/2014/11/forms-authentication-revisited-for-net.html
A snippet from this approach
var identity = new ClaimsIdentity( "custom" );
identity.AddClaim( new Claim( ClaimTypes.Name, txtLogin.Text ) );
var principal = new ClaimsPrincipal( identity );
principal.AddClaim( new Claim( ClaimTypes.UserData, "whatever goes here" ) );
SessionAuthenticationModule sam =
FederatedAuthentication.SessionAuthenticationModule;
var token =
sam.CreateSessionSecurityToken( principal, string.Empty,
DateTime.Now.ToUniversalTime(), DateTime.Now.AddMinutes(20).ToUniversalTime(), false );
sam.WriteSessionTokenToCookie( token );
strange problem here. On local development in asp.net webforms (4.5 / 4.7) I am finding httpruntime.Cache always null even when properly set. I attempted it on another iis express workstation and found the same behavior, even with a tester single page web page. That same page in production IIS 7.5 works and is storing and delivering from cache. The code specifically is below, but I have tried a tester storing a simple string in httpruntime.Cache.
var cache = System.Runtime.Caching.MemoryCache.Default;
var luCacheKey = "lu_" + dsName;
var ic = HttpRuntime.Cache.Get(luCacheKey) as ICollection;
if (ic == null) {
and from the tester
var item = HttpRuntime.Cache.Get("x");
if (item == null)
{
HttpContext.Current.Cache.Insert("x", "test" , null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
Response.Write("added to cache<br>");
}
else {
Response.Write("already in cache");
}
So, I am wondering if there is something perhaps in web.config that I could look at or is this expected IIS express behavior? Note, System.runtime.Caching does work properly.
var cache = System.Runtime.Caching.MemoryCache.Default;
var ic = cache[luCacheKey] as ICollection;
if (ic == null)
{
var filterCriteria = new BinaryOperator("LookupGroup", dsName, BinaryOperatorType.Equal);
var lookups = xpoSession.GetClassInfo(typeof(Lookups));
ic = xpoSession.GetObjects(lookups, filterCriteria, new SortingCollection(), 0, 0, false, false);
var cachePolicy = new System.Runtime.Caching.CacheItemPolicy() { AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(30) };
cache.Add(new System.Runtime.Caching.CacheItem(luCacheKey, ic), cachePolicy);
You incorrectly add your object to the cache.
Instead of DateTime.Now follow the docs and put DateTime.UtcNow. This resolves a common issue where your machine is in a "non-zero" time zone which prevents the inner logic of the cache to manage your expirations correctly.
From the docs
To avoid possible issues with local time such as changes from standard time to daylight saving time, use UtcNow rather than Now for this parameter value.
https://msdn.microsoft.com/en-us/library/4y13wyk9(v=vs.110).aspx
Adding more information as follow up on why the behavior may change between servers.
This change in behavior may be caused by having .NET 4.7 installed on the machine. The article linked below says that Microsoft will fix this in the next version of .NET and in the next hotfix.
Quoting parts of the Microsoft page:
Symptoms:
Assume that you have Microsoft .NET Framework 4.7 installed on a
computer. When you try to insert items into the Cache object by using
the Cache.Insert (string, object, CacheDependency, DateTime, TimeSpan)
Insert overload method, you may notice that the inserted Cache items
expire much earlier or later than the specified DateTime (expiration
time).
Cause:
The internal implementation of System.Web.Caching.Cache uses
Coordinated Universal Time (UTC) time-stamp for absolute expiration.
But this particular Cache.Insert (string, object, CacheDependecy,
DateTime, TimeSpan) Insert overload method does not make sure whether
the expiration time is converted to UTC. Therefore, expiration for
items that are inserted into the Cache object by using this overload
will occur earlier or later than expected, depending on the computer
time zone difference from Greenwich Mean Time (GMT).
Workaround:
The temporary workaround for this issue is to use either the Cache.Add method or a different Cache.Insert overload method.
Resolution:
This issue will be fixed in the next version of the .NET Framework, and will also be available in the next hotfix for the .NET Framework 4.7.
References:
https://support.microsoft.com/en-us/help/4035412/fix-expiration-time-issue-when-you-insert-items-by-using-the-cache-ins
http://vimvq1987.com/2017/08/episerver-caching-issue-net-4-7/
I have a search box on the page (webservice-fed results) and I'd like to save the search TERMS for the user in a UL\LI list on the page. So the next time they come back to the page, the results are still there....but if they clear their cache then it gets reset.
What's the best way to go about that?...I can persist between postbacks easily, but this is a new one for me.
Thanks,
Steve
Probably use a cookie, but remember you're limited to 4kb of data.
Otherwise, store the session in a database. Then save that records ID to a cookie. That way you can load the data from the database based on the ID in the cookie. Then just flush any entries in DB older than say, 30 days or something.
Due to lack of sleep, I have given you an answer in PHP. Sorry about that, I'll leave it because the information is still correct, just the syntax will be slightly different in asp.net
You have two options for data-persistence in php; cookies and sessions.
Sessions are server-side, and last as long as the browser window stays open.
Cookies are client-side, and last until the user clears their cache.
So it sounds like you want a cookie option. So in your search query processor, add the line
setcookie('search_' . time(), $_POST['search_query'], (time() + 10368000));
This will create a cookie on the client machine, with the name search_xxxx where xxxx is a timestamp (each cookie has to have a unique name otherwise they will overwrite eachother).
The weird looking calculation at the end is an expire time, which is set to 120 days in the future.
Then in your php document that displays your search page, you need to spit out all these cookie values.
foreach($_COOKIES as $k => $v) {
if(substr($k, 0, 7) == 'search_') echo($v . '<br />');
}
This will spit out each of the search terms found on the clients machine. The if statement is to make sure it only displays search term cookies, and no others.
Use a cookie. Assuming you start with a List<string> of search terms called terms, do:
var sb = new StringBuilder();
foreach (var t in terms) sb.Append(t).Append(";")
var c = new HttpCookie("terms");
c.Value = sb.ToString().TrimEnd(';');
c.Expires = DateTime.Now.AddDays(30);
Response.Cookies.Add(aCookie);
Then when you need to access those terms again (to databind to a Repeater, or process in some other way for display on your page):
if (Request.Cookies["terms"] != null) {
var terms = new List<string>();
foreach (var t in Request.Cookies["terms"].Value.Split(';')) list.Add(t);
}
A Cookie is probably your solution for today, but HTML5 localStorage will eventually be the best bet. Only supported by modern browser versions right now, depends on your users.
we are using the asp.net profiles provider in our app and I need to update one particular property for most of our users. Does anyone have a console app or sample code written? We are not storing the properties in string format. This binary stream is in the aspnet_Profile table.
thanks,
hp
You can do something like this fairly easily:
Membership.GetAllUsers().Cast<MembershipUser>()
.Where(u => true /*insert your criteria here*/)
.ToList().ForEach(user =>
{
var p = ProfileBase.Create(user.UserName, true);
// do whatever you want to the profile here
int counter = (int)p["Counter"];
counter++;
p["Counter"] = counter;
p.Save();
});
The above code will work as-is in any handler of your site, but if you want to do it with a console app, simply copy the <system.web> section from your web.config to the app.config of the console app.
caveat: if you are using the default provider connection string, localSqlServer, you will need to create a new connection string explicitly pointing to the .mdf as only web applications have a concept of DATADIRECTORY (app_data).