Been doing some sample code with ASP.NET Core to try to understand how it fits together and I am stumped as to why I am unable to successfully resolve a service.
The configure services method has the call to add ISeedDataService
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<CustomerDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<ICustomerDbContext, CustomerDbContext>();
services.AddScoped<ICustomerRepository, CustomerRepository>();
services.AddScoped<ISeedDataService, SeedDataService>();
}
In Configure I am calling AddSeedData() as below
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.AddSeedData();
}
which is calling the extension method below
public static async void AddSeedData(this IApplicationBuilder app)
{
var seedDataService = app.ApplicationServices.GetRequiredService<ISeedDataService>();
await seedDataService.EnsureSeedData();
}
and the SeedDataService is below
public class SeedDataService : ISeedDataService
{
private ICustomerDbContext _context;
public SeedDataService(ICustomerDbContext context)
{
_context = context;
}
public async Task EnsureSeedData()
{
_context.Database.EnsureCreated();
_context.Customers.RemoveRange(_context.Customers);
_context.SaveChanges();
Customer customer = new Customer();
customer.FirstName = "Chuck";
customer.LastName = "Norris";
customer.Age = 30;
customer.Id = Guid.NewGuid();
_context.Add(customer);
Customer customer2 = new Customer();
customer2.FirstName = "Fabian";
customer2.LastName = "Gosebrink";
customer2.Age = 31;
customer2.Id = Guid.NewGuid();
_context.Add(customer2);
await _context.SaveChangesAsync();
}
}
Totally unsure as to what I am doing wrong, the error is System.InvalidOperationException: 'Cannot resolve scoped service 'secondapp.Services.ISeedDataService' from root provider.'
You are (and should be) adding the ISeedDataService as scoped service. However, you are attempting to resolve it from the root service provider (e.g. app.ApplicationServices) which is not scoped. This means that scoped services resolved from it effectively are turned into a singleton service and are not disposed until the application shuts down or it will result in an error.
The solution here is to create a scope yourself:
public void Configure(IApplicationBuilder app)
{
using (var scope = app.ApplicationServices.CreateScope())
{
var seedDataService = scope.ServiceProvider.GetRequiredService<ISeedDataService>();
// Use seedDataService here
}
}
Please take a look at the documentation regarding dependency injection scopes.
On a second note: your AddSeedData extension method is async void and you are not waiting for the result. You should return a task (async Task) call AddSeedData().GetAwaiter().GetResult() to make sure you block until the seeding is complete.
The Configure() method allows parameter dependency injection so you can do the following.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ISeedDataService seedService)
{
seedService.EnsureSeedData().Wait(); // Configure() is not async so you have to wait
}
Related
We have a Web API Core application that use the EF Core with the SQL Server in a backend. I am trying to update one of the database tables whenever the Web API service starts (in a Startup.cs Configure method)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, SomeDBContext dataContext, ILoggerFactory loggerFactory)
{
.......................
// Automatically perform database migration
dataContext.Database.Migrate();
PopulateSomeTable(dataContext);
}
private async void PopulateSomeTable(SomeDBContext context)
{
var someTables = context.SomeTables;
if (someTables != null && (await someTables .CountAsync()) == 0)
{
someTables .Add(new Entities.SomeTable
{
someProperty1= 20,
someProperty2 = "Something",
someProperty3 = DateTimeOffset.Now,
});
await context.SaveChangesAsync();
}
}
However, when I try to access the context I get this error
Cannot access a disposed object. A common cause of this error is
disposing a context that was resolved from dependency injection and
then later trying to use the same context instance elsewhere in your
application. This may occur if you are calling Dispose() on the
context, or wrapping the context in a using statement. If you are
using dependency injection, you should let the dependency injection
container take care of disposing context
How can I fix it?
Thank you in advance
You need to replace private async void PopulateSomeTable(SomeDBContext context) to private async Task PopulateSomeTable(SomeDBContext context). Replace async void to async task. Check this link for more info.
remove all your code from startup, it is not the best place to make db migration, and add only this code
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env, MyDbContext context)
{
if (env.IsDevelopment())
{
context.Database.EnsureCreated();
}
}
add this code to ConfigureServices of startup
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver());
services.AddDbContext<SomeDBContext (options =>
options.UseSqlServer(Configuration.GetConnectionString("CategoryDbConnection")));
to populate data add code like this OnModelCreating of SomeDBContext
modelBuilder.Entity<SomeTable>().HasData(
new SomeTable{ ....
});
to migrate classes to database tables follow this link
https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=vs
I am converting my startup code into new ServiceStack Modular Startup approach and have hit a snag.
I have this code in old startup
public void Configure(IApplicationBuilder app)
{
app.UseHangfireDashboard(hangfirePath, new DashboardOptions
{
Authorization = new[] { new HangFireAuthorizationFilter() }
});
var appHost = new AppHost
{
AppSettings = settings
};
app.UseServiceStack(appHost);
var container = appHost.Resolve<Container>();
GlobalConfiguration.Configuration.UseActivator(new ContainerJobActivator(container));
app.UseHangfireServer();
}
It's important that app.UseHangfireDashboard is registered before app.UseServiceStack or the dashboard wont work.
I got it working all fine except for the part where it links the IoC container to hangfire:
GlobalConfiguration.Configuration.UseActivator(new ContainerJobActivator(container));
This is the working code without linking container:
[Priority(-1)]
public class ConfigureHangfirePostgreSql : IConfigureServices, IConfigureApp
{
IConfiguration Configuration { get; }
public ConfigureHangfirePostgreSql(IConfiguration configuration) => Configuration = configuration;
public void Configure(IServiceCollection services)
{
var conn = Configuration.GetValue<string>("database:connectionString");
services.AddHangfire((isp, config) =>
{
config.UsePostgreSqlStorage(conn, new PostgreSqlStorageOptions
{
InvisibilityTimeout = TimeSpan.FromDays(1)
});
config.UseConsole();
});
}
public void Configure(IApplicationBuilder app)
{
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new HangFireAuthorizationFilter() }
});
// commented out because I dont think it's possible to get container yet and also code doesn't work
//var container = app.Resolve<Container>();
//GlobalConfiguration.Configuration.UseActivator(new ContainerJobActivator(container));
app.UseHangfireServer();
}
}
I am setting the priority to -1 so it runs before servicestack is registered. Because of that I guess the container isn't yet created so I need to make another module to run after like this:
[Priority(2)]
public class ConfigureHangfirePostgreSqlPost : IConfigureApp
{
public void Configure(IApplicationBuilder app)
{
//how do I resolve container here?
}
}
The container isn't registered as a service (as I am asking the container for the service) so im not sure how I am able to access it.
What is the right way of getting hold of the container in a startup module?
It's hard to decipher what the actual question is, answering a clear one found on the last line:
What is the right way of getting hold of the container in a startup module?
This existing answer has a good summary of ASP.NET Core IOC, where you can access any singleton dependencies from app.ApplicationServices, e.g:
public void Configure(IApplicationBuilder app)
{
var serviceProvider = app.ApplicationServices;
var hostingEnv = serviceProvider.GetService<IHostingEnvironment>();
}
I have the following SampleData class to generate some user data after running EF Migration.
namespace App.Models
{
public interface ISampleData
{
void Initialize();
}
public class SampleData: ISampleData
{
private readonly AppDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public SampleData(
AppDbContext context,
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager
)
{
_context = context;
_userManager = userManager;
_roleManager = roleManager;
}
public void Initialize()
{
ApplicationUser user;
IdentityRole myrole = _roleManager.FindByNameAsync("Admin").Result;
if (myrole != null) return;
IdentityResult result = _roleManager.CreateAsync(new IdentityRole { Name = "Admin", NormalizedName = "Admin".ToUpper() }).Result;
string userId1 = Guid.NewGuid().ToString();
if (result.Succeeded)
{
user = new ApplicationUser
{
Id = userId1.ToString(),
UserName = "erkanererkaner#gmail.com",
Email = "erkanererkaner#gmail.com",
FirstName = "Erkan",
LastName = "Er"
};
result = _userManager.CreateAsync(user, "123456Aa*").Result;
if (result.Succeeded) _userManager.AddToRoleAsync(user, "Admin").Wait();
}
}
}
}
The Initialize() method is caled from Startup file, like this:
public class Startup
{
private readonly ISampleData _sampleData;
public Startup(IConfiguration configuration, ISampleData sampleData)
{
_sampleData = sampleData;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
//other implementation details
services.AddScoped<ISampleData, SampleData>();
}
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider services)
{
//other implementation details
_sampleData.Initialize();
}
However, I got the following error:
Using application service provider from IWebHost accessor on
'Program'. System.InvalidOperationException: Unable to resolve service
for type 'App.Models.ISampleData' while attempting to activate
'App.Startup'. at
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider
provider)
I see that this is about the way I implemented Dependency Injection. But I cannot see the problem. Any ideas?
You cannot inject types other than IConfiguration and IHostingEnvironment in your Startup constructor. The reason for this is that the Startup class actually configures the dependency injection container first (through the ConfigureServices method). So at the time you want to resolve the dependencies (in the constructor), the whole DI infrastructure does not even exist yet.
Instead, you can resolve the services earliest within the Configure method, which gets called after the dependency injection container is created. You can actually add dependencies directly in the method signature to have them resolved automatically:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ISampleData sampleData)
{
// …
sampleData.Initialize();
}
Note that for seeding your database, it is generally not recommended to do that withint he Configure method since that may run in situations where you don’t want to seed your database yet (for example, when you are doing integration tests, or when you call dotnet ef from the command line).
Instead, the recommended pattern for seeding the database is to do that outside of the Startup but at the web host level instead. So inside of your Program class, you could modify it like this:
public class Program
{
public static void Main(string[] args)
{
// create web host
var host = CreateWebHostBuilder(args).Build();
// create service scope for seeding the database
using (var scope = host.Services.CreateScope())
{
// retrieve the sample data service
var sampleData = scope.ServiceProvider.GetRequiredService<ISampleData>();
// run your sample data initialization
_sampleData.Initialize();
}
// run the application
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
=> WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
I have problem with understanding source of errors in my code. I try to get throw course about microservices in .net core. After running build solution I get:
------- Project finished: CrossX.Services.Identity. Succeeded: True. Errors: 0. Warnings: 0
But when I run it I get:
/opt/dotnet/dotnet /RiderProjects/crossx/src/CrossX.Services.Identity/bin/Debug/netcoreapp2.2/CrossX.Services.Identity.dll
Unhandled Exception: System.InvalidOperationException: Cannot resolve scoped service 'CrossX.NETCore.Commands.ICommandHandler`1[CrossX.NETCore.Commands.CreateUser]' from root provider.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at CrossX.NETCore.Services.ServiceHost.BusBuilder.SubscribeToCommand[TCommand]() in /RiderProjects/crossx/src/CrossX.NETCore/Services/ServiceHost.cs:line 78
at CrossX.Services.Identity.Program.Main(String[] args) in /RiderProjects/crossx/src/CrossX.Services.Identity/Program.cs:line 11
When I added to webHostBuilder .UseDefaultServiceProvider(options => options.ValidateScopes = false) my problem was solved. But turning off validations isn't good idea from what I know. Also When I changed AddScope to AddTransient problem was solved (or at least it run).
Problem is that I have no idea where to look for source of this error. I guess I lack of understanding what is wrong, so I would appreciate if someone would help me, or at least give a hint.
Here is my
Startup.cs:
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)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddRabbitMq(Configuration);
services.AddScoped<ICommandHandler<CreateUser>, CreateUserHandler>();
services.AddScoped<IEncrypter, Encrypter>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
Program.cs
public class Program
{
public static void Main(string[] args)
{
ServiceHost.Create<Startup>(args)
.UseRabbitMq()
.SubscribeToCommand<CreateUser>()
.Build()
.Run();
}
}
ServiceHost.cs
public class ServiceHost : IServiceHost
{
private readonly IWebHost _webHost;
public ServiceHost(IWebHost webHost)
{
_webHost = webHost;
}
public void Run() => _webHost.Run();
public static HostBuilder Create<TStartup>(string[] args) where TStartup : class
{
Console.Title = typeof(TStartup).Namespace;
var config = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddCommandLine(args)
.Build();
var webHostBuilder = WebHost.CreateDefaultBuilder(args)
.UseConfiguration(config)
// .UseDefaultServiceProvider(options => options.ValidateScopes = false)
.UseStartup<TStartup>();
return new HostBuilder(webHostBuilder.Build());
}
public abstract class BuilderBase
{
public abstract ServiceHost Build();
}
public class HostBuilder : BuilderBase
{
private readonly IWebHost _webHost;
private IBusClient _bus;
public HostBuilder(IWebHost webHost)
{
_webHost = webHost;
}
public BusBuilder UseRabbitMq()
{
_bus = (IBusClient) _webHost.Services.GetService(typeof(IBusClient));
return new BusBuilder(_webHost, _bus);
}
public override ServiceHost Build()
{
return new ServiceHost(_webHost);
}
}
public class BusBuilder : BuilderBase
{
private readonly IWebHost _webHost;
private IBusClient _bus;
public BusBuilder(IWebHost webHost, IBusClient bus)
{
_webHost = webHost;
_bus = bus;
}
public BusBuilder SubscribeToCommand<TCommand>() where TCommand : ICommand
{
var handler = (ICommandHandler<TCommand>) _webHost.Services.GetService(typeof(ICommandHandler<TCommand>));
_bus.WithCommandHandlerAsync(handler);
return this;
}
public BusBuilder SubscribeToEvent<TEvent>() where TEvent : IEvent
{
var handler = (IEventHandler<TEvent>) _webHost.Services.GetService(typeof(IEventHandler<TEvent>));
_bus.WithEventHandlerAsync(handler);
return this;
}
public override ServiceHost Build()
{
return new ServiceHost(_webHost);
}
}
}
Cannot resolve scoped service ICommandHandler<CreateUser> from root provider
As the error says, you cannot create a scoped service instance from the root provider. The root provider is the root service provider that exists outside of service scopes. As such, it cannot resolve services that should only be consumed within service scopes.
If you want to resolve a scoped service from the root provider, for example when you are consuming it from a singleton service, you should create a service scope first using the IServiceScopeFactory:
var serviceScopeFactory = _webHost.Services.GetService<IServiceScopeFactory>();
using (var scope = serviceScopeFactory.CreateScope())
{
var handler = (IEventHandler<TEvent>)scope.ServiceProvider.GetService(typeof(IEventHandler<TEvent>))
// …
}
Note that service scopes are supposed to be short lived, and that you need to dispose them afterwards to clean up.
Looking at your implementation, it seems as if you pass your scoped services to some other service in order to subscribe to events. This generally seems like a bad idea since that means that a reference to a scoped service will be kept by a (likely) singleton service for the whole lifetime of the application. This is generally a bad idea (as I said, scoped services are supposed to live only a short time).
You should ask yourself why you need the services to be scoped there, and whether you cannot make them singleton instead. Or if you actually need the subscribe mechanism to be based on the instance (instead of for example just the type, using a factory pattern or something).
Good afternoon,
I recently started experimenting with Service Fabric and .NET Core.
I created a Stateless Web API and performed some DI using:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
var connString = Configuration.GetConnectionString("DefaultConnection");
services.AddScoped<FaxLogic>();
services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connString));
}
With the above I can use constructor inject on my FaxLogic class as well as my DbContext class (through the FaxLogic):
private readonly FaxLogic _faxLogic;
public FaxController(
FaxLogic faxLogic)
{
_faxLogic = faxLogic;
}
private readonly ApplicationContext _context;
public FaxLogic(ApplicationContext context)
{
_context = context;
}
I then created a non-Web API stateless service. I want to be able to access my FaxLogic and DbContext like in my WebAPI, but within the RunAsync method of the stateless service:
protected override async Task RunAsync(CancellationToken cancellationToken)
{
// TODO: Replace the following sample code with your own logic
// or remove this RunAsync override if it's not needed in your service.
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
ServiceEventSource.Current.ServiceMessage(this.Context, "Hello!");
// do db stuff here!
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
}
}
I am wondering how I'd do it. I tried playing with the CreateServiceInstanceListeners() method and the Program.cs file where ServiceRuntime is is used to register but I can't seem to figure it out! Any help would be appreciated.
The solution has been already answered here: Set up Dependency Injection on Service Fabric using default ASP.NET Core DI container
In summary, you have to register the dependencies before you create a new instance of your stateless service and then create a factory method to resolve the dependencies:
i.e:
public static class Program
{
public static void Main(string[] args)
{
var provider = new ServiceCollection()
.AddLogging()
.AddSingleton<IFooService, FooService>()
.AddSingleton<IMonitor, MyMonitor>()
.BuildServiceProvider();
ServiceRuntime.RegisterServiceAsync("MyServiceType",
context => new MyService(context, provider.GetService<IMonitor>());
}).GetAwaiter().GetResult();
See the linked answer for more details.
TaeSeo,
I think what you are looking for is implemented in the project I am working on - CoherentSolutions.Extensions.Hosting.ServiceFabric.
In the terms of CoherentSolutions.Extensions.Hosting.ServiceFabric what you are looking for would look like:
private static void Main(string[] args)
{
new HostBuilder()
.DefineStatelessService(
serviceBuilder => {
serviceBuilder
.UseServiceType("ServiceName")
.DefineDelegate(
delegateBuilder => {
delegateBuilder.ConfigureDependencies(
dependencies => {
dependencies.AddScoped<FaxLogic>();
});
delegateBuilder.UseDelegate(
async (StatelessServiceContext context, FaxLogic faxLogic) => {
while (true) {
cancellationToken.ThrowIfCancellationRequested();
ServiceEventSource.Current.ServiceMessage(context, "Hello!");
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
});
})
})
.Build()
.Run();
}
If you have more questions feel free to ask or check out the project wiki
Hope it helps.