Access IUrlHelper inside DelegatingHandler - asp.net-core

Following a migration to ASP.Net core, the following handler does not work. I cannot see how to get access to IUrlHelper from the HttpRequestMessage as was previously possible, and can't find a package with relevant extension methods.
The handler is added using config.MessageHandlers.Add(new LinkDecoratorHandler());
Can anyone help?
public class LinkDecoratorHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var response = task.Result;
if (!(response.Content is ObjectContent))
{
return response;
}
var entity = (response.Content as ObjectContent).Value as ILinkedEntity;
var enumeration = (response.Content as ObjectContent).Value as IEnumerable<ILinkedEntity>;
if (entity != null || enumeration != null)
{
//no longer available
var helper = request.GetUrlHelper();
//blah
}
return response;
});
}
}
Thanks in advance

If your LinkDecoratorHandler is instantiated via dependency injection then you could inject an instance of IActionContextAccessor to get the current ActionContext. From there, you can create your own UrlHelper instance.

Related

Blazor WASM controller: read request body causes the IIS process to crash

So I am trying to simply read the body (with string content) in a Blazor WASM ApiController. My code on the server-side:
[AllowAnonymous]
[ApiController]
[Route("[controller]")]
public class SmartMeterDataController : ControllerBase
{
[HttpPost("UploadData")]
public async void UploadData()
{
string body = null;
if (Request.Body.CanRead && (Request.Method == HttpMethods.Post || Request.Method == HttpMethods.Put))
{
Request.EnableBuffering();
Request.Body.Position = 0;
body = await new StreamReader(Request.Body).ReadToEndAsync();
}
}
}
My app builder in Program.cs is pretty much out of the box:
//enable REST API controllers
var mvcBuillder = builder.Services.AddMvcCore(setupAction: options => options.EnableEndpointRouting = false).ConfigureApiBehaviorOptions(options => //activate MVC and configure error handling
{
options.InvalidModelStateResponseFactory = context => //error 400 (bad request)
{
JsonApiErrorHandler.HandleError400BadRequest(context);
return new Microsoft.AspNetCore.Mvc.BadRequestObjectResult(context.ModelState);
};
});
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
...
app.UseRouting();
app.UseMvcWithDefaultRoute();
app.MapRazorPages();
app.MapControllers();
The request body looks like this:
{"api_key":"K12345667565656", "field1":"1.10", "field2":"0.76",
"field3":"0.65", "field4":"455", "field5":"0", "field6":"1324",
"field7":"433761", "field8":"11815" }
Yes, this is JSON. No, I don't want to parse it with [FromBody] or similar.
POSTing to this endpoint causes the following exception (as seen in the Windows event viewer thingy):
Application: w3wp.exe
CoreCLR Version: 6.0.1222.56807
.NET Version: 6.0.12
Description: The process was terminated due to an unhandled exception.
Exception Info: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'HttpRequestStream'.
at Microsoft.AspNetCore.Server.IIS.Core.HttpRequestStream.ValidateState(CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.IIS.Core.HttpRequestStream.ReadAsync(Memory`1 destination, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.IIS.Core.WrappingStream.ReadAsync(Memory`1 destination, CancellationToken cancellationToken)
at Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)
at System.IO.StreamReader.ReadBufferAsync(CancellationToken cancellationToken)
at System.IO.StreamReader.ReadToEndAsyncInternal()
After that, a second error is always logged. It states something like it is described here.
Note that it's usually not the first, but the second or third POST that causes this. After this, the error keeps happening with every POST and after a short while the application stops working and the Windows Server 2019 need to be rebooted.
According to the internet, the code should work. Anyone have a guess why it doesn't?
I use this HttpContext extension method to read the request body and cache it in the context in case needed later in the pipeline. It works for me.
Notice the condition around EnableBuffering. Perhaps adding that condition to your code will help.
public static async Task<string> GetRequestBodyAsStringAsync(
this HttpContext httpContext)
{
if (httpContext.Items.TryGetValue("BodyAsString", out object? value))
return (string)value!;
if (!httpContext.Request.Body.CanSeek)
{
// We only do this if the stream isn't *already* rewindable,
// as EnableBuffering will create a new stream instance
// each time it's called
httpContext.Request.EnableBuffering();
}
httpContext.Request.Body.Position = 0;
StreamReader reader = new(httpContext.Request.Body, Encoding.UTF8);
string bodyAsString = await reader.ReadToEndAsync().ConfigureAwait(false);
httpContext.Request.Body.Position = 0;
httpContext.Items["BodyAsString"] = bodyAsString;
return bodyAsString;
}
EDIT ...
Possibly, your issue could also be related to fact your controller method is returning a void instead of Task?
Finally, I found the original article I used for my extension method. Interestingly, if you that extension method for the FIRST time after model-binding then it won't work (in my project I do call it from middleware).
https://markb.uk/asp-net-core-read-raw-request-body-as-string.html
Adding:
public class EnableRequestBodyBufferingMiddleware
{
private readonly RequestDelegate _next;
public EnableRequestBodyBufferingMiddleware(RequestDelegate next) =>
_next = next;
public async Task InvokeAsync(HttpContext context)
{
context.Request.EnableBuffering();
await _next(context);
}
}
and
app.UseMiddleware<EnableRequestBodyBufferingMiddleware>();
may therefore also help.

