Store common date accross pages in ASP.NET MVC - c#

Im using a base controller & OnActionExecuting to get 'common' site data that I want to show & keep accross most page's (name, ID etc... not more than 4/5 fields)
The Method in OnActionExecuting reads the database & saves to ViewBag which Ipick up in my Views but I cant help thinking this is a waste as it needs a DB call for every OnActionExecuting.
How can consolodate these DB calls and slim down the DB access?

What I have done on a recent project is during Login I get the 'common' data which in this case was UserID, FirstName and an ImageName, I saved it in the Auth Ticket like this :
UserData = pModel.PartyId.ToString() + "|" + pModel.BusinessName + "|" + pModel.FirstName + "|" + pModel.LastName + "|" + pModel.ImageUrl + "|" + UsersRole + "|" + IsAct;//PID, BusName, FirstName, LastName, imgUrl, Role, IsAct
// Create the cookie that contains the forms authentication ticket
HttpCookie authCookie = FormsAuthentication.GetAuthCookie(UN, true);
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, UserData);
authCookie.Value = FormsAuthentication.Encrypt(newTicket);
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);
I then retreive this Cookie when the data is needed and get the data out of it like this :
var cookie = context.Request.Cookies[FormsAuthentication.FormsCookieName];
dynamic UN = FormsAuthentication.Decrypt(cookie.Value);
string UserData = UN.UserData;//PID, BusName, FirstName, LastName, imgUrl, Role, IsAct
string[] pFields = UserData.Split('|');
string[] MyRoles = { pFields[5] };
Please Note : This is only good for static data that you know wont change during the login session & be careful what you post in this Cookie.
Don't bloat the cookie either keep the fields to a minimum, max is 4K but I aim for 500-1000 bytes.

Cache the common data values in your repository layer.

You can use System.Runtime.Caching.MemoryCache for this purpose.
Some examples:
http://stevescodingblog.co.uk/net4-caching-with-mvc/
http://msprogrammer.serviciipeweb.ro/tag/memorycache/
The cache will persist across requests until the expiration for each cache item is reached.

Inheriting from a base controller does not mean that you have shared state across requests. Each request will result in a new controller instance.
You need to store this data in some state (cache, session, application) if you don't want to retrieve it every time. Then you can make whatever mechanism you store it in accessible from your base controller.
Then there's the disclaimer...are these trips to the database expensive? Are you going to trade minimal latency problems for memory management issues?
UPDATE:
If it is just 4-5 fields (which appear to be user specific, and like they are not going to change during a user's session), then I'd just store them in session state.
Personal preference here, but I like to have strongly typed accessors in base controllers/pages for things like this:
protected string Name
{
get
{
if (Session["Name"] == null)
{
var results = GoLoadFields();
return Session["Name"].ToString();
}
return Session["Name"].ToString();
}
set
{
Session["Name"] = value;
}
}
Then in all of your controllers that inherit from your base controller can just reference these properties:
myAwesomeViewModel.Name = this.Name;
The memory management disclaimer is meant to have you avoid querying the database, getting the same large result set, and shoving it in the session for each user. Before you keep anything around in memory, just make sure you're only keeping what you need and for how long you need it.

Related

Changing HttpContext.Current.User.Identity.Name

During SetAuthCookie part we are manipulating user name slightly like this
string userInfo = identity.Name + "|" + Util.GetIPAddress();
FormsAuthentication.SetAuthCookie(userInfo, isPersistent);
this is in order to make some checks with users IP Address inside Application_AuthenticateRequest
Later on I want to revert the name back to its normal (without "|" and IP address) but couldn't find a way to do it.
Questions I came across generally handled the user name not updating correctly but what I need is to reassign the name.
I tried to set a new cookie and set a new Authcookie but they didnt work, HttpContext.Current.User.Identity.Name doesn't change.
How can I do this?
It is a better aproach to build your own authentication cookie to add the custom values you need, that way you keep the username unchanged wich is more consistent and the expected behavior.
Take into account doing this, you have the userdata (the ip) encrypted in the cookie.
var cookie = FormsAuthentication.GetAuthCookie(name, rememberMe);
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration,ticket.IsPersistent, userData, ticket.CookiePath);
var encTicket = FormsAuthentication.Encrypt(newTicket);
cookie.Value = encTicket;
//and add the cookie to the current HttpContext.Response
response.Cookies.Add(cookie);
Additionaly, you can retrieve this userData back from the current User.Identity
var data = (HttpContext.Current?.User.Identity as FormsIdentity)?.Ticket.UserData

ASP.NET Identity 2 and Anonymous Users

