2

In the OWIN pipeline, i use a branch to configure a custom authentication middleware. How to return to pipeline root after branch executing?

app.Use<AuthenticationMiddleware1>();
app.Map("/branch", (application) => {
    application.Use<AuthenticationMiddleware2>();
});
app.UseWebApi(new HttpConfiguration());

When i request http://server/branch then web api is not configured and return 404

I tried to write a MapAndContinueMiddleware:

public class MapAndContinueMiddleware:OwinMiddleware
{
    public MapAndContinueMiddleware(OwinMiddleware next, MapOptions options) : base(next)
    {
        this.Options = options;
    }

    public MapOptions Options { get; }


    public async override Task Invoke(IOwinContext context)
    {
        if(context.Request.Path.StartsWithSegments(this.Options.PathMatch))
        {
            await this.Options.Branch(context).ContinueWith((previousTask) =>
            {
                this.Next.Invoke(context);
            });
        }
        else
        {
            await this.Next.Invoke(context);
        }
    }
}

with this extension :

public static IAppBuilder MapAndContinue(this IAppBuilder app, string pathMatch, Action<IAppBuilder> configuration)
{
    // create branch and assign to options
    IAppBuilder branch = app.New();
    configuration(branch);

    MapOptions options = new MapOptions {
        PathMatch = new PathString(pathMatch),
        Branch = (Func<IOwinContext, Task>)branch.Build(typeof(Func<IOwinContext, Task>))
    };
    return MapAndContinue(app, options);
}

public static IAppBuilder MapAndContinue(this IAppBuilder app, MapOptions options)
{
    return app.Use<MapAndContinueMiddleware>(options);
}

But this has a strange behaviour : a web api request run the branch twice and doesn't return to client...!?

Troopers
  • 5,127
  • 1
  • 37
  • 64
  • Did you base you code on the original `MapMiddleware`. I'm reviewing the source and it looks similar – Nkosi Oct 03 '18 at 17:00
  • Also what version are you using as the provided code in your example has syntax errors for `MapOptions.Branch` – Nkosi Oct 03 '18 at 17:09

1 Answers1

3

Have you tried continuing the pipeline as configured after the branch

var config = new HttpConfiguration();
app.Use<AuthenticationMiddleware1>();
app.Map("/branch", (application) => {
    application.Use<AuthenticationMiddleware2>();
    application.UseWebApi(config);
});
app.UseWebApi(config);

That way after the branch it will still be able to use the Web API

Reviewing the Original MapExtension Source it would seem that the order of when you add the middleware to the pipeline is important

Review the following refactor to use your custom map middleware

using AppFunc = Func<IDictionary<string, object>, Task>;

//...

public static class BranchAndMergeExtensions {

    public static IAppBuilder MapAndContinue(this IAppBuilder app, string pathMatch, Action<IAppBuilder> configuration) {
        return MapAndContinue(app, new PathString(pathMatch), configuration);
    }

    public static IAppBuilder MapAndContinue(this IAppBuilder app, PathString pathMatch, Action<IAppBuilder> configuration) {
        if (app == null) {
            throw new ArgumentNullException("app");
        }
        if (configuration == null) {
            throw new ArgumentNullException("configuration");
        }
        if (pathMatch.HasValue && pathMatch.Value.EndsWith("/", StringComparison.Ordinal)) {
            throw new ArgumentException("Path must not end with slash '/'", "pathMatch");
        }

        // put middleware in pipeline before creating branch
        var options = new MapOptions { PathMatch = pathMatch };
        var result = app.Use<MapAndContinueMiddleware>(options);

        // create branch and assign to options
        IAppBuilder branch = app.New();
        configuration(branch);
        options.Branch = (AppFunc)branch.Build(typeof(AppFunc));

        return result;
    }
}

The original MapMiddleware also needed to be refactored to stop it from short-circuiting the pipeline by letting the root pipeline to be invoked after the branch.

public class MapAndContinueMiddleware : OwinMiddleware {
    private readonly MapOptions options;

    public MapAndContinueMiddleware(OwinMiddleware next, MapOptions options)
        : base(next) {
        this.options = options;
    }

    public async override Task Invoke(IOwinContext context) {
        PathString path = context.Request.Path;
        PathString remainingPath;
        if (path.StartsWithSegments(options.PathMatch, out remainingPath)) {
            // Update the path
            PathString pathBase = context.Request.PathBase;
            context.Request.PathBase = pathBase + options.PathMatch;
            context.Request.Path = remainingPath;

            //call branch delegate
            await this.options.Branch(context.Environment);

            context.Request.PathBase = pathBase;
            context.Request.Path = path;
        }
        // call next delegate
        await this.Next.Invoke(context);
    }
}

Which finally results in your original setup example becoming

var config = new HttpConfiguration();
app.Use<AuthenticationMiddleware1>();
app.MapAndContinue("/branch", (application) => {
    application.Use<AuthenticationMiddleware2>();
});
app.UseWebApi(config);
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • yes of course but the configuration is more complicated than this exemple and i wish leave the code in separate files `Startup.Authenitication` and `Startup.WebApi` – Troopers Oct 03 '18 at 15:11
  • @Troopers If it is more complicated then I suggest showing a more complicated example that is more closely align with your actual problem. Current suggestions will be made based on the currently provided examples. – Nkosi Oct 03 '18 at 15:14
  • I cannot display all my code here, but in the idea replace app.UseWebApi with several middlewares. – Troopers Oct 03 '18 at 15:18
  • I understand not being able to show all your code, but a [mcve] is required. I could easily say wrap those other middleware in an extension method and make the same call in the branch. You see what I mean? – Nkosi Oct 03 '18 at 15:19
  • Ok. but i want also understand why my MapAndContinueMiddleware doesn't work! – Troopers Oct 03 '18 at 15:21
  • Because it is creating its own path/branch and you are also mixing async and continuewith – Nkosi Oct 03 '18 at 16:12
  • I used the branch as `Func` instead of `Func, Task>` to pass the owin context and not only the environment. Of course i created a new MapOptions class with this. I'm not sure that this change can be the origine of my problem. I just tried your code and it fails with _Operation is not valid due to the current state of the object_ on `await this.Next.Invoke(context);` after the branch executing – Troopers Oct 04 '18 at 07:24
  • @Troopers https://stackoverflow.com/questions/8832470/operation-is-not-valid-due-to-the-current-state-of-the-object-error-during-pos – Nkosi Oct 04 '18 at 10:01
  • @Troopers also remember that `IOwinContext` - `This wraps OWIN environment dictionary and provides strongly typed accessors`. so makes not much of a difference if you use either – Nkosi Oct 04 '18 at 10:03