Asp.Net Core Cannot access a disposed context instance

I'm trying to implement SignalR in order to consume data from a angular frontend application.
I've checked all the results on google that I can find, but I still can't solve my issue.
The error I'm getting is:
Cannot access a disposed context instance. A common cause of this
error is disposing a context instance that was resolved from
dependency injection and then later trying to use the same context
instance elsewhere in your application. This may occur if you are
calling 'Dispose' on the context instance, or wrapping it in a using
statement. If you are using dependency injection, you should let the
dependency injection container take care of disposing context
instances. Object name: 'AdminContext'
Controller
[Route("api/[controller]")]
[ApiController]
public class ChartController : ControllerBase
{
private IHubContext<ChartHub> _hub;
private readonly ILiveMonitoringService _service;
public ChartController(IHubContext<ChartHub> hub, ILiveMonitoringService service)
{
_hub = hub;
_service = service;
}
public IActionResult Get()
{
var timerManager = new TimerManager(async () => await _hub.Clients.All.SendAsync("transferchartdata", await _service.GetAllAsync()));
return Ok(new { Message = "Request Completed" });
}
}
Service
public Task<List<LiveMonitoring>> GetAllAsync()
{
return _repository.GetAll().Take(100).ToListAsync();
}
Repository
public IQueryable<TEntity> GetAll()
{
try
{
return _adminContext.Set<TEntity>();
}
catch (Exception ex)
{
throw new Exception("Couldn't retrieve entities");
}
}
What could be the problem?
I'm pretty sure that TimerManager is your issue. You did not show its declaration but looks like its constructor accepts a callback to be called at some later point of time. And that's the issue. Your scoped service _service is captured in the callback and used at some later point of time when the request has already ended. So after the request ended, the DbContext is disposed and your _service will consume a disposed context.
The fix is to simply get the data first before passing it into your callback so that the _service will not be captured into that callback, like this:
public async Task<IActionResult> Get()
{
var liveMonitorings = await _service.GetAllAsync();
var timerManager = new TimerManager(async () => await _hub.Clients.All.SendAsync("transferchartdata", liveMonitorings));
return Ok(new { Message = "Request Completed" });
}
We need to change the returned type of Get to Task<IActionResult> to support async call.
If you actually want to call _service.GetAllAsync() at some time later (not at the time of requesting Get) inside the callback, you need to inject an IServiceScopeFactory to create a scope for your service inside that callback, like this:
public IActionResult Get([FromServices] IServiceScopeFactory serviceScopeFactory)
{
var timerManager = new TimerManager(async () =>
{
using(var scope = serviceScopeFactory.CreateScope()){
var service = scope.ServiceProvider.GetRequiredService<ILiveMonitoringService>(); ​
​var liveMonitorings = await service.GetAllAsync();
​return await _hub.Clients.All.SendAsync("transferchartdata", liveMonitorings);
​ }
​});
​return Ok(new { Message = "Request Completed" });
}
This way you don't need to inject your _service into the controller's constructor (because it's not used at all).
​

How to set custom DelegatingHandler to all HttpClients automatically?

I would like to use the LoggingHttpClientHandler for all the clients in the application. I found the only usable way via named/typed client
startup.cs
services.AddTransient<LoggingHttpClientHandler>();
services.AddHttpClient("clientWithLogging").AddHttpMessageHandler<LoggingHttpClientHandler>();
and then in each service I have to use var client = _clientFactory.CreateClient("clientWithLogging") which is kinda uncomfortable.
LoggingHttpClientHandler.cs
public class LoggingHttpClientHandler : DelegatingHandler
{
public LoggingHttpClientHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content != null)
{
Logging.Log.Info(await request.Content.ReadAsStringAsync());
}
return await base.SendAsync(request, cancellationToken);
}
}
Is there a way how to do it without naming each client?
If it is just about the name, there is an extension method CreateClient that takes no arguments and uses the default name, which is string.Empty.
So you might be fine by registering the service using string.Empty as name, e.g.:
services.AddHttpClient(string.Empty).AddHttpMessageHandler<LoggingHttpClientHandler>();
and instantiate clients using:
var client = _clientFactory.CreateClient();
The problem was with resolving of params in useless constructor of LoggingHttpClientHandler.
So I removed the constructor.

