Aspnetcore: .NET core 3 route failure with separate projects

Created on 12 Oct 2019  路  30Comments  路  Source: dotnet/aspnetcore

Describe the bug

Unable to use routing in when creating host and using controllers from separate projects. When using everything from one project, routes work fine.

To Reproduce

Steps to reproduce the behavior:

  • See reproducible project with instructions in comment down below.

Expected behavior

Routes, views, razor, etc. should work regardless of which project the host builder, views and controllers are in (as they do in .NET core 2.2).

area-mvc question

All 30 comments

Can you provide a running project?

Working on it

For some reason, .net core 2 tests can find controllers in other assemblies. This seems to have broken in .net core 3. For now my hacky work-around is this (new code added for assembly part that was not needed in .net core 2):

IMvcBuilder mvcBuilder = services.AddMvc((options) =>
{
    options.EnableEndpointRouting = true;
});
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
    try
    {
        if (assembly.GetTypes().Any(t => t.IsSubclassOf(typeof(Controller))))
        {
            mvcBuilder.AddApplicationPart(assembly);
        }
    }
    catch
    {
        // bugs in unit test framework throw exceptions for weird assemblies like intellitrace
    }
}
mvcBuilder.SetCompatibilityVersion(CompatibilityVersion.Latest).AddJsonOptions(options =>
{
    options.JsonSerializerOptions.IgnoreNullValues = true;
});

With this solution unfortunately now my razor views cannot find any dlls, including system etc, even with preserve compilation context set in the csproj...

Still waiting for a repro.

Still trying to strip my very large project down into the smallest reproducible case, Will post back if I can get it.

I can reproduce it on my machine here regularly but not able as of yet to upload to a repo. Is it possible to do a screen share?

Did you try to reproduce it in a new project?

Yes, so far it can find the controller in a different assembly but I have not tried razor yet, just raw json. Copying the code over to my project and suddenly I get 404 errors with the same controller. Very bizzare.

public static async Task Main(string[] args)
{
    using (var host = CreateHostBuilder(args).Build())
    {
        await host.RunAsync();
    }
}

public static IHostBuilder CreateHostBuilder(params string[] args)
{
    return Host.CreateDefaultBuilder(args).ConfigureLogging((logBuilder) =>
    {
        logBuilder.AddConsole().SetMinimumLevel(LogLevel.Trace);
    })
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.Configure((ctx, app) =>
        {
            if (ctx.HostingEnvironment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseAuthorization();
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapControllers();
                endpoints.MapControllerRoute("default", "/{action}/{id?}", new { action = "Index", controller = "Home" });
            });
        })
        .ConfigureServices((serviceCollection) =>
        {
            serviceCollection.AddAuthorization();
            serviceCollection.AddRazorPages();
        })
        .ConfigureAppConfiguration((ctx, configBuilder) =>
        {
            configBuilder.Build();
        });
    });
}

In my project, I hit same controller Index method, get this errors:

info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
      Request starting HTTP/1.1 GET http://localhost:5000/
dbug: Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware[0]
      Wildcard detected, all requests with hosts will be allowed.
trce: Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware[2]
      All hosts are allowed.
dbug: Microsoft.AspNetCore.Mvc.Razor.Compilation.DefaultViewCompiler[4]
      Initializing Razor view compiler with no compiled views.
dbug: Microsoft.AspNetCore.Routing.Matching.DfaMatcher[1000]
      No candidates found for the request path '/'
dbug: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[2]
      Request did not match any endpoints
dbug: Microsoft.AspNetCore.Server.Kestrel[9]
      Connection id "0HLQFJ9TFKQ6A" completed keep alive response.
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
      Request finished in 27.218ms 404

I have a repro project. It is attached. Nice and small.

Download --> NetCore3RouteTestFail.zip

Instructions:

  • Extract the zip file to somewhere on your machine. Please use VS 2019 with latest .NET core 3.0 SDK.
  • Load NetCore3RouteTestFail.sln
  • Note the controller is in a separate project (Controllers)
  • Set NetCore3RouteTestFail.csproj as start up project
  • Run and visit http://localhost:5000/WeatherForecast
  • You should see json
  • Stop the running console app, leave browser open. Now uncomment line 21 and comment out line 22 in Program.cs in the NetCore3RouteTestFail project.
  • Run again. Refresh the browser using ctrl-F5 to bypass any cache. Notice the 404 error.
  • The method to build and run the host has been copied exactly into a separate project (HostCreator) using line 21 in Program.cs.
  • The unit test project has the same symtom. You can run tests and they pass. Then uncomment line 22 and comment out line 23 in UnitTest1.cs (RouteTestFail project) and run tests again. They fail with a 404.
  • Please note that I follow the same pattern (putting host builder in separate project, controllers in yet another project) in .NET core 2.2 and it works fine in both tests and applications, only fails in .NET core 3.0.
  • I can get the routes to work if I add AddApplicationPart and pass the assembly the controller is in, but this is only needed when the host builder code is not in the startup project. Weird. But the problem with this is, then any razor views do not get assembly references, even with preserve compilation context on.

@davidfowl

Try add __UseSetting__ on _CreateHostBuilder_ function.
CreateHostBuilder

On _Program.Main_ we pass __AssemblyName__.
ProgramMain

And to make easy lunch the app, you can modify __launchSettings.json__ (in _Properties_ folder on project _NetCore3RouteTestFail_).
launchSettings_json

@jjxtra Thanks for contacting us and thanks for the repro.

