Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Consolidating aspnetcore items in Startup, without duplicating service registrations or pipelines

I'm trying to consolidate all of the junk that I'm generating in a project that's turning my Startup class into a dumping ground. However, it's not clear that the clean-up and consolidation can coexist with other libraries that attempt to do the same thing. For example, other libraries may need to call these in ConfigureServices():

services.Configure<AuthorizationOptions>(options =>
{
    options.AddSomePolicies();
});
services.AddMvc(options =>
{
    options.Filters.Add(typeof(MyAttribute));
});
services.AddSignalR().AddMyCustomBackplane();

I wouldn't likely have the only library needing to add some auth policies, or filters or activate SignalR. I can't prove to myself that wrapping all of these in a services.MyUltimateExtension() doesn't lead to a mess.

Similarly, I can't easily encapsulate these in the Configure() method to my own library method:

app.UseSignalR(routes =>
{
    routes.MapHub<MyHub>("/MyHub");
});
app.UseMvc(routes =>
{
    routes.AddMyRoutes(app);
}

To me this sets up multiple MVC pipelines and maybe duplicates SignalR infrastructure.

What do I do? I'm concerned about readability for other developers.

like image 958
Jeff Putz Avatar asked Jan 21 '26 02:01

Jeff Putz


1 Answers

That got me wondering, so I had a look at the way the UseMvc and AddMvc work, and I can say that it's safe to call them multiple times (with caveats).

UseMvc

UseMvc does two things: register the MVC services, and set up the wanted MVC options.

When registering the services, it calls the IServiceCollection.TryAdd method, which doesn't register a service that's already registered. That means all MVC services are registered only once.

For the options, it calls the IServiceCollection.Configure method. The final options is built by calling each configuring action successively. For example:

Calling:

services.Configure<MvcOptions>(options =>
{
    options.EnableEndpointRouting = true;
});

services.Configure<MvcOptions>(options =>
{
    options.EnableEndpointRouting = false;
    options.MaxValidationDepth = 3;
});

is equivalent to:

services.Configure<MvcOptions>(options =>
{
    options.EnableEndpointRouting = true;

    // the last options win
    options.EnableEndpointRouting = false;
    options.MaxValidationDepth = 3;
});

As you can see, the calling order is important, because it can override some previously set options. It's safe if there's no overlap.

AddMvc

AddMvc does one thing: add a new instance of the MVC routing middleware each time that method is called.

Conceptually, that means that calling:

app.UseMvc(routes =>
{
    routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "blog",
        template: "Blog/{**article}",
        defaults: new { controller = "Blog", action = "ReadArticle" });
});

is equivalent to calling:

app.UseMvc(routes =>
{
    routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
    routes.MapRoute(
        name: "blog",
        template: "Blog/{**article}",
        defaults: new { controller = "Blog", action = "ReadArticle" });
});

Note that if there's an overlap between the routing, the MVC framework will reject the query with an AmbiguousMatchException.

Trying to access /Blog/this-is/a/test will fail:

app.UseMvc(routes =>
{
    routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");

    routes.MapRoute(
        name: "blog",
        template: "Blog/{**article}",
        defaults: new { controller = "Blog", action = "ReadArticleOne" });
});

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "blog",
        template: "Blog/{**article}",
        defaults: new { controller = "Blog", action = "ReadArticleTwo" });
});

So, what should you do? What I did when my Startup.cs was getting crowded was to set up a number of extension methods grouped by intent and split up in multiple classes, without worrying about having multiple calls to UseMvc or AddMvc. The only thing you have to keep in mind is the calling order.

like image 94
Métoule Avatar answered Jan 22 '26 15:01

Métoule



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!