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