Will anything bad happen if there are multiple registrations of services in .net core's DI? For example let's say we have the following code
public void ConfigureServices(IServiceCollection services)
{
//....
services.AddHealthChecks();
//...
}
And in another (extension maybe) class we use services.AddHealthChecks() again. Will this mess the DI's container or not?
Thanks in advance
You can register a service several times without an exception has thrown. The problem is when you register a service several times with different scope. consider following example:
public interface IMyInterface
{
void Print();
}
public class MyInterface : IMyInterface
{
public void Print()
{
}
}
And let's register IMyInterface with two different scopes:
internal class Program
{
private static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddScoped<IMyInterface, MyInterface>();
services.AddSingleton<IMyInterface, MyInterface>();
var provider = services.BuildServiceProvider();
for (var i = 0; i < 5; i++)
{
var scope = provider.CreateScope();
using (scope)
{
var myInterface = scope.ServiceProvider.GetService<IMyInterface>();
Console.WriteLine(myInterface.GetHashCode());
}
}
}
}
First, register IMyInterface in the following order:
services.AddScoped<IMyInterface, MyInterface>();
services.AddSingleton<IMyInterface, MyInterface>();
As you can see we get a singleton instance of MyInterface and the hashcode is the same.
Now let's change it to this:
services.AddSingleton<IMyInterface, MyInterface>();
services.AddScoped<IMyInterface, MyInterface>();
Now we get the scoped type of MyInterface and the hashcode is different each time. You always get the last registered scope of your type.
Look at AddHealthChecks code:
public static IHealthChecksBuilder AddHealthChecks(this IServiceCollection services)
{
services.TryAddSingleton<HealthCheckService, DefaultHealthCheckService>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, HealthCheckPublisherHostedService>());
return new HealthChecksBuilder(services);
}
By adding services.AddHealthChecks(); more than one time, you just registering HealthCheckService and IHostedService as a singleton services and I think it doesn't affect the health check functionality.
With the following extension, you can find duplicate registration:
public static class ServiceDescription
{
private static List<IGrouping<Type, ServiceDescriptor>> Descriptors;
public static IHostBuilder ConfigureServiceDescriptionCheck(this IHostBuilder hostBuilder)
{
hostBuilder.ConfigureServices(services =>
{
Descriptors = services.Where(i => !i.ServiceType.Assembly.FullName.Contains("Microsoft"))
.GroupBy(p => p.ServiceType)
.Where(x => x.Count() > 1).ToList();
});
return hostBuilder;
}
public static IHost UseServiceDescriptionCheck(this IHost host)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
Descriptors.ForEach(item =>
{
var count = item.Count();
logger.LogWarning("Service of type {Key} has been registered {count} times", item.Key, count);
});
return host;
}
}
And use it in this way:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().UseServiceDescriptionCheck().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureServiceDescriptionCheck();
}
Read this article to get more details.
Related
I'm trying to create a default admin account when I start an application.
Now what I'm interested in is how to seed a database in asp.net core. I have a seed code that I run in the main program. It shows no error but does not update the database. I've been trying to change "Identity rolls" to Application Role in my SeedData, but it has no effect at all.
I wouldn't want to change most of the code and I know it can be done with a model builder, but I don't want it that way. I think the problem is with the main program, but I don't understand what I need to change. My code is shown here.
SeedData.cs
namespace AspNetCoreTodo
{
public static class SeedData
{
public static async Task InitializeAsync(IServiceProvider services)
{
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
await EnsureRolesAsync(roleManager);
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
await EnsureTestAdminAsync(userManager);
}
private static async Task EnsureRolesAsync(RoleManager<IdentityRole> roleManager)
{
var alreadyExists = await roleManager.RoleExistsAsync(Constants.AdministratorRole);
if (alreadyExists) return;
await roleManager.CreateAsync(new IdentityRole(Constants.AdministratorRole));
}
private static async Task EnsureTestAdminAsync(UserManager<ApplicationUser> userManager)
{
var testAdmin = await userManager.Users
.Where(x => x.UserName == "admin#todo.local")
.SingleOrDefaultAsync();
if (testAdmin != null) return;
testAdmin = new ApplicationUser { Email = "admin#todo.local", UserName = "admin#todo.local" };
await userManager.CreateAsync(testAdmin, "NotSecure123!!");
await userManager.AddToRoleAsync(testAdmin, Constants.AdministratorRole);
}
}
}
ApplicationDbContext.cs
namespace AspNetCoreTodo.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<TodoItem> Items {get; set;}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
}
}
Program.cs
namespace AspNetCoreTodo
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
private static void InitializeDatabase(IWebHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
SeedData.InitializeAsync(services).Wait();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
Startup.cs //Configuration
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddMvc();
services.AddAuthentication();
services.AddScoped<ITodoItemService, TodoItemService>();
}
Does your application ever goes in the method InitializeDatabase(IWebHost host) in Program.cs?
Could you please try to call your method in Main() method:
public static void Main(string[] args)
{
var webHost = CreateWebHostBuilder(args).Build();
InitializeDatabase(webHost);
webHost.Run();
}
Note: You have to create that 'webHost' variable, because your method takes 'IWebHost' as a parameter. And CreateWebHostBuilder(string[] args) method returns type of IWebHostBuilder. Also Run() method will work on type of IWebHost.
Note: As Nilay noticed above, I'd also seed my database in Startup.cs, in the
if(env.isDevelopment){
InitializeDatabase(webHost);
}
Because normally, seeding is a "development" purpose.
I have an ASP.NET Core web app and test setup using WebApplicationFactory to test my controller actions. I used RawRabbit before and it was easy enough for me to mock the IBusClient and add it to the DI container as a singleton. Within the WebApplicationFactory<TStartup>.CreateWebHostBuilder() I call this extension method to add my mocked IBusClient instance like so;
/// <summary>
/// Configures the service bus.
/// </summary>
/// <param name="webHostBuilder">The web host builder.</param>
/// <returns>A web host builder.</returns>
public static IWebHostBuilder ConfigureTestServiceBus(this IWebHostBuilder webHostBuilder)
{
webHostBuilder.ConfigureTestServices(services =>
{
services.AddSingleton<IBusClient, MY_MOCK_INSTANCE>
});
return webHostBuilder;
}
But there are gaps in RawRabbit right now that made me decide to move over to MassTransit. However, I am wondering if there's already a better way to register the IBus into my container without mocking it inside my test. Not sure if InMemoryTestFixture, BusTestFixture, or BusTestHarness is the solution to my problem. Not sure how to use them together and what they do.
By the way, in my ASP.NET Core app, I have a reusable extension method setup like the code below to hook me up to RabbitMQ on startup.
/// <summary>
/// Adds the service bus.
/// </summary>
/// <param name="services">The services.</param>
/// <param name="configurator">The configurator.</param>
/// <returns>A service collection.</returns>
public static IServiceCollection AddServiceBus(this IServiceCollection services, Action<IServiceCollectionConfigurator> configurator)
{
var rabbitMqConfig = new ConfigurationBuilder()
.AddJsonFile("/app/configs/service-bus.json", optional: false, reloadOnChange: true)
.Build();
// Setup DI for MassTransit.
services.AddMassTransit(x =>
{
configurator(x);
// Get the json configuration and use it to setup connection to RabbitMQ.
var rabbitMQConfig = rabbitMqConfig.GetSection(ServiceBusOptionsKey).Get<RabbitMQOptions>();
// Add bus to the container.
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(
new Uri(rabbitMQConfig.Host),
hostConfig =>
{
hostConfig.Username(rabbitMQConfig.Username);
hostConfig.Password(rabbitMQConfig.Password);
hostConfig.Heartbeat(rabbitMQConfig.Heartbeat);
});
cfg.ConfigureEndpoints(provider);
// Add Serilog logging.
cfg.UseSerilog();
}));
});
// Add the hosted service that starts and stops the BusControl.
services.AddSingleton<IMessageDataRepository, EncryptedMessageDataRepository>();
services.AddSingleton<IEndpointNameFormatter, EndpointNameFormatter>();
services.AddSingleton<IBus>(provider => provider.GetRequiredService<IBusControl>());
services.AddSingleton<IHostedService, BusHostedService>();
return services;
}
A MassTransit config defined during Startup could be replaced with a new configuration with custom WebApplicationFactory by removing services from MassTransit namespace, e.g.
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var massTransitHostedService = services.FirstOrDefault(d => d.ServiceType == typeof(IHostedService) &&
d.ImplementationFactory != null &&
d.ImplementationFactory.Method.ReturnType == typeof(MassTransitHostedService)
);
services.Remove(massTransitHostedService);
var descriptors = services.Where(d =>
d.ServiceType.Namespace.Contains("MassTransit",StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var d in descriptors)
{
services.Remove(d);
}
services.AddMassTransitInMemoryTestHarness(x =>
{
//add your consumers (again)
});
});
}
}
Then your test could look like
public class TestClass : IClassFixture<CustomApplicationFactory>
{
private readonly CustomApplicationFactoryfactory;
public TestClass(CustomApplicationFactoryfactory)
{
this.factory = factory;
}
[Fact]
public async Task TestName()
{
CancellationToken cancellationToken = new CancellationTokenSource(5000).Token;
var harness = factory.Services.GetRequiredService<InMemoryTestHarness>();
await harness.Start();
var bus = factory.Services.GetRequiredService<IBusControl>();
try
{
await bus.Publish<MessageClass>(...some message...);
bool consumed = await harness.Consumed.Any<MessageClass>(cancellationToken);
//do your asserts
}
finally
{
await harness.Stop();
}
}
}
I ended up creating a method from within my WebApplicationFactory like so;
public void ConfigureTestServiceBus(Action<IServiceCollectionConfigurator> configurator)
{
this._configurator = configurator;
}
Giving me the ability to register test handlers from within my derived integration class constructor;
public Intg_GetCustomers(WebApplicationTestFactory<Startup> factory)
: base(factory)
{
factory.ConfigureTestServiceBus(c =>
{
c.AddConsumer<TestGetProductConsumer>();
});
}
This configurator gets used when I call my extension method to add an InMemory instance of MassTransit
public static IWebHostBuilder ConfigureTestServiceBus(this IWebHostBuilder webHostBuilder, Action<IServiceCollectionConfigurator> configurator)
{
return webHostBuilder
.ConfigureTestServices(services =>
{
// UseInMemoryServiceBus DI for MassTransit.
services.AddMassTransit(c =>
{
configurator?.Invoke(c);
// Add bus to the container.
c.AddBus(provider =>
{
var control = Bus.Factory.CreateUsingInMemory(cfg =>
{
cfg.ConfigureEndpoints(provider);
});
control.Start();
return control;
});
});
services.AddSingleton<IMessageDataRepository, InMemoryMessageDataRepository>();
});
}
You best bet is to use the InMemoryTestHarness, so that you can ensure your message contracts can be serialized, your consumers are configured properly, and that everything works as expected. While some might call this an integration test, it's really just doing a proper test. And it's extremely quick, since it's all in-memory.
You can see a unit test here, but a short example is also shown below.
[TestFixture]
public class When_a_consumer_is_being_tested
{
InMemoryTestHarness _harness;
ConsumerTestHarness<Testsumer> _consumer;
[OneTimeSetUp]
public async Task A_consumer_is_being_tested()
{
_harness = new InMemoryTestHarness();
_consumer = _harness.Consumer<Testsumer>();
await _harness.Start();
await _harness.InputQueueSendEndpoint.Send(new A());
}
[OneTimeTearDown]
public async Task Teardown()
{
await _harness.Stop();
}
[Test]
public void Should_have_called_the_consumer_method()
{
_consumer.Consumed.Select<A>().Any().ShouldBe(true);
}
class Testsumer :
IConsumer<A>
{
public async Task Consume(ConsumeContext<A> context)
{
await context.RespondAsync(new B());
}
}
class A
{
}
class B
{
}
}
In my case (I'm injecting everywhere IPublishEndpoint interface only) I have just simply registered another IPublishEndpoint in ConfigureTestServices method like so:
[TestClass]
public class TastyTests
{
private readonly WebApplicationFactory<Startup> factory;
private readonly InMemoryTestHarness harness = new();
public TastyTests()
{
factory = new WebApplicationFactory<Startup>().WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddSingleton<IPublishEndpoint>(serviceProvider =>
{
return harness.Bus;
});
});
});
}
[TestMethod]
public async Task Test()
{
await harness.Start();
try
{
var client = factory.CreateClient();
const string url = "/endpoint-that-publish-message";
var content = new StringContent("", Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
(await harness.Published.Any<IMessage>()).Should().BeTrue();
}
finally
{
await harness.Stop();
}
}
}
What is syntax to map both ways in AutoMapper Net Core2?
I need to map ProductViewModel and ProductDto both ways. This is not working,
Startup.cs
var config = new MapperConfiguration
(
cfg => cfg.CreateMap<Models.ProductDto, Models.ProductViewModel>(),
cfg => cfg.CreateMap<Models.ProductViewModel, Models.ProductDto>()
);
var mapper = config.CreateMapper();
I'd rather create a separate initializer and mapper. e.g here is my AutoMapperStartupTask class.
public static class AutoMapperStartupTask
{
public static void Execute()
{
Mapper.Initialize(
cfg =>
{
cfg.CreateMap<ProductViewModel, ProductDto>()
.ReverseMap();
});
}
}
And Mapper
public static class DtoMapping
{
public static ProductViewModel ToModel(this ProductDto dto)
{
return Mapper.Map<ProductDto, ProductViewModel>(dto);
}
public static ProductDto ToDto(this ProductViewModel dto)
{
return Mapper.Map<ProductViewModel, ProductDto>(dto);
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
AutoMapperStartupTask.Execute();
}
Use in Controller
var dto = productModel.ToDto();
var model = productDto.ToModel();
I've a sample asp.net core app with custom Implementation of IServer interface as following:
namespace AspNetCoreAppWithOwinHttpListenerServer
{
public class Program
{
public static void Main(string[] args)
{
IWebHost host = new WebHostBuilder()
.UseHttpListener()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
public class OwinHttpListenerServer : IServer
{
private IDisposable _HttpListenerServer;
public IFeatureCollection Features { get; } = new FeatureCollection();
public OwinHttpListenerServer()
{
Features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
}
public void Start<TContext>(IHttpApplication<TContext> application)
{
Func<IDictionary<string, object>, Task> appFunc = async env =>
{
FeatureCollection features = new FeatureCollection(new OwinFeatureCollection(env));
TContext context = application.CreateContext(features);
try
{
await application.ProcessRequestAsync(context);
}
catch (Exception ex)
{
application.DisposeContext(context, ex);
throw;
}
application.DisposeContext(context, null);
};
appFunc = OwinWebSocketAcceptAdapter.AdaptWebSockets(appFunc);
Dictionary<string, object> props = new Dictionary<string, object>();
props["host.Addresses"] = Features
.Get<IServerAddressesFeature>()
.Addresses
.Select(add => new Uri(add))
.Select(add => new Address(add.Scheme, add.Host, add.Port.ToString(), add.LocalPath).Dictionary)
.ToList();
OwinServerFactory.Initialize(props);
_HttpListenerServer = OwinServerFactory.Create(appFunc, props);
}
public void Dispose()
{
_HttpListenerServer?.Dispose();
}
}
public static class OwinHttpListenerWebHostBuilderExtensions
{
public static IWebHostBuilder UseHttpListener(this IWebHostBuilder builder)
{
return builder.ConfigureServices(services =>
{
services.AddSingleton<IServer, OwinHttpListenerServer>();
});
}
}
}
It works fine without IIS, but running on IIS or IISExpress results in "did not listen on the given port" by VSIISExeLauncher.exe.
How can I make my custom server compatible with IIS?
Thanks in advance.
GitHub repository: https://github.com/ymoradi/AspNetCoreAppWithOwinHttpListenerServer
AspNetCoreModule (IIS) is incompatible with Http.Sys based servers. See https://github.com/aspnet/AspNetCoreModule/issues/23
OwinHttpListener is based on .NET HttpListenerClass and that class is implemented by help of http.sys.
http.sys is a kernel mode code, and for now, there is no support for that in asp.net core iis module.
I am doing something like:
private static IServiceProvider serviceProvider;
public Program(IApplicationEnvironment env, IRuntimeEnvironment runtime)
{
var services = new ServiceCollection();
ConfigureServices(services);
serviceProvider = services.BuildServiceProvider();
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
private void ConfigureServices(IServiceCollection services)
{
//Console.WriteLine(Configuration["Data:DefaultConnection:ConnectionString"]);
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
}
I am struggling to get to use the program using an injected DbContext. Any idea? How do you instantiate the program and get everything injected? I don't know what to do in the static Main method.
Is there an equivalent for this?
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
Something like?
public static void Main(string[] args) => ConsoleApplication.Run<Program>(args);
This is how I did it:
public class Startup
{
public static IConfigurationRoot Configuration { get; set; }
public static void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<DbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddSingleton<IMyManager, Manager>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<Program, Program>();
}
public static void Main(string[] args)
{
var services = new ServiceCollection();
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
.AddEnvironmentVariables()
.AddUserSecrets();
Configuration = builder.Build();
ConfigureServices(services);
var provider = services.BuildServiceProvider();
CancellationTokenSource ctSource = new CancellationTokenSource();
CancellationToken ct = ctSource.Token;
Task task = Task.Run(async () =>
{
Program program = provider.GetRequiredService<Program>();
await program.Run(ct);
});
try
{
task.Wait();
}
catch (AggregateException e)
{
throw e.InnerException;
}
ctSource.Cancel();
ctSource.Dispose();
}
}
Then the program is just:
class Program
{
private IMyManager _myManager;
public Program(IMyManager myManager)
{
_myManager = myManager;
}
public async Task Run(CancellationToken cancelationToken)
{
while (true)
{
cancelationToken.ThrowIfCancellationRequested();
// My things using _myManager
await Task.Delay(10000, cancelationToken);
}
}
}
I deleted a bunch of stuff for the example so it probably crashes somewhere, but you get the idea.
Just in case anyone else is looking for a small and simple example to follow.
Here is a small console app I wrote recently for a an example. It"s only a small password generator demonstration of DI in an app with unit tests.
https://github.com/AnthonySB/PasswordApplication
using System;
using Microsoft.Extensions.DependencyInjection;
using PasswordExercise.Interfaces;
using PasswordExercise.Services;
namespace PasswordExercise
{
class Program
{
static void Main(string[] args)
{
//Dependency injection
var serviceProvider = new ServiceCollection()
.AddSingleton<IPasswordGeneratorService, PasswordGenerator>()
.AddSingleton<IPasswordService, PasswordService>()
.BuildServiceProvider();
//Get the required service
var passwordService = serviceProvider.GetService<IPasswordService>();
//For reading from the console
ConsoleKeyInfo key;
//Display the menu
passwordService.Menu();
do
{
//Read the console key, do not display on the screen
key = Console.ReadKey(true);
switch (key.KeyChar.ToString())
{
case "1":
Console.WriteLine("Simple password: {0}", passwordService.SimplePassword());
break;
case "2":
Console.WriteLine("Moderate password: {0}", passwordService.ModeratePassword());
break;
case "3":
Console.WriteLine("Strong password: {0}", passwordService.StrongPassword());
break;
}
} while (key.Key != ConsoleKey.Escape);
}
}
}
Hope this helps someone.