How to handle deprecation of IWebHostBuilder.UseSerilog() when IWebHostBuilder is still needed? - serilog

The UseSerilog() extension is deprecated* for IWebHostBuilder in serilog-aspnetcore version 5.0.0. However I'm still using a IWebHostBuilder and a Startup class (aspnetcore 6), and IWebHostBuilder is not deprecated.
Since deprecation implies future removal, how should I leverage Serilog going forward?
reference:
https://github.com/serilog/serilog-aspnetcore/releases/tag/v5.0.0
"mark IWebHostBuilder extensions as obsolete on platforms with IHostBuilder"

I was able to get this to work by switching to IHostBuilder instead of IWebHostBuilder. The key in my case was to call ConfigureWebHostDefaults (or ConfigureWebHost), where you can then utilize an IWebHostBuilder.
In this way, I could call UseSerilog on the IHostBuilder while still utilizing the Startup class as before with an IWebHostBuilder.
Example:
public static void Main(string[] args)
{
// temp initial logging
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateBootstrapLogger();
using var app =
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webHostBuilder
=> webHostBuilder.ConfigureAppConfiguration((hostingContext, config) =>
_ = config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", false, true))
.UseStartup<Startup>())
.UseSerilog(
(hostingContext, loggerConfig) =>
loggerConfig
.ReadFrom.Configuration(hostingContext.Configuration)
.Enrich.FromLogContext(),
writeToProviders: true)
.Build();
app.Run();
}

I Solved with:
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog();

Related

Windows Service with .NET 6 not running with WebApplication builder

I am having the following error when trying to create a windows service:
Description: The process was terminated due to an unhandled exception.
Exception Info: System.NotSupportedException: The content root changed from "C:\WINDOWS\system32" to "C:\foo\foo\publish". Changing the host configuration using WebApplicationBuilder.Host is not supported. Use WebApplication.CreateBuilder(WebApplicationOptions) instead.
My builder:
var builder = WebApplication.CreateBuilder(args);
builder.Services...
builder.Host.UseWindowsService();
builder.Host.UseSerilog((ctx, lc) => lc
.WriteTo.Console()
.WriteTo.File(_logDir)
.ReadFrom.Configuration(ctx.Configuration));
WebApplication app = builder.Build();
app.Run();
It works with IHostBuilder:
public static IHostBuilder CreateServiceHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
..
What am I doing wrong?
As the error says, you need to set your ContentRootPath with WebApplicationOptions
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
ContentRootPath = #"C:\foo\foo\publish",
Args = args
});

Add EvenLogging to an IHost container

