ASP.Net Web API - The remote server returned an error: (405) Method Not Allowed - asp.net-mvc-4

I am using the new MVC4 ASP.Net Web API system.
I am calling my API in a test project using WebClient. If I use GET or POST, it works fine. If I use anything else, I get Method Not Allowed. I am actually "faking" the method by injecting the following header. I am doing this because my end users will also have to do this due to the limitations of some firewalls.
I am calling the URL via IIS (i.e. not cassini) - e.g. http://localhost/MyAPI/api/Test
wc.Headers.Add("X-HTTP-Method", "PUT");
I tried adjusting the script mappings in IIS, but as there is no extension, I don't know what I am meant to be adjusting!
Any ideas?
Regards
Nick

The X-HTTP-Method (or X-HTTP-Method-Override) header is not supported out of the box by Web API. You will need to create a custom DelegatingHandler (below implementation assumes that you are making your request with POST method as it should be):
public class XHttpMethodDelegatingHandler : DelegatingHandler
{
private static readonly string[] _allowedHttpMethods = { "PUT", "DELETE" };
private static readonly string _httpMethodHeader = "X-HTTP-Method";
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Post && request.Headers.Contains(_httpMethodHeader))
{
string httpMethod = request.Headers.GetValues(_httpMethodHeader).FirstOrDefault();
if (_allowedHttpMethods.Contains(httpMethod, StringComparer.InvariantCultureIgnoreCase))
request.Method = new HttpMethod(httpMethod);
}
return base.SendAsync(request, cancellationToken);
}
}
Now you just need to register your DelegatingHandler in Global.asax:
protected void Application_Start(object sender, EventArgs e)
{
GlobalConfiguration.Configuration.MessageHandlers.Add(new XHttpMethodDelegatingHandler());
...
}
This should do the trick.

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.

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.

Web API 2 Post 404s, but Get works

I'm confused... I have a very simple Web API and controller, which works fine if I have a GET request, but 404's if I have a POST request.
[RoutePrefix("api/telemetry/trial")]
public class LoginTelemetryController : ApiController
{
[Route("login")]
[HttpPost]
public IHttpActionResult RecordLogin(string appKey) {
using (var context = new Core.Data.CoreContext()) {
context.ActivityLogItems.Add(new Domain.Logging.ActivityLogItem()
{
ActivityType = "Trial.Login",
DateUtc = DateTime.UtcNow,
Key = new Guid(appKey)
});
context.SaveChanges();
}
return Ok();
}
When I post against this in postman, I get:
{
"message": "No HTTP resource was found that matches the request URI 'http://localhost:47275/api/telemetry/trial/login'.",
"messageDetail": "No action was found on the controller 'LoginTelemetry' that matches the request."
}
If I change it to a [HttpGet] and put the appKey as a querystring, all is fine.
My app startup is very simple:
public void Configuration(IAppBuilder app)
{
log4net.Config.XmlConfigurator.Configure();
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.MapHttpAttributeRoutes(); // <------ HERE
FilterConfig.RegisterHttpFilters(httpConfig.Filters);
LoggingConfig.RegisterHandlers(httpConfig.Services);
ConfigureOAuth(app);
ConfigureWebApi(httpConfig);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(httpConfig);
}
Can anyone spot why POST requests aren't being found? Thanks
If I take string parameter out and replace it with a request object, it works...
Instead of: public IHttpActionResult RecordLogin(string appKey)
I create a request model class:
public class PostLoginTelemetryRequest{
public string appKey {get;set;}
}
Then alter the signature:
public IHttpActionResult RecordLogin(PostLoginTelemetryRequest request)
Everything works fine (why it can't take a regular string like MVC5 web dev, I don't know, but anyway...)
(also note that I had tried this in every format from the client with the string method: form-url-encode, raw body, etc, so I'm fairly certain it wasn't a calling format issue).

Does .NET Core HttpClient have the concept of interceptors?

I would like to wrap some timing logic around all calls made through HttpClient from my ASP.NET Core app, including calls made from 3rd party libraries.
Does HttpClient in .NET Core have something I can plug into to run some code on every request?
Yes, it does. HttpClient produces a HTTP request via DelegatingHandler chain. To intercept the HttpClient request, you can add a derived handler with overrided SendAsync method to that chain.
Usage:
var handler = new ExampleHttpHandler(fooService);
var client = new HttpClient(new ExampleHttpHandler(handler));
var response = await client.GetAsync("http://google.com");
Implementation:
public class ExampleHttpHandler : DelegatingHandler
{
//use this constructor if a handler is registered in DI to inject dependencies
public ExampleHttpHandler(FooService service) : this(service, null)
{
}
//Use this constructor if a handler is created manually.
//Otherwise, use DelegatingHandler.InnerHandler public property to set the next handler.
public ExampleHttpHandler(FooService service, HttpMessageHandler innerHandler)
{
//the last (inner) handler in the pipeline should be a "real" handler.
//To make a HTTP request, create a HttpClientHandler instance.
InnerHandler = innerHandler ?? new HttpClientHandler();
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
//add any logic here
return await base.SendAsync(request, cancellationToken);
}
}
BTW, I recommend moving as much business logic out of a custom handler as possible to simplify unit-testing it.

Lightinject with Web API - How can I get the HttpRequestMessage?

How can I get the current HttpRequestMessage when using Lightinject with Web API?
In Simple Injector, for example, I have the following extension method on the container:
var msg = container.GetCurrentHttpRequestMessage()
But I couldn't find a matching one in Lightinject.
If you take a look in the Simple Injector Web API integration source code, you'll find that the implementation is actually really straightforward. You can easily implement this yourself.
What you need is to create a custom DelegatingHandler that stores the current HttpRequestMessage in a way that you can retrieve it later on, as follows:
public sealed class HttpRequestMessageDelegatingHandler : DelegatingHandler {
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken) {
CurrentMessage = request;
return base.SendAsync(request, cancellationToken);
}
public static HttpRequestMessage CurrentMessage {
get { return (HttpRequestMessage)CallContext.LogicalGetData("RequestMessage"); }
private set { CallContext.LogicalSetData(value, "RequestMessage"); }
}
}
// Register this handler as follows in Web API
configuration.MessageHandlers.Add(new HttpRequestMessageDelegatingHandler());
Now you can retrieve the request's current message as follows:
HttpRequestMessageDelegatingHandler.CurrentMessage
The Simple Injector documentation advises to hide this call behind a custom abstraction, such as a simple IRequestMessageProvider. An implementation is of course easily created:
private sealed class RequestMessageProvider : IRequestMessageProvider {
public HttpRequestMessage CurrentMessage {
get { return HttpRequestMessageDelegatingHandler.CurrentMessage; }
}
}
Instead of creating an IRequestMessageProvider abstraction plus implementation, you can register it directly as delegate as follows:
container.Register<Func<HttpRequestMessage>>(_ =>
() => HttpRequestMessageDelegatingHandler.CurrentMessage);
My preference is to use an interface instead of a Func<T>, because such interface is much more explicit and readable.