How to conditionally disable Application Insights in ASP.NET Core? - asp.net-core

I'm trying to avoid the service locator pattern in ASP.NET Core when conditionally including Application Insights in my ASP.NET Core application, the reason for this is I want to completely disable Applications Insights during development.
Service locator pattern (not recommended)
The most basic way of doing this is to service locate a IOptions setting in ConfigureServices() after building a service provider using the BuildServiceProvider() method on the IServiceCollection
Example of (not recommended!) service locator pattern:
public void ConfigureService(IServiceCollection services)
{
// Configure the services
services.Configure<AppSettings>(configuration.GetSection(nameof(AppSettings)));
// Build an intermediate service provider
var sp = services.BuildServiceProvider();
// Resolve the settings from the service provider;
var appSettings = sp.GetRequiredService<AppSettings>();
// Conditionally include the service using the settings
if (appSettings.EnableApplicationInsights) {
services.AddApplicationInsightsTelemetry();
}
}
This is not a recommended pattern as it results in an additional copy of singleton services being created. But we can be sure Application Insights is completely disabled in the application, in fact it's not even included in the DI container.
Better pattern #1
A much better way of resolving classes that are dependent on other services is to use the AddXXX overload that provides you with the IServiceProvider. This way you do not need to instantiate an intermediate service provider.
The following samples show how you can use this overload in AddSingleton/AddTransient methods.
services.AddSingleton(serviceProvider =>
{
var settings = serviceProvider.GetRequiredService<AppSettings>();
var fooService = new FooService();
fooService.Enable = settings.EnableFooService
return fooService;
});
services.AddTransient(serviceProvider =>
{
var settings = serviceProvider.GetRequiredService<AppSettings>();
var barService = new BarService();
barService.Enable = settings.EnableBarService
return barService;
});
The overload with IServiceProvider is available for i.e. AddSingleton, AddScoped, AddTransient. This pattern works great and is simple to implement, but often services have AddFoo() methods that do not provide this overload, i.e. AddApplicationInsightsTelemetry, AddCors, AddAuthentication, AddAuthorization...
I got inspiration from Ehsan Mirsaeedi answer:
https://stackoverflow.com/a/56278027/294242
Better pattern #2
We can implement the IConfigureOptions<TOptions> interface, register our configuration class in the
ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfigureOptions<ApplicationInsightsServiceOptions>, ConfigureApplicationInsightsServiceOptions>();
services.AddApplicationInsightsTelemetry();
}
public class ConfigureApplicationInsightsServiceOptions : IConfigureOptions<ApplicationInsightsServiceOptions>
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public ConfigureApplicationInsightsServiceOptions(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void Configure(ApplicationInsightsServiceOptions options)
{
using var scope = _serviceScopeFactory.CreateScope();
var provider = scope.ServiceProvider;
var settings = provider.GetRequiredService<AppSettings>();
if (!settings.EnableTracking)
{
options.EnableQuickPulseMetricStream = false;
options.EnablePerformanceCounterCollectionModule = false;
options.EnableAppServicesHeartbeatTelemetryModule = false;
options.EnableAzureInstanceMetadataTelemetryModule = false;
options.EnableDependencyTrackingTelemetryModule = false;
options.EnableEventCounterCollectionModule = false;
options.EnableAdaptiveSampling = false;
options.EnableDebugLogger = false;
options.EnableHeartbeat = false;
options.EnableRequestTrackingTelemetryModule = false;
options.EnableAuthenticationTrackingJavaScript = false;
options.EnableDiagnosticsTelemetryModule = false;
}
}
}
I'm currently evaluating this pattern but i'm not sure Application Insights is completely disabled in my application.
I got inspiration from:
https://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/#the-new-improved-answer

In the basic example, there is no need to build a service provider. It is even advised against in most documentation. The desired settings can be extracted directly from configuration.
public void ConfigureService(IServiceCollection services) {
var section = configuration.GetSection(nameof(AppSettings));
// Configure the services for IOptions injection
services.Configure<AppSettings>(section);
// Extract the settings from configuration explicitly as needed
AppSettings appSettings = section.Get<AppSettings>();
// Conditionally include the service using the settings
if (appSettings.EnableApplicationInsights) {
services.AddApplicationInsightsTelemetry();
}
//...
}
There really is no need to involve dependent classes or use a custom IConfigureOptions<TOptions> to satisfy the desired conditional in Startup
Reference: Configuration in ASP.NET Core - Bind to an object graph

Related

.NET 6 how to run Migration automatically in program.cs

