I tried to implement connection to database using Entity Framework and Dependency Injection. I want to create Host in App.xaml.cs.
public partial class App : Application
{
public static IHost? AppHost { get; private set; }
public App()
{
AppHost = Host.CreateDefaultBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<LoginWindow>();
services.AddSingleton<LoginViewModel>();
services.AddDbContext<KnitterNotebookContext>(
options =>
{
string appSettingsPath = Path.Combine(ProjectDirectory.ProjectDirectoryFullPath, "appsettings.json");
string appSettingsString = File.ReadAllText(appSettingsPath);
AppSettings AppSettings = JsonConvert.DeserializeObject<AppSettings>(appSettingsString)!;
options.UseSqlServer(AppSettings.KnitterNotebookConnectionString);
});
})
.Build();
}
protected override async void OnStartup(StartupEventArgs e)
{
await AppHost!.StartAsync();
var startupWindow = AppHost.Services.GetRequiredService<LoginWindow>();
startupWindow.Show();
base.OnStartup(e);
}
protected override async void OnExit(ExitEventArgs e)
{
await AppHost!.StopAsync();
base.OnExit(e);
}
I want to pass DbContext as parameter to ViewModel, but when I do, it throws exception.
public class LoginViewModel : BaseViewModel
{
public LoginViewModel(KnitterNotebookContext knitterNotebookContext)
//public LoginViewModel()
{
KnitterNotebookContext = knitterNotebookContext;
ShowRegistrationWindowCommand = new RelayCommand(ShowRegisterWindow);
LogInCommandAsync = new AsyncRelayCommand(LogIn);
}
private KnitterNotebookContext KnitterNotebookContext { get; set; }
}
There is no problem if I use parameterless constructor of LoginViewModel and create new instances of KnitterNotebookContext with new(), but I want to pass it as a parameter. How to solve it?
I happened to encounter the same problem at one time - i'm guessing that you are setting DataContext for MainWindow in xaml, like this:
<Window.DataContext>
<local:LoginViewModel/>
<Window.DataContext>
Doing it this way, when your LoginWindow is created, tries to create new instance of its DataContext (your LoginViewModel), but it does not know how to resolve constructor - since you injected it with service, it's not parameterless anymore.
You have to provide a way, to enforce DataContext to be instantiated by ServiceProvider (a container for all injected services).
You could achieve this through use of a helper class, which is usable in xaml (inherits from MarkupExtension), like this one below.
public class ServiceDispatcher : MarkupExtension
{
public static Func<Type, object> Resolver { get; set; }
public Type Type { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Resolver?.Invoke(Type);
}
}
And assigning it's service dispatching Resolver delegate in your App.xaml.cs
public App()
{
AppHost = Host.CreateDefaultBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<LoginWindow>();
services.AddSingleton<LoginViewModel>();
services.AddDbContext<KnitterNotebookContext>(
options =>
{
string appSettingsPath = Path.Combine(ProjectDirectory.ProjectDirectoryFullPath, "appsettings.json");
string appSettingsString = File.ReadAllText(appSettingsPath);
AppSettings AppSettings = JsonConvert.DeserializeObject<AppSettings>(appSettingsString)!;
options.UseSqlServer(AppSettings.KnitterNotebookConnectionString);
});
})
.Build();
ConfigureServiceProvider(AppHost.Services);
}
private static void ConfigureServiceProvider(IServiceProvider appHost)
{
ServiceDispatcher.Resolver = (type) =>
{
return appHost.GetRequiredService(type);
};
}
Now you can use your ServiceDispatcher to dynamically provide DataContext instance for your Views using syntax below.
<Window.DataContext>
<base:ServiceDispatcher Type="{x:Type local:LoginViewModel}"/>
</Window.DataContext>
Related
I am building an ASP.NET Core WebAPI application, it is working perfectly fine with the below setup
public void ConfigureServices(IServiceCollection services)
{
var settings = Configuration.Get<Settings>();
CosmosClient client = new CosmosClient(settings.CosmosDB.EndpointUrl, settings.CosmosDB.PrimaryKey);
CosmosDbContainerFactory cosmosDbClientFactory = new CosmosDbContainerFactory(client, settings.CosmosDB.DatabaseName, settings.CosmosDB.Containers);
services.AddSingleton<ICosmosDbContainerFactory>(cosmosDbClientFactory);
services.AddTransient<IFamilyService, FamilyService>();
services.AddTransient<IFamilyRepository, FamilyRepository>();
services.AddControllers();
}
But while trying to replace the manual service registration with Scrutor Scan, like mentioned below
public void ConfigureServices(IServiceCollection services)
{
var settings = Configuration.Get<Settings>();
CosmosClient client = new CosmosClient(settings.CosmosDB.EndpointUrl, settings.CosmosDB.PrimaryKey);
CosmosDbContainerFactory cosmosDbClientFactory = new CosmosDbContainerFactory(client, settings.CosmosDB.DatabaseName, settings.CosmosDB.Containers);
services.AddSingleton<ICosmosDbContainerFactory>(cosmosDbClientFactory);
services.Scan(s => s
.FromAssembliesOf(typeof(IApiAssemblyMarker))
.AddClasses(false)
.UsingRegistrationStrategy(RegistrationStrategy.Append)
.AsImplementedInterfaces()
.WithTransientLifetime()
);
services.AddControllers();
}
I am getting the following error
Error while validating the service descriptor 'ServiceType: FBAuthDemoAPI.Services.Contract.IFamilyService Lifetime: Transient ImplementationType: FBAuthDemoAPI.Services.Implementation.FamilyService': Unable to resolve service for type 'Microsoft.Azure.Cosmos.CosmosClient' while attempting to activate 'FBAuthDemoAPI.CosmosDBFactory.CosmosDbContainerFactory'.
public interface IFamilyService
{
}
public class FamilyService : IFamilyService
{
private readonly IFamilyRepository _familyRepository;
public FamilyService(IFamilyRepository familyRepository)
{
this._familyRepository = familyRepository;
}
}
public interface IGenericRepository<T> where T : class
{
}
public abstract class GenericRepository<T> : IGenericRepository<T> where T : Entity
{
private readonly Container _container;
private readonly ICosmosDbContainerFactory _cosmosDbContainerFactory;
public abstract string DatabaseName { get; }
public abstract string ContainerName { get; }
protected GenericRepository(ICosmosDbContainerFactory cosmosDbContainerFactory)
{
this._cosmosDbContainerFactory = cosmosDbContainerFactory ?? throw new ArgumentNullException(nameof(ICosmosDbContainerFactory));
this._container = this._cosmosDbContainerFactory.GetContainer(ContainerName)._container;
}
}
public interface IFamilyRepository : IGenericRepository<Family>
{
}
public class FamilyRepository : GenericRepository<Family>, IFamilyRepository
{
public override string DatabaseName => "FamilyDB";
public override string ContainerName => "Family";
public FamilyRepository(ICosmosDbContainerFactory factory) :
base(factory)
{
}
}
What is the issue and How do I fix this?
Update:: Having registered the classes shown below, it started working, but I'm not understanding why it failed when registering with the Single instance like mentioned above, Is it due to the limitation of Scrutor library?
var cosmosDBSettings = new CosmosDBSettings();
configration.Bind(CosmosDBSettings.SectionName, cosmosDBSettings);
services.AddSingleton(Microsoft.Extensions.Options.Options.Create(cosmosDBSettings));
services.Scan(scan => scan
.FromAssembliesOf(typeof(IApiAssemblyMarker))
.AddClasses(classes => classes.AssignableTo<ICosmosDbContainerFactory>()) // Filter classes
.AsSelfWithInterfaces()
.WithSingletonLifetime()
.AddClasses(x => x.AssignableTo(typeof(IGenericRepository<>))) // Can close generic types
.AsMatchingInterface()
.WithScopedLifetime()
.AddClasses(x => x.AssignableTo(typeof(IFamilyService))) // Can close generic types
.AsMatchingInterface()
.WithScopedLifetime());
I'm facing a problem with Autofac registrations. In short, if I register the models BEFORE my configuration, when I load the configuration, it works smoothly but, if I register the models AFTER I register the configuration, the configuration models are loaded with their default types (default(T)). Below is the code to reproduce the problem:
using System;
using System.IO;
using Autofac;
using Microsoft.Extensions.Configuration;
namespace AutofacConfigurationTest.CrossCutting
{
public class ModuleModel : Module
{
protected override void Load(ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<Cache.Configuration>()
.As<Cache.IConfiguration>();
containerBuilder.RegisterType<Repository.Configuration>()
.As<Repository.IConfiguration>();
}
}
public class ModuleConfiguration : Module
{
protected override void Load(ContainerBuilder containerBuilder)
{
var configurationRoot = new Configuration.Container().ConfigurationRoot;
containerBuilder.RegisterInstance(configurationRoot).As<IConfigurationRoot>();
containerBuilder
.RegisterInstance(configurationRoot.GetSection(Cache.Configuration.Name)
.Get<Cache.Configuration>()).As<Cache.IConfiguration>();
containerBuilder
.RegisterInstance(configurationRoot.GetSection(Repository.Configuration.Name)
.Get<Repository.Configuration>()).As<Repository.IConfiguration>();
}
}
public class Container
{
public IContainer Kernel { get; }
public Container()
{
var containerBuilder = new ContainerBuilder();
// uncomment the line below to make it work //
containerBuilder.RegisterModule(new ModuleModel()); // if we register the models here, before the configuration, the configuration works properly //
containerBuilder.RegisterModule(new ModuleConfiguration());
// comment the line below to make it work //
containerBuilder.RegisterModule(new ModuleModel()); // if we register the models here, after the configuration, the configuration cannot load the data //
Kernel = containerBuilder.Build();
}
}
}
namespace AutofacConfigurationTest.Configuration
{
public class Container
{
private const string ConfigurationFile = "AppSettings.json";
public Container()
{
ConfigurationRoot = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(ConfigurationFile).Build();
}
public IConfigurationRoot ConfigurationRoot { get; }
}
}
namespace AutofacConfigurationTest.Cache
{
public enum Engine
{
None,
Default
}
public interface IConfiguration
{
Engine Engine { get; set; }
int Duration { get; set; }
string ConnectionString { get; set; }
}
public class Configuration : IConfiguration
{
public const string Name = "Cache";
public Engine Engine { get; set; }
public int Duration { get; set; }
public string ConnectionString { get; set; }
}
}
namespace AutofacConfigurationTest.Repository
{
public enum Engine
{
None,
LiteDb
}
public interface IConfiguration
{
Engine Engine { get; set; }
string ConnectionString { get; set; }
}
public class Configuration : IConfiguration
{
public const string Name = "Repository";
public Engine Engine { get; set; }
public string ConnectionString { get; set; }
}
}
namespace AutofacConfigurationTest
{
internal class Program
{
private static IContainer _container;
private static void RegisterServices() => _container = new CrossCutting.Container().Kernel;
private static void DisposeServices()
{
if (_container != null &&
_container is IDisposable disposable)
disposable.Dispose();
}
private static void Main(string[] args)
{
try
{
RegisterServices();
// the following objects will be have a default(T) instance
// if the in the Autofac modules the Model is registered AFTER the Configuration
var cacheConfiguration = _container.Resolve<Cache.IConfiguration>();
var repositoryConfiguration = _container.Resolve<Repository.IConfiguration>();
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
DisposeServices();
}
}
}
}
I have a second question for you: I use the interfaces to force the contract in my models. In short, these interfaces are NEVER injected anywhere, it's just to make easier the maintenance for me. Should I remove the DI/IoC for these models or is there any reason to keep the models registration in the container?
The observed difference in behavior that now depends on the sequence of module registration, is due to the fact that both modules are registering services keyed to Cache.IConfiguration and Repository.IConfiguration. Therefore, the last module to be registered will "win".
When ModuleModel is registered last it will override any previous registration of the two configuration interfaces, and resolution will yield instances of Cache.Configuration and Repository.Configuration.
If ModuleConfiguration is registered last, resolution will yield instances provided by the configurationRoot object.
Inferring your intent from the two modules, since ModuleConfiguration registrations are actually trying to resolve Cache.Configuration and Repository.Configuration, ModuleModel must register these types keyed to those types instead of keyed to the interfaces. You do that using .AsSelf() instead of .As<some interface>(). Read more about AsSelf here.
I have read Design-time DbContext Creation that there are 3 ways the EF Core tools (for example, the migration commands) obtain derived DbContext instance from the application at design time as opposed to at run time.
From application service provider
From any parameterless ctor
From a class implementing IDesignTimeDbContextFactory<T>
Here I am only interested in the first method by mimicking the pattern used in Asp.net Core. This code does not compile because I have no idea how to make EF Core tool obtain TheContext instance.
Minimal Working Example
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;
public class TheContext : DbContext
{
public TheContext(DbContextOptions<TheContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
private static readonly IConfiguration _configuration;
private static readonly string _connectionString;
static Program()
{
_configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build();
_connectionString = _configuration.GetConnectionString("SqlServer");
}
static void ConfigureServices(IServiceCollection isc)
{
isc.AddSingleton(_ => _configuration);
isc.AddDbContextPool<TheContext>(options => options
.UseSqlServer(_connectionString));
isc.AddSingleton<TheApp>();
}
static void Main()
{
IServiceCollection isc = new ServiceCollection();
ConfigureServices(isc);
IServiceProvider isp = isc.BuildServiceProvider();
isp.GetService<TheApp>().Run();
}
}
class TheApp
{
readonly TheContext _theContext;
public TheApp(TheContext theContext) => _theContext = theContext;
public void Run()
{
// Do something on _theContext
}
}
Question
How to make EF Core tools obtain DbContext instance from service provider of a console application?
Edit:
I forgot to mention the appsettings.json as follows:
{
"ConnectionStrings": {
"Sqlite": "Data Source=MyDatabase.db",
"SqlServer": "Server=(localdb)\\mssqllocaldb;Database=MyDatabase;Trusted_Connection=True"
}
}
Although the documentation topic is called From application services, it starts with
If your startup project is an ASP.NET Core app, the tools try to obtain the DbContext object from the application's service provider.
Looks like they don't expect project types other than ASP.NET Core app to use application service provider :)
Then it continues with
The tools first try to obtain the service provider by invoking Program.BuildWebHost() and accessing the IWebHost.Services property.
and example:
public static IWebHost BuildWebHost(string[] args) => ...
And here is the trick which works with the current (EF Core 2.1.3) bits. The tools actually are searching the class containing your entry point (usually Program) for a static (does not need to be public) method called BuildWebHost with string[] args parameters, and the important undocumented part - the return type does not need to be IWebHost! It could be any object having public property like this
public IServiceProvider Services { get; }
Which gives us the following solution:
class Program
{
// ...
// Helper method for both Main and BuildWebHost
static IServiceProvider BuildServiceProvider(string[] args)
{
IServiceCollection isc = new ServiceCollection();
ConfigureServices(isc);
return isc.BuildServiceProvider();
}
static void Main(string[] args)
{
BuildServiceProvider(args).GetService<TheApp>().Run();
}
// This "WebHost" will be used by EF Core design time tools :)
static object BuildWebHost(string[] args) =>
new { Services = BuildServiceProvider(args) };
}
Update: Starting from v2.1, you could also utilize the new CreateWebHostBuilder pattern, but IMHO it just adds another level of complexity not needed here (the previous pattern is still supported). It's similar, but now we need a method called CreateWebHostBuilder which returns an object having public method Build() returning object having public Services property returning IServiceProvider. In order to be reused from Main, we can't use anonymous type and have to create 2 classes, and also this makes it's usage from Main more verbose:
class AppServiceBuilder
{
public ServiceCollection Services { get; } = new ServiceCollection();
public AppServiceProvider Build() => new AppServiceProvider(Services.BuildServiceProvider());
}
class AppServiceProvider
{
public AppServiceProvider(IServiceProvider services) { Services = services; }
public IServiceProvider Services { get; }
}
class Program
{
// ...
static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Services.GetService<TheApp>().Run();
}
// This "WebHostBuilder" will be used by EF Core design time tools :)
static AppServiceBuilder CreateWebHostBuilder(string[] args)
{
var builder = new AppServiceBuilder();
ConfigureServices(builder.Services);
return builder;
}
}
Try this, i've added some changes to make it run :
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.FileExtensions;
using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;
namespace IOCEFCore
{
public class TheContext : DbContext
{
public TheContext(DbContextOptions<TheContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
private static readonly IConfigurationRoot _configuration;
private static readonly string _connectionString;
static Program()
{
_configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build();
}
static void ConfigureServices(IServiceCollection isc)
{
isc.AddSingleton(_ => _configuration);
isc.AddDbContextPool<TheContext>(options => options.UseInMemoryDatabase("myContext"));
isc.AddSingleton<TheApp>();
}
static void Main()
{
IServiceCollection isc = new ServiceCollection();
ConfigureServices(isc);
IServiceProvider isp = isc.BuildServiceProvider();
isp.GetService<TheApp>().Run();
Console.ReadLine();
}
class TheApp
{
readonly TheContext _theContext;
public TheApp(TheContext theContext) => _theContext = theContext;
public void Run()
{
// Do something on _theContext
_theContext.Users.Add(new User {Id = 1, Name = "Me"});
_theContext.SaveChanges();
foreach (var u in _theContext.Users)
{
Console.WriteLine("{0} : {1}", u.Id, u.Name);
}
}
}
}
}
Now it can be easily done with generic hosts.
public class Product
{
public int Id { get; set; }
public string Description { get; set; } = default!;
public override string ToString() => $"Id: {Id}, Description: {Description}";
}
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<Product>().HasData(new[]
{
new Product{Id=1, Description="Sql Server"},
new Product{Id=2, Description="Asp.Net Core"},
new Product{Id=3, Description=".NET MAUI"}
});
}
}
public class Application : IHostedService
{
private readonly AppDbContext context;
public Application(AppDbContext context)
{
this.context = context;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
foreach (var p in await context.Products.ToArrayAsync())
Console.WriteLine(p);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
public class Program
{
public static async Task Main()
{
var builder = Host.CreateDefaultBuilder();
builder.ConfigureHostConfiguration(icb =>
{
// icb.AddUserSecrets<Program>();
});
builder.ConfigureServices((hbc, isc) =>
{
isc.AddDbContext<AppDbContext>(dcob =>
{
//var constr = hbc.Configuration.GetConnectionString("DefaultConnection");
var constr = "DefaultConnection";
dcob.UseSqlServer(constr);
}, ServiceLifetime.Singleton);
isc.AddHostedService<Application>();
});
//await builder.Build().RunAsync();
await builder.RunConsoleAsync();
}
}
The connection string is retrieved from appsettings.json or secret.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=EFCoreConsoleDb;Integrated Security=true;TrustServerCertificate=true"
}
}
Error: No parameterless constructor for AutoMapperConfiguration
I am using the nuget package automapper DI
public class AutoMapperConfiguration : Profile
{
private readonly ICloudStorage _cloudStorage;
public AutoMapperConfiguration(ICloudStorage cloudStorage)
{
_cloudStorage = cloudStorage;
// Do mapping here
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ICloudStorage, AzureStorage>();
services.AddAutoMapper(); // Errors here
}
How do I use the automapper DI with parameters?
I don't think you are able to add DI parameters to Profiles. Part of the logic behind this may be that these are only instantianted once, so services registered through AddTransient would not behave as expected.
One option would be to inject it into an ITypeConverter:
public class AutoMapperConfiguration : Profile
{
public AutoMapperConfiguration()
{
CreateMap<SourceModel, DestinationModel>().ConvertUsing<ExampleConverter>();
}
}
public class ExampleConverter : ITypeConverter<SourceModel, DestinationModel>
{
private readonly ICloudStorage _storage;
public ExampleCoverter(ICloudStorage storage)
{
// injected here
_storage = storage;
}
public DestinationModel Convert(SourceModel source, DestinationModel destination, ResolutionContext context)
{
// do conversion stuff
return new DestinationModel();
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ICloudStorage, AzureStorage>();
services.AddAutoMapper();
}
you may want to try this on your Startup.cs, if AddAutoMapper is an an extension that you built, then add the code below to your extension.
public void ConfigureServices(IServiceCollection services)
{
var mapperConfiguration = new MapperConfiguration(mc =>
{
IServiceProvider provider = services.BuildServiceProvider();
mc.AddProfile(new AutoMapperConfiguration (provider.GetService<ICloudStorage>()));
});
services.AddSingleton(mapperConfiguration.CreateMapper());
}
I found one solution to resolve this.
Create one list of types before add profile and pass it in the parameter.
public class AutoMapperConfiguration : Profile
{
private readonly ICloudStorage _cloudStorage;
public AutoMapperConfiguration(ICloudStorage cloudStorage)
{
_cloudStorage = cloudStorage;
// Do mapping here
}
}
public void ConfigureServices(IServiceCollection services)
{
var types = new List<Type>();
services.AddSingleton<ICloudStorage, AzureStorage>();
services.AddAutoMapper((provider, cfg) =>
{
var storage = new AutoMapperConfiguration(provider.GetService<ICloudStorage>());
types.Add(storage.GetType());
cfg.AddProfile(storage);
//others profiles
}, types);
}
On Application_Start of a MVC project, using Autofac, I have the following:
public class MvcApplication : HttpApplication {
protected void Application_Start() {
RouteSetup.Run();
} // Application_Start
}
RouteSetup is as follows:
public class RouteSetup {
public static void Run() {
ISettings settings = new Settings();
RouteTable.Routes.Localization(x => {
x.AcceptedCultures = settings.AcceptedLanguages;
x.DefaultCulture = settings.DefaultLanguage;
});
CultureSensitiveHttpModule.GetCultureFromHttpContextDelegate = context => { return new CultureResolver().GetCulture(context); };
} // Run
}
ISettings is a class I inject in various parts of my application.
How should I request this class in RouteSetup?
You can change the Run method to accept a ILifetimeScope (IContainer inherits from ILifetimeScope) or you can use the DependencyResolver provided by ASP.net MVC, in the second case the ASP.net DependencyResolver has to be configured using DependencyResolver.SetResolver(...)
public class RouteSetup {
public static void Run(ILifetimeScope scope) {
ISettings settings = scope.Resolve<ISettings>();
// or
ISettings settings = DependencyResolver.Current.GetService<ISettings>();
RouteTable.Routes.Localization(x => {
x.AcceptedCultures = settings.AcceptedLanguages;
x.DefaultCulture = settings.DefaultLanguage;
});
CultureSensitiveHttpModule.GetCultureFromHttpContextDelegate = context => {
return new CultureResolver().GetCulture(context);
};
} // Run
}
By the way, I recommend you trying to always inject dependency using constructor parameter.