"no body provided" response when POSTing to a minimal endpoint - asp.net-core

I'm experimenting with converting some simple controller methods to using endpoint routing on the latest RC of aspnet core 6. The controller works fine, and it looks like this:
[HttpPost]
public async Task<IActionResult> CreateUser(NewUserRequest newUser)
{
/* do stuff with newUser */
/* return CreatedAtRoute(...); */
}
I've tried converting to using IEndpointRouteBuilder, like so:
endpoints.MapPost("/api/user/test",
(NewUserRequest request) =>
{
/* same thing as controller method */
});
// prints error to the log:
// Implicit body inferred for parameter "request" but no body was provided. Did you mean to use a Service instead?
I'm using the exact same test request (through Postman, usually), so I know it's not the client's problem. What's more, if I just take an HttpRequest parameter, I can read the body into a json blob.
endpoints.MapPost("api/user/test2",
async (HttpRequest request) =>
{
using (var sr = new StreamReader(request.BodyReader.AsStream()))
{
var body = await sr.ReadToEndAsync();
/* body is valid json here */
}
});
I'm really at a loss here, especially since it works in some cases, but not when I'm binding a type to one of the minimal endpoints.

The syntax you used is available in Minimal APIs first introduced in ASP.NET Core 6 Preview 4. At that time it wasn't possible to bind to specific objects yet. It's not related to Endpoint routing which was added in ASP.NET Core 3.1.
If you create a new "empty" web app with dotnet new web you get:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
This is using endpoint routing through the new WebApplication class. To add the POST endpoint you can use :
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapPost("/api/user/test",(NewUserRequest request) =>
{
Console.WriteLine(request.Name);
return request;
/* same thing as controller method */
});
app.Run();
class NewUserRequest
{
public string Name{get;set;}
}
Additional classes and methods must be defined at the end of the file.
Testing this with curl :
curl -X POST https://localhost:7153/api/user/test -d '{ "Name":"A"}' -H 'Content-Type: application/json' -v
Logs A in the service's console and returns
{"name":"A"}%
to the client.
The reason you get this error :
Implicit body inferred for parameter "request" but no body was provided. Did you mean to use a Service instead?
Is that in ASP.NET Core 6 Preview 6 it became possible to inject services without using the [FromServices] attribute. In the following code, the DbContext is injected:
app.MapGet("/todos", async (TodoDbContext db) =>
{
return await db.Todos.ToListAsync();
});

Related

Using Attribute and ActionFilters for logging request and response of controller and actions

