Ok, my previous question/setup had too many variables, so I'm stripping this down to it's bare bones components.
Given the code below using StructureMap3...
//IoC setup
For<HttpContextBase>().UseSpecial(x => x.ConstructedBy(y => HttpContext.Current != null ? new HttpContextWrapper(HttpContext.Current) : null ));
For<ICurrentUser>().Use<CurrentUser>();
//Classes used
public class CurrentUser : ICurrentUser
{
public CurrentUser(HttpContextBase httpContext)
{
if (httpContext == null) return;
if (httpContext.User == null) return;
var user = httpContext.User;
if (!user.Identity.IsAuthenticated) return;
UserId = httpContext.User.GetIdentityId().GetValueOrDefault();
UserName = httpContext.User.Identity.Name;
}
public Guid UserId { get; set; }
public string UserName { get; set; }
}
public static class ClaimsExtensionMethods
public static Guid? GetIdentityId(this IPrincipal principal)
{
//Account for possible nulls
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal == null)
return null;
var claimsIdentity = claimsPrincipal.Identity as ClaimsIdentity;
if (claimsIdentity == null)
return null;
var claim = claimsIdentity.FindFirst(x => x.Type == ClaimTypes.NameIdentifier);
if (claim == null)
return null;
//Account for possible invalid value since claim values are strings
Guid? id = null;
try
{
id = Guid.Parse(claim.Value);
}
catch (ArgumentNullException) { }
catch (FormatException) { }
return id;
}
}
How is this possible in the Watch window?
I have a web application that I'm upgrading to using StructureMap 3.x from 2.x, but I'm getting odd behavior on specific dependency.
I have a ISecurityService that I use to obtain verify some things when a user requests a page. This service depends on a small interface that I've called ICurrentUser. The class implementation is pretty plain, really it could be a struct.
public interface ICurrentUser
{
Guid UserId { get; }
string UserName { get; }
}
This is obtained via dependency injection using the below code.
For<ICurrentUser>().Use(ctx => getCurrentUser(ctx.GetInstance<HttpContextBase>()));
For<HttpContextBase>().Use(() => getHttpContext());
private HttpContextBase getHttpContext()
{
return new HttpContextWrapper(HttpContext.Current);
}
private ICurrentUser getCurrentUser(HttpContextBase httpContext)
{
if (httpContext == null) return null;
if (httpContext.User == null) return null; // <---
var user = httpContext.User;
if (!user.Identity.IsAuthenticated) return null;
var personId = user.GetIdentityId().GetValueOrDefault();
return new CurrentUser(personId, ClaimsPrincipal.Current.Identity.Name);
}
When a request comes in, my site wide authentication happens first, which depends on ISecurityService. This happens inside of OWIN and appears to occur before HttpContext.User has been populated, so it's null, so be it.
Later on, I have an ActionFilter that checks, via a ISecurityService, if the current user has agreed to the current version of the TermsOfUse for the site, if not they are redirected to the page to agree to them first.
This all worked fine in structuremap 2.x. For my migration to StructureMap3 I've installed the Nuget package StructureMap.MVC5 to help speed things up for me.
When my code gets to the line in my ActionFilter for checking the terms of use I have this.
var securityService = DependencyResolver.Current.GetService<ISecurityService>();
agreed = securityService.CheckLoginAgreedToTermsOfUse();
Inside of CheckLoginAgreedToTermsOfUse(), my instance of CurrentUser is null. Even though it would hazve succeeded, and my breakpoint inside of getCurrentUser() never seems to be hit. Its almost as if it's a foregone conclusion, since it was null the last time , even though it would have resolved this time.
I'm kind of baffled as to why getCurrentUser() is never called on the request for ISecurityService. I even tried explicitly sticking a .LifecycleIs<UniquePerRequestLifecycle>() on my hookup for handling ICurrentUser with no effect.
UPDATE:
Ok so just a heads up, I've started using the method accepted below, and while it has worked great so far, it didn't resolve my core problem. Turns out the new StructureMap.MVC5, based on StructureMap3, uses NestedContainers. Which scope their requests to the lifetime of the NestedContainer, regardless of the default being Transient. So when I requested HttpContextBase for the first time, it will then return that same instance for the rest of the request (even though later on in the request lifespan, the context has changed. You need to either not use NestedContainer (which, as I understand it will complicate things ASP.NET vNext), or you explicitly set the lifecycle of the For<>().Use<>() mapping to give you a new instance per request. Note that this scoping per NestedContainer causes problems with Controllers as well in MVC. While the StructureMap.MVC5 package handles this with a ControllerConvention, it does not handle Views, and recursive views or views used multiple times will likely cause you problems as well. I'm still looking for a permanent fix for the Views problem, for the moment I've reverted to the DefaultContainer.
I haven't worked with OWIN, but when hosting in IIS integrated mode the HttpContext is not populated until after the HttpApplication.Start event is complete. In terms of DI, this means that you cannot rely on using properties of HttpContext in any constructor.
This makes sense if you think about it because the application should be initialized outside of any individual user context.
To get around this, you could inject an abstract factory into your ICurrentUser implementation and to use a Singleton pattern to access it, which guarantees HttpContext won't be accessed until it is populated.
public interface IHttpContextFactory
{
HttpContextBase Create();
}
public class HttpContextFactory
: IHttpContextFactory
{
public virtual HttpContextBase Create()
{
return new HttpContextWrapper(HttpContext.Current);
}
}
public class CurrentUser // : ICurrentUser
{
public CurrentUser(IHttpContextFactory httpContextFactory)
{
// Using a guard clause ensures that if the DI container fails
// to provide the dependency you will get an exception
if (httpContextFactory == null) throw new ArgumentNullException("httpContextFactory");
this.httpContextFactory = httpContextFactory;
}
// Using a readonly variable ensures the value can only be set in the constructor
private readonly IHttpContextFactory httpContextFactory;
private HttpContextBase httpContext = null;
private Guid userId = Guid.Empty;
private string userName = null;
// Singleton pattern to access HTTP context at the right time
private HttpContextBase HttpContext
{
get
{
if (this.httpContext == null)
{
this.httpContext = this.httpContextFactory.Create();
}
return this.httpContext;
}
}
public Guid UserId
{
get
{
var user = this.HttpContext.User;
if (this.userId == Guid.Empty && user != null && user.Identity.IsAuthenticated)
{
this.userId = user.GetIdentityId().GetValueOrDefault();
}
return this.userId;
}
set { this.userId = value; }
}
public string UserName
{
get
{
var user = this.HttpContext.User;
if (this.userName == null && user != null && user.Identity.IsAuthenticated)
{
this.userName = user.Identity.Name;
}
return this.userName;
}
set { this.userName = value; }
}
}
Personally, I would make the UserId and UserName properties readonly, which would simplify the design and ensure they don't get hijacked elsewhere in the application. I would also make an IClaimsIdentityRetriever service that is injected into the constructor of ICurrentUser instead of retrieving the claims Id in an extension method. Extension methods go against the grain of DI and are generally only useful for tasks that are guaranteed not to have any dependencies (such as string or sequence manipulation). The loose coupling of making it a service also means you can easily swap or extend the implementation.
Of course, this implies that you cannot call the UserId or UserName properties of your CurrentUser class in any constructor as well. If any other class depends on ICurrentUser, you may also need an ICurrentUserFactory in order to safely use it.
Abstract factory is a lifesaver when dealing with difficult-to-inject dependencies and solves a host of problems including this one.
Related
I am using ninject in my mvc application with AspNet Identity
In my global asax I have the following line of code
kernel.Bind<IIdentityManager>().To<IdentityManager>().InRequestScope().WithConstructorArgument("conn", idConnString);
Whenever a new request is made to the controller this therefore should create a new instance of the above class.
However this is not happening. Break points show that the constructor for the above class is only called once when the website loads and is never being hit again when I refresh the page or make a new request (it is injected into a class that is called on every page request to check the claims and thus authorise or deny the user access to the page they requested). Because of this the db connection context which underpins the identitymanager remains the same and never reloads on request. Thus it never picks up any changes to the claims that have happened externally to the website which is what would happen if a new instance of IdentityManager was created on each request as I would expect it to given the above binding.
Any advice would be greatly appreciated.
EDIT to add more code. (note I've removed code not relating to this particular problem to make it shorter)
My Global.asax
protected override IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Load(Assembly.GetExecutingAssembly());
kernel.Bind<IVariables>().ToConstructor<Variables>(context => new Variables(AppSettingKeys.NRDAWebServer)).InRequestScope();
var variables = kernel.Get<IVariables>();
var masterPassword = VBHelperCore.Encryptor.EncDec.Decrypt(File.ReadAllBytes(AppSettingKeys.ConfigSettingsKeys.ConfigLocation + AppSettingKeys.SQLConnection.NRDAMasterUserName),
AppSettingKeys.HexString);
var s3ConnString = #"Data Source=" + variables.Get(AppSettingKeys.SQLConnection.NRDAMasterSQLServer, null) + #";Initial Catalog=" + variables.Get(AppSettingKeys.SQLConnection.NRDAMasterDB, null) +
";User ID=" + variables.Get(AppSettingKeys.SQLConnection.NRDAMasterUserName, null) + ";Password=" +
masterPassword +
";Trusted_Connection=False;MultipleActiveResultSets=True;";
var idConnString = s3ConnString;
kernel.Bind<IAuthenticationFilter>().To<NrdaAuthenticationAttribute>().InRequestScope();
kernel.Bind<IIdentityManager>().To<IdentityManager>().InRequestScope().WithConstructorArgument("conn", idConnString);
kernel.Bind<INrdaClaimsTransformer>().To<NrdaClaimsTransformer>().InRequestScope();
GlobalConfiguration.Configuration.DependencyResolver = new Ninject.WebApi.DependencyResolver.NinjectDependencyResolver(kernel);
return kernel;
}
This essentially loads configuration variables to get the connection string with passwords and then binds a new instance of IdentityManager with the connection string passed to its constructor.
I have defined an ActionFilter
public class NrdaAuthenticationAttribute : ActionFilterAttribute, IAuthenticationFilter
{
[Inject]
public INrdaClaimsTransformer ClaimsTransformer{ private get; set; }
[Inject]
public ILogger Log { get; set; }
public void OnAuthentication(AuthenticationContext filterContext)
{
try
{
var user = filterContext.HttpContext.User;
if ((user != null && user.Identity.IsAuthenticated))
{
ClaimsPrincipal currentPrincipal = ClaimsPrincipal.Current;
ClaimsPrincipal tranformedClaimsPrincipal = ClaimsTransformer.Authenticate(string.Empty, currentPrincipal);
Thread.CurrentPrincipal = tranformedClaimsPrincipal;
HttpContext.Current.User = tranformedClaimsPrincipal;
}
else
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
catch (SecurityException ex)
{
Log.Error(ex, "Security error" + ex.Message, null);
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
}
}
All the controllers then have so this filter is called everytime you access any controller (so on every request)
[NrdaAuthentication]
The filter uses the kernel bound Claims Transformer
public class NrdaClaimsTransformer : ClaimsAuthenticationManager, INrdaClaimsTransformer
{
[Inject]
public IIdentityManager IdentityManager { private get; set; }
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
var ret = !incomingPrincipal.Identity.IsAuthenticated
? base.Authenticate(resourceName, incomingPrincipal)
: DressUpPrincipal(incomingPrincipal);
return base.Authenticate(resourceName, ret);
}
private ClaimsPrincipal DressUpPrincipal(ClaimsPrincipal incomingPrincipal)
{
var user = IdentityManager.GetByUsername(incomingPrincipal.Identity.Name);
return null //code removed to simplify example
}
}
Obviously I've cut a lot of code out to simplify but due to the ninject bindings all being set to InRequestScope() I would expect the constructor of IdentityManager to be called on each new request. It however isn't. A breakpoint shows it's only called when the website first starts and never again after hence it must be using the same instance with every web request which it shouldn't
EDIT 2
Ok I think I've found the problem. It seems because that InRequestScope doesn't work within a custom authorisation attribute. Or indeed any of the other scope functions I've tried. I assume this is because the attribute instance is loaded into memory for the user's session and thus the injected class never expires so it doesn't need to be reloaded by ninject. When I do a test by calling the injected class using a controller and in the action it works fine. For now I will just need to load a new instance of the identitymanager each time in the attribute instead of the ninject version
I want to use the IoC container in a method to check a logged in users company code when they submit a payment. I have two certificates in my settings class and an IF else statement to differentiate between each one.
public static string FDGCreditCardUserID
{
get
{
if (BillingController.currentcompanycode == 5)
return ConfigurationManager.AppSettings["5FDGCreditCardUserID"];
else
return ConfigurationManager.AppSettings["6FDGCreditCardUserID"];
}
}
public static string FDGCreditCardPassword
{
get
{
if (BillingController.currentcompanycode == 5)
return ConfigurationManager.AppSettings["5FDGCreditCardPassword"];
else
return ConfigurationManager.AppSettings["6FDGCreditCardPassword"];
}
}
Then in my IoC container
x.For<IFDGService>().Use<FDGService>().SetProperty(s =>
{
s.Url = Settings.FDGURL;
s.UserID = Settings.FDGCreditCardUserID;
s.Password = Settings.FDGCreditCardPassword;
s.Certificate = Settings.FDGCreditCardCertFilePath;
});
I have an FDGService that checks credentials but does not return to the IoC on payment submit to check the company code and apply the correct certificate.
SubmitPayment Method where the creditcard control contains the correct company code when i run it.
How do i get my application to select the correct certificate based on the updated company code. Seeing as users can have different company codes based on policies selected for payment. One company code at the moment can either be 5 or 6.
public ActionResult SubmitPayment([ConvertJSON]List<PayModel> payments)
{
List<TransactionModel> transactions = new List<TransactionModel>();
foreach (var pymt in payments)
{
var policyNumber = pymt.PolicyNumber.Trim();
TransactionModel trans = new TransactionModel() { Payment = pymt };
if (pymt.Selected)
{
var creditCardControl = UpdateCreditCardControl(policyNumber);
If you are using StructureMap it uses "Greedy Initialization", meaning when the constructor is called it will call the constructor with the most amount of arguments or parameters passed in.
private IFDGService service;
public MyController(IFDGService service)
{
this.service = service;
}
Then service will be available after IoC.Configure() is called.
Call IoC.Configure() whereever the application is started. google "where does Mvc start" or something like that.
to change the company code set it somewhere other than an instance variable in the controller, like a static class, I know static is bad, get it working and then make it better, since that would be complex to modify, and then get; set; when you need to.
I have to go to meeting, kinda rushed, hope that helps
I've been searching the web for this, and couldn't really find a solution that actually worked. Situation is as follows: I've got a WPF application, where I want to present the user with a simple logon form. Trying to work MVVM, so I've got a LoginViewModel with the following code behind the login command:
try
{
WithClient(servfact.GetServiceClient<IAccountService>(), proxy =>
{
principal = proxy.AuthenticateUser(Login, password);
});
Thread.CurrentPrincipal = principal;
}
catch(...) { ... }
"WithClient" is a short method in my viewmodel baseclass, which I use to instantiate and dispose of my service proxies:
protected void WithClient<T>(T proxy, Action<T> codeToExecute)
{
try { codeToExecute(proxy); }
finally
{
IDisposable toDispose = (proxy as IDisposable);
if(toDispose != null) { toDispose.Dispose(); }
}
}
Now, most of my services are Async, and I've got an async variant of WithClient going on, which also works fine:
protected async Task WithClientAsync<T>(T proxy, Func<T, Task> codeToExecute)
{
try { await codeToExecute(proxy); }
finally
{
IDisposable toDispose = (proxy as IDisposable);
if(toDispose != null) { toDispose.Dispose(); }
}
}
The trouble begins whenever I also want to do the login asynchronously. Obviously I don't want the UI to freeze up as I do the login (or visit any WCF service for that matter). That in itself is working fine, but the problem sits in the piece of code where I set the CurrentPrincipal. This problem is probably familiar to most of you: it seems to set it just fine. Then in my program I want to use the CurrentPrincipal (either on the client side or to send the users login to a WCF service in a messageheader), but it seems to be reset to a standard GenericPrincipal. When I revert the login back to being synchronous, the CurrentPrincipal is just fine. So in short: how do I set the principal in the asynchronous code, having it persist later on, instead of reverting back to a standard principal?
Well, well, no answer in a year. No worries, since I managed to solve this myself: I simply wrapped a singleton around it all:
public sealed class CurrentPrincipalFacade : IPrincipal
{
#region Singleton mechanism
private static readonly CurrentPrincipalFacade instance = new CurrentPrincipalFacade();
public static CurrentPrincipalFacade Instance { get { return instance; } }
private CurrentPrincipalFacade() { }
#endregion
#region IPrincipal members
public IPrincipal Principal { get; set; }
public IIdentity Identity { get { return Principal == null ? null : Principal.Identity; } }
public bool IsInRole(string role) { return Principal != null && Principal.IsInRole(role); }
public void Reset() { Principal = new GenericPrincipal(new GenericIdentity(""), new string[] { }); }
#endregion}
So I set that after login. I guess the problem was I was setting the principal in another thread, which got lost when I got out of that?
I am using the repository and unit of work patterns and dependency injection to access the database with entity framework 5 in my web application. I have a User class from which Entity Framework generates a Code-First database.
public class User
{
public Guid UserId { get; set; }
public string UserName { get; set; }
.
.
.
public string LanguagePreference { get; set; }
public virtual List<Role> Roles { get; set; }
public virtual List<Branch> Branches { get; set; }
}
I have a UserService class that is used to Add or Update users. This class takes an IUserUnitOfWork as a parameter in the constructor and Unity injects a UserUnitOfwork. The IUserUserOfWork contains an IRepository<User>, an IRepository<Location> and an IRepository<Role>. These are set as Repository<T> by the DI bootstrapper. The IUserUnitOfWork sets up the different Repositories with the same entity framework DbContext. I did this as I was having issues updating the many-to-many relationships related to the User (Locations and Roles).
UserUnitOfWork:
public IRepository<Branch> BranchRepository {get; set;}
public IRepository<Role> RoleRepository { get; set; }
public IRepository<User> UserRepository { get; set; }
public DbContext Context { get; set; }
public UserUnitOfWork(DbContext context, ITransientErrorDetectionStrategy errorDetectionStrategy,RetryStrategy retryStrategy )
{
Context = context;
BranchRepository = new Repository<Branch>(context, errorDetectionStrategy, retryStrategy);
RoleRepository = new Repository<Role>(context, errorDetectionStrategy, retryStrategy);
UserRepository = new Repository<User>(context, errorDetectionStrategy, retryStrategy);
}
The Repository class then uses Entity Framework 5 to access the database.
Example of method from Repository.FirstOrDefault:
public virtual T FirstOrDefault(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "")
{
T result = null;
_retryPolicy.ExecuteAction(() =>
{
IQueryable<T> entities = GetHelper(filter, orderBy, includeProperties);
result = entities.FirstOrDefault();
});
return result;
}
And Update from Repository:
public virtual void Update(T entity)
{
if (_dbContext.Entry(entity).State == System.Data.EntityState.Detached)
{
_dbContext.Set<T>().Attach(entity);
_dbContext.Entry(entity).State = System.Data.EntityState.Modified;
}
}
So my problem now is that when I update the User it correctly updates the data in the database, and when I log out and log in the initial change works. However if I update again and log out and in the new change isn't picked up even though the database is updated.
I'm beginning to fear that the approach I've taken is incorrect, can someone tell me how to make sure that when I do an update Entity Framework will always get the latest version?
EDIT:
So I've created a Per Request Lifetime Manager like so:
public class PerHttpRequestLifetimeManager : LifetimeManager
{
private readonly object key = new object();
public override object GetValue()
{
if (HttpContext.Current != null &&
HttpContext.Current.Items.Contains(key))
return HttpContext.Current.Items[key];
else
return null;
}
public override void RemoveValue()
{
if (HttpContext.Current != null)
HttpContext.Current.Items.Remove(key);
}
public override void SetValue(object newValue)
{
if (HttpContext.Current != null)
HttpContext.Current.Items[key] = newValue;
}
}
In my DI bootstrapper I now setup my domain context like below:
container.RegisterType<DbContext, DomainContext>(new PerHttpRequestLifetimeManager());
It still doesn't appear to be working, am I missing something else or am I setting it up incorrectly?
EDIT 2:
Just to point out the architecture:
We have an MVC application which uses Angular JS to make ajax calls to a Web Api service layer. The Web Api has an ISomethingService injected into it. It is this ISomethingService that has the repositories injected into it. Would there be some confusion for the PerHttpRequestLifetimeManager since there is both an MVC and Web API project running?
EDIT 3:
An example of how I am saving the edited user:
We have a UserModel class that is used for communications between the ServiceLayer -> API -> UI layer and back. The User class is the one generated by Entity Framework code first. The EditUser method in the UserService takes in a UserModel.
I then user the _unitOfWork.UserRepository to get the corresponding database user
var editedUser = _unitOfWork.UserRepository.FirstOrDefault(x => x.UserId == userModel.UserId);
I map the fields from the userModel to the editedUser and I then call (in the UserService)
_unitOfWork.UserRepository.Update(editedUser)
and after
_unitOfWork.Save()
YET ANOTHER EDIT:
So I have edited a simple method that updates a single text field on the user table (Language Preference). I explicitly call the dispose method after the update to ensure I am disposing the method.
public void SetUserLanguagePreference(Guid userId, string language)
{
var user = _unitOfWork.UserRepository.FirstOrDefault(x => x.UserId == userId);
user.LanguagePreference = language;
_unitOfWork.UserRepository.Update(user);
_unitOfWork.Save();
_unitOfWork.Dispose();
}
UnitOfWork.Dispose() calls the dispose method of the repositories and the Dbcontext
The database updates correctly. However the behaviour is still incorrect. When I log out and in first it retrieves the correct value. When I change it again and log out and in again it doesn't update. This has been the pattern before, it get the first update after I log out and in, but if I change again and log out and in it doesn't pick it up.
Finally, not an edit but an answer! We use Claims based authentication and have a class that overrides the ClaimsPrinciple Authenticate method that is called whenever a user is authenticated.
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (incomingPrincipal.Identity.IsAuthenticated)
{
//Check here if the authenticated user has access to this system
//using the user repository and if so add more claims to the token
}
return base.Authenticate(resourceName, incomingPrincipal);
}
It was not possible to inject into this method using DI as it always went to the empty constructor (not sure why but that's the way it is).
So instead we were setting the repository in the empty constructor like so:
public PRAuthenticationManager()
{
_userRepository = DiBootstrapper.Container.Resolve<IRepository<User>>();
}
When the Authenticate method is called we check our database for a user with the claims attached to the ClaimsPrincipal. If we make a match we add new claims to the token which are then used for each call to the Web Api later. This repository was not being disposed (even if all the others were) and so when a user logged out and in they got data from that same context which had not been disposed from the last time the user logged in.
Three full days trying to find that one....
See if this helps: How do I get Entity Framework 5 to update stale data
I ran into the same problem, it doesn't refresh from the database if you already have the object in your ObjectContext, of course, this would only work on a per object basis, but that might be just what you need.
Despite the fact that I've been on here for a while, this is my first ever question on SO, so please be gentle with me.
I'm using ASP.NET MVC 3 and I want to create a custom Principal so I can store a bit more info about the current user than is standard thus not have to go to the database too often. It's fairly standard stuff that I'm after. Let's just say email address and user id in the first instance.
I have decided to store the object in the cache as I am aware that it is not advised to store it in the session.
I also don't want to have to keep casting the User object, so I wanted to override the User object in the controller. So I can just go User.UserId and be guaranteed of something.
So I created a custom principal like this:
public class MyPrincipal : IPrincipal
{
public MyPrincipal(IIdentity ident, List<string> roles, string email, Guid userId)
{
this._identity = ident;
this._roles = roles;
this._email = email;
this._userId = userId;
}
IIdentity _identity;
public IIdentity Identity
{
get { return _identity; }
}
private List<string> _roles;
public bool IsInRole(string role)
{
return _roles.Contains(role);
}
private string _email;
public string Email
{
get { return _email; }
}
private Guid _userId;
public Guid UserId
{
get { return _userId; }
}
}
And I have a Base Controller like this:
public class BaseController : Controller
{
protected virtual new MyPrincipal User
{
get
{
if (base.User is MyPrincipal)
{
return base.User as MyPrincipal;
}
else
{
return new MyPrincipal(base.User.Identity, new List<string>(0), "", Guid.Empty );
}
}
}
protected override void OnAuthorization(AuthorizationContext filterContext)
{
if (User != null)
{
if (User.Identity.IsAuthenticated)
{
if (User.Identity is FormsIdentity)
{
FormsIdentity id = base.User.Identity as FormsIdentity;
MyPrincipal principal = (MyPrincipal)filterContext.HttpContext.Cache.Get(id.Name);
if (principal == null)
{
MembershipUser user = Membership.GetUser();
// Create and populate your Principal object with the needed data and Roles.
principal = new MyPrincipal(id, Roles.GetRolesForUser(id.Name).ToList(), user.Email, (Guid)user.ProviderUserKey);
filterContext.HttpContext.Cache.Add(
id.Name,
principal,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
new System.TimeSpan(0, 30, 0),
System.Web.Caching.CacheItemPriority.Default,
null);
}
filterContext.HttpContext.User = principal;
System.Threading.Thread.CurrentPrincipal = principal;
base.OnAuthorization(filterContext);
}
}
}
}
}
If you have a look you will quickly realise that if the user has not logged in then any call to the User object will have to run through this bit of code:
return new MyPrincipal(base.User.Identity, new List<string>(0), "", Guid.Empty );
and this feels terribly inefficient to me, although it's only creating empty objects for the missing stuff.
It works fine.
So I guess I want to know if this is actually okay and I should stop being so anal about performance and efficiency, or if my fears are correct, in which case what should I be doing instead? [Please don't say "Getting a life, mate!"]
No - there is nothing specifically wrong with this code from a performance stand point that stands out. PLENTY of objects are creating on the back end in ASP.NET, your single object is a drop in the bucket. Since class instantiation is extremely fast I wouldn't be concerned about it.
Why are you ignoring sessions here? Session information doesn't have expiration dates, so there is no extra check behind the scenes. Unless you are using an out of proc session server, there is no serialization of your object (none with the cache either).
The cache is for every user - so you right a chance (albeit slight) of a code error returning the wrong principal where a cache being per user - does not run the risk of that.
If you want this available for all requests there (not just MVC based) I would consider setting this in Application_PostAuthenticateRequest
This post may be of use. Notice the use of userdata in the authentication ticket.
ASP.NET MVC - Set custom IIdentity or IPrincipal