private static void SetupRoutesUsingTestClasses() { RouteTableBuilder.ClearRoutes(); var allTestControllerActions = SitkaController.GetAllControllerActionMethods(typeof(MyAbstractBaseController)); RouteTableBuilder.Build(allTestControllerActions, null, new Dictionary <string, string>()); }
public void TestRouteBuilder() { List <MethodInfo> methods = SitkaController.FindControllerActions(typeof(RouteTableBuilderTestController)); Assert.That(methods.Count, Is.EqualTo(6)); List <SitkaRouteTableEntry> routeEntries = RouteTableBuilder.SetupRouteTableImpl(methods); Assert.That(routeEntries.Count, Is.EqualTo(11 + GetDefaultRouteCount())); }
public void CanFindActionsInheritedFromBaseClass() { var controllerActionMethods = SitkaController.FindControllerActions(typeof(MyTest1Controller)); var methodNames = controllerActionMethods.Select(x => String.Format("{0}.{1}", x.ReflectedType.Name, x.Name)).ToList(); var expected = new[] { "MyTest1Controller.MyAction1", "MyTest1Controller.ActionWithNoParameters", "MyTest1Controller.ActionWithOneParameter", "MyTest1Controller.BaseAction" }; Assert.That(methodNames, Is.EquivalentTo(expected), "Should find both base declared actions and derived actions"); var abstractBaseControllerActionMethods = SitkaController.FindControllerActions(typeof(MyAbstractBaseController)); Assert.That(abstractBaseControllerActionMethods, Is.Empty, "Should not find any in abstract base classes - they don't count for routes"); }
public void RoutesShouldNotCollide() { List <MethodInfo> allTestControllerActions = SitkaController.FindControllerActions(typeof(MyAbstractBaseController)); List <SitkaRouteTableEntry> routeEntries = RouteTableBuilder.SetupRouteTableImpl(allTestControllerActions); Assert.That(routeEntries.All(re => re != null)); List <IGrouping <string, SitkaRouteTableEntry> > duplicateRouteNames = routeEntries.ToLookup(x => x.RouteName).Where(grp => grp.Count() > 1).ToList(); Assert.That(duplicateRouteNames, Is.Empty, "All route names should be unique but there are duplicates"); List <IGrouping <string, SitkaRouteTableEntry> > duplicateRouteUrls = routeEntries.ToLookup(x => x.RouteUrl).Where(grp => grp.Count() > 1).ToList(); Assert.That(duplicateRouteUrls, Is.Empty, "All route urls should be unique but there are duplicates"); }
private static void TestRouteParameterOptionality(int expectedCountOfRoutes, Type type, bool shouldThrow) { List <MethodInfo> methods = SitkaController.FindControllerActions(type); Assert.That(methods, Is.Not.Empty, string.Format("Test Precondition: the test type {0} should have a controller action on it", type)); if (shouldThrow) { Assert.Throws <PreconditionException>(() => RouteTableBuilder.SetupRouteTableImpl(methods), string.Format("An illegal route was allowed into the route table! {0}", type.Name)); } else { var routeEntries = new List <SitkaRouteTableEntry>(); Assert.DoesNotThrow(() => routeEntries = RouteTableBuilder.SetupRouteTableImpl(methods), string.Format("A legal route was not allowed into the route table! {0}", type.Name)); Assert.That(routeEntries.Count, Is.EqualTo(expectedCountOfRoutes), string.Format("Route count is not what was expected. {0}", type.Name)); } }
/// <summary> /// Builds a URL based on the Expression passed in /// </summary> /// <typeparam name="TController">Controller Type Only</typeparam> /// <param name="context">The current ViewContext</param> /// <param name="routeCollection">The <see cref="RouteCollection"/> to use for building the URL.</param> /// <param name="action">The action to invoke</param> /// <returns></returns> public static string BuildUrlFromExpression <TController>(RequestContext context, RouteCollection routeCollection, Expression <Action <TController> > action) where TController : Controller { // 4/4/2016 RL: We could not simply just use RouteValueDictionary routeValues = Microsoft.Web.Mvc.Internal.ExpressionHelper.GetRouteValuesFromExpression(action) because it is not Area aware; // so we had to get the guts of that method call and do it ourself to make it respect areas var body = action.Body as MethodCallExpression; Check.RequireNotNull(body, "MvcResources.ExpressionHelper_MustBeMethodCall"); // ReSharper disable PossibleNullReferenceException var actionName = GetTargetActionName(body.Method); // ReSharper restore PossibleNullReferenceException var controllerType = typeof(TController); var controllerName = SitkaController.ControllerTypeToControllerName(controllerType); var routeValues = GetRouteValuesFromExpression(body, controllerType.Namespace, controllerName, actionName); var vpd = routeCollection.GetVirtualPath(context, routeValues); if (vpd == null) { return(null); } var routeUrl = vpd.VirtualPath;// Check if we are using DomainRoutes; really, Armstrong is the only one that does not use this. // Corral/LTInfo should be going this route if (routeCollection.Any(x => x is DomainRoute)) { var route = routeCollection.Where(x => x is DomainRoute).Cast <DomainRoute>() .FirstOrDefault( entry => entry.SitkaRouteTableEntry.Namespace == controllerType.Namespace && entry.SitkaRouteTableEntry.Controller == controllerName && entry.SitkaRouteTableEntry.Action == actionName); Check.Require(route != null, $"Could not build a Url for Namespace \"{controllerType.Namespace}\", Controller \"{controllerName}\", Action \"{actionName}\" because no matching route was found"); if (!route.SitkaRouteTableEntry.IsCrossAreaRoute && !string.IsNullOrWhiteSpace(route.Domain)) { return($"https://{route.Domain}{routeUrl}"); } } return(routeUrl); }
private static IEnumerable <SitkaRouteTableEntry> MakeRoutesForSingleMethod(Dictionary <string, string> areasDictionary, MethodInfo controllerActionMethod, Dictionary <string, List <string> > getMethodsDict) { var sitkaRouteEntries = new List <SitkaRouteTableEntry>(); var controller = SitkaController.ControllerTypeToControllerName(controllerActionMethod.ReflectedType); var controllerPartForUrl = SitkaController.ControllerTypeToControllerNameForUrl(controllerActionMethod.ReflectedType); var isRestrictedToPost = IsRestrictedToPost(controllerActionMethod); var httpPostRouteSuffix = (isRestrictedToPost ? "__HttpPost" : ""); var action = controllerActionMethod.Name; var routeName = string.Format("{0}__{1}{2}", controller, action, httpPostRouteSuffix); var allParameters = controllerActionMethod.GetParameters(); var controllerAndAction = string.Format("{0}.{1}", controller, action); var parameterString = String.Join(",", allParameters.Select(x => string.Format("{0} {1}", x.ParameterType.FullName, x.Name))); var actionControllerNameWithParameters = string.Format("{0}\t{1}\t{2}", controller, action, parameterString); if (!isRestrictedToPost) { AssertOptionalParametersAreOnlyAtEndOfRoute(routeName, allParameters); if (!getMethodsDict.ContainsKey(controllerAndAction)) { getMethodsDict.Add(controllerAndAction, new List <string>()); } getMethodsDict[controllerAndAction].Add(actionControllerNameWithParameters); } else { if (getMethodsDict.ContainsKey(controllerAndAction)) { if (!getMethodsDict[controllerAndAction].Any(actionControllerNameWithParameters.StartsWith)) { var allGetRoutes = String.Join("\r\n", getMethodsDict[controllerAndAction]); throw new ApplicationException(string.Format("The POST route {0} must have a corresponding GET route with the same parameters\r\nPOST route: {1}\r\nGET routes: {2}", controllerAndAction, actionControllerNameWithParameters, allGetRoutes)); } } } // see if it has a route attribute defined; if so, use that as the route url var routeAttribute = controllerActionMethod.GetCustomAttributes <RouteAttribute>(false).SingleOrDefault(); // Now add in all the variants of the route based on parameter overloading // /Foo.mvc/Action/1 => FooController.Action(1) var prependParameters = allParameters.Where(p => p.CustomAttributes.Any(ca => ca.AttributeType.IsAssignableFrom(typeof(PlaceUrlParameterBeforeControllerAndActionName)))).OrderBy(p => p.Position).ToList(); var appendParameters = allParameters.Except(prependParameters).OrderBy(p => p.Position).ToList(); if (!prependParameters.Any()) { var crossAreaRouteAttribute = controllerActionMethod.GetCustomAttributes <CrossAreaRouteAttribute>(false).SingleOrDefault(); var isCrossAreaRoute = crossAreaRouteAttribute != null; if (areasDictionary == null || !areasDictionary.Any()) { CreateSitkaTableRouteEntry(controllerActionMethod, appendParameters, routeAttribute, action, controller, routeName, sitkaRouteEntries, controllerPartForUrl, null, null, false); } else { if (isCrossAreaRoute) { foreach (var areaKey in areasDictionary.Keys) { CreateSitkaTableRouteEntry(controllerActionMethod, appendParameters, routeAttribute, action, controller, routeName, sitkaRouteEntries, controllerPartForUrl, areaKey, areasDictionary[areaKey], true); } } else { var area = SitkaController.ControllerTypeToAreaName(controllerActionMethod.ReflectedType); Check.Require(areasDictionary.ContainsKey(area), $"Area \"{area}\" not found in Areas Dictionary!"); var areaAsSubdomainName = areasDictionary[area]; CreateSitkaTableRouteEntry(controllerActionMethod, appendParameters, routeAttribute, action, controller, routeName, sitkaRouteEntries, controllerPartForUrl, area, areaAsSubdomainName, false); } } } else { // this is the Armstrong prepend the program identifier in the route path; as for now, we do not support Areas for it. var routeAppendVariants = CalculateRouteParameterPermutationsAppend(appendParameters); var routePrependVariants = CalculateRouteParameterPermutationsPrepend(prependParameters); for (var appendIndex = 0; appendIndex < routeAppendVariants.Count; appendIndex++) { sitkaRouteEntries.AddRange( routePrependVariants.Select( (t, prependIndex) => { string routeUrl; int?routeOrder; var calculatedRouteName = $"{routeName}__{prependIndex:0000}__{appendIndex:0000}"; if (routeAttribute != null) { routeUrl = routeAttribute.Template; routeOrder = routeAttribute.Order; } else { routeUrl = $"{t}{controllerPartForUrl}/{action}{routeAppendVariants[appendIndex]}"; routeOrder = null; } return(CreateSitkaTableRouteEntry(controllerActionMethod, action, controller, null, null, routeUrl, calculatedRouteName, routeOrder, false)); })); } } return(sitkaRouteEntries); }