I use Ninject all the time with my MVC 3 applications, but I'm trying to change the Pattern for my Data Objects to use UnitOfWork and I'm having trouble figuring out how to get Ninject to handle this properly.
I know my implementation of classes work when they are constructed manually like this in my console application:
IDatabaseFactory factory = new DatabaseFactory();
IUnitOfWork worker = new UnitOfWork(factory);
IBlogCategoryDao dao = new BlogCategoryDao(factory);
IBlogCategoryService service = new BlogCategoryService(dao);
BlogCategory category = service.GetById(id);
try
{
if (category != null)
{
service.Delete(category);
worker.Commit();
Console.WriteLine("Category deleted successfully!");
}
else
{
Console.WriteLine("Entity doesn't exist.");
}
}
catch (Exception ex)
{
Console.WriteLine("Error deleting category: {0}", ex.Message);
}
In my MVC 3 application I'm using the Ninject.MVC3 NuGet package, and this is in the RegisterServices method.
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IDatabaseFactory>().To<DatabaseFactory>();
kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();
kernel.Bind<IBlogCategoryDao>().To<BlogCategoryDao>();
kernel.Bind<IBlogDao>().To<BlogDao>();
kernel.Bind<IBlogCategoryService>().To<BlogCategoryService>();
kernel.Bind<IBlogService>().To<BlogService>();
}
While this works for the most part, Get requests, all POST requests (Insert, Update, Delete) don't get executed. There is no exception thrown and when I step through it, it goes through the SaveChanges() method without a problem and returns back up the stack, but nothing is executed. So I know I must be missing something with my Ninject configuration.
Here's my Unit of Work class.
public class UnitOfWork : IUnitOfWork
{
private Database _database; <-- DbContext derived class
private readonly IDatabaseFactory _databaseFactory;
public UnitOfWork(IDatabaseFactory databaseFactory)
{
this._databaseFactory = databaseFactory;
}
public Database Database
{
get
{
return _database ?? (_database = _databaseFactory.Get());
}
}
public void Commit()
{
Database.Commit();
}
}
Here's the DatabaseFactory class:
public class DatabaseFactory : Disposable, IDatabaseFactory
{
private Database _database;
public DatabaseFactory()
{
}
public virtual Database Get()
{
if (_database == null)
{
_database = DataObjectFactory.CreateContext();
}
return _database;
}
protected override void DisposeCore()
{
if (_database != null)
{
_database.Dispose();
}
}
}
And my DataObjectFactory class:
public static class DataObjectFactory
{
private static readonly string _connectionString;
/// <summary>
/// Static constructor. Reads the connectionstring from web.config just once.
/// </summary>
static DataObjectFactory()
{
string connectionStringName = ConfigurationManager.AppSettings.Get("ConnectionStringName");
_connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
}
/// <summary>
/// Creates the Context using the current connectionstring.
/// </summary>
/// <remarks>
/// Gof pattern: Factory method.
/// </remarks>
/// <returns>Action Entities context.</returns>
public static Database CreateContext()
{
return new Database(_connectionString);
}
}
This is a similar pattern as used in the EFMVC CodePlex application, but I don't use AutoFac.
Any thoughts on this are appreciated.
Thanks.
I just do this:
kernel.Bind<IUnitOfWork>.To<EFUnitOfWork>().InRequestScope();
EFUnitOfWork.cs
public class EFUnitOfWork : DbContext, IUnitOfWork
{
// your normal DbContext plus your IUnitOfWork members that delegate to EF context
}
Since EF already implements a form of Unit Of Work, this allows you to use a more generic interface for it, and inject it easily.
Also, you can implement the EF constructors for connection strings and just pass them to base constructors. Then you can use the Ninject .WithConstructorArgument() to configure the connection string using your AppSettings code.
Related
I want a simple static class that accesses the Configuration object. All the config info is already read in from the appsettings.json file in the Startup class. I just need an easy way to access it. Is this possible?
namespace MyNamespace
{
public static class Config
{
public string Username => Configuration["Username"];
public string Password => Configuration["Password"];
}
}
Anywhere else in the app:
string username = Config.Username;
string password = Config.Password;
A slightly shorter version based on the same principle as above...
public Startup(IConfiguration configuration)
{
Configuration = configuration;
StaticConfig = configuration;
}
public static IConfiguration StaticConfig { get; private set; }
To use in another static class:
string connString = Startup.StaticConfig.GetConnectionString("DefaultConnection");
create the ConfigurationHelper static class in the service layer, so it can be used in other layers without circular dependency.
public static class ConfigurationHelper
{
public static IConfiguration config;
public static void Initialize(IConfiguration Configuration)
{
config = Configuration;
}
}
initialize the ConfigurationHelper inside the ConfigureServices method in the Startup class.
ConfigurationHelper.Initialize(Configuration);
Use it wherever you want including your static classes
e.g: ConfigurationHelper.config.GetSection("AWS:Accesskey").Value;
After much research, this works (in ASPNetCore 2.2) for accessing the appsettings.json config from a static class but for some reason appsettings.development.json no longer loads properly but it might be something else in my project messing that up. The reloadOnChange does work. As a bonus it also has IHostingEnvironment and IHttpContextAccessor. While this works, I have recently decided to switch back to a more DI approach to follow the paradigm shift as others have mentioned.
So here is one of many ways to access some DI stuff (including the configuration) in a static class:
AppServicesHelper.cs:
public static class AppServicesHelper
{
static IServiceProvider services = null;
/// <summary>
/// Provides static access to the framework's services provider
/// </summary>
public static IServiceProvider Services
{
get { return services; }
set
{
if (services != null)
{
throw new Exception("Can't set once a value has already been set.");
}
services = value;
}
}
/// <summary>
/// Provides static access to the current HttpContext
/// </summary>
public static HttpContext HttpContext_Current
{
get
{
IHttpContextAccessor httpContextAccessor = services.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
return httpContextAccessor?.HttpContext;
}
}
public static IHostingEnvironment HostingEnvironment
{
get
{
return services.GetService(typeof(IHostingEnvironment)) as IHostingEnvironment;
}
}
/// <summary>
/// Configuration settings from appsetting.json.
/// </summary>
public static MyAppSettings Config
{
get
{
//This works to get file changes.
var s = services.GetService(typeof(IOptionsMonitor<MyAppSettings>)) as IOptionsMonitor<MyAppSettings>;
MyAppSettings config = s.CurrentValue;
return config;
}
}
}
}
Startup.cs:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddHttpContextAccessor();//For HttpContext.
// Register the IOptions object
services.Configure<MyAppSettings>(Configuration.GetSection(nameof(MyAppSettings)));
//Explicitly register the settings object by delegating to the IOptions object so that it can be accessed globally via AppServicesHelper.
services.AddSingleton(resolver => resolver.GetRequiredService<IOptionsMonitor<MyAppSettings>>().CurrentValue);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//...
AppServicesHelper.Services = app.ApplicationServices;
//...
}
Controller:
public class MyController: Controller
{
public MyController()
{
}
public MyAppSettings Config => AppServicesHelper.Config;
public async Task<IActionResult> doSomething()
{
testModel tm = await myService.GetModel(Config.Setting_1);
return View(tm);
}
}
Another class library:
public static class MyLibraryClass
{
public static string GetMySetting_ => AppServicesHelper.Config.Setting_1;
public static bool IsDev => AppServicesHelper.HostingEnvironment.IsDevelopment();
}
MyAppSettings.cs is any class that maps to a MyAppSettings section in appsettings.json:
public class MyAppSettings
{
public string Setting_1 {get;set;}
}
appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"MyAppSettings": {
"Setting_1": "something"
}
}
I've similar problems, and after reading the document from Microsoft. I've resolved it:
Create an static ApplicationSettings class
public static class ApplicationSettings
{
public const string User = "UserOptions";
public static UserOptions UserOptions { get; set; } = new UserOptions();
// other options here...
}
Create an options class
public class UserOptions
{
public string Username { get; set; };
public string Password { get; set; };
}
Init the Options class in Program.cs
ConfigurationManager configuration = builder.Configuration;
configuration.GetSection(ApplicationSettings.User).Bind(ApplicationSettings.UserOptions);
Update your appsettings.json
{
"UserOptions": {
"Username": "input_your_username_here",
"Password": "input_your_password_here"
}
}
You can call it anywhere in your project now
public void MethodA()
{
string username = ApplicationSettings.UserOptions.Username;
string password = ApplicationSettings.UserOptions.Password;
}
public static void MethodB()
{
string username = ApplicationSettings.UserOptions.Username;
string password = ApplicationSettings.UserOptions.Password;
}
Hope this could help you well.
I agree with mcbowes, it's in the docs, but the first example looks more like what you need...want:
public class Program
{
public static IConfigurationRoot Configuration { get; set; }
public static void Main(string[] args = null)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
Console.WriteLine($"option1 = {Configuration["option1"]}");
// Edit:
IServiceCollection services = new ServiceCollection();
services.AddOptions();
services.Configure<HelloWorldOptions>(_configuration.GetSection("HelloWorld"));
// And so on...
}
}
Try avoid using a static class and use DI
namespace MyNamespace {
public interface IConfig {
string Username { get; }
string Password { get; }
}
public class Config : IConfig {
public Config(IConfiguration configuration) {
_configuration = configuration;
}
readonly IConfiguration _configuration;
public string Username => _configuration["Username"];
public string Password => _configuration["Password"];
}
}
The setup DI in StartUp class
public class Startup {
public void ConfigureServices(IServiceCollection services) {
//...
services.AddTransient<IConfig, Config>();
...
}
}
And use it like so
public class TestUsage {
public TestUsage(IConfig config) {
_config = config;
}
readonly IConfig _config;
public string Username => _config.Username;
public string Password => _config.Password;
}
You can use Signleton pattern to access your configurations from anywhere
public class ConnectionStrings
{
private ConnectionStrings()
{
}
// property with getter only will not work.
public static ConnectionStrings Instance { get; protected set; } = new ConnectionStrings();
public string DatabaseConnection { get; set; }
}
and in your startup class
public class Startup
{
private readonly IConfiguration configuration;
public Startup(IConfiguration configuration)
{
this.configuration = configuration;
configuration.GetSection("ConnectionStrings").Bind(ConnectionStrings.Instance);
}
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app)
{
}
}
This has already been said but I'm going to say it.
I believe .Net Core wants developers to get values through Dependency Inject. This is what I've noticed from my research but I am also speculating a bit. As developers, we need to follow this paradigm shift in order to use .Net Core well.
The Options Pattern is a good alternative to the static config. In your case, it'll look like this:
appsettings.json
{
"Username": "MyUsername",
"Password": "Password1234"
}
SystemUser.cs
public class SystemUser
{
public string Username { get; set; } = "";
public string Password { get; set; } = "";
}
Startup.cs
services.Configure<SystemUser>(Configuration);
And to use the SystemUser class, we do the following.
TestController.cs
public class TestController : Controller
{
private readonly SystemUser systemUser;
public TestController(IOptionsMonitor<SystemUser> systemUserOptions)
{
this.systemUser = systemUserOptions.CurrentValue;
}
public void SomeMethod()
{
var username = this.systemUser.Username; // "MyUsername"
var password = this.systemUser.Password; // "Password1234"
}
}
Even though we are not using a static class, I think this is the best alternative that fits your needs. Otherwise, you might have to use a static property inside the Startup class which is a scary solution imo.
Personally I like the method used in this link
Essentially it just adding a static field to your options class.
public class WeblogConfiguration
{
public static WeblogConfiguration Current;
public WeblogConfiguration()
{
Current = this;
}
}
Then in any static class you can do:
WeblogConfiguration.Current
Simple and very straight forward
If you are using environment variables as your configuration, you can access the environment variable directly rather than via the configuration object.
using System;
namespace My.Example
{
public static class GetPaths
{
private static readonly string MyPATH =
Environment.GetEnvironmentVariable("PATH");
private static readonly string MySpecialPath =
Environment.GetEnvironmentVariable("PREFIX_SpecialPath");
...
}
}
I think you could use extension function, something like this
public static string ConfigToSomeThing(this IConfiguration config, int value)
{
return config[value.ToString()] ?? "";
}
Then any place , just injection IConfiguration and use extension method
_systemConfiguration.ConfigToSomeThing(123);
I just created below class:
/// <summary>
///
/// </summary>
public static class ConfigurationManager
{
/// <summary>
///
/// </summary>
public sealed class ConfigurationManagerAppSettings
{
/// <summary>
///
/// </summary>
internal ConfigurationManagerAppSettings() { }
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public string this[string key] => (TheConfiguration ?? throw new Exception("Set ConfigurationManager.TheConfiguration in Startup.cs")).GetSection($"AppSettings:{key}").Value;
}
/// <summary>
///
/// </summary>
public static IConfiguration? TheConfiguration { get; set; }
/// <summary>
///
/// </summary>
public static readonly ConfigurationManagerAppSettings AppSettings = new ConfigurationManagerAppSettings();
}
and below is my code:
public class Startup
{
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
ConfigurationManager.TheConfiguration = Configuration;
I've used this approach and it seems to work well. In your static class, add a public IConfiguration property.
namespace MyNamespace
{
public static class Config
{
public static IConfiguration Configuration { get; set; }
public string Username => Configuration["Username"];
public string Password => Configuration["Password"];
}
}
In Startup.cs constructor, use dependency injection to get the configuration, and then assign it to the public member of your static class.
public class Startup
{
public Startup(IConfiguration configuration)
{
MyNamespace.Config.Configuration = configuration;
}
// The rest of the startup code...
}
I was getting same issue and I wanted to access app setting in static class so I put this solution. Write this below code in static class in my code class name was EncryptionUtility
private static string _saltKey = string.Empty;
private static IConfiguration configuration;
public static void AppSettingsConfigure(IConfiguration _config)
{
configuration = _config;
_saltKey = Convert.ToString(configuration["Security:EncyptPassword"]);
}
Call Appsettingconfiure from the program.cs file. Here, EncryptionUtility is static class or you can only write static method as your requirement. I had put this line above app.Run();
EncryptionUtility.AppSettingsConfigure(app.Services.GetRequiredService<IConfiguration>());
appsettings.json :
{
"Security": {
"EncyptPassword": "Password"
}
}
Here is a way to obtain the configuration values from a NET.Core page without having to reference these statically but then still being able to pass them to other static functions called from the non-static class.
At the top of your non-static class add this:
private readonly IConfiguration _configuration;
Then in the constructor function bring in the existing configuration as input to the function:
IConfiguration configuration
Then assign the configuration to your read only variable inside the constructor function:
_configuration = configuration;
Here is an example of what it should look like:
public class IndexModel : PageModel
{
private readonly IConfiguration _configuration;
public IndexModel(IConfiguration configuration)
{
_configuration = configuration;
}
}
After this you can reference the configuration in any function in the class by referencing _configuration and can even then pass this on to other static functions that you call from other classes:
public async Task OnGetAsync()
{
AnotherClass.SomeFunction(_configuration);
}
Then in the called static class I can make use of the configuration values:
public static string SomeFunction(IConfiguration configuration)
{
string SomeValue = configuration.GetSection("SomeSectionOfConfig")["SomeValue"];
}
I have a class that calls some stored procedures for viewing and amending data and passes parameter values from appsettings.json using this approach.
Consider using the instructions here for ASP.NET Core Configuration.
You can create a class to store your configuration settings and then access the values, something like this:
_config.UserName
In Startup - ConfigureServices:
services.Configure<Config>(Configuration.GetSections("General"));
Then just inject your object wherever you need as:
IOptions<Config> config
The IConfiguration is Injectable anywhere within the Project. But in the case of static class, the option I am using and maybe only approach...
var Configuration = new ConfigurationBuilder()
.AddUserSecrets<Startup>()
.Build();
And, you can add required section, such in this code block above, I added 'UserSecrets'.
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.
I am trying to implement an abstract repository pattern as described in THIS post. I'm getting the error message
'C' does not contain a definition for 'Set' and no extension method
'Set' accepting a first argument of type 'C' could be found (are you
missing a using directive or an assembly reference?)
where C is the DBContext
namespace Rental.Data.Entity.Repository
{
public abstract class GenericRepo<C, T> :
IGenericRepo<T> where T : class where C : RentalContainer, new()
{
private C _DBContext = new C();
protected C DBContext
{
get { return _DBContext; }
set { _DBContext = value; }
}
public virtual IQueryable<T> GetAll()
{
IQueryable<T> query = _DBContext.Set<T>(); <-- here is gives the error
return query;
}
yet another update
public partial class RentalContainer : ObjectContext
{
#region Constructors
/// <summary>
/// Initializes a new RentalContainer object using the connection string found in the 'RentalContainer' section of the application configuration file.
/// </summary>
public RentalContainer() : base("name=RentalContainer", "RentalContainer")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
/// <summary>
/// Initialize a new RentalContainer object.
/// </summary>
public RentalContainer(string connectionString) : base(connectionString, "RentalContainer")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
ObjectContext does not have a Set method. It has CreateObjectSet method
public abstract class GenericRepo<C, T> : IGenericRepo<T>
where T : class
where C : RentalContainer, new()
{
private C _DBContext = new C();
protected C DBContext
{
get { return _DBContext; }
set { _DBContext = value; }
}
public virtual IQueryable<T> GetAll()
{
IQueryable<T> query = _DBContext.CreateObjectSet<T>();
return query;
}
}
Add reference to EntityFramework.dll
http://msdn.microsoft.com/en-us/library/gg679544(v=vs.103).aspx
Press ctrl + . and use Microsoft.EntityFrameworkCore; I had to add. But I got this error when I accidentally created an empty class named DbContext.
The solution for me is to delete the empty DbContext class and add the correct using line to DbContext. (using Microsoft.EntityFrameworkCore;)
Make sure your DataContext class extends DBContext
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));
}
}
public ActionResult Edit(int id)
{
var productBrand = brandRepo.FindProductBrand(id);
ProductBrandModel model = Mapper.Map<ProductBrand, ProductBrandModel>(productBrand);
return View(model);
}
[HttpPost]
public ActionResult Edit(ProductBrandModel model)
{
if (ModelState.IsValid)
{
var productBrand = brandRepo.FindProductBrand(model.BrandId);
productBrand.Name = model.Name;
//How to persist that information?
}
}
I have a EF generate class ProductBrand and a model for views called ProductBrandModel.
How would I persist the information of an edit using Entity Framework? Should my brandRepo have a void method called SaveChanges where in it I would go:
public void SaveChanges()
{
dbEntities.SaveChanges();
}
As you correctly assume, you have to commit your changes to the database using the .SaveChanges() method. In your case, brandRepo.SaveChanges() would delegate to dbEntities.SaveChanges().
As a side note: In simple cases a separate repository class only introduces complexity without really providing any benefit. Entity Framework's DbContext pretty much resembles a simple repository itself, so you don't need one on top.
Of course, for the sake of testability an indirection layer might make sense.
Without the repository your code could look somewhat like this:
public ActionResult Edit(int id)
{
var productBrand = dbEntities.ProductBrands.Find(x => x.BrandId = id);
ProductBrandModel model = Mapper.Map<ProductBrand, ProductBrandModel>(productBrand);
return View(model);
}
[HttpPost]
public ActionResult Edit(ProductBrandModel model)
{
if (ModelState.IsValid)
{
var productBrand = dbEntities.ProductBrands.Find(x => x.BrandId = id);
// or something similar, I don't know the inner workings of your
// brandRepo.FindProductBrand(id)
productBrand.Name = model.Name;
dbEntities.SaveChanges();
}
}
I like to have a save method in my repository in conjunction with an entity framework helper method I got from the net. The SaveCustomer is my repository class method and below it is the helper class. In your case you would pass your model into
brandRepository.SaveProdctBrand(productBrand)
(helps to spell out the names for good naming conventions and fxcop rules)
public void SaveCustomer(Customer customer)
{
using (var ctx = new WebStoreEntities())
{
if (customer.CustomerId > 0)
{
//It's an existing record, update it.
ctx.Customers.AttachAsModified(customer);
ctx.SaveChanges();
}
else
{
//its a new record.
ctx.Customers.AddObject(customer);
ctx.SaveChanges();
}
}
}
The helper class is as follows
public static class EntityFrameworkExtensions
{
/// <summary>
/// This class allows you to attach an entity.
/// For instance, a controller method Edit(Customer customer)
/// using ctx.AttachAsModified(customer);
/// ctx.SaveChanges();
/// allows you to easily reattach this item for udpating.
/// Credit goes to: http://geekswithblogs.net/michelotti/archive/2009/11/27/attaching-modified-entities-in-ef-4.aspx
/// </summary>
public static void AttachAsModified<T>(this ObjectSet<T> objectSet, T entity) where T : class
{
objectSet.Attach(entity);
objectSet.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
}
/// <summary>
/// This marks an item for deletion, but does not currently mark child objects (relationships).
/// For those cases you must query the object, include the relationships, and then delete.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="objectSet"></param>
/// <param name="entity"></param>
public static void AttachAsDeleted<T>(this ObjectSet<T> objectSet, T entity) where T : class
{
objectSet.Attach(entity);
objectSet.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Deleted);
}
public static void AttachAllAsModified<T>(this ObjectSet<T> objectSet, IEnumerable<T> entities) where T : class
{
foreach (var item in entities)
{
objectSet.Attach(item);
objectSet.Context.ObjectStateManager.ChangeObjectState(item, EntityState.Modified);
}
}
}