Example #1
0
            public void Apply(ApplicationModel application)
            {
                var matchedSelectors = application.Controllers.FirstOrDefault(c => c.ControllerType == typeof(T))?.Selectors;

                if (matchedSelectors != null && matchedSelectors.Any())
                {
                    var centralPrefix = new AttributeRouteModel(_routeTemplateProvider);
                    foreach (var selectorModel in matchedSelectors)
                    {
                        selectorModel.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(centralPrefix,
                                                                                                           selectorModel.AttributeRouteModel);
                    }
                }
            }
Example #2
0
        public void ConfigureServices(IServiceCollection services)
        {
            // Register the db context, but do not specify a provider/connection
            // string since these vary by tenant.
            services.AddDbContext <ApplicationDbContext>();

            services.AddIdentity <ApplicationUser, ApplicationRole>()
            .AddEntityFrameworkStores <ApplicationDbContext>()
            .AddDefaultTokenProviders();

            services.Configure <DataProtectionTokenProviderOptions>(options =>
            {
                options.TokenLifespan = TimeSpan.FromDays(1.0);
            });

            services.Configure <IdentityOptions>(options =>
            {
                // Default Password settings.
                options.Password.RequireDigit           = false;
                options.Password.RequireLowercase       = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase       = false;
                options.Password.RequiredLength         = 6;
                options.Password.RequiredUniqueChars    = 1;
            });

            //services.AddControllersWithViews().AddRazorRuntimeCompilation();
            services.AddRazorPages(options =>
            {
                // Since we are using the route multitenant strategy we must add the
                // route parameter to the Pages conventions used by Identity.
                options.Conventions.AddAreaFolderRouteModelConvention("Identity", "/Account", model =>
                {
                    foreach (var selector in model.Selectors)
                    {
                        selector.AttributeRouteModel.Template =
                            AttributeRouteModel.CombineTemplates("{__tenant__}", selector.AttributeRouteModel.Template);
                    }
                });
            });

            services.DecorateService <LinkGenerator, AmbientValueLinkGenerator>(new List <string> {
                "__tenant__"
            });

            services.AddMultiTenant <ApplicationTenantInfo>()
            .WithRouteStrategy()
            .WithConfigurationStore()
            .WithPerTenantAuthentication();
        }
        public ApiPrefixConvention()
        {
            //These are meant to be combined with existing route attributes
            _apiPrefix = new AttributeRouteModel(
                new RouteAttribute("api/"));
            _apiCulturePrefix = new AttributeRouteModel(
                new RouteAttribute("api/{language:regex(^[[a-z]]{{2}}(?:-[[A-Z]]{{2}})?$)}/"));

            //These are meant to be added as routes for api controllers that do not specify any route attribute
            _apiRouteWithController = new AttributeRouteModel(
                new RouteAttribute("api/[controller]"));
            _apiCultureRouteWithController = new AttributeRouteModel(
                new RouteAttribute("api/{language:regex(^[[a-z]]{{2}}(?:-[[A-Z]]{{2}})?$)}/[controller]"));
        }
    /// <summary>
    /// Copy constructor for <see cref="AttributeRoute"/>.
    /// </summary>
    /// <param name="other">The <see cref="AttributeRouteModel"/> to copy.</param>
    public AttributeRouteModel(AttributeRouteModel other)
    {
        if (other == null)
        {
            throw new ArgumentNullException(nameof(other));
        }

        Attribute = other.Attribute;
        Name      = other.Name;
        Order     = other.Order;
        Template  = other.Template;
        SuppressLinkGeneration = other.SuppressLinkGeneration;
        SuppressPathMatching   = other.SuppressPathMatching;
    }
 public void Apply(ControllerModel controller)
 {
     foreach (var selector in controller.Selectors)
     {
         if (selector.AttributeRouteModel != null)
         {
             selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_routePrefix, selector.AttributeRouteModel);
         }
         else
         {
             selector.AttributeRouteModel = _routePrefix;
         }
     }
 }