In .Net 5, we use to be able to call the migration by passing DataContext to Configure method and call the migration in startup class.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext dataContext)
{
// migrate any database changes on startup (includes initial db creation)
dataContext.Database.Migrate();
...
}
How can we do it in .Net 6?
Short Version
It sounds like the real question is where to put code that used to live in Startup.Configure.
In Program.cs use
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<SomeDbContext>();
db.Database.Migrate();
}
Rather long explanation
The Applying Migrations at Runtime section in the EF Core Migrations docs shows that nothing's changed as far as EF Core is concerned.
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<SomeDbContext>();
//Same as the question
db.Database.Migrate();
}
host.Run();
}
It sounds like the real question is where to put code that used to live in Startup.Configure. That code can be placed in the Main method or, if Minimal APIs are used, inside Program.cs. Configuration, Services, Environment etc are available as properties in the WebApplicationBuilder class or the WebApplication created by it. WebApplicationBuilder contains the builder interfaces for DI, configuration, Logging and the host, eg WebApplicationBuilder.Services exposes IServiceCollection.
WebApplication properties expose the middleware configured by WebApplicationBuilder, eg WebApplication.Services exposes IServiceProvider
Startup replacement in Minimal APIs
The methods that were in Startup.cswere merged in Program.cs in .NET 6. Startup.cs contained two kinds of methods:
Methods to configure the host and application, like setting up configuration and DI, by calling the various builder interfaces like IServiceCollection, IConfigurationBuilder. This includes the code that used to be in Startup.ConfigureServices.
Methods that used the host to configure endpoints, use services and middleware. This includes code that was in Startup.Configure.
In .NET 6, the interfaces move to the WebApplicationBuilder and WebApplication classes. Instead of .NET Core calling a "magic" Startup class and injecting the interfaces, the code in Program.cs can access the interfaces it needs directly.
The host building/configuration services are now available through the WebApplicationBuilder class.
Interfaces provided by the complete application host are now available through the WebApplication class which is built by the WebApplicationBuilder.
If you don't need to configure services, you can create a minimal API application with just 3 lines :
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
In your case you need to configure the DbContext at least, so you need to use WebApplicationBuilder and WebApplication separately. This is shown in the next section
Migrations in Minimal APIs
In the basic minimal API Program.cs :
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
DbContexts can be created once a WebApplication instance is created through its Services property:
var builder = WebApplication.CreateBuilder(args);
//Register the DbContexts etc.
...
builder.Services.AddDbContext<SomeDbContext>(....);
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<SomeDbContext>();
db.Database.Migrate();
}
app.MapGet("/", () => "Hello World!");
app.Run();
Of course it's a lot better to use separate methods or classes for such code, keeping Program.cs clean :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<SomeDbContext>(....);
var app = builder.Build();
ApplyMigrations(app);
app.MapGet("/", () => "Hello World!");
app.Run();
static void ApplyMigrations(WebApplication app)
{
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<SomeDbContext>();
db.Database.Migrate();
}
Or even :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<SomeDbContext>(....);
var app = builder.Build();
app.ApplyMigrations()
.UseCustomLogging()
.DoSomeOtherConfiguration()
...;
app.MapGet("/", () => "Hello World!");
app.Run();
With ApplyMigrations an extension method in a separate class :
public static DataExtensions
{
public static WebApplication ApplyMigrations(this WebApplication app)
{
using var scope = app.Services.CreateScope()
var db = scope.ServiceProvider.GetRequiredService<SomeDbContext>();
db.Database.Migrate();
return app;
}
}
In ASP.NET Core 6, it should be:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<YourDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("YourConnectionString")));
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<YourDbContext>();
db.Database.Migrate();
}

Why could not my Blazor project consume MyProj.HttpApi.Client correctly?

I used ABP CLI generated a MVC template, with which I would like to try a Blazor Server project. I do add a MyProjBlazorModule which was as same as every common Module, just like the ConsoleTestApp project did:
namespace MyProj.Blazor
{
[DependsOn(
typeof(MyProjHttpApiClientModule),
typeof(AbpHttpClientIdentityModelModule)
)]
public class MyProjBlazorModule : AbpModule
{
}
}
Then I added the module as service to ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddSyncfusionBlazor();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddApplication<TaurusBlazorModule>();
}
for a rapid test, I also copied ClientDemoService class from template project MyProj.HttpApi.Client.ConsoleTestApp , and I consume it in my index.razor like this:
#inject ClientDemoService _clientService
...
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
profile = await _clientService.RunAsync();
}
But it couldn't work, with a error message in browser:
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found. The default schemes can
be set using either AddAuthentication(string defaultScheme) or
AddAuthentication(Action configureOptions).
while If I copy code identical to the console test project like this:
using (var application = AbpApplicationFactory.Create<MyProjConsoleApiClientModule>())
{
application.Initialize();
var demo = application.ServiceProvider.GetRequiredService<ClientDemoService>();
profile = AsyncHelper.RunSync(() => demo.RunAsync());
}
and it worked. I would like to know the difference between using ABP module and explicitly calling an ugly ServiceProvider method here, and how can I fix this issue in some correct and beautiful way?
Thanks for everyone's help!
Finally, I have got what's wrong with that. In the template source code from abp CLI, the MyProjHttpApiHostModule's ConfigureAuthentication method register authenticate service like this:
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
context.Services.AddAuthentication()
.AddIdentityServerAuthentication(options =>
{
options.Authority = configuration["AuthServer:Authority"];
options.RequireHttpsMetadata = false;
options.ApiName = "MyProj";
options.JwtBackChannelHandler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
});
}
where AddAuthentication() method used empty parameter overload, that caused the No authenticationScheme was specified error. I referenced IdentityServer4 official document and found the right way to do:
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
...
});
That's easy, I should set the default scheme JwtBearerDefaults.AuthenticationScheme
using a different overload of AddAuthentication method just as the error had reported.
I hope this post could help someone facing the same or similar issue.

