MVC3 - How to correctly inject dependencies with MVC3 and Ninject? - asp.net-mvc

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

Related

asp.net mvc web api 2 custom logger + controller lifecycle

story:I have following component in my asp.net mvc 5 web api project(using ef code first)
myBaseController : base api controller class
myLogger : my costume logger class
class myBaseController { MyLogger Logger; }
class MyLogger
{
public addLog(Log log)
{
db.logs.add(log);
db.saveChanges();
}
}
Each api controller use base.logger.addLog(new log("somethings")) and the log committed to database immediately.
Problem: I want MyLogger class be able to collect all log in memory and some where add theme to database at once.
I put it in
protected override void Dispose(bool disposing)
{
logger.AddRange(_logs);
}
but it doesn't work,it seems objects are not available in this method or grabbed from memory
How it should be handled?
You don't need to collect records in memory separately. Because EF will do that for you. For instance db.logs.add means, please add it to memory. But when you call SaveChanges it will commit all of the in-memory collected data to the database at once. So you will need to implement the unit of work pattern (UoW). By using this pattern, you will have only one context per request and then at the end of the request you will call the SaveChanges method once. You shouldn't have multiple SaveChanges everywhere in your codes, this is your problem right now. Your addLog method calls SaveChanges, your other methods call SaveChanges and so on.
More info from Microsoft: Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application
You need to store your entries in memory during request cycle and at end of request you can save those entries into database
protected void Application_EndRequest()
{
//get entries from memory/session/cache whatever your source to store entries
//do stuff to save entries
}
I normally create ActionFilterAttributes, and place it on action methods where I want to log any activity.
TraceMvcAttribute
public class TraceMvcAttribute : ActionFilterAttribute
{
// IoC container will inject this dependencies.
public IWebUserSession WebUserSession { get; set; }
public IDateTime DateTime { get; set; }
public ITraceListener TraceListener { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var traceLog = new TraceLog
{
Controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
Action = filterContext.ActionDescriptor.ActionName,
Message = JsonConvert.SerializeObject(filterContext.ActionParameters),
PerformedOn = this.DateTime.Now,
PerformedBy = WebUserSession?.UserName
};
TraceListener.AddTraceLogAsync(traceLog);
base.OnActionExecuting(filterContext);
}
}
TraceApiAttribute
Web API filter is a little bit different from MVC filter. I do not have it in my sample code at Git; in case you might want to take a look at it.
public class TraceApiAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
// http://autofac.readthedocs.org/en/latest/integration/webapi.html#standard-web-api-filters-are-singletons
// All filter attributes in Web API are effectively singleton instances that exist
// for the entire lifetime of the application. We must use service location,
// since we need per-request services in your filters.
var requestScope = actionContext.Request.GetDependencyScope();
var datetime = requestScope.GetService(typeof(IDateTime)) as IDateTime;
var webUserSession = requestScope.GetService(typeof(IWebUserSession)) as IWebUserSession;
var traceListener = requestScope.GetService(typeof(ITraceListener)) as ITraceListener;
var traceLog = new TraceLog
{
Controller = actionContext.ControllerContext.ControllerDescriptor.ControllerName,
Action = actionContext.ActionDescriptor.ActionName,
Message = JsonConvert.SerializeObject(actionContext.ActionArguments.Where(x => x.Key != "request").ToList()),
PerformDateTime = datetime.Now,
PerformBy = webUserSession?.UserName
};
traceListener.AddTraceLogAsync(traceLog);
base.OnActionExecuting(actionContext);
}
}
I then view the log history like this -

How MeController constractor calls?

When I create default SPA template progect VS2013 creates MeController. Calling Get I have a user information. But when I create almost the same Controller, for example UserController, and copy-paste all from Me to User I do not have necessary info about user on breakpoint in the method:
// GET api/Me
public GetViewModel Get()
{
var user = UserManager.FindById(User.Identity.GetUserId());
return new GetViewModel() { Hometown = user.Hometown };
}
I don't uderstand this magic! I also do not see a caller of constractor and sender of userManager. Where is it?
public MeController(ApplicationUserManager userManager)
{
UserManager = userManager;
}
There is a default constructor in the MeController which gets called by default. In this case the UserManager, is got from the Owin contect as below
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
Even if you copy this over to a new class like UserController, this should happen. Are you using any DI library like StrucuteMap/Unity etc? Certain DI controllers choose the 'greediest' constructor(the one with the maximum arguments). You could override that by using appropriate methods for that DI library(like this if you are using StructureMap)
In case you would want to use your own UserManager you could use your own implementation of UserManager and have it used. But if you are planning to stick with the default, this is already added in Startup.Auth.cs into the Owin pipeline.

Manage multiple ravendb document stores through castle windsor in an MVC app?

