Sub-Middleware like options builder for an Asp.Net Core Middleware

Building upon a recent question I answered here

Simple middleware pipeline builder (similar to asp.net-core)

I came across a cross site question

Stackoverflow: Adding middleware in Carter framework makes some URL to not trigger

which looked like it could benefit from what I had learned from the previous question.

It is related to a GitHub library called Carter

Carter is a library that allows Nancy-esque routing for use with ASP.Net Core.

The OP was hoping to be able to add tasks to the expected delegate for the middleware's CarterOptions class.

/// <summary>
/// Initializes <see cref="CarterOptions"/>
/// </summary>
/// <param name="before">A global before handler which is invoked before all routes</param>
/// <param name="after">A global after handler which is invoked after all routes</param>
/// <param name="openApiOptions">A <see cref="OpenApiOptions"/> instance to configure OpenApi</param>
public CarterOptions(Func<HttpContext, Task<bool>> before = null, Func<HttpContext, Task> after = null, OpenApiOptions openApiOptions = null) {
//...
}


This is the before code where the options is created directly.

public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
var options = new CarterOptions(
before: ctx => {
return this.BeforeLog(ctx);
},
after: ctx => AfterLog(ctx)
);

app.UseCarter(options);

//...
}


I came up with the following builder for the middleware's options class

sealed class CarterOptionsBuilder {

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

befores.Push(next => async context => {
return await handler(context) && await next(context);
});
return this;
}

afters.Push(next => context => {
handler(context);
return next(context);
});
return this;
}

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


Which allowed for functionality like

public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
CarterOptions options = new CarterOptionsBuilder()
.Build();

app.UseCarter(options);
}

//...
}

//...
}

}


This does open extensibility for more customization like

public static CarterOptionsBuilder AddLog(this CarterOptionsBuilder builder) {
return builder
}
private static Task AfterLog(HttpContext arg) {
//...
}

private static Task<bool> BeforeLog(HttpContext arg) {
//...
}


Might consider contributing it as a pull-request to the repository.

Is it even worth the trouble?

Thoughts and opinions on the design, given the original feature request.

• Cool, inspired by my question ;-) I think HandleAfter is incorrect. after(context); should be placed below return next(context);... if this means after-a-request. – t3chb0t Aug 19 '19 at 18:00
• @t3chb0t in this case the delegate(s) is not after a request per say, but a desired action/task that is to be perform after a request. – Nkosi Aug 19 '19 at 18:02
• ok, I guess then I need to take a look at that framework first as these concepts are new to me. – t3chb0t Aug 19 '19 at 18:04
• @t3chb0t it is technically two pipelines. one is invoked before request, the other after. But each pipeline acts like a normal pipeline. – Nkosi Aug 19 '19 at 18:05

I still don't undestand that framework but there are few minor things that I think could be improved anyway:

• AddBeforeHook & AddAfterHook could actually use the BeforeDelegate & AfterDelegate respectively instead of Funcs.
• I would rename handler to before & after since the third one is already called next. I find this way it would be clearer what they handle.
• I think you should be able to rewrite the whiles in the Build method with an Aggregate like:

before = befores.Aggregate(before, (current, next) => next(current));


or even

var before = befores.Aggregate(new BeforeDelegate(c => Task.FromResult(true)), (current, next) => next(current));


Elements are enumerated in the Pop order.

• Like the aggregates idea. – Nkosi Aug 22 '19 at 18:36
• Had them named previously before and after, but thought they caused confusion which to me was evident in your comment. – Nkosi Aug 22 '19 at 18:38
• @Nkosi my comment was a sign of a lack of knowledge about how this framework works. I think to someone who knows the ins and outs of it, make before and after probably more sense than just handler. – t3chb0t Aug 22 '19 at 18:46