HttpContext is null asp net core 6 - c#

I am trying to use HttpContextAccessor on my custom class(BLL class) and while i succesfully initializes the ContextAccessor meantime HttpContext itself is null.
Code in program.cs
builder.Services.AddSingleton<IUserPermissionConfig, UserPermisionConfig>();
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
var setUserPermission = app.Services.GetRequiredService<IUserPermissionConfig>();
setUserPermission.SetUserPermissionsSession(UserConstants.SYSTEM_ID);
UserPermisionConfig component code
private readonly IHttpContextAccessor _httpContextAccessor;
public UserPermisionConfig( IHttpContextAccessor httpContextAccessor)
{
_permisionServiceClient = new PermissionServiceClient();
_httpContextAccessor = httpContextAccessor ?? throw new Exception("Http context accessor is null.");
}
public async Task SetUserPermissionsSession(int systemId)
{
string userName = _httpContextAccessor.HttpContext.
User.Identity.Name; //here httpcontext is alway null
UserPermissionsModel userPermissionModel = await GetUserPermission(systemId, userName);
_httpContextAccessor.HttpContext.Session.Set(UserConstants.SESSION_USER_PERMISSIOS, ByteArrayExtensions.ToByteArray(userPermissionModel));
}
Any help ?

I think you could check this document
just as mentioned in the document:
HttpContext isn't thread-safe. Reading or writing properties of the HttpContext outside of processing a request can result in a NullReferenceException.
In Asp.net if you call httpcontext directly in Application_Start() method in global.asax.cs,you would get the similar error
If your codes was in your HttpModule in Asp.net ,check this document to learn how to migrate module codes to middleware
And I tried as below:
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly IUserPermisionConfig _config;
public MyMiddleware(RequestDelegate next, IUserPermisionConfig config)
{
_next = next;
_config = config;
}
public async Task InvokeAsync(HttpContext context)
{
_config.SetUserPermissionsSession(5);
await _next(context);
}
}
public interface IUserPermisionConfig
{
Task SetUserPermissionsSession(int systemId);
}
public class UserPermisionConfig : IUserPermisionConfig
{
private readonly IHttpContextAccessor _accessor;
public UserPermisionConfig(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public Task SetUserPermissionsSession(int systemId)
{
var httpcontext = _accessor.HttpContext;
var user = httpcontext.User.Identity?.Name;
httpcontext.Session.SetString("Somekey", systemId.ToString());
return Task.CompletedTask;
}
}
Regist the services:
builder.Services.AddHttpContextAccessor();
builder.Services.AddTransient<IUserPermisionConfig, UserPermisionConfig>();
// The two following services are required to use session in asp.net core
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();
call the middleware in the pipeline :
.....
app.UseSession();
app.UseMiddleware<MyMiddleware>();
.....
it works well in my case:

Ok, it looks like you are trying to access the HTTP context directly from Program.cs. This is not how it works.
The HTTP context is only avialable during an incoming HTTP request.
Your IUserPermissionConfig service must be called from the ASP.net pipeline somewhere (controller, filter, middleware).

Related

Scoped service creating two different instances for a request

I'm fairly new to Asp.Net core 6 and am working on an GraphQL API that receives a bearer token in the request. The API then invokes another Web API and passes the same bearer token in the header. Below is what my code looks like-
Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<HeaderHandler>();
builder.Services.AddScoped<TokenContainer>();
//builder.Services.AddScoped<IFooGateway, FooGateway>();
builder.Services.AddHttpClient<IFooGateway, FooGateway>((c) =>
{
c.BaseAddress = new Uri(builder.Configuration["FooApiUrl"]);
})
.AddHttpMessageHandler<HeaderHandler>();
builder.Services.AddTransient<GraphApiService>();
var app = builder.Build();
app.UseMiddleware<HeaderMiddleware>();
app.MapGraphQL();
app.Run();
HeaderMiddleware.cs
public class HeaderMiddleware
{
//TokenContainer _tokenContainer;
private readonly RequestDelegate _requestDelegate;
public HeaderMiddleware()
{
}
public HeaderMiddleware(RequestDelegate requestDelegate)
{
_requestDelegate = requestDelegate;
//_tokenContainer = tokenContainer;
}
public async Task InvokeAsync(HttpContext context, TokenContainer tokenContainer)
{
var header = context.Request.Headers.Authorization;
tokenContainer.SetToken(header);
await _requestDelegate(context);
}
TokenContainer.cs:
public class TokenContainer
{
public string BearerToken { get; private set; }
public void SetToken(string token) => BearerToken = token;
}
HeaderHandler.cs:
public class HeaderHandler : DelegatingHandler
{
TokenContainer _tokenContainer;
public HeaderHandler()
{
}
public HeaderHandler(TokenContainer tokenContainer)
{
_tokenContainer = tokenContainer;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// for every request sent via the http client, intercept & add the bearer token header.
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", _tokenContainer.BearerToken);
// continue with request pipeline
return await base.SendAsync(request, cancellationToken);
}
}
FooGateway.cs:
public class FooGateway : IFooGateway
{
private readonly HttpClient _httpClient;
private readonly IConfiguration _configuration;
private readonly string _context = String.Empty;
public FooGateway(HttpClient httpClient, IConfiguration configuration)
{
_configuration = configuration;
_context = configuration["FooContext"];
_httpClient = httpClient;
}
public void DoSomething()
{
_httpClient.PostAsync("/blabla");
}
}
So, the idea was that the bearer token for every incoming request will be stored in a class called TokenContainer and the HttpHandler will add it to all the outgoing requests.
However, what is happening is that the token is stored in the TokenContainer but the HeaderHandler gets a different instance of TokenContainer in its constructor with its BearerToken property set to null.
Can someone please explain why the same instance of TokenContainer from the middleware is not being passed into the HeaderHandler?
The issue you are seeing is because the lifetime of the HttpMessageHandler is not the same as the lifetime of the request: usually, the same handler will be reused across many requests and be controlled separately on expiration timers and such.
You should not expect that a service injected into your message handler will be the same object that is injected outside it when it is registered as scoped.
https://andrewlock.net/understanding-scopes-with-ihttpclientfactory-message-handlers/#scope-duration-in-ihttpclientfactory
As the article suggests, to use the same scoped instance as you do outside the handler, you have to rely on IHttpContextAccessor to access the current HttpContext and fetch the service from there. So your handler implementation would look something like this:
public class HeaderHandler : DelegatingHandler
{
IHttpContextAccessor _httpContextAccessor;
public HeaderHandler()
{
}
public HeaderHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var tokenContainer = _httpContextAccessor
.HttpContext
.RequestServices
.GetRequiredService<TokenContainer>();
// for every request sent via the http client, intercept & add the bearer token header.
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", tokenContainer.BearerToken);
// continue with request pipeline
return await base.SendAsync(request, cancellationToken);
}
}
This should make sure that the TokenContainer instance is the same across your current request and http calls.
Remember that to add this functionality you need to add the accessor like this:
services.AddHttpContextAccessor();

How to inject an async service into a singleton Middleware

I had the following Middleware, the main idea is that check for every request (throught View or Ajax) if the user is not blocked in the database.
internal class UserMiddleware
{
private readonly RequestDelegate next;
private readonly IUsuariosRepository userRepository;
public UserMiddleware(RequestDelegate next, IUsuariosRepository userRepository)
{
this.next = next ?? throw new ArgumentNullException(nameof(next));
this.userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
}
public async Task Invoke(HttpContext httpContext)
{
string guid = httpContext.User.Claims?.FirstOrDefault(x => x.Type.Equals("theClaimGuid"))?.Value;
bool isBlocked = await userRepository.CheckUserBlockByGUID(guid);
if (isBlocked)
{
await httpContext.Response.WriteAsync("You are blocked.");
return;
}
await next(httpContext);
}
}
The call to the database with the context is made in the CheckUserBlockByGUID method:
public class UsuariosRepository : IUsuariosRepository
{
private readonly nContext _context;
private readonly IWebHostEnvironment _hostingEnvironment;
public UsuariosRepository(nContext context, IWebHostEnvironment hostingEnvironment)
{
_context = context;
_hostingEnvironment = hostingEnvironment;
}
public async Task<bool> CheckUserBlockByGUID(string guid) {
bool isOK = await _context.Usuarios.Where(u => u.Guid == guid && u.Deleted == 0 && u.blocked == 0).AnyAsync();
return !isOK;
}
...
and the Middleware is called inside Startup.cs / Config as:
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<UserMiddleware>();
All my calls to the database are used with async / await but with this one, I get the following error:
A second operation started on this context before a previous operation
completed. This is usually caused by different threads using the same
instance of DbContext. For more information on how to avoid threading
issues with DbContext, see
https://go.microsoft.com/fwlink/?linkid=2097913.
I understand that I'm calling without waiting for the close operation of the other one. But I can't find that call. I don't know even if the error is this one or the problem is another one that I'm not seeing right now.
Any help or tip will be preciated.
Also please ask me in order to clarify this question or update it.
Thanks in advance and sorry for my english.

How do I access browser local storage from .cs files in blazor?

First of all, I can access localstorage data in .razor pages. I mean I cannot access localstorage data in .cs files. How can I access?
_Imports.razor:
#using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
#inject ProtectedLocalStorage protectedLocalStorage
anyone .razor file:
await protectedLocalStorage.SetAsync(key, JsonSerializer.Serialize(instance));
Above code works for me but I want to call protectedLocalStorage from .cs files additionally.
P.S sorry for grammar mistakes
Edit:
I am using IHttpClientFactory in startup.cs and I want to add token as a header before api request.
startup.cs
services.AddHttpClient("api", hc =>
{
hc.BaseAddress = new Uri("http://localhost:5000/");
string tokenVal = tokenService.GetToken();
if (!String.IsNullOrEmpty(tokenVal))
hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenVal);
});
I want to take token value from local storage from this .cs file
public class TokenService : ITokenService
{
private IHttpContextAccessor httpContextAccessor;
public TokenService(IHttpContextAccessor HttpContextAccessor, IProtected) => httpContextAccessor = HttpContextAccessor;
public string GetToken()
{
return "";
}
}
How do I access browser local storage from .cs files in blazor?
ASP.NET supports injection in most constructors. Expanding OP's example:
// Startup.cs -> ConfigureServices(IServiceCollection services)
// Probably not necessary in your case but, to be thorough:
services.AddScoped<ProtectedLocalStorage>();
// SomeFile.cs
public class TokenService : ITokenService
{
// Ignore for the moment that these are being used in the same context
private IHttpContextAccessor httpContextAccessor;
private readonly ProtectedBrowserStorage _storage;
// Injection can happen here in ASP.NET
public TokenService(
IHttpContextAccessor HttpContextAccessor,
ProtectedBrowserStorage storage)
{
httpContextAccessor = HttpContextAccessor;
// injection works but the PBS service might not: see below
_storage = storage;
}
//..
}
However, I don't recommend this for ProtectedBrowserStorage, since it uses IJSRuntime under the hood. If you try to use this in a non-javascript aware context (e.g. during Startup.Configure where the client is still awaiting a response and there is no way to execute javascript), you will run into errors. In Blazor, ProtectedBrowserStorage should only be called - directly or indirectly - from a Blazor component; to keep it simple, wrap it in a class you only use with components, or keep it in the component itself.
Thus, if you are trying to do this:
I am using IHttpClientFactory in startup.cs and I want to add token as a header before api request.
ProtectedBrowserStorage is not the tool for you. Use cookies or another web server technology.
How I solved in the end:
I have created custom authentication class inherited AuthenticationStateProvider. Then I designed all of check processes to be solved on ProtectedLocalStorage.
AuthenticationService
public class AuthenticationService : AuthenticationStateProvider
{
private const string USER_SESSION_OBJECT_KEY = "user_session_obj";
private const string ACCESS_TOKEN = "accesstoken";
private const string USER_PERMISSIONS = "userpermissions";
private readonly ProtectedLocalStorage _protectedLocalStorage;
private readonly IHttpContextAccessor _httpContextAccessor;
public AuthenticationService(ProtectedLocalStorage protectedSessionStore, IHttpContextAccessor httpContextAccessor)
{
_protectedLocalStorage = protectedSessionStore;
_httpContextAccessor = httpContextAccessor;
}
public string IpAddress => _httpContextAccessor?.HttpContext?.Connection?.RemoteIpAddress?.ToString() ?? string.Empty;
private User User { get; set; }
private List<UserPermission> UserPermissionList { get; set; }
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
try
{
User userSession = await GetUserSession();
List<UserPermission> userPermissions = await GetUserPermission();
if (userSession != null)
return await GenerateAuthenticationState(userSession, userPermissions);
return await GenerateEmptyAuthenticationState();
}
catch
{
await LogoutAsync();
return null;
}
}
public async Task LoginAsync(User user,List<UserPermission> userPermissions)
{
await SetUserSession(user);
await SetUserPermissionSession(userPermissions);
NotifyAuthenticationStateChanged(GenerateAuthenticationState(user, userPermissions));
}
public async Task LogoutAsync()
{
//await SetUserSession(null);
RefreshUserSession(null);
await _protectedLocalStorage.DeleteAsync(USER_SESSION_OBJECT_KEY);
await _protectedLocalStorage.DeleteAsync(ACCESS_TOKEN);
await _protectedLocalStorage.DeleteAsync(USER_PERMISSIONS);
NotifyAuthenticationStateChanged(GenerateEmptyAuthenticationState());
}
public async Task<User> GetUserSession()
{
if (User != null)
return User;
//TODO burda localUserJson get yaparken hata alıyor. try catch işi çözmezse buraya tekrardan bakılacak.
try
{
var localUserJson = await _protectedLocalStorage.GetAsync<string>(USER_SESSION_OBJECT_KEY);
if (string.IsNullOrEmpty(localUserJson.Value))
return null;
return RefreshUserSession(JsonConvert.DeserializeObject<User>(localUserJson.Value));
}
catch
{
await LogoutAsync();
return null;
}
}
public async Task<List<UserPermission>> GetUserPermission()
{
if (UserPermissionList != null)
return UserPermissionList;
try
{
var localUserPermissionJson = await _protectedLocalStorage.GetAsync<string>(USER_PERMISSIONS);
if (string.IsNullOrEmpty(localUserPermissionJson.Value))
return null;
return RefreshUserPermissionSession(JsonConvert.DeserializeObject<List<UserPermission>>(localUserPermissionJson.Value));
}
catch
{
await LogoutAsync();
return null;
}
}
private async Task SetUserSession(User user)
{
RefreshUserSession(user);
await _protectedLocalStorage.SetAsync(USER_SESSION_OBJECT_KEY, JsonConvert.SerializeObject(user));
}
private async Task SetUserPermissionSession(List<UserPermission> userPermissions)
{
RefreshUserPermissionSession(userPermissions);
await _protectedLocalStorage.SetAsync(USER_PERMISSIONS, JsonConvert.SerializeObject(userPermissions));
}
private User RefreshUserSession(User user) => User = user;
private List<UserPermission> RefreshUserPermissionSession(List<UserPermission> userPermission) => UserPermissionList = userPermission;
private Task<AuthenticationState> GenerateAuthenticationState(User user, List<UserPermission> userPermission)
{
ClaimsIdentity claimsIdentity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, user.Id.ToString()),
new Claim(ClaimTypes.Role, userPermission.ToString()),
}, "auth");
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
return Task.FromResult(new AuthenticationState(claimsPrincipal));
}
private Task<AuthenticationState> GenerateEmptyAuthenticationState() => Task.FromResult(new AuthenticationState(new ClaimsPrincipal()));
}
Then I registered this class in startup.cs
Startup
services.AddScoped<AuthenticationStateProvider, AuthenticationService>();
During changing page, authentication system interrupt showing page to check if it is authenticate or not thanks to below code.
_Imports
#attribute [Authorize]
*You can set localstorage at login page. You can create your way to check thanks to this way.

How to call a controller method from middleware and only allow this method to be called from this middleware?

I have a Controller, which has 2 functions and a middleware.
My middleware
try
{
var request = httpContext.Request;
if (/* some logic here */)
{
await _next(httpContext);
// Some logic here
var someReturnValue = someFunction();
// Need to call the Controller Method here
// call SaveStaging(someReturnValue);
}
else
{
await _next(httpContext);
}
}
catch (Exception ex)
{
await _next(httpContext);
}
In my Controller
[Authorize(AuthenticationSchemes = "APIKeyAuthentication")]
[LogAction]
[ApiController]
[Route("api/[controller]/[action]")]
public class MyController : ControllerBase
{
private IHttpContextAccessor _accessor;
public MyController(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
[HttpPost]
public async Task<ActionResult<ServiceResult>> PostData([FromBody] DataObj request)
{
ServiceResult result = new ServiceResult();
result.isSuccessful = true;
return Ok(result);
}
public async Task SaveStaging(int logNumber)
{
}
}
So, initially, user will call the PostData endpoint. In this method, it will return that it accepts the data posted by user.
Then in the middleware, I will need to do some things, and finally, I would like to call the SaveStaging method in the Controller. I want this SaveStaging method to be only accessible for the middleware, and should not be called directly by the user.
So my question:
How to call this method from the middleware?
How to limit SaveStaging to be accessible only to the middleware?
You should consider wrapping SaveStaging into a service because it appears to be a cross-cutting concern. Beyond that, it's likely wise to implement a layered architecture as a matter of refactoring.
Since this is .NET Core, I would recommend fully embracing dependency injection and injecting the service into your middleware.
public interface IStagingService
{
Task Save(int logNumber);
}
public class MyController : ControllerBase
{
private readonly IStagingService _service;
private readonly IHttpContextAccessor _accessor;
public MyController(IStagingService service, IHttpContextAccessor accessor)
{
_service = service ?? throw new ArgumentNullException(nameof(service));
_accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));;
}
//....
}
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly IStagingService _service;
public RequestCultureMiddleware(RequestDelegate next, IStagingService service)
{
_next = next;
_service = service ?? throw new ArgumentNullException(nameof(service));
}
public async Task InvokeAsync(HttpContext context)
{
// ...
}
In the beginning, your service's will likely start out small and simple. In the event the app grows, the complexity of this layer will likely increase, spawning the need for some facade services (i.e. services composed of services) and maybe some providers for thing like data access.

