Search code examples
dependency-injectionasp.net-coreasp.net-core-mvcasp.net-mvc-routingentity-framework-core

Custom Route Constraint with Dependency Injection in .Net Core


I'm trying to implement a custom route constraint that would check the database for information. I've gotten the route constraint to work but I'm running into parameterless constructor issues when trying to inject a DbContext into the class to work with the database. Here's my code:

My custom Route Constraint:

    public class DynamicPageRouteConstraint : IRouteConstraint
    {
        public const string ROUTE_LABEL = "dbDynamicRoute";
        private ApplicationDbContext db;

        public DynamicPageRouteConstraint(ApplicationDbContext _db)
        {
            db = _db;
        }

        public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
        {
            List<string> items = db.DynamicPages.Select(p => p.Url).ToList();

            if (items.Contains(values["dynamicPageFriendlyURL"]))
            {
                return true;
            }

            return false;
        }
    }

Startup.cs:

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(ConnectionString));

        services.Configure<RouteOptions>(options =>
        {
            options.ConstraintMap.Add(DynamicPageRouteConstraint.ROUTE_LABEL, typeof(DynamicPageRouteConstraint));
        });

        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        ...

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "DynamicPageRoutes",
                template: "{*dynamicPageFriendlyURL:" + DynamicPageRouteConstraint.ROUTE_LABEL + "}",
                defaults: new { controller = "DynamicPage", action = "Loader" },
                constraints: new { dynamicPageFriendlyURL = new DynamicPageRouteConstraint(app.ApplicationServices.GetRequiredService<ApplicationDbContext>()) });

            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }

My problems are thus:

The code as it stands immediately complains of missing a parameterless constructor as soon as it encounters my route in startup.cs.

If I put a parameterless constructor in my RouteConstraint implementation, I can get the code to work, but when hitting the route, it gets hit twice, once with my dependency injected and then again using the parameterless constructor, which means no db object instantiated.

I don't want to forego injecting the dbContext by instantiating a new dbcontext object as I would need to specify a connection string and that would throw a large wrench in a dev/production connection string setup we have implemented.

My questions:

Why is a parameterless constructor needed?

Why would my route be called twice? (once with injected ctr, and once with parameterless ctr)

Is there a better way to do dependency injection into an implementation of IRouteConstraint?

Is there a better way to be doing this at all? I'm attempting to provide custom URLs for pages stored in a database.

Any help is appreciated! Thanks!


Solution

  • I think you are applying the constraint twice in your MapRoute, once in the template parameter (where the system will create an instance of your constraint class with no constructor) and then in the constraints parameter. I think your answer would be to change your template to just

    template: "{*dynamicPageFriendlyUrl}"
    

    Then the system won't attempt to apply the constraint at the template level.