I try to build WPF application to interact with database through Repositories above Entity framework, and I use caliburn micro as MVVM framework
the problem is when I try to inject Repertories in ViewModels Through Simple Container it does not instantiate My DbContext
Repository
public class UserRepo : IUserRepo
{
private AppDb _ctx;
public UserRepo(AppDb ctx)
{
_ctx = ctx;
}
}
application context
public class AppDb : DbContext
{
public AppDb(DbContextOptions options) : base(options)
{
}
public DbSet<User> Users { get; set; }
}
configuration on Simple Container
class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container = new SimpleContainer();
private AppDb _db;
public Bootstrapper()
{
Initialize();
var options = new DbContextOptionsBuilder<AppDb>()
.UseSqlServer(#"Server=(localdb)\MSSQLLocalDB;Database=XRaySystem;Integrated Security=True;")
.Options;
_db = new AppDb(options);
}
protected override void Configure()
{
_container.Instance(_container);
_container
.Singleton<IWindowManager, WindowManager>()
.Singleton<IEventAggregator, EventAggregator>();
//register the DataContext
// i don't know how to add it
_container.RegisterInstance(typeof(AppDb), null, _db); // <<<<<<<<<< how to add this correctly
//Register Reporisotries
_container
.PerRequest<IUserRepo, UserRepo>();
//Register ViewModels
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => _container.RegisterPerRequest(
viewModelType, viewModelType.ToString(), viewModelType));
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<DashBoardViewModel>();
//base.OnStartup(sender, e);
}
protected override object GetInstance(Type service, string key)
{
return _container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}
View model
class DoctorViewModel : Screen
{
private readonly IUserRepo _userRepo;
public DoctorViewModel(IUserRepo userRepo)
{
_userRepo = userRepo;
}
}
UserRepo is instantiated but with null AppDb
my Question
How to configure Simple Container to Add AppDb to UserRepo?
I have reproduced the same problem in on GitHub
After some debugging, I found that Configure Method run first then the constructor call !! So the instantiation is happened after the Configuration already done with null
I solve it by adding the instantiation of _db in Configure method itself
class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container = new SimpleContainer();
private AppDb _db;
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
var options = new DbContextOptionsBuilder<AppDb>()
.UseSqlServer(#"Server=(localdb)\MSSQLLocalDB;Database=XRaySystem;Integrated Security=True;")
.Options;
_db = new AppDb(options); //<<<< solve the problem
_container.Instance(_container);
_container
.Singleton<IWindowManager, WindowManager>()
.Singleton<IEventAggregator, EventAggregator>();
//register the DataContext
_container.Instance(_db);
// _container.RegisterInstance(typeof(AppDb), null, _db);
//Register Reporisotries
_container
.PerRequest<IUserRepo, UserRepo>();
//Register ViewModels
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => _container.RegisterPerRequest(
viewModelType, viewModelType.ToString(), viewModelType));
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<DashBoardViewModel>();
//base.OnStartup(sender, e);
}
protected override object GetInstance(Type service, string key)
{
return _container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}
Related
I want to add my AppDbContext for every request in my WPFapp.
I'm using caliburn.micro's simple container for dependency injection and I'm wondering How should I register my AppDbContext per request.
Because I should register a pre-constructed instance of it for configure method using _container.Instance(_db) or it will throw a null exception when I start the app.
after that if I say _container.PerRequest<AppDbContext, AppDbContext>() for example, it will throw an exception and says that "Sequence contains more than one element".
if I don't use PerRequest<AppDbContext, AppDbContext>() It will throw a null exception in the class where I Injected AppDbContext in constructor.
How should I register this service?
my Bootstrapper Class:
public class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container = new SimpleContainer();
private AppDbContext _db;
public Bootstrapper()
{
Initialize();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseJet(#"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=F:\AccessDatabase \TestDatabase1.accdb;")
.Options;
_db = new AppDbContext(options);
ConventionManager.AddElementConvention<PasswordBox>(
PasswordBoxHelper.BoundPasswordProperty,
"Password",
"PasswordChanged");
}
protected override void Configure()
{
_container.Instance(_container);
_container.Singleton<IWindowManager, WindowManager>();
_container.Singleton<IEventAggregator, EventAggregator>();
_container.PerRequest<DataAccess, DataAccess>();
_container.Instance(_db);
_container.PerRequest<AppDbContext, AppDbContext>();
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => _container.RegisterPerRequest(
viewModelType, viewModelType.ToString(), viewModelType));
}
protected override async void OnStartup(object sender, StartupEventArgs e)
{
await DisplayRootViewForAsync<ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
return _container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}
My migration for my SqLite database is throwing an error:
Microsoft.EntityFrameworkCore.Design.OperationException: Unable to create an object of type 'ProductDbContext'.
---> System.MissingMethodException: No parameterless constructor defined for type 'WpfApp1.Data.ProductDbContext'.
Here is my DbContext
public class ProductDbContext : DbContext
{
#region Constructor
public ProductDbContext(DbContextOptions<ProductDbContext> options) : base(options)
{
Database.EnsureCreated();
}
#endregion
#region Public properties
public DbSet<Product> Products { get; set; }
public DbSet<Category> Types { get; set; }
#endregion
#region Overridden methods
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new ProductDbConfig());
modelBuilder.ApplyConfiguration(new CategoryDbConfig());
modelBuilder.Entity<Product>().HasData(GetProducts());
base.OnModelCreating(modelBuilder);
}
private Product[] GetProducts()
{
// create seed data
}
}
I have an App.xaml.cs where I do my configuration
public partial class App : Application
{
#region Private members
private readonly ServiceProvider serviceProvider;
#endregion
#region Constructor
public App()
{
ServiceCollection services = new ServiceCollection();
services.AddDbContext<ProductDbContext>(options =>
{
options.UseSqlite("Data Source = Product.db");
});
services.AddSingleton<MainWindow>();
serviceProvider = services.BuildServiceProvider();
}
#endregion
#region Event Handlers
private void OnStartup(object s, StartupEventArgs e)
{
var mainWindow = serviceProvider.GetService<MainWindow>();
mainWindow.Show();
}
#endregion
}
I tried adding a parameterless constructor in my dbContext. Didn't work.
What is that causes this error? And how to solve this?
Any help is appreciated!
In my Xamarin.Forms application I use it this way inside the DbContext class
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Specify that we will use sqlite and the path of the database here
var options = optionsBuilder
.UseSqlite($"Data Source={SQLiteDatabasePath}");
base.OnConfiguring(options);
}
== EDIT ==
Singleton construction in my base class for all services:
private static ETabberDb _DbContext;
protected ETabberDb ETabberDbContext
{
get
{
if (_DbContext == null)
{
var dbPath = DeviceService.GetSQLiteDatabasePath();
if (!File.Exists(dbPath))
{
File.Create(dbPath).Dispose();
new SQLite.SQLiteConnection(dbPath).Dispose();
}
ETabberDb.SQLiteDatabasePath = dbPath;
_DbContext = new ETabberDb();
}
return _DbContext;
}
}
I would like to switch from Autofac to SimpleContainer (Caliburn.Micro) due the really cool WindowManager function and a I would like to use Caliburn.Micro.
Right now my Autofac stuff looks like this:
Configure
public static IContainer Configure()
{
var builder = new ContainerBuilder();
builder.RegisterType<DbContext>().As<IDbContext>();
builder.RegisterType<DepartmentRepository>().As<IDepartmentRepository>();
return builder.Build();
}
Usage
var container = ContainerConfig.Configure();
using (var scope = container.BeginLifetimeScope())
{
var test = scope.Resolve<IDepartmentRepository>();
dept = test.GetById(DepartmentFK);
}
I already created a new Bootstrapper file
#region SimpleContainer Creation, Configuration and Registration
private SimpleContainer _container = new SimpleContainer();
protected override void Configure()
{
_container.Instance(_container);
_container
.Singleton<IWindowManager, WindowManager>()
.Singleton<IEventAggregator, EventAggregator>();
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => _container.RegisterPerRequest(
viewModelType, viewModelType.ToString(), viewModelType));
}
protected override object GetInstance(Type service, string key)
{
return _container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
#endregion
How would the using stuff (scope.resolve<>) look like using SimleContainer instead of Autofac?
I am using Autofac for DI and i have NacyModule like:
public class TestModule: NancyModule
{
public ISessionFactory SessionFactory { get; set; }
public IMapper Mapper { get; set; }
public TestModule(ITestRepository testRepository)
{
Get("hello", _ => "hello world");
}
}
My AutoFac configuration
In Startup.cs
var builder = new ContainerBuilder();
builder.RegisterModule(new ServicesModule());
builder.RegisterModule(new NHibernateModule(configuration.GetConnectionString("DefaultConnection")));
builder.RegisterModule(new AutomapperModule());
builder.Populate(services);
container = builder.Build();
return new AutofacServiceProvider(container);
in ServiceModule.cs
builder.RegisterAssemblyTypes(ThisAssembly)
.Where(t => new[]
{
"Processor",
"Process",
"Checker",
"Indexer",
"Searcher",
"Translator",
"Mapper",
"Exporter",
"Repository" }.Any(y =>
{
var a = t.Name;
return a.EndsWith(y);
}))
.AsSelf()
.AsImplementedInterfaces()
.PropertiesAutowired()
.InstancePerLifetimeScope();
in NHibernateModule.cs
builder.Register(c => CreateConfiguration(connectionString)).SingleInstance();
builder.Register(c => c.Resolve<Configuration>().BuildSessionFactory()).As<ISessionFactory>().SingleInstance().PropertiesAutowired();
And in my nancy bootstraper I have something like this
public class Bootstrapper : AutofacNancyBootstrapper
{
private static readonly ILogger logger = LogManager.GetLogger(typeof(Bootstrapper).FullName);
private readonly ILifetimeScope _container;
public Bootstrapper(ILifetimeScope container)
{
_container = container;
}
protected override ILifetimeScope GetApplicationContainer()
{
return _container;
}
public override void Configure(INancyEnvironment environment)
{
base.Configure(environment);
environment.Tracing(false, true);
}
protected override void ConfigureRequestContainer(ILifetimeScope container, NancyContext context)
{
container.Update(builder =>
{
builder.Register(c =>
{
var sf = c.Resolve<ISessionFactory>();
return new Lazy<NHibernate.ISession>(() =>
{
var s = sf.OpenSession();
s.BeginTransaction();
return s;
});
}).InstancePerLifetimeScope();
builder.Register(c => c.Resolve<Lazy<NHibernate.ISession>>().Value).As<NHibernate.ISession>();
});
}
}
I now about constructor injection, works ok, and property injection works ok in other classes, but not works in nancy modules
Note I tried adding .PropertiesAutowired() in ConfigureRequestContainer after the container update
thanks.
The AutofacNancyBootstrapper class automatically register the module in Autofac even if the service is already registered :
AutofacNancyBootstrapper.cs
protected override INancyModule GetModule(ILifetimeScope container, Type moduleType)
{
return container.Update(builder => builder.RegisterType(moduleType)
.As<INancyModule>())
.Resolve<INancyModule>();
}
With the default implementation the module is always registered and PropertiesAutoWired is not applied.
To change this, you can override the method like this :
protected override INancyModule GetModule(ILifetimeScope container, Type moduleType)
{
return container.Update(builder => builder.RegisterType(moduleType)
.As<INancyModule>())
.Resolve<INancyModule>()
.PropertiesAutoWired();
}
Or change it like this :
protected override INancyModule GetModule(ILifetimeScope container, Type moduleType)
{
INancyModule module = null;
if (container.IsRegistered(moduleType))
{
module = container.Resolve(moduleType) as INancyModule;
}
else
{
IEnumerable<IComponentRegistration> registrations = container.ComponentRegistry.RegistrationsFor(new TypedService(typeof(INancyModule)));
IComponentRegistration registration = registrations.FirstOrDefault(r => r.Activator.LimitType == moduleType);
if (registration != null)
{
module = container.ResolveComponent(registration, Enumerable.Empty<Parameter>()) as INancyModule;
}
else
{
module = base.GetModule(container, moduleType);
}
}
return module;
}
and then register the module in your composition root
builder.RegisterType<TestModule>()
.As<INancyModule>()
.PropertiesAutoWired()
I have 3 tier architecture application. my Data Access is like below:
ICategoryRepository.cs
public interface ICategoryRepository
{
void Add(Category category);
}
CategoryRepository.cs
internal class CategoryRepository : ICategoryRepository
{
void ICategoryRepository.Add(Category category)
{
// Dbcontext goes here
}
}
And I have a Autofac model to register the above classes in autofac container is:
public class RepositoryModule : Module
{
protected override void Load(ContainerBuilder builder)
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces();
base.Load(builder);
}
}
And I have a service layer as below:
ICategoryService.cs
public interface ICategoryService
{
void Add(Category category);
}
CategoryService.cs
internal class CategoryService : ICategoryService
{
private ICategoryRepository _categoryRepository;
public CategoryService(ICategoryRepository categoryRepository)
{
_categoryRespoitory = categoryRepository;
}
void ICategoryService.Add(Category category)
{
_categoryRepository.Add(category);
}
}
Similarly, I have a module to register the above class in container as,
public class ServiceModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
And in my Web, Global.asax.cs:
public class Global : System.Web.HttpApplication, IContainerProviderAccessor
{
static IContainerProvider _containerProvider;
public IContainerProvider ContainerProvider
{
get { return _containerProvider; }
}
void Application_Start(object sender, EventArgs e)
{
var builder = new ContainerBuilder();
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
_containerProvider = new ContainerProvider(builder.Build());
}
}
Here my question is, how can I call the service and DataAccess module from the web.
I don't have the data access reference in my web, but I have reference of service module in my web
You can use the RegisterAssemblyModules method
void Application_Start(object sender, EventArgs e)
{
var builder = new ContainerBuilder();
IEnumerable<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies();
if (HostingEnvironment.InClientBuildManager)
{
assemblies = assemblies.Union(BuildManager.GetReferencedAssemblies()
.Cast<Assembly>())
.Distinct();
}
builder.RegisterAssemblyModules(assemblies);
_containerProvider = new ContainerProvider(builder.Build());
}
In order to let this code works, all your assemblies should be available on the bin folder.
I use BuildManager.GetReferencedAssemblies() to avoid issue after ASP.net recycling process. See IIS Hosted Web Application on autofac documentation for more explanation.