Here's how I do it.
I decided to use IPrincipal instead of IIdentity because it means I don't have to implement both IIdentity and IPrincipal.
Create the interface
interface ICustomPrincipal : IPrincipal
{
int Id { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
}
CustomPrincipal
public class CustomPrincipal : ICustomPrincipal
{
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) { return false; }
public CustomPrincipal(string email)
{
this.Identity = new GenericIdentity(email);
}
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
CustomPrincipalSerializeModel - for serializing custom information into userdata field in FormsAuthenticationTicket object.
public class CustomPrincipalSerializeModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
LogIn method - setting up a cookie with custom information
if (Membership.ValidateUser(viewModel.Email, viewModel.Password))
{
var user = userRepository.Users.Where(u => u.Email == viewModel.Email).First();
CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
serializeModel.Id = user.Id;
serializeModel.FirstName = user.FirstName;
serializeModel.LastName = user.LastName;
JavaScriptSerializer serializer = new JavaScriptSerializer();
string userData = serializer.Serialize(serializeModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
viewModel.Email,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
return RedirectToAction("Index", "Home");
}
Global.asax.cs - Reading cookie and replacing HttpContext.User object, this is done by overriding PostAuthenticateRequest
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JavaScriptSerializer serializer = new JavaScriptSerializer();
CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
newUser.Id = serializeModel.Id;
newUser.FirstName = serializeModel.FirstName;
newUser.LastName = serializeModel.LastName;
HttpContext.Current.User = newUser;
}
}
Access in Razor views
@((User as CustomPrincipal).Id)
@((User as CustomPrincipal).FirstName)
@((User as CustomPrincipal).LastName)
and in code:
(User as CustomPrincipal).Id
(User as CustomPrincipal).FirstName
(User as CustomPrincipal).LastName
I think the code is self-explanatory. If it isn't, let me know.
Additionally to make the access even easier you can create a base controller and override the returned User object (HttpContext.User):
public class BaseController : Controller
{
protected virtual new CustomPrincipal User
{
get { return HttpContext.User as CustomPrincipal; }
}
}
and then, for each controller:
public class AccountController : BaseController
{
// ...
}
which will allow you to access custom fields in code like this:
User.Id
User.FirstName
User.LastName
But this will not work inside views. For that you would need to create a custom WebViewPage implementation:
public abstract class BaseViewPage : WebViewPage
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
Make it a default page type in Views/web.config:
<pages pageBaseType="Your.Namespace.BaseViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
and in views, you can access it like this:
@User.FirstName
@User.LastName
In classic (legacy) ASP, there are a handful of special function names that, if defined in your global.asa file, will be run at specified points during the application lifecycle. These are defined as:
- Application_OnStart - runs once, when your application receives the first HTTP request and immediately before any .ASP files are processed.
- Application_OnEnd - runs once, during application shutdown, after all requests have been processed.
- Session_OnStart - runs at the start of each unique user session. If a user/client has cookies disabled, this runs for every request because ASP never detects the session cookie identifying an existing session.
- Session_OnEnd - (theoretically!) runs each time a user session expires. Good luck with this.
These are basically hard-wired into the classic ASP runtime - you can't change them, and you can't attach any other methods to these events.
In ASP.NET, there's a thing called AutoEventWireup
that uses reflection to find methods conforming to particular naming conventions, and runs those methods in response to matching events raised by the ASP.NET runtime. The most common example is the Page_Load
method, which is automatically invoked in response to the Page class firing the Load event during the page lifecycle.
The same technique is used to attach handlers to application-level lifecycle events. It will look for methods named either ModuleName_EventName or ModuleName_OnEventName, taking either no parameters ()
or (object sender, EventArgs e)
Here's the fun part - if you define more than one matching method, only the one that appears latest in the file will execute. (The last method wins, basically)
So if your global.asax.cs looks like this:
public class Global : System.Web.HttpApplication {
protected void Application_Start() {
Debug.WriteLine("A: Application_Start()");
}
protected void Application_Start(object sender, EventArgs e) {
Debug.WriteLine("B: Application_Start(object sender, EventArgs e)");
}
protected void Application_OnStart() {
Debug.WriteLine("C: Application_OnStart()");
}
protected void Application_OnStart(object sender, EventArgs e) {
Debug.WriteLine("D: Application_OnStart(object sender, EventArgs e)");
}
}
you'll see message D in your debug output; if you comment out the last method in that block, you'll see message C instead.
So - use whichever naming convention you like, but if you define more than one, only the one that appears last in your source file will be executed. I would personally stick with Application_Start(object sender, EventArgs e)
since that's the signature generated by the Visual Studio project templates and most .NET design/coding tools.
Best Solution
I found an answer to my question.
In loggin_in event I should save authentication cookie (I can store all information that I later need in my customPrincipal in UserData property) and in Application_PostAuthenticateRequest I should create CustomPrincipal from that cookie. That way this event fires every request but I don't hit database - I read data from cookie.
I followed http://www.ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html
In my case code is: