Property injection into custom WebViewPage using Autofac - asp.net-mvc-3

I'm creating an internal application framework that other dev teams in our organisation will use to build MVC applications from. As part of that, I'm creating the menu structure for all views, that is read from configuration and modified based on the current user's permissions. To create the menu as part of the framework, I've created a custom WebViewPage implementation with a custom HTML Helper class that needs to take a dependency on an ApplicationDataReader to construct the menu.
I've read various posts that explain that MVC needs the WebViewPage to have a paramterless constructor, so I would need to use property injection. I've configured Autofac MVC3 Integration, including registering a ViewRegistrationSource. Trouble is, when the dependent property is accessed, it's always null.
Here's the custom view page and helper with the call I'm trying to make:
public abstract class OuBaseViewPage<TModel> : WebViewPage<TModel> where TModel : class
{
public OuHelper<TModel> Ou { get; set; }
public override void InitHelpers()
{
base.InitHelpers();
Ou = new OuHelper<TModel>(ViewContext, this);
}
}
public class OuHelper<TModel> where TModel : class
{
public OuHelper(ViewContext viewContext, IViewDataContainer viewDataContainer)
: this(viewContext, viewDataContainer, RouteTable.Routes)
{
}
public OuHelper(ViewContext viewContext, IViewDataContainer viewDataContainer, RouteCollection routeCollection)
{
ViewContext = viewContext;
ViewData = new ViewDataDictionary<TModel>(viewDataContainer.ViewData);
}
public ViewDataDictionary<TModel> ViewData { get; private set; }
public ViewContext ViewContext { get; private set; }
public IList<BusinessFocusArea> ReadFocusAreas()
{
// this is null - yes, service location, but an isolated instance of...
return DependencyResolver.Current.GetService<IDataReader>().Read();
}
}
The problem stems from the fact that InitHelpers is being called (via Layout.Execute()) BEFORE Application_Start is called, so none of the dependencies have been registered. I know that it's not good practice to inject logic into views and that views should simply be dumb, but this is an application framework and it needs to perform certain setup steps that the developers using the framework mustn't have visibility of.
Is there a better approach I could follow?
There's a similar issue here: Can Autofac inject dependencies into layout view files?

The problem stems from the fact that InitHelpers is being called (via
Layout.Execute()) BEFORE Application_Start is called
I don't think that something is called before Application_Start. I cannot reproduce your problem.
Here are the steps I did and which worked perfectly fine:
Create a new ASP.NET MVC 3 application using the Internet Template
Install the Autofac.Mvc3 NuGet
Define a dummy interface:
public interface IDataReader
{
}
And a dummy implementation:
public class DataReader : IDataReader
{
}
Define a custom helper:
public class OuHelper<TModel> where TModel : class
{
private readonly IDataReader dataReader;
public OuHelper(ViewContext viewContext, IViewDataContainer viewDataContainer, IDataReader dataReader)
: this(viewContext, viewDataContainer, RouteTable.Routes, dataReader)
{
}
public OuHelper(ViewContext viewContext, IViewDataContainer viewDataContainer, RouteCollection routeCollection, IDataReader dataReader)
{
ViewContext = viewContext;
ViewData = new ViewDataDictionary<TModel>(viewDataContainer.ViewData);
this.dataReader = dataReader;
}
public ViewDataDictionary<TModel> ViewData { get; private set; }
public ViewContext ViewContext { get; private set; }
public IDataReader DataReader
{
get { return this.dataReader; }
}
}
Define a custom WebViewPage using this helper:
public abstract class OuBaseViewPage<TModel> : WebViewPage<TModel> where TModel : class
{
public OuHelper<TModel> Ou { get; set; }
public override void InitHelpers()
{
base.InitHelpers();
var dataReader = DependencyResolver.Current.GetService<IDataReader>();
Ou = new OuHelper<TModel>(ViewContext, this, dataReader);
}
}
Replace the default view page with the custom one in ~/Views/web.config:
<pages pageBaseType="MvcApplication1.OuBaseViewPage">
Configure your container in Application_Start:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.RegisterType<DataReader>().As<IDataReader>();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
Now you could happily use the custom helper in all views including the _Layout without any problems:
#Ou.DataReader.GetType()
Of course in this example I have just exposed the IDataReader dependency as a public property to illustrate you that it will always be injected and it will never be null. In your particular code you could of course use only the private readonly field inside the helper to achieve your task.

Related

MVC 6 EF7 RC1 creating multiple dbcontexts

I am trying to figure out how to create a second DB context in EF7 RC1. In the past I could use a constructor with :base("connectionName") but that no longer seems an option since it says cannot convert string to System.IServiceProvider.
My second context code is as follows:
public class DecAppContext : DbContext
{
public DecAppContext()
// :base("DefaultConnection")
{
}
public DbSet<VignetteModels> VignetteModels { get; set; }
public DbSet<VignetteResult> Result { get; set; }
}
}
In my config.json I have the connection specified:
"Data": {
"DefaultConnection": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-xxxxx...;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
In my configure services section of my startup I have both contexts added:
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]))
.AddDbContext<DecAppContext>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
The applicationDB context works fine since I can create a user and login without issue
However when I try to access the other context as in my controller via:
private DecAppContext db = new DecAppContext();
var vignette = db.VignetteModels.SingleOrDefault(v => v.CaseId == vid);
I get the error:
No database providers are configured. Configure a database provider by
overriding OnConfiguring in your DbContext class or in the
AddDbContext method when setting up services.
Any working examples in EF7 RC1 with multiple db contexts and accessing them would be much appreciated.
First of all I would recommend you the article from the wiki of EntityFramework on GitHub. The article describes many ways to define DbContext, which references to a section of appsettings.json. I personally prefer the way with the usage of [FromServices] attribute.
The code could be about the following:
First of all you defined appsettings.json with the following content
{
"Data": {
"ApplicationDbConnectionString": "Server=(localdb)\\mssqllocaldb;Database=ApplicationDb;Trusted_Connection=True;MultipleActiveResultSets=true",
"DecAppDbConnectionString": "Server=Server=(localdb)\\mssqllocaldb;Database=DecAppDb;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
where you define two connection strings.
Seconds you declare the classes DecAppContext and ApplicationDbContext which have DbContext as the base class. The simplest form will be just
public class ApplicationDbContext : DbContext
{
}
public class DecAppContext : DbContext
{
}
without any DbSet properties.
Third Step. You use Microsoft.Extensions.DependencyInjection to inject the database contexts. To do this you need just include in Startup.cs something like
public class Startup
{
// property for holding configuration
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
// save the configuration in Configuration property
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options => {
options.UseSqlServer(Configuration["Data:ApplicationDbConnectionString"]);
})
.AddDbContext<DecAppContext>(options => {
options.UseSqlServer(Configuration["Data:DecAppDbConnectionString"]);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
}
}
Se create two DbContext (DecAppContext and ApplicationDbContext) using the configuration "Data:DecAppDbConnectionString" and "Data:ApplicationDbConnectionString".
Now we can just use the context in the controller. For example
[Route("api/[controller]")]
public class UsersController : Controller
{
[FromServices]
public ApplicationDbContext ApplicationDbContext { get; set; }
[FromServices]
public DecAppContext DecAppContext { get; set; }
[HttpGet]
public IEnumerable<object> Get() {
var returnObject = new List<dynamic>();
using (var cmd = ApplicationDbContext.Database.GetDbConnection().CreateCommand()) {
cmd.CommandText = "SELECT Id, FirstName FROM dbo.Users";
if (cmd.Connection.State != ConnectionState.Open)
cmd.Connection.Open();
var retObject = new List<dynamic>();
using (var dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
var dataRow = new ExpandoObject() as IDictionary<string, object>;
for (var iFiled = 0; iFiled < dataReader.FieldCount; iFiled++)
dataRow.Add(
dataReader.GetName(iFiled),
dataReader.IsDBNull(iFiled) ? null : dataReader[iFiled] // use null instead of {}
);
retObject.Add((ExpandoObject)dataRow);
}
}
return retObject;
}
}
}
or the same using async/await:
[Route("api/[controller]")]
public class UsersController : Controller
{
[FromServices]
public ApplicationDbContext ApplicationDbContext { get; set; }
[FromServices]
public DecAppContext DecAppContext { get; set; }
[HttpGet]
public async IEnumerable<object> Get() {
var returnObject = new List<dynamic>();
using (var cmd = ApplicationDbContext.Database.GetDbConnection().CreateCommand()) {
cmd.CommandText = "SELECT Id, FirstName FROM dbo.Users";
if (cmd.Connection.State != ConnectionState.Open)
cmd.Connection.Open();
var retObject = new List<dynamic>();
using (var dataReader = await cmd.ExecuteReaderAsync())
{
while (await dataReader.ReadAsync())
{
var dataRow = new ExpandoObject() as IDictionary<string, object>;
for (var iFiled = 0; iFiled < dataReader.FieldCount; iFiled++)
dataRow.Add(dataReader.GetName(iFiled), dataReader[iFiled]);
retObject.Add((ExpandoObject)dataRow);
}
}
return retObject;
}
}
}
One can just declare the property public ApplicationDbContext ApplicationDbContext { get; set; } with the attribute [FromServices] and ASP.NET initialize it from the context injected in ConfigureServices. In the same way one can use the second context DecAppContext whenever you need it.
The above code example will execute SELECT Id, FirstName From dbo.Users in the database context and return JSON data in the form [{"id":123, "firstName":"Oleg"},{"id":456, "firstName":"Xaxum"}]. The conversion of property names from Id and FirstName to id and firstName will be done automatically during serialization because of usage AddJsonOptions in ConfigureServices.
UPDATE: I have to reference the announcement. The next version of MVC (RC2) will require to change the above code to use [FromServices] as additional parameter (of method Get() for example) instead of usage public property [FromServices] public ApplicationDbContext ApplicationDbContext { get; set; }. One will need to remove the property ApplicationDbContext and to add additional parameter to Get() method: public async IEnumerable<object> Get([FromServices] ApplicationDbContext applicationDbContext) {...}. Such changes can be easy done. See here and example of the changes in the demo example of MVC:
[Route("api/[controller]")]
public class UsersController : Controller
{
[HttpGet]
public async IEnumerable<object> Get(
[FromServices] ApplicationDbContext applicationDbContext,
[FromServices] DecAppContext decAppContext)
{
var returnObject = new List<dynamic>();
// ... the same code as before, but using applicationDbContext
// and decAppContext parameters instead of ApplicationDbContext
// and DecAppContext properties
}

Passing multiple models to a view using MVC3 and Ninject

I'm new to MVC3 (which is why I bought a book on it, which is why I now have this question!), so apologies if there is an obvious answer to this!
I'm following a simple example of building a shopping cart in MVC3. The book advocates the use of Ninject for dependency injection, which I'm also new to. It all seems straight forward enough with one model, in this case Product, but building upon this I am struggling to add a second model and display this in the same view where the Product model is displayed. I've tried using a View Model but all examples I find wrap several classes into one model and I can't quite figure out how to implement this in my code.
The class:
public class Product
{
public int ProductId {get;set;}
public string Name {get;set;}
}
Abstract Repository:
public interface IProductRepository
{
IQueryable<Product> Products {get;}
}
Class to associate model with database:
public class EFDbContext : DbContext
{
public DbSet<Product> Products {get;set;}
}
Product Repository which implements abstract interface:
public class EFProductRepository : IProductRepository
{
private EFDbContext context = new EFDbContext();
public IQueryable<Product> Products
{
get {return context.Products;}
}
}
Ninject binds IProductRepository to EFProductRepository in a ControllerFactory class.
Controller:
public class ProductController : Controller
{
private IProductRepository repository;
public ProductController(IProductRepository productRepository)
{
repository = productRepository;
}
public ViewResult List()
{
return View(repository.Products);
}
}
My problem is passing repository.Products to the strongly typed view; if I need to pass another entity, which is very feasible how would I achieve this???
You can build a ViewModel which looks like the following:
public class YourViewModel
{
public List<Product> Products { get; set; }
public List<OtherEntity> OtherEntities { get; set; }
}
Then you can wrap the repository in a service which contains all the methods
you need to fulfill your requests and/or businesslogic:
public class YourService
{
private IProductRepository repository;
public List<Product> GetAllProducts( )
{
return this.repository.Products.ToList( );
}
public List<OtherEntity> GetAllOtherEntites( )
{
return this.repository.OtherEntites.ToList( );
}
}
and finally in the Controller you fill the ViewModel appropriately
public class ProductController : Controller
{
private YourControllerService service = new YourControllerService( );
// you can make also an IService interface like you did with
// the repository
public ProductController(YourControllerService yourService)
{
service = yourService;
}
public ViewResult List()
{
var viewModel = new YourViewModel( );
viewModel.Products = service.GetAllProducts( );
viewModel.OtherEntities = service.GetAllOtherEntities( );
return View( viewModel );
}
}
Now you have multiple entities on you ViewModel.
Maybe it is not directly answer to your question but it is connected.
If you correctly pass the model to view, you can handle it like this
#model SolutionName.WebUI.Models.YourViewModel
#Model.Product[index].ProductId
#Model.OtherEntity[index].OtherId
I know that it's old post but it maybe help others :)

Injecting dependencies into MVC3 filters

I've been having a heck of a time trying to get dependencies injected into a custom authorization filter.
OutletService (this is a service I'm trying to inject into my filter)
public class OutletService : IOutletService
{
#region Fields
private readonly IRepository<Outlet> _outletRepository;
#endregion
#region Ctor
public OutletService(IRepository<Outlet> outletRepository)
{
_outletRepository = outletRepository;
}
#endregion
// Rest of class omitted
CustomAuthorizeAttribute (partial, name changed for this example also)
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private IOutletService _outletService;
private IModuleService _moduleService;
public string Action { get; set; }
public int Level { get; set; }
public MarcusAuthorizeAttribute()
{
}
[Inject]
public MyAuthorizeAttribute(IOutletService outletService, IModuleService moduleService)
{
_outletService = outletService;
_moduleService = moduleService;
}
I tried using this post as an example, but as soon as I wire it up, none of my routes seem to work (IIS Express returns a 401/cannot find?)
Injecting dependencies into ASP.NET MVC 3 action filters. What's wrong with this approach?
If anyone has any ideas or suggestions, I'd appreciate it! (It's literally driving me up a wall now!)
Thanks!
Ninject's MVC extension has a mechanism for injecting dependencies into filters, which is described in the documentation here.
You may try this
Filter
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private IOutletService _outletService;
private IModuleService _moduleService;
public string Action { get; set; }
public int Level { get; set; }
public MarcusAuthorizeAttribute()
{
_outletService = DependencyResolver.Current.GetService<IHelloService>();
_moduleService = DependencyResolver.Current.GetService<IModuleService>();
}
}
Make sure you register your services with dependency resolver you are using.

NinjectMVC3 2.2.00 and Membership Provider

Does anyone know how to use the NinjectMVC3.cs to inject a custom membership provider class? I've googled and tried every single implementation and none of them work. Is anyone doing this? I've tried injecting using the property attribute [Inject] doesn't work and don't know of any other way since constructor injection doesn't work either.
None of this works:
public class AccountMembershipProvider : MembershipProvider
[Inject]
protected IAccountRepository accountRepository { get; set; }
//NinjectMVC.cs RegisterServices
kernel.Bind<IAccountRepository>().To<AccountRepository>();
kernel.Bind<MembershipProvider>().ToProvider<AccountMembershipProvider>();
public class AccountMembershipProvider : MembershipProvider
[Inject]
protected IAccountRepository accountRepository { get; set; }
//NinjectMVC.cs RegisterServices
kernel.Bind<IAccountRepository>().To<AccountRepository>();
kernel.Bind<MembershipProvider>().ToMethod(ctx=>Membership.Provider);
A complete example of injecting a custom membership provider would be nice.
The Ninject 3.0.0 works it instantiated the IRepository that is used by the Custom Membership Provider although I was getting the context was disposed error so I added .InRequestScope to the IAccountRepository bindings. Can you check if the bindings are correct? Do I need to use InRequestScope on the IAccount Repository or do I need to implement some code in the HttpModule ??
Here's what I have
//custom membership provider
public class AccountMembershipProvider : MembershipProvider
{
[Inject]
public IAccountRepository accountRepository
{
get;
set;
}
//example method
public override int GetNumberOfUsersOnline()
{
return this.accountRepository.TotalUsers;
}
//AccountRepository
public class AccountRepository : IAccountRepository
{
private EFDbContext context;
public AccountRepository(EFDbContext dbContext)
{
this.context = dbContext;
}
public int TotalUsers
{
get { return this.context.Users.Count(); }
}
//HttpModule Do I need to implement anything here ???
public class ProviderInitializationHttpModule : IHttpModule
{
public ProviderInitializationHttpModule(MembershipProvider membershipProvider){}
public void Init(HttpApplication context){}
public void Dispose{}
}
//NinjectWebCommon.cs RegisterServices
//dbContext bindings
kernel.Bind<EFDbContext>().ToSelf().InRequestScope();
kernel.Bind<IDbContext>().ToMethod(ctx => ctx.Kernel.Get<EFDbContext>());
kernel.Bind<DbContext>().ToMethod(ctx => ctx.Kernel.Get<EFDbContext>());
//Repository - I had to add .InRequestScope so the Memeberhip Provider doesn't
//dispose the dbcontext. I believe this is because of the HttpModule disposing
kernel.Bind<IAccountRepository>().To<AccountRepository>().InRequestScope();**
//Custom Membership implemented using the Repository Pattern
kernel.Bind<MembershipProvider>().ToMethod(ctx=>Membership.Provider);
kernel.Bind<IHttpModule>().To<ProviderInitializationHttpModule>();

Custom Membership + Ninject + InRequestScope = ObjectContext instance has been disposed

ObjectContext instance has been disposed in InRequestScope!
I tried for several hours across the web to try to solve a problem.
The ObjectContext instance has been disposed and can no longer be used
for operations that require a connection.
I found several articles and posts with the same problem like this, this, this and this
I tried all ways, but always an error occurs.
Code
Context
public class BindSolutionContext : DbContext
{
public DbSet<Project> Projects { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Address> Addresses { get; set; }
public DbSet<ProjectImage> ProjectImages { get; set; }
public BindSolutionContext()
: base("name=Data")
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<BindSolutionContext>());
}
}
Ninject
kernel.Bind<BindSolutionContext>().ToSelf().InRequestScope();
kernel.Bind<IProjectRepository>().To<ProjectRepository>().InRequestScope();
kernel.Bind<IUserRepository>().To<UserRepository>().InRequestScope();
kernel.Bind<IRoleRepository>().To<RoleRepository>().InRequestScope();
kernel.Bind<IAddressRepository>().To<AddressRepository>().InRequestScope();
kernel.Bind<IProjectImageRepository>().To<ProjectImageRepository>().InRequestScope();
Repository
public class ProjectRepository : IProjectRepository
{
private readonly BindSolutionContext _context;
public ProjectRepository(BindSolutionContext context)
{
_context = context;
}
public IQueryable<Project> Query(params Expression<Func<Project, object>>[] includeProperties)
{
return includeProperties.Aggregate<Expression<Func<Project, object>>,
IQueryable<Project>>(_context.Projects, (current, includeProperty) => current.Include(includeProperty));
}
public IQueryable<Project> Query(int pageIndex, int pageSize, params Expression<Func<Project, object>>[] includeProperties)
{
return includeProperties.Aggregate<Expression<Func<Project, object>>,
IQueryable<Project>>(_context.Projects, (current, includeProperty) => current.Include(includeProperty)).OrderBy(p => p.Name).Skip(pageIndex).Take(pageSize);
}
//Rest of Implementation
}
For ProjectImageRepository, AddressRepository, RoleRepository and UserRepository implementation follows the same model!
public class BindUserProvider : MembershipProvider
{
[Inject]
public IUserService UserService { get; set; }
//Rest of implementation
}
public class BindRoleProvider : RoleProvider
{
private IRoleService _roleServ;
private IRoleService RoleServ { get { return _roleServ ?? (_roleServ = DependencyResolver.Current.GetService<IRoleService>()); } }
private IUserService _userServ;
private IUserService UserServ { get { return _userServ ?? (_userServ = DependencyResolver.Current.GetService<IUserService>()); } }
//Rest of implementation
}
As the scope is request, the Ninject should dispose of object at then end of the request. But in some situations, dispose occurs before finalizing the request.
Attempts
I'm not sure if the problem is related to the Custom membership, but did some testing. follows:
Ninject
kernel.Bind<BindSolutionContext>().ToSelf().InTransientScope();
kernel.Bind<IProjectRepository>().To<ProjectRepository>().InSingletonScope();
kernel.Bind<IUserRepository>().To<UserRepository>().InSingletonScope();
kernel.Bind<IRoleRepository>().To<RoleRepository>().InSingletonScope();
kernel.Bind<IAddressRepository>().To<AddressRepository>().InSingletonScope();
kernel.Bind<IProjectImageRepository>().To<ProjectImageRepository>().InSingletonScope();
So there is no more error!
But another problem arises! As the repository and context are singleton objects are not updated.
For example, if I register a new address for the project, the collection project.Addresses is not updated !
Note: The address is registered in the database without any problems!
Membership and RoleProviders have a longer lifecycle than a request. Objects should never depend on shorter lived objects (unless locally created and destroyed during a method execution) because they would end up referencing disposed objects.
Since you want a new context foreach request to avoid having cached objects you must not inject the context into the repositories but pass it from outside with the method call and either create it in the services or the providers using a factory.
To avoid this exception, use DependencyResolver.Current.GetService() instead of injected properties in classes that have long life cycle (action filters, membership providers etc.). This approach is not test friendly, but it lets you access a data context instance of the current http-request when you use InRequestScope().
I removed dependency injection and did it this way...
public class CustomRoleProvider:RoleProvider
{
private IGroupService _groupService;
private MyDbContext context;
public CustomRoleProvider()
{
// _groupService = DependencyResolver.Current.GetService<IGroupService>();
context = new MyDbContext();
_groupService = new GroupService(new GroupRepository(context), new AccountRepository(context));
}
}

Resources