I twist myself around a workable solution to use several databases in RavenDB for an ASP.Net MVC app using Castle Windsor for the wiring.
This is the current installer
public class RavenInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDocumentStore>().Instance(CreateDocumentStore()).LifeStyle.Singleton,
Component.For<IDocumentSession>().UsingFactoryMethod(GetDocumentSesssion).LifeStyle.PerWebRequest
);
}
static IDocumentStore CreateDocumentStore()
{
var store = new DocumentStore { ConnectionStringName = "RavenDb_CS9" };
store.Initialize();
IndexCreation.CreateIndexes(typeof(Users).Assembly, store);
return store;
}
static IDocumentSession GetDocumentSesssion(IKernel kernel)
{
var store = kernel.Resolve<IDocumentStore>();
return store.OpenSession();
}
}
The above works perfect but only for one Database.
I can't find the proper thinking how to handle another database. The whole chain starts with a domain service asking for an IDocumentSession. Then the flow is as specified in the above installer. But where/how do I ask for a "SessionToDb1" or a "SessionToDb2"?
The important is of course what connection string to use (where the DB property is specified) but also what indexes to create in respective DB / DocumentStore.
Did anyone accomplish this using Windsor? Am I thinking/attacking it wrong here?
Thanks!
Because you have:
Component.For<IDocumentSession>()
.UsingFactoryMethod(GetDocumentSesssion)
.LifeStyle.PerWebRequest
Your GetDocumentSession method is going to be called any time you inject an IDocumentSession. This is good.
When working with multiple databases, you need to pass the database name as a parameter to OpenSession. So, you need some way to resolve which database you would like to connect to based on the current web request.
You need to modify the GetDocumentSession method to implement whatever custom logic you are going to use. For example, you may want to look at a cookie, asp.net session item, current thread principal, or some other criteria. The decision is custom to your application, all that matters is somehow you open the session with the correct database name.
I've run into this problem before with nhibernate.
I found the best solution is to create a SessionManager class which wraps the Creation of the document store and the Session..
So I.E.
public interface ISessionManager
{
void BuildDocumentStore();
IDocumentSession OpenSession();
}
public interface ISiteSessionManager : ISessionManager
{
}
public class SiteSessionManager : ISiteSessionManager
{
IDocumentStore _documentStore;
public SiteSessionManager()
{
BuildDocumentStore();
}
public void BuildDocumentStore()
{
_documentStore = new DocumentStore
{
Url = "http://localhost:88",
DefaultDatabase = "test"
};
_documentStore.Initialize();
IndexCreation.CreateIndexes(typeof(SiteSessionManager).Assembly, _documentStore);
}
public IDocumentSession OpenSession()
{
return _documentStore.OpenSession();
}
}
// And then!.
Container.Register(Component.For<ISiteSessionManager>().Instance(new SiteSessionManager()).LifestyleSingleton());
// And then!.
public class FindUsers
{
readonly ISiteSessionManager _siteSessionManager;
public FindUsers(ISiteSessionManager siteSessionManager)
{
_siteSessionManager = siteSessionManager;
}
public IList<User> GetUsers()
{
using (var session = _siteSessionManager.OpenSession())
{
// do your query
return null;
}
}
}
Rinse and repeat for multiple databases.!

Best approach to don't request same info over and over

On my controller I have it inherit a MainController and there I override the Initialize and the OnActionExecuting.
Here I see what is the URL and by that I can check what Client is it, but I learned that for every Method called, this is fired up again and again, even a simple redirectToAction will fire the Initialization of the same controller.
Is there a better technique to avoid this repetition of database call? I'm using Entity Framework, so it will take no time to call the DB as it has the result in cache already, but ... just to know if there is a better technique now in MVC3 rather that host the variables in a Session Variable
sample code
public class MyController : MainController
{
public ActionResult Index()
{
return View();
}
}
public class MainController : Controller
{
public OS_Clients currentClient { get; set; }
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
// get URL Info
string url = requestContext.HttpContext.Request.Url.AbsoluteUri;
string action = requestContext.RouteData.GetRequiredString("action");
string controller = requestContext.RouteData.GetRequiredString("controller");
object _clientUrl = requestContext.RouteData.Values["cliurl"];
if (_clientUrl != null && _clientUrl.ToString() != "none")
{
// Fill up variables
this.currrentClient = db.FindClientById(_clientUrl.ToString());
}
base.Initialize(requestContext);
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// based on client and other variables, redirect to Disable or Login Actions
// ... more code here like:
// filterContext.Result = RedirectToAction("Login", "My");
base.OnActionExecuting(filterContext);
}
}
is it still best to do as:
public OS_Clients currentClient {
get {
OS_Clients _currentClient = null;
if (Session["CurrentClient"] != null)
_currentClient = (OS_Clients)Session["CurrentClient"];
return _currentClient;
}
set {
Session["CurrentClient"] = value;
}
}
It seems that you dealing with application security in that case I would suggest to create Authorization filter, which comes much early into the action. You can put your permission checking code over there and the framework will automatically redirect the user to login page if the permission does not meet AuthorizeCore.
Next, if the user has permission you can use the HttpContext.Items as a request level cache. And then you can create another ActionFilter and in action executing or you can use the base controller to get the user from the Httpcontext.items and assign it to controller property.
If you are using asp.net mvc 3 then you can use the GlobalFilters to register the above mentioned filters instead of decorating each controller.
Hope that helps.
In your base controller, you need to cache the result of the first call in a Session variable.
This makes sure the back-end (DB) is not called unnecessarily, and that the data is bound to the user's Session instead of shared across users, as would be the case with the Application Cache.

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