Access Cookies inside unit test in AspNet.TestHost.TestServer context on ASP.NET 5 / MVC 6

There is no easy way to get an access to a CookieContainer in response object running integration tests with AspNet.TestHost.TestServer. Cookies have to be set by the controller action. What is the best way to achieve that?
var client = TestServer.Create(app =>
{
app.UseMvc(routes =>
routes.MapRoute("default", "{controller}/{action}/{id?}"));
app.UseIdentity();
}).CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "account/login");
var response = await client.SendAsync(request);
// how to get an access to cookie container ?????????
// response.Cookies prop doesn't exist
Assert.NotEmpty(response.Cookies["auth"]);
Solution that I see is to extend instance of the TestServer, return instance of a class CustomClientHandler : ClientHandler and override the whole process of sending a request in that handler, but it needs literally to change all logic except relatively small code of the TestServer.
Any better suggestion how to implement an access to Cookies in a response?
As an addition to #Oleh's response, you can achieve the same without reflection on newer frameworks like .NET 4.6.1+ / .NET Core
public class TestHttpClientHandler : DelegatingHandler
{
[NotNull]
private readonly CookieContainer cookies = new CookieContainer();
public TestHttpClientHandler([NotNull] HttpMessageHandler innerHandler)
: base(innerHandler) { }
[NotNull, ItemNotNull]
protected override async Task<HttpResponseMessage> SendAsync([NotNull] HttpRequestMessage request, CancellationToken ct)
{
Uri requestUri = request.RequestUri;
request.Headers.Add(HeaderNames.Cookie, this.cookies.GetCookieHeader(requestUri));
HttpResponseMessage response = await base.SendAsync(request, ct);
if (response.Headers.TryGetValues(HeaderNames.SetCookie, out IEnumerable<string> setCookieHeaders))
{
foreach (SetCookieHeaderValue cookieHeader in SetCookieHeaderValue.ParseList(setCookieHeaders.ToList()))
{
Cookie cookie = new Cookie(cookieHeader.Name.Value, cookieHeader.Value.Value, cookieHeader.Path.Value);
if (cookieHeader.Expires.HasValue)
{
cookie.Expires = cookieHeader.Expires.Value.DateTime;
}
this.cookies.Add(requestUri, cookie);
}
}
return response;
}
}
I've implemented custom HttpMessageHandler that tracks cookies.
It uses reflection to invoke the actual handler and just reads/sets Cookie headers.
class TestMessageHandler : HttpMessageHandler
{
delegate Task<HttpResponseMessage> HandlerSendAsync(HttpRequestMessage message, CancellationToken token);
private readonly HandlerSendAsync nextDelegate;
private readonly CookieContainer cookies = new System.Net.CookieContainer();
public TestMessageHandler(HttpMessageHandler next)
{
if(next == null) throw new ArgumentNullException(nameof(next));
nextDelegate = (HandlerSendAsync)
next.GetType()
.GetTypeInfo()
.GetMethod("SendAsync", BindingFlags.NonPublic | BindingFlags.Instance)
.CreateDelegate(typeof(HandlerSendAsync), next);
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("Cookie", cookies.GetCookieHeader(request.RequestUri));
var resp = await nextDelegate(request, cancellationToken).ConfigureAwait(false);
if (resp.Headers.TryGetValues("Set-Cookie", out var newCookies))
{
foreach (var item in SetCookieHeaderValue.ParseList(newCookies.ToList()))
{
cookies.Add(request.RequestUri, new Cookie(item.Name, item.Value, item.Path));
}
}
return resp;
}
}
And then you create your HttpClient like this:
var httpClient = new HttpClient(
new TestMessageHandler(
server.CreateHandler()));
TestMessageHandler now takes care of tracking cookies.
For a dotnet core integration test approach like the one described in the docs here, you can get cookies with the following code:
public class CookieTests : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
public CookieTests(WebApplicationFactory<Startup> factory)
{
_factory = factory;
}
[Fact]
public async Task GetPage_ShouldSetCookie_CookieSet()
{
using (var client = _factory.CreateClient())
{
var response = await client.GetAsync("/cookie_setting_url");
response.EnsureSuccessStatusCode();
//or other assertions
Assert.True(response.Headers.TryGetValues(HeaderNames.SetCookie, out IEnumerable<string> cookies));
}
}
}
The proper way, using minimal code getting cookies
in Asp.Net Core Functional Tests is as follows, (I leave out init code for setting up WebApplicationFactory, which is known stuff)
The given examples above, require either reflection (Since I think MS made a design bug on not exposing the default handlers) or require cookie parsing, which is annoying in 2023.
private (HttpClient, CookieContainerHandler) GetHttpClient()
{
CookieContainerHandler cookieContainerHandler = new();
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication(defaultScheme: "YourSchema")
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
"TestAzure", options => { });
});
}).CreateDefaultClient(cookieContainerHandler);
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(scheme: "YourSchema");
return (client, cookieContainerHandler);
}
[Fact]
public async Task MyUnitTest()
{
// Arrange
var (client, cookieHandler) = GetHttpClient();
// Act PUT/GET/POST etc
var response = await client.PutAsync("youruri", null);
var sessionCookie = cookieHandler.Container.GetAllCookies().FirstOrDefault(f => f.Name == "yourcookie"); // note this ignores cookie domain policy
}