How to access Route Data / Value Provider data in a service in ASP.NET Core?

I am attempting to write a Policy-based Authorization Handler. The business logic of the handler needs to use the record id of the current request that is passed in through the default route.
[Authorize(Roles = "TaskAdmin", Policy = "RecordOwner")]
public IActionResult Index(int id) // <-- Need this id
{
// <snip>
return View();
}
Policy
Here is the class where I need to access the id route value.
public class RecordOwnerHandler : AuthorizationHandler<RecordOwnerRequirement>
{
private readonly ApplicationDbContext dbContext;
public RecordOwnerHandler(ApplicationDbContext dbContext)
{
this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RecordOwnerRequirement requirement)
{
if (IsUserAuthorized(context))
{
context.Succeed(requirement);
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
private bool IsUserAuthorized(AuthorizationHandlerContext context)
{
//****************************************
// Need the id here...
//****************************************
// Return the result
return true;
}
}
Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// *** Add policy for record owner ***
services.AddAuthorization(options =>
{
options.AddPolicy("RecordOwner", policy =>
policy.Requirements.Add(new RecordOwnerRequirement()));
});
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
// *** Register record owner handler with the DI container ***
services.AddTransient<IAuthorizationHandler, RecordOwnerHandler>();
services.AddMvc();
}
What I Tried
I tried using the IHttpContextAccessor as a constructor parameter of RecordOwnerHandler, but IHttpContextAccessor.HttpContext doesn't seem to contain the RouteData of the request.
I did several Google searches to see if there was any info about how to do this and came up blank.
Then I dug through the source code for both Routing and Model Binding, but can't seem to find an abstraction that is meant for injecting route values into services.
I realize I could try to parse this info out of the URL, but I am hoping for a cleaner way to get the value.
So, how can I access route values and/or value provider data inside of a service in ASP.NET Core 2.0?
Route values can be accessed by using the ActionContextAccessor class.
DI Registration
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
Usage
public class RecordOwnerHandler : AuthorizationHandler<RecordOwnerRequirement>
{
private readonly ApplicationDbContext dbContext;
private readonly IActionContextAccessor actionContextAccessor;
public RecordOwnerHandler(ApplicationDbContext dbContext, IActionContextAccessor actionContextAccessor)
{
this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
this.actionContextAccessor = actionContextAccessor ?? throw new ArgumentNullException(nameof(actionContextAccessor));
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RecordOwnerRequirement requirement)
{
if (IsUserAuthorized(context))
{
context.Succeed(requirement);
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
private bool IsUserAuthorized(AuthorizationHandlerContext context)
{
// Now the id route value can be accessed directly...
var id = this.actionContextAccessor.ActionContext.RouteData.Values["id"];
// Use the dbContext to compare the id against the database...
// Return the result
return true;
}
}
NOTE: I would still like to find out a way to access the value providers to do this, so it wouldn't matter if the parameter is passed through route values, query string, form values, etc.
For future reference, starting .NET Core 5.0, the HttpContext is now passed instead, so you can do:
if (context.Resource is HttpContext httpContext)
{
var value = httpContext.GetRouteValue("key");
}
See this for reference [AspNetCore] Authorization resource in endpoint routing will now be HttpContext
Don't know about .NET Core 2.0, but in 3.0 you can access route values with the help of IHttpContextAccessor, with a service implementation that will be provided automatically for you by the framework (so no need to register anything).
public class RecordOwnerHandler : AuthorizationHandler<RecordOwnerRequirement>
{
private readonly ApplicationDbContext dbContext;
private readonly IHttpContextAccessor httpContextAccessor;
public RecordOwnerHandler(ApplicationDbContext dbContext, IHttpContextAccessor httpContextAccessor)
{
this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RecordOwnerRequirement requirement)
{
if (IsUserAuthorized(context))
{
context.Succeed(requirement);
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
private bool IsUserAuthorized(AuthorizationHandlerContext context)
{
if (!_httpContextAccessor.HttpContext.Request.RouteValues.TryGetValue("id", out var id))
{
return false;
}
// TODO Whatever logic you need to perform with the id
return true;
}
}
I have the same use case and tried accessing the route value through the endpoint metadata in the AuthorizationHandler as recommended in the Access MVC request context in handlers section from the ASP.NET Core 5 Security docs:
if (!(context.Resource is Endpoint endpoint)) throw new InvalidOperationException("endpoint routing required");
var id = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>()?.RouteValues["id"];
But the context.Resource was of type DefaultHttpContext, so instead I had to do this:
public class RecordOwnerHandler : AuthorizationHandler<RecordOwnerRequirement>
{
private readonly ApplicationDbContext dbContext;
public RecordOwnerHandler(ApplicationDbContext dbContext)
{
this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RecordOwnerRequirement requirement) {
if (IsUserAuthorized(context))
{
context.Succeed(requirement);
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
private bool IsUserAuthorized(AuthorizationHandlerContext context)
{
//****************************************
// Need the id here...
//****************************************
if (!(context.Resource is DefaultHttpContext httpContext)) throw new InvalidOperationException("DefaultHttpContext expected");
var id = httpContext.Request.RouteValues["id"];
// Return the result
return true;
}
}

Categories

Resources