Why could not my Blazor project consume MyProj.HttpApi.Client correctly? - asp.net-core

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.

Related

How to conditionally disable Application Insights in 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

Configure JwtBearerOptions from a configuration file

I am trying to find a documentation how to configure a jwt bearer and its JwtBearerOptions in asp.net core from a configuration file using a microsoft predefined confuration section/keys. There is no explanation in Mucrosoft docs about this is possible or not. I feel that it should be possible because everything in the .net core generation is using the options pattern.
Here is an example how the same technique is used to configure a Kestrel host.
This is not a real answer to the initial question. However I am very happy with this solution.
After several hours of digging into the AspNetCore source code I found that the JwtBearerOptions are added to the DI as a named options. This means that you cannot provide the configuration from a config file without writing code. However I found an acceptable solution which will work for the majority of cases.
I do not have a list of all available keys and the sample here is showing only two of them. You can inspect the public properties of the JwtBearerOptions and add them in the appsettings.json. They will be picked and used by the framework.
See the code bellow and the comments there for details how this works:
appsettings.json
{
"Cronus": {
"Api": {
"JwtAuthentication": {
"Authority": "https://example.com",
"Audience": "https://example.com/resources"
}
}
}
}
Startup.cs
public class Startup
{
const string JwtSectionName = "Cronus:Api:JwtAuthentication";
private readonly IConfiguration configuration;
public Startup(IConfiguration configuration)
{
this.configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
// Gets the settings from a configuration section. Notice how we specify the name for the JwtBearerOptions to be JwtBearerDefaults.AuthenticationScheme.
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, configuration.GetSection(JwtSectionName));
// OR
// Gets the settings from a configuration. Notice how we specify the name for the JwtBearerOptions to be JwtBearerDefaults.AuthenticationScheme.
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, configuration);
services.AddAuthentication(o =>
{
// AspNetCore uses the DefaultAuthenticateScheme as a name for the JwtBearerOptions. You can skip these settings because .AddJwtBearer() is doing exactly this.
o.DefaultAuthenticateScheme = Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer();
}
}
services.AddAuthentication(defaultScheme: JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o => Configuration.Bind("JwtBearerOptions", o));
where
application settings.json
{
"JwtBearerOptions": {
"Audience": "Your aud"
}
}

Options<T> not populating in DI

I'm using .Net Core 2.1 and an Aggregate / Facade pattern for my dependencies (which I happily do elsewhere using Ninject / .net 4.6). But when I try to pass through options I get a null (Debugging I can see there being picked up) but there not passed to Autofac (I'm fairly sure its my as they weren't when I tried Ninject either).
I've made a simple test project (new .net core web application /2.1) and then added a minimal amount of code to replicate
Startup.cs
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.Configure<ApiEndpointsConfiguration>(Configuration);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Create the container builder.
var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterAggregateService<IViewModelProvider>();
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.FullName.StartsWith("TEST")).ToArray();
builder.RegisterAssemblyTypes(assemblies)
.Where(t => t.IsClass)
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterAggregateService<IDomainServiceProvider>();
ApplicationContainer = builder.Build();
var chkOptions = ApplicationContainer.Resolve<IOptions<ApiEndpointsConfiguration>>();
// Create the IServiceProvider based on the container.
return new AutofacServiceProvider(ApplicationContainer);
}
Program.cs
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureServices(services => services.AddAutofac())
.UseStartup<Startup>();
}
IViewModelProvider.cs
public interface IViewModelProvider
{
IProgrammeViewModelBuilder ProgrammeViewModel { get; }
}
IProgrammeViewModelBuilder.cs
public interface IProgrammeViewModelBuilder
{
ProgrammeViewModel GetProgrammeViewModel();
}
My initial issue was that in my service, controller calls the injected viewmodelbuilder
var viewModel = _viewModels.ProgrammeViewModel.GetProgrammeViewModel();
which in turn calls the service -
readonly IOptions<ApiEndpointsConfiguration> _apiSettings;
public ProgrammeService(IOptions<ApiEndpointsConfiguration> apiSettings) : base (new Uri(apiSettings.Value.BaseAddress))
{
_apiSettings = apiSettings;
}
but at that point (the constructor firing) the service configuration items were null so I've stepped through and I can see that services has the values for "ApiEndpointsConfiguration" picked up but when they get passed through to the "builder" the values are null
ApplicationContainer.Resolve<IOptions<ApiEndpointsConfiguration>>();
shows null for the values inside.
Not sure what it is I'm doing wrong?
:( Truly this is when the answer is so much simpler thank it looks. Kudos to anyone who spots it;
services.Configure<ApiEndpointsConfiguration>(Configuration.GetSection("ApiEndpointsConfiguration"));
rather than
services.Configure<ApiEndpointsConfiguration>(Configuration);
So essentially whilst I thought I could see it debugging I was seeing the raw JSON provided values not the "configured service". I'll leave this here as a lesson to myself to check the simple things first.
Not sure what what was actually being "registered" in my first effort.

Set dummy IP address in integration test with Asp.Net Core TestServer

I have a C# Asp.Net Core (1.x) project, implementing a web REST API, and its related integration test project, where before any test there's a setup similar to:
// ...
IWebHostBuilder webHostBuilder = GetWebHostBuilderSimilarToRealOne()
.UseStartup<MyTestStartup>();
TestServer server = new TestServer(webHostBuilder);
server.BaseAddress = new Uri("http://localhost:5000");
HttpClient client = server.CreateClient();
// ...
During tests, the client is used to send HTTP requests to web API (the system under test) and retrieve responses.
Within actual system under test there's some component extracting sender IP address from each request, as in:
HttpContext httpContext = ReceiveHttpContextDuringAuthentication();
// edge cases omitted for brevity
string remoteIpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString()
Now during integration tests this bit of code fails to find an IP address, as RemoteIpAddress is always null.
Is there a way to set that to some known value from within test code? I searched here on SO but could not find anything similar. TA
You can write middleware to set custom IP Address since this property is writable:
public class FakeRemoteIpAddressMiddleware
{
private readonly RequestDelegate next;
private readonly IPAddress fakeIpAddress = IPAddress.Parse("127.168.1.32");
public FakeRemoteIpAddressMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
httpContext.Connection.RemoteIpAddress = fakeIpAddress;
await this.next(httpContext);
}
}
Then you can create StartupStub class like this:
public class StartupStub : Startup
{
public StartupStub(IConfiguration configuration) : base(configuration)
{
}
public override void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
base.Configure(app, env);
}
}
And use it to create a TestServer:
new TestServer(new WebHostBuilder().UseStartup<StartupStub>());
As per this answer in ASP.NET Core, is there any way to set up middleware from Program.cs?
It's also possible to configure the middleware from ConfigureServices, which allows you to create a custom WebApplicationFactory without the need for a StartupStub class:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost
.CreateDefaultBuilder<Startup>(new string[0])
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
public class CustomStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
next(app);
};
}
}
Using WebHost.CreateDefaultBuilder can mess up with your app configuration.
And there's no need to change Product code just to accommodate for testing, unless absolutely necessary.
The simplest way to add your own middleware, without overriding Startup class methods, is to add the middleware through a IStartupFilterā€ as suggested by Elliott's answer.
But instead of using WebHost.CreateDefaultBuilder, just use
base.CreateWebHostBuilder().ConfigureServices...
public class CustomWAF : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return base.CreateWebHostBuilder().ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
I used Elliott's answer within an ASP.NET Core 2.2 project. However, updating to ASP.NET 5.0, I had to replace the override of CreateWebHostBuilder with the below override of CreateHostBuilder:
protected override IHostBuilder CreateHostBuilder()
{
return Host
.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}

