Introducing JustController:
// many using directives
namespace MyWebApiProject.Controllers
{
[Route("api/[controller].[action]")]
[ApiController]
public class JustController : ControllerBase
{
[HttpPost]
public async Task<ActionResult> GetById(/*Model Class*/ body)
{
return StatusCode(/* http status code */, "THIS IS NEEDED VALUE"); // <- Look at this!
}
}
}
We also have this middleware:
// many using directives
public class StatusCodeMiddleware
{
private readonly RequestDelegate _next;
public StatusCodeMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
await _next(httpContext);
/*
How to get "THIS IS NEEDED VALUE" from HttpContext here
*/
}
}
public static class StatusCodeMiddlewareExtensions // For convenient use in Startup.cs
{
public static void ConfigureCustomStatusCodeMiddleware(this IApplicationBuilder app)
{
app.UseMiddleware<StatusCodeMiddleware>();
}
}
Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.ConfigureCustomStatusCodeMiddleware();
app.UseRouting();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
How do I get "THIS IS NEEDED VALUE" from HttpContext inside the InvokeAsync() method, inside of StatusCodeMiddleware?
Thanks in advance! I really hope the Stack Overflow audience helps me! I googled the solution to this problem for 2 days so I did not find anything. Sorry for my bad English!
In order to do this you need to fiddle a little with streams. It's not pretty but if you really need to do this in a middleware you could do it like suggested in the answer here:
public async Task InvokeAsync(HttpContext httpContext)
{
Stream originalBody = context.Response.Body;
try
{
using (var memStream = new MemoryStream())
{
context.Response.Body = memStream;
await next(context);
memStream.Position = 0;
string responseBody = new StreamReader(memStream).ReadToEnd();
// Here responseBody == "THIS IS NEEDED VALUE"
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
}
}
finally
{
context.Response.Body = originalBody;
}
}
Related
I'm using asp.net core 6, in my program.cs I have the following middleware, which is used to redirect the user when the statuscode is 404.
app.Use(async (ctx, next) =>
{
await next();
if (ctx.Response.StatusCode == 404 && !ctx.Response.HasStarted)
{
string originalPath = ctx.Request.Path.Value;
ctx.Items["originalPath"] = originalPath;
ctx.Request.Path = "/error/NotFound404";
await next();
}
});
This all works fine, but I want to clean up my program.cs a bit, so I decided to put this code in it's own class like this:
public class NotFoundMiddleware
{
private readonly RequestDelegate _next;
public NotFoundMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
if (httpContext.Response.StatusCode == 404 && !httpContext.Response.HasStarted)
{
string originalPath = httpContext.Request.Path.Value;
httpContext.Items["originalPath"] = originalPath;
httpContext.Request.Path = "/error/NotFound404";
}
await _next(httpContext);
}
}
public static class NotFoundMiddlewareExtensions
{
public static IApplicationBuilder CheckNotFound(this IApplicationBuilder builder)
{
return builder.UseMiddleware<NotFoundMiddleware>();
}
}
And in my program.cs
app.CheckNotFound(); // on the same place the upp.Use... was before.
But then it doesn't work anymore.
I went through my code using breakpoints. and the InvokeAsync gets called on each request,
The problem is that the httpContext.Response.StatusCode always returns 200.
Your inline middleware calls next before testing the return value. The class only calls next after.
I want to send user bad request code back when something goes wrong in middleware.
My Startup.cs looks like this:
// configure method
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("CorsPolicy");
app.UseMiddleware<RequestMiddleware>();
app.UseMiddleware<SecondRequestMiddleware>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
My middleware looks like this:
public class RequestMiddleware
{
private readonly RequestDelegate _next;
public RequestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, IAuthInfoService authInfoService, IPowiadomieniaCacheService cacheService)
{
string jwt = context.Request.Headers["custom_header"];
if (string.IsNullOrEmpty(jwt))
{
// no jwt in headers so i want to return Unauthorized to user:
await ReturnErrorResponse(HttpContext context);
}
}
private Task ReturnErrorResponse(HttpContext context)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.CompletedTask;
}
}
But still i getting into my SecondRequestMiddleware. I want to return 401 Stauts Code to user, when there is no jwt in headers (thats what my RequestMiddleware checks) and stopped processing this request.
How to validate request in middleware and if conditions passed, return error code / response to user?
You could modify your middleware to short circuit a request, like this.
Where await context.Response.StartAsync(); will start a response and will not proceed further.
public class RequestMiddleware
{
private readonly RequestDelegate _next;
public RequestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, IAuthInfoService authInfoService, IPowiadomieniaCacheService cacheService)
{
string jwt = context.Request.Headers["custom_header"];
if (string.IsNullOrEmpty(jwt))
{
// no jwt in headers so i want to return Unauthorized to user:
await ReturnErrorResponse(HttpContext context);
}
else
{
await _next(context);
}
}
private async Task ReturnErrorResponse(HttpContext context)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await context.Response.StartAsync();
}
}
when there is no jwt in headers (thats what my RequestMiddleware
checks) and stopped processing this request.
You can terminate the request by writing the following:
await context.Response.WriteAsync("error message");
If the headers have the jwt , then you need to trigger the following middleware through the following statement, otherwise it will not be executed automatically:
await _next.Invoke(context);
More details, refer to ASP.NET Core Middleware.
Change your RequestMiddleware as follow:
public class RequestMiddleware
{
private readonly RequestDelegate _next;
public RequestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
string jwt = context.Request.Headers["custom_header"];
if (string.IsNullOrEmpty(jwt))
{
await ReturnErrorResponse(context);
}
else
{
await _next.Invoke(context);// call next middleware
}
}
private async Task ReturnErrorResponse(HttpContext context)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await context.Response.WriteAsync("error message!");
}
}
In order to log the response body into app-insights from HttpContext I should develop a Middleware, that intercepts each request and response and extracts the HttpContext, in my case Response.Body and then sends it into app-insights using telemetryClient.TrackTrace().
The problem is that when I debug and extract the body of the respone is always empty.
This is my Middleware class
public class ResponseBodyInitializer
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly RequestDelegate _next;
public ResponseBodyInitializer(IHttpContextAccessor httpContextAccessor, RequestDelegate next)
{
_httpContextAccessor = httpContextAccessor;
_next = next;
}
public async Task Invoke(HttpContext context)
{
string resBody = await GetResponseBodyForTelemetry(context);
SendDataToTelemetryLog(resBody, context);
}
private static void SendDataToTelemetryLog(string respBody, HttpContext context)
{
if (context.Request.Method == "POST" || context.Request.Method =="PUT")
{
var telemetryClient = new TelemetryClient();
var traceTelemetry = new TraceTelemetry
{
Message = respBody,
SeverityLevel = SeverityLevel.Information
};
//Send a trace message for display in Diagnostic Search.
telemetryClient.TrackTrace(traceTelemetry);
}
}
private async Task<string> GetResponseBodyForTelemetry(HttpContext context)
{
var originalBody = context.Response.Body;
try
{
using (var memStream = new MemoryStream())
{
context.Response.Body = memStream;
//await the responsebody
await _next(context);
if (context.Response.StatusCode == 204)
{
return null;
}
memStream.Position = 0;
var responseBody = new StreamReader(memStream).ReadToEnd();
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
return responseBody;
}
}
finally
{
context.Response.Body = originalBody;
}
}
}
StartUp.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
app.UseDefaultFiles()
.UseStaticFiles()
.UseMiddleware<ResponseBodyInitializer>()
.UseBotFramework();
}
OR is there any other way to log the response in app-insights?
Here are the steps which you have to do it:-
1) You have to implement ITelemetryInitializer.
2) Inject the IHttpContextAccessor to the class and read the response stream within the Initialize method.
3) Ensure the passed ITelemetry object is from type RequestTelemetry and that the HttpRequest was either a Post or Put.
4) Then you can read the response using the IHttpContext.HttpContext.Response.Body property and log it using Application Insight.
Finally, register your class within the ConfigureService method in your Startup.cs
I see, you are not implementing ITelemetryInitializer ,also read the stream in Initialize method
Try these steps and see if it helps.
I'd like to have every request logged automatically. In previous .Net Framwork WebAPI project, I used to register a delegateHandler to do so.
WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
config.MessageHandlers.Add(new AutoLogDelegateHandler());
}
AutoLogDelegateHandler.cs
public class AutoLogDelegateHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var requestBody = request.Content.ReadAsStringAsync().Result;
return await base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
HttpResponseMessage response = task.Result;
//Log use log4net
_LogHandle(request, requestBody, response);
return response;
});
}
}
an example of the log content:
------------------------------------------------------
2017-08-02 19:34:58,840
uri: /emp/register
body: {
"timeStamp": 1481013427,
"id": "0322654451",
"type": "t3",
"remark": "system auto reg"
}
response: {"msg":"c556f652fc52f94af081a130dc627433","success":"true"}
------------------------------------------------------
But in .NET Core WebAPI project, there is no WebApiConfig , or the register function at Global.asax GlobalConfiguration.Configure(WebApiConfig.Register);
So is there any way to achieve that in .NET Core WebAPI?
ActionFilter will work until you need to log only requests processed by MVC middleware (as controller actions).
If you need logging for all incoming requests, then you need to use a middleware approach.
Good visual explanation:
Note that middleware order is important, and if your logging should be done at the start of pipeline execution, your middleware should be one of the first one.
Simple example from docs:
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
// Do loging
// Do work that doesn't write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});
For someone that wants a quick and (very) dirty solution for debugging purposes (that works on .Net Core 3), here's an expansion of this answer that's all you need...
app.Use(async (context, next) =>
{
var initialBody = context.Request.Body;
using (var bodyReader = new StreamReader(context.Request.Body))
{
string body = await bodyReader.ReadToEndAsync();
Console.WriteLine(body);
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));
await next.Invoke();
context.Request.Body = initialBody;
}
});
You can create your own filter attribute...
public class InterceptionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var x = "This is my custom line of code I need executed before any of the controller actions, for example log stuff";
base.OnActionExecuting(actionContext);
}
}
... and you would register it with GlobalFilters, but since you said you're using .NET Core, this is how you can try proceeding...
From learn.microsoft.com:
You can register a filter globally (for all controllers and actions)
by adding it to the MvcOptions.Filters collection in the
ConfigureServices method in the Startup class:
Let us know if it worked.
P.S.
Here's a whole tutorial on intercepting requests with WebAPI, in case someone needs more details.
Demo:
AutologArribute.cs (new file)
/// <summary>
/// <see cref="https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#Dependency injection"/>
/// </summary>
public class AutoLogAttribute : TypeFilterAttribute
{
public AutoLogAttribute() : base(typeof(AutoLogActionFilterImpl))
{
}
private class AutoLogActionFilterImpl : IActionFilter
{
private readonly ILogger _logger;
public AutoLogActionFilterImpl(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<AutoLogAttribute>();
}
public void OnActionExecuting(ActionExecutingContext context)
{
// perform some business logic work
}
public void OnActionExecuted(ActionExecutedContext context)
{
//TODO: log body content and response as well
_logger.LogDebug($"path: {context.HttpContext.Request.Path}");
}
}
}
StartUp.cs
public void ConfigureServices(IServiceCollection services)
{
//....
// https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#filter-scopes-and-order-of-execution
services.AddMvc(opts=> {
opts.Filters.Add(new AutoLogAttribute());
});
//....
}
This is a complete Log component for .NET Core 2.2 Web API.
It will log Requests and Responses, both Headers and Bodies.
Just make sure you have a "Logs" folder.
AutoLogMiddleWare.cs (New file)
public class AutoLogMiddleWare
{
private readonly RequestDelegate _next;
public AutoLogMiddleWare(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
string route = context.Request.Path.Value;
string httpStatus = "0";
// Log Request
var originalRequestBody = context.Request.Body;
originalRequestBody.Seek(0, SeekOrigin.Begin);
string requestBody = new StreamReader(originalRequestBody).ReadToEnd();
originalRequestBody.Seek(0, SeekOrigin.Begin);
// Log Response
string responseBody = string.Empty;
using (var swapStream = new MemoryStream())
{
var originalResponseBody = context.Response.Body;
context.Response.Body = swapStream;
await _next(context);
swapStream.Seek(0, SeekOrigin.Begin);
responseBody = new StreamReader(swapStream).ReadToEnd();
swapStream.Seek(0, SeekOrigin.Begin);
await swapStream.CopyToAsync(originalResponseBody);
context.Response.Body = originalResponseBody;
httpStatus = context.Response.StatusCode.ToString();
}
// Clean route
string cleanRoute = route;
foreach (var c in Path.GetInvalidFileNameChars())
{
cleanRoute = cleanRoute.Replace(c, '-');
}
StringBuilder sbRequestHeaders = new StringBuilder();
foreach (var item in context.Request.Headers)
{
sbRequestHeaders.AppendLine(item.Key + ": " + item.Value.ToString());
}
StringBuilder sbResponseHeaders = new StringBuilder();
foreach (var item in context.Response.Headers)
{
sbResponseHeaders.AppendLine(item.Key + ": " + item.Value.ToString());
}
string filename = DateTime.Now.ToString("yyyyMMdd.HHmmss.fff") + "_" + httpStatus + "_" + cleanRoute + ".log";
StringBuilder sbLog = new StringBuilder();
sbLog.AppendLine("Status: " + httpStatus + " - Route: " + route);
sbLog.AppendLine("=============");
sbLog.AppendLine("Request Headers:");
sbLog.AppendLine(sbRequestHeaders.ToString());
sbLog.AppendLine("=============");
sbLog.AppendLine("Request Body:");
sbLog.AppendLine(requestBody);
sbLog.AppendLine("=============");
sbLog.AppendLine("Response Headers:");
sbLog.AppendLine(sbResponseHeaders.ToString());
sbLog.AppendLine("=============");
sbLog.AppendLine("Response Body:");
sbLog.AppendLine(responseBody);
sbLog.AppendLine("=============");
var path = Directory.GetCurrentDirectory();
string filepath = ($"{path}\\Logs\\{filename}");
File.WriteAllText(filepath, sbLog.ToString());
}
catch (Exception ex)
{
// It cannot cause errors no matter what
}
}
}
public class EnableRequestRewindMiddleware
{
private readonly RequestDelegate _next;
public EnableRequestRewindMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
context.Request.EnableRewind();
await _next(context);
}
}
public static class EnableRequestRewindExtension
{
public static IApplicationBuilder UseEnableRequestRewind(this IApplicationBuilder builder)
{
return builder.UseMiddleware<EnableRequestRewindMiddleware>();
}
}
Startup.cs (existing file)
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IMapper mapper, ILoggerFactory loggerFactory)
{
bool isLogEnabled = true; // Replace it by some setting, Idk
if(isLogEnabled)
app.UseEnableRequestRewind(); // Add this first
(...)
if(isLogEnabled)
app.UseMiddleware<AutoLogMiddleWare>(); // Add this just above UseMvc()
app.UseMvc();
}
Starting with ASP.NET Core 6 you can use default middleware for such behaviour (source):
app.UseHttpLogging();
I have a owin middleware, which checks for particular value in the query string, and updates another value in query string. I am using Microsoft.OWin.Testing to call this piece of middleware and then make a request. How do I exactly check the query string was changed after I make request.
public static void UseInjectQueryString(this IAppBuilder app)
{
app.Use(async (context, next) =>
{
// Some code here
if (context.Environment.ContainsKey("owin.RequestQueryString"))
{
var existingQs = context.Environment["owin.RequestQueryString"];
var parser = new UrlParser(existingQs.ToString());
parser[Constants.AuthorizeRequest.AcrValues] = newAcrValues;
context.Environment.Remove("owin.RequestQueryString");
context.Environment["owin.RequestQueryString"] = parser.ToString();
}
}
await next();
});
Unit test :
[TestMethod]
public async Task SomeTest()
{
using (var server = TestServer.Create(app =>
{
//.... injecting middleware..
}))
{
HttpResponseMessage response = await server.CreateRequest("core/connect/token?client_id=clientStub").GetAsync();
}
}
I would refactor the middleware so that you can test it outside of the pipeline. For example, you could structure it like this:
public static class InjectQueryStringMiddleware
{
public static void InjectQueryString(IOwinContext context)
{
if (context.Environment.ContainsKey("owin.RequestQueryString"))
{
var existingQs = context.Environment["owin.RequestQueryString"];
var parser = new UrlParser(existingQs.ToString());
parser[Constants.AuthorizeRequest.AcrValues] = newAcrValues;
context.Environment.Remove("owin.RequestQueryString");
context.Environment["owin.RequestQueryString"] = parser.ToString();
}
}
public static void UseInjectQueryString(this IAppBuilder app)
{
app.Use(async (context, next) =>
{
// some code here
InjectQueryString(context);
}
}
}
Now you can test InjectQueryString and that it does the right thing to the context, without having to create the entire pipeline.