I have an ASP.NET MVC view that uses jquery.Uploadify to post files to one of my controllers for use in my application and one of the side effects I noticed with Uploadify is that when the Flash file Uploadify uses to submit files to the server posts to my controller it gets its own SessionID from ASP.NET. This would be fine, of course, if my Upload controller didn't use the Session to store a list of files that have been uploaded by the current user for future manipulation by my application... So given this issue, after I upload files with my View, the current user's session does not contain any of the files that were just posted.
Any suggestions on how to achieve what I want without relying on Sessions (and, preferably, without a database)?
Since Uploadify is purely a front end script, I don't understand why it would be getting a session from ASP.NET. I also don't fully understand what your particular problem is.
If your problem is that once the files are uploaded, the user can't see them on the screen, then I would suggest figuring out a method displaying the list of files that is independent of Uploadify. If it can, have it send an ID token along with the files and then grab the data needed to show the list from a database.
Maybe a static hashmap which key is the user:ip of the client?
The value will be whatever object you want to stored across the different Sessions.
One thing to cross check--did you make the session "live" by adding some data to it before going to uploadify? ASP.NET regenerates sessions until it has data in the session.
This is the solution I came up with. I haven't done much testing, but it seems to be an acceptable alternative to Session in my current scenario. I will use the Global.asax's Session_End/Session_Start to ensure rows are created and removed as needed.
public class UserTable : Dictionary<string, Dictionary<string, object>>
{
public new object this[string key]
{
get
{
object value = null;
if (HttpContext.Current != null)
{
var sessionId = HttpContext.Current.Session.SessionID;
if (ContainsKey(sessionId) && base[sessionId].ContainsKey(key))
value = base[sessionId][key];
}
else
throw new Exception("No HttpContext present.");
return value;
}
set
{
if (HttpContext.Current != null)
{
var sessionId = HttpContext.Current.Session.SessionID;
if (!ContainsKey(sessionId))
Add(sessionId, new Dictionary<string, object>());
if (!base[sessionId].ContainsKey(key))
base[sessionId].Add(key, value);
else
base[sessionId][key] = value;
}
else
throw new Exception("No HttpContext present.");
}
}
public object this[string sessionId, string key]
{
get
{
object value = null;
if (ContainsKey(sessionId) && base[sessionId].ContainsKey(key))
value = base[sessionId][key];
return value;
}
set
{
if (!ContainsKey(sessionId))
Add(sessionId, new Dictionary<string, object>());
if (!base[sessionId].ContainsKey(key))
base[sessionId].Add(key, value);
else
base[sessionId][key] = value;
}
}
public void Add(string sessionId)
{
Add(sessionId, new Dictionary<string, object>());
}
public void Add()
{
if (HttpContext.Current != null)
Add(HttpContext.Current.Session.SessionID);
else
throw new Exception("No HttpContext present.");
}
public new void Remove(string sessionId)
{
base.Remove(sessionId);
}
public void Remove()
{
if (HttpContext.Current != null)
Remove(HttpContext.Current.Session.SessionID);
else
throw new Exception("No HttpContext present.");
}
}
Related
We're using ASP.Net Web API to generate a feed and it includes the ability to do paging.
myfeed.com/afeed?page=2
My boss says "let's also allow users to use 'paged', because that's what WP uses." In addition, we're also using pageIndex in some of our older feeds. So what I'd like to do is accept all three.
myfeed.com/afeed?page=2
myfeed.com/afeed?paged=2
myfeed.com/afeed?pageIndex=2
I'd like to do is be able to write a clean Web API method, such as
public Foo Get(int page = 1)
{
//do some stuff
return foo;
}
without cluttering the method with page 'plumbing'. So I tried creating an ActionFilter
public override void OnActionExecuting(HttpActionContext actionContext)
{
object pageParam = new object(); //query["page"]
if (pageParam == null)
{
var altPageParam = GetPageParamUsingAlternateParams(actionContext);
if (altPageParam != null){}
//SetPageParam here
}
base.OnActionExecuting(actionContext);
}
private object GetPageParamUsingAlternateParams(HttpActionContext actionContext)
{
object result = new object();
object pageIndexParam = new object(); //Query["pageIndex"]
object pagedParam = new object(); ////Query["paged"]
if (pagedParam != null)
result = pagedParam;
else if (pageIndexParam != null)
result = pageIndexParam;
return result;
}
I didn't finish. As I was looking for the best way to get the query params, I stumbled into a big mistake!
OnActionExecuting is executed after int page = 1. Sure, I could override it in an ActionFilter, but that would lead to confusion down the road. I really want to be able to do a simple flow through the URI query parameters that goes from
page -> paged -> pageIndex -> default value in method
I have found a lot of articles on custom binding to a an object. Also, I found articles about "parameter binding", however those dealt with FromUri and FromBody. I didn't find anything that I felt had a direct parallel to what I'm facing.
You could achieve what you want by defining 3 different GET method with parameters matched with the query segment of the Url like the code snippet below:
public class ProductsController : ApiController
{
//Matched api/products?page=1
public IHttpActionResult Get(int page)
{
return GetPagedData(page);
}
//Matched api/products?paged=1
public IHttpActionResult GetPaged(int paged)
{
return GetPagedData(paged);
}
//Matched api/products?pagIndex=1
public IHttpActionResult GetPageIndex(int pageIndex)
{
return GetPagedData(pageIndex);
}
//Do the real paging here
private IHttpActionResult GetPagedData(int page =1)
{
return Ok("Data Pages");
}
}
In my ASP .NET MVC 2 - application, there are several controllers, that need the session state. However, one of my controllers in some cases runs very long and the client should be able to stop it.
Here is the long running controller:
[SessionExpireFilter]
[NoAsyncTimeout]
public void ComputeAsync(...) //needs the session
{
}
public ActionResult ComputeCompleted(...)
{
}
This is the controller to stop the request:
public ActionResult Stop()
{
...
}
Unfortunately, in ASP .NET MVC 2 concurrent requests are not possible for one and the same user, so my Stop-Request has to wait until the long running operation has completed. Therefore I have tried the trick described in this article and added the following handler to Global.asax.cs:
protected void Application_BeginRequest()
{
if (Request.Url.AbsoluteUri.Contains("Stop") && Request.Cookies["ASP.NET_SessionId"] != null)
{
var session_id = Request.Cookies["ASP.NET_SessionId"].Value;
Request.Cookies.Remove("ASP.NET_SessionId");
...
}
}
This simply removes the session-id from the Stop-Request. At the first glance this works well - the Stop-Request comes through and the operation is stopped. However, after that, it seems that the session of the user with the long running request has been killed.
I use my own SessionExpireFilter in order to recognize session timeouts:
public class SessionExpireFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
// check if session is supported
if (ctx.Session != null)
{
// check if a new session id was generated
if (ctx.Session.IsNewSession)
{
// If it says it is a new session, but an existing cookie exists, then it must
// have timed out
string sessionCookie = ctx.Request.Headers["Cookie"];
if ((null != sessionCookie) && (sessionCookie.IndexOf("ASP.NET_SessionId") >= 0))
{
filterContext.Result = new JsonResult() { Data = new { success = false, timeout = true }, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
}
}
base.OnActionExecuting(filterContext);
}
}
ctx.Session.IsNewSession is always true after the Stop-Request has been called, but I don't know why. Does anyone know why the session is lost? Is there any mistake in the implementation of the Stop-Controller?
The session is lost because you removed the session cookie. I'm not sure why that seems illogical. Each new page request supplies the cookie to asp.net, and if there is no cookie it generates a new one.
One option you could use to use cookieless sessions, which will add a token to the querystring. All you need to do is generate a new session for each login, or similar.
But this is one of the reasons why session variables are discouraged. Can you change the code to use an in-page variable, or store the variable in a database?
I added a caching layer between my ASP.NET MVC application and database using the GetOrStore<T> cache helper below.
This includes caching users' system roles.
When I remove a cached roles object for a signed-in user via HttpRuntime.Cache.Remove(userRoleCacheKey), subsequent requests fail with a NullReferenceException because the cache helper is returning a null value for the role cache, even though the cached key should not exist and the helper should regenerate it.
It seems like the cached key lingers around with a null value. The exception won't budge until I request a role-heavy page a few seconds later.
Why is my cache breaking?
public static class CacheExtensions
{
public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator)
{
var result = cache.Get(key);
if (result == null)
{
result = generator();
if (result != null) // can't store null values in cache.
{
cache[key] = result;
}
}
return (T)result;
}
}
Here is the code that fetches the user's roles and caches it:
public override string[] GetRolesForUser(string userId)
{
return HttpRuntime.Cache.GetOrStore<string[]>(
"RolesForUser[" + userId + "]",
() => Con.Query<string>("SELECT Role FROM vw_UserRoles WHERE UserId = #userId", new { userId = Guid.Parse(userId) }).ToArray());
}
where Con retrieves an open IDbConnection.
Minor issue in your code that can hide the problem from you:
if (result != null) // can't store null values in cache.
{
cache[key] = result;
}
What about else clause? The result returned anyway, even if it is null value. Throw InvalidOperationException in that case and debug your queries passed as generator parameter.
I have an MVC3 application and I would like to give the users the ability to set preferences that would be enabled when the user logs in.
I really don't have any idea where to start with this and would really appreciate being pointed in the right direction. I did try some changes to the membership class but now I am thinking that's probably not the best way to go about things.
You could do it in a database (sounds like you might be using one at least with the out-of-the-box membership provider) once uniquely identifying a user. In that case, you may want to implement your own membership provider.
You have to do a little work to start implementing your own provider. If this is your only requirement, you might be able to avoid it by writing your own class that returns settings in a format of your choosing
public static class UserSettings
{
public static string GetSettings(IPrincipal user)
{
if(user.Identity.IsAuthenticated)
{
// dip into database using user.Identity.Name property
return "string with user settings";
// this also assumes user.Identity.Name is uniquely able
// to identify a user in your database!
}
return string.Empty;
}
}
Or, if the information is completely trivial, maybe you could implement a cookie representation of the user settings. This, of course, comes with all the caveats of using cookies, but you could avoid storing the information in a database
Anywhere you have an HttpContext you could grab the settings value like so:
if(HttpContext.Current != null)
{
string userSettings = HttpRequest.Current.Request.Cookies["NameOfCookie"];
}
You can use the FormsAuthentication cookie to store your user information and avoid accessing the database all the time. That cookie is encrypted and whatever information you're storing as safe as the user session itself. The only problem with the cookies is that they have a maximum size of 4K so, if your user info is massive then you might run into a problem. When I use the cookie approach I store my user data as a JSON and then deserialize that JSON on each page request. Here is my login controller logic (I'm using SimpleMembership but the approach is the same:
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, model.RememberMe))
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null)
{
var user = _userLogic.GetItem(model.UserName);
if (user != null && user.IsActive)
{
var newAuthTicket = new FormsAuthenticationTicket(authTicket.Version, authTicket.Name, authTicket.IssueDate, authTicket.Expiration, authTicket.IsPersistent, JsonConvert.SerializeObject(user));
var newCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(newAuthTicket))
{
Expires = authCookie.Expires
};
Response.Cookies.Add(newCookie);
return RedirectToLocal(returnUrl);
}
WebSecurity.Logout();
ModelState.AddModelError("UserName", "This account has been deactivated.");
return View(model);
}
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
Notice the newAuthTicket creation and how user instance is passed to it as a JSON. After that all I have to do is desirialize this user object in my base controller's OnAuthorization method:
protected override void OnAuthorization(AuthorizationContext filterContext)
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null)
{
var principal = new CustomPrincipal(HttpContext.User.Identity)
{
CurrentUserInfo = JsonConvert.DeserializeObject<User>(authTicket.UserData)
};
HttpContext.User = principal;
AppUser = principal.CurrentUserInfo;
ViewBag.AppUser = AppUser;
}
}
base.OnAuthorization(filterContext);
}
Create a new table in your database.
I've got some MVC code that serializes an EF 3.5 object into an anonymous type for return as a JSON result to an AJAX call on my page. The hurdle I have is that when I send the object back to the server via JSON, (and let the ModelBinder deserialize it for me into my EF type), I have to update it in my Entity Framework context manually. Or at least that's what I'm doing now. It has no EntityKey, so attaching it fails. I end up having to look up the old object and update it property by property. Any ideas around this? Is the solution to pass the EntityKey around with my object?
Here's what I have:
public void Update(Album album)
{
using (var db = new BandSitesMasterEntities())
{
var albumToUpdate = db.Album.First(x => x.ID == album.ID);
albumToUpdate.AlbumTitle = album.AlbumTitle;
albumToUpdate.Description = album.Description;
albumToUpdate.ReleaseYear = album.ReleaseYear;
albumToUpdate.ImageURL = album.ImageURL;
albumToUpdate.OtherURL = album.OtherURL;
db.SaveChanges();
}
}
And here's what I'd like to do, or something similar:
public void Update(Album album)
{
using (var db = new BandSitesMasterEntities())
{
db.Attach(album)
db.SaveChanges();
}
}
or you could use AutoMapper to map those fields for you, so you'd just add one extra line to your example.
Why not just use the UpdateModel or TryUpdateModel controller methods instead? It works really well with EF and you can even explicitly set the included property list.
The id parameter will auto-map via the MVC framework to the hidden field on your form specifying the id.
public void Update(int id, FormCollection collection)
{
using (var db = new BandSitesMasterEntities())
{
var albumToUpdate = db.Album.First(x => x.ID == id);
//use UpdateModel to update object, or even TryUpdateModel
UpdateModel(albumToUpdate, new string[] { "AlbumTitle", "Description", "ReleaseYear", "ImageURL", "OtherURL" });
db.SaveChanges();
}
}
This became much easier for us in EF 4.0. This is what we did in EF 3.5:
public static void AttachAsModified(this ObjectContext objectContext, string setName, object entity,
IEnumerable<String> modifiedFields)
{
objectContext.AttachTo(setName, entity);
ObjectStateEntry stateEntry = objectContext.ObjectStateManager.GetObjectStateEntry(entity);
foreach (String field in modifiedFields)
{
stateEntry.SetModifiedProperty(field);
}
}
And then:
using (var db = new BandSitesMasterEntities())
{
db.AttachAsModified("Album", album, new string[] { "AlbumTitle", "Description", "ReleaseYear", "ImageURL", "OtherURL" })
db.SaveChanges();
}
It becomes more complicated if you have foreign key constraints, but it looks like you don't.
There is no way around the entity key issue. You either have to add it to your anonymous type or I would recommend you port your code to using data services.
http://www.hanselman.com/blog/jQueryToShipWithASPNETMVCAndVisualStudio.aspx
which would allow you to do all of the db manipulation on the client side.
http://msdn.microsoft.com/en-us/data/bb931106.aspx
Did you try something like:
object original;
var key = contexte..CreateEntityKey("EntitySet", modified);
if(contexte.TryGetObjectByKey(key, out original))
{
var originalEntity = (YourEntityType)original;
// You have to mannualy set your entityKey
originalEntity.YourEntityReference.EntityKey = new EntityKey("Entities.EntitySet", "Id", modified.YourEntity.Id);
contexte.ApplyPropertyChanges("EntitySet", modified);
}
contexte.SaveChanges();
Assuming that your EntityReference are set by dropDown, you'll still have the Id
In your Album entity's partial class you may define a CopyFrom function and call it from your Update function
partial class Album
{
public void CopyFrom(Album album)
{
//individual field copying here
}
}
public void Update(Album album)
{
...
albumToUpdate.CopyFrom(album);
...
}