NInject, nHibernate, and auditing in ASP.NET MVC - asp.net-mvc

I am working on an inherited application which makes use of NInject and nHibernate as part of an ASP.NET MVC (C#) application. Currently, I'm looking at a problem with the auditing of modifications. Each entity has ChangedOn/ChangedBy and CreatedOn/CreatedBy fields, which are mapped to database columns. However, these either get filled with the wrong username or no username at all. I think this is because it has been configured in the wrong way, but I don't know enough about nHibernate and NInject to solve the issue, so I hope someone can help. Below some code snippets to hopefully provide sufficient insight in the application.
Creating the session factory and session:
public class NHibernateModule : NinjectModule
{
public override void Load()
{
Bind<ISessionFactory>().ToProvider(new SessionFactoryProvider()).InSingletonScope();
Bind<ISession>().ToProvider(new SessionProvider()).InRequestScope();
Bind<INHibernateUnitOfWork>().To<NHibernateUnitOfWork>().InRequestScope();
Bind<User>().ToProvider(new UserProvider()).InRequestScope();
Bind<IStamper>().ToProvider(new StamperProvider()).InRequestScope();
}
}
public class SessionProvider : Provider<ISession>
{
protected override ISession CreateInstance(IContext context)
{
// Create session
var sessionFactory = context.Kernel.Get<ISessionFactory>();
var session = sessionFactory.OpenSession();
session.FlushMode = FlushMode.Commit;
return session;
}
}
public class SessionFactoryProvider : Provider<ISessionFactory>
{
protected override ISessionFactory CreateInstance(IContext context)
{
var connectionString = ConfigurationManager.ConnectionStrings["DefaultConnectionString"].ToString();
var stamper = context.Kernel.Get<IStamper>();
return NHibernateHelper.CreateSessionFactory(connectionString, stamper);
}
}
public class StamperProvider : Provider<IStamper>
{
protected override IStamper CreateInstance(IContext context)
{
System.Security.Principal.IPrincipal user = HttpContext.Current.User;
System.Security.Principal.IIdentity identity = user == null ? null : user.Identity;
string name = identity == null ? "Unknown" : identity.Name;
return new Stamper(name);
}
}
public class UserProvider : Provider<User>
{
protected override UserCreateInstance(IContext context)
{
var userRepos = context.Kernel.Get<IUserRepository>();
System.Security.Principal.IPrincipal user = HttpContext.Current.User;
System.Security.Principal.IIdentity identity = user == null ? null : user.Identity;
string name = identity == null ? "" : identity.Name;
var user = userRepos.GetByName(name);
return user;
}
}
Configuring the session factory:
public static ISessionFactory CreateSessionFactory(string connectionString, IStamper stamper)
{
// Info: http://wiki.fluentnhibernate.org/Fluent_configuration
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(connectionString))
.Mappings(m =>
{
m.FluentMappings
.Conventions.Add(PrimaryKey.Name.Is(x => "Id"))
.AddFromAssemblyOf<NHibernateHelper>();
m.HbmMappings.AddFromAssemblyOf<NHibernateHelper>();
})
// Register
.ExposeConfiguration(c => {
c.EventListeners.PreInsertEventListeners =
new IPreInsertEventListener[] { new EventListener(stamper) };
c.EventListeners.PreUpdateEventListeners =
new IPreUpdateEventListener[] { new EventListener(stamper) };
})
.BuildSessionFactory();
}
Snippet from the eventlistener:
public bool OnPreInsert(PreInsertEvent e)
{
_stamper.Insert(e.Entity as IStampedEntity, e.State, e.Persister);
return false;
}
As you can see the session factory is in a singleton scope. Therefore the eventlistener and stamper also get instantiated in this scope (I think). And this means that when the user is not yet logged in, the username in the stamper is set to an empty string or "Unknown".
I tried to compensate for this problem, by modifying the Stamper. It checks if the username is null or empty. If this is true, it tries to find the active user, and fill the username-property with that user's name:
private string GetUserName()
{
if (string.IsNullOrWhiteSpace(_userName))
{
var user = ServiceLocator.Resolve<User>();
if (user != null)
{
_userName = user.UserName;
}
}
return _userName;
}
But this results in a completely different user's name, which is also logged in to the application, being logged in the database. My guess this is because it resolves the wrong active user, being the last user logged in, instead of the user that started the transaction.