I am trying to find an elegant way of logging every request and response in my Web API using Filters in Asp.net Core 3.1 rather than have them in each action and each controller.
Haven't found a nice solution that seems performable well to deploy in production.
I've been trying to do something like this (below) but no much success.
Any other suggestion would be appreciated.
public class LogFilter : IAsyncActionFilter
{
private readonly ILogger _logger;
public LogFilter(ILogger logger)
{
_logger = logger;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var requestBodyData = context.ActionArguments["request"];
var responseBodyData = "";//how to get the response result
_logger.LogInformation($"{AppDomain.CurrentDomain.FriendlyName} Endpoint: {nameof(context.ActionDescriptor.DisplayName)} - Request Body: {requestBodyData}");
await next();
_logger.LogInformation($"{AppDomain.CurrentDomain.FriendlyName} Endpoint: {nameof(context.ActionDescriptor.DisplayName)} - Response Body: {responseBodyData}");
}
}
I think logging the response should be done in debugging mode only and really can be done at your service API (by using DI interception). That way you don't need to use IActionFilter which actually can provide you only a wrapper IActionResult which wraps the raw value from the action method (which is usually the result returned from your service API). Note that at the phase of action execution (starting & ending can be intercepted by using IActionFilter or IAsyncActionFilter), the HttpContext.Response may have not been fully written (because there are next phases that may write more data to it). So you cannot read the full response there. But here I suppose you mean reading the action result (later I'll show you how to read the actual full response body in a correct phase). When it comes to IActionResult, you have various kinds of IActionResult including custom ones. So it's hard to have a general solution to read the raw wrapped data (which may not even be exposed in some custom implementations). That means you need to target some specific well-known action results to handle it correctly. Here I introduce code to read JsonResult as an example:
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var requestBodyData = context.ActionArguments["request"];
_logger.LogInformation($"{AppDomain.CurrentDomain.FriendlyName} Endpoint: {nameof(context.ActionDescriptor.DisplayName)} - Request Body: {requestBodyData}");
var actionExecutedContext = await next();
var responseBodyData = "not supported result";
//sample for JsonResult
if(actionExecutedContext.Result is JsonResult jsonResult){
responseBodyData = JsonSerializer.Serialize(jsonResult.Value);
}
//check for other kinds of IActionResult if any ...
//...
_logger.LogInformation($"{AppDomain.CurrentDomain.FriendlyName} Endpoint: {nameof(context.ActionDescriptor.DisplayName)} - Response Body: {responseBodyData}");
}
IActionResult has a method called ExecuteResultAsync which can trigger the next processing phase (result execution). That's when the action result is fully written to the HttpContext.Response. So you can try creating a dummy pipeline (starting with a dummy ActionContext) on which to execute the action result and get the final data written to the response body. However that's what I can imagine in theory. It would be very complicated to go that way. Instead you can just use a custom IResultFilter or IAsyncResultFilter to try getting the response body there. Now there is one issue, the default HttpContext.Response.Body is an HttpResponseStream which does not support reading & seeking at all (CanRead & CanSeek are false), we can only write to that kind of stream. So there is a hacky way to help us mock in a readable stream (such as MemoryStream) before running the code that executes the result. After that we swap out the readable stream and swap back the original HttpResponseStream in after copying data from the readable stream to that stream. Here is an extension method to help achieve that:
public static class ResponseBodyCloningHttpContextExtensions
{
public static async Task<Stream> CloneBodyAsync(this HttpContext context, Func<Task> writeBody)
{
var readableStream = new MemoryStream();
var originalBody = context.Response.Body;
context.Response.Body = readableStream;
try
{
await writeBody();
readableStream.Position = 0;
await readableStream.CopyToAsync(originalBody);
readableStream.Position = 0;
}
finally
{
context.Response.Body = originalBody;
}
return readableStream;
}
}
Now we can use that extension method in an IAsyncResultFilter like this:
//this logs the result only, to write the log entry for starting/beginning the action
//you can rely on the IAsyncActionFilter as how you use it.
public class LoggingAsyncResultFilterAttribute : Attribute, IAsyncResultFilter
{
//missing code to inject _logger here ...
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var readableStream = await context.HttpContext.CloneBodyAsync(() => next());
//suppose the response body contains text-based content
using (var sr = new StreamReader(readableStream))
{
var responseText = await sr.ReadToEndAsync();
_logger.LogInformation($"{AppDomain.CurrentDomain.FriendlyName} Endpoint: {nameof(context.ActionDescriptor.DisplayName)} - Response Body: {responseText}");
}
}
}
You can also use an IAsyncResourceFilter instead, which can capture result written by IExceptionFilter. Or maybe the best, use an IAsyncAlwaysRunResultFilter which can capture the result in all cases.
I assume that you know how to register IAsyncActionFilter so you should know how to register IAsyncResultFilter as well as other kinds of filter. It's just the same.
starting with dotnet 6 asp has HTTP logging built in. Microsoft has taken into account redacting information and other important concepts that need to be considered when logging requests.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
/* enabled HttpLogging with this line */
app.UseHttpLogging();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.MapGet("/", () => "Hello World!");
app.Run();
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-logging/?view=aspnetcore-6.0#enabling-http-logging

Apikey authentication webapi core 2.1