I've looked at the project you sent us and I believe the reason you are not finding the controllers when using HostCreator is because it doesn't have a reference to the Controllers project.

The way discovery works in MVC is thought metadata stamped into the assembly at build time. When you try to start the host it looks for an assembly with the application name to find the assembly level attributes that tell it which assemblies to use for discovering controllers and other MVC primitives.

@soeleman answer will probably help here, as setting the application name should fix this issue. That said, if you are doing this for testing purposes I would highly recommend using https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.0 instead.

Let us know if that fixes your issue.

Maybe this is related or not but if one tries to put a Page into an RCL the page cannot be routed too with the default startup.cs. Not sure if that is supported or not.

@chassq I don't think your issue is related.

If you think you've found an issue open a separate issue describing your scenario so that we can track it.

So has this changed from net core 2.2? I had the same pattern in 2.2 and it worked fine, no extra stuff needed.

@jjxtra This might have been a change in hosting as a result of the move to generic host.

@Tratcher Did we change something in how we set the app name from 2.2 to 3.0 with regards to UseStartup?

Not intentionally, but there may be a subtle ordering difference or similar.

I don't know if it helps, but the legacy web host builder also has an identical problem in net core 3. It's possible that it and the new host builder share the same common code now.

Thanks for contacting us. We believe that the question you've raised have been answered. If you still feel a need to continue the discussion, feel free to reopen it and add your comments.

I will attempt the suggested fix this evening and post back

This worked for the regular project, unfortunately the unit test project still fails. I am unable to re-open this issue.

I don't see any good documentation on this usesetting with this key, it feels a little bit hacky. Trying it in my other big project also did not resolve the 404 error so I am still concerned that something is not working quite right in .net core 3.

@mkArtakMSFT

I see this link: https://github.com/dotnet/core/blob/master/release-notes/3.0/preview/3.0.0-preview-known-issues.md

I believe the issue "Referencing 3.0.0 MVC libraries don't work as intended: There are several issues with referencing a 3.0.0 MVC library" is not completely resolved, just from what I am seeing...

I can add application part for all assembly with typeof(Controller) and that allows api controllers to be found, but anything with razor still gives this, even with these project settings:

IMvcBuilder mvcBuilder = services.AddRazorPages().AddRazorRuntimeCompilation(options =>
{

}).AddMvcOptions((options) => options.EnableEndpointRouting = true);
foreach (Assembly a in IPBanExtensionMethods.GetAssembliesWithType(typeof(Controller)))
{
    mvcBuilder.AddApplicationPart(a);
}
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<CopyRefAssembliesToPublishDirectory>true</CopyRefAssembliesToPublishDirectory>
<PreserveCompilationReferences>true</PreserveCompilationReferences>
<PreserveCompilationContext>true</PreserveCompilationContext>

image

I鈥檒l take another look

@jjxtra With the repro you provided I did the following:

  • Add a project reference from HostCreator to controllers
  • Change the test as follows:
    csharp var host = HostCreator.HostBuilder.CreateHostBuilder().Build(); // broken //var host = NetCore3RouteTestFail.Program.CreateHostBuilder().Build(); // works
  • With that, starting up host creator (you need to add the line below) I can get to the API without problems.

    • CreateHostBuilder(args).Build().Start(); (This needs to be added to program main).

    • image

The other issue that you are seeing is likely caused by using a different app name, as it results in loading the wrong dependencycontext which is what razor uses to perform recompilation.

My original recommendation still stands, if you are doing this for testing purposes use WebApplicationFactory as it handles all these concerns.

I'm closing this issue as there's no more action to be taken here.

I was able to hack around the issue by doing the following. Posting it here for anyone else who comes across this issue in .NET core 3. I really hope it saves people the headache I went through.

You have to capture the web host environment before the service provider is built. You can do this in ConfigureAppConfiguration, .i.e.:

builder.ConfigureAppConfiguration((hostingContext, configurationBuilder) =>
{
    this.WebHostEnvironment = hostingContext.HostingEnvironment;
    // rest of your configuration code if any
});

Then, in ConfigureServices, set the application name, i.e.

webBuilder.ConfigureServices((ctx, serviceCollection) =>
{
    this.WebHostEnvironment.ApplicationName = Assembly.GetEntryAssembly().GetName().Name;
    // rest of your services configuration code if any
});

Unit tests unfortunately are still broken. Something does not wire up right when running under testhost and razor views cannot find any assemblies, despite using the appropriate csproj properties like preserve compilation context. I am still looking into a hack/workaround for that.

@jjxtra , I'm trying to understand your resolution and trying to apply it to #15046 to be able to find a fix, but I still don't see how to relate them. Could you please give a bit more insights on how you was able to solve the issue?

I can confirm the .UseSetting(WebHostDefaults.ApplicationKey, typeof(Startup).Assembly.GetName().Name) works like a charm.

Sample code:

WebHost.CreateDefaultBuilder()
                .UseStartup<TestStartup>()
                .UseSetting(WebHostDefaults.ApplicationKey, typeof(Startup).Assembly.GetName().Name)
                .Build()

The only issue remaining still is that razor does not work in unit tests, something to do with testhost I think, but not sure...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

markrendle picture markrendle  路  3Comments

guardrex picture guardrex  路  3Comments

TanvirArjel picture TanvirArjel  路  3Comments

ipinak picture ipinak  路  3Comments

UweKeim picture UweKeim  路  3Comments