I was refactoring my endpoints in a Minimal WebApi project and faced this situation:
namespace DACRL.API.Endpoints;
public class ApiUserManagementEndpoints : IEndpoints
{
private const string ContentType = "application/json";
private const string Tag = "ApiAccounts";
private const string BaseRoute = "apiAccounts";
public static void DefineEndpoints(IEndpointRouteBuilder app)
{
app.MapPost($"{BaseRoute}/login", LoginAsync)
.WithName("LoginApiUser")
.Accepts<ApiUserModel>(ContentType)
.Produces<string>()
.Produces<ApiAuthResponse>()
.Produces((int)HttpStatusCode.NotFound)
.Produces((int)HttpStatusCode.BadRequest)
.WithTags(Tag);
}
#region Helpers
private static async Task<IResult> LoginAsync(ApiUserModel model, string ipAddress, IApiUserService userService, IOptions<Jwt> jwtConfigRaw, CancellationToken ct)
{
...
return Results.Ok(result);
}
private static string GenerateToken(Jwt jwtConfig, string username)
{
// Token stuff
}
#endregion
public static void AddServices(IServiceCollection services, IConfiguration configuration) =>
services.AddTransient<IApiUserService, ApiUserService>();
}
Now, LoginAsync needs a parameter value for ipAddress. How do I pass HttpContext.Connection.RemoteIpAddress.ToString() from my app.MapPost ?
You can add the HttpContext as handler method parameter and use it:
private static async Task<IResult> LoginAsync(ApiUserModel model,
HttpContext ctx, // here
IApiUserService userService,
IOptions<Jwt> jwtConfigRaw,
CancellationToken ct)
{
var ip = ctx.Connection.RemoteIpAddress;
//...
}
See the special types subsection of Minimal APIs parameter minding docs:
Special types
The following types are bound without explicit attributes:
HttpContext: The context which holds all the information about the current HTTP request or response.
HttpRequest and HttpResponse: The HTTP request and HTTP response.
CancellationToken: The cancellation token associated with the current HTTP request.
ClaimsPrincipal: The user associated with the request, bound from HttpContext.User.
After a bit of head scratching, I realized exposing the ipAddress parameter made swagger think I had to supply that myself. Here is what I did:
private static async Task<IResult> LoginAsync(ApiUserModel model, IHttpContextAccessor httpContextAccessor, IApiUserService userService, IOptions<Jwt> jwtConfigRaw, CancellationToken ct)
{
...
var ipAddress = httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString();
...
}
So injecting the IHttpContextAccessor solved this for me.
Related
I'm trying to use SignalR to send an update through a websocket when a request is made to an endpoint. But, I'm struggling to inject the relevant Controller with a hub dependency. I've got a controller with the following GET function:
[HttpGet]
public IEnumerable<Zaal> GetZaal()
{
new BoekingUpdateHub().SendData("Test");
foreach(Stoel s in _context.Stoel)
{
Console.WriteLine(s.Id);
}
return _context.Zaal;
}
Problem is, when I try to use this endpoint, the hub is not instantiated properly, as
public class BoekingUpdateHub : Hub
{
public async Task SendData(string data)
{
await Clients.All.SendAsync("ReceiveData", data);
}
}
Gives me a null reference exception on the await Clients.All, as Clients was null
I've tried using dependency injection to resolve this problem, but it gives me:
System.InvalidOperationException: Unable to resolve service for type
'WDPR.Hubs.BoekingUpdateHub' while attempting to activate
'WDPR.Controllers.ZaalController'.
I've looked around and I can't find a solution, I'm completely lost on how to implement this properly.
I figured it out.
Rather than putting the Hub as an argument in the constructor, you add IHubContext as an argument, in this case the T is the Hub you want to actually use (in my case the hub is called MyHub)
private readonly DbTheaterLaakContext _context;
private readonly IHubContext<MyHub> _hubContext;
public ZaalController(DbTheaterLaakContext context, IHubContext<BoekingUpdateHub> hubContext)
{
_context = context;
_hubContext = hubContext;
}
Then, you can create the Hub inside one of the Endpoint functions:
[HttpGet]
public IEnumerable<Zaal> GetZaal()
{
new MyHub(_hubContext).SendData("Zaal GET request received");
}
Finally, make sure the IHubContext is an argument in the Hub's constructor:
public class MyHub: Hub
{
protected IHubContext<MyHub> _context;
public MyHub(IHubContext<MyHub> context)
{
this._context = context;
}
public async Task SendData(string data)
{
await _context.Clients.All.SendAsync("ReceiveData", data);
}
}
Now, notice we're using the IHubContext as a source for our Clients, this prevents the Clients from being null. Doing these steps allows you to send information through a SignalR websocket when an endpoint receives a request.
Configure the controller as follows
public class DemoController : ControllerBase
{
private readonly IHubContext<MyHub> _hubContext;
public DemoController(
IHubContext<MyHub> hubContext
)
{
_hubContext = hubContext;
}
[HttpGet]
public IEnumerable<Zaal> GetZaal()
{
_hubContext.Clients.All.SendData("Zaal GET request received");
}
}
I want to create a global action filter to audit all requests to my API. I want to use a globally registered ActionFilter to ensure all API Controller Actions are audited. I want to inject a scoped instance of AuditService, however, I get a System.ObjectDisposedException when calling _auditService.Create. What is the correct way to inject a scoped service into an ActionFilter so that it doesn't get disposed of before OnActionExecuted is called? The service is also disposed of before the OnActionExecuting event.
Startup code:
public void ConfigureServices(IServiceCollection services)
{
// ..
services.AddControllers(c => c.Filters.Add<AuditFilter>());
services.AddDbContext<MyDbContext>(c => c.UseSqlServer("ConnectionString"));
services.AddScoped<IAuditService, AuditService>();
// ..
}
Action Filter:
public class AuditFilter : IActionFilter
{
private readonly IAuditService _auditService;
public AuditFilter(IAuditService auditService)
{
_auditService = auditService;
}
public void OnActionExecuting(ActionExecutingContext context)
{
}
public async void OnActionExecuted(ActionExecutedContext context)
{
string username = ClaimsPrincipal.Current?.Identity?.Name;
string remoteAddr = $"{context.HttpContext.Connection.RemoteIpAddress}:{context.HttpContext.Connection.RemotePort}";
string queryString = context.HttpContext.Request.QueryString.HasValue ? context.HttpContext.Request.QueryString.Value : null;
using StreamReader reader = new StreamReader(context.HttpContext.Request.Body);
string body = await reader.ReadToEndAsync();
body = body.Length > 0 ? body : null;
// System.ObjectDisposedException: 'Cannot access a disposed context instance
await _auditService.Create(username, remoteAddr, context.HttpContext.Request.Method,
context.HttpContext.Request.Path, queryString, body, DateTime.Now);
}
}
Audit Service:
public class AuditService : IAuditService
{
private DRSContext Context { get; }
public AuditService(DRSContext context)
{
Context = context;
}
public async Task<bool> Create(string username, string remoteAddr, string httpMethod, string path, string query,
string body, DateTime timestamp)
{
await Context.AuditLogs.AddAsync(new AuditLog
{
Username = username,
RemoteAddress = remoteAddr,
HttpMethod = httpMethod,
Path = path,
Query = query,
Body = body,
Timestamp = timestamp
});
return await Context.SaveChangesAsync() > 0;
}
// ..
}
There are some things wrong in your code here:
You use async void on the OnActionExecuted. This should be avoided because of unpredictable results you may have. If you want to use async code, try implementing IAsyncActionFilter instead.
You implement Dispose for your implementation class of IAuditService in which you explicitly dispose the DbContext. You don't need to do that manually which can go out-of-sync with how the DI manages the DbContext (as scoped service) for you. Usually code inside Dispose is used to dispose unmanaged resources.
Finally I would suggest you to use IAsyncResourceFilter instead. It will be invoked by both controller actions & page handlers whereas the IAsyncActionFilter will be executed only by controller actions. You can examine the ResourceExecutingContext.ActionDescriptor to know about the action. It can be a ControllerActionDescriptor or a PageActionDescriptor.
Injecting state into your HttpRequest when using IHttpClientFactory is achievable by populating HttpRequestMessage.Properties see Using DelegatingHandler with custom data on HttpClient
Now if I have third party extensions on HttpClient (such as IdentityModel), how would I intercept these http requests using custom state?
public async Task DoEnquiry(IHttpClientFactory factory)
{
var id = Database.InsertEnquiry();
var httpClient = factory.CreateClient();
// GetDiscoveryDocumentAsync is a third party extension method on HttpClient
// I therefore cannot inject or alter the request message to be handled by the InterceptorHandler
var discovery = await httpClient.GetDiscoveryDocumentAsync();
// I want id to be associated with any request / response GetDiscoveryDocumentAsync is making
}
The only plausible solution I currently have is to override HttpClient.
public class InspectorHttpClient: HttpClient
{
private readonly HttpClient _internal;
private readonly int _id;
public const string Key = "insepctor";
public InspectorHttpClient(HttpClient #internal, int id)
{
_internal = #internal;
_id = id;
}
public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// attach data into HttpRequestMessage for the delegate handler
request.Properties.Add(Key, _id);
return _internal.SendAsync(request, cancellationToken);
}
// override all methods forwarding to _internal
}
A then I'm able to intercept these requests.
public async Task DoEnquiry(IHttpClientFactory factory)
{
var id = Database.InsertEnquiry();
var httpClient = new InspectorHttpClient(factory.CreateClient(), id);
var discovery = await httpClient.GetDiscoveryDocumentAsync();
}
Is that a plausible solution? Something tell me now not to override HttpClient. Quoting from https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-5.0
The HttpClient also acts as a base class for more specific HTTP clients. An example would be a FacebookHttpClient providing additional methods specific to a Facebook web service (a GetFriends method, for instance). Derived classes should not override the virtual methods on the class. Instead, use a constructor overload that accepts HttpMessageHandler to configure any pre- or post-request processing instead.
I almost included this in my other answer as an alternative solution, but I figured it was too long already. :)
The technique is practically the same, but instead of HttpRequestMessage.Properties, use AsyncLocal<T>. "Async local" is kind of like thread-local storage but for a specific asynchronous code block.
There are a few caveats to using AsyncLocal<T> that aren't particularly well-documented:
Use an immutable nullable type for T.
When setting the async local value, return an IDisposable that resets it.
If you don't do this, then only set the async local value from an async method.
You don't have to follow these guidelines, but they will make your life much easier.
With that out of the way, the solution is similar to the last one, except it just uses AsyncLocal<T> instead. Starting with the helper methods:
public static class AmbientContext
{
public static IDisposable SetId(int id)
{
var oldValue = AmbientId.Value;
AmbientId.Value = id;
// The following line uses Nito.Disposables; feel free to write your own.
return Disposable.Create(() => AmbientId.Value = oldValue);
}
public static int? TryGetId() => AmbientId.Value;
private static readonly AsyncLocal<int?> AmbientId = new AsyncLocal<int?>();
}
Then the calling code is updated to set the ambient value:
public async Task DoEnquiry(IHttpClientFactory factory)
{
var id = Database.InsertEnquiry();
using (AmbientContext.SetId(id))
{
var httpClient = factory.CreateClient();
var discovery = await httpClient.GetDiscoveryDocumentAsync();
}
}
Note that there is an explicit scope for that ambient id value. Any code within that scope can get the id by calling AmbientContext.TryGetId. Using this pattern ensures that this is true for any code: synchronous, async, ConfigureAwait(false), whatever - all code within that scope can get the id value. Including your custom handler:
public class HttpClientInterceptor : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var id = AmbientContext.TryGetId();
if (id == null)
throw new InvalidOperationException("The caller must set an ambient id.");
// associate the id with this request
Database.InsertEnquiry(id.Value, request);
return await base.SendAsync(request, cancellationToken);
}
}
Followup readings:
Blog post on "async local" - written before AsyncLocal<T> existed, but has details on how it works. This answers the questions "why should T be immutable?" and "if I don't use IDisposable, why do I have to set the value from an async method?".
I am trying to add one more parameter to my constructors in my Asp.Net Core MVC application, but facing some difficulties to do so. Here is what my implementation looks like.
Login action:
[HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
public IActionResult Login(LoginViewModel loginModel, string returnUrl = null)
{
returnUrl = string.IsNullOrWhiteSpace(returnUrl) ? ApiConstants.Dashboard : returnUrl;
ViewData["ReturnUrl"] = returnUrl;
if (!ModelState.IsValid) return View(loginModel);
var token = Service.Login(loginModel);
if (string.IsNullOrWhiteSpace(token)) return View(loginModel);
TempData["token"] = token;
AddCookie(token);
return RedirectToAction("Index", "Dashboard");
}
private void AddCookie(string token)
{
HttpContext.Response.Cookies.Append("token", token,new CookieOptions()
{
Expires = DateTimeOffset.Now.AddDays(-1)
});
}
Controller:
private readonly INozzleService _nozzleService;
public NozzleController(INozzleService nozzleService)
{
var token = HttpContext.Request.Cookies["token"];
_nozzleService = nozzleService;
}
Nozzle Service:
private static INozzleAdapter Adapter { get; set; }
public NozzleService(INozzleAdapter adapter)
{
Adapter = adapter;
}
Nozzle Adapter:
private readonly string _token;
public NozzleAdapter(string token)
{
_token = token;
}
Once I get the token in the adapter, I will be adding the token to the HttpClient header.
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token);
ConfigureServices in Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
services.AddDistributedMemoryCache();
services.AddSession();
services.AddTransient<IAccountService, AccountService>();
services.AddTransient<IAccountAdapter, AccountAdapter>();
services.AddTransient<INozzleService, NozzleService>();
services.AddTransient<INozzleAdapter, NozzleAdapter>();
services.AddMvc();
}
Can you please let me know what could be the best way to achieve this in Asp.Net core 2.0 MVC application? I have read a post saying that using multiple constructors is not a good idea in Asp.Net Core MVC application, so I don't want to use multiple constructors.
At the same time, I want to make sure all of my classes are unit testable with DI. What should be the best approach here?
Please let me know if anyone needs more information.
Update:
As per Shyju's solution, I was able to implement the cookie, however, I am still in a need to pass two parameters to one of my controllers.
private readonly IAccountService _service;
private readonly ITokenProvider _tokenProvider;
public AccountController(IAccountService service, ITokenProvider tokenProvider)
{
_service = service;
_tokenProvider = tokenProvider;
}
So that I can, use the method AddToken as below.
_tokenProvider.AddToken(token);
You may consider abstracting out the logic to get the token to a separate class and inject that as needed.
public interface ITokenProvider
{
/// <summary>
/// Gets the token
/// </summary>
/// <returns></returns>
string GetToken();
}
Now create an implementation of this, which will be reading the token from the cookie. Here is a simple implementation, which reads the token from the cookies collection
public class CookieTokenProvider : ITokenProvider
{
private readonly IHttpContextAccessor httpContextAccessor;
public CookieTokenProvider(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string GetToken()
{
if (httpContextAccessor.HttpContext.Request.Cookies
.TryGetValue("token", out string tokenValue))
{
return tokenValue;
}
return null;
}
}
Now, you can inject the ITokenProvider implementation to anyplace you want and call the GetToken method to get the token value. For example, you may inject this to the NozzleAdapter class constructor.
private readonly ITokenProvider tokenProvider;
public NozzleAdapter(ITokenProvider tokenProvider)
{
tokenProvider=tokenProvider;
}
public string SomeOtherMethod()
{
var token = this.tokenProvider.GetToken();
//Do null check and use it
}
Make sure you register this in the ConfigureServices method in Startup class
services.AddTransient<ITokenProvider, CookieTokenProvider>();
Regarding your comment about getting the token and persisting it, it is up to you where you want to do it. You can do that in the CookieTokenProvider implementation. Read the value and store it somewhere ( a local db, in memory cache etc) and get it from there if exists (the next time)
Now, for your unit tests you can create a MockTokenProvider which does not use HttpContext, but simply return a mock value for your testing,
public class MockTokenProvider : ITokenProvider
{
public string GetToken() => "FakeToken";
}
Background:
I want to authenticate a POST request to my web API using an implementation of IAuthenticationFilter injected using Ninject. To authenticate a request I need access to request body.
Problem:
ActionContext.ActionArguments, which I usually use to access request payload, is empty when I try to access it inside the filter.
Question:
How to access POST request payload inside an IAuthenticationFilter implementation?
Why ActionContext.ActionArguments is empty inside an IAuthenticationFilter implementation, but has values if my filter implements ActionFilterAttribute?
Code:
Filter implementation:
public class AuthenticateFilter : IAuthenticationFilter
{
private const string AuthenticationHeader = "X-Auth-Token";
private const string UserHeader = "X-Auth-User";
private readonly ILog log;
public AuthenticateFilter(ILog log)
{
this.log = log;
}
public Task AuthenticateAsync(HttpAuthenticationContext context,
CancellationToken cancellationToken)
{
// context.ActionContext.ActionArguments is empty
if (!IsAuthenticated(context))
{
context.ErrorResult =
new StatusCodeResult(HttpStatusCode.Unauthorized,
context.Request);
}
return Task.FromResult(0);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context,
CancellationToken cancellationToken)
{
context.Result =
new StatusCodeResult(HttpStatusCode.Unauthorized,
context.Request);
return Task.FromResult(0);
}
private bool IsAuthenticated(HttpAuthenticationContext context)
{
// Authentication code here
// context.ActionContext.ActionArguments is empty
}
}
The filter is injected using Ninject when controller method has a attribute.
kernel.BindHttpFilter<AuthenticateFilter>(FilterScope.Action)
.WhenActionMethodHas<AuthenticateAttribute>();
AuthenticateAttribute is an empty ActionFilterAttribute.
public class AuthenticateAttribute : ActionFilterAttribute
{
}
Thank you!
This is expected behavior. Authentication and Authorization filters run before ModelBinding/Formatter deserialization stage, where as Action filters run after this stage.
I struggled a bit myself with the same situation, in case it helps anyone, you need to use Reflection and System.Web.Helpers's Json.Decode:
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
var content = request.Content.ReadAsAsync(typeof(Object)).Result.ToString();
var methodInfo = ((ReflectedHttpActionDescriptor)request.Properties["MS_HttpActionDescriptor"]).MethodInfo; // get the method descriptor
if (methodInfo.GetParameters().Any()) //this will get the parameter types
{
var parameterType = methodInfo.GetParameters().First().ParameterType; //you iterate can through the parameters if you need
var casted = Json.Decode(content, parameterType); //convert the json content into the previous type (your parameter)
//do something with your populated object :)
}
return Task.FromResult(context.Request);
}