In our developing e-commerce solution we are using AspNet Identity 2.2.1 and it is required that any guest (anonymous) users should complete checkout without prior registration to the website. In order to fullfill this requirement have written an ActionFilter named UserMigrationAttribute which obtains SessionTrackId (string GUID) from cookie -which we set from a HttpModule for every request if SessionTrackId is not found along with request cookies- and creates and actual IdentityUser in database with the username something like SessionTrackId#mydomain.com.
We have decorated our BaseController class with this UserMigration attribute in order to utilize its functions throughout the site.
Everything up to this point works as expected with single downside issue, which is when the page is being loaded for the first time for any user, if we try to make an Jquery Ajax Call to a Method which have [ValidateAntiForgeryToken] attribute, the call fails with the 'The provided anti-forgery token was meant for a different claims-based user than the current user.' error, even though we are sending __RequestVerificationToken parameter with every ajax call.
But if user opens another page by clicking link and/or reloads/refreshes current page, all the subsequent ajax calls complete successfully.
In our understanding UserMigrationAttribute creates user on OnActionExecuting method, but after we signIn user in the process #Html.AntiForgeryToken() is not being updated with the right values.
You may find the UserMigrationAttribute code below;
[AttributeUsage(AttributeTargets.Class)]
public class UserMigrationAttribute : ActionFilterAttribute
{
public ApplicationSignInManager SignInManager(ActionExecutingContext filterContext)
{
return filterContext.HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
public UserManager UserManager(ActionExecutingContext filterContext)
{
return filterContext.HttpContext.GetOwinContext().GetUserManager<UserManager>();
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CreateMigrateCurrentUser(filterContext);
base.OnActionExecuting(filterContext);
}
private static readonly object LockThis = new object();
private void CreateMigrateCurrentUser(ActionExecutingContext filterContext)
{
lock (LockThis)
{
var signInManager = SignInManager(filterContext);
var userManager = UserManager(filterContext);
var sessionTrackId = GetSessionTrackId(filterContext);
if (!filterContext.HttpContext.Request.IsAuthenticated)
{
if (!string.IsNullOrEmpty(sessionTrackId))
{
var username = string.Format("{0}#mydomain.com", sessionTrackId);
var user = userManager.FindByName(username);
if (user == null)
{
user = new User() {UserName = username, Email = username};
var result = userManager.Create(user);
userManager.AddToRole(user.Id, StringResources.AnonymousVisitorsGroup);
}
signInManager.SignIn(user, true, true);
}
}
else
{
if (!string.IsNullOrEmpty(sessionTrackId))
{
var username = string.Format("{0}#mydomain.com", sessionTrackId);
var user = userManager.FindByName(username);
if (user != null)
{
if (!HttpContext.Current.User.IsInRole(StringResources.AnonymousVisitorsGroup))
{
var targetUserId = HttpContext.Current.User.Identity.GetUserId<int>();
var service = new Service();
service.Users.MigrateUser(user.Id, targetUserId);
}
}
}
}
}
}
private string GetSessionTrackId(ActionExecutingContext filterContext)
{
var retVal = string.Empty;
if (filterContext.HttpContext.Request.Cookies["stid"] != null)
{
retVal = filterContext.HttpContext.Request.Cookies["stid"].Value;
}
return retVal;
}
}
Any help or suggestions are highly appreciated.
Thank you,
This is happening because the anti-forgery token is set in a cookie, which will not be updated until the next request. If you're manually signing a user in, you should also issue a redirect (even if to the same page they were already headed to), simply to ensure that the cookie data is correct. This normally happens naturally, as the sign in form will redirect to the URL that needed authorization after the user is signed in, thus negating the problem. Since you're not redirecting currently, the data is out of sync.
However, I have to say that this seems like a very poor solution to this particular use case. Creating some sort of temporary-type user and signing that user in to handle guest checkout creates an unnecessary glut of useless data in your database, at best, and leads to bugs and other issues like this one you're experiencing, at worst.
I also run an ecommerce site, and the way we handled guest checkout is incredibly simplistic. The checkout data is just stored in the session (email, shipping/billing address, etc.). We build a view model to handle the actual checkout where the data necessary for submitting the sale comes either from the user object, if they're logged in, or these session variables, if they aren't. If the user is neither logged in, nor has the requisite session variables set, then they are redirected to the onboarding form where billing/shipping, etc. is collected.
For other aspects like maintaining an anonymous cart, we use a permanent cookie with the cart identifier. If the user ends up creating an account, we associate the anonymous cart with their user, and then remove the cookie. This ensures that their cart survives past the session timeout and things like closing the browser, even if they're anonymous.
In other words, in all these things, no user object is actually needed. If it's there (user is logged in), great, we'll use it. Otherwise, we collect and persist the requisite information for checkout via other means.

.NET Web API + Session Timeout

I am creating a web service with web api controller. I want to be able to create a session and check the status of the session. I have the following:
Controller:
public string Get(string user, string pass)
{
bool loginValue = false;
loginValue = UserNamepassword(user, pass);
if (loginValue == true)
{
HttpContext.Current.Session.Add("Username", user);
//session["Username"] = user;
//session.Add("Username", user);
if ((string)HttpContext.Current.Session["Username"] != null)
{
HttpContext.Current.Session.Add("Time", DateTime.Now);
return "Username: " + (string)HttpContext.Current.Session["Time"] + (string)HttpContext.Current.Session["Username"];
}
return "Logged in but session is not availabe for " + (string)HttpContext.Current.Session["Username"];
}
else
return "Login failed for " + user;
}
WebConfig
public static void RegisterRoutes(RouteCollection routes)
{
var route = routes.MapHttpRoute(
name: "SessionApi",
routeTemplate: "api/{controller}/{user}/{pass}",
defaults: new { user = RouteParameter.Optional, pass = RouteParameter.Optional }
);
route.RouteHandler = new MyHttpControllerRouteHandler();
}
public class MyHttpControllerHandler: HttpControllerHandler, IRequiresSessionState
{
public MyHttpControllerHandler(RouteData routeData): base(routeData){ }
}
public class MyHttpControllerRouteHandler: HttpControllerRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MyHttpControllerHandler(requestContext.RouteData);
}
}
Global.asax.cs
WebApiConfig.RegisterRoutes(RouteTable.Routes);
When I run this code I keep on getting null reference in the session.
HttpContext.Current.Session.Add("Username", user);
//session["Username"] = user;
//session.Add("Username", user);
Does anyone knows why I cannot set the session variable to anything. It does not matter which method I use non of the three are working. The code was taking from another post.
This is where semantics often clouds the discussion. People confuse the Session object with statelessness. And often say: 'don't use session because it isn't stateless!'.
However they really mean that you should strive to have your the restful calls to be idempotent, meaning they don't change their behavior depending on whatever it is you do in the background.
Session, or the runtime-cache, or whatever it is you use to cache data, has no effect on your stateless design, because really, what's next? Your database is statefull too? And you shouldn't read data from that? Nonsense obviously; your underlying storage, if it's in-memory or on disk has no reflection on your state to the client.
So use, by all means, the session object as Ben Robinson pointed out. But never let the fact if something is IN session return a different result then when something is OUT of session.
This is by design in Web API because it is designed for creating restful web services. To be truly restful a service should not have any kind of state, i.e. /myserver/somendpoint/5 should have the same result for any request with a given verb.
However if that doesn't suit you, you can enable session in web API by adding following to global.asax.
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
Please, don't!
Web API is supposed to be stateless, RESTful, etc. By using State you're defeating its whole purpose.

