I would like to pass user information to service layer like this (in net core web app):
services.AddScoped<RequestInfo>(provider =>
{
var context = provider.GetRequiredService<IHttpContextAccessor>();
var userJson = ((ClaimsIdentity)context.HttpContext.User.Identity).FindFirst("LoggedInUser").Value;
var user = JsonConvert.DeserializeObject<User>(userJson);
return new RequestInfo(int.Parse(user.UserId), user.ClientId);
});
This should work for controllers and services, but I also have BackgroundService to handle messages. I'd like to use user information from message before I resolve service to handle message. Is it possible to achieve or any workaround? Something like (pseudo code)
using var scope = serviceProvider.CreateScope();
SetupUserInfo(..have no access to ServiceCollection .., contract user info);
var service = scope.ServiceProvider.GetRequiredService(..my type);
Update
My best idea
services.AddScoped<RequestInfo>();
var info = provider.GetRequiredService<RequestInfo>();
info.UserId = .. get info from http context somewhere in middleware (for API)
info.UserId = .. get info from message before resolve of main dependency (for Background service)
Related
I am working in Multi-tenant solution primarily there are 2 type of applications
WebAPI
Console app to process message from queue
I have implemented dependency injection to inject all services. I have crated TenantContext class where I am resolving tenant information from HTTP header and it's working fine for API, but console application getting tenant information with every message (tenant info is part of queue message) so I am calling dependency injection register method on every incoming message which is not correct, do you have any suggestion/solution here?
The way I am resolving ITenantContext in API
services.AddScoped<ITenantContext>(serviceProvider =>
{
//Get Tenant from JWT token
if (string.IsNullOrWhiteSpace(tenantId))
{
//1. Get HttpAccessor and processor settings
var httpContextAccessor =
serviceProvider.GetRequiredService<IHttpContextAccessor>();
//2. Get tenant information (temporary code, we will get token from JWT)
tenantId = httpContextAccessor?.HttpContext?.Request.Headers["tenant"]
.FirstOrDefault();
if (string.IsNullOrWhiteSpace(tenantId))
//throw bad request for api
throw new Exception($"Request header tenant is missing");
}
var tenantSettings =
serviceProvider.GetRequiredService<IOptionsMonitor<TenantSettings>>();
return new TenantContext(tenantId, tenantSettings );
});
Create two different ITenantContext implementations. One for your Web API, and one for your Console application.
Your Web API implementation than might look as follows:
public class WebApiTenantContext : ITenantContext
{
private readonly IHttpContextAccessor accessor;
private readonly IOptionsMonitor<TenantSettings> settings;
public WebApiTenantContext(
IHttpContextAccessor accessor,
IOptionsMonitor<TenantSettings> settings)
{
// Notice how the dependencies are not used in this ctor; this is a best
// practice. For more information about this, see Mark's blog:
// https://blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple/
this.accessor = accessor;
this.settings = settings;
}
// This property searches for the header each time its called. If needed,
// it can be optimized by using some caching, e.g. using Lazy<string>.
public string TenantId =>
this.accessor.HttpContext?.Request.Headers["tenant"].FirstOrDefault()
?? throw new Exception($"Request header tenant is missing");
}
Notice that this implementation might be a bit naive for your purposes, but hopefully you'll get the idea.
This class can be registered in the Composition Root of the Web API project as follows:
services.AddScoped<ITenantContext, WebApiTenantContext>();
Because the WebApiTenantContext has all its dependencies defined in the constructor, you can do a simple mapping between the ITenantContext abstraction and the WebApiTenantContext implementation.
For the Console application, however, you need a very different approach. The WebApiTenantContext, as shown above, is currently stateless. It is able to pull in the required data (i.e. TenantId) from its dependencies. This probably won't work for your Console application. In that case, you will likely need to manually wrap the execution of each message from the queue in a IServiceScope and initialize the ConsoleTenantContext at the beginning of that request. In that case, the ConsoleTenantContext would look merely as follows:
public class ConsoleTenantContext : ITentantContext
{
public string TenantId { get; set; }
}
Somewhere in the Console application's Composition Root, you will have to pull messages from the queue (logic that you likely already have), and that's the point where you do something as follows:
var envelope = PullInFromQueue();
using (var scope = this.serviceProvider.CreateScope())
{
// Initialize the tenant context
var context = scope.ServiceProvider.GetRequiredService<ConsoleTenantContext>();
content.TenantId = envelope.TenantId;
// Forward the call to the message handler
var handler = scope.ServiceProvider.GetRequiredService<IMessageHandler>();
handler.Handle(envelope.Message);
}
The Console application's Composition Root will how have the following registrations:
services.AddScoped<ConsoleTenantContext>();
services.AddScoped<ITenentContext>(
c => c.GetRequiredServices<ConsoleTenantContext>());
With the registrations above, you register the ConsoleTenantContext as scoped. This is needed, because the previous message infrastructure needs to pull in ConsoleTenantContext explicitly to configure it. But the rest of the application will depend instead on ITenantContext, which is why it needs to be registered as well. That registration just forwards itself to the registered ConsoleTenantContext to ensure that both registrations lead to the same instance within a single scope. This wouldn't work when there would be two instances.
Note that you could use the same approach for Web API as demonstrated here for the Console application, but in practice it's harder to intervene in the request lifecycle of Web API compared to doing that with your Console application, where you are in full control. That's why using an ITenantContext implementation that is itself responsible of retrieving the right values is in this case an easier solution for a Web API, compared to the ITenantContext that is initialized from the outside.
What you saw here was a demonstration of different composition models that you can use while configuring your application. I wrote extensively about this in my series on DI Composition Models on my blog.
I am having below situation : I am using web api core 3.1 framework (c#)
I am using typed httpclient registered in the startup . while registering typed client on startup, i am not able to provide the base URL and credentials because I am getting thru a service called configread and it reads the data from the header , which will be only available when one of our middle ware runs and sets it.
in my case base address, user id and passwords are coming from a service call but service calls depends on the request header (httpContext object). in the configureService methods , request context is not available.
Right now i am having trouble to get the httpClient from the startup.
Any guidance would be appreciated.
Update1:
I am adding a typed client as below
service.AddHttpClient<IAgencyServiceAgent,AgencyServiceAgent> (GetAgencyAgentHttpClient()).
ConfigurePrimaryHttpMessageHandler(GetAgencyHttpMessageHandler()) private Action<HttpClient> GetAgencyAgentHttpClient ()
{
var configUrl = Environment.GetEnvironmentVariable(ConfigConstants.CONFIGSERVICE URL)
return httpClient => {
// Here the base address is availble thru another service // which accept the data from the httpContext and based on the values / It pulls the base address and request header etc...
}
}
Update2:
I am having difficulty in setting this httpclient in the startup beacuse baseUrl and other info depends on the request object. For ex: i am reading a request header called DEV1 and passing it to another service , then it will return me the base address and credentials needed then after i can set the http client My questions are how do go about it . When httpClient configurations are depend on the httpContext object .. then how we should register and use it Thanks
According to your description, I suggest you could try to build ServiceProvider inside your GetAgencyAgentHttpClient method and use GetService method to get which service you want to use.
More details, you could refer to below codes:
services.AddHttpClient("hello", c =>
{
//Build service provider
ServiceProvider serviceProvider = services.BuildServiceProvider();
//Get the ICurrentUserService
var currentUserService = serviceProvider.GetService<ICurrentUserService>();
//Use ICurrentUserService GetIPaddress method
var re= currentUserService.GetIPaddress();
c.BaseAddress = new Uri("http://localhost:5000");
}).AddTypedClient(c => Refit.RestService.For<IHelloClient>(c)); ;
Result:
If you want to check get the http header from current httprequest, you could try to get the httpcontext accessor in the service.
More details, you could refer to below codes:
services.AddHttpClient("hello", c =>
{
ServiceProvider serviceProvider = services.BuildServiceProvider();
//var currentUserService = serviceProvider.GetService<ICurrentUserService>();
//var re= currentUserService.GetIPaddress();
var httpcontext = serviceProvider.GetService<IHttpContextAccessor>();
var re = httpcontext.HttpContext.Request.Headers.ToList();
c.BaseAddress = new Uri("http://localhost:5000");
}).AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
Result:
I am trying to Azure AD authentication with gRPC-Web in a blazor webassembly app. I am using protobuf-net to help me with the serialization. I am not sure how to pass the token to have the server side recognize it. this is what I have:
var headers = new Metadata
{
{ "Authorization", $"Bearer {Token}" }
};
and, I am sending that as a parameter in the method I want to consume
var result = await Client.CreateCustomer(this.customer, headers);
This is how the service is injected:
builder.Services.AddTransient(services =>
{
var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
var channel = Grpc.Net.Client.GrpcChannel.ForAddress("****", new GrpcChannelOptions { HttpClient = httpClient });
return channel.CreateGrpcService<Application.Services.ICustomerService<ServerCallContext>>();
});
This is how the service is published:
endpoints.MapGrpcService<CustomerService>().RequireAuthorization().EnableGrpcWeb()
and, this is the implementation:
public class CustomerService : ICustomerService<ServerCallContext>
{
[Authorize]
public async ValueTask<Customer> CreateCustomer(Customer customerDTO, ServerCallContext context)
{****}
}
the error I am getting is cannot convert from 'Grpc.Core.Metadata' to 'Grpc.Core.ServerCallContext' which is kind of obvious.
The reference I have found uses Metadata but is ServerCallContext the one I am supposed to use https://learn.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/metadata so what I am missing, what I am doing wrong, how to properly use both using protobuf-net?
It looks like the problem here is that you're using ServerCallContext in the method signature; the underlying gRPC core has separate client/server context APIs, but this is not amenable to use on an agnostic interface, and as such, protobuf-net.Grpc unifies these two APIs, via CallContext. So: instead of:
async ValueTask<Customer> CreateCustomer(Customer customerDTO, ServerCallContext context)
for the signature, consider:
async ValueTask<Customer> CreateCustomer(Customer customerDTO, CallContext context)
or
async ValueTask<Customer> CreateCustomer(Customer customerDTO, CallContext context = default)
The CallContext API exposes the common server-side and client-side APIs (headers, cancellation, etc) in a single way, or you can use (for example) context.ServerCallContext to get the server-specific API if needed (this will throw an exception if used on a client-context). For client-side usage, a CallContext can be constructed from a CallOptions, which is the core gRPC client-side API, for example:
var result = await service.CreateCustomer(customer, new CallOptions(headers));
I'm open to the idea of allowing CallContext to be created directly from Metadata / CancellationToken etc (allowing var result = await service.CreateCustomer(customer, headers);) - but it doesn't seem essential.
I am learning ASP.NET core and struggling trying to inject some ILogger instances required as parameters within a factory method used within my AddJsonProtocol of the AddSignalR method. See code below.
The JsonConvertersFactory factory method creates the JsonConverters for serialization/deserialization in signalR. It is used within the .AddJsonProtocol anonymous method when adding SignalR service.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddHostedService<MqttKafkaBridge>();
services.AddHostedService<ConsumerService>();
services.AddLogging();
// how can I retrieve ILogger<MotionDetection>, ILogger<MotionInfo>, ILogger<JsonVisitor>?
services.AddSignalR(o => o.EnableDetailedErrors = true)
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions = JsonConvertersFactory.CreateDefaultJsonConverters(LoggerMotionDetection, LoggerMotionInfo, LoggerJsonVisitor);
});
services.AddCustomConfiguration(services);
}
I could build a temporary service provider and add logger dependencies so that the SignalR Json protocol could be configued. However I am not sure if this is considered an anti-pattern....
var sp = services.BuildServiceProvider();
var loggerMD = sp.GetService<ILogger<MotionDetection>>();
var loggerMI = sp.GetService<ILogger<MotionInfo>>();
var loggerJV = sp.GetService<ILogger<JsonVisitor>>();
When I use BuildServiceProvider I receive the following warning message:
Startup.cs(53,22): warning ASP0000: Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
Is it possible to add a service in Configure method, i.e. add the SignalR service in Configure when ILogger instances can be resolved?
OR
Is it possible to inject the SignalRService in Configure method and update it's Json protocol configuration?
Update
Tried injecting ISignalRServerBuilder in Configurebut receive an exception that could not find an ISignalRServerBuilder instance in container registry (Autofac).
Any ideas?
public void Configure(IApplicationBuilder app, ISignalRServerBuilder builder, IWebHostEnvironment env)
{
// this tries to build the options from the service container but
// the builder is null
builder.AddJsonProtocol(op =>
{
var lMD = app.ApplicationServices.GetService<ILogger<MotionDetectionConverter>>();
var lMI = app.ApplicationServices.GetService<ILogger<MotionInfoConverter>>();
var lJV = app.ApplicationServices.GetService<ILogger<JsonVisitor>>();
op.PayloadSerializerOptions = JsonConvertersFactory.CreateDefaultJsonConverters(lMD, lMI, lJV);
});
...
}
Can anyone point me to a suitable WCF Extension Point for hooking into the WCF Pipeline to extract credentials for UserNamePasswordValidator from the headers of an incoming HTTP REST Request?
Yes I know about all the funky stunts with Http Handlers etc. you can pull to somehow get Basic/Digest Auth working but since the client I'm working on will be strictly Javascript based I've opted for a simple model where the credentials are passed using two custom headers over an SSL pipe.
Update: I've managed to improve on this by using the approach described here. While this does not solves the problem described in my question, it gets rid of having to authenticate in a authorization policy since authentication is now handled by a custom AuthenticationManager, bypassing the UsernamePasswordValidator alltogether.
For the time being I've solved the problem by combining Authentication and Authorization in a custom Authorization Policy. I'd still rather find a way to hook into the normal UserNamePasswordValidator authentication scheme because an Authorization Policy is supposed to to Authorization not Authentication.
internal class RESTAuthorizationPolicy : IAuthorizationPolicy
{
public RESTAuthorizationPolicy()
{
Id = Guid.NewGuid().ToString();
Issuer = ClaimSet.System;
}
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
const String HttpRequestKey = "httpRequest";
const String UsernameHeaderKey = "x-ms-credentials-username";
const String PasswordHeaderKey = "x-ms-credentials-password";
const String IdentitiesKey = "Identities";
const String PrincipalKey = "Principal";
// Check if the properties of the context has the identities list
if (evaluationContext.Properties.Count > 0 ||
evaluationContext.Properties.ContainsKey(IdentitiesKey) ||
!OperationContext.Current.IncomingMessageProperties.ContainsKey(HttpRequestKey))
return false;
// get http request
var httpRequest = (HttpRequestMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpRequestKey];
// extract credentials
var username = httpRequest.Headers[UsernameHeaderKey];
var password = httpRequest.Headers[PasswordHeaderKey];
// verify credentials complete
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return false;
// Get or create the identities list
if (!evaluationContext.Properties.ContainsKey(IdentitiesKey))
evaluationContext.Properties[IdentitiesKey] = new List<IIdentity>();
var identities = (List<IIdentity>) evaluationContext.Properties[IdentitiesKey];
// lookup user
using (var con = ServiceLocator.Current.GetInstance<IDbConnection>())
{
using (var userDao = ServiceLocator.Current.GetDao<IUserDao>(con))
{
var user = userDao.GetUserByUsernamePassword(username, password);
...