we can access the IServiceProvider as:
public class ProductService
{
private readonly IProductRepository _productRepository;
public ProductService(IServiceProvider serviceProvider)
{
_productRepository = serviceProvider.GetRequiredService<IProductRepository>();
}
...
}
so it is obvious that .net registers IServiceProvider as a service by default, but I checked the source code:https://github.com/aspnet/Hosting/blob/master/src/Microsoft.Extensions.Hosting/HostBuilder.cs#L198
private void CreateServiceProvider() {
var services = new ServiceCollection();
services.AddSingleton(_hostingEnvironment);
services.AddSingleton(_hostBuilderContext);
services.AddSingleton(_appConfiguration);
services.AddSingleton<IApplicationLifetime, ApplicationLifetime>();
services.AddSingleton<IHostLifetime, ConsoleLifetime>();
services.AddSingleton<IHost, Host>();
services.AddOptions();
services.AddLogging();
foreach (var configureServicesAction in _configureServicesActions) {
configureServicesAction(_hostBuilderContext, services);
}
var containerBuilder = _serviceProviderFactory.CreateBuilder(services);
foreach (var containerAction in _configureContainerActions) {
containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
}
_appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder); // _appServices is IServiceProvider
if (_appServices == null) {
throw new InvalidOperationException($"The IServiceProviderFactory returned a null IServiceProvider.");
}
}
so we can see that for example:
services.AddSingleton(_appConfiguration);
register IConfiguration (ConfigurationRoot instance under the hood), that's why we can use it in the startup.cs:
public class Startup {
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public IConfiguration Configuration { get; }
but I can't see the source code register _appServices(IServiceProvider) anywhere, there is no sth like services.AddSingleton(_appServices); in the source code
So how we still can access IServiceProvider automatically? or I must be missing somewhere, the source code does register IServiceProvider somewhere else?
When BuildServiceProvider extension is eventually invoked on the service collection
/// <summary>
/// Creates a <see cref="ServiceProvider"/> containing services from the provided <see cref="IServiceCollection"/>
/// optionally enabling scope validation.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> containing service descriptors.</param>
/// <param name="options">
/// Configures various service provider behaviors.
/// </param>
/// <returns>The <see cref="ServiceProvider"/>.</returns>
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return new ServiceProvider(services, options);
}
Source
The ServiceProvider instance, adds itself as a service.
internal ServiceProvider(ICollection<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
{
// note that Root needs to be set before calling GetEngine(), because the engine may need to access Root
Root = new ServiceProviderEngineScope(this, isRootScope: true);
_engine = GetEngine();
_createServiceAccessor = CreateServiceAccessor;
_realizedServices = new ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object>>();
CallSiteFactory = new CallSiteFactory(serviceDescriptors);
// The list of built in services that aren't part of the list of service descriptors
// keep this in sync with CallSiteFactory.IsService
CallSiteFactory.Add(typeof(IServiceProvider), new ServiceProviderCallSite());
CallSiteFactory.Add(typeof(IServiceScopeFactory), new ConstantCallSite(typeof(IServiceScopeFactory), Root));
CallSiteFactory.Add(typeof(IServiceProviderIsService), new ConstantCallSite(typeof(IServiceProviderIsService), CallSiteFactory));
if (options.ValidateScopes)
{
_callSiteValidator = new CallSiteValidator();
}
if (options.ValidateOnBuild)
{
List<Exception> exceptions = null;
foreach (ServiceDescriptor serviceDescriptor in serviceDescriptors)
{
try
{
ValidateService(serviceDescriptor);
}
catch (Exception e)
{
exceptions = exceptions ?? new List<Exception>();
exceptions.Add(e);
}
}
if (exceptions != null)
{
throw new AggregateException("Some services are not able to be constructed", exceptions.ToArray());
}
}
DependencyInjectionEventSource.Log.ServiceProviderBuilt(this);
}
Source
Related
The ILoggerProvider Dispose function is not being invoked when I am running a unit test. Here is how I am initating the logger provider:
.AddTransient<ILogger, MyLogger>()
.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.AddProvider(new LoggerProvider(config));
})
and in InitializeTest() I am getting the logger like this
logger = servicesProvider.GetRequiredService<ILogger>();
the loggerprovider class:
public class LoggerProvider : ILoggerProvider
{
public IConfiguration Config { get; }
private MyLogger logger;
public LoggerProvider(IConfiguration config)
{
Config = config;
}
/// <summary>
/// Create logger method.
/// </summary>
/// <param name="categoryName">Category name</param>
/// <returns></returns>
public ILogger CreateLogger(string categoryName)
{
logger = new MyLogger(Config);
return logger;
}
/// <summary>
/// Dispose.
/// </summary>
public void Dispose()
{
logger.Dispose();
}
}
Unit test part
[TestInitialize()]
public void InitializeTest()
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory
.GetCurrentDirectory()) Microsoft.Extensions.Configuration.Json
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();
servicesProvider = ServiceProvider.BuildDi(config);
logger = servicesProvider.GetRequiredService<ILogger>();
}
[TestMethod]
public void TestInformation()
{
try
{
logger.LogInformation("Testing");
}
catch (Exception ex)
{
Assert.Fail(ex.Message);
}
Assert.IsTrue(true);
}
Is dispose not triggered after running a unit test?
Generally speaking for the resources inside the ServiceProvider to be disposed, the ServiceProvider needs to be disposed itself (unless the resource is scoped, then the scope needs to be disposed).
So in this case ServiceProvider needs to be moved to within the test and it needs to be wrapped inside a using:
using (var servicesProvider = ServiceProvider.BuildDi(config))
{
...
}
Or the new way:
using var servicesProvider = ServiceProvider.BuildDi(config);
Here is a .Net Fiddle illustrating this behavior.
I'm working on a small reusable package för .NET Core applications that will help with automatic migrations on application startup.
It will basically run Database.Migrate() on every DbContext.
But the thing here is that I only want to run it on DbContexts that has been "marked" for auto-migration. I'm thinking that I could extend AddDbContext, and somehow tell the IServiceCollection to keep track of the particular DbContext. Something like this:
public static IServiceCollection AddDbContextWithMigration<TContext>(this IServiceCollection serviceCollection, Action<DbContextOptionsBuilder> optionsAction = null, ServiceLifetime contextLifetime = ServiceLifetime.Scoped, ServiceLifetime optionsLifetime = ServiceLifetime.Scoped) where TContext : DbContext
{
//TODO: Somehow remember that this DbContext should be migrated.
return serviceCollection.AddDbContext<TContext, TContext>(optionsAction, contextLifetime, optionsLifetime);
}
Usage:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddDbContextWithMigration<DbContext1>();
services.AddDbContext<DbContext2>();
services.AddDbContextWithMigration<DbContext3>();
}
Then I thought that I either could use IStartupFilter or create an extensionmethod for IApplicationBuilder.
With extensionmethod:
public static IApplicationBuilder RunMigrations(this IApplicationBuilder app)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
var contexts = app.ApplicationServices.GetService();
foreach (DbContext context in contexts)
{
context.Database.Migrate();
}
return app;
}
With IStartupFilter:
public class MigrationsFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return builder =>
{
var contexts = builder.ApplicationServices.GetService();
foreach (DbContext context in contexts)
{
context.Database.Migrate();
}
next(builder);
};
}
}
So I basically have two questions.
How can I keep track of which DbContexts that should be migrated?
Is this a "correct" usage of IStartupFilter?
I solved it by registering a wrapper-class (IContextMigrator) for my migrations.
First, my extension methods:
public static IApplicationBuilder RunMigrations(this IApplicationBuilder app)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
IEnumerable<IContextMigrator> migrations = app.ApplicationServices.GetServices<IContextMigrator>();
foreach (IContextMigrator migration in migrations)
{
migration.Migrate();
// Other logic for dispose...
}
return app;
}
public static IServiceCollection AddDbContextWithMigration<TContext>(this IServiceCollection serviceCollection, Action<DbContextOptionsBuilder> optionsAction = null, ServiceLifetime contextLifetime = ServiceLifetime.Scoped, ServiceLifetime optionsLifetime = ServiceLifetime.Scoped) where TContext : DbContext
{
// By simply registering a transient of a wrapper-class I can resolve it later.
serviceCollection.AddTransient<IContextMigrator, ContextMigrator<TContext>>();
return serviceCollection.AddDbContext<TContext, TContext>(optionsAction, contextLifetime, optionsLifetime);
}
Then my class for actually migrating:
public interface IContextMigrator : IDisposable
{
void Migrate();
}
/// <summary>
/// Class responsible for migration of a DbContext.
/// Used by ApplicationBuilderExtensions to perform migrations.
/// </summary>
/// <typeparam name="T"></typeparam>
internal sealed class ContextMigrator<T> : IContextMigrator
where T : DbContext
{
private readonly T context;
public ContextMigrator(T context)
{
this.context = context;
}
public void Migrate()
{
try
{
this.context.Database.Migrate();
}
catch (Exception e)
{
throw new MigrationException(context.GetType().Name, e);
}
}
public void Dispose()
{
}
}
I have a really weird issue which I have spent an entire day debugging and nowhere close to solving. I am in the process of upgrading my application from ASP.NET Core 1.x to 2.1. As part of doing this, I am having to re-wire the Authentication and Authorization mechanism. We use JWTBearer Authentication, and I am using postman to fire an API call, which executes the pipeline and I can see the AuthHandler executing. However if I fire the same request again, the AuthHandler does not execute and debugger "steps-over" the "context.AuthenticateAsync" call and returns the previous result. For the sake of elaborating, I have written a custom auth handler which is a copy paste of JWTAuthHandler. The code to create a custom handler is based off the answer here.
using Microsoft.AspNetCore.Authentication.JwtBearer;
public class CustomAuthOptions : JwtBearerOptions
{
}
using Microsoft.AspNetCore.Authentication;
public static class CustomAuthExtensions
{
public static AuthenticationBuilder AddCustomAuth(this AuthenticationBuilder builder, Action<CustomAuthOptions> configureOptions)
{
return builder.AddScheme<CustomAuthOptions, CustomAuthHandler>("CustomScheme", configureOptions);
}
}
public class CustomAuthHandler : AuthenticationHandler<CustomAuthOptions>
{
private OpenIdConnectConfiguration _configuration;
public CustomAuthHandler(IOptionsMonitor<CustomAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
/// <summary>
/// The handler calls methods on the events which give the application control at certain points where processing is occurring.
/// If it is not provided a default instance is supplied which does nothing when the methods are called.
/// </summary>
protected new JwtBearerEvents Events
{
get => (JwtBearerEvents)base.Events;
set => base.Events = value;
}
protected override Task<object> CreateEventsAsync() => Task.FromResult<object>(new JwtBearerEvents());
/// <summary>
/// Searches the 'Authorization' header for a 'Bearer' token. If the 'Bearer' token is found, it is validated using <see cref="TokenValidationParameters"/> set in the options.
/// </summary>
/// <returns></returns>
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string token = null;
try
{
// Give application opportunity to find from a different location, adjust, or reject token
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
// event can set the token
await Events.MessageReceived(messageReceivedContext);
if (messageReceivedContext.Result != null)
{
return messageReceivedContext.Result;
}
// If application retrieved token from somewhere else, use that.
token = messageReceivedContext.Token;
if (string.IsNullOrEmpty(token))
{
string authorization = Request.Headers["Authorization"];
// If no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
}
if (authorization.StartsWith("CustomAuth ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("CustomAuth ".Length).Trim();
}
// If no token found, no further work possible
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
}
if (_configuration == null && Options.ConfigurationManager != null)
{
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
var validationParameters = Options.TokenValidationParameters.Clone();
if (_configuration != null)
{
var issuers = new[] { _configuration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(_configuration.SigningKeys)
?? _configuration.SigningKeys;
}
List<Exception> validationFailures = null;
SecurityToken validatedToken;
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception ex)
{
////Logger.TokenValidationFailed(ex);
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
&& ex is SecurityTokenSignatureKeyNotFoundException)
{
Options.ConfigurationManager.RequestRefresh();
}
if (validationFailures == null)
{
validationFailures = new List<Exception>(1);
}
validationFailures.Add(ex);
continue;
}
////Logger.TokenValidationSucceeded();
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
{
Principal = principal,
SecurityToken = validatedToken
};
await Events.TokenValidated(tokenValidatedContext);
if (tokenValidatedContext.Result != null)
{
return tokenValidatedContext.Result;
}
if (Options.SaveToken)
{
tokenValidatedContext.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
}
tokenValidatedContext.Success();
return tokenValidatedContext.Result;
}
}
if (validationFailures != null)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
return AuthenticateResult.Fail(authenticationFailedContext.Exception);
}
return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]");
}
catch (Exception ex)
{
////Logger.ErrorProcessingMessage(ex);
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = ex
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
throw;
}
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
var authResult = await HandleAuthenticateOnceSafeAsync();
var eventContext = new JwtBearerChallengeContext(Context, Scheme, Options, properties)
{
AuthenticateFailure = authResult?.Failure
};
// Avoid returning error=invalid_token if the error is not caused by an authentication failure (e.g missing token).
if (Options.IncludeErrorDetails && eventContext.AuthenticateFailure != null)
{
eventContext.Error = "invalid_token";
eventContext.ErrorDescription = CreateErrorDescription(eventContext.AuthenticateFailure);
}
await Events.Challenge(eventContext);
if (eventContext.Handled)
{
return;
}
Response.StatusCode = 401;
if (string.IsNullOrEmpty(eventContext.Error) &&
string.IsNullOrEmpty(eventContext.ErrorDescription) &&
string.IsNullOrEmpty(eventContext.ErrorUri))
{
Response.Headers.Append(HeaderNames.WWWAuthenticate, Options.Challenge);
}
else
{
// https://tools.ietf.org/html/rfc6750#section-3.1
// WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
var builder = new StringBuilder(Options.Challenge);
if (Options.Challenge.IndexOf(" ", StringComparison.Ordinal) > 0)
{
// Only add a comma after the first param, if any
builder.Append(',');
builder.Append(',');
}
if (!string.IsNullOrEmpty(eventContext.Error))
{
builder.Append(" error=\"");
builder.Append(eventContext.Error);
builder.Append("\"");
}
if (!string.IsNullOrEmpty(eventContext.ErrorDescription))
{
if (!string.IsNullOrEmpty(eventContext.Error))
{
builder.Append(",");
}
builder.Append(" error_description=\"");
builder.Append(eventContext.ErrorDescription);
builder.Append('\"');
}
if (!string.IsNullOrEmpty(eventContext.ErrorUri))
{
if (!string.IsNullOrEmpty(eventContext.Error) ||
!string.IsNullOrEmpty(eventContext.ErrorDescription))
{
builder.Append(",");
}
builder.Append(" error_uri=\"");
builder.Append(eventContext.ErrorUri);
builder.Append('\"');
}
Response.Headers.Append(HeaderNames.WWWAuthenticate, builder.ToString());
}
}
private static string CreateErrorDescription(Exception authFailure)
{
IEnumerable<Exception> exceptions;
if (authFailure is AggregateException agEx)
{
exceptions = agEx.InnerExceptions;
}
else
{
exceptions = new[] { authFailure };
}
var messages = new List<string>();
foreach (var ex in exceptions)
{
// Order sensitive, some of these exceptions derive from others
// and we want to display the most specific message possible.
switch (ex)
{
case SecurityTokenInvalidAudienceException _:
messages.Add("The audience is invalid");
break;
case SecurityTokenInvalidIssuerException _:
messages.Add("The issuer is invalid");
break;
case SecurityTokenNoExpirationException _:
messages.Add("The token has no expiration");
break;
case SecurityTokenInvalidLifetimeException _:
messages.Add("The token lifetime is invalid");
break;
case SecurityTokenNotYetValidException _:
messages.Add("The token is not valid yet");
break;
case SecurityTokenExpiredException _:
messages.Add("The token is expired");
break;
case SecurityTokenSignatureKeyNotFoundException _:
messages.Add("The signature key was not found");
break;
case SecurityTokenInvalidSignatureException _:
messages.Add("The signature is invalid");
break;
}
}
return string.Join("; ", messages);
}
}
And then the Startup.cs to hook it up:
public class Startup
{
/// <summary>
/// Initializes a new instance of the <see cref="Startup"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
ConfigureLogging();
}
/// <summary>
/// Gets the configuration.
/// </summary>
/// <value>
/// The configuration.
/// </value>
public IConfiguration Configuration { get; }
/// <summary>
/// Gets or sets the Container
/// </summary>
private IUnityContainer Container { get; set; }
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var logger = Logger.For(this).ForAction(nameof(ConfigureServices));
services.Configure<GzipCompressionProviderOptions>(options => options.Level = CompressionLevel.Optimal);
services.AddResponseCompression();
logger.Info("Configuring JWT Bearer Token Authorization...");
services.AddAuthentication(options =>
{
// the scheme name has to match the value we're going to use in AuthenticationBuilder.AddScheme(...)
options.DefaultAuthenticateScheme = "CustomScheme";
options.DefaultChallengeScheme = "CustomScheme";
})
.AddCustomAuth(options => {
options.Audience = this.Configuration.ObtainConfiguredString(ConfigurationKeys.ValidAudienceId);
options.Authority = this.Configuration.ObtainConfiguredString(ConfigurationKeys.IssuerId);
options.SaveToken = false;
options.TokenValidationParameters = new TokenValidationParameters().WithConfiguredParameters(this.Configuration);
});
logger.Info("Adding Authorization policies to Services...");
services.AddAuthorization(
options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder("CustomScheme").RequireAuthenticatedUser().Build();
});
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IAuthenticationHandler, CustomAuthHandler>();
EnableCors(services);
logger.Info("Adding MVC support to Services...");
services.AddMvc(config =>
{
var defaultPolicy = new AuthorizationPolicyBuilder(new[] { "CustomScheme" })
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(defaultPolicy));
});
Container = new UnityContainer();
logger.Info("Registering other Services with UnityContainer...");
Container.RegisterServices(Configuration);
// Configure Microsoft DI for Unity resolution
logger.Info("Configuring ASP.Net Core service resolution to use UnityContainer...");
return services.UseUnityResolution(Container, s => s.BuildServiceProvider());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// <summary>
/// The Configure
/// </summary>
/// <param name="app">The app<see cref="IApplicationBuilder"/></param>
/// <param name="env">The env<see cref="IHostingEnvironment"/></param>
/// <param name="loggerFactory">The loggerFactory<see cref="ILoggerFactory"/></param>
/// <param name="memoryCache">The memoryCache<see cref="IMemoryCache"/></param>
/// <param name="contextAccessor">The contextAccessor<see cref="IHttpContextAccessor"/></param>
/// <param name="authzClient">The authzClient<see cref="IAuthzClient"/></param>
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
IMemoryCache memoryCache,
IHttpContextAccessor contextAccessor,
IAuthzClient authzClient)
{
var logger = Logger.For(this).ForAction(nameof(Configure));
logger.Info("Configuring ASP.Net Core logging framework...");
loggerFactory.AddConsole(this.Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
var corsEnabled = this.Configuration.ObtainConfiguredBooleanWithDefault(ConfigurationKeys.EnableCors, false);
if (corsEnabled)
{
app.UseCors("CorsPolicy");
}
logger.Info("Configuring ASP.Net Core custom status page...");
app.UseStatusCodePagesWithReExecute("/error/{0}");
if (env.IsDevelopment())
{
logger.Info("Configuring development middle-ware...");
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
logger.Info("Configuring standard ASP.Net Core behaviors...");
app.UseDefaultFiles();
app.UseStaticFiles();
////app.UseAuthentication();
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
var result = await context.AuthenticateAsync("CustomScheme");
if (result?.Principal != null)
{
context.User = result.Principal;
}
}
await next.Invoke();
});
app.UseMvc();
app.WithRequestLogging();
}
private void EnableCors(IServiceCollection service)
{
var logger = Logger.For(this).ForAction(nameof(EnableCors));
var corsEnabled = this.Configuration.ObtainConfiguredBooleanWithDefault(ConfigurationKeys.EnableCors, false);
if (corsEnabled)
{
logger.Verbose("Configuring ASP.Net Core CORS support...");
service.AddCors(
options =>
{
options.AddPolicy("CorsPolicy",
builder =>
{
builder.AllowAnyOrigin();
builder.AllowAnyHeader();
builder.AllowAnyMethod();
builder.AllowCredentials();
});
});
}
}
}
}
Can someone please tell me what I am doing wrong? First time around when I fire the postman request with the correct AuthorizationHeader with the access token this line executes the CustomAuthHandler:
var result = await context.AuthenticateAsync("CustomScheme");
However the second time around the debugger steps over that code? This is driving me up the wall. I must be missing something basic!
EDIT:
In the Core 1.x version, ConfigureServices was setup as
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var logger = Logger.For(this).ForAction(nameof(ConfigureServices));
logger.Verbose("Adding MVC support to Services...");
// Add framework services.
services.AddMvc();
logger.Verbose("Adding Authorization policies to Services...");
services.AddAuthorization(
options =>
{
options.AddPolicy(
"SomePermission",
policy => policy.RequireClaim("claimUrl", "Some Permission"));
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
container = new UnityContainer();
logger.Verbose("Registering other Services with UnityContainer...");
container.RegisterServices(Configuration);
// Configure Microsoft DI for Unity resolution
logger.Verbose("Configuring ASP.Net Core service resolution to use UnityContainer...");
return services.UseUnityResolution(container, s => s.BuildServiceProvider());
}
And Configure() was wired up as follows
app.UseAuth0JwtBearerAuthentication(
new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters =
new TokenValidationParameters().WithConfiguredParameters(this.Configuration)
});
if (env.IsDevelopment())
{
logger.Verbose("Configuring development middleware...");
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
logger.Verbose("Configuring standard ASP.Net Core behaviors...");
app.UseDefaultFiles();
app.UseMvc();
app.UseStaticFiles();
Using this version if I execute postman calls, then I get a new ClaimsPrincipal for every request. So what has changed in ASP.NET Core 2.1?
For anyone who faces the same issue; my problem turned out to be Unity. ASP.NET Core 2.0 does not support Unity out of the box, because the ConfigureServices() method in Startup.cs is a replacement for third party DI container like Unity or Autofac. However if you still want to use Unity, you need to Nuget Unity.Microsoft.DependencyInjection to your project. The Github repo has details on how to wire it up.
Additionally all of the other dependent projects were using Unity 4.0.1 which has IUnityContainer under Microsoft.Practices.Unity, whereas from Unity 5 upwards, the IUnityContainer has been moved to Unity namespace. This was an additional gotcha, whereby even after setting up the DI container as per Github repo's instructions, I was getting exceptions with failed dependency resolutions. The workaround was the create a new UnityContainer using Microsoft.Practices.Unity, let the dependent projects bootstrap it, and then copy those registrations to IUnityContainer under the Unity namespace.
Startup.cs
public void ConfigureContainer(IUnityContainer container)
{
container.RegisterServices(Configuration);
}
UnityRegistrations.cs
public static void RegisterServices(this IUnityContainer container, IConfiguration configuration)
{
// Microsoft.Practices.Unity
var currentContainer = new UnityContainer();
// Bootstrap this and register dependencies
// Then copy them over
foreach (var registration in currentContainer.Registrations)
{
container.RegisterType(registration.RegisteredType, registration.MappedToType);
}
}
I have a custom AuthorizationFilter Attribute on my Web Api project like this
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class GenericAuthenticationFilter : AuthorizationFilterAttribute
{
/// <summary>
/// Public default Constructor
/// </summary>
public GenericAuthenticationFilter()
{
}
private readonly bool _isActive = true;
/// <summary>
/// parameter isActive explicitly enables/disables this filter.
/// </summary>
/// <param name="isActive"></param>
public GenericAuthenticationFilter(bool isActive)
{
_isActive = isActive;
}
/// <summary>
/// Checks basic authentication request
/// </summary>
/// <param name="filterContext"></param>
public override void OnAuthorization(HttpActionContext filterContext)
{
if (!_isActive) return;
var identity = FetchAuthHeader(filterContext);
if (identity == null)
{
ChallengeAuthRequest(filterContext);
return;
}
var genericPrincipal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = genericPrincipal;
if (!OnAuthorizeUser(identity.Name, identity.Password, filterContext))
{
ChallengeAuthRequest(filterContext);
return;
}
base.OnAuthorization(filterContext);
}
My StartUpClass is like this
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
// Get your HttpConfiguration. In OWIN, you'll create one
// rather than using GlobalConfiguration.
var config = new HttpConfiguration();
WebApiConfig.Register(config);
IoC.Instance.RegisterApiControllers(Assembly.GetExecutingAssembly());
config.DependencyResolver =
new AutofacWebApiDependencyResolver(IoC.Instance.GetComponentsContainer());
// Register your Web Api controllers.
IoC.Instance.RegisterApiControllers(Assembly.GetExecutingAssembly());
IoC.Instance.RegisterWebApiModelBinders(Assembly.GetExecutingAssembly());
IoC.Instance.RegisterWebApiModelBinderProvider();
IoC.Instance.RegisterWebApiFilterProvider(config);
// Register the Autofac middleware FIRST, then the Autofac Web API middleware,
// and finally the standard Web API middleware.
app.UseAutofacMiddleware(IoC.Instance.GetComponentsContainer());
app.UseAutofacWebApi(config);
app.UseWebApi(config);
}
}
and my IoC class where all dependencies are resolved is like this
public class IoC : ContainerBuilder
{
/// <summary>
///
/// </summary>
private readonly static IoC _instance = new IoC();
/// <summary>
///
/// </summary>
private static object _lock;
/// <summary>
///
/// </summary>
private IContainer _componentsContainer;
/// <summary>
///
/// </summary>
public static IoC Instance
{
get
{
return _instance;
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public IContainer GetComponentsContainer()
{
if (_componentsContainer == null)
{
lock (_lock)
{
if (_componentsContainer == null)
_componentsContainer = this.Build();
}
}
return _componentsContainer;
}
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Resolve<T>() where T : class
{
return GetComponentsContainer().Resolve<T>();
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public ILifetimeScope BeginLifetimeScope()
{
return GetComponentsContainer().BeginLifetimeScope();
}
/// <summary>
///
/// </summary>
private IoC()
{
_lock = new object();
ConfigureDependencies();
}
/// <summary>
///
/// </summary>
private void ConfigureDependencies()
{
//Configure all your depedendencies here!!
//Database connection
var connectionString = ConfigurationManager.ConnectionStrings["DBConnectionStringName"].ConnectionString;
this.Register(c => new SqlConnection(connectionString)).As<IDbConnection>().InstancePerRequest();// InstancePerLifetimeScope();
//Database Connection OrmLite
OrmLiteConfig.DialectProvider = SqlServerDialect.Provider;
//Register Repositories
this.RegisterType<Repository>().As<IRepository>().InstancePerRequest();// InstancePerLifetimeScope();
// Register Services
this.RegisterType<UserService>().As<IUserService>().InstancePerRequest();// InstancePerLifetimeScope();
this.RegisterType<TokenService>().As<ITokenService>().InstancePerRequest();
this.RegisterType<DKMenuService>().As<IDKMenuService>().InstancePerRequest();// InstancePerLifetimeScope();
this.RegisterType<DKGRIDTblService>().As<IDKGRIDTblService>().InstancePerRequest();// InstancePerLifetimeScope();
this.RegisterType<FKService>().As<IFKService>().InstancePerRequest();// InstancePerLifetimeScope();
this.RegisterType<LOVService>().As<ILOVService>().InstancePerRequest();// InstancePerLifetimeScope();
this.RegisterType<JobService>().As<IJobService>().InstancePerRequest();// InstancePerLifetimeScope();
this.RegisterType<MADEService>().As<IMADEService>().InstancePerRequest();// InstancePerLifetimeScope();
}
}
And I decorate my Controllers with this filter like this
[GenericAuthenticationFilter]
public AuthenticateController(ITokenService tokenService)
{
_tokenService = tokenService;
}
My Problem is that the OnAuthorazation method of the GenericAuthenticationFilter is never fired.
If on the IoC Class class I change InstancePerRequest to InstancePerLifetimeScope everything works ok, but I want my dependencies to work per Request
Any Ideas?
I'm not sure if this is part or all of your issue, but... you can only build a ContainerBuilder once. In Startup.Configuration() I see on lines 11-12:
config.DependencyResolver =
new AutofacWebApiDependencyResolver(IoC.Instance.GetComponentsContainer());
And IoC.Instance.GetComponentsContainer() calls Build() to create the container.
But just two lines later I see you're adding more components to the container, and after that I see a second call:
app.UseAutofacMiddleware(IoC.Instance.GetComponentsContainer());
Based on your code, that's going to be the same container that was built before you added the new registrations. The container won't include the API controllers, the model binders, or the filter provider.
I'm actually not sure why you're not having more problems than you're having now.
Try moving the setting of the containers (the calls to IoC.Instance.GetComponentsContainer()) until all the way at the end, after you've finished registering all of your dependencies.
The only configuration that worked was the following
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
// Get your HttpConfiguration. In OWIN, you'll create one
// rather than using GlobalConfiguration.
var config = new HttpConfiguration();
WebApiConfig.Register(config);
// Register your Web Api controllers.
IoC.Instance.RegisterApiControllers(Assembly.GetExecutingAssembly());
IoC.Instance.RegisterWebApiModelBinders(Assembly.GetExecutingAssembly());
IoC.Instance.RegisterWebApiModelBinderProvider();
config.DependencyResolver =
new AutofacWebApiDependencyResolver(IoC.Instance.GetComponentsContainer());
// Register your Web Api controllers.
//IoC.Instance.RegisterApiControllers(Assembly.GetExecutingAssembly());
//IoC.Instance.RegisterWebApiModelBinders(Assembly.GetExecutingAssembly());
//IoC.Instance.RegisterWebApiModelBinderProvider();
// Register the Autofac middleware FIRST, then the Autofac Web API middleware,
// and finally the standard Web API middleware.
app.UseAutofacMiddleware(IoC.Instance.GetComponentsContainer());
app.UseAutofacWebApi(config);
app.UseWebApi(config);
}
}
I am still not sure whether it is right.
Examples of usage. I have a filter like the following
public class ApiAuthenticationFilter : GenericAuthenticationFilter
{
/// <summary>
/// Default Authentication Constructor
/// </summary>
public ApiAuthenticationFilter()
{
}
}
A method in this filter is using a service which is resolved like this
protected override bool OnAuthorizeUser(string username, string password, HttpActionContext actionContext)
{
var provider = actionContext.Request.GetDependencyScope().GetService(typeof(IUserService)) as IUserService;
}
My controllers on the other hand do not have parameterless constructors and the dependencies are resolved automatically
public class DKMenuController : ApiController
{
#region Private variable.
private readonly ITokenService _tokenService;
private readonly IDKMenuService _dkMenuService;
private readonly IUserService _userservice;
private const string Token = "Token";
#endregion
#region Public Constructor
/// <summary>
/// Public constructor to initialize DKMenu service instance
/// </summary>
public DKMenuController(ITokenService tokenService, IUserService userservice, IDKMenuService dkMenuService)
{
_tokenService = tokenService;
_dkMenuService = dkMenuService;
_userservice = userservice;
}
}
I don't know whether it is correct but it works
Is it possible with an Owin Middleware implementation to add claims prior to the execution of a Web API controller?
Created an OwinMiddleware implementation and added an identity:
var id = new ClaimsIdentity();
id.AddClaim(new Claim("Whatever", "is possible"));
context.Authentication.User.AddIdentity(id);
await Next.Invoke(context);
However, even this Invoke method call the identities are not updated (just the internal claims array). And the controller when executed of course never gets the new dummy claim.
Ideas?
There's already a class that can provide claims enrichment ClaimsAuthenticationManager, which you can extend so it handles your domain-specific claims, for example...
public class MyClaimsAuthenticationManager : ClaimsAuthenticationManager
{
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return base.Authenticate(resourceName, incomingPrincipal);
}
return AddApplicationClaims(incomingPrincipal);
}
private ClaimsPrincipal AddApplicationClaims(ClaimsPrincipal principal)
{
// TODO: Add custom claims here based on current principal.
return principal;
}
}
Next task is to provide appropriate middleware to invoke this. For my projects I've written the following classes...
/// <summary>
/// Middleware component to apply claims transformation to current context
/// </summary>
public class ClaimsTransformationMiddleware
{
private readonly Func<IDictionary<string, object>, Task> next;
private readonly IServiceProvider serviceProvider;
public ClaimsTransformationMiddleware(Func<IDictionary<string, object>, Task> next, IServiceProvider serviceProvider)
{
this.next = next;
this.serviceProvider = serviceProvider;
}
public async Task Invoke(IDictionary<string, object> env)
{
// Use Katana's OWIN abstractions
var context = new OwinContext(env);
if (context.Authentication != null && context.Authentication.User != null)
{
var manager = serviceProvider.GetService<ClaimsAuthenticationManager>();
context.Authentication.User = manager.Authenticate(context.Request.Uri.AbsoluteUri, context.Authentication.User);
}
await next(env);
}
}
And then a wiring extension...
public static class AppBuilderExtensions
{
/// <summary>
/// Add claims transformation using <see cref="ClaimsTransformationMiddleware" /> any depdendency resolution is done via IoC
/// </summary>
/// <param name="app"></param>
/// <param name="serviceProvider"></param>
/// <returns></returns>
public static IAppBuilder UseClaimsTransformation(this IAppBuilder app, IServiceProvider serviceProvider)
{
app.Use<ClaimsTransformationMiddleware>(serviceProvider);
return app;
}
}
I know this is service locator anti-pattern but using IServiceProvider is container neutral and seems to be the accepted way of putting dependencies into Owin middleware.
Last you need to wire this up in your Startup, example below presumes Unity and registering/exposing a IServiceLocator property...
// Owin config
app.UseClaimsTransformation(UnityConfig.ServiceLocator);
You may find useful inheriting from Authorizate Attribute and extending it to meet your requirements:
public class DemoAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext){
if (Authorize(actionContext)){
return;
}
HandleUnauthorizedRequest(actionContext);
}
protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext){
var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized;
//Adding your code here
var id = new ClaimsIdentity();
id.AddClaim(new Claim("Whatever", "is possible"));
context.Authentication.User.AddIdentity(id);
challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
throw new HttpResponseException(challengeMessage);
}
private bool Authorize(System.Web.Http.Controllers.HttpActionContext actionContext){
try{
var someCode = (from h in actionContext.Request.Headers where h.Key == "demo" select h.Value.First()).FirstOrDefault();
// or check for the claims identity property.
return someCode == "myCode";
}
catch (Exception){
return false;
}
}
}
And in your controller:
[DemoAuthorize]
public class ValuesController : ApiController{
Here is a link on other custom implemenation for WebApi Authorizations:
http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/
This is how I ended up adding a new claim in owin middleware, based on the OP's comment about hooking into UseOAuthBearerAuthentication. It uses IdentityServer3.AccessTokenValidation, which calls UseOAuthBearerAuthentication internally and passes the OAuthBearerAuthenticationProvider through to it.
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityServer3.AccessTokenValidation;
using Owin;
using Microsoft.Owin.Security.OAuth;
//...
public void Configuration(IAppBuilder app)
{
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://127.0.0.1/identityserver",
TokenProvider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = AddClaim
}
});
}
private Task AddClaim(OAuthValidateIdentityContext context)
{
context.Ticket.Identity.AddClaim(new Claim("test", "123"));
return Task.CompletedTask;
}