C# Populate drop down based on data from cookie

I will see if I can explain this clearly enough. I have 2 web forms. One is a basic Forms Authentication login page and the other form displays tasks from multiple servers. I am creating a cookie that stores the UserID. Here is the code for my cookie:
FormsAuthenticationTicket tkt = new FormsAuthenticationTicket(1, txtUser.Text, DateTime.Now, DateTime.Now.AddMinutes(120), true, rdr.GetInt32(0).ToString(), FormsAuthentication.FormsCookiePath);
string hash = FormsAuthentication.Encrypt(tkt);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
On my other form, I have a drop down box that displays all servers by Server IP from the Servers table.
public void Populate()
{
SqlConnection myConnection1 = new SqlConnection(ConfigurationManager.ConnectionStrings["DBConnection"].ConnectionString);
myConnection1.Open();
SqlCommand cmd1 = new SqlCommand("SELECT ServerIP FROM Servers", myConnection1);
SqlDataReader dropReader;
dropReader = cmd1.ExecuteReader();
drpChoose.DataSource = dropReader;
drpChoose.DataTextField = "ServerIP";
drpChoose.DataValueField = "ServerIP";
drpChoose.DataBind();
}
I am calling Populate in Page Load. I have another table that stores permissions. It has UserID, ServerID, and Permission (read or execute). Let's say that UserID 1 is associated with only ServerID 1 which has an IP of 192.168.0.10. How can I get this one Server IP to display in the drop down? I am pretty sure if I pass the cookie into the second form that I can take the UserID from that but I do not know where to begin.
I apologize if I have not given enough information. I will provide more if need be.
Looks like you'll need to do a join to your permissions table something like
SELECT ServerIP from Servers s, Permissions p where p.serverid = s.serverid and p.userid = :userIdFromCookie
Then you'll need to pass in the user id from your cookie into the Populate method and use a DbParameter to pass the value into your Sql command.
something like (this is pseudocode by the way as I'm not at my dev machine)
cmd.AddInParameter(":userIdFromCookie",dbType.AnsiString, Request.Cookies["mycookie"]["userid"])

Storing more information using FormsAuthentication.SetAuthCookie

I am using aspx and c# for a setting a authentication cookie for a login.
FormsAuthentication.SetAuthCookie(UserName, True)
I want to store more information in the same cookie. Can I add values to this authentication cookie or do I have to use a second http cookie?
Basically I'm looking for away to store the User's Id so I may be able to access the database using the users table row key
Thanks,
Eden
You can add user data to the FormsAuthenticationTicket, then generate the cookie yourself.
There's an example in the the MSDN documentation for FormsAuthenticationTicket.
EDIT
Note that when creating the ticket, you need to set the timeout, which in general you will want to be the same as the value configured in web.config. Unfortunately, in the Framework 3.5 or earlier, the FormsAuthentication class does not expose this timeout publicly. For a workaround, use one of the techniques described in the response to this connect feedback item.
UPDATE
That Connect feedback item is no longer there, sadly. Wish you had briefly described what the techniques were.
Yes, it's a pity Microsoft has discarded historical Connect items. IIRC, the two techniques they suggested were:
Use WebConfigurationManager to read the relevant configuration section and get the timeout value.
Create a cookie using FormsAuthentication.GetAuthCookie, decrypt it using FormsAuthentication.Decrypt and inspect the generated FormsAuthenticationTicket.
Or upgrade to .NET 4.x where there is a FormsAuthentication.Timeout property.
See this question for more info
You can put whatever you want in the auth cookie as long as it's useful to you. That said, if you're putting sensitive information you should, at the very least, encrypt it, but I'd recommend against putting sensitive information there. You can do something like:
Forms.SetAuthCookie (UserName + "|" + UserId, true);
Then, whenever you need the username or the user id, it is there. Just load the cookie and parse out the values you need.
Again, I'd advise against doing this, especially as I have it presented above. That said, it is possible. You should create accessor methods to pull the data back out:
public int CurrentUserId
{
get
{
int userId = 0;
if (HttpContext.Current.Request.IsAuthenticated)
{
userId = Convert.ToInt32(HttpContext.Current.User.Identity.Name.Split('|')[1]);
}
return userId;
}
}
public string CurrentUserName
{
get
{
string userName = string.Empty;
if (HttpContext.Current.Request.IsAuthenticated)
{
userName = HttpContext.Current.User.Identity.Name.Split('|')[0];
}
return userName;
}
}
Yes it is smart to use "|" to put more info. If Microsoft have another overloaded method
public static void SetAuthCookie(String userName, bool createPersistentCookie, string userData)`
Then our life will be much easier, our code will be safer.
Pass that user ID as the userName param.
FormsAuthentication.SetAuthCookie(userId, True)
How are you securing your auth tickets?
You can store additional information in the UserData property of the FormsAuthenticationTicket:
using Newtonsoft.Json;
using System.Web;
using System.Web.Security;
public class LoggedInUser
{
public string FirstName { get; set; } = null;
public bool IsAdmin { get; set; } = false;
}
public static class Authentication
{
static void SignIn(
HttpContextBase context,
string emailAddress,
bool rememberMe,
LoggedInUser user = null)
{
var cookie = FormsAuthentication.GetAuthCookie(
emailAddress.ToLower(),
rememberMe);
var oldTicket = FormsAuthentication.Decrypt(cookie.Value);
var newTicket = new FormsAuthenticationTicket(
oldTicket.Version,
oldTicket.Name,
oldTicket.IssueDate,
oldTicket.Expiration,
oldTicket.IsPersistent,
JsonConvert.SerializeObject(user ?? new LoggedInUser()));
cookie.Value = FormsAuthentication.Encrypt(newTicket);
context.Response.Cookies.Add(cookie);
}
static void SignOut(HttpContextBase context)
{
FormsAuthentication.SignOut();
}
static LoggedInUser GetLoggedInUser()
{
if (HttpContext.Current.User?.Identity?.Name != null && HttpContext.Current.User?.Identity is FormsIdentity identity)
return JsonConvert.DeserializeObject<LoggedInUser>(identity.Ticket.UserData);
return new LoggedInUser();
}
}
Further Reading: https://learn.microsoft.com/en-us/aspnet/web-forms/overview/older-versions-security/introduction/forms-authentication-configuration-and-advanced-topics-cs#step-4-storing-additional-user-data-in-the-ticket

Categories

Resources