I’m creating a console app and have recently started adding custom services to the IHost container so I can simply pass the IHost to any number of factory classes and have everything thing I need to configure them. But I’ve gotten stuck when it comes to adding Windows Event Logging as a service, could use some help getting past this.
My Main static method in Program calls CreateHostBuilder and returns an IHostBuilder as shown below.
public static IHostBuilder CreateHostBuilder(string[] args)
{
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("appsettings.json");
IConfiguration configuration = configurationBuilder.Build();
var hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureLogging((hostContext, logging) =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Information);
logging.AddEventLog(eventViewerSettings =>
{
eventViewerSettings.SourceName = "MeCore2";
eventViewerSettings.LogName = "Application";
eventViewerSettings.MachineName = ".";
});
})
.ConfigureServices(services => services.AddDbContext<MeCore2Context>())
// Add custom service for performing DNS queries
.ConfigureServices(services => services.AddTransient<IDnsQueryService>(DnsQueryFactory.Create))
// Add custom service for Managing Runtime Environment Settings
.ConfigureServices(services => services.AddTransient<IEnvironmentSettings>(EnvironmentSettingsFactory.Create))
// Add custom service for Managing String Extractions
.ConfigureServices(services => services.AddTransient<IExtractStringsService>(ExtraxtStringsFactory.Create))
// Add custom service for IP GeoLocation
.ConfigureServices(services => services.AddTransient<IIpGeolocationService>(IpGeolocationFactory.Create));
return hostBuilder;
}
My factory classes are implemented like this.
public static class DnsQueryFactory
{
public static DnsQueryService Create(IServiceProvider serviceProvider)
{
bool exceptionDisplayOnly = serviceProvider.GetRequiredService<IEnvironmentSettings>().WriteErrorsToEventLogs;
IHost host = serviceProvider.GetRequiredService<IHost>();
return new DnsQueryService(exceptionDisplayOnly, host);
}
}
And my concrete service constructors are implemented like this.
public DnsQueryService(bool exceptionDisplayOnly, IHost host)
{
this.exceptionDisplayOnly = exceptionDisplayOnly;
this.logger = host.Services.GetRequiredService<ILogger>();
this.environmentSettings = host.Services.GetRequiredService<IEnvironmentSettings>();
}
When I ran the app after setting up in this manner, I was unable to pull an ILogger from the host container, I could though, pull an ILoggerFactory then I needed to take some additional steps before I had a fully functional ILogger.
I would like to be able to pull the ILogger from the Host container with it fully configured and ready to use for exception handling, warnings, and basic information logging. But I'm stumped here as I can't seem to get the right syntax for using the ILoggingBuilder or ILoggerFactory into the Host container.
I started down the path of creating a static class EventLoggingServices that would accept an IServiceProvider finish out the configuration steps and return an ILogger, but this too has got me stumped. I'm close but not where I need to be and can't find a blog that covers this approach, either that or I'm going at this the wrong way, to begin with. Appreciate the help and thanks in advance.
I believe I've answered my own question with the following code, it is writing to the event logs. I implemented a factory method to encapsulate the ILogger as follows.
public static class EventLoggingFactory
{
public static ILogger<IEventLogging> Create(IServiceProvider serviceProvider)
{
return new EventLogging().EventLogger;
}
}
public class EventLogging : IEventLogging
{
#region *-- Private Members --*
private ILogger<IEventLogging> _logger = null;
#endregion
public ILogger<IEventLogging> EventLogger { get { return this._logger; } }
public EventLogging()
{
EventLogSettings settings = new EventLogSettings();
settings.LogName = "Application";
settings.SourceName = "MeCore2";
settings.MachineName = ".";
ILoggerFactory loggerFactory = new LoggerFactory();
loggerFactory.AddProvider(new EventLogLoggerProvider(settings));
this._logger = loggerFactory.CreateLogger<IEventLogging>();
}
}
public interface IEventLogging
{
ILogger<IEventLogging> EventLogger { get; }
}
And in my HostBuilder the following:
.ConfigureServices(services => services.AddTransient(EventLoggingFactory.Create))
What I haven't considered and I'm still wrapping my head around are service LifeTimes. Using this approach the Ilogger is Transient, but is that the best way to implement it?
The final code block on this post has been a sufficient solution for my needs. With a little more effort I've been able to expand the features used to capture log data for viewing in Windows Event Viewer.

Registering Dependency with in NServiceBus with Autofac after upgrade

