/// <summary> /// Initializes a new instance of the <see cref="T:Ceen.Mvc.ControllerRouter"/> class. /// </summary> /// <param name="config">The configuration to use</param> /// <param name="types">The types to use, must all derive from <see cref="T:Ceen.Mvc.Controller"/>.</param> public ControllerRouter(ControllerRouterConfig config, IEnumerable <Type> types) { if (types == null) { throw new ArgumentNullException($"{types}"); } if (config == null) { throw new ArgumentNullException($"{config}"); } // Make sure the caller cannot edit the config afterwards m_config = config.Clone(); var variables = new RouteParser(m_config.Template, !m_config.CaseSensitive, null).Variables; if (variables.Count(x => x.Key == m_config.ControllerGroupName) != 1) { throw new ArgumentException($"The template must contain exactly 1 named group called {m_config.ControllerGroupName}"); } if (variables.Count(x => x.Key == m_config.ActionGroupName) != 1) { throw new ArgumentException($"The template must contain exactly 1 named group called {m_config.ActionGroupName}"); } types = types.Where(x => x != null).Distinct().ToArray(); if (types.Count() == 0) { throw new ArgumentException($"No controller entries to load from \"{types}\""); } var wrong = types.FirstOrDefault(x => !typeof(Controller).IsAssignableFrom(x)); if (wrong != null) { throw new ArgumentException($"The type \"{wrong.FullName}\" does not derive from {typeof(Controller).FullName}"); } wrong = types.FirstOrDefault(x => x.IsAbstract || x.IsGenericTypeDefinition); if (wrong != null) { throw new ArgumentException($"The type \"{wrong.FullName}\" cannot be instantiated"); } m_routeparser = BuildParse(types.Select(x => (Controller)Activator.CreateInstance(x)).ToArray(), m_config); }
/// <summary> /// Gets the name of an item /// </summary> /// <returns>The item name.</returns> /// <param name="self">The type to get the name for.</param> /// <param name="config">The configuration to use</param> public static string GetItemName(this MethodInfo self, ControllerRouterConfig config) { var nameattr = self.GetCustomAttributes(typeof(NameAttribute), false).Cast <NameAttribute>().FirstOrDefault(); // Extract an assigned name if (nameattr != null) { return(nameattr.Name); } var name = self.Name; if (config.LowerCaseNames) { name = name.ToLowerInvariant(); } return(name); }
/// <summary> /// Initializes a new instance of the <see cref="T:Ceen.Mvc.ControllerRouter"/> class. /// </summary> /// <param name="config">The configuration to use</param> /// <param name="instances">The instances to use.</param> public ControllerRouter(ControllerRouterConfig config, IEnumerable <Ceen.Mvc.Controller> instances) { if (instances == null || instances.Any(x => x == null)) { throw new ArgumentNullException($"{instances}"); } if (config == null) { throw new ArgumentNullException($"{config}"); } // Make sure the caller cannot edit the config afterwards m_config = config.Clone(); var variables = new RouteParser(m_config.Template, !m_config.CaseSensitive, null).Variables; if (variables.Count(x => x.Key == m_config.ControllerGroupName) != 1) { throw new ArgumentException($"The template must contain exactly 1 named group called {m_config.ControllerGroupName}"); } if (variables.Count(x => x.Key == m_config.ActionGroupName) != 1) { throw new ArgumentException($"The template must contain exactly 1 named group called {m_config.ActionGroupName}"); } if (instances.Count() == 0) { throw new ArgumentException($"No instances were added as routes", nameof(instances)); } var duplicates = instances.GroupBy(x => x.GetType()).Where(x => x.Count() > 1); if (duplicates.Any()) { throw new ArgumentException($"The type \"{duplicates.First().Key}\" has {duplicates.First().Count()} instances"); } m_routeparser = BuildParse(instances, m_config); }
private static RouteParser BuildParse(IEnumerable <Controller> controllers, ControllerRouterConfig config) { var controller_routes = controllers.SelectMany(x => { string name; var nameattr = x.GetType().GetCustomAttributes(typeof(NameAttribute), false).Cast <NameAttribute>().FirstOrDefault(); // Extract controller name if (nameattr != null) { name = nameattr.Name; } else { name = x.GetType().Name; if (config.ControllerSuffixRemovals != null) { foreach (var rm in config.ControllerSuffixRemovals) { while (!string.IsNullOrWhiteSpace(rm) && name.EndsWith(rm, StringComparison.InvariantCultureIgnoreCase)) { name = name.Substring(0, name.Length - rm.Length); } } } if (config.LowerCaseNames) { name = name.ToLowerInvariant(); } } var routes = x.GetType().GetCustomAttributes(typeof(RouteAttribute), false).Cast <RouteAttribute>().Select(y => y.Route); // Add default route, if there are no route attributes if (routes.Count() == 0) { routes = new[] { string.Empty } } ; return(routes.Distinct().Select(y => new { Controller = x, ControllerRoute = y })); } ).ToArray(); var interface_expanded_routes = controller_routes.SelectMany(x => { var interfaces = x.Controller.GetType().GetParentInterfaces <IControllerPrefix>(); var interfacenames = interfaces .Select(y => x.Controller.GetType().GetParentInterfaceSequence(y).Reverse().Where(z => z != x.Controller.GetType() && z != typeof(IControllerPrefix))) .Select(y => string.Join("/", y.Select(z => z.GetItemName(config)))); if (interfacenames.Count() == 0) { interfacenames = new[] { string.Empty } } ; return(interfacenames.Distinct().Select(y => new { Controller = x.Controller, ControllerRoute = x.ControllerRoute, InterfacePath = (y ?? string.Empty) })); } ).ToArray(); var target_method_routes = interface_expanded_routes.SelectMany(x => { return (x.Controller.GetType() .GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(y => y.ReturnType == typeof(void) || typeof(IResult).IsAssignableFrom(y.ReturnType) || typeof(Task).IsAssignableFrom(y.ReturnType)) .Select(y => new { Controller = x.Controller, ControllerRoute = x.ControllerRoute, InterfacePath = x.InterfacePath, Method = y })); } ).ToArray(); var target_routes = target_method_routes.SelectMany(x => { var routes = x.Method.GetCustomAttributes(typeof(RouteAttribute), false).Cast <RouteAttribute>().Select(y => y.Route); // Add default route, if there are no route attributes if (routes.Count() == 0) { routes = new[] { string.Empty } } ; return(routes.Select(y => new { Controller = x.Controller, ControllerRoute = x.ControllerRoute, InterfacePath = x.InterfacePath, Method = x.Method, MethodRoute = y })); } ).ToArray(); // Now we have the cartesian product of all route/controller/action pairs, then build the target strings var tmp = new RouteParser(config.Template, !config.CaseSensitive, null); var defaultcontrollername = tmp.GetDefaultValue(config.ControllerGroupName); var defaultactionname = tmp.GetDefaultValue(config.ActionGroupName); var target_strings = target_routes.SelectMany(x => { var methodverbs = x.Method.GetCustomAttributes(typeof(HttpVerbFilterAttribute), false).Cast <HttpVerbFilterAttribute>().Select(b => b.Verb.ToUpperInvariant()); var entry = new RouteEntry(x.Controller, x.Method, methodverbs.ToArray(), null); var path = new RouteParser("/", !config.CaseSensitive, entry); if (!string.IsNullOrWhiteSpace(x.InterfacePath)) { if (x.InterfacePath.StartsWith("/", StringComparison.Ordinal)) { path = new RouteParser(x.InterfacePath, !config.CaseSensitive, entry); } else { path = path.Append(x.InterfacePath, !config.CaseSensitive, entry); } } var ct = string.IsNullOrWhiteSpace(x.ControllerRoute) ? config.Template : x.ControllerRoute; if (!string.IsNullOrWhiteSpace(ct)) { if (ct.StartsWith("/", StringComparison.Ordinal)) { path = new RouteParser(ct, !config.CaseSensitive, entry); } else { if (!path.Path.EndsWith("/", StringComparison.Ordinal)) { ct = "/" + ct; } path = path.Append(ct, !config.CaseSensitive, entry); } } var mt = x.MethodRoute; if (!string.IsNullOrWhiteSpace(mt)) { if (mt.StartsWith("/", StringComparison.Ordinal)) { path = new RouteParser(mt, !config.CaseSensitive, entry); } else { path = path.Bind(config.ActionGroupName, string.Empty, !config.CaseSensitive, true); if (!path.Path.EndsWith("/", StringComparison.Ordinal)) { mt = "/" + mt; } path = path.Append(mt, !config.CaseSensitive, entry); } } var controllername = x.Controller.GetType().GetItemName(config); var actionname = x.Method.GetItemName(config); var controllernames = new List <string>(); var actionnames = new List <string>(); if (!config.HideDefaultController || controllername != defaultcontrollername) { controllernames.Add(controllername); } if (!config.HideDefaultAction || actionname != defaultactionname) { actionnames.Add(actionname); } if (!string.IsNullOrEmpty(defaultcontrollername) && controllername == defaultcontrollername) { controllernames.Add(string.Empty); } if (!string.IsNullOrEmpty(defaultactionname) && actionname == defaultactionname) { actionnames.Add(string.Empty); } // Cartesian product with controller and action names bound return(controllernames .SelectMany(y => actionnames.Select( z => path .Bind(config.ControllerGroupName, y, !config.CaseSensitive, true) .Bind(config.ActionGroupName, z, !config.CaseSensitive, true) .PrunePath() .ReplaceTarget(x.Controller, x.Method, methodverbs.ToArray()) ) )); } ) .Distinct(x => x.ToString()) .ToArray(); var merged = RouteParser.Merge(target_strings); if (config.Debug) { Console.WriteLine("All target paths:"); foreach (var x in target_strings) { Console.WriteLine(x); } Console.WriteLine("Map structure:"); Console.WriteLine(merged.ToString()); } return(merged); }
/// <summary> /// Initializes a new instance of the <see cref="T:Ceen.Mvc.ControllerRouter"/> class. /// </summary> /// <param name="config">The configuration to use</param> /// <param name="types">The types to use, must all derive from <see cref="T:Ceen.Mvc.Controller"/>.</param> public ControllerRouter(ControllerRouterConfig config, params Type[] types) : this(config, (types ?? new Type[0]).AsEnumerable()) { }
/// <summary> /// Initializes a new instance of the <see cref="T:Ceen.Mvc.ControllerRouter"/> class. /// </summary> /// <param name="config">The configuration to use</param> /// <param name="assemblies">The assemblies to scan for controllers.</param> public ControllerRouter(ControllerRouterConfig config, IEnumerable <Assembly> assemblies) : this(config, assemblies.SelectMany(x => x.GetTypes()).Where(x => x != null && typeof(Controller).IsAssignableFrom(x))) { }
/// <summary> /// Initializes a new instance of the <see cref="T:Ceen.Mvc.ControllerRouter"/> class. /// </summary> /// <param name="config">The configuration to use</param> /// <param name="assembly">The assembly to scan for controllers.</param> public ControllerRouter(ControllerRouterConfig config, Assembly assembly) : this(config, assembly == null ? new Assembly[0] : new [] { assembly }) { }
/// <summary> /// Gets the name of an item /// </summary> /// <returns>The item name.</returns> /// <param name="self">The type to get the name for.</param> /// <param name="config">The configuration to use</param> public static string GetItemName(this Type self, ControllerRouterConfig config) { var nameattr = self.GetCustomAttributes(typeof(NameAttribute), false).Cast <NameAttribute>().FirstOrDefault(); // Extract an assigned name if (nameattr != null) { return(nameattr.Name); } var name = self.Name; if (typeof(Controller).IsAssignableFrom(self) && self.IsClass) { if (config.ControllerSuffixRemovals != null) { foreach (var rm in config.ControllerSuffixRemovals) { while (!string.IsNullOrWhiteSpace(rm) && name.EndsWith(rm, StringComparison.InvariantCultureIgnoreCase)) { name = name.Substring(0, name.Length - rm.Length); } } } if (config.ControllerPrefixRemovals != null) { foreach (var rm in config.ControllerPrefixRemovals) { while (!string.IsNullOrWhiteSpace(rm) && name.StartsWith(rm, StringComparison.InvariantCultureIgnoreCase)) { name = name.Substring(rm.Length); } } } } else if (typeof(IControllerPrefix).IsAssignableFrom(self) && self.IsInterface) { if (config.InterfaceSuffixRemovals != null) { foreach (var rm in config.InterfaceSuffixRemovals) { while (!string.IsNullOrWhiteSpace(rm) && name.EndsWith(rm, StringComparison.InvariantCultureIgnoreCase)) { name = name.Substring(0, name.Length - rm.Length); } } } if (config.InterfacePrefixRemovals != null) { foreach (var rm in config.InterfacePrefixRemovals) { while (!string.IsNullOrWhiteSpace(rm) && name.StartsWith(rm, StringComparison.InvariantCultureIgnoreCase)) { name = name.Substring(rm.Length); } } } } if (config.LowerCaseNames) { name = name.ToLowerInvariant(); } return(name); }
/// <summary> /// Initializes a new instance of the <see cref="T:Ceen.Mvc.ControllerRouter"/> class. /// </summary> /// <param name="config">The configuration to use</param> /// <param name="assemblies">The assemblies to scan for controllers.</param> public ControllerRouter(ControllerRouterConfig config, IEnumerable <Assembly> assemblies) : this(config, assemblies.SelectMany(x => x.GetTypes()).Where(x => x != null && typeof(Controller).IsAssignableFrom(x) && !x.IsAbstract && !x.IsGenericTypeDefinition)) { }
private static RouteParser BuildParse(IEnumerable <Controller> controllers, ControllerRouterConfig config) { var target_routes = ParseControllers(controllers, config) // Attach custom controller routes .Concat( controllers.OfType <ManualRoutingController>().SelectMany(x => x.Routes) ) .ToArray(); // Now we have the cartesian product of all route/controller/action pairs, then build the target strings var tmp = new RouteParser(config.Template, !config.CaseSensitive, null); var defaultcontrollername = tmp.GetDefaultValue(config.ControllerGroupName); var defaultactionname = tmp.GetDefaultValue(config.ActionGroupName); var target_strings = target_routes.SelectMany(x => { // Provide a hook point for the dynamic routing option if (x.Controller is IIDynamicConfiguredController idc) { x = idc.PatchRoute(x); } var entry = new RouteEntry(x.Controller, x.Method, x.Verbs, null); var path = new RouteParser("/", !config.CaseSensitive, entry); if (!string.IsNullOrWhiteSpace(x.InterfacePath)) { if (x.InterfacePath.StartsWith("/", StringComparison.Ordinal)) { path = new RouteParser(x.InterfacePath, !config.CaseSensitive, entry); } else { path = path.Append(x.InterfacePath, !config.CaseSensitive, entry); } } var ct = string.IsNullOrWhiteSpace(x.ControllerPath) ? config.Template : x.ControllerPath; if (!string.IsNullOrWhiteSpace(ct)) { if (ct.StartsWith("/", StringComparison.Ordinal)) { path = new RouteParser(ct, !config.CaseSensitive, entry); } else { if (!path.Path.EndsWith("/", StringComparison.Ordinal)) { ct = "/" + ct; } path = path.Append(ct, !config.CaseSensitive, entry); } } var mt = x.MethodPath; if (!string.IsNullOrWhiteSpace(mt)) { if (mt.StartsWith("/", StringComparison.Ordinal)) { path = new RouteParser(mt, !config.CaseSensitive, entry); } else { path = path.Bind(config.ActionGroupName, string.Empty, !config.CaseSensitive, true); if (!path.Path.EndsWith("/", StringComparison.Ordinal)) { mt = "/" + mt; } path = path.Append(mt, !config.CaseSensitive, entry); } } var controllername = x.Controller.GetType().GetItemName(config); var actionname = x.Method.GetItemName(config); var controllernames = new List <string>(); var actionnames = new List <string>(); if (!config.HideDefaultController || controllername != defaultcontrollername) { controllernames.Add(controllername); } if (!config.HideDefaultAction || actionname != defaultactionname) { actionnames.Add(actionname); } if (!string.IsNullOrEmpty(defaultcontrollername) && controllername == defaultcontrollername) { controllernames.Add(string.Empty); } if (!string.IsNullOrEmpty(defaultactionname) && actionname == defaultactionname) { actionnames.Add(string.Empty); } // Cartesian product with controller and action names bound return(controllernames .SelectMany(y => actionnames.Select( z => path .Bind(config.ControllerGroupName, y, !config.CaseSensitive, true) .Bind(config.ActionGroupName, z, !config.CaseSensitive, true) .PrunePath() .ReplaceTarget(x.Controller, x.Method, x.Verbs) ) )); } ) .Distinct(x => x.ToString()) .ToArray(); var merged = RouteParser.Merge(target_strings); if (config.Debug) { Console.WriteLine("All target paths:"); foreach (var x in target_strings) { Console.WriteLine(x); } Console.WriteLine("Map structure:"); Console.WriteLine(merged.ToString()); } return(merged); }
/// <summary> /// Creates partial routes for all controllers in the sequence /// </summary> /// <param name="controllers">The controllers to create partial routes for</param> /// <param name="config">The configuration to use</param> /// <returns>The partial routes</returns> public static PartialParsedRoute[] ParseControllers(IEnumerable <Controller> controllers, ControllerRouterConfig config) { var controller_routes = controllers .Where(x => !(x is ManualRoutingController)) .SelectMany(x => { string name; var nameattr = x.GetType().GetCustomAttributes(typeof(NameAttribute), false).Cast <NameAttribute>().FirstOrDefault(); // Extract controller name if (nameattr != null) { name = nameattr.Name; } else { name = x.GetType().Name; if (config.ControllerSuffixRemovals != null) { foreach (var rm in config.ControllerSuffixRemovals) { while (!string.IsNullOrWhiteSpace(rm) && name.EndsWith(rm, StringComparison.InvariantCultureIgnoreCase)) { name = name.Substring(0, name.Length - rm.Length); } } } if (config.LowerCaseNames) { name = name.ToLowerInvariant(); } } var routes = x.GetType().GetCustomAttributes(typeof(RouteAttribute), false).Cast <RouteAttribute>().Select(y => y.Route); // Add default route, if there are no route attributes if (routes.Count() == 0) { routes = new[] { string.Empty } } ; return(routes.Distinct().Select(y => new { Controller = x, ControllerRoute = y })); } ).ToArray(); var interface_expanded_routes = controller_routes.SelectMany(x => { var interfaces = x.Controller.GetType().GetParentInterfaces <IControllerPrefix>(); var interfacenames = interfaces .Select(y => x.Controller.GetType().GetParentInterfaceSequence(y).Reverse().Where(z => z != x.Controller.GetType() && z != typeof(IControllerPrefix))) .Select(y => string.Join("/", y.Select(z => z.GetItemName(config)))); if (interfacenames.Count() == 0) { interfacenames = new[] { string.Empty } } ; return(interfacenames.Distinct().Select(y => new { Controller = x.Controller, ControllerRoute = x.ControllerRoute, InterfacePath = (y ?? string.Empty) })); } ).ToArray(); var target_method_routes = interface_expanded_routes.SelectMany(x => { return (x.Controller.GetType() .GetMethods(BindingFlags.Public | BindingFlags.Instance) // Only target the methods that return something useful .Where(y => y.ReturnType == typeof(void) || typeof(IResult).IsAssignableFrom(y.ReturnType) || typeof(Task).IsAssignableFrom(y.ReturnType) ) // Remove properties and others .Where(y => !y.IsSpecialName) .Select(y => new { Controller = x.Controller, ControllerRoute = x.ControllerRoute, InterfacePath = x.InterfacePath, Method = y })); } ).ToArray(); return (target_method_routes.SelectMany(x => { var routes = x.Method.GetCustomAttributes(typeof(RouteAttribute), false).Cast <RouteAttribute>().Select(y => y.Route); // Add default route, if there are no route attributes if (routes.Count() == 0) { routes = new[] { string.Empty } } ; // Get any accepted verbs var verbs = x.Method.GetCustomAttributes(typeof(HttpVerbFilterAttribute), false).Cast <HttpVerbFilterAttribute>().Select(b => b.Verb.ToUpperInvariant()).ToArray(); return routes.Select(y => new PartialParsedRoute() { Controller = x.Controller, ControllerPath = x.ControllerRoute, InterfacePath = x.InterfacePath, Method = x.Method, MethodPath = y, Verbs = verbs }); } ) .ToArray()); }
/// <summary> /// Initializes a new instance of the <see cref="T:Ceen.Mvc.ControllerRouter"/> class. /// </summary> /// <param name="config">The configuration to use</param> /// <param name="instances">The instances to use.</param> public ControllerRouter(ControllerRouterConfig config, params Controller[] instances) : this(config, (instances ?? new Controller[0]).AsEnumerable()) { }