I have some service that is going to send some messages to my endpoint. But I need to validate these messages by checking if the http header consist out of a fixed (for a period) api key and id. I can do this by check the header but I don't think this is good practice. Anybody a clue on how to verify that the message send from the service?
I have found something but it is for core2.2 and I need to use 2.1... (https://github.com/mihirdilip/aspnetcore-authentication-apiKey)
Thanks in advance
If you have quite a few endpoints, maybe even multiple controllers i would suggest writing a middleware to handle this.
But if this apikey check is only needed to one endpoint. Since you said "my endpoint".
I would recommend just checking the header value in the controller action/endpoint
Example:
[HttpGet]
public async Task<IActionResult> ExampleEndpoint() {
var headerValue = Request.Headers["Apikey"];
if(headerValue.Any() == false)
return BadRequest(); //401
//your endpoint code
return Ok(); //200
}
You can check the request header in custom middleware as shown here . Or you can use action filter to check the api key , see code sample here .
Like I said I want to do this via the middleware and not in the end of the http pipeline. In the meantime I figured out a solution, it is a simple one but it works.
I created a class called MiddelWareKeyValidation with the following async method:
public async Task Invoke(HttpContext context)
{
if (!context.Request.Headers.Keys.Contains("X-GCS-Signature") || !context.Request.Headers.Keys.Contains("X-GCS-KeyId"))
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("User Key is missing");
return;
}
else
{
var apiKey = new ApiKey { Signature = context.Request.Headers["X-GCS-Signature"], Key = context.Request.Headers["X-GCS-KeyId"] };
if (!ContactsRepo.CheckValidUserKey(apiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Invalid User Key");
return;
}
}
await _next.Invoke(context);
}
Then I go to my Startup.cs in the Configure method where I add a new middleware like so:
app.UseMiddleware<MiddelWareKeyValidation>();
A good resource and credits goes to this article: https://www.mithunvp.com/write-custom-asp-net-core-middleware-web-api/

FluentValidation errors to Logger

I'm registering my FluentValidation validators as follows:
services.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<CustomRequestValidator>());
How can I get a handle of any validation errors to log them via my logging implementation, e.g. Serilog?
For users stumbling into this problem (like me) I'd like to provide the solution I found.
The actual problem is that ASP.NET Core, and APIs in particular, make Model State errors automatically trigger a 400 error response, which doesn't log the validation errors.
Below that same documentation they include instructions on how to enable automatic logging for that feature which I believe should be the correct solution to this problem.
To jump onto what #mdarefull said, I did the following:
public void ConfigureServices(IServiceCollection services)
{
// Possible other service configurations
services.AddMvc()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
if (!context.ModelState.IsValid)
{
LogAutomaticBadRequest(context);
}
return new BadRequestObjectResult(context.ModelState);
};
});
// Possible other service configurations
}
The LogAutomaticBadRequest method is as follows:
private static void LogAutomaticBadRequest(ActionContext context)
{
// Setup logger from DI - as explained in https://github.com/dotnet/AspNetCore.Docs/issues/12157
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger(context.ActionDescriptor.DisplayName);
// Get error messages
var errorMessages = string.Join(" | ", context.ModelState.Values
.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage));
var request = context.HttpContext.Request;
// Use whatever logging information you want
logger.LogError("Automatic Bad Request occurred." +
$"{System.Environment.NewLine}Error(s): {errorMessages}" +
$"{System.Environment.NewLine}|{request.Method}| Full URL: {request.Path}{request.QueryString}");
}

CreateRef method migrated to .Net Core results in 404, how to implement Create Relationships in OData with .Net Core

