Adding middleware in Carter framework makes some URL to not trigger

356 Views Asked by At

I have successfully added some custom logging to my request using Carter in C#.

this is what my startup.cs code looks like :

    public void Configure(IApplicationBuilder app,IHostingEnvironment env) {
        app.UseCarter(this.RequestId())
        app.UseCarter(this.Log())
    }

    public CarterOptions Log(){
          return new CarterOptions(ctx => this.BeforeLog(ctx), ctx => AfterLog(ctx))
    }

    public CarterOptions RequestId(){
          return new CarterOptions(ctx => this.AddRequestId(ctx))
    }

    private Task<bool> AddRequestId() {
        ctx.Request.Header["x-request-id"] = Guid.NewGuid().ToString()
    }

The problem is if I comment app.UseCarter(this.RequestId()) then all the requests are successfully logged both going in and out.

But If I uncomment it, only the request which fail are logged.

Can someone clarify if I am doing something wrong here?

1

There are 1 best solutions below

7
On

The multiple options calls are causing conflicts with creating the middleware.

consider refactoring to a more explicit statement that clarifies what it is you are actually trying to do.

public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
    var options = new CarterOptions(
        before: ctx => {
            ctx.Request.Header["x-request-id"] = Guid.NewGuid().ToString();
            return this.BeforeLog(ctx);
        },
        after: ctx => AfterLog(ctx)
    );

    app.UseCarter(options);

    //...
}

Based on conversation in comments

... but I was hoping I could give a bunch of tasks which could be called in succession for each request. Your solution is good, but I could have just stuck that line inside of BeforeLog() and it would have worked too. But BeforeLog should only log, and AddRequestId should add the x-request-id and further task should focus in one thing. I could certainly stick everything together in one huge method but that does not seem appropriate.

What you describe is like having a middleware like feature for the middleware's options.

It is doable but it wont be pretty.

I thought about it and came up with the following builder for the CarterOptions class

sealed class CarterOptionsBuilder {
    delegate Task<bool> BeforeDelegate(HttpContext context);
    delegate Task AfterDelegate(HttpContext context);

    private readonly Stack<Func<BeforeDelegate, BeforeDelegate>> befores = new Stack<Func<BeforeDelegate, BeforeDelegate>>();
    private readonly Stack<Func<AfterDelegate, AfterDelegate>> afters = new Stack<Func<AfterDelegate, AfterDelegate>>();

    public CarterOptionsBuilder HandleBefore(Func<HttpContext, Task<bool>> before) {
        befores.Push(next => async context => {
            return await before(context) && await next(context);
        });
        return this;
    }

    public CarterOptionsBuilder HandleAfter(Func<HttpContext, Task> after) {
        afters.Push(next => context => {
            after(context);
            return next(context);
        });
        return this;
    }

    public CarterOptions Build() {
        var before = new BeforeDelegate(c => Task.FromResult(true));
        while (befores.Any()) {
            var current = befores.Pop();
            before = current(before);
        }
        var after = new AfterDelegate(c => Task.CompletedTask);
        while (afters.Any()) {
            var current = afters.Pop();
            after = current(after);
        }
        return new CarterOptions(before.Invoke, after.Invoke);
    }
}

After some unit tests to confirm that it behaves as expected, it can be used like the following based on your original code

public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
     CarterOptions options = new CarterOptionsBuilder()
        .HandleBefore(this.AddRequestId)
        .HandleBefore(this.BeforeLog)
        .HandleAfter(this.AfterLog)
        .Build();

    app.UseCarter(options);
}

private Task AfterLog(HttpContext arg) {
    //...
    return Task.CompletedTask;
}

private Task<bool> BeforeLog(HttpContext arg) {
    //...
    return Task.FromResult(true);
}

private Task<bool> AddRequestId(HttpContext ctx) {
    ctx.Request.Header["x-request-id"] = Guid.NewGuid().ToString();
    return Task.FromResult(true);
}