I have a webservice (.asmx) and I want it to retain a value on a per-user basis. Is this possible?
ie (pseudo-code)
MyWebservice
{
object perUserVariable = something;
[WebMethod]
public void myMethod()
{
if (something == null)
{
something = doBigExpensivedatabaseCall();
}
return something;
}
}
You can use ASP.NET's session mechanism.
Change your WebMethod attribute, so that it will look like that:
[WebMethod(EnableSession = true)]
This is normally achieved by cookies, or by sending the session id in the query string (both ways are completely handled by ASP.NET). The former is the default, to achieve the latter, just set cookieless="true" in your config.web file.
Yes, you would need to pass in some kind of user identifier to specify who the user is, do your operation and instead of storing it in something you will to use a durable or semi-durable store such as Cache or Session. Then look that value up from the Cache or Session instead of a local member.
Also fwiw the way you have that configured the fact something isn't static means it would be null on every single request because it would be newly initialized. Making it static however would then server the individual instance of something to each and every request there after.
This is why you need to use a store that can differentiate on the user such as Cache[userid+"something"] or the Session["something"] instead.
Your choices seem to be using some type of caching system like session or memcache.
Session will require a session id passed as a cookie to the requests. Other caching providers could probably key off of a post value like the userid.
Related
I just discovered System.Web.Caching.Cache used in a project that I am working on and I am having a hard time finding more information on it.
My question is how this cache is persisted? Is it client-side (similar to ViewState), server-side (Session)? Entirely different?
Example:
protected string FileContent
{
get
{
return Cache[FILE_UPLOAD_KEY + Id] as string ?? GetFileUpload();
}
}
It's a server-side, application-wide cache.
One instance of this class is created per application domain, and it
remains valid as long as the application domain remains active.
Information about an instance of this class is available through the
Cache property of the HttpContext object or the Cache property of the
Page object. (Cache Class, MSDN)
It grants the ability to set time limits and so forth on cached objects. And it doesn't promise the object will be there when you need it again. It keeps items in cache only so long as there is sufficient memory to do so.
So, it's not intended for passing objects between page views (use ViewState or Session for that) or controls (use Items for that). It's intended to cache global objects (accessible in any request from all clients) that are expensive to build.
It's persisted at the server, and it's global across sessions, like Application. So when you set a value in the Cache, it's available to all users until it expires.
EDIT
The example you've got probably isn't quite right (unless GetFileUpload() actually writes to the cache). Generally your calls to cache look something like:
string GetSomeStringFromCache()
{
string someString = Cache[SomeKey] as string;
if (someString == null)
{
someString = GetStringUsingSomeExpensiveFunction();
Cache.Add(SomeKey, someString, /*a bunch of other parameters*/);
}
return someString;
}
This will put it in the Cache if it's not already there, but if it is, it will just use it.
Question about settings of IIS7. How I can do one cache for each of bindings? For example, I have one site and three bindings for the site. I need to create three different caches for each of three bindings.
Use Request.Host as part of the cache key.
usr's answer is correct if you are manually reading from and writing to the cache. You have less direct control over the cache key with the OutputCacheAttribute, however.
Note that the OutputCacheAttribute still relies on cache keys in its implementation. In ASP.NET, each item that is cached is assigned a key through which it is looked up.
When you call a Controller Action that has an OutputCacheAttribute, a cache key is generated based on your request; for instance, if you have some VaryByParam designations, cache keys can differ for each user. Then the response your Action returns is stored in the cache under that key.
When the next request comes in, the cache key is generated and we check in the cache to see if there is already something cached under that key. If so, we just return that; otherwise, we continue with the Action.
We can have a different cache for each binding by including the host name in the cache key. If you're using OutputCacheAttribute, you can override it to allow varying the cache by host:
public override string GetVaryByCustomString(HttpContext context, string customVary)
{
if(customVary == "Host")
{
return context.Request.Url.Host;
}
// other behaviors here if necessary
return "";
}
This will allow the cache key to be dynamically modified to include the host name through which the site is accessed. This means that if you have three different bindings, you will have three different cache keys (assuming no other varying parameters).
Here's how to modify your Controller Action:
[MyOutputCache(VaryByParam = "None", VaryByCustom = "Host", Duration = 7200)]
public ActionResult Index()
{
// ...
return View();
}
Notice the inclusion of VaryByCustom = "Host", which is then seen by your overriden OutputCacheAttribute's GetVaryByCustomString() method and thus included in the cache key that is used.
I've got a web service (ASP.NET 2.0) that I'm calling from javascript using jQuery's $.ajax(). I've been told that the session key is often used as a nonce in a situation like this for security purposes; the web service takes a key as one of its parameters and only returns data if it matches the current session key.
I'm attempting to accomplish this by setting the value of a hidden field to the current SessionID on every Page_Load (i.e. every postback), then grabbing it in the javascript to pass as a parameter. But it's never the same key as the web service's current key (Context.Session.SessionID).
Is this possible to resolve, or should I be doing this another way?
EDIT: code to set session in hidden field as requested.
hfSession.Value = Context.Session.SessionID;
That's in the Page_Load of a .ascx control, not under any conditional (i.e. not wrapped with if (!Page.IsPostBack).
I believe you are trying to prevent Cross Site Script Request Forgery (CSRF). The Session ID is actually sent across as a cookie and the attacker can set this. Rather than use the Session ID itself, you should use a randomly generated number stored in a Session variable.
String GetCSRFToken()
{
String token = (String)Session["CSRFToken"];
if( token == null )
{
token = GenerateLongRandomString();
Session["CSRFToken"] = token;
}
return token;
}
void AssertValidCSRFToken(String token)
{
if( token != GetCSRFToken() )
{
throw new Exception("Invalid Request!")
}
}
Here is a link to another question with more info on preventing this kind of attack:
CSRF Validation Token: session id safe?
Asp.net actually generates a new Session ID for every request until you use the Session State to store some value. This could be a reason why the values are different. Try and save something in the session. Perhaps
Session["SessionID"] = Context.Session.SessionID;
hfSession.Value = Context.Session.SessionID;
A new SessionID is generated each time the page loads until the session is actually allocated. So if you don't actually allocate anything to the session, the SessionID will change upon each page load.
When a user log into my asp.net site I use the following code:
FormsAuthentication.RedirectFromLoginPage(userid, false);
As I often need to use the userid I can then later get the userid by:
string userid = System.Web.HttpContext.Current.User.Identity.Name;
Now I also want to show the logged in username on each page and my questions is therefore where do I place the username best if I need to use it on every page. User.Identity.Name is already taken by the userid so I can't use that one. Another solution would be to get the username from the database on each page, but that seems like a bad solution.
So: Is the best way to use Sessions to store the username?
There are essentially 6 different ways to store information, each with it's own benefits and drawbacks.
Class member variables. These are only good for the life of one page refresh.
HttpContext variables. Like class member variables, only good for one page refresh.
ViewState, these are passed from page to page to keep state, but increase the size of the downloaded data. Also, not good for sensitive information as it can be decoded.
Cookies. Sent on each page request. Also not good for sensitive information, even encrypted.
Session. Not passed to the end user, so good for sensitive information, but it increases the resource usage of the page, so minimizing usage for busy sites is important.
Authentication Cookie User Data - This is like like cookies, but can be decoded with the authentication data and used to create a custom IIdentity provider that implements your desired Identity information, such as Name or other profile information. The size is limited, however.
You can store just about anything in SessionState in asp.net. Just be careful and store the right things in the right places (you can also use ViewState to store variables.
Check this out for how to use SessionState to store and retrieve variables across postbacks.
public string currentUser
{
get { return Session["currentUser"] as string; }
private set { Session["currentUser"] = value; }
}
Using sessions isn't a bad idea but make sure to check for NULL when retrieving the values for when the sessions time out.
Or you could pass the variable through in the URL e.g
/Set
Response.Redirect("Webform2.aspx?Username=" + this.txtUsername.Text);
/Read
this.txtBox1.Text = Request.QueryString["Username"];
I am fixing an ASP.NET application that makes heavy use of session to track per-page data. One of the problems is that the session bleeds between pages.
ViewState would be a better solution, except:
The data is not serializable
There is too much data to be sending back and forth each postback
So I want to:
create a page key for the session data (i.e. stick a random GUID in a hidden field)
expire data from an abandoned page even if the overall session is active
Is there a good way to expire partial session data?
The following temporary storage locations are available:
Session. This follows a user around and uses a cookie. It can be configured to use a URL param to retrieve. Session can also be configured to be stored in process(inproc) on the web server, in SQL Server, or in a state server. InProc can store any data type, but the others require the type to be serializable.
Cache. Data stored in Cache is available to be used by any user in an session. It works similar to session, as the objects are retrievable via a key. One of the nicer features of cache is that you can control how long things are stored, and you can consume event when they expire. You can store anything here, but you may run into issues with using it in a webfarm.
HttpContext. This is scoped to the current request. Remember, requests can be webservice calls, calls to get webpages to get HTML, or calls to a service that returns images. Anything can be stored here.
ViewState. View state is scoped to a page. Must be serializable.
You may want to examine cache. If you're using a webfarm, it won't work, but you could use a GUID of some sort as the key that you map back to a session.
I would probably do it this way:
Create an object to store the state information you want to be page specific. If different pages need different information, create multiple classes.
Store this object in a single session key: Session["PageSpecific"]; for example.
Create a class which inherits from System.Web.UI.Page.
In the OnLoad event of the base class, clear the session key if the the page is not performing a postback.
Create and call an overloadable method to populate the session object.
Instead of inheriting from System.Web.UI.Page in each of your pages, inherit from your new base class.
Something like this (warning: air code. May contain syntax errors):
public class PageBase
: System.Web.UI.Page
{
protected overrides OnInit(System.EventArgs e) {
base.OnInit(e);
if(!this.IsPostBack) {
Guid requestToken = System.Guid.NewGuid();
ViewState["RequestToken"] = requestToken;
Session["PageSpecific" & requestToken.ToString()] = InitializePageSpecificState();
}
}
protected virtual object InitializePageSpecificState() {
return new GenericPageState();
}
//You can use generics to strongly type this, if you want to.
protected object PageSpecificState {
get {
return Session["PageSpecific" & ViewState["RequestToken"].ToString()];
}
}
}
Perhaps on each page !IsPostBack or through a base page you could null out all references to session data not pertaining to that page. This would "expire" the data each time the user goes to another page in the site.
If the user leaves the site or goes in-active, there's not much you can do until the session expires, but in this scenario there would only be one page worth of session data per user.