How does one transform SOAP EndpointAddress in .NET Core?

When connecting a SOAP service in .NET Core the Connected Service is shown as expected in the solution explorer
The ConnectedService.json does contain the definitions as supposed. I.e.
{
"ProviderId": "Microsoft.VisualStudio.ConnectedService.Wcf",
...
"ExtendedData": {
"Uri": "https://test.example.net/Service.svc",
"Namespace": "UserWebService",
"SelectedAccessLevelForGeneratedClass": "Public",
...
}
The Uri from ExtendedData ends up in the Reference.cs file
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.WSHttpBinding_IAnvandareService))
{
return new System.ServiceModel.EndpointAddress("https://test.example.net/Service.svc");
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
If a deployment process looks like TEST > STAGING > PRODUCTION one might like to have corresponding endpoints. I.e. https://production.example.net/Service.svc.
We use Azure Devops for build and Azure Devops/Octopus Deploy for deployments
The solution (as I figured) was to change the endpoint address when you register the dependency i.e.
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
services.AddTransient<IAnvandareService, AnvandareServiceClient>((ctx) => new AnvandareServiceClient()
{
Endpoint =
{
Address = new EndpointAddress($"https://{environment}.example.net/Service.svc")
}
});
This is just an expansion of the answer provided by Eric Herlitz. Primarily meant to show how to use your appsettings.json file to hold the value for the endpoint url.
You will need to add the different endpoints to your appsettings.{enviroment}.json files.
{
...
"ServiceEndpoint": "http://someservice/service1.asmx",
...
}
Then you will need to make sure your environment variable is updated when you publish to different environments. How to transform appsettings.json
In your startup class find the method ConfigureServices() and register your service for dependency injection
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ADSoap, ADSoapClient>(fac =>
{
var endpoint = Configuration.GetValue<string>("ServiceEndpoint");
return new ADSoapClient(ADSoapClient.EndpointConfiguration.ADSoap12)
{
Endpoint =
{
Address = new EndpointAddress(new Uri(endpoint))
}
};
});
}
Then to consume the service in some class you can inject the service into the constructor:
public class ADProvider : BaseProvider, IADProvider
{
public ADSoap ADService { get; set; }
public ADProvider(IAPQ2WebApiHttpClient httpClient, IMemoryCache cache, ADSoap adClient) : base(httpClient, cache)
{
ADService = adClient;
}
}

Nservicebus 5 and later Web Api Depenedency Injection settings