I am having a problem working out how to register dependencies in my NServiceBus endpoint. I am using NServiceBus 7.2 and Autofac 5.0 and NServiceBus.Autofac 7.0.0 and can't find any examples that use these versions. I am using Asp.Net Core 3.
My Program.cs code looks like this
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
My ConfigureServices method looks like this
public void ConfigureServices(IServiceCollection services)
{
services.InstallServicesInAssembly(Configuration);
services.AddAutoMapper(typeof(Startup));
services.AddMediatR(typeof(Startup).Assembly);
}
This runs all the installers I have but this runs BEFORE the ConfigureContainer method called by the Framework. AutoFac automatically adds all the services that have been added in COnfigureServices. I have a separate class for each installer. My ConfigureContainer method is currently empty since the Automapper and MediatR services are added anyway because the services are added in ConfigureServices. This resolves both IMediatR and IMapper when they are injected into the controllers of the Api. But they are not available in the NServiceBus Message Handlers. This is because I can't see how to register the endpoint configuration or share the Autofac container after it is created. See the NServiceBus installer code comment below.
//here we register stuff directly with autofac
public void ConfigureContainer(ContainerBuilder builder)
{
// Register your own things directly with Autofac, like:
//builder.AddAutoMapper(typeof(Startup).Assembly);
// builder.AddMediatR(typeof(Startup).Assembly);
}
I want to be able to make use of AutoMapper and MediatR in my NServiceBus Message Handlers and so want these dependencies injected into the constructors of the handlers.
And My NServiceBusInstaller is as follows
public class NServiceBusInstaller : IInstaller
{
public async void InstallServices(IServiceCollection services, IConfiguration configuration)
{
var rabbitMQSettings = new RabbitMQSettings();
configuration.Bind(nameof(rabbitMQSettings), rabbitMQSettings);
services.AddSingleton(rabbitMQSettings);
var endpointConfiguration = new EndpointConfiguration(rabbitMQSettings.SilvaDirectory);
endpointConfiguration.EnableInstallers();
//Here I want to configure the endpoint to use the dependencies in the AutoFac container
//How to get reference to this container??
/*
endpointConfiguration.UseContainer<AutofacBuilder>(
customizations: customizations => {
customizations.ExistingLifetimeScope(container);
});
*/
var transport = endpointConfiguration.UseTransport<RabbitMQTransport>();
transport.UseConventionalRoutingTopology();
transport.ConnectionString(rabbitMQSettings.ConnectionString);
transport.TimeToWaitBeforeTriggeringCircuitBreaker(TimeSpan.FromMinutes(1));
//services.AddNServiceBus(endpointConfiguration);
await Endpoint.Start(endpointConfiguration).ConfigureAwait(false);
}
}
ANd finally one of my message handlers looks like this. Currently I am getting an Exception because NServiceBus cannot resolve the IMapper and IMediator..
public class CreateDirectoryEntryHandler : IHandleMessages<CreateDirectoryEntry>
{
private readonly IMapper _mapper;
private readonly IMediator _mediator;
public CreateDirectoryEntryHandler(IMapper mapper, IMediator mediator)
{
_mapper = mapper;
_mediator = mediator;
}
public async Task Handle(CreateDirectoryEntry message, IMessageHandlerContext context)
{
var command = _mapper.Map<CreateNewCustomerCommand>(message);
CommandResponse response = await _mediator.Send(command);
if(response.Success)
{
await context.Reply(new DirectoryEntryCreated() { Email = message.Email }).ConfigureAwait(false);
}
else
{
await context.Reply(new DirectoryEntryRejected() { Email = message.Email, Error = response.Error }).ConfigureAwait(false);
}
}
}
I am probably missing something obvious. My mental block is because the ConfigureContainer method is called after the Configure services method by the framework so I don't have the container reference to pass in to the NServiceBus installer. What am I missing? Any help would be greatly appreciated.
Thanks
you might want to look at this sample https://docs.particular.net/samples/dependency-injection/aspnetcore/. It shows how to use Asp.Net Core 3 with NServiceBus and Autofac.
In your sample code, you use endpointConfiguration.UseContainer<AutofacBuilder>() API that assumes it's NServiceBus that controls the lifecycle of the DI container. This obviously isn't the case.
The sample uses NServiceBus.Extensions.Hosting and Autofac.Extensions.DependencyInjection packages. First, makes sure that NServiceBus can work with DI container that is managed externally by via Microsoft.Extensions.Hosting and the second is Autofac adapter for Microsoft DI abstractions.

How to Overcome Entity Framework Caching/Tracking Using MassTransit?

Forgive my ignorance, but it appears that MassTransit treats singleton and scoped dependencies effectively the same. At least in the case of an Entity Framework DbContext and, therefore, the UserManager registered via the extension method AddEntityFrameworkStores.
This means that any entities loaded with tracking or otherwise added to the context are cached for the duration of the application lifetime. That, in turn, means that changes to these entities via anything outside of the context (say ad-hoc scripts) won't be recognized until the service is recycled.
Is there a best practice for addressing this limitation? For context, consider the following snippet from startup:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddDbContextPool<SomeDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("SomeConnectionString")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<SomeDbContext>()
.AddDefaultTokenProviders();
// ...
}
And the following consumer:
public class SomeConsumer : IConsumer<ISomeRequest>
{
private readonly SomeDbContext _dbContext;
private readonly UserManager<IdentityUser> _userManager;
public SomeConsumer(SomeDbContext dbContext, UserManager<IdentityUser> userManager)
{
_dbContext = dbContext;
_userManager = userManager;
}
public async Task Consume(ConsumeContext<ISomeRequest> context)
{
var identityUser1 = await _dbContext.Users.FindAsync(1);
var identityUser2 = await _userManager.FindByIdAsync(1);
var consumerHashCode = GetHashCode();
var dbContextHashCode = _dbContext.GetHashCode();
var userManagerHashCode = _userManager.GetHashCode();
var identityUser1HashCode = identityUser1.GetHashCode();
var identityUser2HashCode = identityUser2.GetHashCode();
context.Respond(new SomeResponse());
}
}
In the example above, the consumer (registered transient) serves a different hash code on each request. All of the others serve the same ones (suggesting the same instances), despite the DB context and user manager being registered as scoped.
In case it matters, MassTransit is running from a console application as opposed to a web application.
I was directed to the answer in comments on the original question, but thought I'd post the details for anyone else. Bottom line is that I needed to correctly configure the dependency injection using MassTransit.Extensions.DependencyInjection (see http://masstransit-project.com/MassTransit/usage/containers/msdi.html).
I had started down that path, but missed a key piece when configuring endpoints: e.LoadFrom(provider). Missing that piece effectively turned my consumers into singletons, regardless of how they were actually configured.
Lastly, I needed to configure my DbContext using AddDbContext instead of AddDbContextPool.
Here's a snippet of the bare minimum I needed to make this happen:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddDbContext<SomeDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("SomeConnectionString")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<SomeDbContext>()
.AddDefaultTokenProviders();
// ...
services.AddScoped<SomeConsumer>();
services.AddMassTransit(x =>
{
x.AddConsumer<SomeConsumer>();
});
services.AddSingleton(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host("localhost", "/", h => { });// However you want to get your host
var queueName = "web-service-endpoint";
cfg.ReceiveEndpoint(host, queueName, e => e.LoadFrom(provider));
}));
}

