Unit testing owin middleware which sets query string - c#

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.

Related

ASPNetCore REST API calls triggered unexpectedly

I have a REST API like so:
[ApiController]
[Route("abc/events/v{version:ApiVersion}")]
[ApiVersion("1.0")]
public class ABCController : Controller
{
[HttpPut("a")]
[Produces(MediaTypeNames.Application.Json)]
public ActionResult A(
[FromBody, BindRequired] AEvent aEvent)
{
StatusCodeResult result = Ok();
// do something with aEvent
return result;
}
[HttpPut("b")]
[Produces(MediaTypeNames.Application.Json)]
public ActionResult B(
[FromBody, BindRequired] BEvent bEvent)
{
StatusCodeResult result = Ok();
// do something with event b
return result;
}
[HttpPut("game")]
[Produces(MediaTypeNames.Application.Json)]
public ActionResult C(
[FromBody, BindRequired] CEvent cEvent)
{
StatusCodeResult result = Ok();
// do something with event c
return result;
}
}
I call my API from an Integration test project one of whose tests look like this:
[TestMethod]
public async Task Test_Put_A_Event_OK()
{
var logger = _container.Resolve<ILogger>();
var client = _container.Resolve<IMyClientAPI>(
new NamedParameter("logger", logger),
new NamedParameter("schema", "http"),
new NamedParameter("hostname", "localhost"),
new NamedParameter("hostPort", 5000));
var result = await client.PutEvent(_AJsonData);
Assert.IsTrue(result == Result.Success);
}
The calls these tests make to each of the endpoints occurs correctly. the problem then occurs a second or two after the successfully handled call returns to the client. The server appears to receive a request for each endpoint one after the other (even for those endpoints that have not been requested).
The associated Startup code:
public class Startup
{
// Location of xml comments for generating
// additional swagger UI information.
// Type '///' before any endpoint route function
// and it will auto-generate the comment structure.
static string XmlCommentsFilePath
{
get
{
var exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
return Path.Combine(exePath, "docs", "etsapi.xml");
}
}
public void ConfigureServices(IServiceCollection services)
{
var abcService = new ABCService();
services.AddControllers();
services.AddApiVersioning();
services.AddSingleton<IEtsSignageService>(abcService);
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "ABCApi", Version = "v1" });
options.IncludeXmlComments(XmlCommentsFilePath);
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
// Cors, Authentication and authorization here if needed
app.UseEndpoints(x => x.MapControllers());
// Enable middleware to serve generated Swagger as a JSON endpoint.
if (env.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
}
}
Any thoughts on what I may be doing wrong will be greatly appreciated.
The issue I was having was to do with Autofac Dependancy injecting multiple versions of a backend class generating duplicate messages in my queue that issues the the API Requests.

Integration tests do not recognize actions of controllers