Example #6
0
 public void Apply(ApplicationModel application)
 {
     foreach (var selector in application.Controllers.Where(c => c.Filters.Any(f => f is IApiBehaviorMetadata)).SelectMany(c => c.Selectors))
     {
         if (selector.AttributeRouteModel != null)
         {
             selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_routePrefix, selector.AttributeRouteModel);
         }
         else
         {
             selector.AttributeRouteModel = _routePrefix;
         }
     }
 }
Example #7
0
 public void Apply(ApplicationModel applicationModel)
 {
     foreach (var controller in applicationModel.Controllers.SelectMany(x => x.Selectors))
     {
         if (controller.AttributeRouteModel != null)
         {
             controller.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_attributeRouteModel, controller.AttributeRouteModel);
         }
         else
         {
             controller.AttributeRouteModel = _attributeRouteModel;
         }
     }
 }
 /// <summary>
 /// Called to apply the convention to the <see cref="T:Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel" />.
 /// </summary>
 /// <param name="application">The <see cref="T:Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel" />.</param>
 public void Apply(ApplicationModel application)
 {
     foreach (var selector in application.Controllers.SelectMany(c => c.Selectors))
     {
         if (selector.AttributeRouteModel != null)
         {
             selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_routePrefix, selector.AttributeRouteModel);
         }
         else
         {
             selector.AttributeRouteModel = _routePrefix;
         }
     }
 }
        public void ConfigureServices(IServiceCollection services)
        {
            // Register the db context, but do not specify a provider/connection
            // string since these vary by tenant.
            services.AddDbContext <ApplicationDbContext>();

            services.AddIdentity <IdentityUser, IdentityRole>()
            .AddDefaultTokenProviders()
            .AddDefaultUI(UIFramework.Bootstrap4)
            .AddEntityFrameworkStores <ApplicationDbContext>();

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
            .AddMvcOptions(options => options.EnableEndpointRouting = false)
            .AddRazorPagesOptions(options =>
            {
                // Since we are using the route multitenant strategy we must add the
                // route parameter to the Pages conventions used by Identity.
                options.Conventions.AddAreaFolderRouteModelConvention("Identity", "/Account", model =>
                {
                    foreach (var selector in model.Selectors)
                    {
                        selector.AttributeRouteModel.Template =
                            AttributeRouteModel.CombineTemplates("{__tenant__}", selector.AttributeRouteModel.Template);
                    }
                });
            });

            services.AddMultiTenant()
            .WithRouteStrategy(ConfigRoutes)
            .WithInMemoryStore(Configuration.GetSection("Finbuckle:MultiTenant:InMemoryStore"))
            .WithPerTenantOptions <CookieAuthenticationOptions>((options, tenantInfo) =>
            {
                // Since we are using the route strategy configure each tenant
                // to have a different cookie name and adjust the paths.
                options.Cookie.Name = $"{tenantInfo.Id}_{options.Cookie.Name}";
                // See below for why this is commented out.
                //options.LoginPath = $"/{tenantInfo.Identifier}/Home/Login";
                //options.LogoutPath = $"/{tenantInfo.Identifier}";
                options.Cookie.Path = $"/{tenantInfo.Identifier}";
            });

            // Required due to a bug in ASP.NET Core Identity (https://github.com/aspnet/Identity/issues/2019)
            services.PostConfigure <CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme, options =>
            {
                // This will result in a path of /_tenant_/Identity/Account/Login
                options.LoginPath  = $"{options.Cookie.Path}{options.LoginPath}";
                options.LogoutPath = $"{options.Cookie.Path}{options.LogoutPath}";
            });
        }
 private void ApplyControllerModel(ControllerModel controllerModel)
 {
     if (typeof(IRemoteService).IsAssignableFrom(controllerModel.ControllerType))
     {
         if (controllerModel.ApiExplorer.IsVisible == null)
         {
             controllerModel.ApiExplorer.IsVisible = true;
         }
         controllerModel.ControllerName = ApplyControllerName(controllerModel.ControllerName);
         foreach (var item in controllerModel.Selectors)
         {
             if (!controllerModel.Attributes.Any(a => a.GetType() == typeof(ApiControllerAttribute)))
             {
                 item.EndpointMetadata.Add(new ApiControllerAttribute());
             }
             if (!controllerModel.Attributes.Any(a => a.GetType() == typeof(ControllerAttribute)))
             {
                 item.EndpointMetadata.Add(new ControllerAttribute());
             }
             if (!controllerModel.Attributes.Any(a => a.GetType() == typeof(ControllerAttribute)))
             {
                 item.EndpointMetadata.Add(new RouteAttribute("[controller]"));
             }
             if (item.AttributeRouteModel == null)
             {
                 item.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute("[controller]"));
             }
             if (!string.IsNullOrEmpty(_options.ServiceNamePrefix))
             {
                 var prefixRoute = new AttributeRouteModel(new RouteAttribute(_options.ServiceNamePrefix));
                 item.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(prefixRoute, item.AttributeRouteModel);
             }
         }
     }
     else if (!string.IsNullOrEmpty(_options.ControllerNamePrefix))
     {
         foreach (var item in controllerModel.Selectors)
         {
             if (item.AttributeRouteModel == null)
             {
                 item.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(_options.ControllerNamePrefix));
             }
             else
             {
                 item.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(new AttributeRouteModel(new RouteAttribute(_options.ControllerNamePrefix)), item.AttributeRouteModel);
             }
         }
     }
 }
Example #11
0
        //接口的Apply方法

        public void Apply(ApplicationModel application)

        {
            //遍历所有的 Controller

            foreach (var controller in application.Controllers)

            {
                // 1、已经标记了 RouteAttribute 的 Controller

                //这一块需要注意,如果在控制器中已经标注有路由了,则会在路由的前面再添加指定的路由内容。



                var matchedSelectors = controller.Selectors.Where(x => x.AttributeRouteModel != null).ToList();

                if (matchedSelectors.Any())

                {
                    foreach (var selectorModel in matchedSelectors)

                    {
                        // 在 当前路由上 再 添加一个 路由前缀

                        selectorModel.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_centralPrefix,

                                                                                                           selectorModel.AttributeRouteModel);
                    }
                }



                //2、 没有标记 RouteAttribute 的 Controller

                var unmatchedSelectors = controller.Selectors.Where(x => x.AttributeRouteModel == null).ToList();

                if (unmatchedSelectors.Any())

                {
                    foreach (var selectorModel in unmatchedSelectors)

                    {
                        // 添加一个 路由前缀

                        selectorModel.AttributeRouteModel = _centralPrefix;
                    }
                }
            }
        }
    public void Combine_RightOverridesReflectedAttributeRouteModel(
        AttributeRouteModel left,
        AttributeRouteModel right)
    {
        // Arrange
        var expectedTemplate = AttributeRouteModel.CombineTemplates(null, right.Template);

        // Act
        var combined = AttributeRouteModel.CombineAttributeRouteModel(left, right);

        // Assert
        Assert.NotNull(combined);
        Assert.Equal(expectedTemplate, combined.Template);
        Assert.Equal(combined.Order, right.Order);
    }
 public void Apply(ApplicationModel application)
 {
     foreach (var controller in application.Controllers)
     {
         var matchedSelectors = controller.Selectors.Where(x => x.AttributeRouteModel != null).ToList();
         if (matchedSelectors.Any())
         {
             foreach (var selectorModel in matchedSelectors)
             {
                 selectorModel.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_routingPrefix,
                                                                                                    selectorModel.AttributeRouteModel);
             }
         }
     }
 }
 /// <summary>
 /// Add an AttributeRouteModel to a SelectorModel list.
 /// It also tries to set the first entry of the list if the AttributeRouteModel is null there.
 /// </summary>
 /// <param name="selectorModels"></param>
 /// <param name="attributeRouteModel"></param>
 public void AddAttributeRouteModel(IList <SelectorModel> selectorModels, AttributeRouteModel attributeRouteModel)
 {
     // Override what seems to be default SelectorModel
     if (selectorModels.Count == 1 && selectorModels[0].AttributeRouteModel == null)
     {
         selectorModels[0].AttributeRouteModel = attributeRouteModel;
     }
     else
     {
         selectorModels.Add(new SelectorModel
         {
             AttributeRouteModel = attributeRouteModel
         });
     }
 }
        public void Apply(ApplicationModel application)
        {
            var globalPrefix = new AttributeRouteModel(new RouteAttribute("api/"));

            application.Controllers?.ToList().ForEach(controller => {
                var routeSelectors = controller.Selectors.Where(x => x.AttributeRouteModel != null);

                //routeSelectors?.ToList().ForEach( sel => Console.WriteLine("sel "+ sel.AttributeRouteModel.Name));
                //Console.WriteLine("---------");
                routeSelectors?.ToList().ForEach(sel =>
                                                 sel.AttributeRouteModel =
                                                     AttributeRouteModel.CombineAttributeRouteModel(globalPrefix, sel.AttributeRouteModel)
                                                 );
            });
        }