ASP.NET Core 2 not reading endpoints from appsettings

I'm trying to use ASP.NET Core 2.0's new way of configuring Kestrel endpoints via settings in appsettings.json. This was demonstrated at the Microsoft Build 2017 event (see it on YouTube timeframe 21:00-26:20).
The speaker said (and demonstrated) that the following would configuring the listening ports on Kestrel:
{
"Message": "TEST!!",
"Kestrel": {
"Endpoints": {
"Localhost": {
"Address": "127.0.0.1",
"Port": "5011"
}
}
}
}
But this hasn't worked for me. When I use 'dotnet run', the default port of 5000 is still being used. I know the appsettings.json file is being loaded, because I can use the Message value elsewhere.
From the code on GitHub, I can't see any place where Kestrel is being configured.
Has anyone else been able to get this method to work? I can't understand how it works in the demo but not in my own code.
I know this was asked a while ago, but I think it would make sense to post on this one as the ASP.NET Core framework has been enhanced to support Kestrel endpoint configuration from application settings since this question was initially asked.
Please find below more details and a little summary on how to achieve that in ASP.NET Core 2.1 and earlier versions.
It is possible to configure Kestrel endpoints in application settings starting from ASP.NET Core version 2.1, as also stated in the documentation.
For further details, it is possible to check in the actual WebHost.CreateDefaultBuilder() implementation:
from release 2.1, it loads the Kestrel configuration section, while on ASP.NET Core earlier versions it is necessary to explicitly call UseKestrel() when setting up your WebHost.
ASP.NET Core 2.1 setup
On ASP.NET Core 2.1, you can setup Kestrel to listen at http://127.0.0.1:5011 by simply providing the following appsettings.json:
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://127.0.0.1:5011"
}
}
}
}
Note you will need to use Url instead of Address and Port.
ASP.NET Core 2.0 setup
On ASP.NET Core 2.0 this very same behavior can be achieved by explicitly calling UseKestrel() in Program.cs. You may use an extension method to keep your Program.cs clean:
public class Program {
public static void Main(string[] args) {
// configuration
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true)
.AddCommandLine(args)
.Build();
// service creation
var webHost = WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseKestrelEndpoints(config) // extension method to keep things clean
.UseConfiguration(config)
.UseApplicationInsights()
.Build();
webHost.Run();
}
}
The UseKestrelEndpoints() is an extension method that looks into the actual configuration settings and setup Kestrel options accordingly:
public static IWebHostBuilder UseKestrelEndpoints(this IWebHostBuilder webHostBuilder, IConfiguration configuration) {
var endpointSection = configuration.GetSection("kestrel:endpoints");
if (endpointSection.Exists()) {
webHostBuilder.UseKestrel(options => {
// retrieve url
var httpUrl = endpointSection.GetValue<string>("http:url", null);
/* setup options accordingly */
});
}
return webHostBuilder;
}
If needed, you may setup HTTPS in a similar way.
Unfortunately that doesn't work anymore. Check out How to use Kestrel in ASP.NET Core apps . So it doesn't really work with settings anymore. You can do something like the this in you program.cs. Here's I'm setting things up to use https locally but you get the general idea...
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)
{
IHostingEnvironment env = null;
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureAppConfiguration((hostingContext, config) =>
{
env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
.UseKestrel(options =>
{
if (env.IsDevelopment())
{
options.Listen(IPAddress.Loopback, 44321, listenOptions =>
{
listenOptions.UseHttps("testcert.pfx", "ordinary");
});
}
else
{
options.Listen(IPAddress.Loopback, 5000);
}
})
.Build();
}
}
}

Resources