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 => {
ctx.Request.Header["x-request-id"] = Guid.NewGuid().ToString();
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 {
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 AddBeforeHook(Func<HttpContext, Task<bool>> handler) {
befores.Push(next => async context => {
return await handler(context) && await next(context);
});
return this;
}
public CarterOptionsBuilder AddAfterHook(Func<HttpContext, Task> handler) {
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);
}
var after = new AfterDelegate(c => Task.CompletedTask);
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()
.AddBeforeHook(this.AddRequestId)
.AddBeforeHook(this.BeforeLog)
.AddAfterHook(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);
}
This does open extensibility for more customization like
public static CarterOptionsBuilder AddLog(this CarterOptionsBuilder builder) {
return builder
.AddBeforeHook(this.BeforeLog)
.AddAfterHook(this.AfterLog);
}
private static Task AfterLog(HttpContext arg) {
//...
return Task.CompletedTask;
}
private static Task<bool> BeforeLog(HttpContext arg) {
//...
return Task.FromResult(true);
}
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.
1 Answer 1
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 theBeforeDelegate
&AfterDelegate
respectively instead ofFunc
s.- I would rename
handler
tobefore
&after
since the third one is already callednext
. I find this way it would be clearer what they handle. I think you should be able to rewrite the
while
s in theBuild
method with anAggregate
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.
-
\$\begingroup\$ Like the aggregates idea. \$\endgroup\$Nkosi– Nkosi2019年08月22日 18:36:43 +00:00Commented Aug 22, 2019 at 18:36
-
\$\begingroup\$ Had them named previously before and after, but thought they caused confusion which to me was evident in your comment. \$\endgroup\$Nkosi– Nkosi2019年08月22日 18:38:09 +00:00Commented Aug 22, 2019 at 18:38
-
\$\begingroup\$ @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
andafter
probably more sense than justhandler
. \$\endgroup\$t3chb0t– t3chb0t2019年08月22日 18:46:24 +00:00Commented Aug 22, 2019 at 18:46
HandleAfter
is incorrect.after(context);
should be placed belowreturn next(context);
... if this means after-a-request. \$\endgroup\$