The offending parts are here:
Bind<ISessionFactory>().
.ToProvider(new SessionFactoryProvider())
.InSingletonScope();
Bind<IStamper>()
.ToProvider(new StamperProvider())
.InRequestScope();
And later on:
public class SessionFactoryProvider : Provider<ISessionFactory>
{
protected override ISessionFactory CreateInstance(IContext context)
{
// Unimportant lines omitted
var stamper = context.Kernel.Get<IStamper>();
return NHibernateHelper.CreateSessionFactory(connectionString, stamper);
}
}
public class StamperProvider : Provider<IStamper>
{
protected override IStamper CreateInstance(IContext context)
{
// Unimportant lines omitted
string name = /* whatever */
return new Stamper(name);
}
}
Let's analyze what's going on with the code:
The ISessionFactory is bound as single-instance. There will only ever be one throughout the lifetime of the process. This is fairly typical.
The ISessionFactory is initialized with SessionFactoryProvider which immediately goes out to get an instance of IStamper, and passes this as a constant argument to initialize the session factory.
The IStamper in turn is initialized by the StamperProvider which initializes a Stamper class with a constant name set to the current user principal/identity.
The net result of this is that as long as the process is alive, every single "stamp" will be assigned the name of whichever user was first to log in. This might even be the anonymous user, which explains why you're seeing so many blank entries.
Whoever wrote this only got half the equation right. The IStamper is bound to the request scope, but it's being supplied to a singleton, which means that only one IStamper will ever be created. You're lucky that the Stamper doesn't hold any resources or have any finalizers, otherwise you'd probably end up with a lot of ObjectDisposedException and other weird errors.
There are three possible solutions to this:
(Recommended) - Rewrite the Stamper class to look up the current user on each call, instead of being initialized with static user info. Afterward, the Stamper class would no longer take any constructor arguments. You can the bind the IStamper InSingletonScope instead of InRequestScope.
Create an abstract IStamperFactory with a GetStamper method, and a concrete StamperFactory which implements it by wrapping the IKernel instance. Bind these together InSingletonScope. Have your concrete factory return kernel.Get<IStamper>(). Modify the session factory to accept and hold an IStamperFactory instead of an IStamper. Each time it needs to stamp, use the factory to get a new IStamper instance.
Change the ISessionFactory to be InRequestScope. Not recommended because it will hurt performance and potentially mess up ID generators if you don't use DB-generated identities, but it will solve your auditing problem.

Aaronaught, you're analysis describes exactly what I suspected. However, I found there is a fourth solution which is easier and more straightforward IMHO.
I modified the sessionprovider, such that the call to OpenSession takes an instance of IInterceptor as argument. As it turns out, the event listeners aren't actually supposed to be used for auditing (a bit of a rant, but other than that he is right, according to Fabio as well).
The AuditInterceptor implements OnFlushDirty (for auditing existing entities) and OnSave (for auditing newly created entities). The SessionProvider looks as below:
public class SessionProvider : Provider<ISession>
{
protected override ISession CreateInstance(IContext context)
{
// Create session
System.Security.Principal.IPrincipal user = HttpContext.Current.User;
System.Security.Principal.IIdentity identity = user == null ? null : user.Identity;
string name = identity == null ? "" : identity.Name;
var sessionFactory = context.Kernel.Get<ISessionFactory>();
var session = sessionFactory.OpenSession(new AuditInterceptor(name));
session.FlushMode = FlushMode.Commit;
return session;
}
}

Related

Strategy to guard against malicious data changes

