1

I am trying to follow this guide to create a logical separation in my MVC app between the API portion and the MVC portion:

https://www.strathweb.com/2017/04/running-multiple-independent-asp-net-core-pipelines-side-by-side-in-the-same-application/

Here is my implementation of the extension method he is using:

    public static IApplicationBuilder UseBranchWithServices(this IApplicationBuilder app,
        PathString path, Action<IServiceCollection> servicesConfiguration,
        Action<IApplicationBuilder> appBuilderConfiguration)
    {
        var webhost = new WebHostBuilder()
            .UseKestrel()
            .ConfigureServices(servicesConfiguration)
            .UseStartup<StartupHarness>()
            .Build()
  /* ojO */ .CreateOrMigrateDatabase(); /* Ojo */

        var serviceProvider = webhost.Services;
        var featureCollection = webhost.ServerFeatures;
        var appFactory = serviceProvider.GetRequiredService<IApplicationBuilderFactory>();
        var branchBuilder = appFactory.CreateBuilder(featureCollection);
        var factory = serviceProvider.GetRequiredService<IServiceScopeFactory>();

        branchBuilder.Use(async (context, next) => {
            using (var scope = factory.CreateScope()) {
                context.RequestServices = scope.ServiceProvider;
                await next();
            }
        });

        appBuilderConfiguration(branchBuilder);

        var branchDelegate = branchBuilder.Build();

        return app.Map(path, builder => { builder.Use(async (context, next) => {
            await branchDelegate(context);
          });
        });
    }

When I try to use this and the SignInManager together, HttpContext is always null.

Here's a ridiculously simplified version of my AccountController:

    public AccountController(SignInManager<AppUser> mgr) {
        _mgr = mgr;
    }

    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            var result = await _mgr.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
        }
    }

I am calling app.UseAuthentication() in the ApplicationBuilder; for purposes of brevity, I have that setup as an extension method. This works:

    public void ConfigureServices(IServiceCollection services)
    {
        services.ConfigureMvcServices(Configuration);
    }

    public void Configure(IApplicationBuilder builder, IHostingEnvironment env)
    {
        builder.ConfigureMvcBuilder(env);
    }

This does not:

    public void Configure(IApplicationBuilder builder, IHostingEnvironment env)
    {
        builder.UseBranchWithServices(StaticConfiguration.RootPath,
            services =>
            {
                services.ConfigureMvcServices(Configuration); 
            },
            app => { app.ConfigureMvcBuilder(env); }
        );
        builder
            .Run(async c =>
                await c.Response.WriteAsync("This should work"));
    }

In the latter example, the services object ends up with a Count of 335 descriptors, in the former (working) example it has 356.

I tried looking over this blog to see if I could figure out what is going on, but I don't see a strong correlation between what's in the method and what Gordon is describing. So I'm totally lost. Any help would be appreciated. If there's a way to split up the extension method and pass in the services for each pipeline, that would be fine with me.

Yes I know you only see one pipeline here. The point is to add more.

Chaim Eliyah
  • 2,743
  • 4
  • 24
  • 37

1 Answers1

0

Here's what I ended up with:

This was the wrong way to solve this problem. I needed one properly configured pipeline. I combined my API and Web projects into a single project (though that probably doesn't matter, as long as everything is registered).

(For other reasons, I added async. Nothing to do with it.)

Program.cs:

        public static async Task Main(string[] args)
        {
            IWebHost webhost = CreateWebHostBuilder(args).Build();

            await webhost.CreateOrMigrateDatabase();

            await webhost.RunAsync();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            new WebHostBuilder()
                .ConfigureAppConfiguration((host, config) =>
                {
                    config.SetBasePath(Directory.GetCurrentDirectory());
                    config.AddJsonFile("appsettings.json");

                    if (host.HostingEnvironment.IsDevelopment())
                        config.AddUserSecrets<Startup>();
                    else
                        config.AddEnvironmentVariables(prefix: "MYAPP_");
                })
                .UseKestrel()
                .ConfigureLogging((app, logging) =>
                {
//snip
                })
                .UseStartup<Startup>()
                .UseUrls("http://*:4213");
    }

Startup.cs:

        public void ConfigureServices(IServiceCollection services)
        {
            var connString = Configuration.GetConnectionString("default");

            services.ConfigureJwt(_signingKey, Configuration);

            // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-2.2#configure-localization
            services.AddLocalization(options => options.ResourcesPath = StartupConfiguration.ResourcesFolder);

            services.AddDbContext<AppDbContext>(builder =>
            {
                builder.UseLazyLoadingProxies()
                       .UseSqlServer(connString, with => with.MigrationsAssembly("App.Data"));
            });

            services.ConfigureAuthentication(Configuration);

            var mapperConfig = new MapperConfiguration(maps =>
            {
                maps.ConfigureMvc();
                maps.ConfigureApi();
            });
            var mapper = mapperConfig.CreateMapper();
            services.AddSingleton(mapper);

            // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-2.2#configure-localization
            services.AddMvc() // (config => { config.Filters.Add(new AuthorizeFilter()); }) // <~ for JWT auth
                .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
                .AddDataAnnotationsLocalization()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            services.AddSpaStaticFiles(angularApp =>
            {
                angularApp.RootPath = "dist"; // TODO
            }); 
//snip           
}

I split the maps (IMapper, unrelated; they are just extension methods to configure the ViewModels for the two namespaces) but I did not split the pipelines. One properly configured pipeline, with the static website in {folder}/dist. Runs both the API and the MVC project. And it's a lot less to manage.

The full code, for the interested, is here.

Chaim Eliyah
  • 2,743
  • 4
  • 24
  • 37
  • Hi can you help me with my question https://stackoverflow.com/questions/59505803/merge-two-net-core-apis-in-one-application – Ali Jan 02 '20 at 11:08