/// <summary> /// Bind the named variable(s) to the given value /// </summary> /// <param name="name">The name of the variable to bind.</param> /// <param name="value">The literal value to bind to.</param> /// <param name="iscasesensitive">If set to <c>true</c>, compares are performed case sensitive.</param> /// <param name="asliteral">If set to <c>true</c>, binding is done with literal values.</param> public RouteParser Bind(string name, string value, bool iscasesensitive, bool asliteral) { var lst = BuildList(this.m_root); for (var i = 0; i < lst.Count; i++) { var x = lst[i]; if (x is Variable && ((Variable)x).Name == name) { if (asliteral) { lst[i] = new Literal(value, iscasesensitive, x.Children); } else { lst[i] = new BoundVariable(name, value, iscasesensitive, x.Children); } //break; // If we want a single match } } var rp = new RouteParser(LinkList(lst)); return(rp); }
/// <summary> /// Appends a route to the end of this route /// </summary> /// <param name="suffix">The route to append.</param> /// <returns>The combined route</returns> public RouteParser Append(RouteParser suffix) { var lst = BuildList(this.m_root); lst.RemoveAt(lst.Count - 1); lst.AddRange(BuildList(suffix.m_root)); if (!(lst.Last() is Result)) { throw new InvalidOperationException("Cannot append an entry that does not terminate"); } return(new RouteParser(LinkList(lst))); }
/// <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> /// 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); }
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); }