Example #16
0
 public void Apply(PageRouteModel model)
 {
     (from SelectorModel in model.Selectors
      select new SelectorModel
     {
         AttributeRouteModel = new AttributeRouteModel
         {
             Order = -1,
             Template = AttributeRouteModel.CombineTemplates("/{culture:required}",
                                                             SelectorModel.AttributeRouteModel.Template)
         }
     })
     .ToList()
     .ForEach(s => model.Selectors.Add(s));
 }
Example #17
0
    /// <summary>
    /// Intializes a new <see cref="SelectorModel"/>.
    /// </summary>
    /// <param name="other">The <see cref="SelectorModel"/> to copy from.</param>
    public SelectorModel(SelectorModel other)
    {
        if (other == null)
        {
            throw new ArgumentNullException(nameof(other));
        }

        ActionConstraints = new List <IActionConstraintMetadata>(other.ActionConstraints);
        EndpointMetadata  = new List <object>(other.EndpointMetadata);

        if (other.AttributeRouteModel != null)
        {
            AttributeRouteModel = new AttributeRouteModel(other.AttributeRouteModel);
        }
    }
Example #18
0
        private ThrottleRoute CreateRoute(AttributeRouteModel controllerTemplate)
        {
            string routeTemplate;

            if (controllerTemplate == null)
            {
                routeTemplate = _actionTemplate.Template;
            }
            else
            {
                routeTemplate = AttributeRouteModel.CombineAttributeRouteModel(controllerTemplate, _actionTemplate).Template;
            }

            return(new NamedThrottleRoute(_httpMethods, routeTemplate, _policyName));
        }
Example #19
0
        public void Apply(PageRouteModel model)
        {
            var selectorCount = model.Selectors.Count;

            for (var i = 0; i < selectorCount; i++)
            {
                var selector = model.Selectors[i];

                model.Selectors.Add(new SelectorModel {
                    AttributeRouteModel = new AttributeRouteModel {
                        Order    = -1,
                        Template = AttributeRouteModel.CombineTemplates("{culture?}", selector.AttributeRouteModel.Template),
                    }
                });
            }
        }
    public void ReplaceTokens_InvalidFormat(string template, Dictionary <string, string> values, string reason)
    {
        // Arrange
        var expected = string.Format(
            CultureInfo.InvariantCulture,
            "The route template '{0}' has invalid syntax. {1}",
            template,
            reason);

        // Act
        var ex = Assert.Throws <InvalidOperationException>(
            () => { AttributeRouteModel.ReplaceTokens(template, values); });

        // Assert
        Assert.Equal(expected, ex.Message);
    }
        private static AttributeRouteInfo CreateAttributeRouteInfo(AttributeRouteModel routeModel)
        {
            if (routeModel == null)
            {
                return(null);
            }

            return(new AttributeRouteInfo
            {
                Template = routeModel.Template,
                Order = routeModel.Order ?? DefaultAttributeRouteOrder,
                Name = routeModel.Name,
                SuppressLinkGeneration = routeModel.SuppressLinkGeneration,
                SuppressPathMatching = routeModel.SuppressPathMatching,
            });
        }
 public void Apply(ApplicationModel application)
 {
     foreach (var item in application.Controllers)
     {
         var matchedSelectors = item.Selectors.Where(p => p.AttributeRouteModel != null).ToList();
         matchedSelectors.ForEach(p =>
         {
             p.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_centralPrefix, p.AttributeRouteModel);
         });
         var unmatchedSeletors = item.Selectors.Where(p => p.AttributeRouteModel == null).ToList();
         unmatchedSeletors.ForEach(p =>
         {
             p.AttributeRouteModel = _centralPrefix;
         });
     }
 }
    public static IPageRouteModelConvention AddFolderRouteParameter(
        this PageConventionCollection conventions,
        string folder,
        string routeParameter)
    {
        return(conventions.AddFolderRouteModelConvention(folder, model =>
        {
            foreach (var s in model.Selectors)
            {
                var templateWithId = AttributeRouteModel
                                     .CombineTemplates(s.AttributeRouteModel.Template, routeParameter);

                s.AttributeRouteModel.Template = templateWithId;
            }
        }));
    }