How to correctly get dependent scoped services from ISecurityTokenValidator

In my asp.net core 2.0 web app, I've got a custom ISecurityTokenValidator which validates tokens.
It depends on a repository to do a db lookup - the repository itself is setup as a scoped dependency:
services.AddScoped<IMyRepository>(MyRepository);
Now the funkiness comes about because of the way the ISecurityTokenValidator is setup.
It's added in ConfigureServices:
.AddJwtBearer(options =>
{
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(new MyTokenValidator(services.BuildServiceProvider()));
})
This is how it looks:
public class MyTokenValidator : ISecurityTokenValidator
{
private readonly IServiceProvider _serviceProvider;
public MyTokenValidator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public bool CanReadToken(string securityToken) => true;
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters,
out SecurityToken validatedToken)
{
var serviceScopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
using (var scope = serviceScopeFactory.CreateScope())
{
var myRepository = scope.ServiceProvider.GetService<IMyRepository>();
var principalFactory = scope.ServiceProvider.GetService<IUserClaimsPrincipalFactory<User>>();
// Use the repo....
}
}
}
Now, because the IsecurityTokenProvider is only instantiated once, it's effectively a singleton. When I use the service provider to ask for a IMyRepository I was finding that I was always received the same object - there is no new scope as far as it was concerned, because it's in a singleton class.
To get round that, you'll see in the code above Ive had to manually force a new scope every time the token validator is called. Is this really the only way to resolve this, it seems like I'm hacking around to make it work here...
Old question but the best way I have found to solve this problem is to use IPostConfigureOptions<JwtBearerOptions> to configure SecurityTokenValidators.
First register the JWT bearer and options
services.AddAuthentication(options =>
{
...
}).AddJwtBearer(AuthenticateScheme, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
...
};
});
Then register a custom implementation of IPostConfigureOptions<JwtBearerOptions>
services.AddSingleton<IPostConfigureOptions<JwtBearerOptions>, CustomJwtBearerOptionsPostConfigureOptions>();
And register a custom implementation of ISecurityTokenValidator
services.AddSingleton<MyCustomSecurityTokenValidator>();
CustomJwtBearerOptionsPostConfigureOptions could look something like:
public class CustomJwtBearerOptionsPostConfigureOptions : IPostConfigureOptions<JwtBearerOptions>
{
private readonly MyCustomSecurityTokenValidator _tokenValidator; //example dependancy
public CustomJwtBearerOptionsPostConfigureOptions(MyCustomSecurityTokenValidator tokenValidator)
{
_tokenValidator = tokenValidator;
}
public void PostConfigure(string name, JwtBearerOptions options)
{
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(_tokenValidator);
}
}
Now options.SecurityTokenValidators is configured by CustomJwtBearerOptionsPostConfigureOptions which is instantiated by dependency injection and can pass on the relevant decencies.