Looking for ideas to guard against malicious data changes: userA manipulating (editing or deleting) data that belongs to userB. Since we are creating entities on the client, we need to assign them (or at least some of them) to the authenticated user.
For example:
var newItem = ds.createNewItem();
newItem.OwnerId(22); //this is the problem that I see.
newItem.Name("New Item");
newItem.Description("I just changed your item!");
... //and so on
ds.saveChanges();
Assuming we know the identity of the user calling SaveChanges on our API, how do we validate our entities (new or modified) against this user?
The first thought that comes to mind is to subclass EFContextProvider, override BeforeSaveEntity and examine the entities OwnerId property against the identity of our user. For example:
if (entityInfo.Entity.GetType() == typeof(Item)
&& (entityInfo.EntityState == EntityState.Added
|| entityInfo.EntityState == EntityState.Modified)
&& ((Item)entityInfo.Entity).OwnerId != _currentUserId) {
return false
... //and so on
If using this approach, does it make sense to establish _currentUserId in the constructor of our new EFContextProvider class?
An ideas or perhaps a better way to approach this problem?
I think you are on the right track. I've been noodling this myself and have gone down much the same path.
Let's assume you've handled authentication and there's an IPrincipal available. You've got yourself a custom IIdentity too (call it AppIdentity) where you can stash the UserId for the authenticated user.
The Web Api's base ApiController class makes the ambient IPrincipal available via its User property. We will leverage that in your custom Breeze Web Api controller which might begin like this:
[Authorize]
[JsonFormatter, ODataActionFilter]
public class BreezeApiController : ApiController
{
private readonly AppContextProvider _context;
public BreezeApiController() {
// pass 'User' IPrincipal to the context ctor
_context = new AppContextProvider(User);
}
...
// one of the Query action methods
[HttpGet]
public IQueryable<Foo> Foos() {
return _context.Foos
}
...
Your custom EFContextProvider might begin like this:
public class AppContextProvider : EFContextProvider<AppDbContext>
{
public AppContextProvider(IPrincipal user)
{
UserId = ((AppIdentity) user.Identity).UserId;
}
public int UserId { get; private set; }
...
Now you probably want to prevent UserB's entities from being seen by UserA. So instead of allowing every Foo to go out the door, your custom EFContextProvider could filter accordingly.
public DbQuery Foos
{
get
{
// Here the 'Context' is your EF DbContext
return (DbQuery) Context.Foos
.Where(f => f.UserId == UserId);
}
}
Looking back at the controller, we see that its Foos GET action method is oblivious to the filter ... as it should be. We want our controllers to be light and move the business logic to the custom EFContextProvider and its helpers.
Finally, a highly simplified, general purpose BeforeSaveEntity could look like this:
private bool BeforeSaveEntity(EntityInfo info)
{
var entity = info.Entity;
if (info.EntityState == EntityState.Added)
{
entity.UserId = UserId;
return true;
}
return UserId == entity.UserId || throwCannotSaveEntityForThisUser();
}
...
private bool throwCannotSaveEntityForThisUser()
{
throw new SecurityException("Unauthorized user");
}
Notice that the custom context provider on the server is responsible for setting the UserId of added entities. We wouldn't trust the client to do that anyway. And of course it is responsible for verifying the UserId of modified and deleted entities.
Hope this helps. Remember, this is only a sketch. The real deal would have greater sophistication and be refactored into helpers.

Mocking static method in ASP.NET MVC Test project

I have a method which looks like the one below
public List<Rajnikanth> GetRajnis()
{
string username = Utility.Helpers.GetLoggedInUserName();
return _service.GetRajni(username);
}
Utility.Helper is a static class,
public static class Helpers
{
public static String GetLoggedInUserName()
{
string username = "";
if (System.Web.HttpContext.Current.User.Identity.IsAuthenticated)
{
username = ((System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity).Ticket.Name;
}
return username;
}
}
I want to test : GetRajnis()
I want to mock : GetLoggedInUserName()
So my test method looks something like...
[TestMethod]
public void TestGetRajnis()
{
SomeController s = new SomeController(new SomeService());
var data = s.GetRajnis();
Assert.IsNotNull(data);
}
how do I mock the static method GetLoggedInUserName() ?
The Simplest Approach: Override the return value
If you are looking to mock a return value, then this is very simple. You can modify the Utility.Helper class to include a property called OverrideLoggedInUserName. When someone calls GetLogedInUserName(), if the override property is set, it is returned, otherwise the normal code to get the value from the HttpContext is used to get the return value.
public static class Helper
{
// Set this value to override the return value of GetLoggedInUserName().
public static string OverrideLoggedInUserName { get; set; };
public static string GetLoggedInUserName()
{
// Return mocked value if one is specified.
if ( !string.IsNullOrEmpty( OverrideLoggedInUserName ) )
return OverrideLoggedInUserName;
// Normal implementation.
string username = "";
if ( System.Web.HttpContext.Current.User.Identity.IsAuthenticated )
{
username = ( (System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity ).Ticket.Name;
}
return username;
}
}
This will effectively allow you to override the return value, which technically isn't a mock--it's a stub (according to the excellent article Mocks Aren't Stubs by Martin Fowler). This allows you to stub a return value, but won't allow you to assert whether the method was called or not. Anyhow as long as you only want to manipulate the return value this works fine.
Here is how you would use this in a test.
[ TestMethod ]
public void TestGetRajnis()
{
// Set logged in user name to be "Bob".
Helper.OverrideLoggedInUserName = "Bob";
SomeController s = new SomeController( new SomeService() );
var data = s.GetRajnis();
// Any assertions...
}
This design does have one drawback. Because it's a static class, if you set the override value, it remains set until you un-set it. So you must remember to re-set it to null.
A Better Approach: Inject the dependency
A better approach may be to create a class that retrieves the logged in user name, and pass it into the constructor of SomeController. We call this dependency injection. This way, you can inject a mocked instance into it for testing, but pass the real instance (that gets the user from the HttpContext) when not testing. This is a much cleaner and clearer approach. Plus, you can leverage all the power of whatever mocking framework you are using, since they are designed specifically to handle this approach. Here is what that would look like.
// Define interface to get the logged in user name.
public interface ILoggedInUserInfo
{
string GetLoggedInUserName();
}
// Implementation that gets logged in user name from HttpContext.
// This class will be used in production code.
public class LoggedInUserInfo : ILoggedInUserInfo
{
public string GetLoggedInUserName()
{
// This is the same code you had in your example.
string username = "";
if ( System.Web.HttpContext.Current.User.Identity.IsAuthenticated )
{
username = ( (System.Web.Security.FormsIdentity)HttpContext.Current.User.Identity ).Ticket.Name;
}
return username;
}
}
// This controller uses the ILoggedInUserInfo interface
// to get the logged in user name.
public class SomeController
{
private SomeService _service;
private ILoggedInUserInfo _userInfo;
// Constructor allows you inject an object that tells it
// how to get the logged in user info.
public SomeController( SomeService service, ILoggedInUserInfo userInfo )
{
_service = service;
_userInfo = userInfo;
}
public List< Rajnikanth > GetRajnis()
{
// Use the injected object to get the logged in user name.
string username = _userInfo.GetLoggedInUserName();
return _service.GetRajni( username );
}
}
And here is a test using Rhino Mocks to inject a stub object into the controller.
[ TestMethod ]
public void TestGetRajnis()
{
// Create a stub that returns "Bob" as the current logged in user name.
// This code uses Rhino Mocks mocking framework...
var userInfo = MockRepository.GenerateStub< ILoggedInUserInfo >();
userInfo.Stub( x => x.GetLoggedInUserName() ).Return( "Bob" );
SomeController s = new SomeController( new SomeService(), userInfo );
var data = s.GetRajnis();
// Any assertions...
}
The disadvantage here is that you can't just call Helper.GetLoggedInUserName() from anywhere in your code, because it's no longer static. However, you no longer have the need to reset the stubbed username every time you finish a test. Because it's not static, it it automatically reset. You just recreate it for the next test and set a new return value.
I hope this helps.
Get rid of the static class if you are looking for testability. A simple fix for now would be to create a wrapper around the static class. Unless you use something like TypeMock or something equally as powerful, then you cannot alter the logic of a static class. Nor do I suggest it. If you have to stub a static class, it probably should not be a static class.
public class StaticWrapper
{
public virtual String GetLoggedInUserName()
{
Utility.Helpers.GetLoggedInUserName();
}
}

Multitenancy with Fluent nHibernate and Ninject. One Database per Tenant

I'm building a multi-tenant web application where for security concerns, we need to have one instance of the database per tenant. So I have a MainDB for authentication and many ClientDB for application data.
I am using Asp.net MVC with Ninject and Fluent nHibernate. I have already setup my SessionFactory/Session/Repositories using Ninject and Fluent nHibernate in a Ninject Module at the start of the application. My sessions are PerRequestScope, as are repositories.
My problem is now I need to instanciate a SessionFactory (SingletonScope) instance for each of my tenants whenever one of them connects to the application and create a new session and necessary repositories for each webrequest. I'm puzzled as to how to do this and would need a concrete example.
Here's the situation.
Application starts : The user of TenantX enters his login info. SessionFactory of MainDB gets created and opens a session to the MainDB to authenticate the user. Then the application creates the auth cookie.
Tenant accesses the application : The Tenant Name + ConnectionString are extracted from MainDB and Ninject must construct a tenant specific SessionFactory (SingletonScope) for that tenant. The rest of the web request, all controllers requiring a repository will be inject with a Tenant specific session/repository based on that tenant's SessionFactory.
How do I setup that dynamic with Ninject? I was originally using Named instance when I had multiple databases but now that the databases are tenant specific, I'm lost...
After further research I can give you a better answer.
Whilst it's possible to pass a connection string to ISession.OpenSession a better approach is to create a custom ConnectionProvider. The simplest approach is to derive from DriverConnectionProvider and override the ConnectionString property:
public class TenantConnectionProvider : DriverConnectionProvider
{
protected override string ConnectionString
{
get
{
// load the tenant connection string
return "";
}
}
public override void Configure(IDictionary<string, string> settings)
{
ConfigureDriver(settings);
}
}
Using FluentNHibernate you set the provider like so:
var config = Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008
.Provider<TenantConnectionProvider>()
)
The ConnectionProvider is evaluated each time you open a session allowing you to connect to tenant specific databases in your application.
An issue with the above approach is that the SessionFactory is shared. This is not really a problem if you are only using the first level cache (since this is tied to the session) but is if you decide to enable the second level cache (tied to the SessionFactory).
The recommended approach therefore is to have a SessionFactory-per-tenant (this would apply to schema-per-tenant and database-per-tenant strategies).
Another issue often overlooked is that although the second level cache is tied to the SessionFactory, in some cases the cache space itself is shared (reference). This can be resolved by setting the "regionName" property of the provider.
Below is a working implementation of SessionFactory-per-tenant based on your requirements.
The Tenant class contains the information we need to set up NHibernate for the tenant:
public class Tenant : IEquatable<Tenant>
{
public string Name { get; set; }
public string ConnectionString { get; set; }
public bool Equals(Tenant other)
{
if (other == null)
return false;
return other.Name.Equals(Name) && other.ConnectionString.Equals(ConnectionString);
}
public override bool Equals(object obj)
{
return Equals(obj as Tenant);
}
public override int GetHashCode()
{
return string.Concat(Name, ConnectionString).GetHashCode();
}
}
Since we'll be storing a Dictionary<Tenant, ISessionFactory> we implement the IEquatable interface so we can evaluate the Tenant keys.
The process of getting the current tenant is abstracted like so:
public interface ITenantAccessor
{
Tenant GetCurrentTenant();
}
public class DefaultTenantAccessor : ITenantAccessor
{
public Tenant GetCurrentTenant()
{
// your implementation here
return null;
}
}
Finally the NHibernateSessionSource which manages the sessions:
public interface ISessionSource
{
ISession CreateSession();
}
public class NHibernateSessionSource : ISessionSource
{
private Dictionary<Tenant, ISessionFactory> sessionFactories =
new Dictionary<Tenant, ISessionFactory>();
private static readonly object factorySyncRoot = new object();
private string defaultConnectionString =
#"Server=(local)\sqlexpress;Database=NHibernateMultiTenancy;integrated security=true;";
private readonly ISessionFactory defaultSessionFactory;
private readonly ITenantAccessor tenantAccessor;
public NHibernateSessionSource(ITenantAccessor tenantAccessor)
{
if (tenantAccessor == null)
throw new ArgumentNullException("tenantAccessor");
this.tenantAccessor = tenantAccessor;
lock (factorySyncRoot)
{
if (defaultSessionFactory != null) return;
var configuration = AssembleConfiguration("default", defaultConnectionString);
defaultSessionFactory = configuration.BuildSessionFactory();
}
}
private Configuration AssembleConfiguration(string name, string connectionString)
{
return Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008.ConnectionString(connectionString)
)
.Mappings(cfg =>
{
cfg.FluentMappings.AddFromAssemblyOf<NHibernateSessionSource>();
})
.Cache(c =>
c.UseSecondLevelCache()
.ProviderClass<HashtableCacheProvider>()
.RegionPrefix(name)
)
.ExposeConfiguration(
c => c.SetProperty(NHibernate.Cfg.Environment.SessionFactoryName, name)
)
.BuildConfiguration();
}
private ISessionFactory GetSessionFactory(Tenant currentTenant)
{
ISessionFactory tenantSessionFactory;
sessionFactories.TryGetValue(currentTenant, out tenantSessionFactory);
if (tenantSessionFactory == null)
{
var configuration = AssembleConfiguration(currentTenant.Name, currentTenant.ConnectionString);
tenantSessionFactory = configuration.BuildSessionFactory();
lock (factorySyncRoot)
{
sessionFactories.Add(currentTenant, tenantSessionFactory);
}
}
return tenantSessionFactory;
}
public ISession CreateSession()
{
var tenant = tenantAccessor.GetCurrentTenant();
if (tenant == null)
{
return defaultSessionFactory.OpenSession();
}
return GetSessionFactory(tenant).OpenSession();
}
}
When we create an instance of NHibernateSessionSource we set up a default SessionFactory to our "default" database.
When CreateSession() is called we get a ISessionFactory instance. This will either be the default session factory (if the current tenant is null) or a tenant specific session factory. The task of locating the tenant specific session factory is performed by the GetSessionFactory method.
Finally we call OpenSession on the ISessionFactory instance we have obtained.
Note that when we create a session factory we set the SessionFactory name (for debugging/profiling purposes) and cache region prefix (for the reasons mentioned above).
Our IoC tool (in my case StructureMap) wires everything up:
x.For<ISessionSource>().Singleton().Use<NHibernateSessionSource>();
x.For<ISession>().HttpContextScoped().Use(ctx =>
ctx.GetInstance<ISessionSource>().CreateSession());
x.For<ITenantAccessor>().Use<DefaultTenantAccessor>();
Here NHibernateSessionSource is scoped as a singleton and ISession per request.
Hope this helps.
If all the databases are on the same machine, maybe the schema property of class mappings could be used to set the database on a pre-tenant basis.

MVC3 - How to correctly inject dependencies with MVC3 and Ninject?

I am attempting to redesign an existing application using dependency injection with Ninject in MVC3. Here is a portion of the legacy behavior I'm having difficulty with (and yes I know its bad, that's why I'm trying to refactor it):
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
MyUserSession userSession = filterContext.HttpContext.Session[SESSIONKEY_USER] as MyUserSession;
// if session empty, rebuild user information
if (userSession == null)
{
string userName = HttpContext.User.Identity.Name;
userSession = new MyUserSession();
using (ADSearcher ad = new ADSearcher(ldapPath, excludeOUString.Split(',')))
{
// get basic user information from Active Directory
ADUserInfo aduser = MyActiveDirectorySearcher.GetUserRecord(userName);
// ... set several properties queries from AD...
userSession.propertyXYZ = aduser.propXYZ
}
// if user can proxy as another indivudual, set property
using (EDMContainer db = new EDMContainer())
{
if (db.Proxies.Any(p => p.ProxyLogin == userSession.userLogin))
userSession.CanProxy == true;
}
// save new user object to session
filterContext.HttpContext.Session[SESSIONKEY_USER] = userSession;
if(userSession.canProxy)
filterContext.Result = RedirectToAction("Proxy", "Home");
return;
}
}
So currently, the controller users several objects directly: Session, ActiveDirectorySearch, EF Database. I understand it would be better to create a class that exposes a single method "GetUser" masking all the complexity but I'm struggling with how to inject the dependencies.
If I create a class SomeUserProvider, it will also need access to the Session to check for existing user information, and then ActiveDirectorySearcher and Database to rebuild the user properties if session was empty.
My confusion is over the fact that the controller itself will need access to ActiveDirectorySearcher in other action methods and then other classes will also use the same database. Do I inject an IActiveDirSearchrer into the controller's constructor and then pass it down into the ISomeUserProvider? What about IMyDatabase? Is it also injected in controller constructor and passed down?
And last but not lease, ISessionWrapper? I know session is controversial, but I need to track who the current user is and who they are proxied as during each request (GETs and POSTs). So, does that get injected as well?
If the answer is yes to each of those, is it bad to have 3+ injected contstuctor parameters?
I realize my question may be vague, so please ask for clarification where needed. I am open to any and all suggestions and recommendations. My goal is to learn how to do it correctly.
Thanks.
I'm not certain if this is exactly what you're looking for, but this should get you started down the path of refactoring your app for DI
public class YourController : Controller
{
private readonly ISessionWrapper _sessionWrapper;
private readonly IActiveDirSearcher _adSearcher;
private readonly IMyDatabase _database;
public YourController(ISessionWrapper sessionWrapper,
IActiveDirSearcher adSearcher, IMyDatabase database)
{
this._sessionWrapper = sessionWrapper;
this._adSearcher = adSearcher;
this._database = database;
}
// now all actions in this controller have a _sessionWrapper,
// _adSearcher and _database
}
Then you have to bind your injections the Ninject way. Subclass your application from NinjectHttpApplication and override OnApplicationStarted and CreateKernel
public class MvcApplication : NinjectHttpApplication
{
// ...
protected override void OnApplicationStarted()
{
base.OnApplicationStarted();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
protected override IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<ISessionWrapper>().To<YourSessionWrapperImplementation>();
kernel.Bind<IActiveDirSearcher>().To<YourADImplementation>();
kernel.Bind<IMyDataBase>().To<YourEDMContainerIThink>();
return kernel;
}
}
The implementations of these appear to be described in your question. However, you mentioned other actions (and other classes) depend on these implementations. Good news--the bindings in CreateKernel will take care of any missing dependencies elsewhere in your app. e.g.
public class MyActiveDirImplementation : IActiveDirSearcher
{
private readonly IMyDatabase _database;
// injected automagically WOOHOO!
public MyActiveDirImplementation(IMyDatabase database)
{
this._database = database;
}
public ADUserInfo GetUserRecord(string username)
{
return _database.GetSomeUserRecord(username);
}
}
You could, of course, similarly implement your ISessionWrapper or IMyDatabase

StructureMap IOC/DI and object creation

I'm building small web shop with asp.net mvc and Structuremap ioc/di. My Basket class uses session object for persistence, and I want use SM to create my basket object through IBasket interface. My basket implementation need HttpSessionStateBase (session state wrapper from mvc) in constructor, which is available inside Controller/Action. How do I register my IBasket implementation for SM?
This is my basket interface:
public interface IBasketService {
BasketContent GetBasket();
void AddItem(Product productItem);
void RemoveItem(Guid guid);
}
And SM registration:
ForRequestedType(typeof (IBasketService)).TheDefaultIsConcreteType(typeof (StoreBasketService));
But my StoreBasketService implementation has constructor:
public StoreBasketService(HttpSessionStateBase sessionState)
How do I provide HttpSessionStateBase object to SM, which is available only in controller?
This is my first use of SM IOC/DI, and cann't find solution/example in official documentation and web site ;)
If you absolutely have to have your StoreBasketService use the session, I'd be tempted to define an interface and wrapper around HttpSessionState instead of using HttpSessionStateBase so that you can register it with StructureMap as well.The wrapper would get the session state from the current context. Register the wrapper with StructureMap and then have your StoreBasketService take the interface as the argument to the constructor. Structure map should then know how to create an instance of the interface wrapper and inject it into your StoreBasketService class.
Using an interface and wrapper will allow you to mock the wrapper in your unit tests, muc in the same way HttpSessionStateBase allows mocking the actual session.
public interface IHttpSessionStateWrapper
{
HttpSessionState GetSessionState();
}
public class HttpSessionStateWrapper : IHttpSessionStateWrapper
{
public virtual HttpSessionState GetSessionState()
{
return HttpContext.Current.Session;
}
}
ForRquestedType(typeof(IHttpSessionStateWrapper))
.TheDefaultIsConcreteType(typeof(IHttpSessionStateWrapper));
public class StoreBasketService
{
HttpSessionState session;
public StoreBasketService( IHttpSessionstateWrapper wrapper )
{
session = wrapper.GetSessionState();
}
// basket implementation ...
}
However, you can have StructureMap actually store your basket in the session using .CacheBy(InstanceScope.HttpContext) when registering it. It may actually be better to have your StoreBasketService implement internal storage instead of storing things in the session -- then you lose the dependency on the session state entirely (from the perspective of your class) and your solution could be simpler. Your internal storage could be a Dictionary<Guid,Product> since this is how you access them via your interface.
See also:
http://www.lostechies.com/blogs/chad_myers/archive/2008/07/15/structuremap-basic-scenario-usage.aspx
http://www.lostechies.com/blogs/chad_myers/archive/2008/07/17/structuremap-medium-level-usage-scenarios.aspx
ForRequestedType<IBasketService>()
.TheDefault.Is.OfConcreteType<StoreBasketService>()
.WithCtorArg("sessionState").EqualTo(HttpContext.Current.Session);
?? does that work?
I just started with StructureMap, and I do not get the results you are describing.
I performed a simple test using a simple class, configuring Structuremap to cacheby HttpContext, and from what I can see, CacheBy.HttpContext means within the same request you will get the same instance... not within the same Session
The constructor of my class, sets the date/time in a private field
I have a button which gets 2 instances of MyClass with one second interval...
It then display the time of both instances in a label.
Pressing the first time this button, object A and B are same instance, as their creation time is exactly the same, as expected.
Clicking the button a second time, you would expect the creation time to not have changed if instances would be cached in session... however, in my test I get a new creation time ...
Structuremap configuration:
ObjectFactory.Initialize(x=>x.ForRequestedType<MyClass>(). CacheBy(InstanceScope.HttpContext));
Button clicked event of test page
protected void btnTest_Click(object sender, EventArgs e)
{
MyClass c = ObjectFactory.GetInstance<MyClass>();
System.Threading.Thread.Sleep(1000);
MyClass b = ObjectFactory.GetInstance<MyClass>();
lblResult.Text = String.Format("cache by httpcontext First:{0} Second:{1} session id {2} ", c.GetTimeCreated(), b.GetTimeCreated(),Session.SessionID);
}
MyClass
public class MyClass
{
private DateTime _timeCreated;
public MyClass()
{
_timeCreated = DateTime.Now;
}
public string GetTimeCreated()
{
return _timeCreated.ToString("dd/MM/yyyy hh:mm:ss");
}
}
You could also use one of the ObjectFactory.Inject methods to inject the HttpSessionStateBase into StructureMap. It would then invoke the constructor with the injected HttpSessionStateBase.
I just made my first attempt at creating an custom scope... build a small web application with it, and as far as I can see, it seems to work. This will cache the object inside the current user session and will return the same object as long as you remain inside the same session:
public class HttpSessionBuilder : CacheInterceptor
{
private readonly string _prefix = Guid.NewGuid().ToString();
protected override CacheInterceptor clone()
{
return this;
}
private string getKey(string instanceKey, Type pluginType)
{
return string.Format("{0}:{1}:{2}", pluginType.AssemblyQualifiedName, instanceKey, this._prefix);
}
public static bool HasContext()
{
return (HttpContext.Current.Session != null);
}
protected override bool isCached(string instanceKey, Type pluginType)
{
return HttpContext.Current.Session[this.getKey(instanceKey, pluginType)] != null;
}
protected override object retrieveFromCache(string instanceKey, Type pluginType)
{
return HttpContext.Current.Session[this.getKey(instanceKey, pluginType)];
}
protected override void storeInCache(string instanceKey, Type pluginType, object instance)
{
HttpContext.Current.Session.Add(this.getKey(instanceKey, pluginType), instance);
}
}
You have to configure the ObjectFactory as follows in the global.asax Application_start
ObjectFactory.Initialize(x=>
x.ForRequestedType<MyClass>().InterceptConstructionWith(new HttpSessionBuilder()));

Resources