How can I configure Web api dependency settings for NserviceBus 5 and later version.
Version 3 or 4 is like this:
public static class ConfigureWebApi
{
public static Configure ForWebApi(this Configure configure)
{
// Register our http controller activator with NSB
configure.Configurer.RegisterSingleton(typeof(IHttpControllerActivator),
new NSBHttpControllerActivator());
// Find every http controller class so that we can register it
var controllers = Configure.TypesToScan
.Where(t => typeof(IHttpController).IsAssignableFrom(t));
// Register each http controller class with the NServiceBus container
foreach (Type type in controllers)
configure.Configurer.ConfigureComponent(type, ComponentCallModelEnum.Singlecall);
// Set the WebApi dependency resolver to use our resolver
GlobalConfiguration.Configuration.ServiceResolver.SetResolver(new NServiceBusResolverAdapter(configure.Builder));
// Required by the fluent configuration semantics
return configure;
}
}
But Version 5 does not use Configure class, that use BusConfiguration
I try this but can not scan assemblies:
public static class ConfigureWebApi
{
public static BusConfiguration ForWebApi(this BusConfiguration configuration)
{
configuration.RegisterComponents(c => c.RegisterSingleton(typeof(IHttpControllerActivator),
new NServiceBusHttpControllerActivator()));
????
}
}
I'm not sure which way you're thinking. I'm asking, because I might be wrong with my answer. If so, let me know and I'll try to update it.
The way I go about this issue is setting up the container first and then have NServiceBus use that container. I'm using AutoFac and create a special class to set it up.
Disclaimer : I'm copying this from an existing app and didn't try nor compile it. I'm 100% sure this is working though, although I might've forgotten a line or added one too much! :)
public class DependenciesConfig
{
public static IContainer RegisterDependencies()
{
ContainerBuilder builder = new ContainerBuilder();
// MVC Controllers
builder.RegisterModule(new AutofacWebTypesModule());
builder.RegisterControllers(Assembly.GetExecutingAssembly())
// WebAPI controllers
var config = GlobalConfiguration.Configuration;
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterWebApiFilterProvider(config);
// Way more registrations
// Next line is AutoFac specific for WebAPI
builder.RegisterFilterProvider();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
return container;
}
}
Then I have an additional class for registering NServiceBus. I don't have handlers in my web app, nor do I publish messages, so I use a SendOnly endpoint.
public class ServiceBus
{
public static ISendOnlyBus Bus { get; private set; }
private static readonly object padlock = new object();
public static void Init(ILifetimeScope container)
{
if (Bus != null) return;
NServiceBus.Logging.LogManager.Use<CommonLoggingFactory>();
lock (padlock)
{
if (Bus != null) return;
var configuration = new BusConfiguration();
configuration.UseSerialization<JsonSerializer>();
configuration.UseContainer<AutofacBuilder>(x => x.ExistingLifetimeScope(container));
configuration.UseTransport<AzureStorageQueueTransport>();
ConventionsBuilder conventions = configuration.Conventions();
conventions.DefiningCommandsAs(t => t.Namespace != null && t.Namespace.StartsWith("Messages") && t.Namespace.EndsWith("Commands"));
Bus = NServiceBus.Bus.CreateSendOnly(configuration);
}
}
}
Is this what you're looking for?

Asp.Net Web Api and Autofac with Custom Authorisation attribute issue (property injection)

I am using Autofac to inject all my project dependencies which is working great. Now I have added a Custom Authorization attribute (I don't need very complex functionality like OWIN and Identity stuff). The custom authorization attribute has dependency to data layer and therefore I am trying to inject it as a property injection. However the property is always Null. The code is below:
public class CustomAuthorizationFilterAttribute : AuthorizeAttribute, IAutofacAuthorizationFilter
{
public IAuthorisationHelper AuthorisationHelper { get; set; }
public override void OnAuthorization(HttpActionContext actionContext)
{
**... removed for brevity**
**// TODO: this should be injected by autofac and is always null??**
if (AuthorisationHelper.IsValidUser(username, password, out roleOfUser))
{
var principal =
new GenericPrincipal((new GenericIdentity(username)),
(new[] { roleOfUser }));
Thread.CurrentPrincipal = principal;
return;
}
... removed for brevity
}
}
Code that injects the AuthorizationHelper:
public static IContainer Container()
{
var builder = new ContainerBuilder();
var assemblies = new List<Assembly>();
assemblies.Add(Assembly.Load("Kids.Math.Interfaces"));
assemblies.Add(Assembly.Load("Kids.Math.Data"));
assemblies.Add(Assembly.Load("Kids.Math.Business"));
assemblies.Add(Assembly.Load("Kids.Math.ImportExport"));
assemblies.Add(Assembly.Load("Kids.Math.Common"));
assemblies.Add(Assembly.Load("Kids.Math.Api"));
builder.RegisterAssemblyTypes(assemblies.ToArray()).
AsImplementedInterfaces();
builder.RegisterType(typeof(MathContext)).As(typeof (DbContext)).InstancePerRequest();
// Register web API controllers.
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// TODO: this is not working, also this should be generic to register it for all controllers
// inject the authorisation filter
builder.RegisterType<AuthorisationHelper>().As<IAuthorisationHelper>();
builder.Register(c => new CustomAuthorizationFilterAttribute()).PropertiesAutowired()
.AsWebApiAuthorizationFilterFor<QuestionsImportController>()
.InstancePerRequest();
// Set the dependency resolver to be Autofac.
var container = builder.Build();
return container;
}
Attribute is registered in FilterConfig as
filters.Add(new CustomAuthorizationFilterAttribute());
All the wiring up works but AuthorisationHelper is always null.
Any comments will be appreciated.
Aren't you missing some key registration steps here? Refer to the Autofac doco
// OPTIONAL: Register the Autofac filter provider.
builder.RegisterWebApiFilterProvider(config);
// Set the dependency resolver to be Autofac.
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
EDIT: After being told that the configuration has been setup correctly, have you tried registering your filter like this?
builder.RegisterType<CustomAuthorizationFilterAttribute>().PropertiesAutowired()
.AsWebApiAuthorizationFilterFor<QuestionsImportController>()
.InstancePerRequest();
seems like this is a known bug in autofac:
https://code.google.com/p/autofac/issues/detail?id=289