return status code Unauthorized for custom IActionFilter in WebAPI

I am working with asp.net WebAPI and I need to create a custom ActionFilter that does a quick check to see if the user requesting the URI should actually be able to get data back.
They have already been authorized to use the web service via basic auth and their role has been validated via a custom role provider.
The last thing I need to do is to check that they have permission to view the data they are requesting with a parameter in their URI.
Here is my code:
public class AccessActionFilter : FilterAttribute, IActionFilter
{
public System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken, Func<System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage>> continuation)
{
var result = //code to see if they have permission returns either 0 or 1
if (result==0) {
throw new ArgumentException("You do not have access to this resource");
}
return continuation();
}
}
Currently I just throw an error which is not what I want, I'd rather return System.Net.HttpStatusCode.Unauthorized but I am a little miffed by the method I am overriding and I do not really understand it completely.
How would I go about returning that value?
You are probably best sticking to an exception but using the HttpResponseException which will return an Http status code too.
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
Good question here about this.
p.s.
It may be simpler/cleaner to implement ActionFilterAttribute
public class AccessActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var result = //code to see if they have permission returns either 0 or 1
if (result==0)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
base.OnActionExecuting(actionContext);
}
}
Instead of throwing exception you can set status code
public class ExecutionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var result = 0;//code to see if they have permission returns either 0 or 1
if (result == 0)
{
actionContext.Response = new HttpResponseMessage()
{
StatusCode = HttpStatusCode.Unauthorized,
Content = new StringContent("Unauthorized User")
};
}
base.OnActionExecuting(actionContext);
}
}