I have 2 POCOs, Lessons and Traits with int PKs.
I have navigation properties set up such that I can successfully $expand like so:
http://localhost:54321/odata/Lessons?$expand=Traits
http://localhost:54321/odata/Traits?$expand=Lessons
My final hurdle in migrating project from Net 461 to .Net Core 2 is Creating Relationships.
Specifically, when I try to call the following method, with the following request, I get a 404.
[AcceptVerbs("POST", "PUT")]
public async Task<IActionResult> CreateRef(
[FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
{
.... Do Work
}
Postman request:
http://localhost:54321/odata/Lessons(1)/Traits/$ref
body:
{
"#odata.id":"http://localhost:54321/OData/traits(1)"
}
The following is my Startup.Configure method.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
var builder = ConfigureOdataBuilder(app);
app.UseMvc(routeBuilder =>
{
routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(null).Count();
routeBuilder.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());
// Work-around for #1175
routeBuilder.EnableDependencyInjection();
routeBuilder.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}"); // enable mvc controllers
});
}
private ODataConventionModelBuilder ConfigureOdataBuilder(IApplicationBuilder app)
{
var builder = new ODataConventionModelBuilder(app.ApplicationServices);
builder.EntitySet<Lessons>(nameof(Lessons));
builder.EntitySet<Traits>(nameof(Traits));
return builder;
}
Question: How do I reach this controller method?
Things I have tried,
Rename CreateRef to CreateLink and Create
Followed every link in these Git Issues, here and
here.
Read up on Attribute Routing spec.
Tried solution based off this DeleteRef in this Web Api oData v4 $ref 404 or server error
Tried explicitly defining route with [ODataRoute("Lessons({key})/{navigationProperty}")]
It was a long way, but I finally found the answer.
[ODataRoute("lessons({lessonId})/traits({traitId})/$ref")]
public IActionResult CreateRef([FromODataUri] int lessonId, [FromODataUri] int traitId)
{
//do work
}
Important: You have to call the id-params as I did. Don´t just call them Id - otherwise you´ll get a 404.
One more thing...
For those who tried the way from the microsoft docs - the Api-Names changed.. You don´t need them for this task, but if you have to convert an Uri to an OData-Path, here is an Uri-Extension doing this for you:
public static Microsoft.AspNet.OData.Routing.ODataPath CreateODataPath(this Uri uri, HttpRequest request)
{
var pathHandler = request.GetPathHandler();
var serviceRoot = request.GetUrlHelper().CreateODataLink(
request.ODataFeature().RouteName,
pathHandler,
new List<ODataPathSegment>());
return pathHandler.Parse(serviceRoot, uri.LocalPath, request.GetRequestContainer());
}
If you have an Uri like this: http://localhost:54321/OData/traits(1) you can split this into OData-segments to get e.g. the navigation: returnedPath.NavigationSource or the specified key: returnedPath.Segments.OfType<KeySegment>().FirstOrDefault().Keys.FirstOrDefault().Value

Adding values to header in MassTransit.RabbitMq

I am using MassTransit 3.0.0.0 and I have a hard time understanding how to intercept messages in a Request-Response scenario on their way out and add some information to the headers field that I can read on the receiver's end.
I was looking at the Middleware, as recommended in the MassTransit docs - see Observers warning - but the context you get on the Send is just a Pipe context that doesn't have access to the Headers field so I cannot alter it. I used the sample provided in Middleware page.
I then, looked at IPublishInterceptor
public class X<T> : IPublishInterceptor<T> where T : class, PipeContext
{
public Task PostPublish(PublishContext<T> context)
{
return new Task(() => { });
}
public Task PostSend(PublishContext<T> context, SendContext<T> sendContext)
{
return new Task(() => { });
}
public Task PrePublish(PublishContext<T> context)
{
context.Headers.Set("ID", Guid.NewGuid().ToString());
return new Task(() => { });
}
public Task PreSend(PublishContext<T> context, SendContext<T> sendContext)
{
context.Headers.Set("ID", Guid.NewGuid().ToString());
return new Task(() => { });
}
}
Which is very clear and concise. However, I don't know where it is used and how to link it to the rest of the infrastructure. As it stands, this is just an interface that is not really linked to anything.
If you need to add headers when a message is being sent, you can add middleware components to either the Send or the Publish pipeline as shown below. Note that Send filters will apply to all messages, whereas Publish filters will only apply to messages which are published.
// execute a synchronous delegate on send
cfg.ConfigureSend(x => x.Execute(context => {}));
// execute a synchronous delegate on publish
cfg.ConfigurePublish(x => x.Execute(context => {}));
The middleware can be configured on either the bus or individual receive endpoints, and those configurations are local to where it's configured.
You can also add headers in the consumer class:
public async Task Consume(ConsumeContext<MyMessage> context)
{
....
await context.Publish<MyEvent>(new { Data = data }, c => AddHeaders(c));
}
public static void AddHeaders(PublishContext context)
{
context.Headers.Set("CausationId", context.MessageId);
}
http://masstransit-project.com/MassTransit/advanced/middleware/custom.html
Shows adding an extension method to make it clear what you're setup. That's a big help if it's an interceptor that will be used a lot, so it's clear that purpose. You can skip that step if you want.
Basically, just...
cfg.AddPipeSpecification(new X<MyMessage>());
When configuring the transport.