I am trying integration tests with XUnit. I can access the endpoints that have been assigned on MapGet but to not actions inside controllers. I get a NotFound error. I am running the application on an API project with ApplicationFactory : WebApplicationFactory but all requests that I make with Factory.Client return NotFound. Is there any definition needed in integration tests in order to be able to send requests to controllers?
Test projects code:
private ApplicationFactory Factory { get; }
public AccountsControllerTests(ITestOutputHelper output)
{
Factory = new ApplicationFactory(output);
}
[Fact]
public async Task ListBalancesTest()
{
var client = Factory.CreateClient(new WebApplicationFactoryClientOptions(){AllowAutoRedirect = false});;
var resp = await client.GetAsync($"api/v1/test/get");
//!!!!!!! resp.StatusCode is HttpStatusCode.NotFound !!!!!!!
var mapgetResp= await client.GetAsync($"/test");
//!!!!!!! mapgetResp is Working !!!!!!!
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
API Code:
[ApiController]
[Route("api/v1/test")]
public class TestController : ControllerBase
{
[HttpGet("get")]
public async Task<ActionResult> Get()
{
return await Task.FromResult(Ok("Response from test"));
}
}
ApplicationFactory Code:
public class ApplicationFactory : WebApplicationFactory<TestStartup>
{
public ApplicationFactory(ITestOutputHelper testOutput = null)
{
_testOutput = testOutput;
}
protected override IWebHostBuilder CreateWebHostBuilder()
{
var builder = WebHost
.CreateDefaultBuilder()
.UseEnvironment("Development")
.UseStartup<TestStartup>()
.UseSerilog();
if (_testOutput != null)
{
builder = builder.ConfigureLogging(logging =>
{
logging.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<ILoggerProvider>(new TestOutputHelperLoggerProvider(_testOutput)));
});
}
return builder;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseContentRoot(".");
builder.ConfigureServices(services =>
{/...../}
}
}
Startup:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapGet("/test", async context =>
{
await context.Response.WriteAsync(#$"Test value from MapGet");
});
/..../
});
Add code in API project in ConfigureWebHost(IWebHostBuilder builder)
builder.UseEnvironment("Test");
builder.ConfigureAppConfiguration((ctx, b) =>
{
ctx.HostingEnvironment.ApplicationName = typeof(Program).Assembly.GetName().Name;
});
builder.ConfigureServices(services =>
{
services.AddMvc()
.AddApplicationPart(typeof(TestStartup).Assembly); //TestStartup is Test project's startup!!
}
And works now thanks!

Middleware works in program.cs, but not when moved to it's own class

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.

Application Builder Map does not work for middlewares

I am encountering the following problem:
I have a ASP NET Core Application where I am using the following routes:
status, message, http.The first 2 accept a websocket request.
The problem is that the AppBuilder.Map in the pipeline does not work and it always sends me to the first route for all requests.
Program
class Program
{
static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)=>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseKestrel()
.UseSockets()
.UseUrls($"http://0.0.0.0:{Constants.SERVER_PORT}/")
.Build();
}
Startup
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<StatusService>();//(x => new StatusService());
services.AddTransient<RegisterService>();
services.AddTransient<MessagingService>();
services.AddCors();
}
public void Configure(IApplicationBuilder builder)
{
builder.UseCors((p) => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());
builder.UseWebSockets();
builder.Map("/status", app =>
{
builder.UseMiddleware<StatusWare>();
});
builder.Map("/http", app =>
{
builder.UseMiddleware<HTTPWare>();
});
builder.Map("/message", app =>
{
builder.UseMiddleware<MessageWare>();
});
}
}
The Middlewares all use their specific service which I will not post since the Invoke method of the other two middlewares does not get invoked.
Middlewares
Status
class StatusWare
{
StatusService handler;
public StatusWare(StatusService _handler,RequestDelegate del)
{
this.handler = _handler;
this.next = del;
}
RequestDelegate next;
public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await this.next(context);
return;
}
await this.handler.AddClientAsync(context.WebSockets);
}
}
Message
class MessageWare
{
private MessagingService messageService;
private RequestDelegate next;
public MessageWare(MessagingService serv,RequestDelegate del)
{
this.messageService = serv;
this.next = del;
}
public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await next(context);
}
await this.messageService.AcceptClientAsync(context.WebSockets);
}
}
HTTP
class HTTPWare
{
RequestDelegate next;
RegisterService registerService;
public HTTPWare(RequestDelegate _del,RegisterService service)
{
this.next = _del;
this.registerService = service;
}
public async Task Invoke(HttpContext context)
{
}
}
As you can see the middlewares are almost identical (I did not write anything in HttpWare since its Invoke method does not get called either.
So my question is .why despite using AppBuilder.Map all requests go into the first middleware StatusWare?
Could it be because of the way the specific services are added in ConfigureServices?
Configure is calling the UseMiddleware on the original builder and not on the delegate argument. In this case app
Update the Map calls to use the middleware on the builder delegate argument.
public void Configure(IApplicationBuilder builder) {
builder.UseCors((p) => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());
builder.UseWebSockets();
builder.Map("/status", app => {
app.UseMiddleware<StatusWare>();
});
builder.Map("/http", app => {
app.UseMiddleware<HTTPWare>();
});
builder.Map("/message", app => {
app.UseMiddleware<MessageWare>();
});
}
Reference ASP.NET Core Middleware

How to auto log every request in .NET Core WebAPI?

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();

Categories