Example #24
0
        private static void AppendTenantPageRoute(PageRouteModel model)
        {
            var selectorCount = model.Selectors.Count;

            for (var i = 0; i < selectorCount; i++)
            {
                var selector = model.Selectors[i];
                var appended = new SelectorModel(selector);

                var template        = appended.AttributeRouteModel.Template;
                var tenantTemplates = AttributeRouteModel.CombineTemplates("{tenant}", template);

                appended.AttributeRouteModel.Template = tenantTemplates;
                model.Selectors.Add(appended);
            }
        }
Example #25
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();

            // Register the ToDo db context, but do not specify a provider/connection string since
            // these vary by tenant.
            services.AddDbContext <ToDoDbContext>();

            // Configure Identity
            services.AddRazorPages(options =>
            {
                // Since we are using the route multitenant strategy we must add the
                // route parameter to the Pages conventions used by Identity.
                options.Conventions.AddAreaFolderRouteModelConvention("Identity", "/Account", model =>
                {
                    foreach (var selector in model.Selectors)
                    {
                        selector.AttributeRouteModel.Template =
                            AttributeRouteModel.CombineTemplates("{__tenant__}", selector.AttributeRouteModel.Template);
                    }
                });
            });

            // Preserve the tenant route param when new links are generated.
            services.DecorateService <LinkGenerator, AmbientValueLinkGenerator>(new List <string> {
                "__tenant__"
            });
            services.AddDbContext <FinbuckleSqlServerToDoIdentityDbContext>(options =>
                                                                            options.UseSqlite(Configuration.GetConnectionString("FinbuckleSqlServerToDoIdentityDbContextConnection")));

            services.AddDefaultIdentity <IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddEntityFrameworkStores <FinbuckleSqlServerToDoIdentityDbContext>();

            // Register the tenant store db context.
            services.AddDbContext <TenantStoreDbContext>(options =>
            {
                options.UseSqlServer(Configuration.GetConnectionString("TenantStoreConnectionString"));
            });

            // Configure Finbuckle, the store db context does not need to be
            // added separately.
            // Also note this must come after Identity configuration.
            services.AddMultiTenant <TenantInfo>()
            .WithEFCoreStore <TenantStoreDbContext, TenantInfo>()
            .WithRouteStrategy()
            .WithPerTenantAuthentication();
        }
Example #26
0
    public static IEnumerable <(AttributeRouteModel?route, SelectorModel actionSelector, SelectorModel?controllerSelector)> GetAttributeRoutes(ActionModel actionModel)
    {
        var controllerAttributeRoutes = actionModel.Controller.Selectors
                                        .Where(sm => sm.AttributeRouteModel != null)
                                        .Select(sm => sm.AttributeRouteModel)
                                        .ToList();

        foreach (var actionSelectorModel in actionModel.Selectors)
        {
            var actionRouteModel = actionSelectorModel.AttributeRouteModel;

            // We check the action to see if the template allows combination behavior
            // (It doesn't start with / or ~/) so that in the case where we have multiple
            // [Route] attributes on the controller we don't end up creating multiple
            if (actionRouteModel != null && actionRouteModel.IsAbsoluteTemplate)
            {
                var route = AttributeRouteModel.CombineAttributeRouteModel(
                    left: null,
                    right: actionRouteModel);

                yield return(route, actionSelectorModel, null);
            }
            else if (controllerAttributeRoutes.Count > 0)
            {
                for (var i = 0; i < actionModel.Controller.Selectors.Count; i++)
                {
                    // We're using the attribute routes from the controller
                    var controllerSelector = actionModel.Controller.Selectors[i];

                    var route = AttributeRouteModel.CombineAttributeRouteModel(
                        controllerSelector.AttributeRouteModel,
                        actionRouteModel);

                    yield return(route, actionSelectorModel, controllerSelector);
                }
            }

            else
            {
                var route = AttributeRouteModel.CombineAttributeRouteModel(
                    left: null,
                    right: actionRouteModel);

                yield return(route, actionSelectorModel, null);
            }
        }
    }
        public void Apply(ApplicationModel application)
        {
            foreach (var controller in application.Controllers)
            {
                var matchedSelectors = controller.Selectors.Where(x => x.AttributeRouteModel != null).ToList();
                foreach (var selectorModel in matchedSelectors)
                {
                    selectorModel.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_centralPrefix, selectorModel.AttributeRouteModel);
                }

                var unmatchedSelectors = controller.Selectors.Where(x => x.AttributeRouteModel == null).ToList();
                foreach (var selectorModel in unmatchedSelectors)
                {
                    selectorModel.AttributeRouteModel = _centralPrefix;
                }
            }
        }
        public void ConfigureServices(IServiceCollection services)
        {
            // Register the db context, but do not specify a provider/connection
            // string since these vary by tenant.
            services.AddDbContext <ApplicationDbContext>();

            services.AddDefaultIdentity <MultiTenantIdentityUser>()
            .AddEntityFrameworkStores <ApplicationDbContext>();

            services.AddAuthentication()
            .AddGoogle("Google", options =>
            {
                // These configuration settings should be set via user-secrets or environment variables!
                options.ClientId              = Configuration.GetValue <string>("GoogleClientId");
                options.ClientSecret          = Configuration.GetValue <string>("GoogleClientSecret");
                options.AuthorizationEndpoint = string.Concat(options.AuthorizationEndpoint, "?prompt=consent");
            });

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
            .AddRazorPagesOptions(options =>
            {
                // Since we are using the route multitenant strategy we must add the
                // route parameter to the Pages conventions used by Identity.
                options.Conventions.AddAreaFolderRouteModelConvention("Identity", "/Account", model =>
                {
                    foreach (var selector in model.Selectors)
                    {
                        selector.AttributeRouteModel.Template =
                            AttributeRouteModel.CombineTemplates("{__tenant__}", selector.AttributeRouteModel.Template);
                    }
                });
            });

            services.AddMultiTenant()
            .WithRouteStrategy(ConfigRoutes)
            .WithInMemoryStore(Configuration.GetSection("Finbuckle:MultiTenant:InMemoryStore"))
            .WithPerTenantOptions <CookieAuthenticationOptions>((options, tenantInfo) =>
            {
                // Since we are using the route strategy configure each tenant
                // to have a different cookie name and adjust the paths.
                options.Cookie.Name = $"{tenantInfo.Id}_{options.Cookie.Name}";
                options.LoginPath   = $"/{tenantInfo.Identifier}/Home/Login";
                options.LogoutPath  = $"/{tenantInfo.Identifier}";
                options.Cookie.Path = $"/{tenantInfo.Identifier}";
            });
        }
    public void Combine_SetsSuppressPathGenerationToTrue_IfEitherIsTrue(bool leftSuppress, bool rightSuppress)
    {
        // Arrange
        var left = new AttributeRouteModel
        {
            Template             = "Template",
            SuppressPathMatching = leftSuppress,
        };
        var right = new AttributeRouteModel
        {
            SuppressPathMatching = rightSuppress,
        };
        var combined = AttributeRouteModel.CombineAttributeRouteModel(left, right);

        // Assert
        Assert.True(combined.SuppressPathMatching);
    }
    /// <inheritdoc />
    public void Apply(ApplicationModel application)
    {
        foreach (var controller in application.Controllers)
        {
            var matched = controller.Selectors.Where(x => x.AttributeRouteModel != null);
            foreach (var model in matched)
            {
                model.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(prefix, model.AttributeRouteModel);
            }

            var unmatched = controller.Selectors.Where(x => x.AttributeRouteModel == null);
            foreach (var model in unmatched)
            {
                model.AttributeRouteModel = prefix;
            }
        }
    }