From 5f97bc8ae389a5e646f9d74d74c1188ffef5ca1d Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Mon, 26 Nov 2012 06:05:30 -0700 Subject: [PATCH 01/97] #153 - fixed bad parsing of regex route constraint patterns that use a comma. --- .../AttributeRouting.Specs.csproj | 5 +- .../Features/InheritingActions.feature.cs | 54 ++-- .../Features/Logging.feature.cs | 12 +- .../Features/RouteAreas.feature.cs | 90 +++--- .../Features/RouteConstraints.feature | 1 + .../Features/RouteConstraints.feature.cs | 261 +++++++++--------- .../Features/RouteConventions.feature.cs | 144 +++++----- .../Features/RouteDefaults.feature.cs | 42 +-- .../Features/RoutePrecedence.feature.cs | 228 +++++++-------- .../Features/RoutePrefixes.feature.cs | 102 +++---- .../Features/StandardUsage.feature.cs | 76 ++--- .../HttpInlineRouteConstraintsControllers.cs | 6 + .../InlineRouteConstraintsControllers.cs | 6 + src/AttributeRouting.Specs/app.config | 2 +- src/AttributeRouting.Specs/packages.config | 2 +- .../Framework/RouteBuilder.cs | 9 +- 16 files changed, 530 insertions(+), 510 deletions(-) diff --git a/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj b/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj index 2c0000a..49f1048 100644 --- a/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj +++ b/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj @@ -81,9 +81,8 @@ - - False - ..\packages\SpecFlow.1.8.1\lib\net35\TechTalk.SpecFlow.dll + + ..\packages\SpecFlow.1.9.0\lib\net35\TechTalk.SpecFlow.dll diff --git a/src/AttributeRouting.Specs/Features/InheritingActions.feature.cs b/src/AttributeRouting.Specs/Features/InheritingActions.feature.cs index 0f21fd5..6b5284b 100644 --- a/src/AttributeRouting.Specs/Features/InheritingActions.feature.cs +++ b/src/AttributeRouting.Specs/Features/InheritingActions.feature.cs @@ -1,8 +1,8 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.8.1.0 -// SpecFlow Generator Version:1.8.0.0 +// SpecFlow Version:1.9.1.84 +// SpecFlow Generator Version:1.9.0.0 // Runtime Version:4.0.30319.17929 // // Changes to this file may cause incorrect behavior and will be lost if @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.8.1.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Inheriting Actions")] @@ -72,17 +72,17 @@ public virtual void InheritingActionsInADerivedController() #line 3 this.ScenarioSetup(scenarioInfo); #line 4 - testRunner.Given("I have registered the routes for the SuperController"); + testRunner.Given("I have registered the routes for the SuperController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 5 - testRunner.When("I fetch the routes for the SuperController\'s Index action"); + testRunner.When("I fetch the routes for the SuperController\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 6 - testRunner.Then("the route url is \"InheritedActions/Index\""); + testRunner.Then("the route url is \"InheritedActions/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 7 - testRunner.Given("I have registered the routes for the DerivedController"); + testRunner.Given("I have registered the routes for the DerivedController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 8 - testRunner.When("I fetch the routes for the DerivedController\'s Index action"); + testRunner.When("I fetch the routes for the DerivedController\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 9 - testRunner.Then("the route url is \"InheritedActions/Index\""); + testRunner.Then("the route url is \"InheritedActions/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -95,17 +95,17 @@ public virtual void InheritingActionsInADerivedControllerOverridingTheUrlOfAnAct #line 11 this.ScenarioSetup(scenarioInfo); #line 12 - testRunner.Given("I have registered the routes for the SuperController"); + testRunner.Given("I have registered the routes for the SuperController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 13 - testRunner.When("I fetch the routes for the SuperController\'s Index action"); + testRunner.When("I fetch the routes for the SuperController\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 14 - testRunner.Then("the route url is \"InheritedActions/Index\""); + testRunner.Then("the route url is \"InheritedActions/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 15 - testRunner.Given("I have registered the routes for the DerivedWithOverrideController"); + testRunner.Given("I have registered the routes for the DerivedWithOverrideController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 16 - testRunner.When("I fetch the routes for the DerivedWithOverrideController\'s Index action"); + testRunner.When("I fetch the routes for the DerivedWithOverrideController\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 17 - testRunner.Then("the route url is \"InheritedActions/IndexDerived\""); + testRunner.Then("the route url is \"InheritedActions/IndexDerived\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -118,17 +118,17 @@ public virtual void InheritingActionsInADerivedControllerThatSpecifiesAnArea() #line 19 this.ScenarioSetup(scenarioInfo); #line 20 - testRunner.Given("I have registered the routes for the SuperWithAreaController"); + testRunner.Given("I have registered the routes for the SuperWithAreaController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 21 - testRunner.When("I fetch the routes for the SuperWithAreaController\'s Index action"); + testRunner.When("I fetch the routes for the SuperWithAreaController\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 22 - testRunner.Then("the route url is \"Super/InheritedActions/Index\""); + testRunner.Then("the route url is \"Super/InheritedActions/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 23 - testRunner.Given("I have registered the routes for the DerivedWithAreaController"); + testRunner.Given("I have registered the routes for the DerivedWithAreaController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 24 - testRunner.When("I fetch the routes for the DerivedWithAreaController\'s Index action"); + testRunner.When("I fetch the routes for the DerivedWithAreaController\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 25 - testRunner.Then("the route url is \"Derived/InheritedActions/Index\""); + testRunner.Then("the route url is \"Derived/InheritedActions/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -141,17 +141,17 @@ public virtual void InheritingActionsInADerivedControllerThatSpecifiesAPrefix() #line 27 this.ScenarioSetup(scenarioInfo); #line 28 - testRunner.Given("I have registered the routes for the SuperWithPrefixController"); + testRunner.Given("I have registered the routes for the SuperWithPrefixController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 29 - testRunner.When("I fetch the routes for the SuperWithPrefixController\'s Index action"); + testRunner.When("I fetch the routes for the SuperWithPrefixController\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 30 - testRunner.Then("the route url is \"InheritedActions/Super/Index\""); + testRunner.Then("the route url is \"InheritedActions/Super/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 31 - testRunner.Given("I have registered the routes for the DerivedWithPrefixController"); + testRunner.Given("I have registered the routes for the DerivedWithPrefixController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 32 - testRunner.When("I fetch the routes for the DerivedWithPrefixController\'s Index action"); + testRunner.When("I fetch the routes for the DerivedWithPrefixController\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 33 - testRunner.Then("the route url is \"InheritedActions/Derived/Index\""); + testRunner.Then("the route url is \"InheritedActions/Derived/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Features/Logging.feature.cs b/src/AttributeRouting.Specs/Features/Logging.feature.cs index 4b3307c..b033593 100644 --- a/src/AttributeRouting.Specs/Features/Logging.feature.cs +++ b/src/AttributeRouting.Specs/Features/Logging.feature.cs @@ -1,8 +1,8 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.8.1.0 -// SpecFlow Generator Version:1.8.0.0 +// SpecFlow Version:1.9.1.84 +// SpecFlow Generator Version:1.9.0.0 // Runtime Version:4.0.30319.17929 // // Changes to this file may cause incorrect behavior and will be lost if @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.8.1.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Logging")] @@ -72,11 +72,11 @@ public virtual void LogTheRoutesToTheStandardOutput() #line 3 this.ScenarioSetup(scenarioInfo); #line 4 - testRunner.Given("I generate the routes defined in the subject controllers"); + testRunner.Given("I generate the routes defined in the subject controllers", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 5 - testRunner.When("I log the routes"); + testRunner.When("I log the routes", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 6 - testRunner.Then("ta-da!"); + testRunner.Then("ta-da!", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs b/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs index d15ef4c..63879f1 100644 --- a/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs @@ -1,8 +1,8 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.8.1.0 -// SpecFlow Generator Version:1.8.0.0 +// SpecFlow Version:1.9.1.84 +// SpecFlow Generator Version:1.9.0.0 // Runtime Version:4.0.30319.17929 // // Changes to this file may cause incorrect behavior and will be lost if @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.8.1.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Route Areas")] @@ -72,21 +72,21 @@ public virtual void GeneratingAreaRoutes() #line 3 this.ScenarioSetup(scenarioInfo); #line 5 - testRunner.Given("I have registered the routes for the AreasController"); + testRunner.Given("I have registered the routes for the AreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 6 - testRunner.When("I fetch the routes for the Areas controller\'s Index action"); + testRunner.When("I fetch the routes for the Areas controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 7 - testRunner.Then("the route url is \"Area/Index\""); + testRunner.Then("the route url is \"Area/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 8 - testRunner.And("the data token for \"area\" is \"Area\""); + testRunner.And("the data token for \"area\" is \"Area\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 10 - testRunner.Given("I have registered the routes for the HttpAreasController"); + testRunner.Given("I have registered the routes for the HttpAreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 11 - testRunner.When("I fetch the routes for the HttpAreas controller\'s Get action"); + testRunner.When("I fetch the routes for the HttpAreas controller\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 12 - testRunner.Then("the route url is \"ApiArea/Get\""); + testRunner.Then("the route url is \"ApiArea/Get\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 13 - testRunner.And("the data token for \"area\" is \"ApiArea\""); + testRunner.And("the data token for \"area\" is \"ApiArea\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -99,17 +99,17 @@ public virtual void GeneratingAreaRoutesWhenRouteUrlsSpecifyADuplicateAreaPrefix #line 15 this.ScenarioSetup(scenarioInfo); #line 17 - testRunner.Given("I have registered the routes for the AreasController"); + testRunner.Given("I have registered the routes for the AreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 18 - testRunner.When("I fetch the routes for the Areas controller\'s DuplicatePrefix action"); + testRunner.When("I fetch the routes for the Areas controller\'s DuplicatePrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 19 - testRunner.Then("the route url is \"Area/DuplicatePrefix\""); + testRunner.Then("the route url is \"Area/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 21 - testRunner.Given("I have registered the routes for the HttpAreasController"); + testRunner.Given("I have registered the routes for the HttpAreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 22 - testRunner.When("I fetch the routes for the HttpAreas controller\'s DuplicatePrefix action"); + testRunner.When("I fetch the routes for the HttpAreas controller\'s DuplicatePrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 23 - testRunner.Then("the route url is \"ApiArea/DuplicatePrefix\""); + testRunner.Then("the route url is \"ApiArea/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -122,17 +122,17 @@ public virtual void GeneratingAbsoluteRoutesWhenARouteAreaIsDefined() #line 25 this.ScenarioSetup(scenarioInfo); #line 27 - testRunner.Given("I have registered the routes for the AreasController"); + testRunner.Given("I have registered the routes for the AreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 28 - testRunner.When("I fetch the routes for the Areas controller\'s Absolute action"); + testRunner.When("I fetch the routes for the Areas controller\'s Absolute action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 29 - testRunner.Then("the route url is \"AreaAbsolute\""); + testRunner.Then("the route url is \"AreaAbsolute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 31 - testRunner.Given("I have registered the routes for the HttpAreasController"); + testRunner.Given("I have registered the routes for the HttpAreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 32 - testRunner.When("I fetch the routes for the HttpAreas controller\'s Absolute action"); + testRunner.When("I fetch the routes for the HttpAreas controller\'s Absolute action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 33 - testRunner.Then("the route url is \"ApiAreaAbsolute\""); + testRunner.Then("the route url is \"ApiAreaAbsolute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -145,17 +145,17 @@ public virtual void GeneratingAreaRoutesWhenRouteUrlStartsWithTheAreaPrefix() #line 35 this.ScenarioSetup(scenarioInfo); #line 37 - testRunner.Given("I have registered the routes for the AreasController"); + testRunner.Given("I have registered the routes for the AreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 38 - testRunner.When("I fetch the routes for the Areas controller\'s RouteBeginsWithAreaName action"); + testRunner.When("I fetch the routes for the Areas controller\'s RouteBeginsWithAreaName action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 39 - testRunner.Then("the route url is \"Area/Areas\""); + testRunner.Then("the route url is \"Area/Areas\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 41 - testRunner.Given("I have registered the routes for the HttpAreasController"); + testRunner.Given("I have registered the routes for the HttpAreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 42 - testRunner.When("I fetch the routes for the HttpAreas controller\'s RouteBeginsWithAreaName action"); + testRunner.When("I fetch the routes for the HttpAreas controller\'s RouteBeginsWithAreaName action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 43 - testRunner.Then("the route url is \"ApiArea/ApiAreas\""); + testRunner.Then("the route url is \"ApiArea/ApiAreas\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -168,21 +168,21 @@ public virtual void GeneratingAreaRoutesWithAnExplicitAreaUrl() #line 45 this.ScenarioSetup(scenarioInfo); #line 47 - testRunner.Given("I have registered the routes for the ExplicitAreaUrlController"); + testRunner.Given("I have registered the routes for the ExplicitAreaUrlController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 48 - testRunner.When("I fetch the routes for the ExplicitAreaUrl controller\'s Index action"); + testRunner.When("I fetch the routes for the ExplicitAreaUrl controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 49 - testRunner.Then("the route url is \"ExplicitArea/Index\""); + testRunner.Then("the route url is \"ExplicitArea/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 50 - testRunner.And("the data token for \"area\" is \"Area\""); + testRunner.And("the data token for \"area\" is \"Area\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 52 - testRunner.Given("I have registered the routes for the HttpExplicitAreaUrlController"); + testRunner.Given("I have registered the routes for the HttpExplicitAreaUrlController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 53 - testRunner.When("I fetch the routes for the HttpExplicitAreaUrl controller\'s Get action"); + testRunner.When("I fetch the routes for the HttpExplicitAreaUrl controller\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 54 - testRunner.Then("the route url is \"ApiExplicitArea/Get\""); + testRunner.Then("the route url is \"ApiExplicitArea/Get\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 55 - testRunner.And("the data token for \"area\" is \"ApiArea\""); + testRunner.And("the data token for \"area\" is \"ApiArea\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -197,22 +197,22 @@ public virtual void GeneratingAreaRoutesWithAnExplicitAreaUrlWhenRouteUrlsSpecif #line 57 this.ScenarioSetup(scenarioInfo); #line 59 - testRunner.Given("I have registered the routes for the ExplicitAreaUrlController"); + testRunner.Given("I have registered the routes for the ExplicitAreaUrlController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 60 - testRunner.When("I fetch the routes for the ExplicitAreaUrl controller\'s DuplicatePrefix action"); + testRunner.When("I fetch the routes for the ExplicitAreaUrl controller\'s DuplicatePrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 61 - testRunner.Then("the route url is \"ExplicitArea/DuplicatePrefix\""); + testRunner.Then("the route url is \"ExplicitArea/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 62 - testRunner.And("the data token for \"area\" is \"Area\""); + testRunner.And("the data token for \"area\" is \"Area\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 64 - testRunner.Given("I have registered the routes for the HttpExplicitAreaUrlController"); + testRunner.Given("I have registered the routes for the HttpExplicitAreaUrlController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 65 testRunner.When("I fetch the routes for the HttpExplicitAreaUrl controller\'s DuplicatePrefix actio" + - "n"); + "n", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 66 - testRunner.Then("the route url is \"ApiExplicitArea/DuplicatePrefix\""); + testRunner.Then("the route url is \"ApiExplicitArea/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 67 - testRunner.And("the data token for \"area\" is \"ApiArea\""); + testRunner.And("the data token for \"area\" is \"ApiArea\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Features/RouteConstraints.feature b/src/AttributeRouting.Specs/Features/RouteConstraints.feature index ecc4d16..c8aea4e 100644 --- a/src/AttributeRouting.Specs/Features/RouteConstraints.feature +++ b/src/AttributeRouting.Specs/Features/RouteConstraints.feature @@ -30,6 +30,7 @@ Scenario Outline: Inline constraints | Max | MaxRouteConstraint | | Range | RangeRouteConstraint | | Regex | RegexRouteConstraint | + | RegexRange | RegexRouteConstraint | | Compound | IntRouteConstraint | | Compound | MaxRouteConstraint | diff --git a/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs b/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs index 3ade24f..e13d6d2 100644 --- a/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs @@ -1,8 +1,8 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.8.1.0 -// SpecFlow Generator Version:1.8.0.0 +// SpecFlow Version:1.9.0.77 +// SpecFlow Generator Version:1.9.0.0 // Runtime Version:4.0.30319.17929 // // Changes to this file may cause incorrect behavior and will be lost if @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.8.1.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Route Constraints")] @@ -66,48 +66,49 @@ public virtual void ScenarioCleanup() [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Inline constraints")] - [NUnit.Framework.TestCaseAttribute("Alpha", "AlphaRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Int", "IntRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Long", "LongRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Float", "FloatRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Double", "DoubleRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Decimal", "DecimalRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Bool", "BoolRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Guid", "GuidRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("DateTime", "DateTimeRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Length", "LengthRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("MinLength", "MinLengthRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("MaxLength", "MaxLengthRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("LengthRange", "LengthRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Min", "MinRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Max", "MaxRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Range", "RangeRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Regex", "RegexRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Compound", "IntRouteConstraint", new string[0])] - [NUnit.Framework.TestCaseAttribute("Compound", "MaxRouteConstraint", new string[0])] + [NUnit.Framework.TestCaseAttribute("Alpha", "AlphaRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Int", "IntRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Long", "LongRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Float", "FloatRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Double", "DoubleRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Decimal", "DecimalRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Bool", "BoolRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Guid", "GuidRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("DateTime", "DateTimeRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Length", "LengthRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("MinLength", "MinLengthRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("MaxLength", "MaxLengthRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("LengthRange", "LengthRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Min", "MinRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Max", "MaxRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Range", "RangeRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Regex", "RegexRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("RegexRange", "RegexRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Compound", "IntRouteConstraint", null)] + [NUnit.Framework.TestCaseAttribute("Compound", "MaxRouteConstraint", null)] public virtual void InlineConstraints(string actionName, string constraintTypeName, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Inline constraints", exampleTags); #line 3 this.ScenarioSetup(scenarioInfo); #line 5 - testRunner.Given("I have registered the routes for the InlineRouteConstraintsController"); + testRunner.Given("I have registered the routes for the InlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 6 - testRunner.When(string.Format("I fetch the routes for the InlineRouteConstraints controller\'s {0} action", actionName)); + testRunner.When(string.Format("I fetch the routes for the InlineRouteConstraints controller\'s {0} action", actionName), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 7 - testRunner.Then(string.Format("the route url is \"Inline-Constraints/{0}/{{x}}\"", actionName)); + testRunner.Then(string.Format("the route url is \"Inline-Constraints/{0}/{{x}}\"", actionName), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 8 testRunner.And(string.Format("the parameter \"x\" is constrained by an inline AttributeRouting.Web.Constraints.{0" + - "}", constraintTypeName)); + "}", constraintTypeName), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 10 - testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController"); + testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 11 - testRunner.When(string.Format("I fetch the routes for the HttpInlineRouteConstraints controller\'s {0} action", actionName)); + testRunner.When(string.Format("I fetch the routes for the HttpInlineRouteConstraints controller\'s {0} action", actionName), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 12 - testRunner.Then(string.Format("the route url is \"Http-Inline-Constraints/{0}/{{x}}\"", actionName)); + testRunner.Then(string.Format("the route url is \"Http-Inline-Constraints/{0}/{{x}}\"", actionName), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 13 testRunner.And(string.Format("the parameter \"x\" is constrained by an inline AttributeRouting.Web.Constraints.{0" + - "}", constraintTypeName)); + "}", constraintTypeName), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -117,34 +118,34 @@ public virtual void InlineConstraints(string actionName, string constraintTypeNa public virtual void MultipleInlineConstraintsPerUrlSegment() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Multiple inline constraints per url segment", ((string[])(null))); -#line 36 +#line 37 this.ScenarioSetup(scenarioInfo); -#line 38 - testRunner.Given("I have registered the routes for the InlineRouteConstraintsController"); #line 39 - testRunner.When("I fetch the routes for the InlineRouteConstraints controller\'s MultipleWithinUrlS" + - "egment action"); + testRunner.Given("I have registered the routes for the InlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 40 - testRunner.Then("the route url is \"Inline-Constraints/avatar/{width}x{height}/{image}\""); + testRunner.When("I fetch the routes for the InlineRouteConstraints controller\'s MultipleWithinUrlS" + + "egment action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 41 - testRunner.And("the parameter \"width\" is constrained by an inline AttributeRouting.Web.Constraint" + - "s.IntRouteConstraint"); + testRunner.Then("the route url is \"Inline-Constraints/avatar/{width}x{height}/{image}\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 42 + testRunner.And("the parameter \"width\" is constrained by an inline AttributeRouting.Web.Constraint" + + "s.IntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 43 testRunner.And("the parameter \"height\" is constrained by an inline AttributeRouting.Web.Constrain" + - "ts.IntRouteConstraint"); -#line 44 - testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController"); + "ts.IntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 45 - testRunner.When("I fetch the routes for the HttpInlineRouteConstraints controller\'s MultipleWithin" + - "UrlSegment action"); + testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 46 - testRunner.Then("the route url is \"Http-Inline-Constraints/avatar/{width}x{height}/{image}\""); + testRunner.When("I fetch the routes for the HttpInlineRouteConstraints controller\'s MultipleWithin" + + "UrlSegment action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 47 - testRunner.And("the parameter \"width\" is constrained by an inline AttributeRouting.Web.Constraint" + - "s.IntRouteConstraint"); + testRunner.Then("the route url is \"Http-Inline-Constraints/avatar/{width}x{height}/{image}\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 48 + testRunner.And("the parameter \"width\" is constrained by an inline AttributeRouting.Web.Constraint" + + "s.IntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 49 testRunner.And("the parameter \"height\" is constrained by an inline AttributeRouting.Web.Constrain" + - "ts.IntRouteConstraint"); + "ts.IntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -154,29 +155,29 @@ public virtual void MultipleInlineConstraintsPerUrlSegment() public virtual void InlineConstraintsSpecifiedInTheRoutePrefixAttribute() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Inline constraints specified in the RoutePrefixAttribute", ((string[])(null))); -#line 50 +#line 51 this.ScenarioSetup(scenarioInfo); -#line 52 - testRunner.Given("I have registered the routes for the PrefixedInlineRouteConstraintsController"); #line 53 - testRunner.When("I fetch the routes for the PrefixedInlineRouteConstraints controller\'s Index acti" + - "on"); + testRunner.Given("I have registered the routes for the PrefixedInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 54 - testRunner.Then("the route url is \"Prefixed-Inline-Constraints/{id}/Howdy\""); + testRunner.When("I fetch the routes for the PrefixedInlineRouteConstraints controller\'s Index acti" + + "on", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 55 + testRunner.Then("the route url is \"Prefixed-Inline-Constraints/{id}/Howdy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 56 testRunner.And("the parameter \"id\" is constrained by an inline AttributeRouting.Web.Constraints.I" + - "ntRouteConstraint"); -#line 57 - testRunner.Given("I have registered the routes for the HttpPrefixedInlineRouteConstraintsController" + - ""); + "ntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 58 - testRunner.When("I fetch the routes for the HttpPrefixedInlineRouteConstraints controller\'s Index " + - "action"); + testRunner.Given("I have registered the routes for the HttpPrefixedInlineRouteConstraintsController" + + "", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 59 - testRunner.Then("the route url is \"Http-Prefixed-Inline-Constraints/{id}/Howdy\""); + testRunner.When("I fetch the routes for the HttpPrefixedInlineRouteConstraints controller\'s Index " + + "action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 60 + testRunner.Then("the route url is \"Http-Prefixed-Inline-Constraints/{id}/Howdy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 61 testRunner.And("the parameter \"id\" is constrained by an inline AttributeRouting.Web.Constraints.I" + - "ntRouteConstraint"); + "ntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -186,98 +187,98 @@ public virtual void InlineConstraintsSpecifiedInTheRoutePrefixAttribute() public virtual void InlineConstraintsSpecifiedInTheRouteAreaAttribute() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Inline constraints specified in the RouteAreaAttribute", ((string[])(null))); -#line 62 +#line 63 this.ScenarioSetup(scenarioInfo); -#line 64 - testRunner.Given("I have registered the routes for the AreaInlineRouteConstraintsController"); #line 65 - testRunner.When("I fetch the routes for the AreaInlineRouteConstraints controller\'s Index action"); + testRunner.Given("I have registered the routes for the AreaInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 66 - testRunner.Then("the route url is \"Area-Inline-Constraints/{id}/Howdy\""); + testRunner.When("I fetch the routes for the AreaInlineRouteConstraints controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 67 + testRunner.Then("the route url is \"Area-Inline-Constraints/{id}/Howdy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 68 testRunner.And("the parameter \"id\" is constrained by an inline AttributeRouting.Web.Constraints.I" + - "ntRouteConstraint"); -#line 69 - testRunner.Given("I have registered the routes for the HttpAreaInlineRouteConstraintsController"); + "ntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 70 - testRunner.When("I fetch the routes for the HttpAreaInlineRouteConstraints controller\'s Index acti" + - "on"); + testRunner.Given("I have registered the routes for the HttpAreaInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 71 - testRunner.Then("the route url is \"Http-Area-Inline-Constraints/{id}/Howdy\""); + testRunner.When("I fetch the routes for the HttpAreaInlineRouteConstraints controller\'s Index acti" + + "on", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 72 + testRunner.Then("the route url is \"Http-Area-Inline-Constraints/{id}/Howdy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 73 testRunner.And("the parameter \"id\" is constrained by an inline AttributeRouting.Web.Constraints.I" + - "ntRouteConstraint"); + "ntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Matching inline route constraints")] - [NUnit.Framework.TestCaseAttribute("Alpha/abc", "Alpha", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Alpha/123", "Alpha", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Int/53", "Int", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Int/abc", "Int", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("IntOptional", "IntOptional", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Long/79", "Long", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Long/xyz", "Long", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Float/1.334", "Float", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Float/gg2", "Float", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Double/3.14", "Double", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Double/adf78", "Double", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Decimal/99.32123345", "Decimal", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Decimal/d8uasdf", "Decimal", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Bool/true", "Bool", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Bool/false", "Bool", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Bool/truish", "Bool", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Bool/falsish", "Bool", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Guid/6076668C-57AA-47FD-A914-94FD552C8493", "Guid", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Guid/6076668C-57AA-47FD-A914-94FD552C", "Guid", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("DateTime/2012-4-22", "DateTime", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("DateTime/Today", "DateTime", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Length/a", "Length", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Length/aa", "Length", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("MinLength/abcdefghi", "MinLength", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("MinLength/abcdefghij", "MinLength", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("MaxLength/abcdefghij", "MaxLength", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("MaxLength/abcdefghijk", "MaxLength", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("LengthRange/abcdefghijk", "LengthRange", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("LengthRange/a", "LengthRange", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("LengthRange/ab", "LengthRange", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("LengthRange/abcdefghij", "LengthRange", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("LengthRange/abcdefghijk", "LengthRange", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Min/0", "Min", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Min/1", "Min", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Max/10", "Max", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Max/11", "Max", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Range/0", "Range", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Range/1", "Range", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Range/10", "Range", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Range/11", "Range", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Regex/Howdy", "Regex", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Regex/BoyHowdy", "Regex", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Compound/5", "Compound", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Compound/5.0", "Compound", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("Enum/red", "Enum", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("Enum/taupe", "Enum", "is not", new string[0])] - [NUnit.Framework.TestCaseAttribute("WithOptional", "WithOptional", "is", new string[0])] - [NUnit.Framework.TestCaseAttribute("WithDefault", "WithDefault", "is", new string[0])] + [NUnit.Framework.TestCaseAttribute("Alpha/abc", "Alpha", "is", null)] + [NUnit.Framework.TestCaseAttribute("Alpha/123", "Alpha", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Int/53", "Int", "is", null)] + [NUnit.Framework.TestCaseAttribute("Int/abc", "Int", "is not", null)] + [NUnit.Framework.TestCaseAttribute("IntOptional", "IntOptional", "is", null)] + [NUnit.Framework.TestCaseAttribute("Long/79", "Long", "is", null)] + [NUnit.Framework.TestCaseAttribute("Long/xyz", "Long", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Float/1.334", "Float", "is", null)] + [NUnit.Framework.TestCaseAttribute("Float/gg2", "Float", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Double/3.14", "Double", "is", null)] + [NUnit.Framework.TestCaseAttribute("Double/adf78", "Double", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Decimal/99.32123345", "Decimal", "is", null)] + [NUnit.Framework.TestCaseAttribute("Decimal/d8uasdf", "Decimal", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Bool/true", "Bool", "is", null)] + [NUnit.Framework.TestCaseAttribute("Bool/false", "Bool", "is", null)] + [NUnit.Framework.TestCaseAttribute("Bool/truish", "Bool", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Bool/falsish", "Bool", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Guid/6076668C-57AA-47FD-A914-94FD552C8493", "Guid", "is", null)] + [NUnit.Framework.TestCaseAttribute("Guid/6076668C-57AA-47FD-A914-94FD552C", "Guid", "is not", null)] + [NUnit.Framework.TestCaseAttribute("DateTime/2012-4-22", "DateTime", "is", null)] + [NUnit.Framework.TestCaseAttribute("DateTime/Today", "DateTime", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Length/a", "Length", "is", null)] + [NUnit.Framework.TestCaseAttribute("Length/aa", "Length", "is not", null)] + [NUnit.Framework.TestCaseAttribute("MinLength/abcdefghi", "MinLength", "is not", null)] + [NUnit.Framework.TestCaseAttribute("MinLength/abcdefghij", "MinLength", "is", null)] + [NUnit.Framework.TestCaseAttribute("MaxLength/abcdefghij", "MaxLength", "is", null)] + [NUnit.Framework.TestCaseAttribute("MaxLength/abcdefghijk", "MaxLength", "is not", null)] + [NUnit.Framework.TestCaseAttribute("LengthRange/abcdefghijk", "LengthRange", "is not", null)] + [NUnit.Framework.TestCaseAttribute("LengthRange/a", "LengthRange", "is not", null)] + [NUnit.Framework.TestCaseAttribute("LengthRange/ab", "LengthRange", "is", null)] + [NUnit.Framework.TestCaseAttribute("LengthRange/abcdefghij", "LengthRange", "is", null)] + [NUnit.Framework.TestCaseAttribute("LengthRange/abcdefghijk", "LengthRange", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Min/0", "Min", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Min/1", "Min", "is", null)] + [NUnit.Framework.TestCaseAttribute("Max/10", "Max", "is", null)] + [NUnit.Framework.TestCaseAttribute("Max/11", "Max", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Range/0", "Range", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Range/1", "Range", "is", null)] + [NUnit.Framework.TestCaseAttribute("Range/10", "Range", "is", null)] + [NUnit.Framework.TestCaseAttribute("Range/11", "Range", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Regex/Howdy", "Regex", "is", null)] + [NUnit.Framework.TestCaseAttribute("Regex/BoyHowdy", "Regex", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Compound/5", "Compound", "is", null)] + [NUnit.Framework.TestCaseAttribute("Compound/5.0", "Compound", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Enum/red", "Enum", "is", null)] + [NUnit.Framework.TestCaseAttribute("Enum/taupe", "Enum", "is not", null)] + [NUnit.Framework.TestCaseAttribute("WithOptional", "WithOptional", "is", null)] + [NUnit.Framework.TestCaseAttribute("WithDefault", "WithDefault", "is", null)] public virtual void MatchingInlineRouteConstraints(string url, string action, string condition, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Matching inline route constraints", exampleTags); -#line 74 +#line 75 this.ScenarioSetup(scenarioInfo); -#line 76 - testRunner.Given("I have registered the routes for the InlineRouteConstraintsController"); #line 77 - testRunner.When(string.Format("a request for \"Inline-Constraints/{0}\" is made", url)); + testRunner.Given("I have registered the routes for the InlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 78 - testRunner.Then(string.Format("the {0} action {1} matched", action, condition)); -#line 80 - testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController"); + testRunner.When(string.Format("a request for \"Inline-Constraints/{0}\" is made", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 79 + testRunner.Then(string.Format("the {0} action {1} matched", action, condition), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 81 - testRunner.When(string.Format("a request for \"Http-Inline-Constraints/{0}\" is made", url)); + testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 82 - testRunner.Then(string.Format("the {0} action {1} matched", action, condition)); + testRunner.When(string.Format("a request for \"Http-Inline-Constraints/{0}\" is made", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 83 + testRunner.Then(string.Format("the {0} action {1} matched", action, condition), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Features/RouteConventions.feature.cs b/src/AttributeRouting.Specs/Features/RouteConventions.feature.cs index b2dbbc0..f388969 100644 --- a/src/AttributeRouting.Specs/Features/RouteConventions.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteConventions.feature.cs @@ -1,8 +1,8 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.8.1.0 -// SpecFlow Generator Version:1.8.0.0 +// SpecFlow Version:1.9.1.84 +// SpecFlow Generator Version:1.9.0.0 // Runtime Version:4.0.30319.17929 // // Changes to this file may cause incorrect behavior and will be lost if @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.8.1.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Route Conventions")] @@ -66,32 +66,32 @@ public virtual void ScenarioCleanup() [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Generating routes using the RestfulRouteConvention")] - [NUnit.Framework.TestCaseAttribute("Index", "GET", "RestfulRouteConvention", new string[0])] - [NUnit.Framework.TestCaseAttribute("New", "GET", "RestfulRouteConvention/New", new string[0])] - [NUnit.Framework.TestCaseAttribute("Create", "POST", "RestfulRouteConvention", new string[0])] - [NUnit.Framework.TestCaseAttribute("Show", "GET", "RestfulRouteConvention/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("Edit", "GET", "RestfulRouteConvention/{id}/Edit", new string[0])] - [NUnit.Framework.TestCaseAttribute("Update", "PUT", "RestfulRouteConvention/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("Delete", "GET", "RestfulRouteConvention/{id}/Delete", new string[0])] - [NUnit.Framework.TestCaseAttribute("Destroy", "DELETE", "RestfulRouteConvention/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("Custom", "GET", "RestfulRouteConvention/Custom", new string[0])] + [NUnit.Framework.TestCaseAttribute("Index", "GET", "RestfulRouteConvention", null)] + [NUnit.Framework.TestCaseAttribute("New", "GET", "RestfulRouteConvention/New", null)] + [NUnit.Framework.TestCaseAttribute("Create", "POST", "RestfulRouteConvention", null)] + [NUnit.Framework.TestCaseAttribute("Show", "GET", "RestfulRouteConvention/{id}", null)] + [NUnit.Framework.TestCaseAttribute("Edit", "GET", "RestfulRouteConvention/{id}/Edit", null)] + [NUnit.Framework.TestCaseAttribute("Update", "PUT", "RestfulRouteConvention/{id}", null)] + [NUnit.Framework.TestCaseAttribute("Delete", "GET", "RestfulRouteConvention/{id}/Delete", null)] + [NUnit.Framework.TestCaseAttribute("Destroy", "DELETE", "RestfulRouteConvention/{id}", null)] + [NUnit.Framework.TestCaseAttribute("Custom", "GET", "RestfulRouteConvention/Custom", null)] public virtual void GeneratingRoutesUsingTheRestfulRouteConvention(string action, string method, string url, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes using the RestfulRouteConvention", exampleTags); #line 3 this.ScenarioSetup(scenarioInfo); #line 4 - testRunner.Given("I have registered the routes for the RestfulRouteConventionController"); + testRunner.Given("I have registered the routes for the RestfulRouteConventionController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 5 - testRunner.When(string.Format("I fetch the routes for the RestfulRouteConvention controller\'s {0} action", action)); + testRunner.When(string.Format("I fetch the routes for the RestfulRouteConvention controller\'s {0} action", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 6 - testRunner.Then(string.Format("the route url is \"{0}\"", url)); + testRunner.Then(string.Format("the route url is \"{0}\"", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 7 - testRunner.And("the default for \"controller\" is \"RestfulRouteConvention\""); + testRunner.And("the default for \"controller\" is \"RestfulRouteConvention\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 8 - testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action)); + testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 9 - testRunner.And(string.Format("the route for {0} is constrained to {1} requests", action, method)); + testRunner.And(string.Format("the route for {0} is constrained to {1} requests", action, method), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -99,14 +99,14 @@ public virtual void GeneratingRoutesUsingTheRestfulRouteConvention(string action [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Generating routes using the RestfulRouteConvention on controllers with a RoutePre" + "fix attribute")] - [NUnit.Framework.TestCaseAttribute("Index", "GET", "Prefix", new string[0])] - [NUnit.Framework.TestCaseAttribute("New", "GET", "Prefix/New", new string[0])] - [NUnit.Framework.TestCaseAttribute("Create", "POST", "Prefix", new string[0])] - [NUnit.Framework.TestCaseAttribute("Show", "GET", "Prefix/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("Edit", "GET", "Prefix/{id}/Edit", new string[0])] - [NUnit.Framework.TestCaseAttribute("Update", "PUT", "Prefix/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("Delete", "GET", "Prefix/{id}/Delete", new string[0])] - [NUnit.Framework.TestCaseAttribute("Destroy", "DELETE", "Prefix/{id}", new string[0])] + [NUnit.Framework.TestCaseAttribute("Index", "GET", "Prefix", null)] + [NUnit.Framework.TestCaseAttribute("New", "GET", "Prefix/New", null)] + [NUnit.Framework.TestCaseAttribute("Create", "POST", "Prefix", null)] + [NUnit.Framework.TestCaseAttribute("Show", "GET", "Prefix/{id}", null)] + [NUnit.Framework.TestCaseAttribute("Edit", "GET", "Prefix/{id}/Edit", null)] + [NUnit.Framework.TestCaseAttribute("Update", "PUT", "Prefix/{id}", null)] + [NUnit.Framework.TestCaseAttribute("Delete", "GET", "Prefix/{id}/Delete", null)] + [NUnit.Framework.TestCaseAttribute("Destroy", "DELETE", "Prefix/{id}", null)] public virtual void GeneratingRoutesUsingTheRestfulRouteConventionOnControllersWithARoutePrefixAttribute(string action, string method, string url, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes using the RestfulRouteConvention on controllers with a RoutePre" + @@ -114,46 +114,46 @@ public virtual void GeneratingRoutesUsingTheRestfulRouteConventionOnControllersW #line 23 this.ScenarioSetup(scenarioInfo); #line 24 - testRunner.Given("I have registered the routes for the RestfulRouteConventionPrefixController"); + testRunner.Given("I have registered the routes for the RestfulRouteConventionPrefixController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 25 - testRunner.When(string.Format("I fetch the routes for the RestfulRouteConventionPrefix controller\'s {0} action", action)); + testRunner.When(string.Format("I fetch the routes for the RestfulRouteConventionPrefix controller\'s {0} action", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 26 - testRunner.Then(string.Format("the route url is \"{0}\"", url)); + testRunner.Then(string.Format("the route url is \"{0}\"", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 27 - testRunner.And("the default for \"controller\" is \"RestfulRouteConventionPrefix\""); + testRunner.And("the default for \"controller\" is \"RestfulRouteConventionPrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 28 - testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action)); + testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 29 - testRunner.And(string.Format("the route for {0} is constrained to {1} requests", action, method)); + testRunner.And(string.Format("the route for {0} is constrained to {1} requests", action, method), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Generating routes using the DefaultHttpRouteConvention")] - [NUnit.Framework.TestCaseAttribute("GetAll", "GET", "DefaultHttpRouteConvention", new string[0])] - [NUnit.Framework.TestCaseAttribute("Get", "GET", "DefaultHttpRouteConvention/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("Post", "POST", "DefaultHttpRouteConvention", new string[0])] - [NUnit.Framework.TestCaseAttribute("Put", "PUT", "DefaultHttpRouteConvention/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("Delete", "DELETE", "DefaultHttpRouteConvention/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("Custom", "GET", "DefaultHttpRouteConvention/Custom", new string[0])] + [NUnit.Framework.TestCaseAttribute("GetAll", "GET", "DefaultHttpRouteConvention", null)] + [NUnit.Framework.TestCaseAttribute("Get", "GET", "DefaultHttpRouteConvention/{id}", null)] + [NUnit.Framework.TestCaseAttribute("Post", "POST", "DefaultHttpRouteConvention", null)] + [NUnit.Framework.TestCaseAttribute("Put", "PUT", "DefaultHttpRouteConvention/{id}", null)] + [NUnit.Framework.TestCaseAttribute("Delete", "DELETE", "DefaultHttpRouteConvention/{id}", null)] + [NUnit.Framework.TestCaseAttribute("Custom", "GET", "DefaultHttpRouteConvention/Custom", null)] public virtual void GeneratingRoutesUsingTheDefaultHttpRouteConvention(string action, string method, string url, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes using the DefaultHttpRouteConvention", exampleTags); #line 42 this.ScenarioSetup(scenarioInfo); #line 43 - testRunner.Given("I have registered the routes for the DefaultHttpRouteConventionController"); + testRunner.Given("I have registered the routes for the DefaultHttpRouteConventionController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 44 - testRunner.When(string.Format("I fetch the routes for the DefaultHttpRouteConvention controller\'s {0} action", action)); + testRunner.When(string.Format("I fetch the routes for the DefaultHttpRouteConvention controller\'s {0} action", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 45 - testRunner.Then(string.Format("the route url is \"{0}\"", url)); + testRunner.Then(string.Format("the route url is \"{0}\"", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 46 - testRunner.And("the default for \"controller\" is \"DefaultHttpRouteConvention\""); + testRunner.And("the default for \"controller\" is \"DefaultHttpRouteConvention\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 47 - testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action)); + testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 48 - testRunner.And(string.Format("the route for {0} is constrained to {1} requests", action, method)); + testRunner.And(string.Format("the route for {0} is constrained to {1} requests", action, method), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -161,12 +161,12 @@ public virtual void GeneratingRoutesUsingTheDefaultHttpRouteConvention(string ac [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Generating routes using the DefaultHttpRouteConventionPrefix on controllers with " + "a RoutePrefix attribute")] - [NUnit.Framework.TestCaseAttribute("GetAll", "GET", "Prefix", new string[0])] - [NUnit.Framework.TestCaseAttribute("Get", "GET", "Prefix/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("Post", "POST", "Prefix", new string[0])] - [NUnit.Framework.TestCaseAttribute("Put", "PUT", "Prefix/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("Delete", "DELETE", "Prefix/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("Custom", "GET", "Prefix/Custom", new string[0])] + [NUnit.Framework.TestCaseAttribute("GetAll", "GET", "Prefix", null)] + [NUnit.Framework.TestCaseAttribute("Get", "GET", "Prefix/{id}", null)] + [NUnit.Framework.TestCaseAttribute("Post", "POST", "Prefix", null)] + [NUnit.Framework.TestCaseAttribute("Put", "PUT", "Prefix/{id}", null)] + [NUnit.Framework.TestCaseAttribute("Delete", "DELETE", "Prefix/{id}", null)] + [NUnit.Framework.TestCaseAttribute("Custom", "GET", "Prefix/Custom", null)] public virtual void GeneratingRoutesUsingTheDefaultHttpRouteConventionPrefixOnControllersWithARoutePrefixAttribute(string action, string method, string url, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes using the DefaultHttpRouteConventionPrefix on controllers with " + @@ -174,18 +174,18 @@ public virtual void GeneratingRoutesUsingTheDefaultHttpRouteConventionPrefixOnCo #line 59 this.ScenarioSetup(scenarioInfo); #line 60 - testRunner.Given("I have registered the routes for the DefaultHttpRouteConventionPrefixController"); + testRunner.Given("I have registered the routes for the DefaultHttpRouteConventionPrefixController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 61 testRunner.When(string.Format("I fetch the routes for the DefaultHttpRouteConventionPrefix controller\'s {0} acti" + - "on", action)); + "on", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 62 - testRunner.Then(string.Format("the route url is \"{0}\"", url)); + testRunner.Then(string.Format("the route url is \"{0}\"", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 63 - testRunner.And("the default for \"controller\" is \"DefaultHttpRouteConventionPrefix\""); + testRunner.And("the default for \"controller\" is \"DefaultHttpRouteConventionPrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 64 - testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action)); + testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 65 - testRunner.And(string.Format("the route for {0} is constrained to {1} requests", action, method)); + testRunner.And(string.Format("the route for {0} is constrained to {1} requests", action, method), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -201,14 +201,14 @@ public virtual void GeneratingRoutesUsingTheRestfulRouteConventionOnActionsWithA this.ScenarioSetup(scenarioInfo); #line 77 testRunner.Given("I have registered the routes for the RestfulRouteConventionWithExplicitRouteContr" + - "oller"); + "oller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 78 testRunner.When("I fetch the routes for the RestfulRouteConventionWithExplicitRoute controller\'s I" + - "ndex action"); + "ndex action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 79 - testRunner.Then("the 1st route url is \"RestfulRouteConventionWithExplicitRoute\""); + testRunner.Then("the 1st route url is \"RestfulRouteConventionWithExplicitRoute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 80 - testRunner.And("the 2nd route url is \"Legacy\""); + testRunner.And("the 2nd route url is \"Legacy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -224,14 +224,14 @@ public virtual void GeneratingRoutesUsingTheRestfulRouteConventionOnActionsWithA this.ScenarioSetup(scenarioInfo); #line 83 testRunner.Given("I have registered the routes for the RestfulRouteConventionWithExplicitOrderedRou" + - "teController"); + "teController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 84 testRunner.When("I fetch the routes for the RestfulRouteConventionWithExplicitOrderedRoute control" + - "ler\'s Index action"); + "ler\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 85 - testRunner.Then("the 1st route url is \"RestfulRouteConventionWithExplicitOrderedRoute/Primary\""); + testRunner.Then("the 1st route url is \"RestfulRouteConventionWithExplicitOrderedRoute/Primary\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 86 - testRunner.And("the 2nd route url is \"RestfulRouteConventionWithExplicitOrderedRoute\""); + testRunner.And("the 2nd route url is \"RestfulRouteConventionWithExplicitOrderedRoute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -247,14 +247,14 @@ public virtual void GeneratingRoutesUsingTheDefaultHttpRouteConventionOnActionsW this.ScenarioSetup(scenarioInfo); #line 89 testRunner.Given("I have registered the routes for the DefaultHttpRouteConventionWithExplicitRouteC" + - "ontroller"); + "ontroller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 90 testRunner.When("I fetch the routes for the DefaultHttpRouteConventionWithExplicitRoute controller" + - "\'s Get action"); + "\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 91 - testRunner.Then("the 1st route url is \"DefaultHttpRouteConventionWithExplicitRoute\""); + testRunner.Then("the 1st route url is \"DefaultHttpRouteConventionWithExplicitRoute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 92 - testRunner.And("the 2nd route url is \"Legacy\""); + testRunner.And("the 2nd route url is \"Legacy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -270,15 +270,15 @@ public virtual void GeneratingRoutesUsingTheDefaultHttpRouteConventionOnActionsW this.ScenarioSetup(scenarioInfo); #line 95 testRunner.Given("I have registered the routes for the DefaultHttpRouteConventionWithExplicitOrdere" + - "dRouteController"); + "dRouteController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 96 testRunner.When("I fetch the routes for the DefaultHttpRouteConventionWithExplicitOrderedRoute con" + - "troller\'s Get action"); + "troller\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 97 testRunner.Then("the 1st route url is \"DefaultHttpRouteConventionWithExplicitOrderedRoute/Primary\"" + - ""); + "", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 98 - testRunner.And("the 2nd route url is \"DefaultHttpRouteConventionWithExplicitOrderedRoute\""); + testRunner.And("the 2nd route url is \"DefaultHttpRouteConventionWithExplicitOrderedRoute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Features/RouteDefaults.feature.cs b/src/AttributeRouting.Specs/Features/RouteDefaults.feature.cs index e8777c3..ce10e38 100644 --- a/src/AttributeRouting.Specs/Features/RouteDefaults.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteDefaults.feature.cs @@ -1,8 +1,8 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.8.1.0 -// SpecFlow Generator Version:1.8.0.0 +// SpecFlow Version:1.9.1.84 +// SpecFlow Generator Version:1.9.0.0 // Runtime Version:4.0.30319.17929 // // Changes to this file may cause incorrect behavior and will be lost if @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.8.1.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Route Defaults")] @@ -68,13 +68,13 @@ public virtual void FeatureBackground() { #line 3 #line 4 - testRunner.Given("I have a new configuration object"); + testRunner.Given("I have a new configuration object", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 5 - testRunner.And("I add the routes from the RouteDefaults controller"); + testRunner.And("I add the routes from the RouteDefaults controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 6 - testRunner.And("I add the routes from the HttpRouteDefaults controller"); + testRunner.And("I add the routes from the HttpRouteDefaults controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 7 - testRunner.And("I generate the routes with this configuration"); + testRunner.And("I generate the routes with this configuration", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden } @@ -88,21 +88,21 @@ public virtual void RouteDefaultSpecifiedInline() #line 3 this.FeatureBackground(); #line 10 - testRunner.When("I fetch the routes for the RouteDefaults controller\'s InlineDefaults action"); + testRunner.When("I fetch the routes for the RouteDefaults controller\'s InlineDefaults action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 11 - testRunner.Then("the route url is \"InlineDefaults/{hello}/{goodnight}\""); + testRunner.Then("the route url is \"InlineDefaults/{hello}/{goodnight}\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 12 - testRunner.Then("the default for \"hello\" is \"sun\""); + testRunner.Then("the default for \"hello\" is \"sun\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 13 - testRunner.Then("the default for \"goodnight\" is \"moon\""); + testRunner.Then("the default for \"goodnight\" is \"moon\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 14 - testRunner.When("I fetch the routes for the HttpRouteDefaults controller\'s InlineDefaults action"); + testRunner.When("I fetch the routes for the HttpRouteDefaults controller\'s InlineDefaults action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 15 - testRunner.Then("the route url is \"InlineDefaults/{hello}/{goodnight}\""); + testRunner.Then("the route url is \"InlineDefaults/{hello}/{goodnight}\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 16 - testRunner.Then("the default for \"hello\" is \"sun\""); + testRunner.Then("the default for \"hello\" is \"sun\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 17 - testRunner.Then("the default for \"goodnight\" is \"moon\""); + testRunner.Then("the default for \"goodnight\" is \"moon\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -117,17 +117,17 @@ public virtual void OptionalParametersSpecifiedWithAUrlParameterToken() #line 3 this.FeatureBackground(); #line 20 - testRunner.When("I fetch the routes for the RouteDefaults controller\'s Optionals action"); + testRunner.When("I fetch the routes for the RouteDefaults controller\'s Optionals action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 21 - testRunner.Then("the route url is \"Optionals/{p1}\""); + testRunner.Then("the route url is \"Optionals/{p1}\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 22 - testRunner.And("the parameter \"p1\" is optional"); + testRunner.And("the parameter \"p1\" is optional", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 23 - testRunner.When("I fetch the routes for the HttpRouteDefaults controller\'s Optionals action"); + testRunner.When("I fetch the routes for the HttpRouteDefaults controller\'s Optionals action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 24 - testRunner.Then("the route url is \"Optionals/{p1}\""); + testRunner.Then("the route url is \"Optionals/{p1}\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 25 - testRunner.And("the parameter \"p1\" is optional"); + testRunner.And("the parameter \"p1\" is optional", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Features/RoutePrecedence.feature.cs b/src/AttributeRouting.Specs/Features/RoutePrecedence.feature.cs index 8c6773d..287e5a9 100644 --- a/src/AttributeRouting.Specs/Features/RoutePrecedence.feature.cs +++ b/src/AttributeRouting.Specs/Features/RoutePrecedence.feature.cs @@ -1,8 +1,8 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.8.1.0 -// SpecFlow Generator Version:1.8.0.0 +// SpecFlow Version:1.9.1.84 +// SpecFlow Generator Version:1.9.0.0 // Runtime Version:4.0.30319.17929 // // Changes to this file may cause incorrect behavior and will be lost if @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.8.1.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Route Precedence")] @@ -72,44 +72,44 @@ public virtual void RoutePrecedenceAmongRoutesForAnActionUsingTheOrderProperty() #line 3 this.ScenarioSetup(scenarioInfo); #line 5 - testRunner.Given("I have registered the routes for the RoutePrecedenceAmongRoutesController"); + testRunner.Given("I have registered the routes for the RoutePrecedenceAmongRoutesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 6 - testRunner.When("I fetch the routes for the RoutePrecedenceAmongRoutes controller\'s Index action"); + testRunner.When("I fetch the routes for the RoutePrecedenceAmongRoutes controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 7 - testRunner.Then("the 1st route\'s url is \"Index/First\""); + testRunner.Then("the 1st route\'s url is \"Index/First\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 8 - testRunner.Then("the 1st route\'s url is \"Index/First\""); + testRunner.Then("the 1st route\'s url is \"Index/First\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 9 - testRunner.And("the 2nd route\'s url is \"Index/Second\""); + testRunner.And("the 2nd route\'s url is \"Index/Second\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 10 - testRunner.And("the 3rd route\'s url is \"Index/Third\""); + testRunner.And("the 3rd route\'s url is \"Index/Third\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 11 - testRunner.And("the 4th route\'s url is \"Index/Fourth\""); + testRunner.And("the 4th route\'s url is \"Index/Fourth\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 12 - testRunner.And("the 5th route\'s url is \"Index/Fifth\""); + testRunner.And("the 5th route\'s url is \"Index/Fifth\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 13 - testRunner.And("the 6th route\'s url is \"Index/Sixth\""); + testRunner.And("the 6th route\'s url is \"Index/Sixth\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 14 - testRunner.And("the 7th route\'s url is \"Index/Seventh\""); + testRunner.And("the 7th route\'s url is \"Index/Seventh\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 16 - testRunner.Given("I have registered the routes for the HttpRoutePrecedenceAmongRoutesController"); + testRunner.Given("I have registered the routes for the HttpRoutePrecedenceAmongRoutesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 17 testRunner.When("I fetch the routes for the HttpRoutePrecedenceAmongRoutes controller\'s Get action" + - ""); + "", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 18 - testRunner.Then("the 1st route\'s url is \"Get/First\""); + testRunner.Then("the 1st route\'s url is \"Get/First\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 19 - testRunner.And("the 2nd route\'s url is \"Get/Second\""); + testRunner.And("the 2nd route\'s url is \"Get/Second\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 20 - testRunner.And("the 3rd route\'s url is \"Get/Third\""); + testRunner.And("the 3rd route\'s url is \"Get/Third\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 21 - testRunner.And("the 4th route\'s url is \"Get/Fourth\""); + testRunner.And("the 4th route\'s url is \"Get/Fourth\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 22 - testRunner.And("the 5th route\'s url is \"Get/Fifth\""); + testRunner.And("the 5th route\'s url is \"Get/Fifth\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 23 - testRunner.And("the 6th route\'s url is \"Get/Sixth\""); + testRunner.And("the 6th route\'s url is \"Get/Sixth\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 24 - testRunner.And("the 7th route\'s url is \"Get/Seventh\""); + testRunner.And("the 7th route\'s url is \"Get/Seventh\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -122,43 +122,43 @@ public virtual void RoutePrecedenceAmongActionsWithinAControllerUsingThePreceden #line 26 this.ScenarioSetup(scenarioInfo); #line 28 - testRunner.Given("I have registered the routes for the RoutePrecedenceAmongActionsController"); + testRunner.Given("I have registered the routes for the RoutePrecedenceAmongActionsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 29 - testRunner.When("I fetch the routes for the RoutePrecedenceAmongActions controller"); + testRunner.When("I fetch the routes for the RoutePrecedenceAmongActions controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 30 - testRunner.Then("the 1st route\'s url is \"Route0\""); + testRunner.Then("the 1st route\'s url is \"Route0\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 31 - testRunner.Then("the 2st route\'s url is \"Route1\""); + testRunner.Then("the 2st route\'s url is \"Route1\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 32 - testRunner.And("the 3nd route\'s url is \"Route2\""); + testRunner.And("the 3nd route\'s url is \"Route2\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 33 - testRunner.And("the 4rd route\'s url is \"Route3\""); + testRunner.And("the 4rd route\'s url is \"Route3\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 34 - testRunner.And("the 5th route\'s url is \"Route4\""); + testRunner.And("the 5th route\'s url is \"Route4\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 35 - testRunner.And("the 6th route\'s url is \"Route5\""); + testRunner.And("the 6th route\'s url is \"Route5\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 36 - testRunner.And("the 7th route\'s url is \"Route6\""); + testRunner.And("the 7th route\'s url is \"Route6\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 37 - testRunner.And("the 8th route\'s url is \"Route7\""); + testRunner.And("the 8th route\'s url is \"Route7\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 39 - testRunner.Given("I have registered the routes for the HttpRoutePrecedenceAmongActionsController"); + testRunner.Given("I have registered the routes for the HttpRoutePrecedenceAmongActionsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 40 - testRunner.When("I fetch the routes for the HttpRoutePrecedenceAmongActions controller"); + testRunner.When("I fetch the routes for the HttpRoutePrecedenceAmongActions controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 41 - testRunner.Then("the 1st route\'s url is \"ApiRoute1\""); + testRunner.Then("the 1st route\'s url is \"ApiRoute1\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 42 - testRunner.And("the 2nd route\'s url is \"ApiRoute2\""); + testRunner.And("the 2nd route\'s url is \"ApiRoute2\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 43 - testRunner.And("the 3rd route\'s url is \"ApiRoute3\""); + testRunner.And("the 3rd route\'s url is \"ApiRoute3\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 44 - testRunner.And("the 4th route\'s url is \"ApiRoute4\""); + testRunner.And("the 4th route\'s url is \"ApiRoute4\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 45 - testRunner.And("the 5th route\'s url is \"ApiRoute5\""); + testRunner.And("the 5th route\'s url is \"ApiRoute5\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 46 - testRunner.And("the 6th route\'s url is \"ApiRoute6\""); + testRunner.And("the 6th route\'s url is \"ApiRoute6\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 47 - testRunner.And("the 7th route\'s url is \"ApiRoute7\""); + testRunner.And("the 7th route\'s url is \"ApiRoute7\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -171,37 +171,37 @@ public virtual void RoutePrecedenceSetForTheSiteUsingTheSitePrecedenceProperty() #line 49 this.ScenarioSetup(scenarioInfo); #line 52 - testRunner.Given("I have a new configuration object"); + testRunner.Given("I have a new configuration object", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 53 - testRunner.And("I add the routes from the RoutePrecedenceAmongControllers1 controller"); + testRunner.And("I add the routes from the RoutePrecedenceAmongControllers1 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 54 - testRunner.And("I add the routes from the RoutePrecedenceAmongTheSitesRoutes controller"); + testRunner.And("I add the routes from the RoutePrecedenceAmongTheSitesRoutes controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 55 - testRunner.When("I generate the routes with this configuration"); + testRunner.When("I generate the routes with this configuration", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 56 - testRunner.And("I fetch all the routes"); + testRunner.And("I fetch all the routes", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 57 - testRunner.Then("the 1st route\'s url is \"The-First-Route\""); + testRunner.Then("the 1st route\'s url is \"The-First-Route\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 58 - testRunner.Then("the 2nd route\'s url is \"Controller1/Index\""); + testRunner.Then("the 2nd route\'s url is \"Controller1/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 59 - testRunner.Then("the 3rd route\'s url is \"The-Last-Route\""); + testRunner.Then("the 3rd route\'s url is \"The-Last-Route\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 61 - testRunner.Given("I have a new configuration object"); + testRunner.Given("I have a new configuration object", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 62 - testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers1 controller"); + testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers1 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 63 - testRunner.And("I add the routes from the HttpRoutePrecedenceAmongTheSitesRoutes controller"); + testRunner.And("I add the routes from the HttpRoutePrecedenceAmongTheSitesRoutes controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 64 - testRunner.When("I generate the routes with this configuration"); + testRunner.When("I generate the routes with this configuration", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 65 - testRunner.And("I fetch all the routes"); + testRunner.And("I fetch all the routes", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 66 - testRunner.Then("the 1st route\'s url is \"The-First-Route\""); + testRunner.Then("the 1st route\'s url is \"The-First-Route\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 67 - testRunner.Then("the 2nd route\'s url is \"ApiController1/Get\""); + testRunner.Then("the 2nd route\'s url is \"ApiController1/Get\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 68 - testRunner.Then("the 3rd route\'s url is \"The-Last-Route\""); + testRunner.Then("the 3rd route\'s url is \"The-Last-Route\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -214,42 +214,42 @@ public virtual void RoutePrecedenceSetViaOrderPrecedenceAndSitePrecedencePropert #line 70 this.ScenarioSetup(scenarioInfo); #line 72 - testRunner.Given("I have registered the routes for the RoutePrecedenceViaRoutePropertiesController"); + testRunner.Given("I have registered the routes for the RoutePrecedenceViaRoutePropertiesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 73 - testRunner.When("I fetch all the routes"); + testRunner.When("I fetch all the routes", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 74 - testRunner.Then("the 1st route\'s url is \"Route1\""); + testRunner.Then("the 1st route\'s url is \"Route1\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 75 - testRunner.And("the 2nd route\'s url is \"Route2\""); + testRunner.And("the 2nd route\'s url is \"Route2\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 76 - testRunner.And("the 3rd route\'s url is \"Route3\""); + testRunner.And("the 3rd route\'s url is \"Route3\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 77 - testRunner.And("the 4th route\'s url is \"Route4\""); + testRunner.And("the 4th route\'s url is \"Route4\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 78 - testRunner.And("the 5th route\'s url is \"Route5\""); + testRunner.And("the 5th route\'s url is \"Route5\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 79 - testRunner.And("the 6th route\'s url is \"Route6\""); + testRunner.And("the 6th route\'s url is \"Route6\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 80 - testRunner.And("the 7th route\'s url is \"Route7\""); + testRunner.And("the 7th route\'s url is \"Route7\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 82 testRunner.Given("I have registered the routes for the HttpRoutePrecedenceViaRoutePropertiesControl" + - "ler"); + "ler", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 83 - testRunner.When("I fetch all the routes"); + testRunner.When("I fetch all the routes", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 84 - testRunner.Then("the 1st route\'s url is \"ApiRoute1\""); + testRunner.Then("the 1st route\'s url is \"ApiRoute1\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 85 - testRunner.And("the 2nd route\'s url is \"ApiRoute2\""); + testRunner.And("the 2nd route\'s url is \"ApiRoute2\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 86 - testRunner.And("the 3rd route\'s url is \"ApiRoute3\""); + testRunner.And("the 3rd route\'s url is \"ApiRoute3\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 87 - testRunner.And("the 4th route\'s url is \"ApiRoute4\""); + testRunner.And("the 4th route\'s url is \"ApiRoute4\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 88 - testRunner.And("the 5th route\'s url is \"ApiRoute5\""); + testRunner.And("the 5th route\'s url is \"ApiRoute5\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 89 - testRunner.And("the 6th route\'s url is \"ApiRoute6\""); + testRunner.And("the 6th route\'s url is \"ApiRoute6\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 90 - testRunner.And("the 7th route\'s url is \"ApiRoute7\""); + testRunner.And("the 7th route\'s url is \"ApiRoute7\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -264,69 +264,69 @@ public virtual void RoutePrecedenceAmongControllersAddedIndividuallyUsingTheConf #line 92 this.ScenarioSetup(scenarioInfo); #line 94 - testRunner.Given("I have a new configuration object"); + testRunner.Given("I have a new configuration object", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 95 - testRunner.And("I add the routes from the RoutePrecedenceAmongControllers1 controller"); + testRunner.And("I add the routes from the RoutePrecedenceAmongControllers1 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 96 - testRunner.And("I add the routes from the RoutePrecedenceAmongControllers2 controller"); + testRunner.And("I add the routes from the RoutePrecedenceAmongControllers2 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 97 - testRunner.And("I add the routes from the RoutePrecedenceAmongControllers3 controller"); + testRunner.And("I add the routes from the RoutePrecedenceAmongControllers3 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 98 - testRunner.And("I add the Mvc routes from the executing assembly"); + testRunner.And("I add the Mvc routes from the executing assembly", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 99 - testRunner.And("I add the routes from the RoutePrecedenceAmongControllers4 controller"); + testRunner.And("I add the routes from the RoutePrecedenceAmongControllers4 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 100 - testRunner.And("I add the routes from the RoutePrecedenceAmongControllers5 controller"); + testRunner.And("I add the routes from the RoutePrecedenceAmongControllers5 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 101 - testRunner.When("I generate the routes with this configuration"); + testRunner.When("I generate the routes with this configuration", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 102 testRunner.Then("the routes from the RoutePrecedenceAmongControllers1 controller precede those fro" + - "m the RoutePrecedenceAmongControllers2 controller"); + "m the RoutePrecedenceAmongControllers2 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 103 testRunner.And("the routes from the RoutePrecedenceAmongControllers2 controller precede those fro" + - "m the RoutePrecedenceAmongControllers3 controller"); + "m the RoutePrecedenceAmongControllers3 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 104 testRunner.And("the routes from the RoutePrecedenceAmongControllers3 controller precede those fro" + - "m the StandardUsage controller"); + "m the StandardUsage controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 105 testRunner.And("the routes from the StandardUsage controller precede those from the RoutePreceden" + - "ceAmongControllers4 controller"); + "ceAmongControllers4 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 106 testRunner.And("the routes from the RoutePrecedenceAmongControllers4 controller precede those fro" + - "m the RoutePrecedenceAmongControllers5 controller"); + "m the RoutePrecedenceAmongControllers5 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 107 - testRunner.And("no routes follow the routes from the RoutePrecedenceAmongControllers5 controller"); + testRunner.And("no routes follow the routes from the RoutePrecedenceAmongControllers5 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 109 - testRunner.Given("I have a new configuration object"); + testRunner.Given("I have a new configuration object", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 110 - testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers1 controller"); + testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers1 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 111 - testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers2 controller"); + testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers2 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 112 - testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers3 controller"); + testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers3 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 113 - testRunner.And("I add the Http routes from the executing assembly"); + testRunner.And("I add the Http routes from the executing assembly", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 114 - testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers4 controller"); + testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers4 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 115 - testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers5 controller"); + testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers5 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 116 - testRunner.When("I generate the routes with this configuration"); + testRunner.When("I generate the routes with this configuration", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 117 testRunner.Then("the routes from the HttpRoutePrecedenceAmongControllers1 controller precede those" + - " from the HttpRoutePrecedenceAmongControllers2 controller"); + " from the HttpRoutePrecedenceAmongControllers2 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 118 testRunner.And("the routes from the HttpRoutePrecedenceAmongControllers2 controller precede those" + - " from the HttpRoutePrecedenceAmongControllers3 controller"); + " from the HttpRoutePrecedenceAmongControllers3 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 119 testRunner.And("the routes from the HttpStandardUsage controller precede those from the HttpRoute" + - "PrecedenceAmongControllers4 controller"); + "PrecedenceAmongControllers4 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 120 testRunner.And("the routes from the HttpRoutePrecedenceAmongControllers4 controller precede those" + - " from the HttpRoutePrecedenceAmongControllers5 controller"); + " from the HttpRoutePrecedenceAmongControllers5 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 121 testRunner.And("no routes follow the routes from the HttpRoutePrecedenceAmongControllers5 control" + - "ler"); + "ler", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -341,35 +341,35 @@ public virtual void RoutePrecedenceAmongControllersAddedByBaseTypeUsingTheConfig #line 123 this.ScenarioSetup(scenarioInfo); #line 125 - testRunner.Given("I have a new configuration object"); + testRunner.Given("I have a new configuration object", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 126 testRunner.And("I add the routes from controllers derived from the RoutePrecedenceAmongDerivedCon" + - "trollersBase controller"); + "trollersBase controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 127 - testRunner.And("I add the routes from the RoutePrecedenceAmongControllers1 controller"); + testRunner.And("I add the routes from the RoutePrecedenceAmongControllers1 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 128 - testRunner.When("I generate the routes with this configuration"); + testRunner.When("I generate the routes with this configuration", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 129 testRunner.Then("the routes from the RoutePrecedenceAmongDerivedControllers1 controller precede th" + - "ose from the RoutePrecedenceAmongControllers1 controller"); + "ose from the RoutePrecedenceAmongControllers1 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 130 testRunner.And("the routes from the RoutePrecedenceAmongDerivedControllers2 controller precede th" + - "ose from the RoutePrecedenceAmongControllers1 controller"); + "ose from the RoutePrecedenceAmongControllers1 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 132 - testRunner.Given("I have a new configuration object"); + testRunner.Given("I have a new configuration object", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 133 testRunner.And("I add the routes from controllers derived from the HttpRoutePrecedenceAmongDerive" + - "dControllersBase controller"); + "dControllersBase controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 134 - testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers1 controller"); + testRunner.And("I add the routes from the HttpRoutePrecedenceAmongControllers1 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 135 - testRunner.When("I generate the routes with this configuration"); + testRunner.When("I generate the routes with this configuration", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 136 testRunner.Then("the routes from the HttpRoutePrecedenceAmongDerivedControllers1 controller preced" + - "e those from the HttpRoutePrecedenceAmongControllers1 controller"); + "e those from the HttpRoutePrecedenceAmongControllers1 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 137 testRunner.And("the routes from the HttpRoutePrecedenceAmongDerivedControllers2 controller preced" + - "e those from the HttpRoutePrecedenceAmongControllers1 controller"); + "e those from the HttpRoutePrecedenceAmongControllers1 controller", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs index 07d92d5..d5cc48d 100644 --- a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs +++ b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs @@ -1,8 +1,8 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.8.1.0 -// SpecFlow Generator Version:1.8.0.0 +// SpecFlow Version:1.9.1.84 +// SpecFlow Generator Version:1.9.0.0 // Runtime Version:4.0.30319.17929 // // Changes to this file may cause incorrect behavior and will be lost if @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.8.1.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Route Prefixes")] @@ -72,17 +72,17 @@ public virtual void GeneratingPrefixedRoutes() #line 3 this.ScenarioSetup(scenarioInfo); #line 5 - testRunner.Given("I have registered the routes for the RoutePrefixesController"); + testRunner.Given("I have registered the routes for the RoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 6 - testRunner.When("I fetch the routes for the RoutePrefixes controller\'s Index action"); + testRunner.When("I fetch the routes for the RoutePrefixes controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 7 - testRunner.Then("the route url is \"Prefix/Index\""); + testRunner.Then("the route url is \"Prefix/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 9 - testRunner.Given("I have registered the routes for the HttpRoutePrefixesController"); + testRunner.Given("I have registered the routes for the HttpRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 10 - testRunner.When("I fetch the routes for the HttpRoutePrefixes controller\'s Get action"); + testRunner.When("I fetch the routes for the HttpRoutePrefixes controller\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 11 - testRunner.Then("the route url is \"ApiPrefix/Get\""); + testRunner.Then("the route url is \"ApiPrefix/Get\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -95,17 +95,17 @@ public virtual void GeneratingPrefixedRoutesWhenRouteUrlsSpecifyADuplicatePrefix #line 13 this.ScenarioSetup(scenarioInfo); #line 15 - testRunner.Given("I have registered the routes for the RoutePrefixesController"); + testRunner.Given("I have registered the routes for the RoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 16 - testRunner.When("I fetch the routes for the RoutePrefixes controller\'s DuplicatePrefix action"); + testRunner.When("I fetch the routes for the RoutePrefixes controller\'s DuplicatePrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 17 - testRunner.Then("the route url is \"Prefix/DuplicatePrefix\""); + testRunner.Then("the route url is \"Prefix/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 19 - testRunner.Given("I have registered the routes for the HttpRoutePrefixesController"); + testRunner.Given("I have registered the routes for the HttpRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 20 - testRunner.When("I fetch the routes for the HttpRoutePrefixes controller\'s DuplicatePrefix action"); + testRunner.When("I fetch the routes for the HttpRoutePrefixes controller\'s DuplicatePrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 21 - testRunner.Then("the route url is \"ApiPrefix/DuplicatePrefix\""); + testRunner.Then("the route url is \"ApiPrefix/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -118,17 +118,17 @@ public virtual void GeneratingAbsoluteRoutesWhenARoutePrefixIsDefined() #line 23 this.ScenarioSetup(scenarioInfo); #line 25 - testRunner.Given("I have registered the routes for the RoutePrefixesController"); + testRunner.Given("I have registered the routes for the RoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 26 - testRunner.When("I fetch the routes for the RoutePrefixes controller\'s Absolute action"); + testRunner.When("I fetch the routes for the RoutePrefixes controller\'s Absolute action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 27 - testRunner.Then("the route url is \"PrefixAbsolute\""); + testRunner.Then("the route url is \"PrefixAbsolute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 29 - testRunner.Given("I have registered the routes for the HttpRoutePrefixesController"); + testRunner.Given("I have registered the routes for the HttpRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 30 - testRunner.When("I fetch the routes for the HttpRoutePrefixes controller\'s Absolute action"); + testRunner.When("I fetch the routes for the HttpRoutePrefixes controller\'s Absolute action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 31 - testRunner.Then("the route url is \"ApiPrefixAbsolute\""); + testRunner.Then("the route url is \"ApiPrefixAbsolute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -141,19 +141,19 @@ public virtual void GeneratingPrefixedRoutesWhenRouteUrlStartsWithTheRoutePrefix #line 33 this.ScenarioSetup(scenarioInfo); #line 35 - testRunner.Given("I have registered the routes for the RoutePrefixesController"); + testRunner.Given("I have registered the routes for the RoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 36 testRunner.When("I fetch the routes for the RoutePrefixes controller\'s RouteBeginsWithRoutePrefix " + - "action"); + "action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 37 - testRunner.Then("the route url is \"Prefix/Prefixer\""); + testRunner.Then("the route url is \"Prefix/Prefixer\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 39 - testRunner.Given("I have registered the routes for the HttpRoutePrefixesController"); + testRunner.Given("I have registered the routes for the HttpRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 40 testRunner.When("I fetch the routes for the HttpRoutePrefixes controller\'s RouteBeginsWithRoutePre" + - "fix action"); + "fix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 41 - testRunner.Then("the route url is \"ApiPrefix/ApiPrefixer\""); + testRunner.Then("the route url is \"ApiPrefix/ApiPrefixer\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -166,17 +166,17 @@ public virtual void GeneratingPrefixedAreaRoutes() #line 43 this.ScenarioSetup(scenarioInfo); #line 45 - testRunner.Given("I have registered the routes for the AreaRoutePrefixesController"); + testRunner.Given("I have registered the routes for the AreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 46 - testRunner.When("I fetch the routes for the AreaRoutePrefixes controller\'s Index action"); + testRunner.When("I fetch the routes for the AreaRoutePrefixes controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 47 - testRunner.Then("the route url is \"Area/Prefix/Index\""); + testRunner.Then("the route url is \"Area/Prefix/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 49 - testRunner.Given("I have registered the routes for the HttpAreaRoutePrefixesController"); + testRunner.Given("I have registered the routes for the HttpAreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 50 - testRunner.When("I fetch the routes for the HttpAreaRoutePrefixes controller\'s Get action"); + testRunner.When("I fetch the routes for the HttpAreaRoutePrefixes controller\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 51 - testRunner.Then("the route url is \"ApiArea/ApiPrefix/Get\""); + testRunner.Then("the route url is \"ApiArea/ApiPrefix/Get\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -189,18 +189,18 @@ public virtual void GeneratingPrefixedAreaRoutesWhenRouteUrlsSpecifyADuplicatePr #line 53 this.ScenarioSetup(scenarioInfo); #line 55 - testRunner.Given("I have registered the routes for the AreaRoutePrefixesController"); + testRunner.Given("I have registered the routes for the AreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 56 - testRunner.When("I fetch the routes for the AreaRoutePrefixes controller\'s DuplicatePrefix action"); + testRunner.When("I fetch the routes for the AreaRoutePrefixes controller\'s DuplicatePrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 57 - testRunner.Then("the route url is \"Area/Prefix/DuplicatePrefix\""); + testRunner.Then("the route url is \"Area/Prefix/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 59 - testRunner.Given("I have registered the routes for the HttpAreaRoutePrefixesController"); + testRunner.Given("I have registered the routes for the HttpAreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 60 testRunner.When("I fetch the routes for the HttpAreaRoutePrefixes controller\'s DuplicatePrefix act" + - "ion"); + "ion", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 61 - testRunner.Then("the route url is \"ApiArea/ApiPrefix/DuplicatePrefix\""); + testRunner.Then("the route url is \"ApiArea/ApiPrefix/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -213,17 +213,17 @@ public virtual void GeneratingAbsoluteRoutesWhenARouteAreaAndRoutePrefixIsDefine #line 63 this.ScenarioSetup(scenarioInfo); #line 65 - testRunner.Given("I have registered the routes for the AreaRoutePrefixesController"); + testRunner.Given("I have registered the routes for the AreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 66 - testRunner.When("I fetch the routes for the AreaRoutePrefixes controller\'s Absolute action"); + testRunner.When("I fetch the routes for the AreaRoutePrefixes controller\'s Absolute action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 67 - testRunner.Then("the route url is \"AreaPrefixAbsolute\""); + testRunner.Then("the route url is \"AreaPrefixAbsolute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 69 - testRunner.Given("I have registered the routes for the HttpAreaRoutePrefixesController"); + testRunner.Given("I have registered the routes for the HttpAreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 70 - testRunner.When("I fetch the routes for the HttpAreaRoutePrefixes controller\'s Absolute action"); + testRunner.When("I fetch the routes for the HttpAreaRoutePrefixes controller\'s Absolute action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 71 - testRunner.Then("the route url is \"ApiAreaPrefixAbsolute\""); + testRunner.Then("the route url is \"ApiAreaPrefixAbsolute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -238,19 +238,19 @@ public virtual void GeneratingRoutesWhenARouteAreaAndRoutePrefixAreDefinedAndThe #line 73 this.ScenarioSetup(scenarioInfo); #line 75 - testRunner.Given("I have registered the routes for the AreaRoutePrefixesController"); + testRunner.Given("I have registered the routes for the AreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 76 testRunner.When("I fetch the routes for the AreaRoutePrefixes controller\'s RelativeUrlIsAreaUrl ac" + - "tion"); + "tion", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 77 - testRunner.Then("the route url is \"Area/Prefix/Area\""); + testRunner.Then("the route url is \"Area/Prefix/Area\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 79 - testRunner.Given("I have registered the routes for the HttpAreaRoutePrefixesController"); + testRunner.Given("I have registered the routes for the HttpAreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 80 testRunner.When("I fetch the routes for the HttpAreaRoutePrefixes controller\'s RelativeUrlIsAreaUr" + - "l action"); + "l action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 81 - testRunner.Then("the route url is \"ApiArea/ApiPrefix/ApiArea\""); + testRunner.Then("the route url is \"ApiArea/ApiPrefix/ApiArea\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Features/StandardUsage.feature.cs b/src/AttributeRouting.Specs/Features/StandardUsage.feature.cs index 0f4a636..c7b3042 100644 --- a/src/AttributeRouting.Specs/Features/StandardUsage.feature.cs +++ b/src/AttributeRouting.Specs/Features/StandardUsage.feature.cs @@ -1,8 +1,8 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.8.1.0 -// SpecFlow Generator Version:1.8.0.0 +// SpecFlow Version:1.9.1.84 +// SpecFlow Generator Version:1.9.0.0 // Runtime Version:4.0.30319.17929 // // Changes to this file may cause incorrect behavior and will be lost if @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.8.1.0")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Standard Usage")] @@ -66,68 +66,68 @@ public virtual void ScenarioCleanup() [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Generating routes for an MVC controller")] - [NUnit.Framework.TestCaseAttribute("GET", "Index", "", new string[0])] - [NUnit.Framework.TestCaseAttribute("HEAD", "Index", "", new string[0])] - [NUnit.Framework.TestCaseAttribute("POST", "Create", "Create", new string[0])] - [NUnit.Framework.TestCaseAttribute("PUT", "Update", "Update/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("DELETE", "Destroy", "Destroy/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("GET", "Wildcards", "Wildcards/{*pathInfo}", new string[0])] - [NUnit.Framework.TestCaseAttribute("", "AnyVerb", "AnyVerb", new string[0])] + [NUnit.Framework.TestCaseAttribute("GET", "Index", "", null)] + [NUnit.Framework.TestCaseAttribute("HEAD", "Index", "", null)] + [NUnit.Framework.TestCaseAttribute("POST", "Create", "Create", null)] + [NUnit.Framework.TestCaseAttribute("PUT", "Update", "Update/{id}", null)] + [NUnit.Framework.TestCaseAttribute("DELETE", "Destroy", "Destroy/{id}", null)] + [NUnit.Framework.TestCaseAttribute("GET", "Wildcards", "Wildcards/{*pathInfo}", null)] + [NUnit.Framework.TestCaseAttribute("", "AnyVerb", "AnyVerb", null)] public virtual void GeneratingRoutesForAnMVCController(string method, string action, string url, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes for an MVC controller", exampleTags); #line 3 this.ScenarioSetup(scenarioInfo); #line 4 - testRunner.Given("I have registered the routes for the StandardUsageController"); + testRunner.Given("I have registered the routes for the StandardUsageController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 5 - testRunner.When(string.Format("I fetch the routes for the StandardUsageController\'s {0} action", action)); + testRunner.When(string.Format("I fetch the routes for the StandardUsageController\'s {0} action", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 6 - testRunner.Then(string.Format("the route is constrained to {0} requests", method)); + testRunner.Then(string.Format("the route is constrained to {0} requests", method), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 7 - testRunner.And(string.Format("the route url is \"{0}\"", url)); + testRunner.And(string.Format("the route url is \"{0}\"", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 8 - testRunner.And("the default for \"controller\" is \"StandardUsage\""); + testRunner.And("the default for \"controller\" is \"StandardUsage\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 9 - testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action)); + testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 10 - testRunner.And("the namespace is \"AttributeRouting.Specs.Subjects\""); + testRunner.And("the namespace is \"AttributeRouting.Specs.Subjects\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Generating routes for an API controller")] - [NUnit.Framework.TestCaseAttribute("GET", "Get", "api", new string[0])] - [NUnit.Framework.TestCaseAttribute("HEAD", "Get", "api", new string[0])] - [NUnit.Framework.TestCaseAttribute("OPTIONS", "Get", "api", new string[0])] - [NUnit.Framework.TestCaseAttribute("POST", "Post", "api", new string[0])] - [NUnit.Framework.TestCaseAttribute("OPTIONS", "Post", "api", new string[0])] - [NUnit.Framework.TestCaseAttribute("PUT", "Put", "api/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("OPTIONS", "Put", "api/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("DELETE", "Delete", "api/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("OPTIONS", "Delete", "api/{id}", new string[0])] - [NUnit.Framework.TestCaseAttribute("GET", "Wildcards", "api/Wildcards/{*pathInfo}", new string[0])] - [NUnit.Framework.TestCaseAttribute("", "AnyVerb", "api/AnyVerb", new string[0])] + [NUnit.Framework.TestCaseAttribute("GET", "Get", "api", null)] + [NUnit.Framework.TestCaseAttribute("HEAD", "Get", "api", null)] + [NUnit.Framework.TestCaseAttribute("OPTIONS", "Get", "api", null)] + [NUnit.Framework.TestCaseAttribute("POST", "Post", "api", null)] + [NUnit.Framework.TestCaseAttribute("OPTIONS", "Post", "api", null)] + [NUnit.Framework.TestCaseAttribute("PUT", "Put", "api/{id}", null)] + [NUnit.Framework.TestCaseAttribute("OPTIONS", "Put", "api/{id}", null)] + [NUnit.Framework.TestCaseAttribute("DELETE", "Delete", "api/{id}", null)] + [NUnit.Framework.TestCaseAttribute("OPTIONS", "Delete", "api/{id}", null)] + [NUnit.Framework.TestCaseAttribute("GET", "Wildcards", "api/Wildcards/{*pathInfo}", null)] + [NUnit.Framework.TestCaseAttribute("", "AnyVerb", "api/AnyVerb", null)] public virtual void GeneratingRoutesForAnAPIController(string method, string action, string url, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes for an API controller", exampleTags); #line 22 this.ScenarioSetup(scenarioInfo); #line 23 - testRunner.Given("I have registered the routes for the HttpStandardUsageController"); + testRunner.Given("I have registered the routes for the HttpStandardUsageController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 24 - testRunner.When(string.Format("I fetch the routes for the HttpStandardUsageController\'s {0} action", action)); + testRunner.When(string.Format("I fetch the routes for the HttpStandardUsageController\'s {0} action", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 25 - testRunner.Then(string.Format("the route is constrained to {0} requests", method)); + testRunner.Then(string.Format("the route is constrained to {0} requests", method), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 26 - testRunner.And(string.Format("the route url is \"{0}\"", url)); + testRunner.And(string.Format("the route url is \"{0}\"", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 27 - testRunner.And("the default for \"controller\" is \"HttpStandardUsage\""); + testRunner.And("the default for \"controller\" is \"HttpStandardUsage\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 28 - testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action)); + testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 29 - testRunner.And("the namespace is \"AttributeRouting.Specs.Subjects.Http\""); + testRunner.And("the namespace is \"AttributeRouting.Specs.Subjects.Http\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -140,11 +140,11 @@ public virtual void RespondingToOPTIONSRequestsInAnAPIController() #line 45 this.ScenarioSetup(scenarioInfo); #line 46 - testRunner.Given("I have registered the routes for the HttpStandardUsageController"); + testRunner.Given("I have registered the routes for the HttpStandardUsageController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 47 - testRunner.When("an OPTIONS request for \"api\" is made"); + testRunner.When("an OPTIONS request for \"api\" is made", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 48 - testRunner.Then("the Get action is matched"); + testRunner.Then("the Get action is matched", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Subjects/Http/HttpInlineRouteConstraintsControllers.cs b/src/AttributeRouting.Specs/Subjects/Http/HttpInlineRouteConstraintsControllers.cs index 651b457..2d10b6e 100644 --- a/src/AttributeRouting.Specs/Subjects/Http/HttpInlineRouteConstraintsControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/Http/HttpInlineRouteConstraintsControllers.cs @@ -135,6 +135,12 @@ public string Regex(int x) return ""; } + [GET(@"RegexRange/{x:regex(\w{1,8})}")] + public string RegexRange(string x) + { + return x; + } + [GET("Compound/{x:int:max(10)}")] public string Compound(int x) { diff --git a/src/AttributeRouting.Specs/Subjects/InlineRouteConstraintsControllers.cs b/src/AttributeRouting.Specs/Subjects/InlineRouteConstraintsControllers.cs index 9b4e6c6..44ee752 100644 --- a/src/AttributeRouting.Specs/Subjects/InlineRouteConstraintsControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/InlineRouteConstraintsControllers.cs @@ -135,6 +135,12 @@ public string Regex(string x) return x; } + [GET(@"RegexRange/{x:regex(\w{1,8})}")] + public string RegexRange(string x) + { + return x; + } + [GET("Compound/{x:int:max(10)}")] public string Compound(int x) { diff --git a/src/AttributeRouting.Specs/app.config b/src/AttributeRouting.Specs/app.config index 59e2304..f5d5a98 100644 --- a/src/AttributeRouting.Specs/app.config +++ b/src/AttributeRouting.Specs/app.config @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/src/AttributeRouting.Specs/packages.config b/src/AttributeRouting.Specs/packages.config index 929efc5..a5c49be 100644 --- a/src/AttributeRouting.Specs/packages.config +++ b/src/AttributeRouting.Specs/packages.config @@ -11,5 +11,5 @@ - + \ No newline at end of file diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 7d36919..66e2ff7 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -232,7 +232,14 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro // Constraint of the form "firstName:string(50)" var indexOfOpenParen = definition.IndexOf('('); constraintName = definition.Substring(0, indexOfOpenParen); - var constraintParams = definition.Substring(indexOfOpenParen + 1, definition.Length - indexOfOpenParen - 2).SplitAndTrim(","); + + // Parse constraint params. + // NOTE: Splitting on commas only applies to non-regex constraints. + var constraintParamsRaw = definition.Substring(indexOfOpenParen + 1, definition.Length - indexOfOpenParen - 2); + var constraintParams = constraintName.ValueEquals("regex") + ? new[] {constraintParamsRaw} + : constraintParamsRaw.SplitAndTrim(","); + constraint = constraintFactory.CreateInlineRouteConstraint(constraintName, constraintParams); } else From 2540cb9a4885136ef270c5d6eb46f544d714b4e3 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Thu, 29 Nov 2012 13:22:29 -0700 Subject: [PATCH 02/97] #132 - basic support for constraints in the querystring. --- .../Features/RouteConstraints.feature | 14 +++ .../Features/RouteConstraints.feature.cs | 107 ++++++++++++------ .../Steps/RouteConstraintSteps.cs | 12 +- .../Steps/SharedSteps.cs | 9 +- .../HttpInlineRouteConstraintsControllers.cs | 6 + .../InlineRouteConstraintsControllers.cs | 6 + .../AttributeRouting.Tests.Web.csproj | 5 + .../QueryStringConstraintController.cs | 20 ++++ .../Factories/RouteConstraintFactory.cs | 5 + .../Factories/RouteConstraintFactory.cs | 5 + .../AttributeRouting.Web.Http.csproj | 2 + .../QueryStringRouteConstraintWrapper.cs | 40 +++++++ .../Factories/RouteConstraintFactory.cs | 5 + .../AttributeRouting.Web.csproj | 1 + .../QueryStringRouteConstraintWrapper.cs | 38 +++++++ src/AttributeRouting/AttributeRouting.csproj | 1 + .../IQueryStringRouteConstraintWrapper.cs | 21 ++++ .../Factories/IRouteConstraintFactory.cs | 6 + .../Framework/RouteBuilder.cs | 78 ++++++++++--- 19 files changed, 326 insertions(+), 55 deletions(-) create mode 100644 src/AttributeRouting.Tests.Web/Controllers/QueryStringConstraintController.cs create mode 100644 src/AttributeRouting.Web.Http/Constraints/QueryStringRouteConstraintWrapper.cs create mode 100644 src/AttributeRouting.Web/Constraints/QueryStringRouteConstraintWrapper.cs create mode 100644 src/AttributeRouting/Constraints/IQueryStringRouteConstraintWrapper.cs diff --git a/src/AttributeRouting.Specs/Features/RouteConstraints.feature b/src/AttributeRouting.Specs/Features/RouteConstraints.feature index c8aea4e..c8db6d5 100644 --- a/src/AttributeRouting.Specs/Features/RouteConstraints.feature +++ b/src/AttributeRouting.Specs/Features/RouteConstraints.feature @@ -34,6 +34,18 @@ Scenario Outline: Inline constraints | Compound | IntRouteConstraint | | Compound | MaxRouteConstraint | +Scenario: Inline constraints in the querystring + # MVC + Given I have registered the routes for the InlineRouteConstraintsController + When I fetch the routes for the InlineRouteConstraints controller's Querystring action + Then the route url is "Inline-Constraints/Querystring" + And the parameter "x" is constrained by an inline AttributeRouting.Web.Constraints.IntRouteConstraint + # Web API + Given I have registered the routes for the HttpInlineRouteConstraintsController + When I fetch the routes for the HttpInlineRouteConstraints controller's Querystring action + Then the route url is "Http-Inline-Constraints/Querystring" + And the parameter "x" is constrained by an inline AttributeRouting.Web.Constraints.IntRouteConstraint + Scenario: Multiple inline constraints per url segment # MVC Given I have registered the routes for the InlineRouteConstraintsController @@ -131,3 +143,5 @@ Scenario Outline: Matching inline route constraints | Enum/taupe | Enum | is not | | WithOptional | WithOptional | is | | WithDefault | WithDefault | is | + | Querystring?x=123 | Querystring | is | + | Querystring?x=abc | Querystring | is not | diff --git a/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs b/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs index e13d6d2..ea7a00b 100644 --- a/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs @@ -3,7 +3,7 @@ // This code was generated by SpecFlow (http://www.specflow.org/). // SpecFlow Version:1.9.0.77 // SpecFlow Generator Version:1.9.0.0 -// Runtime Version:4.0.30319.17929 +// Runtime Version:4.0.30319.18010 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -114,36 +114,67 @@ public virtual void InlineConstraints(string actionName, string constraintTypeNa } [NUnit.Framework.TestAttribute()] - [NUnit.Framework.DescriptionAttribute("Multiple inline constraints per url segment")] - public virtual void MultipleInlineConstraintsPerUrlSegment() + [NUnit.Framework.DescriptionAttribute("Inline constraints in the querystring")] + public virtual void InlineConstraintsInTheQuerystring() { - TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Multiple inline constraints per url segment", ((string[])(null))); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Inline constraints in the querystring", ((string[])(null))); #line 37 this.ScenarioSetup(scenarioInfo); #line 39 testRunner.Given("I have registered the routes for the InlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 40 + testRunner.When("I fetch the routes for the InlineRouteConstraints controller\'s Querystring action" + + "", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 41 + testRunner.Then("the route url is \"Inline-Constraints/Querystring\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 42 + testRunner.And("the parameter \"x\" is constrained by an inline AttributeRouting.Web.Constraints.In" + + "tRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 44 + testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 45 + testRunner.When("I fetch the routes for the HttpInlineRouteConstraints controller\'s Querystring ac" + + "tion", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 46 + testRunner.Then("the route url is \"Http-Inline-Constraints/Querystring\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 47 + testRunner.And("the parameter \"x\" is constrained by an inline AttributeRouting.Web.Constraints.In" + + "tRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Multiple inline constraints per url segment")] + public virtual void MultipleInlineConstraintsPerUrlSegment() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Multiple inline constraints per url segment", ((string[])(null))); +#line 49 +this.ScenarioSetup(scenarioInfo); +#line 51 + testRunner.Given("I have registered the routes for the InlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 52 testRunner.When("I fetch the routes for the InlineRouteConstraints controller\'s MultipleWithinUrlS" + "egment action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 41 +#line 53 testRunner.Then("the route url is \"Inline-Constraints/avatar/{width}x{height}/{image}\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 42 +#line 54 testRunner.And("the parameter \"width\" is constrained by an inline AttributeRouting.Web.Constraint" + "s.IntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 43 +#line 55 testRunner.And("the parameter \"height\" is constrained by an inline AttributeRouting.Web.Constrain" + "ts.IntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 45 +#line 57 testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 46 +#line 58 testRunner.When("I fetch the routes for the HttpInlineRouteConstraints controller\'s MultipleWithin" + "UrlSegment action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 47 +#line 59 testRunner.Then("the route url is \"Http-Inline-Constraints/avatar/{width}x{height}/{image}\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 48 +#line 60 testRunner.And("the parameter \"width\" is constrained by an inline AttributeRouting.Web.Constraint" + "s.IntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 49 +#line 61 testRunner.And("the parameter \"height\" is constrained by an inline AttributeRouting.Web.Constrain" + "ts.IntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden @@ -155,27 +186,27 @@ public virtual void MultipleInlineConstraintsPerUrlSegment() public virtual void InlineConstraintsSpecifiedInTheRoutePrefixAttribute() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Inline constraints specified in the RoutePrefixAttribute", ((string[])(null))); -#line 51 +#line 63 this.ScenarioSetup(scenarioInfo); -#line 53 +#line 65 testRunner.Given("I have registered the routes for the PrefixedInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 54 +#line 66 testRunner.When("I fetch the routes for the PrefixedInlineRouteConstraints controller\'s Index acti" + "on", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 55 +#line 67 testRunner.Then("the route url is \"Prefixed-Inline-Constraints/{id}/Howdy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 56 +#line 68 testRunner.And("the parameter \"id\" is constrained by an inline AttributeRouting.Web.Constraints.I" + "ntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 58 +#line 70 testRunner.Given("I have registered the routes for the HttpPrefixedInlineRouteConstraintsController" + "", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 59 +#line 71 testRunner.When("I fetch the routes for the HttpPrefixedInlineRouteConstraints controller\'s Index " + "action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 60 +#line 72 testRunner.Then("the route url is \"Http-Prefixed-Inline-Constraints/{id}/Howdy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 61 +#line 73 testRunner.And("the parameter \"id\" is constrained by an inline AttributeRouting.Web.Constraints.I" + "ntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden @@ -187,25 +218,25 @@ public virtual void InlineConstraintsSpecifiedInTheRoutePrefixAttribute() public virtual void InlineConstraintsSpecifiedInTheRouteAreaAttribute() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Inline constraints specified in the RouteAreaAttribute", ((string[])(null))); -#line 63 +#line 75 this.ScenarioSetup(scenarioInfo); -#line 65 +#line 77 testRunner.Given("I have registered the routes for the AreaInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 66 +#line 78 testRunner.When("I fetch the routes for the AreaInlineRouteConstraints controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 67 +#line 79 testRunner.Then("the route url is \"Area-Inline-Constraints/{id}/Howdy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 68 +#line 80 testRunner.And("the parameter \"id\" is constrained by an inline AttributeRouting.Web.Constraints.I" + "ntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 70 +#line 82 testRunner.Given("I have registered the routes for the HttpAreaInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 71 +#line 83 testRunner.When("I fetch the routes for the HttpAreaInlineRouteConstraints controller\'s Index acti" + "on", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 72 +#line 84 testRunner.Then("the route url is \"Http-Area-Inline-Constraints/{id}/Howdy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 73 +#line 85 testRunner.And("the parameter \"id\" is constrained by an inline AttributeRouting.Web.Constraints.I" + "ntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden @@ -262,22 +293,24 @@ public virtual void InlineConstraintsSpecifiedInTheRouteAreaAttribute() [NUnit.Framework.TestCaseAttribute("Enum/taupe", "Enum", "is not", null)] [NUnit.Framework.TestCaseAttribute("WithOptional", "WithOptional", "is", null)] [NUnit.Framework.TestCaseAttribute("WithDefault", "WithDefault", "is", null)] + [NUnit.Framework.TestCaseAttribute("Querystring?x=123", "Querystring", "is", null)] + [NUnit.Framework.TestCaseAttribute("Querystring?x=abc", "Querystring", "is not", null)] public virtual void MatchingInlineRouteConstraints(string url, string action, string condition, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Matching inline route constraints", exampleTags); -#line 75 +#line 87 this.ScenarioSetup(scenarioInfo); -#line 77 +#line 89 testRunner.Given("I have registered the routes for the InlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 78 +#line 90 testRunner.When(string.Format("a request for \"Inline-Constraints/{0}\" is made", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 79 +#line 91 testRunner.Then(string.Format("the {0} action {1} matched", action, condition), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 81 +#line 93 testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 82 +#line 94 testRunner.When(string.Format("a request for \"Http-Inline-Constraints/{0}\" is made", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 83 +#line 95 testRunner.Then(string.Format("the {0} action {1} matched", action, condition), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); diff --git a/src/AttributeRouting.Specs/Steps/RouteConstraintSteps.cs b/src/AttributeRouting.Specs/Steps/RouteConstraintSteps.cs index 915867d..5d4d239 100644 --- a/src/AttributeRouting.Specs/Steps/RouteConstraintSteps.cs +++ b/src/AttributeRouting.Specs/Steps/RouteConstraintSteps.cs @@ -37,12 +37,20 @@ public void ThenTheParameterIsConstrainedByAnInline(string key, string type) Assert.That(constraint, Is.Not.Null); + // If this is a querystring route constraint wrapper, then unwrap it. + var queryStringConstraint = constraint as IQueryStringRouteConstraintWrapper; + if (queryStringConstraint != null) + constraint = queryStringConstraint.Constraint; + var compoundRouteConstraint = constraint as ICompoundRouteConstraintWrapper; if (compoundRouteConstraint != null) - Assert.That(compoundRouteConstraint.Constraints.Any(c => c.GetType().FullName == type), - Is.True); + { + Assert.That(compoundRouteConstraint.Constraints.Any(c => c.GetType().FullName == type), Is.True); + } else + { Assert.That(constraint.GetType().FullName, Is.EqualTo(type)); + } } } diff --git a/src/AttributeRouting.Specs/Steps/SharedSteps.cs b/src/AttributeRouting.Specs/Steps/SharedSteps.cs index 6f9e242..beb65a1 100644 --- a/src/AttributeRouting.Specs/Steps/SharedSteps.cs +++ b/src/AttributeRouting.Specs/Steps/SharedSteps.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using System.Web; using System.Web.Http; using System.Web.Routing; using AttributeRouting.Helpers; @@ -163,11 +164,17 @@ public void WhenAMethodRequestForUrlIsMade(string method, string url) { var desiredMethod = (method.HasValue() ? method : "GET").Trim().ToUpperInvariant(); var requestMethod = (desiredMethod.ValueEquals("GET") ? "GET" : "POST").ToUpperInvariant(); + var pathAndQuery = url.SplitAndTrim("?"); var httpContextMock = MockBuilder.BuildMockHttpContext(r => { - r.SetupGet(x => x.AppRelativeCurrentExecutionFilePath).Returns("~/" + Regex.Replace(url, @"[{}]", "")); r.SetupGet(x => x.HttpMethod).Returns(requestMethod); + r.SetupGet(x => x.AppRelativeCurrentExecutionFilePath).Returns("~/" + Regex.Replace(pathAndQuery[0], @"[{}]", "")); + + if (pathAndQuery.Length > 1) + { + r.SetupGet(x => x.QueryString).Returns(HttpUtility.ParseQueryString(pathAndQuery[1])); + } if (desiredMethod != requestMethod) { diff --git a/src/AttributeRouting.Specs/Subjects/Http/HttpInlineRouteConstraintsControllers.cs b/src/AttributeRouting.Specs/Subjects/Http/HttpInlineRouteConstraintsControllers.cs index 2d10b6e..bdd4d18 100644 --- a/src/AttributeRouting.Specs/Subjects/Http/HttpInlineRouteConstraintsControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/Http/HttpInlineRouteConstraintsControllers.cs @@ -27,6 +27,12 @@ public string Index() [RoutePrefix("Http-Inline-Constraints")] public class HttpInlineRouteConstraintsController : ApiController { + [GET("Querystring?x={x:int}")] + public string Querystring(int x) + { + return ""; + } + [GET("Alpha/{x:alpha}")] public string Alpha(string x) { diff --git a/src/AttributeRouting.Specs/Subjects/InlineRouteConstraintsControllers.cs b/src/AttributeRouting.Specs/Subjects/InlineRouteConstraintsControllers.cs index 44ee752..7b1df57 100644 --- a/src/AttributeRouting.Specs/Subjects/InlineRouteConstraintsControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/InlineRouteConstraintsControllers.cs @@ -27,6 +27,12 @@ public string Index() [RoutePrefix("Inline-Constraints")] public class InlineRouteConstraintsController : Controller { + [GET("Querystring?x={x:int}")] + public string Querystring(int x) + { + return ""; + } + [GET("Alpha/{x:alpha}")] public string Alpha(string x) { diff --git a/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj b/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj index c3880ef..277dbbf 100644 --- a/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj +++ b/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj @@ -31,6 +31,10 @@ 4.0 + + + + true @@ -126,6 +130,7 @@ + diff --git a/src/AttributeRouting.Tests.Web/Controllers/QueryStringConstraintController.cs b/src/AttributeRouting.Tests.Web/Controllers/QueryStringConstraintController.cs new file mode 100644 index 0000000..9c54c5d --- /dev/null +++ b/src/AttributeRouting.Tests.Web/Controllers/QueryStringConstraintController.cs @@ -0,0 +1,20 @@ +using System.Web.Mvc; +using AttributeRouting.Web.Mvc; + +namespace AttributeRouting.Tests.Web.Controllers +{ + public class QueryStringConstraintController : Controller + { + [GET("Books?author={author:int}")] + public string GetBooksByAuthor(string author) + { + return "author: " + author; + } + + [GET("Books?isbn={isbn:alpha}")] + public string GetBooksByISBN(string isbn) + { + return "isbn: " + isbn; + } + } +} diff --git a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs index df49312..2133c36 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs @@ -58,5 +58,10 @@ public IOptionalRouteConstraintWrapper CreateOptionalRouteConstraint(object cons { return new OptionalRouteConstraintWrapper((IHttpRouteConstraint)constraint); } + + public IQueryStringRouteConstraintWrapper CreateQueryStringRouteConstraint(object constraint) + { + return new QueryStringRouteConstraintWrapper((IHttpRouteConstraint)constraint); + } } } \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs index 7b7be85..50f5f98 100644 --- a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs @@ -56,5 +56,10 @@ public IOptionalRouteConstraintWrapper CreateOptionalRouteConstraint(object cons { return new OptionalRouteConstraintWrapper((IRouteConstraint)constraint); } + + public IQueryStringRouteConstraintWrapper CreateQueryStringRouteConstraint(object constraint) + { + return new QueryStringRouteConstraintWrapper((IRouteConstraint)constraint); + } } } \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj b/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj index 6407148..ec4d572 100644 --- a/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj +++ b/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj @@ -51,6 +51,7 @@ ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + ..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll @@ -72,6 +73,7 @@ + diff --git a/src/AttributeRouting.Web.Http/Constraints/QueryStringRouteConstraintWrapper.cs b/src/AttributeRouting.Web.Http/Constraints/QueryStringRouteConstraintWrapper.cs new file mode 100644 index 0000000..a19ae16 --- /dev/null +++ b/src/AttributeRouting.Web.Http/Constraints/QueryStringRouteConstraintWrapper.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Web; +using System.Web.Http.Routing; +using AttributeRouting.Constraints; + +namespace AttributeRouting.Web.Http.Constraints +{ + public class QueryStringRouteConstraintWrapper : IQueryStringRouteConstraintWrapper, IHttpRouteConstraint + { + private readonly IHttpRouteConstraint _constraint; + + public QueryStringRouteConstraintWrapper(IHttpRouteConstraint constraint) + { + _constraint = constraint; + } + + public object Constraint + { + get { return _constraint; } + } + + public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary values, HttpRouteDirection routeDirection) + { + // If the query param does not exist, then fail. + var queryString = HttpUtility.ParseQueryString(request.RequestUri.Query); + if (!queryString.AllKeys.Contains(parameterName)) + return false; + + // Process the constraint. + var queryRouteValues = new Dictionary + { + { parameterName, queryString[parameterName] } + }; + + return _constraint.Match(request, route, parameterName, queryRouteValues, routeDirection); + } + } +} \ No newline at end of file diff --git a/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs index fc0180d..72b09f1 100644 --- a/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs @@ -56,5 +56,10 @@ public IOptionalRouteConstraintWrapper CreateOptionalRouteConstraint(object cons { return new OptionalRouteConstraintWrapper((IRouteConstraint)constraint); } + + public IQueryStringRouteConstraintWrapper CreateQueryStringRouteConstraint(object constraint) + { + return new QueryStringRouteConstraintWrapper((IRouteConstraint)constraint); + } } } \ No newline at end of file diff --git a/src/AttributeRouting.Web/AttributeRouting.Web.csproj b/src/AttributeRouting.Web/AttributeRouting.Web.csproj index b8c46b7..ebc0c46 100644 --- a/src/AttributeRouting.Web/AttributeRouting.Web.csproj +++ b/src/AttributeRouting.Web/AttributeRouting.Web.csproj @@ -61,6 +61,7 @@ + diff --git a/src/AttributeRouting.Web/Constraints/QueryStringRouteConstraintWrapper.cs b/src/AttributeRouting.Web/Constraints/QueryStringRouteConstraintWrapper.cs new file mode 100644 index 0000000..b0ca05b --- /dev/null +++ b/src/AttributeRouting.Web/Constraints/QueryStringRouteConstraintWrapper.cs @@ -0,0 +1,38 @@ +using System.Web; +using System.Web.Routing; +using AttributeRouting.Constraints; +using System.Linq; + +namespace AttributeRouting.Web.Constraints +{ + public class QueryStringRouteConstraintWrapper : IQueryStringRouteConstraintWrapper, IRouteConstraint + { + private readonly IRouteConstraint _constraint; + + public QueryStringRouteConstraintWrapper(IRouteConstraint constraint) + { + _constraint = constraint; + } + + public object Constraint + { + get { return _constraint; } + } + + public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) + { + // If the query param does not exist, then fail. + var queryString = httpContext.Request.QueryString; + if (!queryString.AllKeys.Contains(parameterName)) + return false; + + // Process the constraint. + var queryRouteValues = new RouteValueDictionary + { + { parameterName, queryString[parameterName] } + }; + + return _constraint.Match(httpContext, route, parameterName, queryRouteValues, routeDirection); + } + } +} \ No newline at end of file diff --git a/src/AttributeRouting/AttributeRouting.csproj b/src/AttributeRouting/AttributeRouting.csproj index dd141c7..f7d136e 100644 --- a/src/AttributeRouting/AttributeRouting.csproj +++ b/src/AttributeRouting/AttributeRouting.csproj @@ -63,6 +63,7 @@ + diff --git a/src/AttributeRouting/Constraints/IQueryStringRouteConstraintWrapper.cs b/src/AttributeRouting/Constraints/IQueryStringRouteConstraintWrapper.cs new file mode 100644 index 0000000..34c63db --- /dev/null +++ b/src/AttributeRouting/Constraints/IQueryStringRouteConstraintWrapper.cs @@ -0,0 +1,21 @@ +using AttributeRouting.Framework.Factories; + +namespace AttributeRouting.Constraints +{ + /// + /// Abstraction used by + /// when handling inline route constraints contained in query strings. + /// + /// + /// Due to + /// System.Web.Routing.IRouteConstraint (used in web-hosted scenarios) and + /// System.Web.Http.Routing.IHttpRouteConstraint (used in self-hosted scenarios). + /// + public interface IQueryStringRouteConstraintWrapper + { + /// + /// The constraint used in the query string. + /// + object Constraint { get; } + } +} \ No newline at end of file diff --git a/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs b/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs index 385e904..708c0c4 100644 --- a/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs +++ b/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs @@ -42,5 +42,11 @@ public interface IRouteConstraintFactory /// /// The constraint IOptionalRouteConstraintWrapper CreateOptionalRouteConstraint(object constraint); + + /// + /// Creates an query string route constraint wrapper to allow constraints in the query string. + /// + /// The constraint + IQueryStringRouteConstraintWrapper CreateQueryStringRouteConstraint(object constraint); } } \ No newline at end of file diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 66e2ff7..7a7c5b4 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -107,9 +107,10 @@ private string CreateRouteUrl(RouteSpecification routeSpec) private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUrl, bool isAbsoluteUrl, bool? useLowercaseRoute) { var tokenizedUrl = BuildTokenizedUrl(routeUrl, routePrefix, areaUrl, isAbsoluteUrl); - var detokenizedUrl = DetokenizeUrl(tokenizedUrl); + var tokenizedPath = RemoveQueryString(tokenizedUrl); + var detokenizedPath = DetokenizeUrl(tokenizedPath); - var urlParameterNames = GetUrlParameterContents(detokenizedUrl).ToList(); + var urlParameterNames = GetUrlParameterContents(detokenizedPath).ToList(); // {controller} and {action} tokens are not valid if (urlParameterNames.Any(n => n.ValueEquals("controller"))) @@ -122,7 +123,7 @@ private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUr throw new AttributeRoutingException( "{area} url parameters are not allowed. Specify the area name by using the RouteAreaAttribute."); - var urlBuilder = new StringBuilder(detokenizedUrl); + var urlBuilder = new StringBuilder(detokenizedPath); // If we are lowercasing routes, then lowercase everything but the route params var lower = useLowercaseRoute.HasValue ? useLowercaseRoute.Value : _configuration.UseLowercaseRoutes; @@ -201,20 +202,28 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec.IsAbsoluteUrl); var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); + // Need to keep track of query params. + // Can do this by detokenizing URL (which strips query), + // and then taking all the URL parameters except those from the path part of the URL. + var pathOnlyUrl = RemoveQueryString(tokenizedUrl); + var pathOnlyUrlParameters = GetUrlParameterContents(pathOnlyUrl); + var queryStringParameters = urlParameters.Except(pathOnlyUrlParameters).ToList(); + // Inline constraints var constraintFactory = _configuration.RouteConstraintFactory; foreach (var parameter in urlParameters.Where(p => p.Contains(":"))) { - // Keep track of whether this param is optional, + // Keep track of whether this param is optional or in the querystring, // because we wrap the final constraint if so. var parameterIsOptional = parameter.EndsWith("?"); + var parameterIsInQueryString = queryStringParameters.Contains(parameter); // Strip off everything related to defaults. var cleanParameter = parameter.TrimEnd('?').Split('=').FirstOrDefault(); var sections = cleanParameter.SplitAndTrim(":"); var parameterName = sections.First(); - + // Do not override default or legacy inline constraints if (constraints.ContainsKey(parameterName)) continue; @@ -256,18 +265,34 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro inlineConstraints.Add(constraint); } - // Apply the constraint to the parameter. - // Wrap multiple constraints in a compound constraint wrapper. - // Wrap constraints for optional params in an optional constraint wrapper. - var finalConstraint = (inlineConstraints.Count == 1) - ? inlineConstraints.Single() - : constraintFactory.CreateCompoundRouteConstraint(inlineConstraints.ToArray()); + // Apply the constraint to the parameter, and wrap constraints in the following priority: + object finalConstraint; - if (parameterIsOptional) - constraints.Add(parameterName, constraintFactory.CreateOptionalRouteConstraint(finalConstraint)); + // 1. If more than one constraint, wrap in a compound constraint. + if (inlineConstraints.Count > 1) + { + finalConstraint = constraintFactory.CreateCompoundRouteConstraint(inlineConstraints.ToArray()); + } else - constraints.Add(parameterName, finalConstraint); - } + { + finalConstraint = inlineConstraints.Single(); + } + + // 2. If the constraint is in the querystring, wrap in a query string constraint. + if (parameterIsInQueryString) + { + finalConstraint = constraintFactory.CreateQueryStringRouteConstraint(finalConstraint); + } + + // 3. If the constraint is optional, wrap in an optional constraint. + if (parameterIsOptional) + { + finalConstraint = constraintFactory.CreateOptionalRouteConstraint(finalConstraint); + } + + constraints.Add(parameterName, finalConstraint); + + } // ... go to next parameter // Globally configured constraints var detokenizedPrefixedUrl = DetokenizeUrl(tokenizedUrl); @@ -344,6 +369,29 @@ private static string DetokenizeUrl(string url) return Regex.Replace(url, String.Join("|", patterns), ""); } + private static string RemoveQueryString(string url) + { + // Must honor ? in regex expressions and as used to specify optional params, + // So run through the url chars and fast forward when inside a url param (eg: {...}) + for (int i = 0, length = url.Length; i < length; i++) + { + var c = url[i]; + if (c == '?') + { + return url.Substring(0, i); + } + + // Fast-forward past url param contents + if (c == '{') + { + while (url[i] != '}' && i < length) + i++; + } + } + + return url; + } + private IEnumerable CreateRouteTranslations(RouteSpecification routeSpec) { // If no translation provider, then get out of here. From 37e415a2ac8d37d34d01f97c9843b215a16eb5d1 Mon Sep 17 00:00:00 2001 From: ourben Date: Fri, 30 Nov 2012 17:32:25 +0000 Subject: [PATCH 03/97] Update src/AttributeRouting/Framework/RouteBuilder.cs Fixed a little typo there that was causing dEaTh! --- src/AttributeRouting/Framework/RouteBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 7d36919..c8c8113 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -422,7 +422,7 @@ private static IEnumerable GetUrlParameterContents(string url) { // If we find an inner open curly (due to inner regex patterns), // then fast-forward beyond it. - i = urlSegment.IndexOf('}', iOpenCurly); + i = urlSegment.IndexOf('}', i); } i++; From 254dceb5ef929b8f248b6016d4a08efd29c98ab8 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Mon, 3 Dec 2012 07:33:19 -0700 Subject: [PATCH 04/97] #152 - added support for specifying that query param simply exists, without constraining by anything else. --- .../Features/RouteConstraints.feature | 6 +- .../Features/RouteConstraints.feature.cs | 90 ++++++++++--------- .../Steps/RouteConstraintSteps.cs | 4 +- .../HttpInlineRouteConstraintsControllers.cs | 4 +- .../InlineRouteConstraintsControllers.cs | 4 +- .../QueryStringConstraintController.cs | 6 +- .../QueryStringRouteConstraintWrapper.cs | 3 +- .../QueryStringRouteConstraintWrapper.cs | 3 +- .../Framework/RouteBuilder.cs | 8 +- 9 files changed, 74 insertions(+), 54 deletions(-) diff --git a/src/AttributeRouting.Specs/Features/RouteConstraints.feature b/src/AttributeRouting.Specs/Features/RouteConstraints.feature index c8db6d5..ed0f9cb 100644 --- a/src/AttributeRouting.Specs/Features/RouteConstraints.feature +++ b/src/AttributeRouting.Specs/Features/RouteConstraints.feature @@ -40,11 +40,13 @@ Scenario: Inline constraints in the querystring When I fetch the routes for the InlineRouteConstraints controller's Querystring action Then the route url is "Inline-Constraints/Querystring" And the parameter "x" is constrained by an inline AttributeRouting.Web.Constraints.IntRouteConstraint + And the parameter "y" is constrained by an inline AttributeRouting.Web.Constraints.QueryStringRouteConstraintWrapper # Web API Given I have registered the routes for the HttpInlineRouteConstraintsController When I fetch the routes for the HttpInlineRouteConstraints controller's Querystring action Then the route url is "Http-Inline-Constraints/Querystring" And the parameter "x" is constrained by an inline AttributeRouting.Web.Constraints.IntRouteConstraint + And the parameter "y" is constrained by an inline AttributeRouting.Web.Constraints.QueryStringRouteConstraintWrapper Scenario: Multiple inline constraints per url segment # MVC @@ -143,5 +145,7 @@ Scenario Outline: Matching inline route constraints | Enum/taupe | Enum | is not | | WithOptional | WithOptional | is | | WithDefault | WithDefault | is | - | Querystring?x=123 | Querystring | is | + | Querystring?x=123&y=hello | Querystring | is | + | Querystring?x=abc&y=hello | Querystring | is not | | Querystring?x=abc | Querystring | is not | + | Querystring?y=hello | Querystring | is not | diff --git a/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs b/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs index ea7a00b..17a890d 100644 --- a/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs @@ -130,16 +130,22 @@ public virtual void InlineConstraintsInTheQuerystring() #line 42 testRunner.And("the parameter \"x\" is constrained by an inline AttributeRouting.Web.Constraints.In" + "tRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 44 - testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 43 + testRunner.And("the parameter \"y\" is constrained by an inline AttributeRouting.Web.Constraints.Qu" + + "eryStringRouteConstraintWrapper", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 45 + testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 46 testRunner.When("I fetch the routes for the HttpInlineRouteConstraints controller\'s Querystring ac" + "tion", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 46 - testRunner.Then("the route url is \"Http-Inline-Constraints/Querystring\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 47 + testRunner.Then("the route url is \"Http-Inline-Constraints/Querystring\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 48 testRunner.And("the parameter \"x\" is constrained by an inline AttributeRouting.Web.Constraints.In" + "tRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 49 + testRunner.And("the parameter \"y\" is constrained by an inline AttributeRouting.Web.Constraints.Qu" + + "eryStringRouteConstraintWrapper", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -149,32 +155,32 @@ public virtual void InlineConstraintsInTheQuerystring() public virtual void MultipleInlineConstraintsPerUrlSegment() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Multiple inline constraints per url segment", ((string[])(null))); -#line 49 -this.ScenarioSetup(scenarioInfo); #line 51 +this.ScenarioSetup(scenarioInfo); +#line 53 testRunner.Given("I have registered the routes for the InlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 52 +#line 54 testRunner.When("I fetch the routes for the InlineRouteConstraints controller\'s MultipleWithinUrlS" + "egment action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 53 +#line 55 testRunner.Then("the route url is \"Inline-Constraints/avatar/{width}x{height}/{image}\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 54 +#line 56 testRunner.And("the parameter \"width\" is constrained by an inline AttributeRouting.Web.Constraint" + "s.IntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 55 +#line 57 testRunner.And("the parameter \"height\" is constrained by an inline AttributeRouting.Web.Constrain" + "ts.IntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 57 +#line 59 testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 58 +#line 60 testRunner.When("I fetch the routes for the HttpInlineRouteConstraints controller\'s MultipleWithin" + "UrlSegment action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 59 +#line 61 testRunner.Then("the route url is \"Http-Inline-Constraints/avatar/{width}x{height}/{image}\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 60 +#line 62 testRunner.And("the parameter \"width\" is constrained by an inline AttributeRouting.Web.Constraint" + "s.IntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 61 +#line 63 testRunner.And("the parameter \"height\" is constrained by an inline AttributeRouting.Web.Constrain" + "ts.IntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden @@ -186,27 +192,27 @@ public virtual void MultipleInlineConstraintsPerUrlSegment() public virtual void InlineConstraintsSpecifiedInTheRoutePrefixAttribute() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Inline constraints specified in the RoutePrefixAttribute", ((string[])(null))); -#line 63 -this.ScenarioSetup(scenarioInfo); #line 65 +this.ScenarioSetup(scenarioInfo); +#line 67 testRunner.Given("I have registered the routes for the PrefixedInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 66 +#line 68 testRunner.When("I fetch the routes for the PrefixedInlineRouteConstraints controller\'s Index acti" + "on", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 67 +#line 69 testRunner.Then("the route url is \"Prefixed-Inline-Constraints/{id}/Howdy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 68 +#line 70 testRunner.And("the parameter \"id\" is constrained by an inline AttributeRouting.Web.Constraints.I" + "ntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 70 +#line 72 testRunner.Given("I have registered the routes for the HttpPrefixedInlineRouteConstraintsController" + "", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 71 +#line 73 testRunner.When("I fetch the routes for the HttpPrefixedInlineRouteConstraints controller\'s Index " + "action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 72 +#line 74 testRunner.Then("the route url is \"Http-Prefixed-Inline-Constraints/{id}/Howdy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 73 +#line 75 testRunner.And("the parameter \"id\" is constrained by an inline AttributeRouting.Web.Constraints.I" + "ntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden @@ -218,25 +224,25 @@ public virtual void InlineConstraintsSpecifiedInTheRoutePrefixAttribute() public virtual void InlineConstraintsSpecifiedInTheRouteAreaAttribute() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Inline constraints specified in the RouteAreaAttribute", ((string[])(null))); -#line 75 -this.ScenarioSetup(scenarioInfo); #line 77 +this.ScenarioSetup(scenarioInfo); +#line 79 testRunner.Given("I have registered the routes for the AreaInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 78 +#line 80 testRunner.When("I fetch the routes for the AreaInlineRouteConstraints controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 79 +#line 81 testRunner.Then("the route url is \"Area-Inline-Constraints/{id}/Howdy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 80 +#line 82 testRunner.And("the parameter \"id\" is constrained by an inline AttributeRouting.Web.Constraints.I" + "ntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 82 +#line 84 testRunner.Given("I have registered the routes for the HttpAreaInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 83 +#line 85 testRunner.When("I fetch the routes for the HttpAreaInlineRouteConstraints controller\'s Index acti" + "on", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 84 +#line 86 testRunner.Then("the route url is \"Http-Area-Inline-Constraints/{id}/Howdy\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 85 +#line 87 testRunner.And("the parameter \"id\" is constrained by an inline AttributeRouting.Web.Constraints.I" + "ntRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden @@ -293,24 +299,26 @@ public virtual void InlineConstraintsSpecifiedInTheRouteAreaAttribute() [NUnit.Framework.TestCaseAttribute("Enum/taupe", "Enum", "is not", null)] [NUnit.Framework.TestCaseAttribute("WithOptional", "WithOptional", "is", null)] [NUnit.Framework.TestCaseAttribute("WithDefault", "WithDefault", "is", null)] - [NUnit.Framework.TestCaseAttribute("Querystring?x=123", "Querystring", "is", null)] + [NUnit.Framework.TestCaseAttribute("Querystring?x=123&y=hello", "Querystring", "is", null)] + [NUnit.Framework.TestCaseAttribute("Querystring?x=abc&y=hello", "Querystring", "is not", null)] [NUnit.Framework.TestCaseAttribute("Querystring?x=abc", "Querystring", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Querystring?y=hello", "Querystring", "is not", null)] public virtual void MatchingInlineRouteConstraints(string url, string action, string condition, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Matching inline route constraints", exampleTags); -#line 87 -this.ScenarioSetup(scenarioInfo); #line 89 +this.ScenarioSetup(scenarioInfo); +#line 91 testRunner.Given("I have registered the routes for the InlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 90 +#line 92 testRunner.When(string.Format("a request for \"Inline-Constraints/{0}\" is made", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 91 - testRunner.Then(string.Format("the {0} action {1} matched", action, condition), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 93 + testRunner.Then(string.Format("the {0} action {1} matched", action, condition), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 95 testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 94 +#line 96 testRunner.When(string.Format("a request for \"Http-Inline-Constraints/{0}\" is made", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 95 +#line 97 testRunner.Then(string.Format("the {0} action {1} matched", action, condition), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); diff --git a/src/AttributeRouting.Specs/Steps/RouteConstraintSteps.cs b/src/AttributeRouting.Specs/Steps/RouteConstraintSteps.cs index 5d4d239..47e017d 100644 --- a/src/AttributeRouting.Specs/Steps/RouteConstraintSteps.cs +++ b/src/AttributeRouting.Specs/Steps/RouteConstraintSteps.cs @@ -39,8 +39,10 @@ public void ThenTheParameterIsConstrainedByAnInline(string key, string type) // If this is a querystring route constraint wrapper, then unwrap it. var queryStringConstraint = constraint as IQueryStringRouteConstraintWrapper; - if (queryStringConstraint != null) + if (queryStringConstraint != null && queryStringConstraint.Constraint != null) + { constraint = queryStringConstraint.Constraint; + } var compoundRouteConstraint = constraint as ICompoundRouteConstraintWrapper; if (compoundRouteConstraint != null) diff --git a/src/AttributeRouting.Specs/Subjects/Http/HttpInlineRouteConstraintsControllers.cs b/src/AttributeRouting.Specs/Subjects/Http/HttpInlineRouteConstraintsControllers.cs index bdd4d18..0664ee1 100644 --- a/src/AttributeRouting.Specs/Subjects/Http/HttpInlineRouteConstraintsControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/Http/HttpInlineRouteConstraintsControllers.cs @@ -27,8 +27,8 @@ public string Index() [RoutePrefix("Http-Inline-Constraints")] public class HttpInlineRouteConstraintsController : ApiController { - [GET("Querystring?x={x:int}")] - public string Querystring(int x) + [GET("Querystring?{x:int}&{y}")] + public string Querystring(int x, string y) { return ""; } diff --git a/src/AttributeRouting.Specs/Subjects/InlineRouteConstraintsControllers.cs b/src/AttributeRouting.Specs/Subjects/InlineRouteConstraintsControllers.cs index 7b1df57..125dffe 100644 --- a/src/AttributeRouting.Specs/Subjects/InlineRouteConstraintsControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/InlineRouteConstraintsControllers.cs @@ -27,8 +27,8 @@ public string Index() [RoutePrefix("Inline-Constraints")] public class InlineRouteConstraintsController : Controller { - [GET("Querystring?x={x:int}")] - public string Querystring(int x) + [GET("Querystring?{x:int}&{y}")] + public string Querystring(int x, string y) { return ""; } diff --git a/src/AttributeRouting.Tests.Web/Controllers/QueryStringConstraintController.cs b/src/AttributeRouting.Tests.Web/Controllers/QueryStringConstraintController.cs index 9c54c5d..03e4976 100644 --- a/src/AttributeRouting.Tests.Web/Controllers/QueryStringConstraintController.cs +++ b/src/AttributeRouting.Tests.Web/Controllers/QueryStringConstraintController.cs @@ -5,13 +5,13 @@ namespace AttributeRouting.Tests.Web.Controllers { public class QueryStringConstraintController : Controller { - [GET("Books?author={author:int}")] - public string GetBooksByAuthor(string author) + [GET("Books?{author}")] + public string GetBooksByAuthor(string author, string damage) { return "author: " + author; } - [GET("Books?isbn={isbn:alpha}")] + [GET("Books?{isbn}")] public string GetBooksByISBN(string isbn) { return "isbn: " + isbn; diff --git a/src/AttributeRouting.Web.Http/Constraints/QueryStringRouteConstraintWrapper.cs b/src/AttributeRouting.Web.Http/Constraints/QueryStringRouteConstraintWrapper.cs index a19ae16..bf9ff08 100644 --- a/src/AttributeRouting.Web.Http/Constraints/QueryStringRouteConstraintWrapper.cs +++ b/src/AttributeRouting.Web.Http/Constraints/QueryStringRouteConstraintWrapper.cs @@ -34,7 +34,8 @@ public bool Match(HttpRequestMessage request, IHttpRoute route, string parameter { parameterName, queryString[parameterName] } }; - return _constraint.Match(request, route, parameterName, queryRouteValues, routeDirection); + return _constraint == null // ie: Simply ensure that the query param exists. + || _constraint.Match(request, route, parameterName, queryRouteValues, routeDirection); } } } \ No newline at end of file diff --git a/src/AttributeRouting.Web/Constraints/QueryStringRouteConstraintWrapper.cs b/src/AttributeRouting.Web/Constraints/QueryStringRouteConstraintWrapper.cs index b0ca05b..4004d84 100644 --- a/src/AttributeRouting.Web/Constraints/QueryStringRouteConstraintWrapper.cs +++ b/src/AttributeRouting.Web/Constraints/QueryStringRouteConstraintWrapper.cs @@ -32,7 +32,8 @@ public bool Match(HttpContextBase httpContext, Route route, string parameterName { parameterName, queryString[parameterName] } }; - return _constraint.Match(httpContext, route, parameterName, queryRouteValues, routeDirection); + return _constraint == null // ie: Simply ensure that the query param exists. + || _constraint.Match(httpContext, route, parameterName, queryRouteValues, routeDirection); } } } \ No newline at end of file diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 7a7c5b4..aa8b72e 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -211,13 +211,17 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro // Inline constraints var constraintFactory = _configuration.RouteConstraintFactory; - foreach (var parameter in urlParameters.Where(p => p.Contains(":"))) + foreach (var parameter in urlParameters) { // Keep track of whether this param is optional or in the querystring, // because we wrap the final constraint if so. var parameterIsOptional = parameter.EndsWith("?"); var parameterIsInQueryString = queryStringParameters.Contains(parameter); + // If this is a path parameter and doesn't have a constraint, then skip it. + if (!parameterIsInQueryString && !parameter.Contains(":")) + continue; + // Strip off everything related to defaults. var cleanParameter = parameter.TrimEnd('?').Split('=').FirstOrDefault(); @@ -275,7 +279,7 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro } else { - finalConstraint = inlineConstraints.Single(); + finalConstraint = inlineConstraints.FirstOrDefault(); } // 2. If the constraint is in the querystring, wrap in a query string constraint. From a617590df2c1f5320b1a7856cb0afd2a70d19583 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Mon, 3 Dec 2012 08:10:30 -0700 Subject: [PATCH 05/97] #154 - added support for querystring constraint description in routes.axd. --- .../QueryStringConstraintController.cs | 2 +- .../Logging/AttributeRouteInfo.cs | 29 ++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/AttributeRouting.Tests.Web/Controllers/QueryStringConstraintController.cs b/src/AttributeRouting.Tests.Web/Controllers/QueryStringConstraintController.cs index 03e4976..e44d2f6 100644 --- a/src/AttributeRouting.Tests.Web/Controllers/QueryStringConstraintController.cs +++ b/src/AttributeRouting.Tests.Web/Controllers/QueryStringConstraintController.cs @@ -11,7 +11,7 @@ public string GetBooksByAuthor(string author, string damage) return "author: " + author; } - [GET("Books?{isbn}")] + [GET("Books?{isbn}&{x:int}")] public string GetBooksByISBN(string isbn) { return "isbn: " + isbn; diff --git a/src/AttributeRouting/Logging/AttributeRouteInfo.cs b/src/AttributeRouting/Logging/AttributeRouteInfo.cs index 25d534e..2a1e0b4 100644 --- a/src/AttributeRouting/Logging/AttributeRouteInfo.cs +++ b/src/AttributeRouting/Logging/AttributeRouteInfo.cs @@ -62,13 +62,12 @@ public static AttributeRouteInfo GetRouteInfo(string url, else { var constraintValue = constraint.Value; - var constraintType = constraint.Value.GetType(); - string constraintAsString; + var constraintDescriptions = new List(); // Simple string regex constraint - from ASP.NET routing features if (constraintValue is string) { - constraintAsString = constraintValue.ToString(); + constraintDescriptions.Add(constraintValue.ToString()); } else { @@ -77,24 +76,30 @@ public static AttributeRouteInfo GetRouteInfo(string url, if (optionalConstraint != null) { constraintValue = optionalConstraint.Constraint; - constraintType = optionalConstraint.Constraint.GetType(); + } + + // QueryString constraint - unwrap it and continue + var queryStringConstraint = constraintValue as IQueryStringRouteConstraintWrapper; + if (queryStringConstraint != null) + { + constraintValue = queryStringConstraint.Constraint; + constraintDescriptions.Add("QueryStringRouteConstraint"); } // Compound constraint - join type names of the inner constraints var compoundConstraint = constraintValue as ICompoundRouteConstraintWrapper; if (compoundConstraint != null) { - var innerConstraintTypeNames = compoundConstraint.Constraints.Select(x => x.GetType().Name); - constraintAsString = String.Join(", ", innerConstraintTypeNames); + constraintDescriptions.AddRange(compoundConstraint.Constraints.Select(c => c.GetType().Name)); } - else + else if (constraintValue != null) { // Single constraint type - constraintAsString = constraintType.Name; + constraintDescriptions.Add(constraintValue.GetType().Name); } } - item.Constraints.Add(constraint.Key, constraintAsString); + item.Constraints.Add(constraint.Key, String.Join(", ", constraintDescriptions)); } } } @@ -107,9 +112,13 @@ public static AttributeRouteInfo GetRouteInfo(string url, foreach (var token in dataTokens) { if (token.Key.ValueEquals("namespaces")) - item.DataTokens.Add(token.Key, ((string[])token.Value).Aggregate((n1, n2) => n1 + ", " + n2)); + { + item.DataTokens.Add(token.Key, ((string[]) token.Value).Aggregate((n1, n2) => n1 + ", " + n2)); + } else + { item.DataTokens.Add(token.Key, token.Value.ToString()); + } } } From 28fa40e30622751277f84cffb51f9545630f19d2 Mon Sep 17 00:00:00 2001 From: waynebrantley Date: Mon, 3 Dec 2012 13:10:12 -0500 Subject: [PATCH 06/97] Add compiled regex --- .../Constraints/RegexRouteConstraintBase.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/AttributeRouting/Constraints/RegexRouteConstraintBase.cs b/src/AttributeRouting/Constraints/RegexRouteConstraintBase.cs index 58a069c..2b4334e 100644 --- a/src/AttributeRouting/Constraints/RegexRouteConstraintBase.cs +++ b/src/AttributeRouting/Constraints/RegexRouteConstraintBase.cs @@ -16,7 +16,9 @@ protected RegexRouteConstraintBase(string pattern) protected RegexRouteConstraintBase(string pattern, RegexOptions options) { Pattern = pattern; - Options = options; + // shouldn't these be included in the derrived classes by default: RegexOptions.CultureInvariant | RegexOptions.IgnoreCase? + Options = options; //no need to tell user that it is 'compiled' option...so do not include in public options + CompiledExpression = new Regex(pattern, options | RegexOptions.Compiled); } /// @@ -24,6 +26,11 @@ protected RegexRouteConstraintBase(string pattern, RegexOptions options) /// public string Pattern { get; private set; } + /// + /// The compiled reg-ex expression. + /// + private Regex CompiledExpression { get; set; } + /// /// Regex options for matching. /// @@ -36,8 +43,7 @@ public bool IsMatch(string parameterName, IDictionary routeDicti return true; var valueAsString = value.ToString(); - - return Regex.IsMatch(valueAsString, Pattern, Options); + return CompiledExpression.IsMatch(valueAsString); } } } From 1e7e0a4d8f8591fb048b3f294e45e74633942835 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Mon, 3 Dec 2012 21:40:33 -0700 Subject: [PATCH 07/97] #150 - changed IAttributeRouteFactory method to return multiple routes. --- .../Factories/AttributeRouteFactory.cs | 15 ++-- .../Factories/AttributeRouteFactory.cs | 15 ++-- .../Factories/AttributeRouteFactory.cs | 15 ++-- .../Factories/IAttributeRouteFactory.cs | 7 +- .../Framework/RouteBuilder.cs | 78 ++++++++++--------- 5 files changed, 62 insertions(+), 68 deletions(-) diff --git a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/AttributeRouteFactory.cs b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/AttributeRouteFactory.cs index ecd5a9b..f3c5fd5 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/AttributeRouteFactory.cs +++ b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/AttributeRouteFactory.cs @@ -15,16 +15,13 @@ public AttributeRouteFactory(HttpAttributeRoutingConfiguration configuration) _configuration = configuration; } - public IAttributeRoute CreateAttributeRoute(string url, - IDictionary defaults, - IDictionary constraints, - IDictionary dataTokens) + public IEnumerable CreateAttributeRoutes(string url, IDictionary defaults, IDictionary constraints, IDictionary dataTokens) { - return new HttpAttributeRoute(url, - new HttpRouteValueDictionary(defaults), - new HttpRouteValueDictionary(constraints), - new HttpRouteValueDictionary(dataTokens), - _configuration); + yield return new HttpAttributeRoute(url, + new HttpRouteValueDictionary(defaults), + new HttpRouteValueDictionary(constraints), + new HttpRouteValueDictionary(dataTokens), + _configuration); } } } diff --git a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/AttributeRouteFactory.cs b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/AttributeRouteFactory.cs index 4961504..47764cd 100644 --- a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/AttributeRouteFactory.cs +++ b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/AttributeRouteFactory.cs @@ -15,16 +15,13 @@ public AttributeRouteFactory(HttpWebAttributeRoutingConfiguration configuration) _configuration = configuration; } - public IAttributeRoute CreateAttributeRoute(string url, - IDictionary defaults, - IDictionary constraints, - IDictionary dataTokens) + public IEnumerable CreateAttributeRoutes(string url, IDictionary defaults, IDictionary constraints, IDictionary dataTokens) { - return new HttpAttributeRoute(url, - new HttpRouteValueDictionary(defaults), - new HttpRouteValueDictionary(constraints), - new HttpRouteValueDictionary(dataTokens), - _configuration); + yield return new HttpAttributeRoute(url, + new HttpRouteValueDictionary(defaults), + new HttpRouteValueDictionary(constraints), + new HttpRouteValueDictionary(dataTokens), + _configuration); } } } diff --git a/src/AttributeRouting.Web.Mvc/Framework/Factories/AttributeRouteFactory.cs b/src/AttributeRouting.Web.Mvc/Framework/Factories/AttributeRouteFactory.cs index 0b384ec..4d1bb43 100644 --- a/src/AttributeRouting.Web.Mvc/Framework/Factories/AttributeRouteFactory.cs +++ b/src/AttributeRouting.Web.Mvc/Framework/Factories/AttributeRouteFactory.cs @@ -14,16 +14,13 @@ public AttributeRouteFactory(AttributeRoutingConfiguration configuration) _configuration = configuration; } - public IAttributeRoute CreateAttributeRoute(string url, - IDictionary defaults, - IDictionary constraints, - IDictionary dataTokens) + public IEnumerable CreateAttributeRoutes(string url, IDictionary defaults, IDictionary constraints, IDictionary dataTokens) { - return new AttributeRoute(url, - new RouteValueDictionary(defaults), - new RouteValueDictionary(constraints), - new RouteValueDictionary(dataTokens), - _configuration); + yield return new AttributeRoute(url, + new RouteValueDictionary(defaults), + new RouteValueDictionary(constraints), + new RouteValueDictionary(dataTokens), + _configuration); } } } diff --git a/src/AttributeRouting/Framework/Factories/IAttributeRouteFactory.cs b/src/AttributeRouting/Framework/Factories/IAttributeRouteFactory.cs index dac358b..80ced48 100644 --- a/src/AttributeRouting/Framework/Factories/IAttributeRouteFactory.cs +++ b/src/AttributeRouting/Framework/Factories/IAttributeRouteFactory.cs @@ -15,11 +15,8 @@ namespace AttributeRouting.Framework.Factories public interface IAttributeRouteFactory { /// - /// Create a new attribute route that wraps an underlying framework route. + /// Create attribute routes from the given metadata. /// - IAttributeRoute CreateAttributeRoute(string url, - IDictionary defaults, - IDictionary constraints, - IDictionary dataTokens); + IEnumerable CreateAttributeRoutes(string url, IDictionary defaults, IDictionary constraints, IDictionary dataTokens); } } diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 6fdfa08..aa05499 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -53,37 +53,40 @@ where s.Subdomain.HasValue() private IEnumerable Build(RouteSpecification routeSpec) { - var route = _routeFactory.CreateAttributeRoute(CreateRouteUrl(routeSpec), - CreateRouteDefaults(routeSpec), - CreateRouteConstraints(routeSpec), - CreateRouteDataTokens(routeSpec)); + var routes = _routeFactory.CreateAttributeRoutes(CreateRouteUrl(routeSpec), + CreateRouteDefaults(routeSpec), + CreateRouteConstraints(routeSpec), + CreateRouteDataTokens(routeSpec)); - var routeName = CreateRouteName(routeSpec); - if (routeName.HasValue()) + foreach (var route in routes) { - route.RouteName = routeName; - route.DataTokens.Add("routeName", routeName); - } + var routeName = CreateRouteName(routeSpec); + if (routeName.HasValue()) + { + route.RouteName = routeName; + route.DataTokens.Add("routeName", routeName); + } - route.Translations = CreateRouteTranslations(routeSpec); - route.Subdomain = routeSpec.Subdomain; - route.UseLowercaseRoute = routeSpec.UseLowercaseRoute; - route.PreserveCaseForUrlParameters = routeSpec.PreserveCaseForUrlParameters; - route.AppendTrailingSlash = routeSpec.AppendTrailingSlash; + route.Translations = CreateRouteTranslations(routeSpec); + route.Subdomain = routeSpec.Subdomain; + route.UseLowercaseRoute = routeSpec.UseLowercaseRoute; + route.PreserveCaseForUrlParameters = routeSpec.PreserveCaseForUrlParameters; + route.AppendTrailingSlash = routeSpec.AppendTrailingSlash; - // Yield the default route first - yield return route; + // Yield the default route first + yield return route; - // Then yield the translations - if (route.Translations == null) - yield break; + // Then yield the translations + if (route.Translations == null) + yield break; - foreach (var translation in route.Translations) - { - // Backreference the default route. - translation.DefaultRouteContainer = route; + foreach (var translation in route.Translations) + { + // Backreference the default route. + translation.DefaultRouteContainer = route; - yield return translation; + yield return translation; + } } } @@ -433,22 +436,25 @@ private IEnumerable CreateRouteTranslations(RouteSpecification routeSpec.IsAbsoluteUrl, routeSpec.UseLowercaseRoute); - var translatedRoute = _routeFactory.CreateAttributeRoute(routeUrl, - CreateRouteDefaults(routeSpec), - CreateRouteConstraints(routeSpec), - CreateRouteDataTokens(routeSpec)); + var translatedRoutes = _routeFactory.CreateAttributeRoutes(routeUrl, + CreateRouteDefaults(routeSpec), + CreateRouteConstraints(routeSpec), + CreateRouteDataTokens(routeSpec)); - var routeName = CreateRouteName(routeSpec); - if (routeName != null) + foreach (var translatedRoute in translatedRoutes) { - translatedRoute.RouteName = routeName; - translatedRoute.DataTokens.Add("routeName", routeName); - } + var routeName = CreateRouteName(routeSpec); + if (routeName != null) + { + translatedRoute.RouteName = routeName; + translatedRoute.DataTokens.Add("routeName", routeName); + } - translatedRoute.CultureName = cultureName; - translatedRoute.DataTokens.Add("cultureName", cultureName); + translatedRoute.CultureName = cultureName; + translatedRoute.DataTokens.Add("cultureName", cultureName); - yield return translatedRoute; + yield return translatedRoute; + } } } From e6757686318b67525a6367fe35dc0359294af192 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Mon, 3 Dec 2012 22:20:16 -0700 Subject: [PATCH 08/97] updated readme and assembly info for v3.3 --- README.textile | 9 +++++++++ src/SharedAssemblyInfo.cs | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.textile b/README.textile index b00ff92..f091f4d 100644 --- a/README.textile +++ b/README.textile @@ -4,6 +4,15 @@ h2. Please refer to "attributerouting.net":http://attributerouting.net/ for docu h3. Changelog +_3.3_ + +* #153 - fixed bad parsing of regex route constraint patterns that use a comma. +* #132 - basic support for constraints in the querystring. +* #152 - added support for specifying that query param simply exists, without constraining by anything else. +* #154 - added support for querystring constraint description in routes.axd. +* #159 - added compiled regex in RegexRouteConstraint to improve performance. +* #150 - changed IAttributeRouteFactory method to return multiple routes. + _3.2_ * #142 - Now accepting inline route constraints in the area url part of routes." diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index c9cc613..8a3e3f9 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyProduct("AttributeRouting")] [assembly: AssemblyCopyright("Copyright 2010-2012 Tim McCall")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.2")] -[assembly: AssemblyFileVersion("3.2")] -[assembly: AssemblyInformationalVersion("3.2")] +[assembly: AssemblyVersion("3.3")] +[assembly: AssemblyFileVersion("3.3")] +[assembly: AssemblyInformationalVersion("3.3")] [assembly: AssemblyConfiguration("Release")] From a0152b89e704a421bf992620cf2e56e52295196b Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sat, 8 Dec 2012 13:32:46 -0700 Subject: [PATCH 09/97] #162 - modified design of route convention attribute and added facility for specifying an area attribute for a controller. --- .../AttributeRouting.Specs.csproj | 2 +- .../Features/RouteConventions.feature | 76 +++++----- .../Features/RouteConventions.feature.cs | 29 +++- .../Steps/StandardUsageSteps.cs | 9 ++ ...ller.cs => RouteConventionsControllers.cs} | 28 ++++ .../DefaultHttpRouteConventionAttribute.cs | 4 +- .../RestfulRouteConventionAttribute.cs | 142 +++++++++--------- .../Framework/RouteReflector.cs | 34 ++--- .../RouteConventionAttributeBase.cs | 31 +++- 9 files changed, 217 insertions(+), 138 deletions(-) rename src/AttributeRouting.Specs/Subjects/{RouteConventionsController.cs => RouteConventionsControllers.cs} (75%) diff --git a/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj b/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj index 49f1048..e583024 100644 --- a/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj +++ b/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj @@ -159,7 +159,7 @@ - + diff --git a/src/AttributeRouting.Specs/Features/RouteConventions.feature b/src/AttributeRouting.Specs/Features/RouteConventions.feature index 803e717..3714479 100644 --- a/src/AttributeRouting.Specs/Features/RouteConventions.feature +++ b/src/AttributeRouting.Specs/Features/RouteConventions.feature @@ -9,16 +9,16 @@ Scenario Outline: Generating routes using the RestfulRouteConvention And the route for is constrained to requests Examples: - | action | method | url | - | Index | GET | RestfulRouteConvention | - | New | GET | RestfulRouteConvention/New | - | Create | POST | RestfulRouteConvention | - | Show | GET | RestfulRouteConvention/{id} | - | Edit | GET | RestfulRouteConvention/{id}/Edit | - | Update | PUT | RestfulRouteConvention/{id} | - | Delete | GET | RestfulRouteConvention/{id}/Delete | - | Destroy | DELETE | RestfulRouteConvention/{id} | - | Custom | GET | RestfulRouteConvention/Custom | + | action | method | url | + | Index | GET | RestfulRouteConvention | + | New | GET | RestfulRouteConvention/New | + | Create | POST | RestfulRouteConvention | + | Show | GET | RestfulRouteConvention/{id} | + | Edit | GET | RestfulRouteConvention/{id}/Edit | + | Update | PUT | RestfulRouteConvention/{id} | + | Delete | GET | RestfulRouteConvention/{id}/Delete | + | Destroy | DELETE | RestfulRouteConvention/{id} | + | Custom | GET | RestfulRouteConvention/Custom | Scenario Outline: Generating routes using the RestfulRouteConvention on controllers with a RoutePrefix attribute Given I have registered the routes for the RestfulRouteConventionPrefixController @@ -29,15 +29,15 @@ Scenario Outline: Generating routes using the RestfulRouteConvention on controll And the route for is constrained to requests Examples: - | action | method | url | - | Index | GET | Prefix | - | New | GET | Prefix/New | - | Create | POST | Prefix | - | Show | GET | Prefix/{id} | - | Edit | GET | Prefix/{id}/Edit | - | Update | PUT | Prefix/{id} | - | Delete | GET | Prefix/{id}/Delete | - | Destroy | DELETE | Prefix/{id} | + | action | method | url | + | Index | GET | Prefix | + | New | GET | Prefix/New | + | Create | POST | Prefix | + | Show | GET | Prefix/{id} | + | Edit | GET | Prefix/{id}/Edit | + | Update | PUT | Prefix/{id} | + | Delete | GET | Prefix/{id}/Delete | + | Destroy | DELETE | Prefix/{id} | Scenario Outline: Generating routes using the DefaultHttpRouteConvention Given I have registered the routes for the DefaultHttpRouteConventionController @@ -48,13 +48,13 @@ Scenario Outline: Generating routes using the DefaultHttpRouteConvention And the route for is constrained to requests Examples: - | action | method | url | - | GetAll | GET | DefaultHttpRouteConvention | - | Get | GET | DefaultHttpRouteConvention/{id} | - | Post | POST | DefaultHttpRouteConvention | - | Put | PUT | DefaultHttpRouteConvention/{id} | - | Delete | DELETE | DefaultHttpRouteConvention/{id} | - | Custom | GET | DefaultHttpRouteConvention/Custom | + | action | method | url | + | GetAll | GET | DefaultHttpRouteConvention | + | Get | GET | DefaultHttpRouteConvention/{id} | + | Post | POST | DefaultHttpRouteConvention | + | Put | PUT | DefaultHttpRouteConvention/{id} | + | Delete | DELETE | DefaultHttpRouteConvention/{id} | + | Custom | GET | DefaultHttpRouteConvention/Custom | Scenario Outline: Generating routes using the DefaultHttpRouteConventionPrefix on controllers with a RoutePrefix attribute Given I have registered the routes for the DefaultHttpRouteConventionPrefixController @@ -65,13 +65,13 @@ Scenario Outline: Generating routes using the DefaultHttpRouteConventionPrefix o And the route for is constrained to requests Examples: - | action | method | url | - | GetAll | GET | Prefix | - | Get | GET | Prefix/{id} | - | Post | POST | Prefix | - | Put | PUT | Prefix/{id} | - | Delete | DELETE | Prefix/{id} | - | Custom | GET | Prefix/Custom | + | action | method | url | + | GetAll | GET | Prefix | + | Get | GET | Prefix/{id} | + | Post | POST | Prefix | + | Put | PUT | Prefix/{id} | + | Delete | DELETE | Prefix/{id} | + | Custom | GET | Prefix/Custom | Scenario: Generating routes using the RestfulRouteConvention on actions with an explicit route defined Given I have registered the routes for the RestfulRouteConventionWithExplicitRouteController @@ -95,4 +95,12 @@ Scenario: Generating routes using the DefaultHttpRouteConvention on actions with Given I have registered the routes for the DefaultHttpRouteConventionWithExplicitOrderedRouteController When I fetch the routes for the DefaultHttpRouteConventionWithExplicitOrderedRoute controller's Get action Then the 1st route url is "DefaultHttpRouteConventionWithExplicitOrderedRoute/Primary" - And the 2nd route url is "DefaultHttpRouteConventionWithExplicitOrderedRoute" \ No newline at end of file + And the 2nd route url is "DefaultHttpRouteConventionWithExplicitOrderedRoute" + +Scenario: Generating routes using the conventions that define areas on controllers + Given I have registered the routes for the AreaRouteConventionController + When I fetch the routes for the AreaRouteConvention controller's Index action + Then the route url is "Subjects/Index" + And the default for "controller" is "AreaRouteConvention" + And the default for "action" is "Index" + And the route area is "Subjects" diff --git a/src/AttributeRouting.Specs/Features/RouteConventions.feature.cs b/src/AttributeRouting.Specs/Features/RouteConventions.feature.cs index f388969..101219c 100644 --- a/src/AttributeRouting.Specs/Features/RouteConventions.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteConventions.feature.cs @@ -1,9 +1,9 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.9.1.84 +// SpecFlow Version:1.9.0.77 // SpecFlow Generator Version:1.9.0.0 -// Runtime Version:4.0.30319.17929 +// Runtime Version:4.0.30319.18010 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Route Conventions")] @@ -279,6 +279,29 @@ public virtual void GeneratingRoutesUsingTheDefaultHttpRouteConventionOnActionsW "", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 98 testRunner.And("the 2nd route url is \"DefaultHttpRouteConventionWithExplicitOrderedRoute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Generating routes using the conventions that define areas on controllers")] + public virtual void GeneratingRoutesUsingTheConventionsThatDefineAreasOnControllers() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes using the conventions that define areas on controllers", ((string[])(null))); +#line 100 +this.ScenarioSetup(scenarioInfo); +#line 101 + testRunner.Given("I have registered the routes for the AreaRouteConventionController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 102 + testRunner.When("I fetch the routes for the AreaRouteConvention controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 103 + testRunner.Then("the route url is \"Subjects/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 104 + testRunner.And("the default for \"controller\" is \"AreaRouteConvention\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 105 + testRunner.And("the default for \"action\" is \"Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 106 + testRunner.And("the route area is \"Subjects\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs index da3ad9d..8b4a54c 100644 --- a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs +++ b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs @@ -32,6 +32,15 @@ public void ThenTheDefaultForIs(string key, object value) Assert.That(route.Defaults[key], Is.EqualTo(value)); } + [Then(@"the route area is ""(.*?)""")] + public void ThenTheRouteAreaIs(string area) + { + var route = ScenarioContext.Current.GetFetchedRoutes().FirstOrDefault(); + + Assert.That(route, Is.Not.Null); + Assert.That(route.DataTokens["area"], Is.EqualTo(area)); + } + [Then(@"the namespace is ""(.*?)""")] public void ThenTheNamespaceIs(string ns) { diff --git a/src/AttributeRouting.Specs/Subjects/RouteConventionsController.cs b/src/AttributeRouting.Specs/Subjects/RouteConventionsControllers.cs similarity index 75% rename from src/AttributeRouting.Specs/Subjects/RouteConventionsController.cs rename to src/AttributeRouting.Specs/Subjects/RouteConventionsControllers.cs index c6825e2..b71ffb7 100644 --- a/src/AttributeRouting.Specs/Subjects/RouteConventionsController.cs +++ b/src/AttributeRouting.Specs/Subjects/RouteConventionsControllers.cs @@ -1,4 +1,9 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Web.Mvc; +using AttributeRouting.Helpers; using AttributeRouting.Web.Mvc; namespace AttributeRouting.Specs.Subjects @@ -118,4 +123,27 @@ public ActionResult Index() return Content(""); } } + + [AreaRouteConvention] + public class AreaRouteConventionController : Controller + { + public ActionResult Index() + { + return Content(""); + } + } + + public class AreaRouteConventionAttribute : RouteConventionAttributeBase + { + public override IEnumerable GetRouteAttributes(MethodInfo actionMethod) + { + yield return new GETAttribute(actionMethod.Name); + } + + public override RouteAreaAttribute GetDefaultRouteArea(Type controllerType) + { + var areaName = controllerType.Namespace.ValueOr("").Split('.').Last(); + return new RouteAreaAttribute(areaName); + } + } } \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http/DefaultHttpRouteConventionAttribute.cs b/src/AttributeRouting.Web.Http/DefaultHttpRouteConventionAttribute.cs index 591352d..adf29f5 100644 --- a/src/AttributeRouting.Web.Http/DefaultHttpRouteConventionAttribute.cs +++ b/src/AttributeRouting.Web.Http/DefaultHttpRouteConventionAttribute.cs @@ -67,9 +67,9 @@ public override IEnumerable GetRouteAttributes(MethodInfo actio } } - public override string GetDefaultRoutePrefix(MethodInfo actionMethod) + public override RoutePrefixAttribute GetDefaultRoutePrefix(Type controllerType) { - return actionMethod.DeclaringType.GetControllerName(); + return new RoutePrefixAttribute(controllerType.GetControllerName()); } private IRouteAttribute BuildRouteAttribute(HttpRouteConventionInfo convention) diff --git a/src/AttributeRouting.Web.Mvc/RestfulRouteConventionAttribute.cs b/src/AttributeRouting.Web.Mvc/RestfulRouteConventionAttribute.cs index 5a10329..f0699c7 100644 --- a/src/AttributeRouting.Web.Mvc/RestfulRouteConventionAttribute.cs +++ b/src/AttributeRouting.Web.Mvc/RestfulRouteConventionAttribute.cs @@ -1,72 +1,72 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using AttributeRouting.Framework; -using AttributeRouting.Helpers; - -namespace AttributeRouting.Web.Mvc -{ - /// - /// Automatically generates RESTful-style routes for controller actions matching - /// Index, New, Create, Show, Edit, Update, Delete, and Destroy. - /// - public class RestfulRouteConventionAttribute : RouteConventionAttributeBase - { - // Setup conventions - private static readonly List Conventions = new List - { - new RestfulRouteConventionInfo("Index", "GET", ""), - new RestfulRouteConventionInfo("New", "GET", "New"), - new RestfulRouteConventionInfo("Create", "POST", ""), - new RestfulRouteConventionInfo("Show", "GET", "{id}"), - new RestfulRouteConventionInfo("Edit", "GET", "{id}/Edit"), - new RestfulRouteConventionInfo("Update", "PUT", "{id}"), - new RestfulRouteConventionInfo("Delete", "GET", "{id}/Delete"), - new RestfulRouteConventionInfo("Destroy", "DELETE", "{id}") - }; - - public override IEnumerable GetRouteAttributes(MethodInfo actionMethod) - { - var convention = Conventions.SingleOrDefault(c => c.ActionName == actionMethod.Name); - if (convention != null) - yield return BuildRouteAttribute(convention); - } - - public override string GetDefaultRoutePrefix(MethodInfo actionMethod) - { - return actionMethod.DeclaringType.GetControllerName(); - } - - private IRouteAttribute BuildRouteAttribute(RestfulRouteConventionInfo convention) - { - switch (convention.HttpMethod) - { - case "GET": - return new GETAttribute(convention.Url); - case "POST": - return new POSTAttribute(convention.Url); - case "PUT": - return new PUTAttribute(convention.Url); - case "DELETE": - return new DELETEAttribute(convention.Url); - default: - throw new AttributeRoutingException("Unknown HTTP method \"{0}\".".FormatWith(convention.HttpMethod)); - } - } - - private class RestfulRouteConventionInfo - { - public RestfulRouteConventionInfo(string actionName, string httpMethod, string url) - { - ActionName = actionName; - HttpMethod = httpMethod; - Url = url; - } - - public string ActionName { get; private set; } - public string HttpMethod { get; private set; } - public string Url { get; private set; } - } - } -} +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using AttributeRouting.Framework; +using AttributeRouting.Helpers; + +namespace AttributeRouting.Web.Mvc +{ + /// + /// Automatically generates RESTful-style routes for controller actions matching + /// Index, New, Create, Show, Edit, Update, Delete, and Destroy. + /// + public class RestfulRouteConventionAttribute : RouteConventionAttributeBase + { + // Setup conventions + private static readonly List Conventions = new List + { + new RestfulRouteConventionInfo("Index", "GET", ""), + new RestfulRouteConventionInfo("New", "GET", "New"), + new RestfulRouteConventionInfo("Create", "POST", ""), + new RestfulRouteConventionInfo("Show", "GET", "{id}"), + new RestfulRouteConventionInfo("Edit", "GET", "{id}/Edit"), + new RestfulRouteConventionInfo("Update", "PUT", "{id}"), + new RestfulRouteConventionInfo("Delete", "GET", "{id}/Delete"), + new RestfulRouteConventionInfo("Destroy", "DELETE", "{id}") + }; + + public override IEnumerable GetRouteAttributes(MethodInfo actionMethod) + { + var convention = Conventions.SingleOrDefault(c => c.ActionName == actionMethod.Name); + if (convention != null) + yield return BuildRouteAttribute(convention); + } + + public override RoutePrefixAttribute GetDefaultRoutePrefix(Type controllerType) + { + return new RoutePrefixAttribute(controllerType.GetControllerName()); + } + + private IRouteAttribute BuildRouteAttribute(RestfulRouteConventionInfo convention) + { + switch (convention.HttpMethod) + { + case "GET": + return new GETAttribute(convention.Url); + case "POST": + return new POSTAttribute(convention.Url); + case "PUT": + return new PUTAttribute(convention.Url); + case "DELETE": + return new DELETEAttribute(convention.Url); + default: + throw new AttributeRoutingException("Unknown HTTP method \"{0}\".".FormatWith(convention.HttpMethod)); + } + } + + private class RestfulRouteConventionInfo + { + public RestfulRouteConventionInfo(string actionName, string httpMethod, string url) + { + ActionName = actionName; + HttpMethod = httpMethod; + Url = url; + } + + public string ActionName { get; private set; } + public string HttpMethod { get; private set; } + public string Url { get; private set; } + } + } +} diff --git a/src/AttributeRouting/Framework/RouteReflector.cs b/src/AttributeRouting/Framework/RouteReflector.cs index cc9be33..ac45235 100644 --- a/src/AttributeRouting/Framework/RouteReflector.cs +++ b/src/AttributeRouting/Framework/RouteReflector.cs @@ -55,8 +55,16 @@ private IEnumerable BuildRouteSpecifications(IEnumerable(false) let routeAreaAttribute = controllerType.GetCustomAttribute(true) - let routePrefixAttribute = controllerType.GetCustomAttribute(true) + ?? convention.SafeGet(x => x.GetDefaultRouteArea(controllerType)) from actionMethod in controllerType.GetActionMethods(inheritActionsFromBaseController) + // NOTE: The oldConventionalRoutePrefix var is to support obsolete method. + // Once that method is removed, remove oldConventionalRoutePrefix from consideration, + // and move the let routePrefixAttribute op above the loop inside the actionMethed. + let oldConventionalRoutePrefix = convention.SafeGet(x => x.GetDefaultRoutePrefix(actionMethod)) + let routePrefixAttribute = controllerType.GetCustomAttribute(true) + ?? (oldConventionalRoutePrefix.HasValue() + ? new RoutePrefixAttribute(oldConventionalRoutePrefix) + : convention.SafeGet(x => x.GetDefaultRoutePrefix(controllerType))) from routeAttribute in GetRouteAttributes(actionMethod, convention) let routeName = routeAttribute.RouteName let subdomain = GetAreaSubdomain(routeAreaAttribute) @@ -70,14 +78,14 @@ from routeAttribute in GetRouteAttributes(actionMethod, convention) let sitePrecedence = GetSortableOrder(routeAttribute.SitePrecedence) let controllerPrecedence = GetSortableOrder(routeAttribute.ControllerPrecedence) let actionPrecedence = GetSortableOrder(routeAttribute.ActionPrecedence) - orderby sitePrecedence , controllerIndex , controllerPrecedence , actionPrecedence + orderby sitePrecedence, controllerIndex, controllerPrecedence, actionPrecedence select new RouteSpecification { AreaName = routeAreaAttribute.SafeGet(a => a.AreaName), AreaUrl = GetAreaUrl(routeAreaAttribute, subdomain), AreaUrlTranslationKey = routeAreaAttribute.SafeGet(a => a.TranslationKey), Subdomain = subdomain, - RoutePrefixUrl = GetRoutePrefix(routePrefixAttribute, actionMethod, convention), + RoutePrefixUrl = routePrefixAttribute.SafeGet(p => p.Url), RoutePrefixUrlTranslationKey = routePrefixAttribute.SafeGet(a => a.TranslationKey), ControllerType = controllerType, ControllerName = controllerType.GetControllerName(), @@ -171,26 +179,6 @@ private string GetAreaSubdomain(RouteAreaAttribute routeAreaAttribute) return routeAreaAttribute.Subdomain; } - /// - /// Gets a controller's route prefix URL. - /// - /// The for the controller. - /// The for an action. - /// The for the controller. - /// The route prefix URL to apply against the action. - private static string GetRoutePrefix(RoutePrefixAttribute routePrefixAttribute, MethodInfo actionMethod, RouteConventionAttributeBase convention) - { - // Return an explicitly defined route prefix, if defined - if (routePrefixAttribute != null) - return routePrefixAttribute.Url; - - // Otherwise, if this is a convention-based controller, get the convention-based prefix - if (convention != null) - return convention.GetDefaultRoutePrefix(actionMethod); - - return null; - } - /// /// Gets the sortable order for the given value. /// diff --git a/src/AttributeRouting/RouteConventionAttributeBase.cs b/src/AttributeRouting/RouteConventionAttributeBase.cs index 2c43a57..219b4bf 100644 --- a/src/AttributeRouting/RouteConventionAttributeBase.cs +++ b/src/AttributeRouting/RouteConventionAttributeBase.cs @@ -13,18 +13,41 @@ public abstract class RouteConventionAttributeBase : Attribute /// /// Gets the RouteAttributes to be applied to the given action method. /// - /// - /// + /// The action method. + /// A collection of routes for the action. public abstract IEnumerable GetRouteAttributes(MethodInfo actionMethod); /// /// Gets the default route prefix to use if no RoutePrefix is applied on the controller. /// - /// - /// + /// The action method. + /// A default prefix to use for the actions in the controller. + [Obsolete] public virtual string GetDefaultRoutePrefix(MethodInfo actionMethod) { return ""; } + + /// + /// Gets a to apply to the controller + /// if no explicit route prefix is specified. + /// + /// The controller type. + /// A . + public virtual RoutePrefixAttribute GetDefaultRoutePrefix(Type controllerType) + { + return null; + } + + /// + /// Gets a to apply to the controller + /// if no explicit route area is specified. + /// + /// The controller type. + /// A . + public virtual RouteAreaAttribute GetDefaultRouteArea(Type controllerType) + { + return null; + } } } \ No newline at end of file From 39eaefd1803ec39787b2b95bc51bbec1cdc75cf7 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sat, 8 Dec 2012 17:35:21 -0700 Subject: [PATCH 10/97] #164 - added default ctor to route attribute in AttributeRouting.Web.Mvc. These default ctors will end up creating routes that use the name of the action as the route url. --- .../Features/StandardUsage.feature | 47 ++++--- .../Features/StandardUsage.feature.cs | 36 +++--- .../Subjects/RouteAreasControllers.cs | 1 - .../Subjects/StandardUsageController.cs | 30 +++++ .../Controllers/AccountController.cs | 1 - .../Controllers/DangerController.cs | 1 - .../Controllers/RenderActionController.cs | 1 - .../Controllers/RestfulController.cs | 1 - .../DELETEAttribute.cs | 6 + src/AttributeRouting.Web.Mvc/GETAttribute.cs | 6 + src/AttributeRouting.Web.Mvc/POSTAttribute.cs | 6 + src/AttributeRouting.Web.Mvc/PUTAttribute.cs | 6 + .../RouteAttribute.cs | 28 +++- .../Framework/RouteReflector.cs | 5 +- .../Helpers/StringExtensions.cs | 122 +++++++++--------- 15 files changed, 190 insertions(+), 107 deletions(-) diff --git a/src/AttributeRouting.Specs/Features/StandardUsage.feature b/src/AttributeRouting.Specs/Features/StandardUsage.feature index ea75c70..b986a10 100644 --- a/src/AttributeRouting.Specs/Features/StandardUsage.feature +++ b/src/AttributeRouting.Specs/Features/StandardUsage.feature @@ -10,14 +10,21 @@ Scenario Outline: Generating routes for an MVC controller And the namespace is "AttributeRouting.Specs.Subjects" Examples: - | method | action | url | - | GET | Index | | - | HEAD | Index | | - | POST | Create | Create | - | PUT | Update | Update/{id} | - | DELETE | Destroy | Destroy/{id} | - | GET | Wildcards | Wildcards/{*pathInfo} | - | | AnyVerb | AnyVerb | + | method | action | url | + | GET | Index | | + | HEAD | Index | | + | POST | Create | Create | + | PUT | Update | Update/{id} | + | DELETE | Destroy | Destroy/{id} | + | GET | Wildcards | Wildcards/{*pathInfo} | + | | AnyVerb | AnyVerb | + # The URL when using the default ctor should simply be the action name + | GET | GetDefault | GetDefault | + | HEAD | GetDefault | GetDefault | + | PUT | PutDefault | PutDefault | + | POST | PostDefault | PostDefault | + | DELETE | DeleteDefault | DeleteDefault | + | | RouteDefault | RouteDefault | Scenario Outline: Generating routes for an API controller Given I have registered the routes for the HttpStandardUsageController @@ -29,18 +36,18 @@ Scenario Outline: Generating routes for an API controller And the namespace is "AttributeRouting.Specs.Subjects.Http" Examples: - | method | action | url | - | GET | Get | api | - | HEAD | Get | api | - | OPTIONS | Get | api | - | POST | Post | api | - | OPTIONS | Post | api | - | PUT | Put | api/{id} | - | OPTIONS | Put | api/{id} | - | DELETE | Delete | api/{id} | - | OPTIONS | Delete | api/{id} | - | GET | Wildcards | api/Wildcards/{*pathInfo} | - | | AnyVerb | api/AnyVerb | + | method | action | url | + | GET | Get | api | + | HEAD | Get | api | + | OPTIONS | Get | api | + | POST | Post | api | + | OPTIONS | Post | api | + | PUT | Put | api/{id} | + | OPTIONS | Put | api/{id} | + | DELETE | Delete | api/{id} | + | OPTIONS | Delete | api/{id} | + | GET | Wildcards | api/Wildcards/{*pathInfo} | + | | AnyVerb | api/AnyVerb | Scenario: Responding to OPTIONS requests in an API controller Given I have registered the routes for the HttpStandardUsageController diff --git a/src/AttributeRouting.Specs/Features/StandardUsage.feature.cs b/src/AttributeRouting.Specs/Features/StandardUsage.feature.cs index c7b3042..2721111 100644 --- a/src/AttributeRouting.Specs/Features/StandardUsage.feature.cs +++ b/src/AttributeRouting.Specs/Features/StandardUsage.feature.cs @@ -1,9 +1,9 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.9.1.84 +// SpecFlow Version:1.9.0.77 // SpecFlow Generator Version:1.9.0.0 -// Runtime Version:4.0.30319.17929 +// Runtime Version:4.0.30319.18010 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Standard Usage")] @@ -73,6 +73,12 @@ public virtual void ScenarioCleanup() [NUnit.Framework.TestCaseAttribute("DELETE", "Destroy", "Destroy/{id}", null)] [NUnit.Framework.TestCaseAttribute("GET", "Wildcards", "Wildcards/{*pathInfo}", null)] [NUnit.Framework.TestCaseAttribute("", "AnyVerb", "AnyVerb", null)] + [NUnit.Framework.TestCaseAttribute("GET", "GetDefault", "GetDefault", null)] + [NUnit.Framework.TestCaseAttribute("HEAD", "GetDefault", "GetDefault", null)] + [NUnit.Framework.TestCaseAttribute("PUT", "PutDefault", "PutDefault", null)] + [NUnit.Framework.TestCaseAttribute("POST", "PostDefault", "PostDefault", null)] + [NUnit.Framework.TestCaseAttribute("DELETE", "DeleteDefault", "DeleteDefault", null)] + [NUnit.Framework.TestCaseAttribute("", "RouteDefault", "RouteDefault", null)] public virtual void GeneratingRoutesForAnMVCController(string method, string action, string url, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes for an MVC controller", exampleTags); @@ -112,21 +118,21 @@ public virtual void GeneratingRoutesForAnMVCController(string method, string act public virtual void GeneratingRoutesForAnAPIController(string method, string action, string url, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes for an API controller", exampleTags); -#line 22 +#line 29 this.ScenarioSetup(scenarioInfo); -#line 23 +#line 30 testRunner.Given("I have registered the routes for the HttpStandardUsageController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 24 +#line 31 testRunner.When(string.Format("I fetch the routes for the HttpStandardUsageController\'s {0} action", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 25 +#line 32 testRunner.Then(string.Format("the route is constrained to {0} requests", method), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 26 +#line 33 testRunner.And(string.Format("the route url is \"{0}\"", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 27 +#line 34 testRunner.And("the default for \"controller\" is \"HttpStandardUsage\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 28 +#line 35 testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 29 +#line 36 testRunner.And("the namespace is \"AttributeRouting.Specs.Subjects.Http\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); @@ -137,13 +143,13 @@ public virtual void GeneratingRoutesForAnAPIController(string method, string act public virtual void RespondingToOPTIONSRequestsInAnAPIController() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Responding to OPTIONS requests in an API controller", ((string[])(null))); -#line 45 +#line 52 this.ScenarioSetup(scenarioInfo); -#line 46 +#line 53 testRunner.Given("I have registered the routes for the HttpStandardUsageController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 47 +#line 54 testRunner.When("an OPTIONS request for \"api\" is made", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 48 +#line 55 testRunner.Then("the Get action is matched", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); diff --git a/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs b/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs index 401ec08..dced645 100644 --- a/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs @@ -1,5 +1,4 @@ using System.Web.Mvc; -using AttributeRouting.Web; using AttributeRouting.Web.Mvc; namespace AttributeRouting.Specs.Subjects diff --git a/src/AttributeRouting.Specs/Subjects/StandardUsageController.cs b/src/AttributeRouting.Specs/Subjects/StandardUsageController.cs index b26e5eb..15e6a06 100644 --- a/src/AttributeRouting.Specs/Subjects/StandardUsageController.cs +++ b/src/AttributeRouting.Specs/Subjects/StandardUsageController.cs @@ -41,5 +41,35 @@ public ActionResult AnyVerb() { return Content(""); } + + [GET] + public ActionResult GetDefault() + { + return Content(""); + } + + [POST] + public ActionResult PostDefault() + { + return Content(""); + } + + [PUT] + public ActionResult PutDefault() + { + return Content(""); + } + + [DELETE] + public ActionResult DeleteDefault() + { + return Content(""); + } + + [Route] + public ActionResult RouteDefault() + { + return Content(""); + } } } \ No newline at end of file diff --git a/src/AttributeRouting.Tests.Web/Controllers/AccountController.cs b/src/AttributeRouting.Tests.Web/Controllers/AccountController.cs index 2ccdc94..2579f81 100644 --- a/src/AttributeRouting.Tests.Web/Controllers/AccountController.cs +++ b/src/AttributeRouting.Tests.Web/Controllers/AccountController.cs @@ -3,7 +3,6 @@ using System.Web.Routing; using System.Web.Security; using AttributeRouting.Tests.Web.Models; -using AttributeRouting.Web; using AttributeRouting.Web.Mvc; namespace AttributeRouting.Tests.Web.Controllers diff --git a/src/AttributeRouting.Tests.Web/Controllers/DangerController.cs b/src/AttributeRouting.Tests.Web/Controllers/DangerController.cs index 8f568d6..0906b04 100644 --- a/src/AttributeRouting.Tests.Web/Controllers/DangerController.cs +++ b/src/AttributeRouting.Tests.Web/Controllers/DangerController.cs @@ -1,5 +1,4 @@ using System.Web.Mvc; -using AttributeRouting.Web; using AttributeRouting.Web.Mvc; namespace AttributeRouting.Tests.Web.Controllers diff --git a/src/AttributeRouting.Tests.Web/Controllers/RenderActionController.cs b/src/AttributeRouting.Tests.Web/Controllers/RenderActionController.cs index 9974138..d14ce0f 100644 --- a/src/AttributeRouting.Tests.Web/Controllers/RenderActionController.cs +++ b/src/AttributeRouting.Tests.Web/Controllers/RenderActionController.cs @@ -1,5 +1,4 @@ using System.Web.Mvc; -using AttributeRouting.Web; using AttributeRouting.Web.Mvc; namespace AttributeRouting.Tests.Web.Controllers diff --git a/src/AttributeRouting.Tests.Web/Controllers/RestfulController.cs b/src/AttributeRouting.Tests.Web/Controllers/RestfulController.cs index 7c979c7..fb11ad2 100644 --- a/src/AttributeRouting.Tests.Web/Controllers/RestfulController.cs +++ b/src/AttributeRouting.Tests.Web/Controllers/RestfulController.cs @@ -1,5 +1,4 @@ using System.Web.Mvc; -using AttributeRouting.Web; using AttributeRouting.Web.Mvc; namespace AttributeRouting.Tests.Web.Controllers diff --git a/src/AttributeRouting.Web.Mvc/DELETEAttribute.cs b/src/AttributeRouting.Web.Mvc/DELETEAttribute.cs index b98eb03..d431a6b 100644 --- a/src/AttributeRouting.Web.Mvc/DELETEAttribute.cs +++ b/src/AttributeRouting.Web.Mvc/DELETEAttribute.cs @@ -7,6 +7,12 @@ namespace AttributeRouting.Web.Mvc /// public class DELETEAttribute : RouteAttribute { + /// + /// Specify a route for DELETE request. + /// The route URL will be the name of the action. + /// + public DELETEAttribute() : base(HttpVerbs.Delete) {} + /// /// Specify a route for DELETE request. /// diff --git a/src/AttributeRouting.Web.Mvc/GETAttribute.cs b/src/AttributeRouting.Web.Mvc/GETAttribute.cs index 903982f..5adae1b 100644 --- a/src/AttributeRouting.Web.Mvc/GETAttribute.cs +++ b/src/AttributeRouting.Web.Mvc/GETAttribute.cs @@ -7,6 +7,12 @@ namespace AttributeRouting.Web.Mvc /// public class GETAttribute : RouteAttribute { + /// + /// Specify a route for a GET request. + /// The route URL will be the name of the action. + /// + public GETAttribute() : base(HttpVerbs.Get, HttpVerbs.Head) {} + /// /// Specify a route for a GET request. /// diff --git a/src/AttributeRouting.Web.Mvc/POSTAttribute.cs b/src/AttributeRouting.Web.Mvc/POSTAttribute.cs index 5abc877..eb6bf39 100644 --- a/src/AttributeRouting.Web.Mvc/POSTAttribute.cs +++ b/src/AttributeRouting.Web.Mvc/POSTAttribute.cs @@ -7,6 +7,12 @@ namespace AttributeRouting.Web.Mvc /// public class POSTAttribute : RouteAttribute { + /// + /// Specify a route for a POST request. + /// The route URL will be the name of the action. + /// + public POSTAttribute() : base(HttpVerbs.Post) {} + /// /// Specify a route for a POST request. /// diff --git a/src/AttributeRouting.Web.Mvc/PUTAttribute.cs b/src/AttributeRouting.Web.Mvc/PUTAttribute.cs index f179bbf..71f9726 100644 --- a/src/AttributeRouting.Web.Mvc/PUTAttribute.cs +++ b/src/AttributeRouting.Web.Mvc/PUTAttribute.cs @@ -7,6 +7,12 @@ namespace AttributeRouting.Web.Mvc /// public class PUTAttribute : RouteAttribute { + /// + /// Specify a route for a PUT request. + /// The route URL will be the name of the action. + /// + public PUTAttribute() : base(HttpVerbs.Put) {} + /// /// Specify a route for a PUT request. /// diff --git a/src/AttributeRouting.Web.Mvc/RouteAttribute.cs b/src/AttributeRouting.Web.Mvc/RouteAttribute.cs index bc5e180..cf37cda 100644 --- a/src/AttributeRouting.Web.Mvc/RouteAttribute.cs +++ b/src/AttributeRouting.Web.Mvc/RouteAttribute.cs @@ -12,19 +12,39 @@ namespace AttributeRouting.Web.Mvc [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public class RouteAttribute : ActionMethodSelectorAttribute, IRouteAttribute { + /// + /// Specify the route information for an action. + /// The route URL will be the name of the action. + /// + public RouteAttribute() + { + HttpMethods = new string[0]; + ActionPrecedence = int.MaxValue; + ControllerPrecedence = int.MaxValue; + SitePrecedence = int.MaxValue; + } + /// /// Specify the route information for an action. /// /// The url that is associated with this action public RouteAttribute(string routeUrl) + : this() { if (routeUrl == null) throw new ArgumentNullException("routeUrl"); RouteUrl = routeUrl; - HttpMethods = new string[0]; - ActionPrecedence = int.MaxValue; - ControllerPrecedence = int.MaxValue; - SitePrecedence = int.MaxValue; + } + + /// + /// Specify the route information for an action. + /// The route URL will be the name of the action. + /// + /// The httpMethods against which to constrain the route + public RouteAttribute(params HttpVerbs[] allowedMethods) + : this() + { + HttpMethods = allowedMethods.Select(m => m.ToString().ToUpperInvariant()).ToArray(); } /// diff --git a/src/AttributeRouting/Framework/RouteReflector.cs b/src/AttributeRouting/Framework/RouteReflector.cs index ac45235..91223f4 100644 --- a/src/AttributeRouting/Framework/RouteReflector.cs +++ b/src/AttributeRouting/Framework/RouteReflector.cs @@ -69,6 +69,7 @@ from routeAttribute in GetRouteAttributes(actionMethod, convention) let routeName = routeAttribute.RouteName let subdomain = GetAreaSubdomain(routeAreaAttribute) let isAsyncController = controllerType.IsAsyncController() + let actionName = GetActionName(actionMethod, isAsyncController) /* controlling precedence: * site precedence of route > * controller index > @@ -89,8 +90,8 @@ from routeAttribute in GetRouteAttributes(actionMethod, convention) RoutePrefixUrlTranslationKey = routePrefixAttribute.SafeGet(a => a.TranslationKey), ControllerType = controllerType, ControllerName = controllerType.GetControllerName(), - ActionName = GetActionName(actionMethod, isAsyncController), - RouteUrl = routeAttribute.RouteUrl, + ActionName = actionName, + RouteUrl = routeAttribute.RouteUrl ?? actionName, RouteUrlTranslationKey = routeAttribute.TranslationKey, HttpMethods = routeAttribute.HttpMethods, RouteName = routeName, diff --git a/src/AttributeRouting/Helpers/StringExtensions.cs b/src/AttributeRouting/Helpers/StringExtensions.cs index 2cc3a9e..e5ac605 100644 --- a/src/AttributeRouting/Helpers/StringExtensions.cs +++ b/src/AttributeRouting/Helpers/StringExtensions.cs @@ -1,67 +1,67 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; - -namespace AttributeRouting.Helpers -{ - public static class StringExtensions - { - public static bool ValueEquals(this string s, string other) - { - if (s == null) - return other == null; - - return s.Equals(other, StringComparison.OrdinalIgnoreCase); - } - - public static bool HasValue(this string s) - { - return !String.IsNullOrWhiteSpace(s); - } - - public static bool HasNoValue(this string s) - { - return String.IsNullOrWhiteSpace(s); - } - +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace AttributeRouting.Helpers +{ + public static class StringExtensions + { + public static bool ValueEquals(this string s, string other) + { + if (s == null) + return other == null; + + return s.Equals(other, StringComparison.OrdinalIgnoreCase); + } + + public static bool HasValue(this string s) + { + return !String.IsNullOrWhiteSpace(s); + } + + public static bool HasNoValue(this string s) + { + return String.IsNullOrWhiteSpace(s); + } + public static string ValueOr(this string s, string otherValue) { if (s.HasValue()) return s; return otherValue; - } - - public static string FormatWith(this string s, params object[] args) - { - return String.Format(s, args); - } - - public static string[] SplitAndTrim(this string s, params string[] separator) - { - if (!s.HasValue()) - return null; - - return s.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(i => i.Trim()).ToArray(); - } - - public static bool IsValidUrl(this string s, bool allowTokens = false) - { - var urlParts = s.Split(new[] { "/" }, StringSplitOptions.RemoveEmptyEntries); - - var invalidUrlPatterns = new List - { - @"[#%&:<>/{0}]".FormatWith(allowTokens ? null : @"\\\+\{\}?\*"), - @"\.\.", - @"\.$", - @"^ ", - @" $" - }; - - var invalidUrlPattern = String.Join("|", invalidUrlPatterns); - - return !urlParts.Any(p => Regex.IsMatch(p, invalidUrlPattern)); - } - } -} + } + + public static string FormatWith(this string s, params object[] args) + { + return String.Format(s, args); + } + + public static string[] SplitAndTrim(this string s, params string[] separator) + { + if (!s.HasValue()) + return null; + + return s.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(i => i.Trim()).ToArray(); + } + + public static bool IsValidUrl(this string s, bool allowTokens = false) + { + var urlParts = s.Split(new[] { "/" }, StringSplitOptions.RemoveEmptyEntries); + + var invalidUrlPatterns = new List + { + @"[#%&:<>/{0}]".FormatWith(allowTokens ? null : @"\\\+\{\}?\*"), + @"\.\.", + @"\.$", + @"^ ", + @" $" + }; + + var invalidUrlPattern = String.Join("|", invalidUrlPatterns); + + return !urlParts.Any(p => Regex.IsMatch(p, invalidUrlPattern)); + } + } +} From 17e541f34b1223ec49251038929dc0c2ac842b54 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sat, 8 Dec 2012 17:58:04 -0700 Subject: [PATCH 11/97] #164 - added default ctor to RouteAreaAttribute. This default ctor will end up using the last section of the controller's namespace as the value of the aarea name and anrea url prefix. --- .../AttributeRouting.Specs.csproj | 2 - .../Features/RouteAreas.feature | 106 ++++++---------- .../Features/RouteAreas.feature.cs | 120 +++++++----------- .../Subjects/Http/HttpAreasController.cs | 33 ----- .../Http/HttpExplicitAreaUrlController.cs | 21 --- .../Subjects/RouteAreasControllers.cs | 10 ++ .../Framework/RouteReflector.cs | 41 +++++- .../Helpers/ObjectExtensions.cs | 52 ++++---- .../Helpers/ReflectionExtensions.cs | 6 + src/AttributeRouting/RouteAreaAttribute.cs | 7 + 10 files changed, 172 insertions(+), 226 deletions(-) delete mode 100644 src/AttributeRouting.Specs/Subjects/Http/HttpAreasController.cs delete mode 100644 src/AttributeRouting.Specs/Subjects/Http/HttpExplicitAreaUrlController.cs diff --git a/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj b/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj index e583024..a163338 100644 --- a/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj +++ b/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj @@ -147,9 +147,7 @@ - - diff --git a/src/AttributeRouting.Specs/Features/RouteAreas.feature b/src/AttributeRouting.Specs/Features/RouteAreas.feature index 1122ae8..6294193 100644 --- a/src/AttributeRouting.Specs/Features/RouteAreas.feature +++ b/src/AttributeRouting.Specs/Features/RouteAreas.feature @@ -1,67 +1,41 @@ -Feature: Route Areas - -Scenario: Generating area routes - # MVC +Feature: Route Areas + +Scenario: Generating area routes Given I have registered the routes for the AreasController - When I fetch the routes for the Areas controller's Index action - Then the route url is "Area/Index" - And the data token for "area" is "Area" - # Web API - Given I have registered the routes for the HttpAreasController - When I fetch the routes for the HttpAreas controller's Get action - Then the route url is "ApiArea/Get" - And the data token for "area" is "ApiArea" - -Scenario: Generating area routes when route urls specify a duplicate area prefix - # MVC - Given I have registered the routes for the AreasController - When I fetch the routes for the Areas controller's DuplicatePrefix action - Then the route url is "Area/DuplicatePrefix" - # Web API - Given I have registered the routes for the HttpAreasController - When I fetch the routes for the HttpAreas controller's DuplicatePrefix action - Then the route url is "ApiArea/DuplicatePrefix" - -Scenario: Generating absolute routes when a route area is defined - # MVC - Given I have registered the routes for the AreasController - When I fetch the routes for the Areas controller's Absolute action - Then the route url is "AreaAbsolute" - # Web API - Given I have registered the routes for the HttpAreasController - When I fetch the routes for the HttpAreas controller's Absolute action - Then the route url is "ApiAreaAbsolute" - -Scenario: Generating area routes when route url starts with the area prefix - # MVC - Given I have registered the routes for the AreasController - When I fetch the routes for the Areas controller's RouteBeginsWithAreaName action - Then the route url is "Area/Areas" - # Web API - Given I have registered the routes for the HttpAreasController - When I fetch the routes for the HttpAreas controller's RouteBeginsWithAreaName action - Then the route url is "ApiArea/ApiAreas" - -Scenario: Generating area routes with an explicit area url - # MVC - Given I have registered the routes for the ExplicitAreaUrlController - When I fetch the routes for the ExplicitAreaUrl controller's Index action - Then the route url is "ExplicitArea/Index" - And the data token for "area" is "Area" - # Web API - Given I have registered the routes for the HttpExplicitAreaUrlController - When I fetch the routes for the HttpExplicitAreaUrl controller's Get action - Then the route url is "ApiExplicitArea/Get" - And the data token for "area" is "ApiArea" - -Scenario: Generating area routes with an explicit area url when route urls specify a duplicate area prefix - # MVC - Given I have registered the routes for the ExplicitAreaUrlController - When I fetch the routes for the ExplicitAreaUrl controller's DuplicatePrefix action - Then the route url is "ExplicitArea/DuplicatePrefix" - And the data token for "area" is "Area" - # Web API - Given I have registered the routes for the HttpExplicitAreaUrlController - When I fetch the routes for the HttpExplicitAreaUrl controller's DuplicatePrefix action - Then the route url is "ApiExplicitArea/DuplicatePrefix" - And the data token for "area" is "ApiArea" + When I fetch the routes for the Areas controller's Index action + Then the route url is "Area/Index" + And the data token for "area" is "Area" + +Scenario: Generating area routes when route urls specify a duplicate area prefix + Given I have registered the routes for the AreasController + When I fetch the routes for the Areas controller's DuplicatePrefix action + Then the route url is "Area/DuplicatePrefix" + +Scenario: Generating absolute routes when a route area is defined + Given I have registered the routes for the AreasController + When I fetch the routes for the Areas controller's Absolute action + Then the route url is "AreaAbsolute" + +Scenario: Generating area routes when route url starts with the area prefix + Given I have registered the routes for the AreasController + When I fetch the routes for the Areas controller's RouteBeginsWithAreaName action + Then the route url is "Area/Areas" + +Scenario: Generating area routes with an explicit area url + Given I have registered the routes for the ExplicitAreaUrlController + When I fetch the routes for the ExplicitAreaUrl controller's Index action + Then the route url is "ExplicitArea/Index" + And the data token for "area" is "Area" + +Scenario: Generating area routes with an explicit area url when route urls specify a duplicate area prefix + Given I have registered the routes for the ExplicitAreaUrlController + When I fetch the routes for the ExplicitAreaUrl controller's DuplicatePrefix action + Then the route url is "ExplicitArea/DuplicatePrefix" + And the data token for "area" is "Area" + +Scenario: Generating area routes with the default ctor of the RouteAreaAttribute + Given I have registered the routes for the DefaultRouteAreaController + When I fetch the routes for the DefaultRouteArea controller's Index action + Then the route url is "Subjects/Index" + And the data token for "area" is "Subjects" + diff --git a/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs b/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs index 63879f1..2d1d851 100644 --- a/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs @@ -1,9 +1,9 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.9.1.84 +// SpecFlow Version:1.9.0.77 // SpecFlow Generator Version:1.9.0.0 -// Runtime Version:4.0.30319.17929 +// Runtime Version:4.0.30319.18010 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Route Areas")] @@ -71,22 +71,14 @@ public virtual void GeneratingAreaRoutes() TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating area routes", ((string[])(null))); #line 3 this.ScenarioSetup(scenarioInfo); -#line 5 +#line 4 testRunner.Given("I have registered the routes for the AreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 6 +#line 5 testRunner.When("I fetch the routes for the Areas controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 7 +#line 6 testRunner.Then("the route url is \"Area/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 8 +#line 7 testRunner.And("the data token for \"area\" is \"Area\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 10 - testRunner.Given("I have registered the routes for the HttpAreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 11 - testRunner.When("I fetch the routes for the HttpAreas controller\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 12 - testRunner.Then("the route url is \"ApiArea/Get\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 13 - testRunner.And("the data token for \"area\" is \"ApiArea\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -96,20 +88,14 @@ public virtual void GeneratingAreaRoutes() public virtual void GeneratingAreaRoutesWhenRouteUrlsSpecifyADuplicateAreaPrefix() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating area routes when route urls specify a duplicate area prefix", ((string[])(null))); -#line 15 +#line 9 this.ScenarioSetup(scenarioInfo); -#line 17 +#line 10 testRunner.Given("I have registered the routes for the AreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 18 +#line 11 testRunner.When("I fetch the routes for the Areas controller\'s DuplicatePrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 19 +#line 12 testRunner.Then("the route url is \"Area/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 21 - testRunner.Given("I have registered the routes for the HttpAreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 22 - testRunner.When("I fetch the routes for the HttpAreas controller\'s DuplicatePrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 23 - testRunner.Then("the route url is \"ApiArea/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -119,20 +105,14 @@ public virtual void GeneratingAreaRoutesWhenRouteUrlsSpecifyADuplicateAreaPrefix public virtual void GeneratingAbsoluteRoutesWhenARouteAreaIsDefined() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating absolute routes when a route area is defined", ((string[])(null))); -#line 25 +#line 14 this.ScenarioSetup(scenarioInfo); -#line 27 +#line 15 testRunner.Given("I have registered the routes for the AreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 28 +#line 16 testRunner.When("I fetch the routes for the Areas controller\'s Absolute action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 29 +#line 17 testRunner.Then("the route url is \"AreaAbsolute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 31 - testRunner.Given("I have registered the routes for the HttpAreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 32 - testRunner.When("I fetch the routes for the HttpAreas controller\'s Absolute action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 33 - testRunner.Then("the route url is \"ApiAreaAbsolute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -142,20 +122,14 @@ public virtual void GeneratingAbsoluteRoutesWhenARouteAreaIsDefined() public virtual void GeneratingAreaRoutesWhenRouteUrlStartsWithTheAreaPrefix() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating area routes when route url starts with the area prefix", ((string[])(null))); -#line 35 +#line 19 this.ScenarioSetup(scenarioInfo); -#line 37 +#line 20 testRunner.Given("I have registered the routes for the AreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 38 +#line 21 testRunner.When("I fetch the routes for the Areas controller\'s RouteBeginsWithAreaName action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 39 +#line 22 testRunner.Then("the route url is \"Area/Areas\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 41 - testRunner.Given("I have registered the routes for the HttpAreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 42 - testRunner.When("I fetch the routes for the HttpAreas controller\'s RouteBeginsWithAreaName action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 43 - testRunner.Then("the route url is \"ApiArea/ApiAreas\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } @@ -165,24 +139,16 @@ public virtual void GeneratingAreaRoutesWhenRouteUrlStartsWithTheAreaPrefix() public virtual void GeneratingAreaRoutesWithAnExplicitAreaUrl() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating area routes with an explicit area url", ((string[])(null))); -#line 45 +#line 24 this.ScenarioSetup(scenarioInfo); -#line 47 +#line 25 testRunner.Given("I have registered the routes for the ExplicitAreaUrlController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 48 +#line 26 testRunner.When("I fetch the routes for the ExplicitAreaUrl controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 49 +#line 27 testRunner.Then("the route url is \"ExplicitArea/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 50 +#line 28 testRunner.And("the data token for \"area\" is \"Area\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 52 - testRunner.Given("I have registered the routes for the HttpExplicitAreaUrlController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 53 - testRunner.When("I fetch the routes for the HttpExplicitAreaUrl controller\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 54 - testRunner.Then("the route url is \"ApiExplicitArea/Get\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 55 - testRunner.And("the data token for \"area\" is \"ApiArea\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -194,25 +160,35 @@ public virtual void GeneratingAreaRoutesWithAnExplicitAreaUrlWhenRouteUrlsSpecif { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating area routes with an explicit area url when route urls specify a duplic" + "ate area prefix", ((string[])(null))); -#line 57 +#line 30 this.ScenarioSetup(scenarioInfo); -#line 59 +#line 31 testRunner.Given("I have registered the routes for the ExplicitAreaUrlController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 60 +#line 32 testRunner.When("I fetch the routes for the ExplicitAreaUrl controller\'s DuplicatePrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 61 +#line 33 testRunner.Then("the route url is \"ExplicitArea/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 62 +#line 34 testRunner.And("the data token for \"area\" is \"Area\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 64 - testRunner.Given("I have registered the routes for the HttpExplicitAreaUrlController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 65 - testRunner.When("I fetch the routes for the HttpExplicitAreaUrl controller\'s DuplicatePrefix actio" + - "n", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 66 - testRunner.Then("the route url is \"ApiExplicitArea/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 67 - testRunner.And("the data token for \"area\" is \"ApiArea\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Generating area routes with the default ctor of the RouteAreaAttribute")] + public virtual void GeneratingAreaRoutesWithTheDefaultCtorOfTheRouteAreaAttribute() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating area routes with the default ctor of the RouteAreaAttribute", ((string[])(null))); +#line 36 +this.ScenarioSetup(scenarioInfo); +#line 37 + testRunner.Given("I have registered the routes for the DefaultRouteAreaController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 38 + testRunner.When("I fetch the routes for the DefaultRouteArea controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 39 + testRunner.Then("the route url is \"Subjects/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 40 + testRunner.And("the data token for \"area\" is \"Subjects\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Subjects/Http/HttpAreasController.cs b/src/AttributeRouting.Specs/Subjects/Http/HttpAreasController.cs deleted file mode 100644 index 04299d5..0000000 --- a/src/AttributeRouting.Specs/Subjects/Http/HttpAreasController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Web.Http; -using AttributeRouting.Web.Http; - -namespace AttributeRouting.Specs.Subjects.Http -{ - [RouteArea("ApiArea")] - public class HttpAreasController : ApiController - { - [GET("Get")] - public string Get() - { - return ""; - } - - [GET("ApiArea/DuplicatePrefix")] - public string DuplicatePrefix() - { - return ""; - } - - [GET("ApiAreaAbsolute", IsAbsoluteUrl = true)] - public string Absolute() - { - return ""; - } - - [GET("ApiAreas")] - public string RouteBeginsWithAreaName() - { - return ""; - } - } -} \ No newline at end of file diff --git a/src/AttributeRouting.Specs/Subjects/Http/HttpExplicitAreaUrlController.cs b/src/AttributeRouting.Specs/Subjects/Http/HttpExplicitAreaUrlController.cs deleted file mode 100644 index 37dac9c..0000000 --- a/src/AttributeRouting.Specs/Subjects/Http/HttpExplicitAreaUrlController.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Web.Http; -using AttributeRouting.Web.Http; - -namespace AttributeRouting.Specs.Subjects.Http -{ - [RouteArea("ApiArea", AreaUrl = "ApiExplicitArea")] - public class HttpExplicitAreaUrlController : ApiController - { - [GET("Get")] - public string Get() - { - return ""; - } - - [GET("ApiExplicitArea/DuplicatePrefix")] - public string DuplicatePrefix() - { - return ""; - } - } -} diff --git a/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs b/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs index dced645..2c109cb 100644 --- a/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs @@ -46,4 +46,14 @@ public ActionResult DuplicatePrefix() return Content(""); } } + + [RouteArea] + public class DefaultRouteAreaController : Controller + { + [GET("Index")] + public ActionResult Index() + { + return Content(""); + } + } } diff --git a/src/AttributeRouting/Framework/RouteReflector.cs b/src/AttributeRouting/Framework/RouteReflector.cs index 91223f4..5176772 100644 --- a/src/AttributeRouting/Framework/RouteReflector.cs +++ b/src/AttributeRouting/Framework/RouteReflector.cs @@ -51,11 +51,16 @@ private IEnumerable BuildRouteSpecifications(IEnumerable(false) let routeAreaAttribute = controllerType.GetCustomAttribute(true) ?? convention.SafeGet(x => x.GetDefaultRouteArea(controllerType)) + let subdomain = GetAreaSubdomain(routeAreaAttribute) + let areaName = GetAreaName(routeAreaAttribute, controllerType) + let areaUrl = GetAreaUrl(routeAreaAttribute, subdomain, controllerType) from actionMethod in controllerType.GetActionMethods(inheritActionsFromBaseController) // NOTE: The oldConventionalRoutePrefix var is to support obsolete method. // Once that method is removed, remove oldConventionalRoutePrefix from consideration, @@ -67,8 +72,6 @@ from actionMethod in controllerType.GetActionMethods(inheritActionsFromBaseContr : convention.SafeGet(x => x.GetDefaultRoutePrefix(controllerType))) from routeAttribute in GetRouteAttributes(actionMethod, convention) let routeName = routeAttribute.RouteName - let subdomain = GetAreaSubdomain(routeAreaAttribute) - let isAsyncController = controllerType.IsAsyncController() let actionName = GetActionName(actionMethod, isAsyncController) /* controlling precedence: * site precedence of route > @@ -82,8 +85,8 @@ from routeAttribute in GetRouteAttributes(actionMethod, convention) orderby sitePrecedence, controllerIndex, controllerPrecedence, actionPrecedence select new RouteSpecification { - AreaName = routeAreaAttribute.SafeGet(a => a.AreaName), - AreaUrl = GetAreaUrl(routeAreaAttribute, subdomain), + AreaName = areaName, + AreaUrl = areaUrl, AreaUrlTranslationKey = routeAreaAttribute.SafeGet(a => a.TranslationKey), Subdomain = subdomain, RoutePrefixUrl = routePrefixAttribute.SafeGet(p => p.Url), @@ -138,13 +141,33 @@ private static IEnumerable GetRouteAttributes(MethodInfo action return attributes; } + /// + /// Gets the area name. + /// + /// The for the controller. + /// The type of the controller. + /// The name of the area. + private static string GetAreaName(RouteAreaAttribute routeAreaAttribute, Type controllerType) + { + if (routeAreaAttribute == null) + return null; + + // If given an area name, then use it. + if (routeAreaAttribute.AreaName.HasValue()) + return routeAreaAttribute.AreaName; + + // Otherwise, use the last section of the namespace of the controller, as a convention. + return controllerType.GetLastSectionOfNamespace(); + } + /// /// Gets the area URL prefix. /// /// The for the controller. /// The configured subdomain for the area. + /// The type of the controller. /// The URL prefix for the area. - private static string GetAreaUrl(RouteAreaAttribute routeAreaAttribute, string subdomain) + private static string GetAreaUrl(RouteAreaAttribute routeAreaAttribute, string subdomain, Type controllerType) { if (routeAreaAttribute == null) return null; @@ -156,7 +179,13 @@ private static string GetAreaUrl(RouteAreaAttribute routeAreaAttribute, string s if (subdomain.HasValue() && routeAreaAttribute.AreaUrl.HasNoValue()) return null; - return routeAreaAttribute.AreaUrl ?? routeAreaAttribute.AreaName; + // If we're given an area url or an area name, then use it. + var areaUrlOrName = routeAreaAttribute.AreaUrl ?? routeAreaAttribute.AreaName; + if (areaUrlOrName != null) + return areaUrlOrName; + + // Otherwise, use the last section of the namespace of the controller, as a convention. + return controllerType.GetLastSectionOfNamespace(); } /// diff --git a/src/AttributeRouting/Helpers/ObjectExtensions.cs b/src/AttributeRouting/Helpers/ObjectExtensions.cs index 0ec02f0..dc8be6a 100644 --- a/src/AttributeRouting/Helpers/ObjectExtensions.cs +++ b/src/AttributeRouting/Helpers/ObjectExtensions.cs @@ -1,42 +1,42 @@ -using System; -using System.Linq.Expressions; - -namespace AttributeRouting.Helpers -{ - public static class ObjectExtensions - { +using System; +using System.Linq.Expressions; + +namespace AttributeRouting.Helpers +{ + public static class ObjectExtensions + { /// /// Returns true if the object is null or it's string representation is null or empty. /// public static bool HasNoValue(this object obj) { return (obj == null || obj.ToString().HasNoValue()); - } - + } + /// /// Will walk the given expression tree to get the value at the leaf. /// If a NullReferenceException is thrown, the default for the leaf type will be returned. /// - public static TResult SafeGet(this T obj, Expression> memberExpression) - { - return SafeGet(obj, memberExpression, default(TResult)); + public static TResult SafeGet(this T obj, Expression> memberExpression) + { + return SafeGet(obj, memberExpression, default(TResult)); } /// /// Will walk the given expression tree to get the value at the leaf. /// If a NullReferenceException is thrown, the given default will be returned. /// - public static TResult SafeGet(this T obj, Expression> memberExpression, TResult defaultValue) - { - try - { - var result = memberExpression.Compile().Invoke(obj); - return result; - } - catch (NullReferenceException) - { - return defaultValue; - } - } - } -} + public static TResult SafeGet(this T obj, Expression> memberExpression, TResult defaultValue) + { + try + { + var result = memberExpression.Compile().Invoke(obj); + return result; + } + catch (NullReferenceException) + { + return defaultValue; + } + } + } +} diff --git a/src/AttributeRouting/Helpers/ReflectionExtensions.cs b/src/AttributeRouting/Helpers/ReflectionExtensions.cs index 8b01e1e..c04e77f 100644 --- a/src/AttributeRouting/Helpers/ReflectionExtensions.cs +++ b/src/AttributeRouting/Helpers/ReflectionExtensions.cs @@ -8,6 +8,12 @@ namespace AttributeRouting.Helpers { public static class ReflectionExtensions { + public static string GetLastSectionOfNamespace(this Type type) + { + var ns = type.Namespace; + return ns == null ? null : ns.Split('.').Last(); + } + public static IEnumerable GetActionMethods(this Type type, bool inheritActionsFromBaseController) { var flags = BindingFlags.Public | BindingFlags.Instance; diff --git a/src/AttributeRouting/RouteAreaAttribute.cs b/src/AttributeRouting/RouteAreaAttribute.cs index b4b6cac..f837a88 100644 --- a/src/AttributeRouting/RouteAreaAttribute.cs +++ b/src/AttributeRouting/RouteAreaAttribute.cs @@ -8,11 +8,18 @@ namespace AttributeRouting [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class RouteAreaAttribute : Attribute { + /// + /// Defines an area shared by all the routes defined in this controller. + /// The area name will be the most immediate section of the namespace for the controller. + /// + public RouteAreaAttribute() { } + /// /// Defines an area shared by all the routes defined in this controller. /// /// The name of the containing area public RouteAreaAttribute(string areaName) + : this() { if (areaName == null) throw new ArgumentNullException("areaName"); From dc4244ec274fc0bda37d491f6524d1465278fc94 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sat, 8 Dec 2012 18:12:23 -0700 Subject: [PATCH 12/97] #164 - added default ctor to RoutePrefixAttribute. This default ctor will end up using the controller name as the route prefix for routes in the controller. --- .../Features/RoutePrefixes.feature | 12 +- .../Features/RoutePrefixes.feature.cs | 29 +++- .../Http/HttpRoutePrefixesController.cs | 10 ++ .../Subjects/RoutePrefixesControllers.cs | 127 ++++++++++-------- .../Framework/RouteReflector.cs | 34 +++-- src/AttributeRouting/RoutePrefixAttribute.cs | 7 + 6 files changed, 143 insertions(+), 76 deletions(-) diff --git a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature index 77c4e6b..f37cbae 100644 --- a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature +++ b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature @@ -78,4 +78,14 @@ Scenario: Generating routes when a route area and route prefix are defined and t # Web API Given I have registered the routes for the HttpAreaRoutePrefixesController When I fetch the routes for the HttpAreaRoutePrefixes controller's RelativeUrlIsAreaUrl action - Then the route url is "ApiArea/ApiPrefix/ApiArea" \ No newline at end of file + Then the route url is "ApiArea/ApiPrefix/ApiArea" + +Scenario: Generating routes with the default ctor of the RoutePrefixAttribute + # MVC + Given I have registered the routes for the DefaultRoutePrefixController + When I fetch the routes for the DefaultRoutePrefix controller's Index action + Then the route url is "DefaultRoutePrefix/Index" + # Web API + Given I have registered the routes for the HttpDefaultRoutePrefixController + When I fetch the routes for the HttpDefaultRoutePrefix controller's Get action + Then the route url is "HttpDefaultRoutePrefix/Index" diff --git a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs index d5cc48d..d948adc 100644 --- a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs +++ b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs @@ -1,9 +1,9 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.9.1.84 +// SpecFlow Version:1.9.0.77 // SpecFlow Generator Version:1.9.0.0 -// Runtime Version:4.0.30319.17929 +// Runtime Version:4.0.30319.18010 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Route Prefixes")] @@ -251,6 +251,29 @@ public virtual void GeneratingRoutesWhenARouteAreaAndRoutePrefixAreDefinedAndThe "l action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 81 testRunner.Then("the route url is \"ApiArea/ApiPrefix/ApiArea\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Generating routes with the default ctor of the RoutePrefixAttribute")] + public virtual void GeneratingRoutesWithTheDefaultCtorOfTheRoutePrefixAttribute() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes with the default ctor of the RoutePrefixAttribute", ((string[])(null))); +#line 83 +this.ScenarioSetup(scenarioInfo); +#line 85 + testRunner.Given("I have registered the routes for the DefaultRoutePrefixController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 86 + testRunner.When("I fetch the routes for the DefaultRoutePrefix controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 87 + testRunner.Then("the route url is \"DefaultRoutePrefix/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 89 + testRunner.Given("I have registered the routes for the HttpDefaultRoutePrefixController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 90 + testRunner.When("I fetch the routes for the HttpDefaultRoutePrefix controller\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 91 + testRunner.Then("the route url is \"HttpDefaultRoutePrefix/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Subjects/Http/HttpRoutePrefixesController.cs b/src/AttributeRouting.Specs/Subjects/Http/HttpRoutePrefixesController.cs index 652b96f..6236d88 100644 --- a/src/AttributeRouting.Specs/Subjects/Http/HttpRoutePrefixesController.cs +++ b/src/AttributeRouting.Specs/Subjects/Http/HttpRoutePrefixesController.cs @@ -30,4 +30,14 @@ public string RouteBeginsWithRoutePrefix() return ""; } } + + [RoutePrefix] + public class HttpDefaultRoutePrefixController : ApiController + { + [GET("Index")] + public string Get() + { + return ""; + } + } } \ No newline at end of file diff --git a/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs b/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs index 35ff040..8b6806b 100644 --- a/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs @@ -1,63 +1,72 @@ -using System.Web.Mvc; -using AttributeRouting.Web; -using AttributeRouting.Web.Mvc; - -namespace AttributeRouting.Specs.Subjects -{ - [RoutePrefix("Prefix")] - public class RoutePrefixesController : Controller - { - [GET("Index")] - public ActionResult Index() - { - return Content(""); - } - - [GET("Prefix/DuplicatePrefix")] - public ActionResult DuplicatePrefix() - { - return Content(""); - } - - [GET("PrefixAbsolute", IsAbsoluteUrl = true)] - public ActionResult Absolute() - { - return Content(""); - } - - [GET("Prefixer")] - public ActionResult RouteBeginsWithRoutePrefix() - { - return Content(""); - } - } - - [RouteArea("Area")] - [RoutePrefix("Prefix")] - public class AreaRoutePrefixesController : Controller - { - [GET("Index")] - public ActionResult Index() - { - return Content(""); - } - - [GET("Prefix/DuplicatePrefix")] - public ActionResult DuplicatePrefix() - { - return Content(""); - } - - [GET("AreaPrefixAbsolute", IsAbsoluteUrl = true)] - public ActionResult Absolute() - { - return Content(""); - } - - [GET("Area")] +using System.Web.Mvc; +using AttributeRouting.Web.Mvc; + +namespace AttributeRouting.Specs.Subjects +{ + [RoutePrefix("Prefix")] + public class RoutePrefixesController : Controller + { + [GET("Index")] + public ActionResult Index() + { + return Content(""); + } + + [GET("Prefix/DuplicatePrefix")] + public ActionResult DuplicatePrefix() + { + return Content(""); + } + + [GET("PrefixAbsolute", IsAbsoluteUrl = true)] + public ActionResult Absolute() + { + return Content(""); + } + + [GET("Prefixer")] + public ActionResult RouteBeginsWithRoutePrefix() + { + return Content(""); + } + } + + [RouteArea("Area")] + [RoutePrefix("Prefix")] + public class AreaRoutePrefixesController : Controller + { + [GET("Index")] + public ActionResult Index() + { + return Content(""); + } + + [GET("Prefix/DuplicatePrefix")] + public ActionResult DuplicatePrefix() + { + return Content(""); + } + + [GET("AreaPrefixAbsolute", IsAbsoluteUrl = true)] + public ActionResult Absolute() + { + return Content(""); + } + + [GET("Area")] public string RelativeUrlIsAreaUrl() { return ""; - } - } + } + } + + [RoutePrefix] + public class DefaultRoutePrefixController : Controller + { + [GET("Index")] + public ActionResult Index() + { + return Content(""); + } + } } \ No newline at end of file diff --git a/src/AttributeRouting/Framework/RouteReflector.cs b/src/AttributeRouting/Framework/RouteReflector.cs index 5176772..c1a7182 100644 --- a/src/AttributeRouting/Framework/RouteReflector.cs +++ b/src/AttributeRouting/Framework/RouteReflector.cs @@ -70,6 +70,7 @@ from actionMethod in controllerType.GetActionMethods(inheritActionsFromBaseContr ?? (oldConventionalRoutePrefix.HasValue() ? new RoutePrefixAttribute(oldConventionalRoutePrefix) : convention.SafeGet(x => x.GetDefaultRoutePrefix(controllerType))) + let routePrefixUrl = GetRoutePrefixUrl(routePrefixAttribute, controllerType) from routeAttribute in GetRouteAttributes(actionMethod, convention) let routeName = routeAttribute.RouteName let actionName = GetActionName(actionMethod, isAsyncController) @@ -89,7 +90,7 @@ from routeAttribute in GetRouteAttributes(actionMethod, convention) AreaUrl = areaUrl, AreaUrlTranslationKey = routeAreaAttribute.SafeGet(a => a.TranslationKey), Subdomain = subdomain, - RoutePrefixUrl = routePrefixAttribute.SafeGet(p => p.Url), + RoutePrefixUrl = routePrefixUrl, RoutePrefixUrlTranslationKey = routePrefixAttribute.SafeGet(a => a.TranslationKey), ControllerType = controllerType, ControllerName = controllerType.GetControllerName(), @@ -153,11 +154,8 @@ private static string GetAreaName(RouteAreaAttribute routeAreaAttribute, Type co return null; // If given an area name, then use it. - if (routeAreaAttribute.AreaName.HasValue()) - return routeAreaAttribute.AreaName; - // Otherwise, use the last section of the namespace of the controller, as a convention. - return controllerType.GetLastSectionOfNamespace(); + return routeAreaAttribute.AreaName ?? controllerType.GetLastSectionOfNamespace(); } /// @@ -180,12 +178,9 @@ private static string GetAreaUrl(RouteAreaAttribute routeAreaAttribute, string s return null; // If we're given an area url or an area name, then use it. - var areaUrlOrName = routeAreaAttribute.AreaUrl ?? routeAreaAttribute.AreaName; - if (areaUrlOrName != null) - return areaUrlOrName; - // Otherwise, use the last section of the namespace of the controller, as a convention. - return controllerType.GetLastSectionOfNamespace(); + var areaUrlOrName = routeAreaAttribute.AreaUrl ?? routeAreaAttribute.AreaName; + return areaUrlOrName ?? controllerType.GetLastSectionOfNamespace(); } /// @@ -203,10 +198,23 @@ private string GetAreaSubdomain(RouteAreaAttribute routeAreaAttribute) where o.Key == routeAreaAttribute.AreaName select o.Value).FirstOrDefault(); - if (subdomainOverride != null) - return subdomainOverride; + return subdomainOverride ?? routeAreaAttribute.Subdomain; + } + + /// + /// Gets the route prefix for the routes in the controller. + /// + /// The for the controller. + /// The type of the controller. + /// The URL prefix for the routes in the controller. + private static string GetRoutePrefixUrl(RoutePrefixAttribute routePrefixAttribute, Type controllerType) + { + if (routePrefixAttribute == null) + return null; - return routeAreaAttribute.Subdomain; + // If we're given route prefix url, use it. + // Otherwise, use the controller name as a convention. + return routePrefixAttribute.Url ?? controllerType.GetControllerName(); } /// diff --git a/src/AttributeRouting/RoutePrefixAttribute.cs b/src/AttributeRouting/RoutePrefixAttribute.cs index b2238fe..78bb182 100644 --- a/src/AttributeRouting/RoutePrefixAttribute.cs +++ b/src/AttributeRouting/RoutePrefixAttribute.cs @@ -8,11 +8,18 @@ namespace AttributeRouting [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class RoutePrefixAttribute : Attribute { + /// + /// Defines a prefix shared by all the routes defined in this controller. + /// The url prefix will be the name of the controller without the "Controller" suffix. + /// + public RoutePrefixAttribute() { } + /// /// Defines a prefix shared by all the routes defined in this controller. /// /// The url prefix to apply to the routes public RoutePrefixAttribute(string url) + : this() { if (url == null) throw new ArgumentNullException("url"); From a3049c170499d3ec7b4a41665d312bdb09409adf Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sat, 8 Dec 2012 20:03:48 -0700 Subject: [PATCH 13/97] #161 - fixed bug in url generation for routes that included a querystring route param constraint. --- .../AttributeRouting.Specs.csproj | 2 +- .../Subjects/BugFixesController.cs | 16 ------- .../Subjects/BugFixesControllers.cs | 39 +++++++++++++++ .../Tests/BugFixTests.cs | 30 +++++++++--- .../Views/Home/Index.aspx | 48 +++++++++---------- .../Framework/HttpAttributeRoute.cs | 2 +- .../Framework/AttributeRoute.cs | 3 +- .../Framework/AttributeRouteExtensions.cs | 36 +++++++++++++- 8 files changed, 125 insertions(+), 51 deletions(-) delete mode 100644 src/AttributeRouting.Specs/Subjects/BugFixesController.cs create mode 100644 src/AttributeRouting.Specs/Subjects/BugFixesControllers.cs diff --git a/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj b/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj index a163338..e502b37 100644 --- a/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj +++ b/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj @@ -139,7 +139,7 @@ - + diff --git a/src/AttributeRouting.Specs/Subjects/BugFixesController.cs b/src/AttributeRouting.Specs/Subjects/BugFixesController.cs deleted file mode 100644 index 356bb3f..0000000 --- a/src/AttributeRouting.Specs/Subjects/BugFixesController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Web.Mvc; -using AttributeRouting.Web.Mvc; - -namespace AttributeRouting.Specs.Subjects -{ - [RoutePrefix("BugFixes")] - public class BugFixesController : Controller - { - [GET("Gallery/_CenterImage/{guid_Gallery?}/{slideShow?}/{currentController?}/{image?}")] - public ActionResult Issue43_OptionalParamsAreMucky(Guid? guid_Gallery, bool? slideShow, string currentController, string image) - { - return Content("I'm fixed!"); - } - } -} diff --git a/src/AttributeRouting.Specs/Subjects/BugFixesControllers.cs b/src/AttributeRouting.Specs/Subjects/BugFixesControllers.cs new file mode 100644 index 0000000..4e6940e --- /dev/null +++ b/src/AttributeRouting.Specs/Subjects/BugFixesControllers.cs @@ -0,0 +1,39 @@ +using System; +using System.Web.Http; +using System.Web.Mvc; +using AttributeRouting.Web.Mvc; + +namespace AttributeRouting.Specs.Subjects +{ + [RoutePrefix("BugFixes")] + public class BugFixesController : Controller + { + [GET("Gallery/_CenterImage/{guid_Gallery?}/{slideShow?}/{currentController?}/{image?}")] + public ActionResult Issue43_OptionalParamsAreMucky(Guid? guid_Gallery, bool? slideShow, string currentController, string image) + { + return Content("I'm fixed!"); + } + } + + [RouteArea("Cms", AreaUrl = "{culture}/Cms")] + [RoutePrefix("Content")] + public class Issue161TestController : Controller + { + [GET("Items?{p:int}")] + public string Index(int p = 1) + { + return ""; + } + } + + [RouteArea("Cms", AreaUrl = "{culture}/Cms")] + [RoutePrefix("Content")] + public class Issue161TestHttpController : ApiController + { + [GET("Items?{p:int}", RouteName = "Issue161TestHttp")] + public string GetIndex(int p = 1) + { + return ""; + } + } +} diff --git a/src/AttributeRouting.Specs/Tests/BugFixTests.cs b/src/AttributeRouting.Specs/Tests/BugFixTests.cs index 339b158..6e5f728 100644 --- a/src/AttributeRouting.Specs/Tests/BugFixTests.cs +++ b/src/AttributeRouting.Specs/Tests/BugFixTests.cs @@ -3,9 +3,9 @@ using System.Linq; using System.Threading; using System.Web.Http; +using System.Web.Mvc; using System.Web.Routing; using AttributeRouting.Framework.Localization; -using AttributeRouting.Logging; using AttributeRouting.Specs.Subjects; using AttributeRouting.Specs.Subjects.Http; using AttributeRouting.Web.Http.WebHost; @@ -19,7 +19,23 @@ namespace AttributeRouting.Specs.Tests public class BugFixTests { [Test] - public void OData_style_http_url_bonks() + public void Issue161_Querystring_param_constraints_mucks_up_url_generation() + { + // re: issue #161 + + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => config.AddRoutesFromController()); + + var urlHelper = new UrlHelper(MockBuilder.BuildRequestContext()); + var routeValues = new { area = "Cms", culture = "en", p = 1 }; + var expectedUrl = urlHelper.Action("Index", "Issue161Test", routeValues); + + Assert.That(expectedUrl, Is.EqualTo("/en/Cms/Content/Items?p=1")); + } + + [Test] + public void Issue120_OData_style_http_url_bonks() { // re: issue #120 @@ -32,7 +48,7 @@ public void OData_style_http_url_bonks() } [Test] - public void Generating_two_routes_for_api_get_requests() + public void Issue102_Generating_two_routes_for_api_get_requests() { // re: issue #102 @@ -46,7 +62,7 @@ public void Generating_two_routes_for_api_get_requests() } [Test] - public void Ensure_that_incompletely_mocked_request_context_does_not_generate_error_in_determining_http_method() + public void Issue25_Ensure_that_incompletely_mocked_request_context_does_not_generate_error_in_determining_http_method() { // re: issue #25 @@ -59,7 +75,7 @@ public void Ensure_that_incompletely_mocked_request_context_does_not_generate_er } [Test] - public void Ensure_that_routes_with_optional_url_params_are_correctly_matched() + public void Issue43_Ensure_that_routes_with_optional_url_params_are_correctly_matched() { // re: issue #43 @@ -74,7 +90,7 @@ public void Ensure_that_routes_with_optional_url_params_are_correctly_matched() } [Test] - public void Ensure_that_inbound_routing_works_when_contraining_by_culture() + public void Issue53_Ensure_that_inbound_routing_works_when_contraining_by_culture() { // re: issue #53 @@ -104,7 +120,7 @@ public void Ensure_that_inbound_routing_works_when_contraining_by_culture() } [Test] - public void Ensure_that_async_controller_action_can_be_mapped() + public void Issue84_Ensure_that_async_controller_action_can_be_mapped() { // re: issue #84 RouteTable.Routes.Clear(); diff --git a/src/AttributeRouting.Tests.Web/Views/Home/Index.aspx b/src/AttributeRouting.Tests.Web/Views/Home/Index.aspx index 8089916..ec4fa21 100644 --- a/src/AttributeRouting.Tests.Web/Views/Home/Index.aspx +++ b/src/AttributeRouting.Tests.Web/Views/Home/Index.aspx @@ -1,25 +1,25 @@ -<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> - - - Home - - - - -

Attribute-Based Routing for ASP.NET MVC

- -

- The AttributeRouting documentation is available at - github. -

- -

- Do you want to debug the routes for this sample? -

- +<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> + + + Home + + + + +

Attribute-Based Routing for ASP.NET MVC

+

- Just some test links: - <%: Html.ActionLink("About", "About", "Home") %> -

- -
+ The AttributeRouting documentation is available at + github. +

+ +

+ Do you want to debug the routes for this sample? +

+ +

Just some test links:

+
    +
  • <%: Html.ActionLink("About", "About", "Home") %>
  • +
+ +
diff --git a/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs b/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs index 09d3120..994dc8f 100644 --- a/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs +++ b/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs @@ -93,7 +93,7 @@ public override IHttpRouteData GetRouteData(string virtualPathRoot, HttpRequestM public override IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary values) { // Let the underlying route do its thing, and if it does, then add some functionality on top. - var virtualPathData = base.GetVirtualPath(request, values); + var virtualPathData = this.GetVirtualPath(() => base.GetVirtualPath(request, values)); if (virtualPathData == null) return null; diff --git a/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs b/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs index 92eba8b..24d7d9b 100644 --- a/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs +++ b/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Web; using System.Web.Routing; +using AttributeRouting.Constraints; using AttributeRouting.Framework; using AttributeRouting.Helpers; @@ -91,7 +92,7 @@ public override RouteData GetRouteData(HttpContextBase httpContext) public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { // Let the underlying route do its thing, and if it does, then add some functionality on top. - var virtualPathData = base.GetVirtualPath(requestContext, values); + var virtualPathData = this.GetVirtualPath(() => base.GetVirtualPath(requestContext, values)); if (virtualPathData == null) return null; diff --git a/src/AttributeRouting/Framework/AttributeRouteExtensions.cs b/src/AttributeRouting/Framework/AttributeRouteExtensions.cs index 180c5de..7855fa5 100644 --- a/src/AttributeRouting/Framework/AttributeRouteExtensions.cs +++ b/src/AttributeRouting/Framework/AttributeRouteExtensions.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading; +using AttributeRouting.Constraints; using AttributeRouting.Helpers; namespace AttributeRouting.Framework @@ -110,7 +112,7 @@ public static bool IsCultureNameMatched(this IAttributeRoute route, string curre return true; // Match if this route has no translations for the neutral current UI culture. - if (!translations.Any(t => t.CultureName == currentUINeutralCultureName)) + if (translations.All(t => t.CultureName != currentUINeutralCultureName)) return true; } @@ -118,6 +120,38 @@ public static bool IsCultureNameMatched(this IAttributeRoute route, string curre return false; } + public static TVirtualPathData GetVirtualPath(this IAttributeRoute route, Func fromBaseMethod) + where TVirtualPathData : class + { + // Remove querystring route constraints: + // the base GetVirtualPath will not inject route params that have constraints into the querystring. + var queryStringConstraints = new Dictionary(); + var constraintKeys = route.Constraints.Keys.Select(k => k).ToList(); + foreach (var constraintKey in constraintKeys) + { + var constraint = route.Constraints[constraintKey]; + var constraintToTest = constraint is IOptionalRouteConstraintWrapper + ? ((IOptionalRouteConstraintWrapper)constraint).Constraint + : constraint; + + if (constraintToTest is IQueryStringRouteConstraintWrapper) + queryStringConstraints.Add(constraintKey, constraint); + + route.Constraints.Remove(constraintKey); + } + + // Let the underlying route do its thing. + var virtualPathData = fromBaseMethod(); + + // Add the querystring constraints back in. + foreach (var queryStringConstraint in queryStringConstraints) + { + route.Constraints.Add(queryStringConstraint.Key, queryStringConstraint.Value); + } + + return virtualPathData; + } + /// /// Gets the translated virtual path for this route. /// From 7ab21d8b59d8e239ed3fe9ab42ed0f2bb3d7c888 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sat, 8 Dec 2012 21:44:01 -0700 Subject: [PATCH 14/97] #165 - added two flags to the route attributes: IgnoreRoutePrefix and IgnoreAreaUrl. These flags control whether to prepend route prefixes or area urls to the generated route url. --- .../Features/RouteAreas.feature | 5 + .../Features/RouteAreas.feature.cs | 47 +++++++--- .../Features/RoutePrefixes.feature | 10 ++ .../Features/RoutePrefixes.feature.cs | 93 ++++++++++++------- .../Http/HttpRoutePrefixesController.cs | 6 ++ .../Subjects/RouteAreasControllers.cs | 6 ++ .../Subjects/RoutePrefixesControllers.cs | 6 ++ .../HttpRouteAttribute.cs | 10 +- .../RouteAttribute.cs | 10 +- .../Framework/RouteBuilder.cs | 45 ++++----- .../Framework/RouteReflector.cs | 2 + .../Framework/RouteSpecification.cs | 4 + src/AttributeRouting/IRouteAttribute.cs | 12 +++ 13 files changed, 179 insertions(+), 77 deletions(-) diff --git a/src/AttributeRouting.Specs/Features/RouteAreas.feature b/src/AttributeRouting.Specs/Features/RouteAreas.feature index 6294193..ace3daa 100644 --- a/src/AttributeRouting.Specs/Features/RouteAreas.feature +++ b/src/AttributeRouting.Specs/Features/RouteAreas.feature @@ -21,6 +21,11 @@ Scenario: Generating area routes when route url starts with the area prefix When I fetch the routes for the Areas controller's RouteBeginsWithAreaName action Then the route url is "Area/Areas" +Scenario: Generating area routes when ignoring the area url + Given I have registered the routes for the AreasController + When I fetch the routes for the Areas controller's NoAreaUrl action + Then the route url is "NoAreaUrl" + Scenario: Generating area routes with an explicit area url Given I have registered the routes for the ExplicitAreaUrlController When I fetch the routes for the ExplicitAreaUrl controller's Index action diff --git a/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs b/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs index 2d1d851..51449c0 100644 --- a/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs @@ -134,20 +134,37 @@ public virtual void GeneratingAreaRoutesWhenRouteUrlStartsWithTheAreaPrefix() this.ScenarioCleanup(); } + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Generating area routes when ignoring the area url")] + public virtual void GeneratingAreaRoutesWhenIgnoringTheAreaUrl() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating area routes when ignoring the area url", ((string[])(null))); +#line 24 +this.ScenarioSetup(scenarioInfo); +#line 25 + testRunner.Given("I have registered the routes for the AreasController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 26 + testRunner.When("I fetch the routes for the Areas controller\'s NoAreaUrl action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 27 + testRunner.Then("the route url is \"NoAreaUrl\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Generating area routes with an explicit area url")] public virtual void GeneratingAreaRoutesWithAnExplicitAreaUrl() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating area routes with an explicit area url", ((string[])(null))); -#line 24 +#line 29 this.ScenarioSetup(scenarioInfo); -#line 25 +#line 30 testRunner.Given("I have registered the routes for the ExplicitAreaUrlController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 26 +#line 31 testRunner.When("I fetch the routes for the ExplicitAreaUrl controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 27 +#line 32 testRunner.Then("the route url is \"ExplicitArea/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 28 +#line 33 testRunner.And("the data token for \"area\" is \"Area\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); @@ -160,15 +177,15 @@ public virtual void GeneratingAreaRoutesWithAnExplicitAreaUrlWhenRouteUrlsSpecif { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating area routes with an explicit area url when route urls specify a duplic" + "ate area prefix", ((string[])(null))); -#line 30 +#line 35 this.ScenarioSetup(scenarioInfo); -#line 31 +#line 36 testRunner.Given("I have registered the routes for the ExplicitAreaUrlController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 32 +#line 37 testRunner.When("I fetch the routes for the ExplicitAreaUrl controller\'s DuplicatePrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 33 +#line 38 testRunner.Then("the route url is \"ExplicitArea/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 34 +#line 39 testRunner.And("the data token for \"area\" is \"Area\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); @@ -179,15 +196,15 @@ public virtual void GeneratingAreaRoutesWithAnExplicitAreaUrlWhenRouteUrlsSpecif public virtual void GeneratingAreaRoutesWithTheDefaultCtorOfTheRouteAreaAttribute() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating area routes with the default ctor of the RouteAreaAttribute", ((string[])(null))); -#line 36 +#line 41 this.ScenarioSetup(scenarioInfo); -#line 37 +#line 42 testRunner.Given("I have registered the routes for the DefaultRouteAreaController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 38 +#line 43 testRunner.When("I fetch the routes for the DefaultRouteArea controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 39 +#line 44 testRunner.Then("the route url is \"Subjects/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 40 +#line 45 testRunner.And("the data token for \"area\" is \"Subjects\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); diff --git a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature index f37cbae..0bc4ffe 100644 --- a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature +++ b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature @@ -40,6 +40,16 @@ Scenario: Generating prefixed routes when route url starts with the route prefix When I fetch the routes for the HttpRoutePrefixes controller's RouteBeginsWithRoutePrefix action Then the route url is "ApiPrefix/ApiPrefixer" +Scenario: Generating prefixed routes when ignoring the route prefix + # MVC + Given I have registered the routes for the RoutePrefixesController + When I fetch the routes for the RoutePrefixes controller's NoPrefix action + Then the route url is "NoPrefix" + # Web API + Given I have registered the routes for the HttpRoutePrefixesController + When I fetch the routes for the HttpRoutePrefixes controller's NoPrefix action + Then the route url is "NoApiPrefix" + Scenario: Generating prefixed area routes # MVC Given I have registered the routes for the AreaRoutePrefixesController diff --git a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs index d948adc..03fc662 100644 --- a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs +++ b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs @@ -158,24 +158,47 @@ public virtual void GeneratingPrefixedRoutesWhenRouteUrlStartsWithTheRoutePrefix this.ScenarioCleanup(); } + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Generating prefixed routes when ignoring the route prefix")] + public virtual void GeneratingPrefixedRoutesWhenIgnoringTheRoutePrefix() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating prefixed routes when ignoring the route prefix", ((string[])(null))); +#line 43 +this.ScenarioSetup(scenarioInfo); +#line 45 + testRunner.Given("I have registered the routes for the RoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 46 + testRunner.When("I fetch the routes for the RoutePrefixes controller\'s NoPrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 47 + testRunner.Then("the route url is \"NoPrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 49 + testRunner.Given("I have registered the routes for the HttpRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 50 + testRunner.When("I fetch the routes for the HttpRoutePrefixes controller\'s NoPrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 51 + testRunner.Then("the route url is \"NoApiPrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Generating prefixed area routes")] public virtual void GeneratingPrefixedAreaRoutes() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating prefixed area routes", ((string[])(null))); -#line 43 +#line 53 this.ScenarioSetup(scenarioInfo); -#line 45 +#line 55 testRunner.Given("I have registered the routes for the AreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 46 +#line 56 testRunner.When("I fetch the routes for the AreaRoutePrefixes controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 47 +#line 57 testRunner.Then("the route url is \"Area/Prefix/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 49 +#line 59 testRunner.Given("I have registered the routes for the HttpAreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 50 +#line 60 testRunner.When("I fetch the routes for the HttpAreaRoutePrefixes controller\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 51 +#line 61 testRunner.Then("the route url is \"ApiArea/ApiPrefix/Get\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); @@ -186,20 +209,20 @@ public virtual void GeneratingPrefixedAreaRoutes() public virtual void GeneratingPrefixedAreaRoutesWhenRouteUrlsSpecifyADuplicatePrefix() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating prefixed area routes when route urls specify a duplicate prefix", ((string[])(null))); -#line 53 +#line 63 this.ScenarioSetup(scenarioInfo); -#line 55 +#line 65 testRunner.Given("I have registered the routes for the AreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 56 +#line 66 testRunner.When("I fetch the routes for the AreaRoutePrefixes controller\'s DuplicatePrefix action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 57 +#line 67 testRunner.Then("the route url is \"Area/Prefix/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 59 +#line 69 testRunner.Given("I have registered the routes for the HttpAreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 60 +#line 70 testRunner.When("I fetch the routes for the HttpAreaRoutePrefixes controller\'s DuplicatePrefix act" + "ion", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 61 +#line 71 testRunner.Then("the route url is \"ApiArea/ApiPrefix/DuplicatePrefix\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); @@ -210,19 +233,19 @@ public virtual void GeneratingPrefixedAreaRoutesWhenRouteUrlsSpecifyADuplicatePr public virtual void GeneratingAbsoluteRoutesWhenARouteAreaAndRoutePrefixIsDefined() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating absolute routes when a route area and route prefix is defined", ((string[])(null))); -#line 63 +#line 73 this.ScenarioSetup(scenarioInfo); -#line 65 +#line 75 testRunner.Given("I have registered the routes for the AreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 66 +#line 76 testRunner.When("I fetch the routes for the AreaRoutePrefixes controller\'s Absolute action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 67 +#line 77 testRunner.Then("the route url is \"AreaPrefixAbsolute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 69 +#line 79 testRunner.Given("I have registered the routes for the HttpAreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 70 +#line 80 testRunner.When("I fetch the routes for the HttpAreaRoutePrefixes controller\'s Absolute action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 71 +#line 81 testRunner.Then("the route url is \"ApiAreaPrefixAbsolute\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); @@ -235,21 +258,21 @@ public virtual void GeneratingRoutesWhenARouteAreaAndRoutePrefixAreDefinedAndThe { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes when a route area and route prefix are defined and the action r" + "especifies the area url", ((string[])(null))); -#line 73 +#line 83 this.ScenarioSetup(scenarioInfo); -#line 75 +#line 85 testRunner.Given("I have registered the routes for the AreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 76 +#line 86 testRunner.When("I fetch the routes for the AreaRoutePrefixes controller\'s RelativeUrlIsAreaUrl ac" + "tion", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 77 +#line 87 testRunner.Then("the route url is \"Area/Prefix/Area\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 79 +#line 89 testRunner.Given("I have registered the routes for the HttpAreaRoutePrefixesController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 80 +#line 90 testRunner.When("I fetch the routes for the HttpAreaRoutePrefixes controller\'s RelativeUrlIsAreaUr" + "l action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 81 +#line 91 testRunner.Then("the route url is \"ApiArea/ApiPrefix/ApiArea\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); @@ -260,19 +283,19 @@ public virtual void GeneratingRoutesWhenARouteAreaAndRoutePrefixAreDefinedAndThe public virtual void GeneratingRoutesWithTheDefaultCtorOfTheRoutePrefixAttribute() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes with the default ctor of the RoutePrefixAttribute", ((string[])(null))); -#line 83 +#line 93 this.ScenarioSetup(scenarioInfo); -#line 85 +#line 95 testRunner.Given("I have registered the routes for the DefaultRoutePrefixController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 86 +#line 96 testRunner.When("I fetch the routes for the DefaultRoutePrefix controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 87 +#line 97 testRunner.Then("the route url is \"DefaultRoutePrefix/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 89 +#line 99 testRunner.Given("I have registered the routes for the HttpDefaultRoutePrefixController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 90 +#line 100 testRunner.When("I fetch the routes for the HttpDefaultRoutePrefix controller\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 91 +#line 101 testRunner.Then("the route url is \"HttpDefaultRoutePrefix/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); diff --git a/src/AttributeRouting.Specs/Subjects/Http/HttpRoutePrefixesController.cs b/src/AttributeRouting.Specs/Subjects/Http/HttpRoutePrefixesController.cs index 6236d88..597ace7 100644 --- a/src/AttributeRouting.Specs/Subjects/Http/HttpRoutePrefixesController.cs +++ b/src/AttributeRouting.Specs/Subjects/Http/HttpRoutePrefixesController.cs @@ -29,6 +29,12 @@ public string RouteBeginsWithRoutePrefix() { return ""; } + + [GET("NoApiPrefix", IgnoreRoutePrefix = true)] + public string NoPrefix() + { + return ""; + } } [RoutePrefix] diff --git a/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs b/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs index 2c109cb..e9228cf 100644 --- a/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs @@ -29,6 +29,12 @@ public ActionResult RouteBeginsWithAreaName() { return Content(""); } + + [GET("NoAreaUrl", IgnoreAreaUrl = true)] + public ActionResult NoAreaUrl() + { + return Content(""); + } } [RouteArea("Area", AreaUrl = "ExplicitArea")] diff --git a/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs b/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs index 8b6806b..0297b62 100644 --- a/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs @@ -29,6 +29,12 @@ public ActionResult RouteBeginsWithRoutePrefix() { return Content(""); } + + [GET("NoPrefix", IgnoreRoutePrefix = true)] + public string NoPrefix() + { + return ""; + } } [RouteArea("Area")] diff --git a/src/AttributeRouting.Web.Http/HttpRouteAttribute.cs b/src/AttributeRouting.Web.Http/HttpRouteAttribute.cs index 9b7f627..6a7053d 100644 --- a/src/AttributeRouting.Web.Http/HttpRouteAttribute.cs +++ b/src/AttributeRouting.Web.Http/HttpRouteAttribute.cs @@ -62,7 +62,11 @@ public int Precedence public string RouteName { get; set; } - public bool IsAbsoluteUrl { get; set; } + public bool IsAbsoluteUrl + { + get { return IgnoreAreaUrl && IgnoreRoutePrefix; } + set { IgnoreAreaUrl = IgnoreRoutePrefix = value; } + } public string TranslationKey { get; set; } @@ -89,5 +93,9 @@ public bool AppendTrailingSlash } public bool? AppendTrailingSlashFlag { get; private set; } + + public bool IgnoreRoutePrefix { get; set; } + + public bool IgnoreAreaUrl { get; set; } } } \ No newline at end of file diff --git a/src/AttributeRouting.Web.Mvc/RouteAttribute.cs b/src/AttributeRouting.Web.Mvc/RouteAttribute.cs index cf37cda..a5b0523 100644 --- a/src/AttributeRouting.Web.Mvc/RouteAttribute.cs +++ b/src/AttributeRouting.Web.Mvc/RouteAttribute.cs @@ -84,7 +84,11 @@ public int Precedence public string RouteName { get; set; } - public bool IsAbsoluteUrl { get; set; } + public bool IsAbsoluteUrl + { + get { return IgnoreAreaUrl && IgnoreRoutePrefix; } + set { IgnoreAreaUrl = IgnoreRoutePrefix = value; } + } public string TranslationKey { get; set; } @@ -111,6 +115,10 @@ public bool AppendTrailingSlash } public bool? AppendTrailingSlashFlag { get; private set; } + + public bool IgnoreRoutePrefix { get; set; } + + public bool IgnoreAreaUrl { get; set; } public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) { diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index aa05499..35fd328 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -103,13 +103,12 @@ private string CreateRouteUrl(RouteSpecification routeSpec) return CreateRouteUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, - routeSpec.IsAbsoluteUrl, - routeSpec.UseLowercaseRoute); + routeSpec); } - private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUrl, bool isAbsoluteUrl, bool? useLowercaseRoute) + private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUrl, RouteSpecification routeSpec) { - var tokenizedUrl = BuildTokenizedUrl(routeUrl, routePrefix, areaUrl, isAbsoluteUrl); + var tokenizedUrl = BuildTokenizedUrl(routeUrl, routePrefix, areaUrl, routeSpec); var tokenizedPath = RemoveQueryString(tokenizedUrl); var detokenizedPath = DetokenizeUrl(tokenizedPath); @@ -129,7 +128,7 @@ private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUr var urlBuilder = new StringBuilder(detokenizedPath); // If we are lowercasing routes, then lowercase everything but the route params - var lower = useLowercaseRoute.HasValue ? useLowercaseRoute.Value : _configuration.UseLowercaseRoutes; + var lower = routeSpec.UseLowercaseRoute.GetValueOrDefault(_configuration.UseLowercaseRoutes); if (lower) { for (var i = 0; i < urlBuilder.Length; i++) @@ -202,7 +201,7 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro constraints.Add("httpMethod", _routeConstraintFactory.CreateRestfulHttpMethodConstraint(routeSpec.HttpMethods)); // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. - var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec.IsAbsoluteUrl); + var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); // Need to keep track of query params. @@ -320,27 +319,24 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro return constraints; } - private string BuildTokenizedUrl(string routeUrl, string routePrefixUrl, string areaUrl, bool isAbsoluteUrl) + private string BuildTokenizedUrl(string routeUrl, string routePrefixUrl, string areaUrl, RouteSpecification routeSpec) { var delimitedUrl = routeUrl + "/"; - - if (!isAbsoluteUrl) + + // Prepend prefix if available + if (routePrefixUrl.HasValue() && !routeSpec.IgnoreRoutePrefix) { - // Prepend prefix if available - if (routePrefixUrl.HasValue()) - { - var delimitedRoutePrefix = routePrefixUrl + "/"; - if (!delimitedUrl.StartsWith(delimitedRoutePrefix)) - delimitedUrl = delimitedRoutePrefix + delimitedUrl; - } + var delimitedRoutePrefix = routePrefixUrl + "/"; + if (!delimitedUrl.StartsWith(delimitedRoutePrefix)) + delimitedUrl = delimitedRoutePrefix + delimitedUrl; + } - // Prepend area url if available - if (areaUrl.HasValue()) - { - var delimitedAreaUrl = areaUrl + "/"; - if (!delimitedUrl.StartsWith(delimitedAreaUrl)) - delimitedUrl = delimitedAreaUrl + delimitedUrl; - } + // Prepend area url if available + if (areaUrl.HasValue() && !routeSpec.IgnoreAreaUrl) + { + var delimitedAreaUrl = areaUrl + "/"; + if (!delimitedUrl.StartsWith(delimitedAreaUrl)) + delimitedUrl = delimitedAreaUrl + delimitedUrl; } return delimitedUrl.Trim('/'); @@ -433,8 +429,7 @@ private IEnumerable CreateRouteTranslations(RouteSpecification var routeUrl = CreateRouteUrl(translatedRouteUrl ?? routeSpec.RouteUrl, translatedRoutePrefix ?? routeSpec.RoutePrefixUrl, translatedAreaUrl ?? routeSpec.AreaUrl, - routeSpec.IsAbsoluteUrl, - routeSpec.UseLowercaseRoute); + routeSpec); var translatedRoutes = _routeFactory.CreateAttributeRoutes(routeUrl, CreateRouteDefaults(routeSpec), diff --git a/src/AttributeRouting/Framework/RouteReflector.cs b/src/AttributeRouting/Framework/RouteReflector.cs index c1a7182..fa88a67 100644 --- a/src/AttributeRouting/Framework/RouteReflector.cs +++ b/src/AttributeRouting/Framework/RouteReflector.cs @@ -100,6 +100,8 @@ from routeAttribute in GetRouteAttributes(actionMethod, convention) HttpMethods = routeAttribute.HttpMethods, RouteName = routeName, IsAbsoluteUrl = routeAttribute.IsAbsoluteUrl, + IgnoreRoutePrefix = routeAttribute.IgnoreRoutePrefix, + IgnoreAreaUrl = routeAttribute.IgnoreAreaUrl, UseLowercaseRoute = routeAttribute.UseLowercaseRouteFlag, PreserveCaseForUrlParameters = routeAttribute.PreserveCaseForUrlParametersFlag, AppendTrailingSlash = routeAttribute.AppendTrailingSlashFlag diff --git a/src/AttributeRouting/Framework/RouteSpecification.cs b/src/AttributeRouting/Framework/RouteSpecification.cs index 3b8cab4..923ff0e 100644 --- a/src/AttributeRouting/Framework/RouteSpecification.cs +++ b/src/AttributeRouting/Framework/RouteSpecification.cs @@ -40,5 +40,9 @@ public class RouteSpecification public bool? PreserveCaseForUrlParameters { get; set; } public bool? AppendTrailingSlash { get; set; } + + public bool IgnoreRoutePrefix { get; set; } + + public bool IgnoreAreaUrl { get; set; } } } \ No newline at end of file diff --git a/src/AttributeRouting/IRouteAttribute.cs b/src/AttributeRouting/IRouteAttribute.cs index f3795ce..d35de64 100644 --- a/src/AttributeRouting/IRouteAttribute.cs +++ b/src/AttributeRouting/IRouteAttribute.cs @@ -109,5 +109,17 @@ public interface IRouteAttribute /// Gets the tri-state value for AppendTrailingSlash. ///
bool? AppendTrailingSlashFlag { get; } + + /// + /// If true, will ignore any route prefix specified via the + /// when building up the route URL. + /// + bool IgnoreRoutePrefix { get; set; } + + /// + /// If true, will ignore any area URL prefix specified via the + /// when building up the route URL. + /// + bool IgnoreAreaUrl { get; set; } } } \ No newline at end of file From 3c3512e0c92967a2489f3974cd063c315cdc705f Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sun, 9 Dec 2012 00:13:52 -0700 Subject: [PATCH 15/97] #155 - added ability to specify multiple route prefixes on a controller, which will result in all the routes for the actions being prefixed by each. --- .../Features/RoutePrefixes.feature | 12 ++ .../Features/RoutePrefixes.feature.cs | 27 +++ .../Http/HttpRoutePrefixesController.cs | 11 ++ .../Subjects/RoutePrefixesControllers.cs | 12 ++ .../DefaultHttpRouteConventionAttribute.cs | 4 +- .../RestfulRouteConventionAttribute.cs | 4 +- .../Framework/RouteReflector.cs | 186 +++++++++++++----- .../Framework/RouteSpecification.cs | 10 + .../RouteConventionAttributeBase.cs | 4 +- src/AttributeRouting/RoutePrefixAttribute.cs | 17 +- 10 files changed, 225 insertions(+), 62 deletions(-) diff --git a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature index 0bc4ffe..c7f904e 100644 --- a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature +++ b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature @@ -99,3 +99,15 @@ Scenario: Generating routes with the default ctor of the RoutePrefixAttribute Given I have registered the routes for the HttpDefaultRoutePrefixController When I fetch the routes for the HttpDefaultRoutePrefix controller's Get action Then the route url is "HttpDefaultRoutePrefix/Index" + +Scenario: Generating prefixed routes when specifying multiple route prefixes + # MVC + Given I have registered the routes for the MultipleRoutePrefixController + When I fetch the routes for the MultipleRoutePrefix controller's Index action + Then the 1st route url is "FirstPrefix/Index" + And the 3rd route url is "SecondPrefix/Index" + # Web API + Given I have registered the routes for the HttpMultipleRoutePrefixController + When I fetch the routes for the HttpMultipleRoutePrefix controller's Get action + Then the 1st route url is "HttpFirstPrefix/Index" + And the 2st route url is "HttpSecondPrefix/Index" diff --git a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs index 03fc662..8af73cb 100644 --- a/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs +++ b/src/AttributeRouting.Specs/Features/RoutePrefixes.feature.cs @@ -297,6 +297,33 @@ public virtual void GeneratingRoutesWithTheDefaultCtorOfTheRoutePrefixAttribute( testRunner.When("I fetch the routes for the HttpDefaultRoutePrefix controller\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 101 testRunner.Then("the route url is \"HttpDefaultRoutePrefix/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Generating prefixed routes when specifying multiple route prefixes")] + public virtual void GeneratingPrefixedRoutesWhenSpecifyingMultipleRoutePrefixes() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating prefixed routes when specifying multiple route prefixes", ((string[])(null))); +#line 103 +this.ScenarioSetup(scenarioInfo); +#line 105 + testRunner.Given("I have registered the routes for the MultipleRoutePrefixController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 106 + testRunner.When("I fetch the routes for the MultipleRoutePrefix controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 107 + testRunner.Then("the 1st route url is \"FirstPrefix/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 108 + testRunner.And("the 3rd route url is \"SecondPrefix/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 110 + testRunner.Given("I have registered the routes for the HttpMultipleRoutePrefixController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 111 + testRunner.When("I fetch the routes for the HttpMultipleRoutePrefix controller\'s Get action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 112 + testRunner.Then("the 1st route url is \"HttpFirstPrefix/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 113 + testRunner.And("the 2st route url is \"HttpSecondPrefix/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Subjects/Http/HttpRoutePrefixesController.cs b/src/AttributeRouting.Specs/Subjects/Http/HttpRoutePrefixesController.cs index 597ace7..3f99a2e 100644 --- a/src/AttributeRouting.Specs/Subjects/Http/HttpRoutePrefixesController.cs +++ b/src/AttributeRouting.Specs/Subjects/Http/HttpRoutePrefixesController.cs @@ -46,4 +46,15 @@ public string Get() return ""; } } + + [RoutePrefix("HttpFirstPrefix", Precedence = 1)] + [RoutePrefix("HttpSecondPrefix")] + public class HttpMultipleRoutePrefixController : ApiController + { + [GET("Index")] + public string Get() + { + return ""; + } + } } \ No newline at end of file diff --git a/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs b/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs index 0297b62..92c673a 100644 --- a/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs @@ -75,4 +75,16 @@ public ActionResult Index() return Content(""); } } + + [RoutePrefix("FirstPrefix", Precedence = 1)] + [RoutePrefix("SecondPrefix")] + public class MultipleRoutePrefixController : Controller + { + [GET("Index")] + [GET("This/Is/Absolute", IsAbsoluteUrl = true)] + public ActionResult Index() + { + return Content(""); + } + } } \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http/DefaultHttpRouteConventionAttribute.cs b/src/AttributeRouting.Web.Http/DefaultHttpRouteConventionAttribute.cs index adf29f5..7e04104 100644 --- a/src/AttributeRouting.Web.Http/DefaultHttpRouteConventionAttribute.cs +++ b/src/AttributeRouting.Web.Http/DefaultHttpRouteConventionAttribute.cs @@ -67,9 +67,9 @@ public override IEnumerable GetRouteAttributes(MethodInfo actio } } - public override RoutePrefixAttribute GetDefaultRoutePrefix(Type controllerType) + public override IEnumerable GetDefaultRoutePrefixes(Type controllerType) { - return new RoutePrefixAttribute(controllerType.GetControllerName()); + yield return new RoutePrefixAttribute(controllerType.GetControllerName()); } private IRouteAttribute BuildRouteAttribute(HttpRouteConventionInfo convention) diff --git a/src/AttributeRouting.Web.Mvc/RestfulRouteConventionAttribute.cs b/src/AttributeRouting.Web.Mvc/RestfulRouteConventionAttribute.cs index f0699c7..4ef35b1 100644 --- a/src/AttributeRouting.Web.Mvc/RestfulRouteConventionAttribute.cs +++ b/src/AttributeRouting.Web.Mvc/RestfulRouteConventionAttribute.cs @@ -33,9 +33,9 @@ public override IEnumerable GetRouteAttributes(MethodInfo actio yield return BuildRouteAttribute(convention); } - public override RoutePrefixAttribute GetDefaultRoutePrefix(Type controllerType) + public override IEnumerable GetDefaultRoutePrefixes(Type controllerType) { - return new RoutePrefixAttribute(controllerType.GetControllerName()); + yield return new RoutePrefixAttribute(controllerType.GetControllerName()); } private IRouteAttribute BuildRouteAttribute(RestfulRouteConventionInfo convention) diff --git a/src/AttributeRouting/Framework/RouteReflector.cs b/src/AttributeRouting/Framework/RouteReflector.cs index fa88a67..ae3ddd3 100644 --- a/src/AttributeRouting/Framework/RouteReflector.cs +++ b/src/AttributeRouting/Framework/RouteReflector.cs @@ -50,62 +50,99 @@ private IEnumerable BuildRouteSpecifications(IEnumerable(); - // TODO: Reorganize this rat's nest! :) - return (from controllerType in controllerTypes - let controllerIndex = controllerCount++ - let isAsyncController = controllerType.IsAsyncController() - let convention = controllerType.GetCustomAttribute(false) - let routeAreaAttribute = controllerType.GetCustomAttribute(true) - ?? convention.SafeGet(x => x.GetDefaultRouteArea(controllerType)) - let subdomain = GetAreaSubdomain(routeAreaAttribute) - let areaName = GetAreaName(routeAreaAttribute, controllerType) - let areaUrl = GetAreaUrl(routeAreaAttribute, subdomain, controllerType) - from actionMethod in controllerType.GetActionMethods(inheritActionsFromBaseController) - // NOTE: The oldConventionalRoutePrefix var is to support obsolete method. - // Once that method is removed, remove oldConventionalRoutePrefix from consideration, - // and move the let routePrefixAttribute op above the loop inside the actionMethed. - let oldConventionalRoutePrefix = convention.SafeGet(x => x.GetDefaultRoutePrefix(actionMethod)) - let routePrefixAttribute = controllerType.GetCustomAttribute(true) - ?? (oldConventionalRoutePrefix.HasValue() - ? new RoutePrefixAttribute(oldConventionalRoutePrefix) - : convention.SafeGet(x => x.GetDefaultRoutePrefix(controllerType))) - let routePrefixUrl = GetRoutePrefixUrl(routePrefixAttribute, controllerType) - from routeAttribute in GetRouteAttributes(actionMethod, convention) - let routeName = routeAttribute.RouteName - let actionName = GetActionName(actionMethod, isAsyncController) - /* controlling precedence: - * site precedence of route > - * controller index > - * controller precedence of route > - * action precedence of route - */ - let sitePrecedence = GetSortableOrder(routeAttribute.SitePrecedence) - let controllerPrecedence = GetSortableOrder(routeAttribute.ControllerPrecedence) - let actionPrecedence = GetSortableOrder(routeAttribute.ActionPrecedence) - orderby sitePrecedence, controllerIndex, controllerPrecedence, actionPrecedence - select new RouteSpecification + // For each controller type: + foreach (var controllerType in controllerTypes) + { + var controllerIndex = controllerCount++; + var convention = controllerType.GetCustomAttribute(false); + var routeAreaAttribute = GetRouteAreaAttribute(controllerType, convention); + + // For each action method on the controller: + var actionMethods = controllerType.GetActionMethods(inheritActionsFromBaseController); + foreach (var actionMethod in actionMethods) + { + var routePrefixAttributes = GetRoutePrefixAttributes(controllerType, convention, actionMethod).ToList(); + + // For each route attribute on the action method: + var routeAttributes = GetRouteAttributes(actionMethod, convention); + foreach (var routeAttribute in routeAttributes) { - AreaName = areaName, - AreaUrl = areaUrl, - AreaUrlTranslationKey = routeAreaAttribute.SafeGet(a => a.TranslationKey), - Subdomain = subdomain, - RoutePrefixUrl = routePrefixUrl, - RoutePrefixUrlTranslationKey = routePrefixAttribute.SafeGet(a => a.TranslationKey), - ControllerType = controllerType, - ControllerName = controllerType.GetControllerName(), - ActionName = actionName, - RouteUrl = routeAttribute.RouteUrl ?? actionName, - RouteUrlTranslationKey = routeAttribute.TranslationKey, - HttpMethods = routeAttribute.HttpMethods, - RouteName = routeName, - IsAbsoluteUrl = routeAttribute.IsAbsoluteUrl, - IgnoreRoutePrefix = routeAttribute.IgnoreRoutePrefix, - IgnoreAreaUrl = routeAttribute.IgnoreAreaUrl, - UseLowercaseRoute = routeAttribute.UseLowercaseRouteFlag, - PreserveCaseForUrlParameters = routeAttribute.PreserveCaseForUrlParametersFlag, - AppendTrailingSlash = routeAttribute.AppendTrailingSlashFlag - }).ToList(); + if (routePrefixAttributes.Any()) + { + foreach (var routePrefixAttribute in routePrefixAttributes) + { + routeSpecs.Add(BuildRouteSpecification(controllerIndex, controllerType, actionMethod, + routeAreaAttribute, routePrefixAttribute, + routeAttribute)); + + // If ignoring the prefix, do not add the route again for other prefixes! + if (routeAttribute.IgnoreRoutePrefix) + break; + } + } + else + { + routeSpecs.Add(BuildRouteSpecification(controllerIndex, controllerType, actionMethod, + routeAreaAttribute, null, routeAttribute)); + } + } + } + } + + // Return specs ordered by route precedence: + return routeSpecs + .OrderBy(x => x.SitePrecedence) + .ThenBy(x => x.ControllerIndex) + .ThenBy(x => x.PrefixPrecedence) + .ThenBy(x => x.ControllerPrecedence) + .ThenBy(x => x.ActionPrecedence); + } + + /// + /// Builds a from component parts. + /// + /// The index of this controller inthe registered controller types. + /// The controller type. + /// The action method info. + /// An applicable for the controller. + /// An applicable for the controller. + /// The for the action. + /// The route specification. + private RouteSpecification BuildRouteSpecification(int controllerIndex, Type controllerType, MethodInfo actionMethod, RouteAreaAttribute routeAreaAttribute, RoutePrefixAttribute routePrefixAttribute, IRouteAttribute routeAttribute) + { + var isAsyncController = controllerType.IsAsyncController(); + var subdomain = GetAreaSubdomain(routeAreaAttribute); + var actionName = GetActionName(actionMethod, isAsyncController); + + return new RouteSpecification + { + ControllerIndex = controllerIndex, + SitePrecedence = GetSortableOrder(routeAttribute.SitePrecedence), + ControllerPrecedence = GetSortableOrder(routeAttribute.ControllerPrecedence), + ActionPrecedence = GetSortableOrder(routeAttribute.ActionPrecedence), + AreaName = GetAreaName(routeAreaAttribute, controllerType), + AreaUrl = GetAreaUrl(routeAreaAttribute, subdomain, controllerType), + AreaUrlTranslationKey = routeAreaAttribute.SafeGet(a => a.TranslationKey), + Subdomain = subdomain, + ControllerType = controllerType, + ControllerName = controllerType.GetControllerName(), + ActionName = actionName, + RouteUrl = routeAttribute.RouteUrl ?? actionName, + RouteUrlTranslationKey = routeAttribute.TranslationKey, + HttpMethods = routeAttribute.HttpMethods, + RouteName = routeAttribute.RouteName, + IsAbsoluteUrl = routeAttribute.IsAbsoluteUrl, + IgnoreRoutePrefix = routeAttribute.IgnoreRoutePrefix, + IgnoreAreaUrl = routeAttribute.IgnoreAreaUrl, + UseLowercaseRoute = routeAttribute.UseLowercaseRouteFlag, + PreserveCaseForUrlParameters = routeAttribute.PreserveCaseForUrlParametersFlag, + AppendTrailingSlash = routeAttribute.AppendTrailingSlashFlag, + RoutePrefixUrl = GetRoutePrefixUrl(routePrefixAttribute, controllerType), + RoutePrefixUrlTranslationKey = routePrefixAttribute.SafeGet(a => a.TranslationKey), + PrefixPrecedence = GetSortableOrder(routePrefixAttribute.SafeGet(a => a.Precedence, int.MaxValue)) + }; } /// @@ -144,6 +181,18 @@ private static IEnumerable GetRouteAttributes(MethodInfo action return attributes; } + /// + /// Get a to use for the controller. + /// + /// The controller type. + /// An applicable for the controller. + /// An applicable . + private static RouteAreaAttribute GetRouteAreaAttribute(Type controllerType, RouteConventionAttributeBase convention) + { + return controllerType.GetCustomAttribute(true) + ?? convention.SafeGet(x => x.GetDefaultRouteArea(controllerType)); + } + /// /// Gets the area name. /// @@ -203,6 +252,35 @@ private string GetAreaSubdomain(RouteAreaAttribute routeAreaAttribute) return subdomainOverride ?? routeAreaAttribute.Subdomain; } + /// + /// Get a to use for the controller. + /// + /// The controller type. + /// An applicable for the controller. + /// The action method info. + /// A list of applicable . + private static IEnumerable GetRoutePrefixAttributes(Type controllerType, RouteConventionAttributeBase convention, MethodInfo actionMethod) + { + // If there are any explicit route prefixes defined, use them. + var routePrefixAttributes = controllerType.GetCustomAttributes(true).ToList(); + + // Otherwise apply conventional prefixes. + if (!routePrefixAttributes.Any() && convention != null) + { + var oldConventionalRoutePrefix = convention.GetDefaultRoutePrefix(actionMethod); + if (oldConventionalRoutePrefix.HasValue()) + { + routePrefixAttributes.Add(new RoutePrefixAttribute(oldConventionalRoutePrefix)); + } + else + { + routePrefixAttributes.AddRange(convention.GetDefaultRoutePrefixes(controllerType)); + } + } + + return routePrefixAttributes.OrderBy(a => GetSortableOrder(a.Precedence)); + } + /// /// Gets the route prefix for the routes in the controller. /// diff --git a/src/AttributeRouting/Framework/RouteSpecification.cs b/src/AttributeRouting/Framework/RouteSpecification.cs index 923ff0e..597e064 100644 --- a/src/AttributeRouting/Framework/RouteSpecification.cs +++ b/src/AttributeRouting/Framework/RouteSpecification.cs @@ -44,5 +44,15 @@ public class RouteSpecification public bool IgnoreRoutePrefix { get; set; } public bool IgnoreAreaUrl { get; set; } + + public int ControllerIndex { get; set; } + + public long SitePrecedence { get; set; } + + public long PrefixPrecedence { get; set; } + + public long ControllerPrecedence { get; set; } + + public long ActionPrecedence { get; set; } } } \ No newline at end of file diff --git a/src/AttributeRouting/RouteConventionAttributeBase.cs b/src/AttributeRouting/RouteConventionAttributeBase.cs index 219b4bf..cfb1134 100644 --- a/src/AttributeRouting/RouteConventionAttributeBase.cs +++ b/src/AttributeRouting/RouteConventionAttributeBase.cs @@ -34,9 +34,9 @@ public virtual string GetDefaultRoutePrefix(MethodInfo actionMethod) /// /// The controller type. /// A . - public virtual RoutePrefixAttribute GetDefaultRoutePrefix(Type controllerType) + public virtual IEnumerable GetDefaultRoutePrefixes(Type controllerType) { - return null; + yield break; } /// diff --git a/src/AttributeRouting/RoutePrefixAttribute.cs b/src/AttributeRouting/RoutePrefixAttribute.cs index 78bb182..0ab53d7 100644 --- a/src/AttributeRouting/RoutePrefixAttribute.cs +++ b/src/AttributeRouting/RoutePrefixAttribute.cs @@ -5,14 +5,17 @@ namespace AttributeRouting /// /// Defines a prefix shared by all the routes defined in this controller. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] public class RoutePrefixAttribute : Attribute { /// /// Defines a prefix shared by all the routes defined in this controller. /// The url prefix will be the name of the controller without the "Controller" suffix. /// - public RoutePrefixAttribute() { } + public RoutePrefixAttribute() + { + Precedence = int.MaxValue; + } /// /// Defines a prefix shared by all the routes defined in this controller. @@ -31,6 +34,16 @@ public RoutePrefixAttribute(string url) /// public string Url { get; private set; } + /// + /// The order of the routes using the given prefix + /// among all the routes generated with prefixes for this controller. + /// + /// + /// Positive integers (including zero) denote top routes: 1 is first, 2 is second, etc.... + /// Negative integers denote bottom routes: -1 is last, -2 is second to last, etc.... + /// + public int Precedence { get; set; } + /// /// Key used by translation provider to lookup the translation for the . /// From 69aae0498059fc049a894ce8cfe338a7c9afe2dd Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Tue, 11 Dec 2012 14:18:08 -0700 Subject: [PATCH 16/97] #156 - added action method to route data tokens. --- .../Features/StandardUsage.feature | 2 + .../Features/StandardUsage.feature.cs | 28 +++++---- .../Steps/StandardUsageSteps.cs | 9 +++ .../Subjects/RoutePrefixesControllers.cs | 2 +- .../Framework/RouteBuilder.cs | 3 +- .../Framework/RouteReflector.cs | 29 ++++----- .../Framework/RouteSpecification.cs | 59 ++++++++++--------- .../Logging/AttributeRouteInfo.cs | 2 +- 8 files changed, 77 insertions(+), 57 deletions(-) diff --git a/src/AttributeRouting.Specs/Features/StandardUsage.feature b/src/AttributeRouting.Specs/Features/StandardUsage.feature index b986a10..8a73b75 100644 --- a/src/AttributeRouting.Specs/Features/StandardUsage.feature +++ b/src/AttributeRouting.Specs/Features/StandardUsage.feature @@ -8,6 +8,7 @@ Scenario Outline: Generating routes for an MVC controller And the default for "controller" is "StandardUsage" And the default for "action" is "" And the namespace is "AttributeRouting.Specs.Subjects" + And the route has a data token for "actionMethod" Examples: | method | action | url | @@ -34,6 +35,7 @@ Scenario Outline: Generating routes for an API controller And the default for "controller" is "HttpStandardUsage" And the default for "action" is "" And the namespace is "AttributeRouting.Specs.Subjects.Http" + And the route has a data token for "actionMethod" Examples: | method | action | url | diff --git a/src/AttributeRouting.Specs/Features/StandardUsage.feature.cs b/src/AttributeRouting.Specs/Features/StandardUsage.feature.cs index 2721111..c604650 100644 --- a/src/AttributeRouting.Specs/Features/StandardUsage.feature.cs +++ b/src/AttributeRouting.Specs/Features/StandardUsage.feature.cs @@ -98,6 +98,8 @@ public virtual void GeneratingRoutesForAnMVCController(string method, string act testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 10 testRunner.And("the namespace is \"AttributeRouting.Specs.Subjects\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 11 + testRunner.And("the route has a data token for \"actionMethod\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -118,22 +120,24 @@ public virtual void GeneratingRoutesForAnMVCController(string method, string act public virtual void GeneratingRoutesForAnAPIController(string method, string action, string url, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes for an API controller", exampleTags); -#line 29 -this.ScenarioSetup(scenarioInfo); #line 30 - testRunner.Given("I have registered the routes for the HttpStandardUsageController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +this.ScenarioSetup(scenarioInfo); #line 31 - testRunner.When(string.Format("I fetch the routes for the HttpStandardUsageController\'s {0} action", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); + testRunner.Given("I have registered the routes for the HttpStandardUsageController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 32 - testRunner.Then(string.Format("the route is constrained to {0} requests", method), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); + testRunner.When(string.Format("I fetch the routes for the HttpStandardUsageController\'s {0} action", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 33 - testRunner.And(string.Format("the route url is \"{0}\"", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); + testRunner.Then(string.Format("the route is constrained to {0} requests", method), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 34 - testRunner.And("the default for \"controller\" is \"HttpStandardUsage\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); + testRunner.And(string.Format("the route url is \"{0}\"", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 35 - testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); + testRunner.And("the default for \"controller\" is \"HttpStandardUsage\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line 36 + testRunner.And(string.Format("the default for \"action\" is \"{0}\"", action), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 37 testRunner.And("the namespace is \"AttributeRouting.Specs.Subjects.Http\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line 38 + testRunner.And("the route has a data token for \"actionMethod\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -143,13 +147,13 @@ public virtual void GeneratingRoutesForAnAPIController(string method, string act public virtual void RespondingToOPTIONSRequestsInAnAPIController() { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Responding to OPTIONS requests in an API controller", ((string[])(null))); -#line 52 +#line 54 this.ScenarioSetup(scenarioInfo); -#line 53 +#line 55 testRunner.Given("I have registered the routes for the HttpStandardUsageController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 54 +#line 56 testRunner.When("an OPTIONS request for \"api\" is made", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 55 +#line 57 testRunner.Then("the Get action is matched", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); diff --git a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs index 8b4a54c..1b2e6f6 100644 --- a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs +++ b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs @@ -50,6 +50,15 @@ public void ThenTheNamespaceIs(string ns) Assert.That(route.DataTokens["namespaces"], Is.EqualTo(new[] { ns })); } + [Then(@"the route has a data token for ""(.*?)""")] + public void ThenTheRouteHasADataTokeFor(string key) + { + var route = ScenarioContext.Current.GetFetchedRoutes().FirstOrDefault(); + + Assert.That(route, Is.Not.Null); + Assert.That(route.DataTokens[key], Is.Not.Null); + } + [Then(@"the route is constrained to (.*?) requests")] public void ThenTheRouteIsConstrainedToRequests(string method) { diff --git a/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs b/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs index 92c673a..1bdf3c5 100644 --- a/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/RoutePrefixesControllers.cs @@ -80,7 +80,7 @@ public ActionResult Index() [RoutePrefix("SecondPrefix")] public class MultipleRoutePrefixController : Controller { - [GET("Index")] + [GET("Index", ActionPrecedence = 1)] [GET("This/Is/Absolute", IsAbsoluteUrl = true)] public ActionResult Index() { diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 35fd328..7910e2e 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -346,7 +346,8 @@ private IDictionary CreateRouteDataTokens(RouteSpecification rou { var dataTokens = new Dictionary { - { "namespaces", new[] { routeSpec.ControllerType.Namespace } } + { "namespaces", new[] { routeSpec.ControllerType.Namespace } }, + { "actionMethod", routeSpec.ActionMethod } }; if (routeSpec.AreaName.HasValue()) diff --git a/src/AttributeRouting/Framework/RouteReflector.cs b/src/AttributeRouting/Framework/RouteReflector.cs index ae3ddd3..b7168ea 100644 --- a/src/AttributeRouting/Framework/RouteReflector.cs +++ b/src/AttributeRouting/Framework/RouteReflector.cs @@ -118,30 +118,31 @@ private RouteSpecification BuildRouteSpecification(int controllerIndex, Type con return new RouteSpecification { - ControllerIndex = controllerIndex, - SitePrecedence = GetSortableOrder(routeAttribute.SitePrecedence), - ControllerPrecedence = GetSortableOrder(routeAttribute.ControllerPrecedence), + ActionMethod = actionMethod, + ActionName = actionName, ActionPrecedence = GetSortableOrder(routeAttribute.ActionPrecedence), + AppendTrailingSlash = routeAttribute.AppendTrailingSlashFlag, AreaName = GetAreaName(routeAreaAttribute, controllerType), AreaUrl = GetAreaUrl(routeAreaAttribute, subdomain, controllerType), AreaUrlTranslationKey = routeAreaAttribute.SafeGet(a => a.TranslationKey), - Subdomain = subdomain, - ControllerType = controllerType, + ControllerIndex = controllerIndex, ControllerName = controllerType.GetControllerName(), - ActionName = actionName, - RouteUrl = routeAttribute.RouteUrl ?? actionName, - RouteUrlTranslationKey = routeAttribute.TranslationKey, + ControllerPrecedence = GetSortableOrder(routeAttribute.ControllerPrecedence), + ControllerType = controllerType, HttpMethods = routeAttribute.HttpMethods, - RouteName = routeAttribute.RouteName, - IsAbsoluteUrl = routeAttribute.IsAbsoluteUrl, - IgnoreRoutePrefix = routeAttribute.IgnoreRoutePrefix, IgnoreAreaUrl = routeAttribute.IgnoreAreaUrl, - UseLowercaseRoute = routeAttribute.UseLowercaseRouteFlag, + IgnoreRoutePrefix = routeAttribute.IgnoreRoutePrefix, + IsAbsoluteUrl = routeAttribute.IsAbsoluteUrl, + PrefixPrecedence = GetSortableOrder(routePrefixAttribute.SafeGet(a => a.Precedence, int.MaxValue)), PreserveCaseForUrlParameters = routeAttribute.PreserveCaseForUrlParametersFlag, - AppendTrailingSlash = routeAttribute.AppendTrailingSlashFlag, + RouteName = routeAttribute.RouteName, RoutePrefixUrl = GetRoutePrefixUrl(routePrefixAttribute, controllerType), RoutePrefixUrlTranslationKey = routePrefixAttribute.SafeGet(a => a.TranslationKey), - PrefixPrecedence = GetSortableOrder(routePrefixAttribute.SafeGet(a => a.Precedence, int.MaxValue)) + RouteUrl = routeAttribute.RouteUrl ?? actionName, + RouteUrlTranslationKey = routeAttribute.TranslationKey, + SitePrecedence = GetSortableOrder(routeAttribute.SitePrecedence), + Subdomain = subdomain, + UseLowercaseRoute = routeAttribute.UseLowercaseRouteFlag, }; } diff --git a/src/AttributeRouting/Framework/RouteSpecification.cs b/src/AttributeRouting/Framework/RouteSpecification.cs index 597e064..ed454c6 100644 --- a/src/AttributeRouting/Framework/RouteSpecification.cs +++ b/src/AttributeRouting/Framework/RouteSpecification.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace AttributeRouting.Framework { @@ -7,52 +8,54 @@ namespace AttributeRouting.Framework /// public class RouteSpecification { + public MethodInfo ActionMethod { get; set; } + + public string ActionName { get; set; } + + public long ActionPrecedence { get; set; } + + public bool? AppendTrailingSlash { get; set; } + public string AreaName { get; set; } public string AreaUrl { get; set; } public string AreaUrlTranslationKey { get; set; } + + public int ControllerIndex { get; set; } + + public string ControllerName { get; set; } + + public long ControllerPrecedence { get; set; } + + public Type ControllerType { get; set; } + public string[] HttpMethods { get; set; } + + public bool IgnoreAreaUrl { get; set; } + + public bool IgnoreRoutePrefix { get; set; } + + public bool IsAbsoluteUrl { get; set; } + + public long PrefixPrecedence { get; set; } + + public bool? PreserveCaseForUrlParameters { get; set; } + public string RoutePrefixUrl { get; set; } public string RoutePrefixUrlTranslationKey { get; set; } - public Type ControllerType { get; set; } - - public string ControllerName { get; set; } - - public string ActionName { get; set; } - public string RouteUrl { get; set; } public string RouteUrlTranslationKey { get; set; } - public string[] HttpMethods { get; set; } - public string RouteName { get; set; } - public bool IsAbsoluteUrl { get; set; } - + public long SitePrecedence { get; set; } + public string Subdomain { get; set; } public bool? UseLowercaseRoute { get; set; } - - public bool? PreserveCaseForUrlParameters { get; set; } - - public bool? AppendTrailingSlash { get; set; } - - public bool IgnoreRoutePrefix { get; set; } - - public bool IgnoreAreaUrl { get; set; } - - public int ControllerIndex { get; set; } - - public long SitePrecedence { get; set; } - - public long PrefixPrecedence { get; set; } - - public long ControllerPrecedence { get; set; } - - public long ActionPrecedence { get; set; } } } \ No newline at end of file diff --git a/src/AttributeRouting/Logging/AttributeRouteInfo.cs b/src/AttributeRouting/Logging/AttributeRouteInfo.cs index 2a1e0b4..c565d1c 100644 --- a/src/AttributeRouting/Logging/AttributeRouteInfo.cs +++ b/src/AttributeRouting/Logging/AttributeRouteInfo.cs @@ -115,7 +115,7 @@ public static AttributeRouteInfo GetRouteInfo(string url, { item.DataTokens.Add(token.Key, ((string[]) token.Value).Aggregate((n1, n2) => n1 + ", " + n2)); } - else + else if (!token.Key.ValueEquals("actionMethod")) { item.DataTokens.Add(token.Key, token.Value.ToString()); } From 461172b002d5935923256a6c5b5e1592cb0b6325 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Tue, 11 Dec 2012 15:18:39 -0700 Subject: [PATCH 17/97] #146 - AR's custom http method constraint was interfering with IHostBufferPolicySelector, because it was checking for the incoming http method too early in the pipeline. --- .../Steps/StandardUsageSteps.cs | 16 +++++---- .../Areas/Api/Controllers/UploadController.cs | 18 ++++++++++ .../AttributeRouting.Tests.Web.csproj | 2 ++ .../CustomWebHostBufferPolicySelector.cs | 23 ++++++++++++ src/AttributeRouting.Tests.Web/Global.asax.cs | 4 +++ .../AttributeRouting.Web.Http.SelfHost.csproj | 1 - .../RestfulHttpMethodConstraint.cs | 23 ------------ .../Factories/RouteConstraintFactory.cs | 7 ---- .../Factories/RouteConstraintFactory.cs | 5 --- .../Factories/RouteConstraintFactory.cs | 5 --- .../RouteAttribute.cs | 3 ++ .../AttributeRouting.Web.csproj | 1 - .../RestfulHttpMethodConstraint.cs | 35 ------------------- .../Helpers/HttpRequestBaseExtensions.cs | 9 ----- src/AttributeRouting/AttributeRouting.csproj | 1 - .../IRestfulHttpMethodConstraint.cs | 22 ------------ .../Factories/IRouteConstraintFactory.cs | 5 --- .../Framework/RouteBuilder.cs | 20 +++++------ .../Logging/AttributeRouteInfo.cs | 10 +++--- 19 files changed, 74 insertions(+), 136 deletions(-) create mode 100644 src/AttributeRouting.Tests.Web/Areas/Api/Controllers/UploadController.cs create mode 100644 src/AttributeRouting.Tests.Web/CustomWebHostBufferPolicySelector.cs delete mode 100644 src/AttributeRouting.Web.Http.SelfHost/Constraints/RestfulHttpMethodConstraint.cs delete mode 100644 src/AttributeRouting.Web/Constraints/RestfulHttpMethodConstraint.cs delete mode 100644 src/AttributeRouting/Constraints/IRestfulHttpMethodConstraint.cs diff --git a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs index 1b2e6f6..b61bb84 100644 --- a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs +++ b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using AttributeRouting.Framework; using AttributeRouting.Web.Constraints; using NUnit.Framework; using TechTalk.SpecFlow; @@ -66,16 +68,16 @@ public void ThenTheRouteIsConstrainedToRequests(string method) Assert.That(route, Is.Not.Null); - var constraint = route.Constraints["httpMethod"] as RestfulHttpMethodConstraint; + var allowedMethods = route.DataTokens["httpMethods"] as IEnumerable; if (method.HasValue()) { - Assert.That(constraint, Is.Not.Null); - Assert.That(constraint.AllowedMethods.Any(m => m.Equals(method, StringComparison.OrdinalIgnoreCase)), Is.True); + Assert.That(allowedMethods, Is.Not.Null); + Assert.That(allowedMethods.Any(m => m.Equals(method, StringComparison.OrdinalIgnoreCase)), Is.True); } else { - Assert.That(constraint, Is.Null); + Assert.That(allowedMethods, Is.Null); } } @@ -86,10 +88,10 @@ public void ThenTheRouteForIsConstrainedToRequests(string action, string method) Assert.That(route, Is.Not.Null); - var constraint = route.Constraints["httpMethod"] as RestfulHttpMethodConstraint; + var allowedMethods = route.DataTokens["httpMethods"] as IEnumerable; - Assert.That(constraint, Is.Not.Null); - Assert.That(constraint.AllowedMethods.Any(m => m.Equals(method, StringComparison.OrdinalIgnoreCase)), Is.True); + Assert.That(allowedMethods, Is.Not.Null); + Assert.That(allowedMethods.Any(m => m.Equals(method, StringComparison.OrdinalIgnoreCase)), Is.True); } } } diff --git a/src/AttributeRouting.Tests.Web/Areas/Api/Controllers/UploadController.cs b/src/AttributeRouting.Tests.Web/Areas/Api/Controllers/UploadController.cs new file mode 100644 index 0000000..d3e6253 --- /dev/null +++ b/src/AttributeRouting.Tests.Web/Areas/Api/Controllers/UploadController.cs @@ -0,0 +1,18 @@ +using AttributeRouting.Web.Http; + +namespace AttributeRouting.Tests.Web.Areas.Api.Controllers +{ + /// + /// To test issue 146 + /// + [RoutePrefix("upload")] + public class UploadController : BaseApiController + { + // POST /api/plain + [POST("")] + public string Post() + { + return "foo"; + } + } +} \ No newline at end of file diff --git a/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj b/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj index 277dbbf..b36614c 100644 --- a/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj +++ b/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj @@ -118,6 +118,7 @@ + @@ -135,6 +136,7 @@ + Global.asax diff --git a/src/AttributeRouting.Tests.Web/CustomWebHostBufferPolicySelector.cs b/src/AttributeRouting.Tests.Web/CustomWebHostBufferPolicySelector.cs new file mode 100644 index 0000000..9d15c22 --- /dev/null +++ b/src/AttributeRouting.Tests.Web/CustomWebHostBufferPolicySelector.cs @@ -0,0 +1,23 @@ +using System.Web.Http.WebHost; + +namespace AttributeRouting.Tests.Web +{ + public class CustomWebHostBufferPolicySelector : WebHostBufferPolicySelector + { + public override bool UseBufferedInputStream(object hostContext) + { + var contextBase = hostContext as System.Web.HttpContextBase; + + if (contextBase != null + && contextBase.Request.ContentType != null + && contextBase.Request.ContentType.Contains("multipart")) + { + // we are enabling streamed mode here + return false; + } + + // let the default behavior(buffered mode) to handle the scenario + return base.UseBufferedInputStream(hostContext); + } + } +} \ No newline at end of file diff --git a/src/AttributeRouting.Tests.Web/Global.asax.cs b/src/AttributeRouting.Tests.Web/Global.asax.cs index 763b31a..7ca3a41 100644 --- a/src/AttributeRouting.Tests.Web/Global.asax.cs +++ b/src/AttributeRouting.Tests.Web/Global.asax.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Web.Http; +using System.Web.Http.Hosting; using System.Web.Mvc; using System.Web.Routing; using AttributeRouting.Framework.Localization; @@ -88,6 +89,9 @@ public static void RegisterRoutes(RouteCollection routes) "{*path}", new { controller = "home", action = "filenotfound" }, new[] { typeof(HomeController).Namespace }); + + // Testing issue 146 + GlobalConfiguration.Configuration.Services.Replace(typeof(IHostBufferPolicySelector), new CustomWebHostBufferPolicySelector()); } } } \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj index 9e55fab..ea1fdee 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj +++ b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj @@ -72,7 +72,6 @@ - diff --git a/src/AttributeRouting.Web.Http.SelfHost/Constraints/RestfulHttpMethodConstraint.cs b/src/AttributeRouting.Web.Http.SelfHost/Constraints/RestfulHttpMethodConstraint.cs deleted file mode 100644 index c103c4c..0000000 --- a/src/AttributeRouting.Web.Http.SelfHost/Constraints/RestfulHttpMethodConstraint.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Net.Http; -using System.Web.Http.Routing; -using AttributeRouting.Constraints; - -namespace AttributeRouting.Web.Http.SelfHost.Constraints -{ - /// - /// Constrains a route by the specified allowed HTTP methods. - /// - public class RestfulHttpMethodConstraint : HttpMethodConstraint, IRestfulHttpMethodConstraint - { - public RestfulHttpMethodConstraint(params HttpMethod[] allowedMethods) - : base(allowedMethods) {} - - ICollection IRestfulHttpMethodConstraint.AllowedMethods - { - get { return new ReadOnlyCollection(AllowedMethods.Select(method => method.Method).ToList()); } - } - } -} \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs index 2133c36..7523417 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Net.Http; using System.Text.RegularExpressions; using System.Web.Http.Routing; using AttributeRouting.Constraints; @@ -26,12 +25,6 @@ public RegexRouteConstraintBase CreateRegexRouteConstraint(string pattern, Regex return new RegexRouteConstraint(pattern, options); } - public IRestfulHttpMethodConstraint CreateRestfulHttpMethodConstraint(string[] httpMethods) - { - var allowedMethods = httpMethods.Select(m => new HttpMethod(m)).ToArray(); - return new RestfulHttpMethodConstraint(allowedMethods); - } - public object CreateInlineRouteConstraint(string name, params object[] parameters) { var inlineRouteConstraints = _configuration.InlineRouteConstraints; diff --git a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs index 50f5f98..9681fa9 100644 --- a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs @@ -25,11 +25,6 @@ public RegexRouteConstraintBase CreateRegexRouteConstraint(string pattern, Regex return new RegexRouteConstraint(pattern, options); } - public IRestfulHttpMethodConstraint CreateRestfulHttpMethodConstraint(string[] httpMethods) - { - return new RestfulHttpMethodConstraint(httpMethods); - } - public object CreateInlineRouteConstraint(string name, params object[] parameters) { var inlineRouteConstraints = _configuration.InlineRouteConstraints; diff --git a/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs index 72b09f1..3d09d9d 100644 --- a/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs @@ -25,11 +25,6 @@ public RegexRouteConstraintBase CreateRegexRouteConstraint(string pattern, Regex return new RegexRouteConstraint(pattern, options); } - public IRestfulHttpMethodConstraint CreateRestfulHttpMethodConstraint(string[] httpMethods) - { - return new RestfulHttpMethodConstraint(httpMethods); - } - public object CreateInlineRouteConstraint(string name, params object[] parameters) { var inlineRouteConstraints = _configuration.InlineRouteConstraints; diff --git a/src/AttributeRouting.Web.Mvc/RouteAttribute.cs b/src/AttributeRouting.Web.Mvc/RouteAttribute.cs index a5b0523..edcece5 100644 --- a/src/AttributeRouting.Web.Mvc/RouteAttribute.cs +++ b/src/AttributeRouting.Web.Mvc/RouteAttribute.cs @@ -122,10 +122,13 @@ public bool AppendTrailingSlash public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) { + if (controllerContext == null) throw new ArgumentNullException("controllerContext"); + if (!HttpMethods.Any()) return true; var method = controllerContext.HttpContext.Request.GetHttpMethodOverride(); + return HttpMethods.Any(m => m.ValueEquals(method)); } } diff --git a/src/AttributeRouting.Web/AttributeRouting.Web.csproj b/src/AttributeRouting.Web/AttributeRouting.Web.csproj index ebc0c46..cc59709 100644 --- a/src/AttributeRouting.Web/AttributeRouting.Web.csproj +++ b/src/AttributeRouting.Web/AttributeRouting.Web.csproj @@ -65,7 +65,6 @@ - diff --git a/src/AttributeRouting.Web/Constraints/RestfulHttpMethodConstraint.cs b/src/AttributeRouting.Web/Constraints/RestfulHttpMethodConstraint.cs deleted file mode 100644 index c5bdb1a..0000000 --- a/src/AttributeRouting.Web/Constraints/RestfulHttpMethodConstraint.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Linq; -using System.Web; -using System.Web.Routing; -using AttributeRouting.Constraints; -using AttributeRouting.Helpers; -using AttributeRouting.Web.Helpers; - -namespace AttributeRouting.Web.Constraints -{ - /// - /// Constrains a route by the specified allowed HTTP methods. - /// - /// - /// AttributeRouting uses its own implementation of the Match method - /// to handle X-HTTP-Method-Override values in the request headers, form, and query collections. - /// - public class RestfulHttpMethodConstraint : HttpMethodConstraint, IRestfulHttpMethodConstraint - { - /// - /// Constrain a route by the specified allowed HTTP methods. - /// - public RestfulHttpMethodConstraint(params string[] allowedMethods) - : base(allowedMethods) { } - - protected override bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) - { - if (routeDirection == RouteDirection.UrlGeneration) - return true; - - var httpMethod = httpContext.Request.GetHttpMethod(); - - return AllowedMethods.Any(m => m.ValueEquals(httpMethod)); - } - } -} \ No newline at end of file diff --git a/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs b/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs index 6e0f599..5cad7ee 100644 --- a/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs +++ b/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs @@ -1,7 +1,6 @@ using System.Collections.Specialized; using System.Reflection; using System.Web; -using AttributeRouting.Helpers; namespace AttributeRouting.Web.Helpers { @@ -46,13 +45,5 @@ private static string GetUnvalidatedCollectionValue(this HttpRequestBase request return null; } } - - public static string GetHttpMethod(this HttpRequestBase request) - { - return request.SafeGet(r => r.Headers["X-HTTP-Method-Override"]) ?? - request.SafeGet(r => GetFormValue(r, "X-HTTP-Method-Override")) ?? - request.SafeGet(r => GetQueryStringValue(r, "X-HTTP-Method-Override")) ?? - request.SafeGet(r => r.HttpMethod, "GET"); - } } } diff --git a/src/AttributeRouting/AttributeRouting.csproj b/src/AttributeRouting/AttributeRouting.csproj index f7d136e..0eda025 100644 --- a/src/AttributeRouting/AttributeRouting.csproj +++ b/src/AttributeRouting/AttributeRouting.csproj @@ -64,7 +64,6 @@ - diff --git a/src/AttributeRouting/Constraints/IRestfulHttpMethodConstraint.cs b/src/AttributeRouting/Constraints/IRestfulHttpMethodConstraint.cs deleted file mode 100644 index 70ab210..0000000 --- a/src/AttributeRouting/Constraints/IRestfulHttpMethodConstraint.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using AttributeRouting.Framework.Factories; - -namespace AttributeRouting.Constraints -{ - /// - /// Abstraction used by - /// when applying restful route constraints. - /// - /// - /// Due to - /// System.Web.Routing.HttpMethodConstraint (used in web-hosted scenarios) and - /// System.Web.Http.Routing.HttpMethodConstraint (used in self-hosted scenarios). - /// - public interface IRestfulHttpMethodConstraint - { - /// - /// The allowed HTTP methods. - /// - ICollection AllowedMethods { get; } - } -} \ No newline at end of file diff --git a/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs b/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs index 708c0c4..db34075 100644 --- a/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs +++ b/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs @@ -19,11 +19,6 @@ public interface IRouteConstraintFactory ///
RegexRouteConstraintBase CreateRegexRouteConstraint(string pattern, RegexOptions options = RegexOptions.None); - /// - /// Creates a new RestfulHttpMethodConstraint - /// - IRestfulHttpMethodConstraint CreateRestfulHttpMethodConstraint(string[] httpMethods); - /// /// Creates an inline constraint of a specific type with the given parameters. /// diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 7910e2e..41b54a4 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -196,10 +196,6 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro { var constraints = new Dictionary(); - // Default constraints - if (routeSpec.HttpMethods.Any()) - constraints.Add("httpMethod", _routeConstraintFactory.CreateRestfulHttpMethodConstraint(routeSpec.HttpMethods)); - // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); @@ -212,7 +208,6 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro var queryStringParameters = urlParameters.Except(pathOnlyUrlParameters).ToList(); // Inline constraints - var constraintFactory = _configuration.RouteConstraintFactory; foreach (var parameter in urlParameters) { // Keep track of whether this param is optional or in the querystring, @@ -255,13 +250,13 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro ? new[] {constraintParamsRaw} : constraintParamsRaw.SplitAndTrim(","); - constraint = constraintFactory.CreateInlineRouteConstraint(constraintName, constraintParams); + constraint = _routeConstraintFactory.CreateInlineRouteConstraint(constraintName, constraintParams); } else { // Constraint of the form "id:int" constraintName = definition; - constraint = constraintFactory.CreateInlineRouteConstraint(constraintName); + constraint = _routeConstraintFactory.CreateInlineRouteConstraint(constraintName); } if (constraint == null) @@ -277,7 +272,7 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro // 1. If more than one constraint, wrap in a compound constraint. if (inlineConstraints.Count > 1) { - finalConstraint = constraintFactory.CreateCompoundRouteConstraint(inlineConstraints.ToArray()); + finalConstraint = _routeConstraintFactory.CreateCompoundRouteConstraint(inlineConstraints.ToArray()); } else { @@ -287,13 +282,13 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro // 2. If the constraint is in the querystring, wrap in a query string constraint. if (parameterIsInQueryString) { - finalConstraint = constraintFactory.CreateQueryStringRouteConstraint(finalConstraint); + finalConstraint = _routeConstraintFactory.CreateQueryStringRouteConstraint(finalConstraint); } // 3. If the constraint is optional, wrap in an optional constraint. if (parameterIsOptional) { - finalConstraint = constraintFactory.CreateOptionalRouteConstraint(finalConstraint); + finalConstraint = _routeConstraintFactory.CreateOptionalRouteConstraint(finalConstraint); } constraints.Add(parameterName, finalConstraint); @@ -350,6 +345,11 @@ private IDictionary CreateRouteDataTokens(RouteSpecification rou { "actionMethod", routeSpec.ActionMethod } }; + if (routeSpec.HttpMethods.Any()) + { + dataTokens.Add("httpMethods", routeSpec.HttpMethods); + } + if (routeSpec.AreaName.HasValue()) { dataTokens.Add("area", routeSpec.AreaName); diff --git a/src/AttributeRouting/Logging/AttributeRouteInfo.cs b/src/AttributeRouting/Logging/AttributeRouteInfo.cs index c565d1c..08373fe 100644 --- a/src/AttributeRouting/Logging/AttributeRouteInfo.cs +++ b/src/AttributeRouting/Logging/AttributeRouteInfo.cs @@ -51,11 +51,7 @@ public static AttributeRouteInfo GetRouteInfo(string url, if (constraint.Value == null) continue; - if (constraint.Value is IRestfulHttpMethodConstraint) - { - item.HttpMethods = String.Join(", ", ((IRestfulHttpMethodConstraint)constraint.Value).AllowedMethods); - } - else if (constraint.Value is RegexRouteConstraintBase) + if (constraint.Value is RegexRouteConstraintBase) { item.Constraints.Add(constraint.Key, ((RegexRouteConstraintBase)constraint.Value).Pattern); } @@ -115,6 +111,10 @@ public static AttributeRouteInfo GetRouteInfo(string url, { item.DataTokens.Add(token.Key, ((string[]) token.Value).Aggregate((n1, n2) => n1 + ", " + n2)); } + else if (token.Key.ValueEquals("httpMethods")) + { + item.HttpMethods = ((string[]) token.Value).Aggregate((n1, n2) => n1 + ", " + n2); + } else if (!token.Key.ValueEquals("actionMethod")) { item.DataTokens.Add(token.Key, token.Value.ToString()); From 89ac50486e9493877e44c077315276a01eed73d4 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Tue, 11 Dec 2012 15:55:18 -0700 Subject: [PATCH 18/97] #124 - supporting IRouteHandler in web-host scenario by plugging into the RouteTable.MapHttpRoute method if the route handler factory is supplied. --- .../HttpRouteCollectionExtensions.cs | 21 +++++++++++++++++-- .../HttpWebAttributeRoutingConfiguration.cs | 3 +-- .../Framework/HttpAttributeRoute.cs | 2 +- .../HttpAttributeRoutingConfigurationBase.cs | 5 +++++ 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs b/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs index 65f5eb2..3cf1308 100644 --- a/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs +++ b/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Reflection; using System.Web.Http; +using System.Web.Routing; using AttributeRouting.Framework; using AttributeRouting.Web.Http.Framework; @@ -51,9 +52,25 @@ public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, HttpW private static void MapHttpAttributeRoutesInternal(this HttpRouteCollection routes, HttpWebAttributeRoutingConfiguration configuration) { - var generatedRoutes = new RouteBuilder(configuration).BuildAllRoutes(); + var generatedRoutes = new RouteBuilder(configuration).BuildAllRoutes().Cast().ToList(); - generatedRoutes.ToList().ForEach(r => routes.Add(r.RouteName, (HttpAttributeRoute)r)); + // If providing a custom IRouteHandler via config, add the routes to the RouteCollection. + // Have to do this because the HttpRoutes do not support the functionality. + var routeHandler = configuration.RouteHandlerFactory(); + if (routeHandler != null) + { + var mvcRoutes = RouteTable.Routes; + generatedRoutes.ForEach(r => + { + var mvcRoute = mvcRoutes.MapHttpRoute(r.RouteName, r.Url, r.Defaults, r.Constraints, r.Handler); + mvcRoute.RouteHandler = routeHandler; + }); + } + else + { + // Otherwise, add them to the HttpRouteCollection. + generatedRoutes.ForEach(r => routes.Add(r.RouteName, r)); + } } } } \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http.WebHost/HttpWebAttributeRoutingConfiguration.cs b/src/AttributeRouting.Web.Http.WebHost/HttpWebAttributeRoutingConfiguration.cs index ef836f4..b9e767c 100644 --- a/src/AttributeRouting.Web.Http.WebHost/HttpWebAttributeRoutingConfiguration.cs +++ b/src/AttributeRouting.Web.Http.WebHost/HttpWebAttributeRoutingConfiguration.cs @@ -1,5 +1,4 @@ using System; -using System.Web.Http.WebHost; using System.Web.Routing; using AttributeRouting.Web.Http.WebHost.Framework.Factories; @@ -13,7 +12,7 @@ public HttpWebAttributeRoutingConfiguration() ParameterFactory = new RouteParameterFactory(); RouteConstraintFactory = new RouteConstraintFactory(this); - RouteHandlerFactory = () => HttpControllerRouteHandler.Instance; + RouteHandlerFactory = () => null; RegisterDefaultInlineRouteConstraints(typeof(Web.Constraints.RegexRouteConstraint).Assembly); } diff --git a/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs b/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs index 994dc8f..51e5f61 100644 --- a/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs +++ b/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs @@ -23,7 +23,7 @@ public HttpAttributeRoute(string url, HttpRouteValueDictionary constraints, HttpRouteValueDictionary dataTokens, HttpAttributeRoutingConfigurationBase configuration) - : base(url, defaults, constraints, dataTokens) + : base(url, defaults, constraints, dataTokens, configuration.MessageHandler) { _configuration = configuration; } diff --git a/src/AttributeRouting.Web.Http/HttpAttributeRoutingConfigurationBase.cs b/src/AttributeRouting.Web.Http/HttpAttributeRoutingConfigurationBase.cs index 85604a7..c1f06bc 100644 --- a/src/AttributeRouting.Web.Http/HttpAttributeRoutingConfigurationBase.cs +++ b/src/AttributeRouting.Web.Http/HttpAttributeRoutingConfigurationBase.cs @@ -13,6 +13,11 @@ protected HttpAttributeRoutingConfigurationBase() CurrentUICultureResolver = (ctx, data) => Thread.CurrentThread.CurrentUICulture.Name; } + /// + /// The message handler that will be the recipient of the request. + /// + public HttpMessageHandler MessageHandler { get; set; } + /// /// The controller type applicable to this context. /// From 1aa492d43e57e5d218e2809ac1ebb0809d08994b Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Tue, 11 Dec 2012 19:23:10 -0700 Subject: [PATCH 19/97] fixed a couple of bugs introduced in new stuff for v3.4 --- .../Steps/StandardUsageSteps.cs | 34 +++++++++---------- .../AttributeRouting.Web.Http.SelfHost.csproj | 1 + .../RestfulHttpMethodConstraint.cs | 25 ++++++++++++++ .../Factories/RouteConstraintFactory.cs | 7 ++++ .../Factories/RouteConstraintFactory.cs | 5 +++ .../HttpRouteCollectionExtensions.cs | 1 + .../Factories/RouteConstraintFactory.cs | 5 +++ .../AttributeRouting.Web.csproj | 1 + .../RestfulHttpMethodConstraint.cs | 32 +++++++++++++++++ .../Helpers/HttpRequestBaseExtensions.cs | 9 +++++ src/AttributeRouting/AttributeRouting.csproj | 1 + .../IRestfulHttpMethodConstraint.cs | 22 ++++++++++++ .../Framework/AttributeRouteExtensions.cs | 5 +-- .../Factories/IRouteConstraintFactory.cs | 5 +++ .../Framework/RouteBuilder.cs | 4 +++ .../Logging/AttributeRouteInfo.cs | 6 ++-- 16 files changed, 141 insertions(+), 22 deletions(-) create mode 100644 src/AttributeRouting.Web.Http.SelfHost/Constraints/RestfulHttpMethodConstraint.cs create mode 100644 src/AttributeRouting.Web/Constraints/RestfulHttpMethodConstraint.cs create mode 100644 src/AttributeRouting/Constraints/IRestfulHttpMethodConstraint.cs diff --git a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs index b61bb84..8f0f9b3 100644 --- a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs +++ b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Web.Routing; +using AttributeRouting.Constraints; using AttributeRouting.Framework; using AttributeRouting.Web.Constraints; using NUnit.Framework; @@ -66,19 +68,7 @@ public void ThenTheRouteIsConstrainedToRequests(string method) { var route = ScenarioContext.Current.GetFetchedRoutes().FirstOrDefault(); - Assert.That(route, Is.Not.Null); - - var allowedMethods = route.DataTokens["httpMethods"] as IEnumerable; - - if (method.HasValue()) - { - Assert.That(allowedMethods, Is.Not.Null); - Assert.That(allowedMethods.Any(m => m.Equals(method, StringComparison.OrdinalIgnoreCase)), Is.True); - } - else - { - Assert.That(allowedMethods, Is.Null); - } + AssertThatRouteIsConstrainedToHttpMethod(route, method); } [Then(@"the route for (.*?) is constrained to (.*?) requests")] @@ -86,12 +76,22 @@ public void ThenTheRouteForIsConstrainedToRequests(string action, string method) { var route = ScenarioContext.Current.GetFetchedRoutes().FirstOrDefault(r => r.Defaults["action"].ToString() == action); - Assert.That(route, Is.Not.Null); + AssertThatRouteIsConstrainedToHttpMethod(route, method); + } - var allowedMethods = route.DataTokens["httpMethods"] as IEnumerable; + private void AssertThatRouteIsConstrainedToHttpMethod(Route route, string method) + { + var constraint = route.Constraints["httpMethod"] as IRestfulHttpMethodConstraint; - Assert.That(allowedMethods, Is.Not.Null); - Assert.That(allowedMethods.Any(m => m.Equals(method, StringComparison.OrdinalIgnoreCase)), Is.True); + if (method.HasValue()) + { + Assert.That(constraint, Is.Not.Null); + Assert.That(constraint.AllowedMethods.Any(m => m.Equals(method, StringComparison.OrdinalIgnoreCase)), Is.True); + } + else + { + Assert.That(constraint, Is.Null); + } } } } diff --git a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj index ea1fdee..4c584e0 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj +++ b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj @@ -68,6 +68,7 @@ SharedAssemblyInfo.cs + diff --git a/src/AttributeRouting.Web.Http.SelfHost/Constraints/RestfulHttpMethodConstraint.cs b/src/AttributeRouting.Web.Http.SelfHost/Constraints/RestfulHttpMethodConstraint.cs new file mode 100644 index 0000000..6d94f39 --- /dev/null +++ b/src/AttributeRouting.Web.Http.SelfHost/Constraints/RestfulHttpMethodConstraint.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Net.Http; +using System.Web.Http.Routing; +using AttributeRouting.Constraints; + +namespace AttributeRouting.Web.Http.SelfHost.Constraints +{ + /// + /// Constrains a route by HTTP method. + /// + public class RestfulHttpMethodConstraint : HttpMethodConstraint, IRestfulHttpMethodConstraint + { + public RestfulHttpMethodConstraint(params HttpMethod[] allowedMethods) + : base(allowedMethods) + { + } + + ICollection IRestfulHttpMethodConstraint.AllowedMethods + { + get { return new ReadOnlyCollection(AllowedMethods.Select(method => method.Method).ToList()); } + } + } +} \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs index 7523417..2133c36 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net.Http; using System.Text.RegularExpressions; using System.Web.Http.Routing; using AttributeRouting.Constraints; @@ -25,6 +26,12 @@ public RegexRouteConstraintBase CreateRegexRouteConstraint(string pattern, Regex return new RegexRouteConstraint(pattern, options); } + public IRestfulHttpMethodConstraint CreateRestfulHttpMethodConstraint(string[] httpMethods) + { + var allowedMethods = httpMethods.Select(m => new HttpMethod(m)).ToArray(); + return new RestfulHttpMethodConstraint(allowedMethods); + } + public object CreateInlineRouteConstraint(string name, params object[] parameters) { var inlineRouteConstraints = _configuration.InlineRouteConstraints; diff --git a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs index 9681fa9..50f5f98 100644 --- a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs @@ -25,6 +25,11 @@ public RegexRouteConstraintBase CreateRegexRouteConstraint(string pattern, Regex return new RegexRouteConstraint(pattern, options); } + public IRestfulHttpMethodConstraint CreateRestfulHttpMethodConstraint(string[] httpMethods) + { + return new RestfulHttpMethodConstraint(httpMethods); + } + public object CreateInlineRouteConstraint(string name, params object[] parameters) { var inlineRouteConstraints = _configuration.InlineRouteConstraints; diff --git a/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs b/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs index 3cf1308..b219eb4 100644 --- a/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs +++ b/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs @@ -63,6 +63,7 @@ private static void MapHttpAttributeRoutesInternal(this HttpRouteCollection rout generatedRoutes.ForEach(r => { var mvcRoute = mvcRoutes.MapHttpRoute(r.RouteName, r.Url, r.Defaults, r.Constraints, r.Handler); + mvcRoute.DataTokens = new RouteValueDictionary(r.DataTokens); mvcRoute.RouteHandler = routeHandler; }); } diff --git a/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs index 3d09d9d..72b09f1 100644 --- a/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs @@ -25,6 +25,11 @@ public RegexRouteConstraintBase CreateRegexRouteConstraint(string pattern, Regex return new RegexRouteConstraint(pattern, options); } + public IRestfulHttpMethodConstraint CreateRestfulHttpMethodConstraint(string[] httpMethods) + { + return new RestfulHttpMethodConstraint(httpMethods); + } + public object CreateInlineRouteConstraint(string name, params object[] parameters) { var inlineRouteConstraints = _configuration.InlineRouteConstraints; diff --git a/src/AttributeRouting.Web/AttributeRouting.Web.csproj b/src/AttributeRouting.Web/AttributeRouting.Web.csproj index cc59709..48c4ceb 100644 --- a/src/AttributeRouting.Web/AttributeRouting.Web.csproj +++ b/src/AttributeRouting.Web/AttributeRouting.Web.csproj @@ -64,6 +64,7 @@ + diff --git a/src/AttributeRouting.Web/Constraints/RestfulHttpMethodConstraint.cs b/src/AttributeRouting.Web/Constraints/RestfulHttpMethodConstraint.cs new file mode 100644 index 0000000..6a28ff2 --- /dev/null +++ b/src/AttributeRouting.Web/Constraints/RestfulHttpMethodConstraint.cs @@ -0,0 +1,32 @@ +using System.Linq; +using System.Web; +using System.Web.Routing; +using AttributeRouting.Constraints; +using AttributeRouting.Helpers; +using AttributeRouting.Web.Helpers; + +namespace AttributeRouting.Web.Constraints +{ + public class RestfulHttpMethodConstraint : HttpMethodConstraint, IRestfulHttpMethodConstraint + { + /// + /// Constrains a route by HTTP method. + /// + public RestfulHttpMethodConstraint(params string[] allowedMethods) + : base(allowedMethods) + { + } + + protected override bool Match(HttpContextBase httpContext, Route route, string parameterName, + RouteValueDictionary values, RouteDirection routeDirection) + { + + if (routeDirection == RouteDirection.UrlGeneration) + return true; + + var httpMethod = httpContext.Request.GetHttpMethod(); + + return AllowedMethods.Any(m => m.ValueEquals(httpMethod)); + } + } +} \ No newline at end of file diff --git a/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs b/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs index 5cad7ee..6e0f599 100644 --- a/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs +++ b/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs @@ -1,6 +1,7 @@ using System.Collections.Specialized; using System.Reflection; using System.Web; +using AttributeRouting.Helpers; namespace AttributeRouting.Web.Helpers { @@ -45,5 +46,13 @@ private static string GetUnvalidatedCollectionValue(this HttpRequestBase request return null; } } + + public static string GetHttpMethod(this HttpRequestBase request) + { + return request.SafeGet(r => r.Headers["X-HTTP-Method-Override"]) ?? + request.SafeGet(r => GetFormValue(r, "X-HTTP-Method-Override")) ?? + request.SafeGet(r => GetQueryStringValue(r, "X-HTTP-Method-Override")) ?? + request.SafeGet(r => r.HttpMethod, "GET"); + } } } diff --git a/src/AttributeRouting/AttributeRouting.csproj b/src/AttributeRouting/AttributeRouting.csproj index 0eda025..f7d136e 100644 --- a/src/AttributeRouting/AttributeRouting.csproj +++ b/src/AttributeRouting/AttributeRouting.csproj @@ -64,6 +64,7 @@ + diff --git a/src/AttributeRouting/Constraints/IRestfulHttpMethodConstraint.cs b/src/AttributeRouting/Constraints/IRestfulHttpMethodConstraint.cs new file mode 100644 index 0000000..db2a1d9 --- /dev/null +++ b/src/AttributeRouting/Constraints/IRestfulHttpMethodConstraint.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using AttributeRouting.Framework.Factories; + +namespace AttributeRouting.Constraints +{ + /// + /// Abstraction used by + /// when applying restful route constraints. + /// + /// + /// Due to + /// System.Web.Routing.HttpMethodConstraint (used in web-hosted scenarios) and + /// System.Web.Http.Routing.HttpMethodConstraint (used in self-hosted scenarios). + /// + public interface IRestfulHttpMethodConstraint + { + /// + /// The allowed HTTP methods. + /// + ICollection AllowedMethods { get; } + } +} \ No newline at end of file diff --git a/src/AttributeRouting/Framework/AttributeRouteExtensions.cs b/src/AttributeRouting/Framework/AttributeRouteExtensions.cs index 7855fa5..0eb2780 100644 --- a/src/AttributeRouting/Framework/AttributeRouteExtensions.cs +++ b/src/AttributeRouting/Framework/AttributeRouteExtensions.cs @@ -134,9 +134,10 @@ public static TVirtualPathData GetVirtualPath(this IAttributeR ? ((IOptionalRouteConstraintWrapper)constraint).Constraint : constraint; - if (constraintToTest is IQueryStringRouteConstraintWrapper) - queryStringConstraints.Add(constraintKey, constraint); + if (!(constraintToTest is IQueryStringRouteConstraintWrapper)) + continue; + queryStringConstraints.Add(constraintKey, constraint); route.Constraints.Remove(constraintKey); } diff --git a/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs b/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs index db34075..708c0c4 100644 --- a/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs +++ b/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs @@ -19,6 +19,11 @@ public interface IRouteConstraintFactory ///
RegexRouteConstraintBase CreateRegexRouteConstraint(string pattern, RegexOptions options = RegexOptions.None); + /// + /// Creates a new RestfulHttpMethodConstraint + /// + IRestfulHttpMethodConstraint CreateRestfulHttpMethodConstraint(string[] httpMethods); + /// /// Creates an inline constraint of a specific type with the given parameters. /// diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 41b54a4..21b6ae3 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -196,6 +196,10 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro { var constraints = new Dictionary(); + // Default constraints + if (routeSpec.HttpMethods.Any()) + constraints.Add("httpMethod", _routeConstraintFactory.CreateRestfulHttpMethodConstraint(routeSpec.HttpMethods)); + // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); diff --git a/src/AttributeRouting/Logging/AttributeRouteInfo.cs b/src/AttributeRouting/Logging/AttributeRouteInfo.cs index 08373fe..7e3f9d4 100644 --- a/src/AttributeRouting/Logging/AttributeRouteInfo.cs +++ b/src/AttributeRouting/Logging/AttributeRouteInfo.cs @@ -48,7 +48,7 @@ public static AttributeRouteInfo GetRouteInfo(string url, { foreach (var constraint in constraints) { - if (constraint.Value == null) + if (constraint.Value == null || constraint.Value is IRestfulHttpMethodConstraint) continue; if (constraint.Value is RegexRouteConstraintBase) @@ -109,11 +109,11 @@ public static AttributeRouteInfo GetRouteInfo(string url, { if (token.Key.ValueEquals("namespaces")) { - item.DataTokens.Add(token.Key, ((string[]) token.Value).Aggregate((n1, n2) => n1 + ", " + n2)); + item.DataTokens.Add(token.Key, String.Join(", ", (string[])token.Value)); } else if (token.Key.ValueEquals("httpMethods")) { - item.HttpMethods = ((string[]) token.Value).Aggregate((n1, n2) => n1 + ", " + n2); + item.HttpMethods = String.Join(", ", (string[])token.Value); } else if (!token.Key.ValueEquals("actionMethod")) { From d4f81ba41364e867c8ba0f44cb12a6f3854beef4 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Tue, 11 Dec 2012 23:23:51 -0700 Subject: [PATCH 20/97] #146 - stopped checking for x-http-method-overrides when constraing inbound routes by http method in asp.net web api. --- .../Steps/StandardUsageSteps.cs | 2 +- .../AttributeRouting.Web.Http.SelfHost.csproj | 2 +- ...aint.cs => InboundHttpMethodConstraint.cs} | 12 ++++---- .../Factories/RouteConstraintFactory.cs | 4 +-- .../AttributeRouting.Web.Http.WebHost.csproj | 1 + .../InboundHttpMethodConstraint.cs | 26 +++++++++++++++++ .../Factories/RouteConstraintFactory.cs | 4 +-- .../AttributeRouting.Web.Mvc.csproj | 1 + .../InboundHttpMethodConstraint.cs} | 14 +++++----- .../Factories/RouteConstraintFactory.cs | 4 +-- .../RouteAttribute.cs | 5 ++-- .../AttributeRouting.Web.csproj | 1 - .../Helpers/HttpRequestBaseExtensions.cs | 28 ++++++++++++++++--- src/AttributeRouting/AttributeRouting.csproj | 2 +- ...int.cs => IInboundHttpMethodConstraint.cs} | 4 +-- .../Factories/IRouteConstraintFactory.cs | 6 ++-- .../Framework/RouteBuilder.cs | 2 +- .../Logging/AttributeRouteInfo.cs | 2 +- 18 files changed, 84 insertions(+), 36 deletions(-) rename src/AttributeRouting.Web.Http.SelfHost/Constraints/{RestfulHttpMethodConstraint.cs => InboundHttpMethodConstraint.cs} (58%) create mode 100644 src/AttributeRouting.Web.Http.WebHost/Constraints/InboundHttpMethodConstraint.cs rename src/{AttributeRouting.Web/Constraints/RestfulHttpMethodConstraint.cs => AttributeRouting.Web.Mvc/Constraints/InboundHttpMethodConstraint.cs} (60%) rename src/AttributeRouting/Constraints/{IRestfulHttpMethodConstraint.cs => IInboundHttpMethodConstraint.cs} (85%) diff --git a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs index 8f0f9b3..920383c 100644 --- a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs +++ b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs @@ -81,7 +81,7 @@ public void ThenTheRouteForIsConstrainedToRequests(string action, string method) private void AssertThatRouteIsConstrainedToHttpMethod(Route route, string method) { - var constraint = route.Constraints["httpMethod"] as IRestfulHttpMethodConstraint; + var constraint = route.Constraints["httpMethod"] as IInboundHttpMethodConstraint; if (method.HasValue()) { diff --git a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj index 4c584e0..495741c 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj +++ b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj @@ -68,7 +68,7 @@ SharedAssemblyInfo.cs - + diff --git a/src/AttributeRouting.Web.Http.SelfHost/Constraints/RestfulHttpMethodConstraint.cs b/src/AttributeRouting.Web.Http.SelfHost/Constraints/InboundHttpMethodConstraint.cs similarity index 58% rename from src/AttributeRouting.Web.Http.SelfHost/Constraints/RestfulHttpMethodConstraint.cs rename to src/AttributeRouting.Web.Http.SelfHost/Constraints/InboundHttpMethodConstraint.cs index 6d94f39..e4148ca 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Constraints/RestfulHttpMethodConstraint.cs +++ b/src/AttributeRouting.Web.Http.SelfHost/Constraints/InboundHttpMethodConstraint.cs @@ -7,17 +7,17 @@ namespace AttributeRouting.Web.Http.SelfHost.Constraints { - /// - /// Constrains a route by HTTP method. - /// - public class RestfulHttpMethodConstraint : HttpMethodConstraint, IRestfulHttpMethodConstraint + public class InboundHttpMethodConstraint : HttpMethodConstraint, IInboundHttpMethodConstraint { - public RestfulHttpMethodConstraint(params HttpMethod[] allowedMethods) + /// + /// Constrains an inbound route by HTTP method. + /// + public InboundHttpMethodConstraint(params HttpMethod[] allowedMethods) : base(allowedMethods) { } - ICollection IRestfulHttpMethodConstraint.AllowedMethods + ICollection IInboundHttpMethodConstraint.AllowedMethods { get { return new ReadOnlyCollection(AllowedMethods.Select(method => method.Method).ToList()); } } diff --git a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs index 2133c36..f26e220 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs @@ -26,10 +26,10 @@ public RegexRouteConstraintBase CreateRegexRouteConstraint(string pattern, Regex return new RegexRouteConstraint(pattern, options); } - public IRestfulHttpMethodConstraint CreateRestfulHttpMethodConstraint(string[] httpMethods) + public IInboundHttpMethodConstraint CreateInboundHttpMethodConstraint(string[] httpMethods) { var allowedMethods = httpMethods.Select(m => new HttpMethod(m)).ToArray(); - return new RestfulHttpMethodConstraint(allowedMethods); + return new InboundHttpMethodConstraint(allowedMethods); } public object CreateInlineRouteConstraint(string name, params object[] parameters) diff --git a/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj b/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj index c169336..ec02dee 100644 --- a/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj +++ b/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj @@ -72,6 +72,7 @@ SharedAssemblyInfo.cs + diff --git a/src/AttributeRouting.Web.Http.WebHost/Constraints/InboundHttpMethodConstraint.cs b/src/AttributeRouting.Web.Http.WebHost/Constraints/InboundHttpMethodConstraint.cs new file mode 100644 index 0000000..e41288e --- /dev/null +++ b/src/AttributeRouting.Web.Http.WebHost/Constraints/InboundHttpMethodConstraint.cs @@ -0,0 +1,26 @@ +using System.Web; +using System.Web.Routing; +using AttributeRouting.Constraints; + +namespace AttributeRouting.Web.Http.WebHost.Constraints +{ + public class InboundHttpMethodConstraint : HttpMethodConstraint, IInboundHttpMethodConstraint + { + /// + /// Constrains an inbound route by HTTP method. + /// + public InboundHttpMethodConstraint(params string[] allowedMethods) + : base(allowedMethods) + { + } + + protected override bool Match(HttpContextBase httpContext, Route route, string parameterName, + RouteValueDictionary values, RouteDirection routeDirection) + { + if (routeDirection == RouteDirection.UrlGeneration) + return true; + + return base.Match(httpContext, route, parameterName, values, routeDirection); + } + } +} \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs index 50f5f98..a004ddd 100644 --- a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs @@ -25,9 +25,9 @@ public RegexRouteConstraintBase CreateRegexRouteConstraint(string pattern, Regex return new RegexRouteConstraint(pattern, options); } - public IRestfulHttpMethodConstraint CreateRestfulHttpMethodConstraint(string[] httpMethods) + public IInboundHttpMethodConstraint CreateInboundHttpMethodConstraint(string[] httpMethods) { - return new RestfulHttpMethodConstraint(httpMethods); + return new InboundHttpMethodConstraint(httpMethods); } public object CreateInlineRouteConstraint(string name, params object[] parameters) diff --git a/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj b/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj index 79a64f4..a67c256 100644 --- a/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj +++ b/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj @@ -54,6 +54,7 @@ SharedAssemblyInfo.cs + diff --git a/src/AttributeRouting.Web/Constraints/RestfulHttpMethodConstraint.cs b/src/AttributeRouting.Web.Mvc/Constraints/InboundHttpMethodConstraint.cs similarity index 60% rename from src/AttributeRouting.Web/Constraints/RestfulHttpMethodConstraint.cs rename to src/AttributeRouting.Web.Mvc/Constraints/InboundHttpMethodConstraint.cs index 6a28ff2..0b48fd3 100644 --- a/src/AttributeRouting.Web/Constraints/RestfulHttpMethodConstraint.cs +++ b/src/AttributeRouting.Web.Mvc/Constraints/InboundHttpMethodConstraint.cs @@ -1,18 +1,18 @@ using System.Linq; using System.Web; +using System.Web.Mvc; using System.Web.Routing; using AttributeRouting.Constraints; using AttributeRouting.Helpers; -using AttributeRouting.Web.Helpers; -namespace AttributeRouting.Web.Constraints +namespace AttributeRouting.Web.Mvc.Constraints { - public class RestfulHttpMethodConstraint : HttpMethodConstraint, IRestfulHttpMethodConstraint + public class InboundHttpMethodConstraint : HttpMethodConstraint, IInboundHttpMethodConstraint { /// - /// Constrains a route by HTTP method. + /// Constrains an inbound route by HTTP method. /// - public RestfulHttpMethodConstraint(params string[] allowedMethods) + public InboundHttpMethodConstraint(params string[] allowedMethods) : base(allowedMethods) { } @@ -20,11 +20,11 @@ public RestfulHttpMethodConstraint(params string[] allowedMethods) protected override bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { - if (routeDirection == RouteDirection.UrlGeneration) return true; - var httpMethod = httpContext.Request.GetHttpMethod(); + // Make sure to check for HTTP method overrides! + var httpMethod = httpContext.Request.GetHttpMethodOverride(); return AllowedMethods.Any(m => m.ValueEquals(httpMethod)); } diff --git a/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs index 72b09f1..ac2cff7 100644 --- a/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs @@ -25,9 +25,9 @@ public RegexRouteConstraintBase CreateRegexRouteConstraint(string pattern, Regex return new RegexRouteConstraint(pattern, options); } - public IRestfulHttpMethodConstraint CreateRestfulHttpMethodConstraint(string[] httpMethods) + public IInboundHttpMethodConstraint CreateInboundHttpMethodConstraint(string[] httpMethods) { - return new RestfulHttpMethodConstraint(httpMethods); + return new InboundHttpMethodConstraint(httpMethods); } public object CreateInlineRouteConstraint(string name, params object[] parameters) diff --git a/src/AttributeRouting.Web.Mvc/RouteAttribute.cs b/src/AttributeRouting.Web.Mvc/RouteAttribute.cs index edcece5..7f73314 100644 --- a/src/AttributeRouting.Web.Mvc/RouteAttribute.cs +++ b/src/AttributeRouting.Web.Mvc/RouteAttribute.cs @@ -124,12 +124,13 @@ public override bool IsValidForRequest(ControllerContext controllerContext, Meth { if (controllerContext == null) throw new ArgumentNullException("controllerContext"); + // If not constrained by a method, then accept always! if (!HttpMethods.Any()) return true; - var method = controllerContext.HttpContext.Request.GetHttpMethodOverride(); + var httpMethod = controllerContext.HttpContext.Request.GetHttpMethodOverride(); - return HttpMethods.Any(m => m.ValueEquals(method)); + return HttpMethods.Any(m => m.ValueEquals(httpMethod)); } } } \ No newline at end of file diff --git a/src/AttributeRouting.Web/AttributeRouting.Web.csproj b/src/AttributeRouting.Web/AttributeRouting.Web.csproj index 48c4ceb..cc59709 100644 --- a/src/AttributeRouting.Web/AttributeRouting.Web.csproj +++ b/src/AttributeRouting.Web/AttributeRouting.Web.csproj @@ -64,7 +64,6 @@ - diff --git a/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs b/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs index 6e0f599..58e5ed5 100644 --- a/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs +++ b/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs @@ -47,12 +47,32 @@ private static string GetUnvalidatedCollectionValue(this HttpRequestBase request } } + /// + /// The reason we have our own is to provide support for System.Web.Helpers.Validation.Unvalidated calls. + /// public static string GetHttpMethod(this HttpRequestBase request) { - return request.SafeGet(r => r.Headers["X-HTTP-Method-Override"]) ?? - request.SafeGet(r => GetFormValue(r, "X-HTTP-Method-Override")) ?? - request.SafeGet(r => GetQueryStringValue(r, "X-HTTP-Method-Override")) ?? - request.SafeGet(r => r.HttpMethod, "GET"); + var httpMethod = request.HttpMethod; + + // If not a post, method overrides don't apply. + if (!httpMethod.ValueEquals("POST")) + { + return httpMethod; + } + + // Get the method override and if it's not for a GET or POST, then apply it. + var methodOverride = request.SafeGet(r => r.Headers["X-HTTP-Method-Override"]) ?? + request.SafeGet(r => GetFormValue(r, "X-HTTP-Method-Override")) ?? + request.SafeGet(r => GetQueryStringValue(r, "X-HTTP-Method-Override")); + + if (methodOverride != null && + (!methodOverride.ValueEquals("GET") && !methodOverride.ValueEquals("POST"))) + { + return methodOverride; + } + + // Otherwise, just return the http method. + return httpMethod; } } } diff --git a/src/AttributeRouting/AttributeRouting.csproj b/src/AttributeRouting/AttributeRouting.csproj index f7d136e..0deaa0c 100644 --- a/src/AttributeRouting/AttributeRouting.csproj +++ b/src/AttributeRouting/AttributeRouting.csproj @@ -64,7 +64,7 @@ - + diff --git a/src/AttributeRouting/Constraints/IRestfulHttpMethodConstraint.cs b/src/AttributeRouting/Constraints/IInboundHttpMethodConstraint.cs similarity index 85% rename from src/AttributeRouting/Constraints/IRestfulHttpMethodConstraint.cs rename to src/AttributeRouting/Constraints/IInboundHttpMethodConstraint.cs index db2a1d9..be1b849 100644 --- a/src/AttributeRouting/Constraints/IRestfulHttpMethodConstraint.cs +++ b/src/AttributeRouting/Constraints/IInboundHttpMethodConstraint.cs @@ -5,14 +5,14 @@ namespace AttributeRouting.Constraints { /// /// Abstraction used by - /// when applying restful route constraints. + /// when applying inbound http method constraints. /// /// /// Due to /// System.Web.Routing.HttpMethodConstraint (used in web-hosted scenarios) and /// System.Web.Http.Routing.HttpMethodConstraint (used in self-hosted scenarios). /// - public interface IRestfulHttpMethodConstraint + public interface IInboundHttpMethodConstraint { /// /// The allowed HTTP methods. diff --git a/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs b/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs index 708c0c4..c2cbb15 100644 --- a/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs +++ b/src/AttributeRouting/Framework/Factories/IRouteConstraintFactory.cs @@ -15,14 +15,14 @@ namespace AttributeRouting.Framework.Factories public interface IRouteConstraintFactory { /// - /// Creates a new RegexRouteConstraint + /// Creates a new regex route constraint. /// RegexRouteConstraintBase CreateRegexRouteConstraint(string pattern, RegexOptions options = RegexOptions.None); /// - /// Creates a new RestfulHttpMethodConstraint + /// Creates a new inbound http method constraint. /// - IRestfulHttpMethodConstraint CreateRestfulHttpMethodConstraint(string[] httpMethods); + IInboundHttpMethodConstraint CreateInboundHttpMethodConstraint(string[] httpMethods); /// /// Creates an inline constraint of a specific type with the given parameters. diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 21b6ae3..06e9702 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -198,7 +198,7 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro // Default constraints if (routeSpec.HttpMethods.Any()) - constraints.Add("httpMethod", _routeConstraintFactory.CreateRestfulHttpMethodConstraint(routeSpec.HttpMethods)); + constraints.Add("inboundHttpMethod", _routeConstraintFactory.CreateInboundHttpMethodConstraint(routeSpec.HttpMethods)); // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); diff --git a/src/AttributeRouting/Logging/AttributeRouteInfo.cs b/src/AttributeRouting/Logging/AttributeRouteInfo.cs index 7e3f9d4..a6141b9 100644 --- a/src/AttributeRouting/Logging/AttributeRouteInfo.cs +++ b/src/AttributeRouting/Logging/AttributeRouteInfo.cs @@ -48,7 +48,7 @@ public static AttributeRouteInfo GetRouteInfo(string url, { foreach (var constraint in constraints) { - if (constraint.Value == null || constraint.Value is IRestfulHttpMethodConstraint) + if (constraint.Value == null || constraint.Value is IInboundHttpMethodConstraint) continue; if (constraint.Value is RegexRouteConstraintBase) From 3e0cfc6dd04c98bafe6e19ec49ec4fe73a99aabf Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Tue, 11 Dec 2012 23:25:58 -0700 Subject: [PATCH 21/97] fixed broken test execution. --- src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs index 920383c..c68d4d4 100644 --- a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs +++ b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs @@ -81,7 +81,7 @@ public void ThenTheRouteForIsConstrainedToRequests(string action, string method) private void AssertThatRouteIsConstrainedToHttpMethod(Route route, string method) { - var constraint = route.Constraints["httpMethod"] as IInboundHttpMethodConstraint; + var constraint = route.Constraints["inboundHttpMethod"] as IInboundHttpMethodConstraint; if (method.HasValue()) { From 955b0454e3762d254181b48aa37b96980594271e Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Tue, 11 Dec 2012 23:49:33 -0700 Subject: [PATCH 22/97] #162 - opened up {controller} and {action} url params as special params that will be replaced with controller and action values from the defaults collection for the route. --- .../Features/RouteDefaults.feature | 6 ++++ .../Features/RouteDefaults.feature.cs | 27 ++++++++++++-- .../Http/HttpRouteDefaultsController.cs | 6 ++++ .../Subjects/RouteDefaultsController.cs | 6 ++++ .../Framework/RouteBuilder.cs | 36 +++++++++++-------- 5 files changed, 63 insertions(+), 18 deletions(-) diff --git a/src/AttributeRouting.Specs/Features/RouteDefaults.feature b/src/AttributeRouting.Specs/Features/RouteDefaults.feature index 490d2c4..be34fe1 100644 --- a/src/AttributeRouting.Specs/Features/RouteDefaults.feature +++ b/src/AttributeRouting.Specs/Features/RouteDefaults.feature @@ -23,3 +23,9 @@ Scenario: Optional parameters specified with a url parameter token When I fetch the routes for the HttpRouteDefaults controller's Optionals action Then the route url is "Optionals/{p1}" And the parameter "p1" is optional + +Scenario: Using the controller and action url params + When I fetch the routes for the RouteDefaults controller's TheActionName action + Then the route url is "RouteDefaults/TheActionName" + When I fetch the routes for the HttpRouteDefaults controller's TheActionName action + Then the route url is "HttpRouteDefaults/TheActionName" diff --git a/src/AttributeRouting.Specs/Features/RouteDefaults.feature.cs b/src/AttributeRouting.Specs/Features/RouteDefaults.feature.cs index ce10e38..e7552b2 100644 --- a/src/AttributeRouting.Specs/Features/RouteDefaults.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteDefaults.feature.cs @@ -1,9 +1,9 @@ // ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (http://www.specflow.org/). -// SpecFlow Version:1.9.1.84 +// SpecFlow Version:1.9.0.77 // SpecFlow Generator Version:1.9.0.0 -// Runtime Version:4.0.30319.17929 +// Runtime Version:4.0.30319.18010 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -16,7 +16,7 @@ namespace AttributeRouting.Specs.Features using TechTalk.SpecFlow; - [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.1.84")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] [NUnit.Framework.DescriptionAttribute("Route Defaults")] @@ -128,6 +128,27 @@ public virtual void OptionalParametersSpecifiedWithAUrlParameterToken() testRunner.Then("the route url is \"Optionals/{p1}\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 25 testRunner.And("the parameter \"p1\" is optional", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Using the controller and action url params")] + public virtual void UsingTheControllerAndActionUrlParams() + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Using the controller and action url params", ((string[])(null))); +#line 27 +this.ScenarioSetup(scenarioInfo); +#line 3 +this.FeatureBackground(); +#line 28 + testRunner.When("I fetch the routes for the RouteDefaults controller\'s TheActionName action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 29 + testRunner.Then("the route url is \"RouteDefaults/TheActionName\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line 30 + testRunner.When("I fetch the routes for the HttpRouteDefaults controller\'s TheActionName action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 31 + testRunner.Then("the route url is \"HttpRouteDefaults/TheActionName\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Subjects/Http/HttpRouteDefaultsController.cs b/src/AttributeRouting.Specs/Subjects/Http/HttpRouteDefaultsController.cs index ba31baf..7223410 100644 --- a/src/AttributeRouting.Specs/Subjects/Http/HttpRouteDefaultsController.cs +++ b/src/AttributeRouting.Specs/Subjects/Http/HttpRouteDefaultsController.cs @@ -16,5 +16,11 @@ public string Optionals(string p1) { return ""; } + + [GET("{controller}/{action}")] + public string TheActionName() + { + return "is jack"; + } } } \ No newline at end of file diff --git a/src/AttributeRouting.Specs/Subjects/RouteDefaultsController.cs b/src/AttributeRouting.Specs/Subjects/RouteDefaultsController.cs index f1ef0a5..e79638e 100644 --- a/src/AttributeRouting.Specs/Subjects/RouteDefaultsController.cs +++ b/src/AttributeRouting.Specs/Subjects/RouteDefaultsController.cs @@ -16,5 +16,11 @@ public ActionResult Optionals(string p1) { return Content(""); } + + [GET("{controller}/{action}")] + public string TheActionName() + { + return "is joe"; + } } } \ No newline at end of file diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 06e9702..d8f62ca 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -53,10 +53,12 @@ where s.Subdomain.HasValue() private IEnumerable Build(RouteSpecification routeSpec) { - var routes = _routeFactory.CreateAttributeRoutes(CreateRouteUrl(routeSpec), - CreateRouteDefaults(routeSpec), - CreateRouteConstraints(routeSpec), - CreateRouteDataTokens(routeSpec)); + var defaults = CreateRouteDefaults(routeSpec); + var constraints = CreateRouteConstraints(routeSpec); + var dataTokens = CreateRouteDataTokens(routeSpec); + var url = CreateRouteUrl(defaults, routeSpec); + + var routes = _routeFactory.CreateAttributeRoutes(url, defaults, constraints, dataTokens); foreach (var route in routes) { @@ -98,15 +100,16 @@ private string CreateRouteName(RouteSpecification routeSpec) return _configuration.AutoGenerateRouteNames ? _configuration.RouteNameBuilder(routeSpec) : null; } - private string CreateRouteUrl(RouteSpecification routeSpec) + private string CreateRouteUrl(IDictionary defaults, RouteSpecification routeSpec) { return CreateRouteUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, + defaults, routeSpec); } - private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUrl, RouteSpecification routeSpec) + private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUrl, IDictionary defaults, RouteSpecification routeSpec) { var tokenizedUrl = BuildTokenizedUrl(routeUrl, routePrefix, areaUrl, routeSpec); var tokenizedPath = RemoveQueryString(tokenizedUrl); @@ -114,19 +117,21 @@ private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUr var urlParameterNames = GetUrlParameterContents(detokenizedPath).ToList(); - // {controller} and {action} tokens are not valid + var urlBuilder = new StringBuilder(detokenizedPath); + + // Replace {controller} URL param with default value. if (urlParameterNames.Any(n => n.ValueEquals("controller"))) - throw new AttributeRoutingException("{controller} is not a valid url parameter."); + urlBuilder.Replace("{controller}", (string)defaults["controller"]); + + // Replace {action} URL param with default value. if (urlParameterNames.Any(n => n.ValueEquals("action"))) - throw new AttributeRoutingException("{action} is not a valid url parameter."); + urlBuilder.Replace("{action}", (string)defaults["action"]); // Explicitly defined area routes are not valid if (urlParameterNames.Any(n => n.ValueEquals("area"))) throw new AttributeRoutingException( "{area} url parameters are not allowed. Specify the area name by using the RouteAreaAttribute."); - var urlBuilder = new StringBuilder(detokenizedPath); - // If we are lowercasing routes, then lowercase everything but the route params var lower = routeSpec.UseLowercaseRoute.GetValueOrDefault(_configuration.UseLowercaseRoutes); if (lower) @@ -431,15 +436,16 @@ private IEnumerable CreateRouteTranslations(RouteSpecification //********************************************* // Otherwise, build a translated route + var defaults = CreateRouteDefaults(routeSpec); + var constraints = CreateRouteConstraints(routeSpec); + var dataTokens = CreateRouteDataTokens(routeSpec); var routeUrl = CreateRouteUrl(translatedRouteUrl ?? routeSpec.RouteUrl, translatedRoutePrefix ?? routeSpec.RoutePrefixUrl, translatedAreaUrl ?? routeSpec.AreaUrl, + defaults, routeSpec); - var translatedRoutes = _routeFactory.CreateAttributeRoutes(routeUrl, - CreateRouteDefaults(routeSpec), - CreateRouteConstraints(routeSpec), - CreateRouteDataTokens(routeSpec)); + var translatedRoutes = _routeFactory.CreateAttributeRoutes(routeUrl, defaults, constraints, dataTokens); foreach (var translatedRoute in translatedRoutes) { From 2a24eaa88c2765f06c84d254a54260eb8cf83663 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Wed, 12 Dec 2012 00:03:04 -0700 Subject: [PATCH 23/97] Updated reame and assembly info for 3.4 --- README.textile | 11 +++++++++++ src/SharedAssemblyInfo.cs | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/README.textile b/README.textile index f091f4d..3ec8bd1 100644 --- a/README.textile +++ b/README.textile @@ -4,6 +4,17 @@ h2. Please refer to "attributerouting.net":http://attributerouting.net/ for docu h3. Changelog +_3.4_ + +* #124 - Now supporting custom IRouteHandler in web-host scenario. Also supporting custom HttpMessageHandler for Web API. +* #146 - BUG FIX: AR's custom http method constraint was interfering with IHostBufferPolicySelector, because it was checking for the incoming http method too early in the pipeline. +* #155 - Can now specify multiple route prefixes on a controller. Every action will get each prefix. You can control precedence of the prefixes by using the Precedence property. +* #156 - Added action method to route data tokens. +* #161 - BUG FIX: Url generation was bonking for routes that included a querystring route param constraint. +* #162 - Modified design of route convention attribute, adding facility for specifying an area attribute for a controller. Also opened up {controller} and {action} url params as special params that will be replaced with controller and action values from the defaults collection for the route. +* #164 - Added default ctors to RoutePrefixAttribute, RouteAreaAttribute, and the AttributeRouting.Web.Mvc route attributes. These default ctors will use a convention to get their URL components: RoutePrefix - will use the controller name; RouteArea - will use the last section of the controller's namespace; and the route attributes - will use the action name by default. +* #165 - Added two flags to the route attributes: IgnoreRoutePrefix and IgnoreAreaUrl. These flags control whether to prepend route prefixes or area urls to the generated route url. + _3.3_ * #153 - fixed bad parsing of regex route constraint patterns that use a comma. diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index 8a3e3f9..2a41a79 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyProduct("AttributeRouting")] [assembly: AssemblyCopyright("Copyright 2010-2012 Tim McCall")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.3")] -[assembly: AssemblyFileVersion("3.3")] -[assembly: AssemblyInformationalVersion("3.3")] +[assembly: AssemblyVersion("3.4")] +[assembly: AssemblyFileVersion("3.4")] +[assembly: AssemblyInformationalVersion("3.4")] [assembly: AssemblyConfiguration("Release")] From f253275408747131dfb8ddfe3344a273c930b0ec Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Wed, 19 Dec 2012 18:02:20 -0700 Subject: [PATCH 24/97] tersed the names of the config objects. --- .../Steps/SharedSteps.cs | 8 +- .../Tests/Subdomains/RouteReflectorTests.cs | 8 +- .../TrailingSlashes/AttributeRouteTests.cs | 2 +- .../AttributeRouting.Web.Http.SelfHost.csproj | 2 +- .../Factories/AttributeRouteFactory.cs | 4 +- .../Factories/RouteConstraintFactory.cs | 4 +- ...gConfiguration.cs => HttpConfiguration.cs} | 68 +-- .../HttpRouteCollectionExtensions.cs | 10 +- .../AttributeRouting.Web.Http.WebHost.csproj | 2 +- .../Factories/AttributeRouteFactory.cs | 4 +- .../Factories/RouteConstraintFactory.cs | 4 +- .../HttpRouteCollectionExtensions.cs | 10 +- ...nfiguration.cs => HttpWebConfiguration.cs} | 84 +-- .../AttributeRouting.Web.Http.csproj | 2 +- .../Framework/HttpAttributeRoute.cs | 4 +- ...rationBase.cs => HttpConfigurationBase.cs} | 4 +- .../AttributeRouting.Web.Mvc.csproj | 2 +- ...utingConfiguration.cs => Configuration.cs} | 160 ++--- .../Framework/AttributeRoute.cs | 4 +- .../Factories/AttributeRouteFactory.cs | 4 +- .../Factories/RouteConstraintFactory.cs | 4 +- .../RouteCollectionExtensions.cs | 10 +- src/AttributeRouting/AreaConfiguration.cs | 4 +- src/AttributeRouting/AttributeRouting.csproj | 2 +- ...figurationBase.cs => ConfigurationBase.cs} | 562 +++++++++--------- .../Framework/AttributeRouteExtensions.cs | 6 +- .../Framework/IAttributeRoute.cs | 6 +- .../Framework/RouteBuilder.cs | 6 +- .../Framework/RouteReflector.cs | 6 +- src/AttributeRouting/IRouteAttribute.cs | 6 +- 30 files changed, 501 insertions(+), 501 deletions(-) rename src/AttributeRouting.Web.Http.SelfHost/{HttpAttributeRoutingConfiguration.cs => HttpConfiguration.cs} (88%) rename src/AttributeRouting.Web.Http.WebHost/{HttpWebAttributeRoutingConfiguration.cs => HttpWebConfiguration.cs} (89%) rename src/AttributeRouting.Web.Http/{HttpAttributeRoutingConfigurationBase.cs => HttpConfigurationBase.cs} (92%) rename src/AttributeRouting.Web.Mvc/{AttributeRoutingConfiguration.cs => Configuration.cs} (93%) rename src/AttributeRouting/{AttributeRoutingConfigurationBase.cs => ConfigurationBase.cs} (96%) diff --git a/src/AttributeRouting.Specs/Steps/SharedSteps.cs b/src/AttributeRouting.Specs/Steps/SharedSteps.cs index beb65a1..318b996 100644 --- a/src/AttributeRouting.Specs/Steps/SharedSteps.cs +++ b/src/AttributeRouting.Specs/Steps/SharedSteps.cs @@ -21,8 +21,8 @@ namespace AttributeRouting.Specs.Steps [Binding] public class SharedSteps { - private AttributeRoutingConfiguration _configuration; - private HttpWebAttributeRoutingConfiguration _httpConfiguration; + private Configuration _configuration; + private HttpWebConfiguration _httpConfiguration; [Given(@"I generate the routes defined in the subject controllers")] public void GivenIGenerateTheRoutesDefinedInTheSubjectControllers() @@ -74,10 +74,10 @@ public void GivenIHaveRegisteredTheRoutesForThe(string controllerName) [Given(@"I have a new configuration object")] public void GivenIHaveANewConfigurationObject() { - _configuration = new AttributeRoutingConfiguration(); + _configuration = new Configuration(); _configuration.InlineRouteConstraints.Add("color", typeof(EnumRouteConstraint)); - _httpConfiguration = new HttpWebAttributeRoutingConfiguration(); + _httpConfiguration = new HttpWebConfiguration(); _httpConfiguration.InlineRouteConstraints.Add("color", typeof(EnumRouteConstraint)); } diff --git a/src/AttributeRouting.Specs/Tests/Subdomains/RouteReflectorTests.cs b/src/AttributeRouting.Specs/Tests/Subdomains/RouteReflectorTests.cs index d26ee00..ee11de3 100644 --- a/src/AttributeRouting.Specs/Tests/Subdomains/RouteReflectorTests.cs +++ b/src/AttributeRouting.Specs/Tests/Subdomains/RouteReflectorTests.cs @@ -12,7 +12,7 @@ public class RouteReflectorTests [Test] public void Returns_null_for_area_url_when_subdomain_is_specified_and_area_url_is_not_specified() { - var configuration = new AttributeRoutingConfiguration(); + var configuration = new Configuration(); configuration.AddRoutesFromController(); var reflector = new RouteReflector(configuration); @@ -26,7 +26,7 @@ public void Returns_null_for_area_url_when_subdomain_is_specified_and_area_url_i [Test] public void Returns_specified_url_for_area_url_when_both_subdomain_is_specified_and_area_url_is_specified() { - var configuration = new AttributeRoutingConfiguration(); + var configuration = new Configuration(); configuration.AddRoutesFromController(); var reflector = new RouteReflector(configuration); @@ -40,7 +40,7 @@ public void Returns_specified_url_for_area_url_when_both_subdomain_is_specified_ [Test] public void Returns_subdomain_specified_for_area_via_configuration_object() { - var configuration = new AttributeRoutingConfiguration(); + var configuration = new Configuration(); configuration.AddRoutesFromController(); configuration.MapArea("Users").ToSubdomain("override"); @@ -57,7 +57,7 @@ public void Returns_subdomain_specified_for_area_via_configuration_object() [Test] public void Returns_null_area_url_when_controller_configured_with_subdomain_only_via_configuration_object() { - var configuration = new AttributeRoutingConfiguration(); + var configuration = new Configuration(); configuration.AddRoutesFromController(); configuration.MapArea("NoSubdomain").ToSubdomain("subdomain"); diff --git a/src/AttributeRouting.Specs/Tests/TrailingSlashes/AttributeRouteTests.cs b/src/AttributeRouting.Specs/Tests/TrailingSlashes/AttributeRouteTests.cs index 71910a0..4e4576f 100644 --- a/src/AttributeRouting.Specs/Tests/TrailingSlashes/AttributeRouteTests.cs +++ b/src/AttributeRouting.Specs/Tests/TrailingSlashes/AttributeRouteTests.cs @@ -115,7 +115,7 @@ public void It_does_not_append_trailing_slash_to_root_urls_when_configured_for_t private Route BuildAttributeRoute(string url, bool useLowercaseRoutes, bool appendTrailingSlash) { - var configuration = new AttributeRoutingConfiguration + var configuration = new Configuration { UseLowercaseRoutes = useLowercaseRoutes, AppendTrailingSlash = appendTrailingSlash, diff --git a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj index 495741c..f404f8a 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj +++ b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj @@ -69,7 +69,7 @@ - + diff --git a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/AttributeRouteFactory.cs b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/AttributeRouteFactory.cs index f3c5fd5..dc11f59 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/AttributeRouteFactory.cs +++ b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/AttributeRouteFactory.cs @@ -8,9 +8,9 @@ namespace AttributeRouting.Web.Http.SelfHost.Framework.Factories { internal class AttributeRouteFactory : IAttributeRouteFactory { - private readonly HttpAttributeRoutingConfiguration _configuration; + private readonly HttpConfiguration _configuration; - public AttributeRouteFactory(HttpAttributeRoutingConfiguration configuration) + public AttributeRouteFactory(HttpConfiguration configuration) { _configuration = configuration; } diff --git a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs index f26e220..668df50 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Http.SelfHost/Framework/Factories/RouteConstraintFactory.cs @@ -14,9 +14,9 @@ namespace AttributeRouting.Web.Http.SelfHost.Framework.Factories { public class RouteConstraintFactory : IRouteConstraintFactory { - private readonly HttpAttributeRoutingConfiguration _configuration; + private readonly HttpConfiguration _configuration; - public RouteConstraintFactory(HttpAttributeRoutingConfiguration configuration) + public RouteConstraintFactory(HttpConfiguration configuration) { _configuration = configuration; } diff --git a/src/AttributeRouting.Web.Http.SelfHost/HttpAttributeRoutingConfiguration.cs b/src/AttributeRouting.Web.Http.SelfHost/HttpConfiguration.cs similarity index 88% rename from src/AttributeRouting.Web.Http.SelfHost/HttpAttributeRoutingConfiguration.cs rename to src/AttributeRouting.Web.Http.SelfHost/HttpConfiguration.cs index 2afee9b..5840642 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/HttpAttributeRoutingConfiguration.cs +++ b/src/AttributeRouting.Web.Http.SelfHost/HttpConfiguration.cs @@ -1,34 +1,34 @@ -using System.Web.Http.Routing; -using AttributeRouting.Framework; -using AttributeRouting.Web.Http.Constraints; -using AttributeRouting.Web.Http.SelfHost.Framework.Factories; - -namespace AttributeRouting.Web.Http.SelfHost -{ - public class HttpAttributeRoutingConfiguration : HttpAttributeRoutingConfigurationBase - { - public HttpAttributeRoutingConfiguration() - { - AttributeRouteFactory = new AttributeRouteFactory(this); - RouteConstraintFactory = new RouteConstraintFactory(this); - ParameterFactory = new RouteParameterFactory(); - - RegisterDefaultInlineRouteConstraints(typeof(RegexRouteConstraint).Assembly); - - // Must turn on AutoGenerateRouteNames and use the Unique RouteNameBuilder for this to work out-of-the-box. - AutoGenerateRouteNames = true; - RouteNameBuilder = RouteNameBuilders.Unique; - } - - /// - /// Automatically applies the specified constaint against url parameters - /// with names that match the given regular expression. - /// - /// The regex used to match url parameter names - /// The constraint to apply to matched parameters - public void AddDefaultRouteConstraint(string keyRegex, IHttpRouteConstraint constraint) - { - base.AddDefaultRouteConstraint(keyRegex, constraint); - } - } -} +using System.Web.Http.Routing; +using AttributeRouting.Framework; +using AttributeRouting.Web.Http.Constraints; +using AttributeRouting.Web.Http.SelfHost.Framework.Factories; + +namespace AttributeRouting.Web.Http.SelfHost +{ + public class HttpConfiguration : HttpConfigurationBase + { + public HttpConfiguration() + { + AttributeRouteFactory = new AttributeRouteFactory(this); + RouteConstraintFactory = new RouteConstraintFactory(this); + ParameterFactory = new RouteParameterFactory(); + + RegisterDefaultInlineRouteConstraints(typeof(RegexRouteConstraint).Assembly); + + // Must turn on AutoGenerateRouteNames and use the Unique RouteNameBuilder for this to work out-of-the-box. + AutoGenerateRouteNames = true; + RouteNameBuilder = RouteNameBuilders.Unique; + } + + /// + /// Automatically applies the specified constaint against url parameters + /// with names that match the given regular expression. + /// + /// The regex used to match url parameter names + /// The constraint to apply to matched parameters + public void AddDefaultRouteConstraint(string keyRegex, IHttpRouteConstraint constraint) + { + base.AddDefaultRouteConstraint(keyRegex, constraint); + } + } +} diff --git a/src/AttributeRouting.Web.Http.SelfHost/HttpRouteCollectionExtensions.cs b/src/AttributeRouting.Web.Http.SelfHost/HttpRouteCollectionExtensions.cs index 8287df6..6ae3307 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/HttpRouteCollectionExtensions.cs +++ b/src/AttributeRouting.Web.Http.SelfHost/HttpRouteCollectionExtensions.cs @@ -18,7 +18,7 @@ public static class HttpRouteCollectionExtensions /// public static void MapHttpAttributeRoutes(this HttpRouteCollection routes) { - var configuration = new HttpAttributeRoutingConfiguration(); + var configuration = new HttpConfiguration(); configuration.AddRoutesFromAssembly(Assembly.GetCallingAssembly()); routes.MapHttpAttributeRoutesInternal(configuration); @@ -30,9 +30,9 @@ public static void MapHttpAttributeRoutes(this HttpRouteCollection routes) /// /// /// The initialization action that builds the configuration object - public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, Action configurationAction) + public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, Action configurationAction) { - var configuration = new HttpAttributeRoutingConfiguration(); + var configuration = new HttpConfiguration(); configurationAction.Invoke(configuration); routes.MapHttpAttributeRoutesInternal(configuration); @@ -44,12 +44,12 @@ public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, Actio ///
/// /// The configuration object - public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, HttpAttributeRoutingConfiguration configuration) + public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, HttpConfiguration configuration) { routes.MapHttpAttributeRoutesInternal(configuration); } - private static void MapHttpAttributeRoutesInternal(this HttpRouteCollection routes, HttpAttributeRoutingConfiguration configuration) + private static void MapHttpAttributeRoutesInternal(this HttpRouteCollection routes, HttpConfiguration configuration) { var generatedRoutes = new RouteBuilder(configuration).BuildAllRoutes(); diff --git a/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj b/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj index ec02dee..171927e 100644 --- a/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj +++ b/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj @@ -76,7 +76,7 @@ - + diff --git a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/AttributeRouteFactory.cs b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/AttributeRouteFactory.cs index 47764cd..f867d28 100644 --- a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/AttributeRouteFactory.cs +++ b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/AttributeRouteFactory.cs @@ -8,9 +8,9 @@ namespace AttributeRouting.Web.Http.WebHost.Framework.Factories { internal class AttributeRouteFactory : IAttributeRouteFactory { - private readonly HttpWebAttributeRoutingConfiguration _configuration; + private readonly HttpWebConfiguration _configuration; - public AttributeRouteFactory(HttpWebAttributeRoutingConfiguration configuration) + public AttributeRouteFactory(HttpWebConfiguration configuration) { _configuration = configuration; } diff --git a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs index a004ddd..cef4ca0 100644 --- a/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Http.WebHost/Framework/Factories/RouteConstraintFactory.cs @@ -13,9 +13,9 @@ namespace AttributeRouting.Web.Http.WebHost.Framework.Factories { public class RouteConstraintFactory : IRouteConstraintFactory { - private readonly HttpWebAttributeRoutingConfiguration _configuration; + private readonly HttpWebConfiguration _configuration; - public RouteConstraintFactory(HttpWebAttributeRoutingConfiguration configuration) + public RouteConstraintFactory(HttpWebConfiguration configuration) { _configuration = configuration; } diff --git a/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs b/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs index b219eb4..d8d2abb 100644 --- a/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs +++ b/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs @@ -19,7 +19,7 @@ public static class HttpRouteCollectionExtensions ///
public static void MapHttpAttributeRoutes(this HttpRouteCollection routes) { - var configuration = new HttpWebAttributeRoutingConfiguration(); + var configuration = new HttpWebConfiguration(); configuration.AddRoutesFromAssembly(Assembly.GetCallingAssembly()); routes.MapHttpAttributeRoutesInternal(configuration); @@ -31,9 +31,9 @@ public static void MapHttpAttributeRoutes(this HttpRouteCollection routes) /// /// /// The initialization action that builds the configuration object - public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, Action configurationAction) + public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, Action configurationAction) { - var configuration = new HttpWebAttributeRoutingConfiguration(); + var configuration = new HttpWebConfiguration(); configurationAction.Invoke(configuration); routes.MapHttpAttributeRoutesInternal(configuration); @@ -45,12 +45,12 @@ public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, Actio /// /// /// The configuration object - public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, HttpWebAttributeRoutingConfiguration configuration) + public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, HttpWebConfiguration configuration) { routes.MapHttpAttributeRoutesInternal(configuration); } - private static void MapHttpAttributeRoutesInternal(this HttpRouteCollection routes, HttpWebAttributeRoutingConfiguration configuration) + private static void MapHttpAttributeRoutesInternal(this HttpRouteCollection routes, HttpWebConfiguration configuration) { var generatedRoutes = new RouteBuilder(configuration).BuildAllRoutes().Cast().ToList(); diff --git a/src/AttributeRouting.Web.Http.WebHost/HttpWebAttributeRoutingConfiguration.cs b/src/AttributeRouting.Web.Http.WebHost/HttpWebConfiguration.cs similarity index 89% rename from src/AttributeRouting.Web.Http.WebHost/HttpWebAttributeRoutingConfiguration.cs rename to src/AttributeRouting.Web.Http.WebHost/HttpWebConfiguration.cs index b9e767c..3a18d25 100644 --- a/src/AttributeRouting.Web.Http.WebHost/HttpWebAttributeRoutingConfiguration.cs +++ b/src/AttributeRouting.Web.Http.WebHost/HttpWebConfiguration.cs @@ -1,42 +1,42 @@ -using System; -using System.Web.Routing; -using AttributeRouting.Web.Http.WebHost.Framework.Factories; - -namespace AttributeRouting.Web.Http.WebHost -{ - public class HttpWebAttributeRoutingConfiguration : HttpAttributeRoutingConfigurationBase - { - public HttpWebAttributeRoutingConfiguration() - { - AttributeRouteFactory = new AttributeRouteFactory(this); - ParameterFactory = new RouteParameterFactory(); - RouteConstraintFactory = new RouteConstraintFactory(this); - - RouteHandlerFactory = () => null; - RegisterDefaultInlineRouteConstraints(typeof(Web.Constraints.RegexRouteConstraint).Assembly); - } - - public Func RouteHandlerFactory { get; set; } - - /// - /// Specifies a function that returns an alternate route handler. - /// By default, the route handler is the default HttpControllerRouteHandler. - /// - /// The route handler to use. - public void UseRouteHandler(Func routeHandlerFactory) - { - RouteHandlerFactory = routeHandlerFactory; - } - - /// - /// Automatically applies the specified constaint against url parameters - /// with names that match the given regular expression. - /// - /// The regex used to match url parameter names - /// The constraint to apply to matched parameters - public void AddDefaultRouteConstraint(string keyRegex, IRouteConstraint constraint) - { - base.AddDefaultRouteConstraint(keyRegex, constraint); - } - } -} +using System; +using System.Web.Routing; +using AttributeRouting.Web.Http.WebHost.Framework.Factories; + +namespace AttributeRouting.Web.Http.WebHost +{ + public class HttpWebConfiguration : HttpConfigurationBase + { + public HttpWebConfiguration() + { + AttributeRouteFactory = new AttributeRouteFactory(this); + ParameterFactory = new RouteParameterFactory(); + RouteConstraintFactory = new RouteConstraintFactory(this); + + RouteHandlerFactory = () => null; + RegisterDefaultInlineRouteConstraints(typeof(Web.Constraints.RegexRouteConstraint).Assembly); + } + + public Func RouteHandlerFactory { get; set; } + + /// + /// Specifies a function that returns an alternate route handler. + /// By default, the route handler is the default HttpControllerRouteHandler. + /// + /// The route handler to use. + public void UseRouteHandler(Func routeHandlerFactory) + { + RouteHandlerFactory = routeHandlerFactory; + } + + /// + /// Automatically applies the specified constaint against url parameters + /// with names that match the given regular expression. + /// + /// The regex used to match url parameter names + /// The constraint to apply to matched parameters + public void AddDefaultRouteConstraint(string keyRegex, IRouteConstraint constraint) + { + base.AddDefaultRouteConstraint(keyRegex, constraint); + } + } +} diff --git a/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj b/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj index ec4d572..178248c 100644 --- a/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj +++ b/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj @@ -81,7 +81,7 @@ - + diff --git a/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs b/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs index 51e5f61..f712271 100644 --- a/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs +++ b/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs @@ -13,7 +13,7 @@ namespace AttributeRouting.Web.Http.Framework /// public class HttpAttributeRoute : HttpRoute, IAttributeRoute { - private readonly HttpAttributeRoutingConfigurationBase _configuration; + private readonly HttpConfigurationBase _configuration; /// /// Route used by the AttributeRouting framework in self-host projects. @@ -22,7 +22,7 @@ public HttpAttributeRoute(string url, HttpRouteValueDictionary defaults, HttpRouteValueDictionary constraints, HttpRouteValueDictionary dataTokens, - HttpAttributeRoutingConfigurationBase configuration) + HttpConfigurationBase configuration) : base(url, defaults, constraints, dataTokens, configuration.MessageHandler) { _configuration = configuration; diff --git a/src/AttributeRouting.Web.Http/HttpAttributeRoutingConfigurationBase.cs b/src/AttributeRouting.Web.Http/HttpConfigurationBase.cs similarity index 92% rename from src/AttributeRouting.Web.Http/HttpAttributeRoutingConfigurationBase.cs rename to src/AttributeRouting.Web.Http/HttpConfigurationBase.cs index c1f06bc..451132b 100644 --- a/src/AttributeRouting.Web.Http/HttpAttributeRoutingConfigurationBase.cs +++ b/src/AttributeRouting.Web.Http/HttpConfigurationBase.cs @@ -6,9 +6,9 @@ namespace AttributeRouting.Web.Http { - public abstract class HttpAttributeRoutingConfigurationBase : AttributeRoutingConfigurationBase + public abstract class HttpConfigurationBase : ConfigurationBase { - protected HttpAttributeRoutingConfigurationBase() + protected HttpConfigurationBase() { CurrentUICultureResolver = (ctx, data) => Thread.CurrentThread.CurrentUICulture.Name; } diff --git a/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj b/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj index a67c256..7b2f73a 100644 --- a/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj +++ b/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj @@ -53,7 +53,7 @@ SharedAssemblyInfo.cs - + diff --git a/src/AttributeRouting.Web.Mvc/AttributeRoutingConfiguration.cs b/src/AttributeRouting.Web.Mvc/Configuration.cs similarity index 93% rename from src/AttributeRouting.Web.Mvc/AttributeRoutingConfiguration.cs rename to src/AttributeRouting.Web.Mvc/Configuration.cs index df5cbd1..3ac3913 100644 --- a/src/AttributeRouting.Web.Mvc/AttributeRoutingConfiguration.cs +++ b/src/AttributeRouting.Web.Mvc/Configuration.cs @@ -1,80 +1,80 @@ -using System; -using System.Threading; -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; -using AttributeRouting.Web.Constraints; -using AttributeRouting.Web.Mvc.Framework.Factories; - -namespace AttributeRouting.Web.Mvc -{ - public class AttributeRoutingConfiguration : AttributeRoutingConfigurationBase - { - public AttributeRoutingConfiguration() - { - AttributeRouteFactory = new AttributeRouteFactory(this); - ParameterFactory = new RouteParameterFactory(); - RouteConstraintFactory = new RouteConstraintFactory(this); - - RouteHandlerFactory = () => new MvcRouteHandler(); - CurrentUICultureResolver = (ctx, data) => Thread.CurrentThread.CurrentUICulture.Name; - RegisterDefaultInlineRouteConstraints(typeof(RegexRouteConstraint).Assembly); - } - - public Func RouteHandlerFactory { get; set; } - - /// - /// The controller type applicable to this context. - /// - public override Type FrameworkControllerType - { - get { return typeof(IController); } - } - - /// - /// Specifies a function that returns an alternate route handler. - /// By default, the route handler is the default MvcRouteHandler. - /// - /// The route hanlder to use. - public void UseRouteHandler(Func routeHandlerFactory) - { - RouteHandlerFactory = routeHandlerFactory; - } - - /// - /// Appends the routes from the specified controller type to the end of route collection. - /// - /// The controller type. - public void AddRoutesFromController() where T : IController - { - AddRoutesFromController(typeof(T)); - } - - /// - /// Appends the routes from all controllers that derive from the specified controller type to the route collection. - /// - /// The base controller type. - public void AddRoutesFromControllersOfType() where T : IController - { - AddRoutesFromControllersOfType(typeof(T)); - } - - /// - /// Automatically applies the specified constraint against url parameters - /// with names that match the given regular expression. - /// - /// The regex used to match url parameter names - /// The constraint to apply to matched parameters - public void AddDefaultRouteConstraint(string keyRegex, IRouteConstraint constraint) - { - base.AddDefaultRouteConstraint(keyRegex, constraint); - } - - /// - /// This delegate returns the current UI culture name, - /// which is used when constraining inbound routes by culture. - /// The default delegate returns the CurrentUICulture name of the current thread. - /// - public Func CurrentUICultureResolver { get; set; } - } -} +using System; +using System.Threading; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using AttributeRouting.Web.Constraints; +using AttributeRouting.Web.Mvc.Framework.Factories; + +namespace AttributeRouting.Web.Mvc +{ + public class Configuration : ConfigurationBase + { + public Configuration() + { + AttributeRouteFactory = new AttributeRouteFactory(this); + ParameterFactory = new RouteParameterFactory(); + RouteConstraintFactory = new RouteConstraintFactory(this); + + RouteHandlerFactory = () => new MvcRouteHandler(); + CurrentUICultureResolver = (ctx, data) => Thread.CurrentThread.CurrentUICulture.Name; + RegisterDefaultInlineRouteConstraints(typeof(RegexRouteConstraint).Assembly); + } + + public Func RouteHandlerFactory { get; set; } + + /// + /// The controller type applicable to this context. + /// + public override Type FrameworkControllerType + { + get { return typeof(IController); } + } + + /// + /// Specifies a function that returns an alternate route handler. + /// By default, the route handler is the default MvcRouteHandler. + /// + /// The route hanlder to use. + public void UseRouteHandler(Func routeHandlerFactory) + { + RouteHandlerFactory = routeHandlerFactory; + } + + /// + /// Appends the routes from the specified controller type to the end of route collection. + /// + /// The controller type. + public void AddRoutesFromController() where T : IController + { + AddRoutesFromController(typeof(T)); + } + + /// + /// Appends the routes from all controllers that derive from the specified controller type to the route collection. + /// + /// The base controller type. + public void AddRoutesFromControllersOfType() where T : IController + { + AddRoutesFromControllersOfType(typeof(T)); + } + + /// + /// Automatically applies the specified constraint against url parameters + /// with names that match the given regular expression. + /// + /// The regex used to match url parameter names + /// The constraint to apply to matched parameters + public void AddDefaultRouteConstraint(string keyRegex, IRouteConstraint constraint) + { + base.AddDefaultRouteConstraint(keyRegex, constraint); + } + + /// + /// This delegate returns the current UI culture name, + /// which is used when constraining inbound routes by culture. + /// The default delegate returns the CurrentUICulture name of the current thread. + /// + public Func CurrentUICultureResolver { get; set; } + } +} diff --git a/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs b/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs index 24d7d9b..1f6cf70 100644 --- a/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs +++ b/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs @@ -13,7 +13,7 @@ namespace AttributeRouting.Web.Mvc.Framework /// public class AttributeRoute : Route, IAttributeRoute { - private readonly AttributeRoutingConfiguration _configuration; + private readonly Configuration _configuration; /// /// Route used by the AttributeRouting framework in web projects. @@ -22,7 +22,7 @@ public AttributeRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, - AttributeRoutingConfiguration configuration) + Configuration configuration) : base(url, defaults, constraints, dataTokens, configuration.RouteHandlerFactory()) { _configuration = configuration; diff --git a/src/AttributeRouting.Web.Mvc/Framework/Factories/AttributeRouteFactory.cs b/src/AttributeRouting.Web.Mvc/Framework/Factories/AttributeRouteFactory.cs index 4d1bb43..6d8c0b9 100644 --- a/src/AttributeRouting.Web.Mvc/Framework/Factories/AttributeRouteFactory.cs +++ b/src/AttributeRouting.Web.Mvc/Framework/Factories/AttributeRouteFactory.cs @@ -7,9 +7,9 @@ namespace AttributeRouting.Web.Mvc.Framework.Factories { internal class AttributeRouteFactory : IAttributeRouteFactory { - private readonly AttributeRoutingConfiguration _configuration; + private readonly Configuration _configuration; - public AttributeRouteFactory(AttributeRoutingConfiguration configuration) + public AttributeRouteFactory(Configuration configuration) { _configuration = configuration; } diff --git a/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs index ac2cff7..bdfba5d 100644 --- a/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Mvc/Framework/Factories/RouteConstraintFactory.cs @@ -13,9 +13,9 @@ namespace AttributeRouting.Web.Mvc.Framework.Factories { internal class RouteConstraintFactory : IRouteConstraintFactory { - private readonly AttributeRoutingConfiguration _configuration; + private readonly Configuration _configuration; - public RouteConstraintFactory(AttributeRoutingConfiguration configuration) + public RouteConstraintFactory(Configuration configuration) { _configuration = configuration; } diff --git a/src/AttributeRouting.Web.Mvc/RouteCollectionExtensions.cs b/src/AttributeRouting.Web.Mvc/RouteCollectionExtensions.cs index 5a511cc..12f739e 100644 --- a/src/AttributeRouting.Web.Mvc/RouteCollectionExtensions.cs +++ b/src/AttributeRouting.Web.Mvc/RouteCollectionExtensions.cs @@ -18,7 +18,7 @@ public static class RouteCollectionExtensions /// public static void MapAttributeRoutes(this RouteCollection routes) { - var configuration = new AttributeRoutingConfiguration(); + var configuration = new Configuration(); configuration.AddRoutesFromAssembly(Assembly.GetCallingAssembly()); routes.MapAttributeRoutesInternal(configuration); @@ -30,9 +30,9 @@ public static void MapAttributeRoutes(this RouteCollection routes) /// /// /// The initialization action that builds the configuration object - public static void MapAttributeRoutes(this RouteCollection routes, Action configurationAction) + public static void MapAttributeRoutes(this RouteCollection routes, Action configurationAction) { - var configuration = new AttributeRoutingConfiguration(); + var configuration = new Configuration(); configurationAction.Invoke(configuration); routes.MapAttributeRoutesInternal(configuration); @@ -44,12 +44,12 @@ public static void MapAttributeRoutes(this RouteCollection routes, Action /// /// The configuration object - public static void MapAttributeRoutes(this RouteCollection routes, AttributeRoutingConfiguration configuration) + public static void MapAttributeRoutes(this RouteCollection routes, Configuration configuration) { routes.MapAttributeRoutesInternal(configuration); } - private static void MapAttributeRoutesInternal(this RouteCollection routes, AttributeRoutingConfiguration configuration) + private static void MapAttributeRoutesInternal(this RouteCollection routes, Configuration configuration) { var generatedRoutes = new RouteBuilder(configuration).BuildAllRoutes(); diff --git a/src/AttributeRouting/AreaConfiguration.cs b/src/AttributeRouting/AreaConfiguration.cs index e580182..b82723a 100644 --- a/src/AttributeRouting/AreaConfiguration.cs +++ b/src/AttributeRouting/AreaConfiguration.cs @@ -9,12 +9,12 @@ namespace AttributeRouting public class AreaConfiguration { private readonly string _name; - private readonly AttributeRoutingConfigurationBase _configuration; + private readonly ConfigurationBase _configuration; /// /// Helper for configuring areas when initializing AttributeRouting framework. /// - public AreaConfiguration(string name, AttributeRoutingConfigurationBase configuration) + public AreaConfiguration(string name, ConfigurationBase configuration) { _name = name; _configuration = configuration; diff --git a/src/AttributeRouting/AttributeRouting.csproj b/src/AttributeRouting/AttributeRouting.csproj index 0deaa0c..3a6708e 100644 --- a/src/AttributeRouting/AttributeRouting.csproj +++ b/src/AttributeRouting/AttributeRouting.csproj @@ -58,7 +58,7 @@ SharedAssemblyInfo.cs - + diff --git a/src/AttributeRouting/AttributeRoutingConfigurationBase.cs b/src/AttributeRouting/ConfigurationBase.cs similarity index 96% rename from src/AttributeRouting/AttributeRoutingConfigurationBase.cs rename to src/AttributeRouting/ConfigurationBase.cs index 9193722..1a41ca2 100644 --- a/src/AttributeRouting/AttributeRoutingConfigurationBase.cs +++ b/src/AttributeRouting/ConfigurationBase.cs @@ -1,282 +1,282 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using AttributeRouting.Constraints; -using AttributeRouting.Framework; -using AttributeRouting.Framework.Factories; -using AttributeRouting.Framework.Localization; -using AttributeRouting.Helpers; - -namespace AttributeRouting -{ - /// - /// Configuration options to use when generating AttributeRoutes. - /// - public abstract class AttributeRoutingConfigurationBase - { - /// - /// Creates and initializes a new configuration object. - /// - protected AttributeRoutingConfigurationBase() - { - Assemblies = new List(); - OrderedControllerTypes = new List(); - - InheritActionsFromBaseController = false; - - // Constraint setting initialization - DefaultRouteConstraints = new Dictionary(); - InlineRouteConstraints = new Dictionary(); - - // Translation setting initialization - TranslationProviders = new List(); - - // Subdomain config setting initialization - AreaSubdomainOverrides = new Dictionary(); - DefaultSubdomain = "www"; - SubdomainParser = SubdomainParsers.ThreeSection; - - // AutoGenerateRouteNames config setting initialization - RouteNameBuilder = RouteNameBuilders.FirstInWins; - } - - /// - /// Type of the framework controller (IController, IHttpController). - /// - public abstract Type FrameworkControllerType { get; } - - /// - /// Factory for generating routes used by AttributeRouting. - /// - public IAttributeRouteFactory AttributeRouteFactory { get; set; } - - /// - /// Factory for generating route constraints. - /// - public IRouteConstraintFactory RouteConstraintFactory { get; set; } - - /// - /// Factory for generating optional route parameters. - /// - public IParameterFactory ParameterFactory { get; set; } - - internal List Assemblies { get; set; } - - internal List OrderedControllerTypes { get; set; } - - internal IDictionary DefaultRouteConstraints { get; set; } - - internal IDictionary AreaSubdomainOverrides { get; set; } - - /// - /// Collection of available inline route constraint definitions. - /// - public IDictionary InlineRouteConstraints { get; private set; } - - /// - /// Translation providers. - /// - public List TranslationProviders { get; set; } - - /// - /// When true, the generated routes will produce lowercase URLs. - /// The default is false. - /// - public bool UseLowercaseRoutes { get; set; } - - /// - /// When true, the generated routes will not lowercase URL parameter values. - /// The default is false. - /// - public bool PreserveCaseForUrlParameters { get; set; } - - /// - /// When true, the generated routes will have a trailing slash on the path of outbound URLs. - /// The default is false. - /// - public bool AppendTrailingSlash { get; set; } - - /// - /// When true, the generated routes will have auto-generated route names in the form controller_action. - /// The default is false. - /// - public bool AutoGenerateRouteNames { get; set; } - - /// - /// Given a route specification, this delegate returns the route name - /// to use when is true; - /// - public Func RouteNameBuilder { get; set; } - - /// - /// Given the requested hostname, this delegate parses the subdomain. - /// The default yields everything before the domain name; - /// eg: www.example.com yields www, and example.com yields null. - /// - public Func SubdomainParser { get; set; } - - /// - /// Specify the default subdomain for this application. - /// The default is www. - /// - public string DefaultSubdomain { get; set; } - - /// - /// When true, the generated routes will include actions defined on base controllers. - /// The default is false. - /// Note: Base Controllers should be declared as abstract to avoid routes being generated for them - /// - public bool InheritActionsFromBaseController { get; set; } - - /// - /// Constrains translated routes by the thread's current UI culture. - /// The default is false. - /// - public bool ConstrainTranslatedRoutesByCurrentUICulture { get; set; } - - /// - /// Returns a utility for configuring areas when initializing AttributeRouting framework. - /// - /// The name of the area to configure - public AreaConfiguration MapArea(string name) - { - return new AreaConfiguration(name, this); - } - - /// - /// Scans the assembly of the specified controller for routes to register. - /// - /// The type used to specify the assembly. - [Obsolete("Prefer using AddRoutesFromController, AddRoutesFromControllersOfType, and AddRoutesFromAssembly.")] - public void ScanAssemblyOf() - { - ScanAssembly(typeof(T).Assembly); - } - - /// - /// Scans the specified assembly for routes to register. - /// - /// The assembly. - [Obsolete("Prefer using AddRoutesFromController, AddRoutesFromControllersOfType, and AddRoutesFromAssembly.")] - public void ScanAssembly(Assembly assembly) - { - if (!Assemblies.Contains(assembly)) - Assemblies.Add(assembly); - } - - /// - /// Appends the routes from all controllers in the specified assembly to the route collection. - /// - /// The type denoting the assembly. - public void AddRoutesFromAssemblyOf() - { - AddRoutesFromAssembly(typeof(T).Assembly); - } - - /// - /// Appends the routes from all controllers in the specified assembly to the route collection. - /// - /// The assembly. - public void AddRoutesFromAssembly(Assembly assembly) - { - var controllerTypes = assembly.GetControllerTypes(FrameworkControllerType); - - foreach (var controllerType in controllerTypes) - AddRoutesFromControllerInternal(controllerType); - } - - /// - /// Appends the routes from all controllers that derive from the specified controller type to the route collection. - /// - /// The base controller type. - public void AddRoutesFromControllersOfType(Type baseControllerType) - { - var assembly = baseControllerType.Assembly; - - var controllerTypes = from controllerType in assembly.GetControllerTypes(FrameworkControllerType) - where baseControllerType.IsAssignableFrom(controllerType) - select controllerType; - - foreach (var controllerType in controllerTypes) - AddRoutesFromControllerInternal(controllerType, true); - } - - /// - /// Appends the routes from the specified controller type to the end of route collection. - /// - /// The controller type. - public void AddRoutesFromController(Type controllerType) - { - AddRoutesFromControllerInternal(controllerType, true); - } - - /// - /// Appends the routes from the controller to the promoted controller type list, - /// optionally removing an already added type in order to add it to the end of the list. - /// - /// The controller type. - /// Whether to remove and re-add already added controller types. - private void AddRoutesFromControllerInternal(Type controllerType, bool reorderTypes = false) - { - if (!FrameworkControllerType.IsAssignableFrom(controllerType)) - return; - - if (!OrderedControllerTypes.Contains(controllerType)) - { - OrderedControllerTypes.Add(controllerType); - } - else if (reorderTypes) - { - OrderedControllerTypes.Remove(controllerType); - OrderedControllerTypes.Add(controllerType); - } - } - - protected void AddDefaultRouteConstraint(string keyRegex, object constraint) - { - if (!DefaultRouteConstraints.ContainsKey(keyRegex)) - DefaultRouteConstraints.Add(keyRegex, constraint); - } - - /// - /// Add a provider for translating components of routes. - /// - public void AddTranslationProvider() - where TTranslationProvider : TranslationProviderBase, new() - { - TranslationProviders.Add(new TTranslationProvider()); - } - - /// - /// Add a provider for translating components of routes. - /// Use for a default implementation. - /// - public void AddTranslationProvider(TranslationProviderBase provider) - { - TranslationProviders.Add(provider); - } - - internal IEnumerable GetTranslationProviderCultureNames() - { - return (from provider in TranslationProviders - from cultureName in provider.CultureNames - select cultureName).Distinct().ToList(); - } - - protected void RegisterDefaultInlineRouteConstraints(Assembly assembly) - { - var inlineConstraintTypes = from t in assembly.GetTypes() - where typeof(TRouteConstraint).IsAssignableFrom(t) - && typeof(IAttributeRouteConstraint).IsAssignableFrom(t) - select t; - - foreach (var inlineConstraintType in inlineConstraintTypes) - { - var name = Regex.Replace(inlineConstraintType.Name, "RouteConstraint$", "").ToLowerInvariant(); - InlineRouteConstraints.Add(name, inlineConstraintType); - } - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using AttributeRouting.Constraints; +using AttributeRouting.Framework; +using AttributeRouting.Framework.Factories; +using AttributeRouting.Framework.Localization; +using AttributeRouting.Helpers; + +namespace AttributeRouting +{ + /// + /// Configuration options to use when generating AttributeRoutes. + /// + public abstract class ConfigurationBase + { + /// + /// Creates and initializes a new configuration object. + /// + protected ConfigurationBase() + { + Assemblies = new List(); + OrderedControllerTypes = new List(); + + InheritActionsFromBaseController = false; + + // Constraint setting initialization + DefaultRouteConstraints = new Dictionary(); + InlineRouteConstraints = new Dictionary(); + + // Translation setting initialization + TranslationProviders = new List(); + + // Subdomain config setting initialization + AreaSubdomainOverrides = new Dictionary(); + DefaultSubdomain = "www"; + SubdomainParser = SubdomainParsers.ThreeSection; + + // AutoGenerateRouteNames config setting initialization + RouteNameBuilder = RouteNameBuilders.FirstInWins; + } + + /// + /// Type of the framework controller (IController, IHttpController). + /// + public abstract Type FrameworkControllerType { get; } + + /// + /// Factory for generating routes used by AttributeRouting. + /// + public IAttributeRouteFactory AttributeRouteFactory { get; set; } + + /// + /// Factory for generating route constraints. + /// + public IRouteConstraintFactory RouteConstraintFactory { get; set; } + + /// + /// Factory for generating optional route parameters. + /// + public IParameterFactory ParameterFactory { get; set; } + + internal List Assemblies { get; set; } + + internal List OrderedControllerTypes { get; set; } + + internal IDictionary DefaultRouteConstraints { get; set; } + + internal IDictionary AreaSubdomainOverrides { get; set; } + + /// + /// Collection of available inline route constraint definitions. + /// + public IDictionary InlineRouteConstraints { get; private set; } + + /// + /// Translation providers. + /// + public List TranslationProviders { get; set; } + + /// + /// When true, the generated routes will produce lowercase URLs. + /// The default is false. + /// + public bool UseLowercaseRoutes { get; set; } + + /// + /// When true, the generated routes will not lowercase URL parameter values. + /// The default is false. + /// + public bool PreserveCaseForUrlParameters { get; set; } + + /// + /// When true, the generated routes will have a trailing slash on the path of outbound URLs. + /// The default is false. + /// + public bool AppendTrailingSlash { get; set; } + + /// + /// When true, the generated routes will have auto-generated route names in the form controller_action. + /// The default is false. + /// + public bool AutoGenerateRouteNames { get; set; } + + /// + /// Given a route specification, this delegate returns the route name + /// to use when is true; + /// + public Func RouteNameBuilder { get; set; } + + /// + /// Given the requested hostname, this delegate parses the subdomain. + /// The default yields everything before the domain name; + /// eg: www.example.com yields www, and example.com yields null. + /// + public Func SubdomainParser { get; set; } + + /// + /// Specify the default subdomain for this application. + /// The default is www. + /// + public string DefaultSubdomain { get; set; } + + /// + /// When true, the generated routes will include actions defined on base controllers. + /// The default is false. + /// Note: Base Controllers should be declared as abstract to avoid routes being generated for them + /// + public bool InheritActionsFromBaseController { get; set; } + + /// + /// Constrains translated routes by the thread's current UI culture. + /// The default is false. + /// + public bool ConstrainTranslatedRoutesByCurrentUICulture { get; set; } + + /// + /// Returns a utility for configuring areas when initializing AttributeRouting framework. + /// + /// The name of the area to configure + public AreaConfiguration MapArea(string name) + { + return new AreaConfiguration(name, this); + } + + /// + /// Scans the assembly of the specified controller for routes to register. + /// + /// The type used to specify the assembly. + [Obsolete("Prefer using AddRoutesFromController, AddRoutesFromControllersOfType, and AddRoutesFromAssembly.")] + public void ScanAssemblyOf() + { + ScanAssembly(typeof(T).Assembly); + } + + /// + /// Scans the specified assembly for routes to register. + /// + /// The assembly. + [Obsolete("Prefer using AddRoutesFromController, AddRoutesFromControllersOfType, and AddRoutesFromAssembly.")] + public void ScanAssembly(Assembly assembly) + { + if (!Assemblies.Contains(assembly)) + Assemblies.Add(assembly); + } + + /// + /// Appends the routes from all controllers in the specified assembly to the route collection. + /// + /// The type denoting the assembly. + public void AddRoutesFromAssemblyOf() + { + AddRoutesFromAssembly(typeof(T).Assembly); + } + + /// + /// Appends the routes from all controllers in the specified assembly to the route collection. + /// + /// The assembly. + public void AddRoutesFromAssembly(Assembly assembly) + { + var controllerTypes = assembly.GetControllerTypes(FrameworkControllerType); + + foreach (var controllerType in controllerTypes) + AddRoutesFromControllerInternal(controllerType); + } + + /// + /// Appends the routes from all controllers that derive from the specified controller type to the route collection. + /// + /// The base controller type. + public void AddRoutesFromControllersOfType(Type baseControllerType) + { + var assembly = baseControllerType.Assembly; + + var controllerTypes = from controllerType in assembly.GetControllerTypes(FrameworkControllerType) + where baseControllerType.IsAssignableFrom(controllerType) + select controllerType; + + foreach (var controllerType in controllerTypes) + AddRoutesFromControllerInternal(controllerType, true); + } + + /// + /// Appends the routes from the specified controller type to the end of route collection. + /// + /// The controller type. + public void AddRoutesFromController(Type controllerType) + { + AddRoutesFromControllerInternal(controllerType, true); + } + + /// + /// Appends the routes from the controller to the promoted controller type list, + /// optionally removing an already added type in order to add it to the end of the list. + /// + /// The controller type. + /// Whether to remove and re-add already added controller types. + private void AddRoutesFromControllerInternal(Type controllerType, bool reorderTypes = false) + { + if (!FrameworkControllerType.IsAssignableFrom(controllerType)) + return; + + if (!OrderedControllerTypes.Contains(controllerType)) + { + OrderedControllerTypes.Add(controllerType); + } + else if (reorderTypes) + { + OrderedControllerTypes.Remove(controllerType); + OrderedControllerTypes.Add(controllerType); + } + } + + protected void AddDefaultRouteConstraint(string keyRegex, object constraint) + { + if (!DefaultRouteConstraints.ContainsKey(keyRegex)) + DefaultRouteConstraints.Add(keyRegex, constraint); + } + + /// + /// Add a provider for translating components of routes. + /// + public void AddTranslationProvider() + where TTranslationProvider : TranslationProviderBase, new() + { + TranslationProviders.Add(new TTranslationProvider()); + } + + /// + /// Add a provider for translating components of routes. + /// Use for a default implementation. + /// + public void AddTranslationProvider(TranslationProviderBase provider) + { + TranslationProviders.Add(provider); + } + + internal IEnumerable GetTranslationProviderCultureNames() + { + return (from provider in TranslationProviders + from cultureName in provider.CultureNames + select cultureName).Distinct().ToList(); + } + + protected void RegisterDefaultInlineRouteConstraints(Assembly assembly) + { + var inlineConstraintTypes = from t in assembly.GetTypes() + where typeof(TRouteConstraint).IsAssignableFrom(t) + && typeof(IAttributeRouteConstraint).IsAssignableFrom(t) + select t; + + foreach (var inlineConstraintType in inlineConstraintTypes) + { + var name = Regex.Replace(inlineConstraintType.Name, "RouteConstraint$", "").ToLowerInvariant(); + InlineRouteConstraints.Add(name, inlineConstraintType); + } + } + } } \ No newline at end of file diff --git a/src/AttributeRouting/Framework/AttributeRouteExtensions.cs b/src/AttributeRouting/Framework/AttributeRouteExtensions.cs index 0eb2780..fa63f3b 100644 --- a/src/AttributeRouting/Framework/AttributeRouteExtensions.cs +++ b/src/AttributeRouting/Framework/AttributeRouteExtensions.cs @@ -45,7 +45,7 @@ public static bool IsLeftPartOfUrlMatched(this IAttributeRoute route, string req /// The host from the current request /// The configuration for the route /// True if the subdomain for this route matches the current request host. - public static bool IsSubdomainMatched(this IAttributeRoute route, string host, AttributeRoutingConfigurationBase configuration) + public static bool IsSubdomainMatched(this IAttributeRoute route, string host, ConfigurationBase configuration) { // If no subdomains are mapped with AR, then yes. if (!route.MappedSubdomains.Any()) @@ -73,7 +73,7 @@ public static bool IsSubdomainMatched(this IAttributeRoute route, string host, A /// /// /// - public static bool IsCultureNameMatched(this IAttributeRoute route, string currentUICultureName, AttributeRoutingConfigurationBase configuration) + public static bool IsCultureNameMatched(this IAttributeRoute route, string currentUICultureName, ConfigurationBase configuration) { if (!configuration.ConstrainTranslatedRoutesByCurrentUICulture) return true; @@ -190,7 +190,7 @@ public static TVirtualPathData GetTranslatedVirtualPath(this I /// The current virtual path, after translation /// The configuration for the route /// The final virtual path - public static string GetFinalVirtualPath(this IAttributeRoute route, string virtualPath, AttributeRoutingConfigurationBase configuration) + public static string GetFinalVirtualPath(this IAttributeRoute route, string virtualPath, ConfigurationBase configuration) { /** * Lowercase urls. diff --git a/src/AttributeRouting/Framework/IAttributeRoute.cs b/src/AttributeRouting/Framework/IAttributeRoute.cs index 02360f2..61b469e 100644 --- a/src/AttributeRouting/Framework/IAttributeRoute.cs +++ b/src/AttributeRouting/Framework/IAttributeRoute.cs @@ -40,19 +40,19 @@ public interface IAttributeRoute string Url { get; set; } /// - /// If true, will override + /// If true, will override /// set via global configuration and the generated route will have a lowercase URL. /// bool? UseLowercaseRoute { get; set; } /// - /// If true, will override + /// If true, will override /// set via global configuration and the generated route will not lowercase URL parameter values. /// bool? PreserveCaseForUrlParameters { get; set; } /// - /// If true, will override + /// If true, will override /// set via global configuration and the generated route will have a trailing slash on the path of outbound URLs. /// bool? AppendTrailingSlash { get; set; } diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index d8f62ca..793277b 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -10,16 +10,16 @@ namespace AttributeRouting.Framework { /// /// Creates objects according to the - /// options set in implementations of . + /// options set in implementations of . /// public class RouteBuilder { - private readonly AttributeRoutingConfigurationBase _configuration; + private readonly ConfigurationBase _configuration; private readonly IAttributeRouteFactory _routeFactory; private readonly IRouteConstraintFactory _routeConstraintFactory; private readonly IParameterFactory _parameterFactory; - public RouteBuilder(AttributeRoutingConfigurationBase configuration) + public RouteBuilder(ConfigurationBase configuration) { if (configuration == null) throw new ArgumentNullException("configuration"); diff --git a/src/AttributeRouting/Framework/RouteReflector.cs b/src/AttributeRouting/Framework/RouteReflector.cs index b7168ea..68b18b9 100644 --- a/src/AttributeRouting/Framework/RouteReflector.cs +++ b/src/AttributeRouting/Framework/RouteReflector.cs @@ -8,13 +8,13 @@ namespace AttributeRouting.Framework { /// /// Creates objects according to the - /// options set in implementations of . + /// options set in implementations of . /// public class RouteReflector { - private readonly AttributeRoutingConfigurationBase _configuration; + private readonly ConfigurationBase _configuration; - public RouteReflector(AttributeRoutingConfigurationBase configuration) + public RouteReflector(ConfigurationBase configuration) { if (configuration == null) throw new ArgumentNullException("configuration"); diff --git a/src/AttributeRouting/IRouteAttribute.cs b/src/AttributeRouting/IRouteAttribute.cs index d35de64..7595a0e 100644 --- a/src/AttributeRouting/IRouteAttribute.cs +++ b/src/AttributeRouting/IRouteAttribute.cs @@ -78,7 +78,7 @@ public interface IRouteAttribute string TranslationKey { get; set; } /// - /// If set, will override + /// If set, will override /// set via global configuration for this route. /// bool UseLowercaseRoute { get; set; } @@ -89,7 +89,7 @@ public interface IRouteAttribute bool? UseLowercaseRouteFlag { get; } /// - /// If set, will override + /// If set, will override /// set via global configuration for this route. /// bool PreserveCaseForUrlParameters { get; set; } @@ -100,7 +100,7 @@ public interface IRouteAttribute bool? PreserveCaseForUrlParametersFlag { get; } /// - /// If true, will override + /// If true, will override /// set via global configuration for this route. /// bool AppendTrailingSlash { get; set; } From 3cd051f9b452d6c0a33b995a1c438862d90baa51 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Fri, 21 Dec 2012 17:15:55 -0700 Subject: [PATCH 25/97] #175 - fixed logic for finding default area name. --- .../AttributeRouting.Specs.csproj | 1 + .../Features/RouteAreas.feature | 4 ++-- .../Features/RouteAreas.feature.cs | 4 ++-- .../Areas/Sample/DefaultRouteAreaController.cs | 15 +++++++++++++++ .../Subjects/RouteAreasControllers.cs | 10 ---------- src/AttributeRouting/Framework/RouteReflector.cs | 4 ++-- .../Helpers/ReflectionExtensions.cs | 14 +++++++++++--- 7 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 src/AttributeRouting.Specs/Subjects/Areas/Sample/DefaultRouteAreaController.cs diff --git a/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj b/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj index e502b37..c2cb4a3 100644 --- a/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj +++ b/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj @@ -141,6 +141,7 @@ + diff --git a/src/AttributeRouting.Specs/Features/RouteAreas.feature b/src/AttributeRouting.Specs/Features/RouteAreas.feature index ace3daa..dea75a6 100644 --- a/src/AttributeRouting.Specs/Features/RouteAreas.feature +++ b/src/AttributeRouting.Specs/Features/RouteAreas.feature @@ -41,6 +41,6 @@ Scenario: Generating area routes with an explicit area url when route urls speci Scenario: Generating area routes with the default ctor of the RouteAreaAttribute Given I have registered the routes for the DefaultRouteAreaController When I fetch the routes for the DefaultRouteArea controller's Index action - Then the route url is "Subjects/Index" - And the data token for "area" is "Subjects" + Then the route url is "Sample/Index" + And the data token for "area" is "Sample" diff --git a/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs b/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs index 51449c0..d6f9ed5 100644 --- a/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteAreas.feature.cs @@ -203,9 +203,9 @@ public virtual void GeneratingAreaRoutesWithTheDefaultCtorOfTheRouteAreaAttribut #line 43 testRunner.When("I fetch the routes for the DefaultRouteArea controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 44 - testRunner.Then("the route url is \"Subjects/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); + testRunner.Then("the route url is \"Sample/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line 45 - testRunner.And("the data token for \"area\" is \"Subjects\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); + testRunner.And("the data token for \"area\" is \"Sample\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Subjects/Areas/Sample/DefaultRouteAreaController.cs b/src/AttributeRouting.Specs/Subjects/Areas/Sample/DefaultRouteAreaController.cs new file mode 100644 index 0000000..8c657b8 --- /dev/null +++ b/src/AttributeRouting.Specs/Subjects/Areas/Sample/DefaultRouteAreaController.cs @@ -0,0 +1,15 @@ +using System.Web.Mvc; +using AttributeRouting.Web.Mvc; + +namespace AttributeRouting.Specs.Subjects.Areas.Sample +{ + [RouteArea] + public class DefaultRouteAreaController : Controller + { + [GET("Index")] + public ActionResult Index() + { + return Content(""); + } + } +} \ No newline at end of file diff --git a/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs b/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs index e9228cf..4dcf68b 100644 --- a/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/RouteAreasControllers.cs @@ -52,14 +52,4 @@ public ActionResult DuplicatePrefix() return Content(""); } } - - [RouteArea] - public class DefaultRouteAreaController : Controller - { - [GET("Index")] - public ActionResult Index() - { - return Content(""); - } - } } diff --git a/src/AttributeRouting/Framework/RouteReflector.cs b/src/AttributeRouting/Framework/RouteReflector.cs index 68b18b9..a0a86b9 100644 --- a/src/AttributeRouting/Framework/RouteReflector.cs +++ b/src/AttributeRouting/Framework/RouteReflector.cs @@ -207,7 +207,7 @@ private static string GetAreaName(RouteAreaAttribute routeAreaAttribute, Type co // If given an area name, then use it. // Otherwise, use the last section of the namespace of the controller, as a convention. - return routeAreaAttribute.AreaName ?? controllerType.GetLastSectionOfNamespace(); + return routeAreaAttribute.AreaName ?? controllerType.GetConventionalAreaName(); } /// @@ -232,7 +232,7 @@ private static string GetAreaUrl(RouteAreaAttribute routeAreaAttribute, string s // If we're given an area url or an area name, then use it. // Otherwise, use the last section of the namespace of the controller, as a convention. var areaUrlOrName = routeAreaAttribute.AreaUrl ?? routeAreaAttribute.AreaName; - return areaUrlOrName ?? controllerType.GetLastSectionOfNamespace(); + return areaUrlOrName ?? controllerType.GetConventionalAreaName(); } /// diff --git a/src/AttributeRouting/Helpers/ReflectionExtensions.cs b/src/AttributeRouting/Helpers/ReflectionExtensions.cs index c04e77f..51f8239 100644 --- a/src/AttributeRouting/Helpers/ReflectionExtensions.cs +++ b/src/AttributeRouting/Helpers/ReflectionExtensions.cs @@ -8,10 +8,18 @@ namespace AttributeRouting.Helpers { public static class ReflectionExtensions { - public static string GetLastSectionOfNamespace(this Type type) + public static string GetConventionalAreaName(this Type type) { - var ns = type.Namespace; - return ns == null ? null : ns.Split('.').Last(); + var typeNameSpace = type.Namespace; + if (typeNameSpace == null) + return null; + + return typeNameSpace + .Split('.') + .SkipWhile(s => !s.ValueEquals("Areas")) + .Skip(1) // move past "Areas" + .Take(1) // take the next + .FirstOrDefault(); } public static IEnumerable GetActionMethods(this Type type, bool inheritActionsFromBaseController) From 2737bfe1a183a0e809ddc44b1a34bafbf7387661 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Fri, 21 Dec 2012 17:19:01 -0700 Subject: [PATCH 26/97] updated readme and assembly info for 3.4.1 --- README.textile | 4 ++++ src/SharedAssemblyInfo.cs | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.textile b/README.textile index 3ec8bd1..ebd17cb 100644 --- a/README.textile +++ b/README.textile @@ -4,6 +4,10 @@ h2. Please refer to "attributerouting.net":http://attributerouting.net/ for docu h3. Changelog +_3.4.1_ + +* #175 - fixed bug in getting the default area name for a controller. + _3.4_ * #124 - Now supporting custom IRouteHandler in web-host scenario. Also supporting custom HttpMessageHandler for Web API. diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index 2a41a79..29fcad0 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyProduct("AttributeRouting")] [assembly: AssemblyCopyright("Copyright 2010-2012 Tim McCall")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.4")] -[assembly: AssemblyFileVersion("3.4")] -[assembly: AssemblyInformationalVersion("3.4")] +[assembly: AssemblyVersion("3.4.1")] +[assembly: AssemblyFileVersion("3.4.1")] +[assembly: AssemblyInformationalVersion("3.4.1")] [assembly: AssemblyConfiguration("Release")] From cecfd91a1cc97a61e0bd1958884280de8c353dfd Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Fri, 21 Dec 2012 18:02:08 -0700 Subject: [PATCH 27/97] #173 - strong signing assemblies. --- .../AttributeRouting.Web.Http.SelfHost.csproj | 7 +++++++ .../AttributeRouting.snk | Bin 0 -> 596 bytes .../AttributeRouting.Web.Http.WebHost.csproj | 7 +++++++ .../AttributeRouting.snk | Bin 0 -> 596 bytes .../AttributeRouting.Web.Http.csproj | 7 +++++++ .../AttributeRouting.snk | Bin 0 -> 596 bytes .../AttributeRouting.Web.Mvc.csproj | 9 +++++++++ src/AttributeRouting.Web.Mvc/AttributeRouting.snk | Bin 0 -> 596 bytes .../AttributeRouting.Web.csproj | 10 +++++++++- src/AttributeRouting.Web/AttributeRouting.snk | Bin 0 -> 596 bytes src/AttributeRouting.snk | Bin 0 -> 596 bytes src/AttributeRouting/AttributeRouting.csproj | 9 +++++++++ src/AttributeRouting/AttributeRouting.snk | Bin 0 -> 596 bytes 13 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.snk create mode 100644 src/AttributeRouting.Web.Http.WebHost/AttributeRouting.snk create mode 100644 src/AttributeRouting.Web.Http/AttributeRouting.snk create mode 100644 src/AttributeRouting.Web.Mvc/AttributeRouting.snk create mode 100644 src/AttributeRouting.Web/AttributeRouting.snk create mode 100644 src/AttributeRouting.snk create mode 100644 src/AttributeRouting/AttributeRouting.snk diff --git a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj index f404f8a..2d38ede 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj +++ b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj @@ -36,6 +36,12 @@ bin\Release\AttributeRouting.Web.Http.SelfHost.xml 1591, 1587 + + true + + + AttributeRouting.snk + ..\packages\Newtonsoft.Json.4.5.8\lib\net40\Newtonsoft.Json.dll @@ -88,6 +94,7 @@ + diff --git a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.snk b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.snk new file mode 100644 index 0000000000000000000000000000000000000000..b5ab91c2a292360331d78f208218a036f4b58158 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097#FKi0%iVOb>nhNTb6oAn@5Wl-Ypa0MC z&rDP{JXmd`5);UKJA^@|66-Ob?WIsSQ%#9b_o2Xg_1BpO#uo$&t=(0dj{P*0R}Asp zDyc?k!X3Zri**9ilE&i2G1i5b$vfMzrf>TMu~|v{-;DM~s=tr{lV9kV6$CIMtBsx| zvT9T8aIH*7HI6l>;HvEqXg*VIF}ri6a7A%m(MyOju+e|(p=QA|rx@h5F@d2aoHzYk zm1pVD2cU~M3@gf4fk8^GJTf|O_D>dq(oXGTp(eiqu8^`f%;Jq*YsdXS060F+_Gc#; zcswtNqp0vUEo*mjq1b}FZftx2mc_^kA$<6G1dSaJ7F9u)ZgNC}CXyijzMbY<_cZhq zDz7bMV6(Pz=8%8ubhA~~k(k~8*E;*}q68KuCQg&PF1>6yy~^$~&i@ixmC4H<1Nl=4 zEhfs5>uO0fvvm}*qFm8g6-1e8g7nUFlAiJYzv7+@`JbnSvL8w?TDY9a5jnr^)~GI) zL(y3@I<9E6+Mk)-1PNsO>jn)=_*+==V*|6_JD)ZBriW9!WuUDP1pp&7AaWctr3y^h@{ zHJzhy*T5Lb*2a=F?!^C#UmEeSW4nPW2^0d?#1>TqKnrf=&-a* iqRt~2`(LB;RF$;EnkSN)tQ=K9mJWy~@EFoK^wd(n3Mm%= literal 0 HcmV?d00001 diff --git a/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj b/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj index 171927e..4251784 100644 --- a/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj +++ b/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj @@ -36,6 +36,12 @@ bin\Release\AttributeRouting.Web.Http.WebHost.xml 1591, 1587 + + true + + + AttributeRouting.snk + True @@ -96,6 +102,7 @@ + diff --git a/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.snk b/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.snk new file mode 100644 index 0000000000000000000000000000000000000000..b5ab91c2a292360331d78f208218a036f4b58158 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097#FKi0%iVOb>nhNTb6oAn@5Wl-Ypa0MC z&rDP{JXmd`5);UKJA^@|66-Ob?WIsSQ%#9b_o2Xg_1BpO#uo$&t=(0dj{P*0R}Asp zDyc?k!X3Zri**9ilE&i2G1i5b$vfMzrf>TMu~|v{-;DM~s=tr{lV9kV6$CIMtBsx| zvT9T8aIH*7HI6l>;HvEqXg*VIF}ri6a7A%m(MyOju+e|(p=QA|rx@h5F@d2aoHzYk zm1pVD2cU~M3@gf4fk8^GJTf|O_D>dq(oXGTp(eiqu8^`f%;Jq*YsdXS060F+_Gc#; zcswtNqp0vUEo*mjq1b}FZftx2mc_^kA$<6G1dSaJ7F9u)ZgNC}CXyijzMbY<_cZhq zDz7bMV6(Pz=8%8ubhA~~k(k~8*E;*}q68KuCQg&PF1>6yy~^$~&i@ixmC4H<1Nl=4 zEhfs5>uO0fvvm}*qFm8g6-1e8g7nUFlAiJYzv7+@`JbnSvL8w?TDY9a5jnr^)~GI) zL(y3@I<9E6+Mk)-1PNsO>jn)=_*+==V*|6_JD)ZBriW9!WuUDP1pp&7AaWctr3y^h@{ zHJzhy*T5Lb*2a=F?!^C#UmEeSW4nPW2^0d?#1>TqKnrf=&-a* iqRt~2`(LB;RF$;EnkSN)tQ=K9mJWy~@EFoK^wd(n3Mm%= literal 0 HcmV?d00001 diff --git a/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj b/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj index 178248c..7a09a9f 100644 --- a/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj +++ b/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj @@ -36,6 +36,12 @@ bin\Release\AttributeRouting.Web.Http.xml 1591, 1587 + + true + + + AttributeRouting.snk + ..\packages\Newtonsoft.Json.4.5.8\lib\net40\Newtonsoft.Json.dll @@ -94,6 +100,7 @@ + diff --git a/src/AttributeRouting.Web.Http/AttributeRouting.snk b/src/AttributeRouting.Web.Http/AttributeRouting.snk new file mode 100644 index 0000000000000000000000000000000000000000..b5ab91c2a292360331d78f208218a036f4b58158 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097#FKi0%iVOb>nhNTb6oAn@5Wl-Ypa0MC z&rDP{JXmd`5);UKJA^@|66-Ob?WIsSQ%#9b_o2Xg_1BpO#uo$&t=(0dj{P*0R}Asp zDyc?k!X3Zri**9ilE&i2G1i5b$vfMzrf>TMu~|v{-;DM~s=tr{lV9kV6$CIMtBsx| zvT9T8aIH*7HI6l>;HvEqXg*VIF}ri6a7A%m(MyOju+e|(p=QA|rx@h5F@d2aoHzYk zm1pVD2cU~M3@gf4fk8^GJTf|O_D>dq(oXGTp(eiqu8^`f%;Jq*YsdXS060F+_Gc#; zcswtNqp0vUEo*mjq1b}FZftx2mc_^kA$<6G1dSaJ7F9u)ZgNC}CXyijzMbY<_cZhq zDz7bMV6(Pz=8%8ubhA~~k(k~8*E;*}q68KuCQg&PF1>6yy~^$~&i@ixmC4H<1Nl=4 zEhfs5>uO0fvvm}*qFm8g6-1e8g7nUFlAiJYzv7+@`JbnSvL8w?TDY9a5jnr^)~GI) zL(y3@I<9E6+Mk)-1PNsO>jn)=_*+==V*|6_JD)ZBriW9!WuUDP1pp&7AaWctr3y^h@{ zHJzhy*T5Lb*2a=F?!^C#UmEeSW4nPW2^0d?#1>TqKnrf=&-a* iqRt~2`(LB;RF$;EnkSN)tQ=K9mJWy~@EFoK^wd(n3Mm%= literal 0 HcmV?d00001 diff --git a/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj b/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj index 7b2f73a..5554645 100644 --- a/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj +++ b/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj @@ -36,6 +36,12 @@ bin\Release\AttributeRouting.Web.Mvc.xml 1591, 1587 + + true + + + AttributeRouting.snk + @@ -79,6 +85,9 @@ AttributeRouting + + + + --> \ No newline at end of file diff --git a/src/AttributeRouting.Specs/Tests/Extensions/UrlHelperExtensionTests.cs b/src/AttributeRouting.Specs/Tests/Extensions/UrlHelperExtensionTests.cs new file mode 100644 index 0000000..dfb9810 --- /dev/null +++ b/src/AttributeRouting.Specs/Tests/Extensions/UrlHelperExtensionTests.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Specialized; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using AttributeRouting.Specs.Subjects; +using AttributeRouting.Web.Mvc; +using AttributeRouting.Web.Mvc.Extensions; +using Moq; +using NUnit.Framework; + +namespace AttributeRouting.Specs.Tests.Extensions +{ + public class UrlHelperExtensionTests + { + private UrlHelper GetUrlHelper(RouteCollection routes, string host, string schema = "http") + { + Mock httpContextMock = MockBuilder.BuildMockHttpContext(r => + { + r.SetupGet(x => x.Url).Returns(new Uri(schema + "://" + host, UriKind.Absolute)); + r.SetupGet(x => x.Headers).Returns(new NameValueCollection { { "host", host } }); + }); + + return new UrlHelper(new RequestContext(httpContextMock.Object, new RouteData()), routes); + } + + [Test] + public void Local_Host_Action_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => config.AddRoutesFromController()); + var helper = GetUrlHelper(routes, "localhost"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("Index", "Subdomain", "Users"); + Console.WriteLine(path); + Assert.AreEqual("http://users.localhost/", path); + } + + + [Test] + public void Top_Level_Domain_Action_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => config.AddRoutesFromController()); + var helper = GetUrlHelper(routes, "example.com"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("Index", "Subdomain", "Users"); + Console.WriteLine(path); + Assert.AreEqual("http://users.example.com/", path); + } + + [Test] + public void Second_Level_Domain_Action_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => config.AddRoutesFromController()); + var helper = GetUrlHelper(routes, "example.co.uk"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("Index", "Subdomain", "Users"); + Console.WriteLine(path); + Assert.AreEqual("http://users.example.co.uk/", path); + } + + [Test] + public void Top_Level_Domain_With_Subdomain_Action_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + }); + var helper = GetUrlHelper(routes, "private.example.com"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("Index", "Subdomain", "Users"); + Console.WriteLine(path); + Assert.AreEqual("http://users.example.com/", path); + } + + [Test] + public void Second_Level_Domain_With_Subdomain_Action_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + }); + var helper = GetUrlHelper(routes, "private.example.co.ok"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("Index", "Subdomain", "Users"); + Console.WriteLine(path); + Assert.AreEqual("http://users.example.co.ok/", path); + } + + [Test] + public void Current_Subdomain_Action_To_Same_Subdomain_Will_Only_Be_The_Relative_Url() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => config.AddRoutesFromController()); + var helper = GetUrlHelper(routes, "users.example.com"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("Index", "Subdomain", "Users"); + Console.WriteLine(path); + Assert.AreEqual("/", path); + } + + [Test] + public void Current_Subdomain_Action_To_No_Subdomain_And_Default_Subdomain_Will_Return_Full_Url() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + }); + var helper = GetUrlHelper(routes, "users.example.com"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("AnyVerb", "StandardUsage"); + Console.WriteLine(path); + Assert.AreEqual("http://www.example.com/AnyVerb", path); + } + + [Test] + public void Current_Subdomain_Action_To_No_Subdomain_And_Custom_Default_Subdomain_Will_Return_Full_Url() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + config.DefaultSubdomain = "xyz"; + }); + var helper = GetUrlHelper(routes, "users.example.com"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("AnyVerb", "StandardUsage"); + Console.WriteLine(path); + Assert.AreEqual("http://xyz.example.com/AnyVerb", path); + } + + [Test] + public void Current_Subdomain_Action_To_No_Subdomain_And_No_Default_Subdomain_Will_Return_Full_Url() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + config.DefaultSubdomain = ""; + }); + var helper = GetUrlHelper(routes, "users.example.com"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("AnyVerb", "StandardUsage"); + Console.WriteLine(path); + Assert.AreEqual("http://example.com/AnyVerb", path); + } + + [Test] + public void Local_Host_With_Subdomain_Action_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + }); + var helper = GetUrlHelper(routes, "private.localhost"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("Index", "Subdomain", "Users"); + Console.WriteLine(path); + Assert.AreEqual("http://users.localhost/", path); + } + + + [Test] + public void Local_Host_With_Subdomain_Action_To_Non_Area_Method_Will_Return_Full_Url_Without_Default_Subdomain() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + }); + var helper = GetUrlHelper(routes, "users.localhost"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("Index", "StandardUsage"); + Console.WriteLine(path); + Assert.AreEqual("http://localhost/", path); + } + + + [Test] + public void HTTP_URL_Will_Change_To_HTTPS() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => config.AddRoutesFromController()); + var helper = GetUrlHelper(routes, "localhost"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("Index", "Subdomain", new {area = "Users"}, "https"); + Console.WriteLine(path); + Assert.AreEqual("https://users.localhost/", path); + } + + [Test] + public void HTTPS_URL_Will_Change_To_HTTP() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => config.AddRoutesFromController()); + var helper = GetUrlHelper(routes, "localhost", "https"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("Index", "Subdomain", new { area = "Users" }, "http"); + Console.WriteLine(path); + Assert.AreEqual("http://users.localhost/", path); + } + + [Test] + public void Host_With_Port_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => config.AddRoutesFromController()); + var helper = GetUrlHelper(routes, "example.com:81"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainAction("Index", "Subdomain", new { area = "Users" }); + Console.WriteLine(path); + Assert.AreEqual("http://users.example.com:81/", path); + } + } +} \ No newline at end of file diff --git a/src/AttributeRouting.Tests.Web/Views/Web.config b/src/AttributeRouting.Tests.Web/Views/Web.config index 4c30ef2..b38c926 100644 --- a/src/AttributeRouting.Tests.Web/Views/Web.config +++ b/src/AttributeRouting.Tests.Web/Views/Web.config @@ -1,58 +1,60 @@ - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj b/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj index 5554645..d598b27 100644 --- a/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj +++ b/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj @@ -1,100 +1,101 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {4604C450-EBF8-4A7F-BD3A-A24655C41FA4} - Library - Properties - AttributeRouting.Web.Mvc - AttributeRouting.Web.Mvc - v4.0 - 512 - ..\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\AttributeRouting.Web.Mvc.xml - 1591, 1587 - - - true - - - AttributeRouting.snk - - - - - - - - - - - - - - - - - SharedAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - {C91C065B-A821-4890-9F31-F9E245D804D1} - AttributeRouting.Web - - - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4} - AttributeRouting - - - - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {4604C450-EBF8-4A7F-BD3A-A24655C41FA4} + Library + Properties + AttributeRouting.Web.Mvc + AttributeRouting.Web.Mvc + v4.0 + 512 + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\AttributeRouting.Web.Mvc.xml + 1591, 1587 + + + true + + + AttributeRouting.snk + + + + + + + + + + + + + + + + + SharedAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + {C91C065B-A821-4890-9F31-F9E245D804D1} + AttributeRouting.Web + + + {871A79CF-C705-4C6B-8938-F9AA1E02AEA4} + AttributeRouting + + + + + + + + --> \ No newline at end of file diff --git a/src/AttributeRouting.Web.Mvc/Extensions/UrlHelperExtensions.cs b/src/AttributeRouting.Web.Mvc/Extensions/UrlHelperExtensions.cs new file mode 100644 index 0000000..8f233ac --- /dev/null +++ b/src/AttributeRouting.Web.Mvc/Extensions/UrlHelperExtensions.cs @@ -0,0 +1,276 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Web.Mvc; +using System.Web.Routing; +using AttributeRouting.Framework; +using AttributeRouting.Helpers; + +namespace AttributeRouting.Web.Mvc.Extensions +{ + public static class UrlHelperExtensions + { + public static string SubdomainAction(this UrlHelper urlHelper, string actionName, + RouteValueDictionary routeValues) + { + string baseUrl = GetDomainBase(urlHelper, null, actionName, null, routeValues); + return BuildUri(baseUrl, urlHelper.Action(actionName, routeValues)); + } + + public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName, + string areaName = "") + { + var routeValues = new RouteValueDictionary {{"area", areaName}}; + string baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, routeValues); + return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); + } + + public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName, + object routeValues) + { + string baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, + new RouteValueDictionary(routeValues)); + return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); + } + + public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName, + RouteValueDictionary routeValues) + { + string baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, routeValues); + return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); + } + + public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName, + object routeValues, string protocol) + { + string baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, + new RouteValueDictionary(routeValues), protocol); + return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, object routeValues) + { + return SubdomainRouteUrl(urlHelper, null, routeValues); + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, RouteValueDictionary routeValues) + { + return SubdomainRouteUrl(urlHelper, null, routeValues); + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName) + { + return SubdomainRouteUrl(urlHelper, routeName, (object) null); + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName, object routeValues) + { + return SubdomainRouteUrl(urlHelper, routeName, routeValues, null); + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName, + RouteValueDictionary routeValues) + { + string baseUrl = GetDomainBase(urlHelper, routeName, null, null, routeValues); + return BuildUri(baseUrl, urlHelper.RouteUrl(routeName, routeValues)); + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName, object routeValues, + string protocol) + { + var r = new RouteValueDictionary(routeValues); + string baseUrl = GetDomainBase(urlHelper, routeName, null, null, r, protocol); + return BuildUri(baseUrl, urlHelper.RouteUrl(routeName, routeValues)); + } + + /// + /// Gets the domain base url. + /// + /// The URL helper. + /// Name of the route. + /// Name of the action. + /// Name of the controller. + /// The route values. + /// The schema. + /// + private static string GetDomainBase(UrlHelper urlHelper, string routeName, string actionName, + string controllerName, RouteValueDictionary routeValues, + string schema = null) + { + //baseUrl is the return value which by default is an empty string + string baseUrl = string.Empty; + + //just a shortcut variable so we don't have to have this the below line eight million times + Uri currentUrl = urlHelper.RequestContext.HttpContext.Request.Url; + + //get the desired route using a copy of MS internal methods + RouteValueDictionary values = MergeRouteValues(actionName, controllerName, urlHelper.RequestContext.RouteData.Values, routeValues, true); + VirtualPathData virtualPathForArea = urlHelper.RouteCollection.GetVirtualPathForArea(urlHelper.RequestContext, routeName, values); + if (virtualPathForArea == null) + { + return baseUrl; + } + + //if not a AttributeRoute or the current url is funny then nothing we can do so move on + var route = virtualPathForArea.Route as IAttributeRoute; + if (route != null && currentUrl != null && !string.IsNullOrWhiteSpace(currentUrl.OriginalString)) + { + //get the current domain via the current Uri. + string host = currentUrl.GetLeftPart(UriPartial.Authority).Replace(currentUrl.GetLeftPart(UriPartial.Scheme), string.Empty); + + IPAddress ip; + //if the port exists in the host remove it so that we don't run into trouble with the IPAddress parsing + if (host.Contains(":")) + { + host = host.Substring(0, host.IndexOf(":", StringComparison.Ordinal)); + } + + //if an ip then no point in building a subdomain for it + if (IPAddress.TryParse(host, out ip)) + { + return string.Empty; + } + + //save the current host for comparisons later + string currentHost = host; + + //which protocol schema to use. i.e. http, https + string scheme = schema ?? currentUrl.Scheme; + + //what is the current port. needed if non-standard + int port = currentUrl.Port; + + //is the port a standard port? + bool useDefaultPort = port == 80 || port == 443; + + //need the default subdomain incase we are going from one subdomain method to a non-subdomain method + string defaultSubdomain = string.Empty; + if (route.DataTokens.Any(x => x.Key.Equals("defaultSubdomain"))) + { + defaultSubdomain = route.DataTokens["defaultSubdomain"].ToString(); + } + + //if the host contains a dot we need to remove the subdomain if it is in the list of ones to remove + if (host.Contains(".")) + { + //get all registered subdomains + List subdomains = + urlHelper.RouteCollection.Where(x => x is IAttributeRoute) + .Cast() + .Where(x => x.Subdomain.HasValue()) + .Select(x => x.Subdomain) + .Distinct() + .ToList(); + + //also add the default subdomain from the current route + if (!string.IsNullOrWhiteSpace(defaultSubdomain) && !subdomains.Contains(defaultSubdomain)) + { + subdomains.Add(defaultSubdomain); + } + + //strips subdomain information off of current (if matching a current one) + string subDomainSection = host.Split('.')[0]; + foreach ( + string subdomain in + subdomains.Where( + subdomain => + subDomainSection.Equals(subdomain, StringComparison.InvariantCultureIgnoreCase))) + { + host = host.Replace(string.Format("{0}.", subdomain), string.Empty); + break; + } + } + + //if not a subdomain then don't build the url. instead build it to the default subdomain + if (!string.IsNullOrWhiteSpace(route.Subdomain)) + { + //if host already starts with subdomain then skip building the url + if (!currentHost.StartsWith(route.Subdomain)) + { + baseUrl = string.Format("{0}://{1}.{2}", scheme, route.Subdomain, host); + } + } + else + { + //no subdomain so we should add the default subdomain unless it is localhost + var gotoSubdomain = string.Empty; + if (!host.Equals("localhost", StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrWhiteSpace(defaultSubdomain)) + { + gotoSubdomain = string.Format("{0}.", defaultSubdomain); + } + baseUrl = string.Format("{0}://{1}{2}", scheme, gotoSubdomain, host); + } + + //not using a standard port so if the baseurl has a value then append on the port + if (!string.IsNullOrWhiteSpace(baseUrl) && !useDefaultPort) + { + baseUrl = string.Format("{0}:{1}", baseUrl, port); + } + } + return baseUrl; + } + + private static string BuildUri(string baseUrl, string relativeUrl) + { + if (string.IsNullOrWhiteSpace(baseUrl) && string.IsNullOrWhiteSpace(relativeUrl)) + { + return string.Empty; + } + if (string.IsNullOrWhiteSpace(baseUrl)) + { + return relativeUrl; + } + if (string.IsNullOrWhiteSpace(relativeUrl)) + { + return baseUrl; + } + if (!relativeUrl.StartsWith("/")) + { + relativeUrl = "/" + relativeUrl; + } + return string.Format("{0}{1}", baseUrl, relativeUrl); + } + + public static RouteValueDictionary GetRouteValues(RouteValueDictionary routeValues) + { + return routeValues == null ? new RouteValueDictionary() : new RouteValueDictionary(routeValues); + } + + public static RouteValueDictionary MergeRouteValues(string actionName, string controllerName, + RouteValueDictionary implicitRouteValues, + RouteValueDictionary routeValues, + bool includeImplicitMvcValues) + { + var routeValueDictionary = new RouteValueDictionary(); + if (includeImplicitMvcValues) + { + object obj; + if (implicitRouteValues != null && implicitRouteValues.TryGetValue("action", out obj)) + { + routeValueDictionary["action"] = obj; + } + if (implicitRouteValues != null && implicitRouteValues.TryGetValue("controller", out obj)) + { + routeValueDictionary["controller"] = obj; + } + } + if (routeValues != null) + { + foreach (var keyValuePair in GetRouteValues(routeValues)) + { + routeValueDictionary[keyValuePair.Key] = keyValuePair.Value; + } + } + if (actionName != null) + { + routeValueDictionary["action"] = actionName; + } + if (controllerName != null) + { + routeValueDictionary["controller"] = controllerName; + } + return routeValueDictionary; + } + } +} \ No newline at end of file diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 793277b..ee8a6d8 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -1,504 +1,505 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using AttributeRouting.Framework.Factories; -using AttributeRouting.Helpers; - -namespace AttributeRouting.Framework -{ - /// - /// Creates objects according to the - /// options set in implementations of . - /// - public class RouteBuilder - { - private readonly ConfigurationBase _configuration; - private readonly IAttributeRouteFactory _routeFactory; - private readonly IRouteConstraintFactory _routeConstraintFactory; - private readonly IParameterFactory _parameterFactory; - - public RouteBuilder(ConfigurationBase configuration) - { - if (configuration == null) throw new ArgumentNullException("configuration"); - - _configuration = configuration; - _routeFactory = configuration.AttributeRouteFactory; - _routeConstraintFactory = configuration.RouteConstraintFactory; - _parameterFactory = configuration.ParameterFactory; - } - - /// - /// Yields all the routes to register in the route table. - /// - public IEnumerable BuildAllRoutes() - { - var routeReflector = new RouteReflector(_configuration); - var routeSpecs = routeReflector.BuildRouteSpecifications().ToList(); - - var mappedSubdomains = (from s in routeSpecs - where s.Subdomain.HasValue() - select s.Subdomain).Distinct().ToList(); - - foreach (var routeSpec in routeSpecs) - { - foreach (var route in Build(routeSpec)) - { - route.MappedSubdomains = mappedSubdomains; - yield return route; - } - } - } - - private IEnumerable Build(RouteSpecification routeSpec) - { - var defaults = CreateRouteDefaults(routeSpec); - var constraints = CreateRouteConstraints(routeSpec); - var dataTokens = CreateRouteDataTokens(routeSpec); - var url = CreateRouteUrl(defaults, routeSpec); - - var routes = _routeFactory.CreateAttributeRoutes(url, defaults, constraints, dataTokens); - - foreach (var route in routes) - { - var routeName = CreateRouteName(routeSpec); - if (routeName.HasValue()) - { - route.RouteName = routeName; - route.DataTokens.Add("routeName", routeName); - } - - route.Translations = CreateRouteTranslations(routeSpec); - route.Subdomain = routeSpec.Subdomain; - route.UseLowercaseRoute = routeSpec.UseLowercaseRoute; - route.PreserveCaseForUrlParameters = routeSpec.PreserveCaseForUrlParameters; - route.AppendTrailingSlash = routeSpec.AppendTrailingSlash; - - // Yield the default route first - yield return route; - - // Then yield the translations - if (route.Translations == null) - yield break; - - foreach (var translation in route.Translations) - { - // Backreference the default route. - translation.DefaultRouteContainer = route; - - yield return translation; - } - } - } - - private string CreateRouteName(RouteSpecification routeSpec) - { - if (routeSpec.RouteName.HasValue()) - return routeSpec.RouteName; - - return _configuration.AutoGenerateRouteNames ? _configuration.RouteNameBuilder(routeSpec) : null; - } - - private string CreateRouteUrl(IDictionary defaults, RouteSpecification routeSpec) - { - return CreateRouteUrl(routeSpec.RouteUrl, - routeSpec.RoutePrefixUrl, - routeSpec.AreaUrl, - defaults, - routeSpec); - } - - private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUrl, IDictionary defaults, RouteSpecification routeSpec) - { - var tokenizedUrl = BuildTokenizedUrl(routeUrl, routePrefix, areaUrl, routeSpec); - var tokenizedPath = RemoveQueryString(tokenizedUrl); - var detokenizedPath = DetokenizeUrl(tokenizedPath); - - var urlParameterNames = GetUrlParameterContents(detokenizedPath).ToList(); - - var urlBuilder = new StringBuilder(detokenizedPath); - - // Replace {controller} URL param with default value. - if (urlParameterNames.Any(n => n.ValueEquals("controller"))) - urlBuilder.Replace("{controller}", (string)defaults["controller"]); - - // Replace {action} URL param with default value. - if (urlParameterNames.Any(n => n.ValueEquals("action"))) - urlBuilder.Replace("{action}", (string)defaults["action"]); - - // Explicitly defined area routes are not valid - if (urlParameterNames.Any(n => n.ValueEquals("area"))) - throw new AttributeRoutingException( - "{area} url parameters are not allowed. Specify the area name by using the RouteAreaAttribute."); - - // If we are lowercasing routes, then lowercase everything but the route params - var lower = routeSpec.UseLowercaseRoute.GetValueOrDefault(_configuration.UseLowercaseRoutes); - if (lower) - { - for (var i = 0; i < urlBuilder.Length; i++) - { - var c = urlBuilder[i]; - if (Char.IsUpper(c)) - { - urlBuilder[i] = Char.ToLower(c); - } - else if (c == '{') - { - while (urlBuilder[i] != '}' && i < urlBuilder.Length) - i++; - } - } - } - - return urlBuilder.ToString().Trim('/'); - } - - private IDictionary CreateRouteDefaults(RouteSpecification routeSpec) - { - var defaults = new Dictionary - { - { "controller", routeSpec.ControllerName }, - { "action", routeSpec.ActionName } - }; - - var urlParameters = GetUrlParameterContents(routeSpec.RouteUrl).ToList(); - - // Inspect the url for optional parameters, specified with a trailing ? - foreach (var parameter in urlParameters.Where(p => p.EndsWith("?"))) - { - var parameterName = parameter.TrimEnd('?'); - - if (parameterName.Contains(':')) - parameterName = parameterName.Substring(0, parameterName.IndexOf(':')); - - if (defaults.ContainsKey(parameterName)) - continue; - - defaults.Add(parameterName, _parameterFactory.Optional()); - } - - // Inline defaults - foreach (var parameter in urlParameters.Where(p => p.Contains('='))) - { - var indexOfEquals = parameter.IndexOf('='); - var parameterName = parameter.Substring(0, indexOfEquals); - - if (parameterName.Contains(':')) - parameterName = parameterName.Substring(0, parameterName.IndexOf(':')); - - if (defaults.ContainsKey(parameterName)) - continue; - - var defaultValue = parameter.Substring(indexOfEquals + 1, parameter.Length - indexOfEquals - 1); - defaults.Add(parameterName, defaultValue); - } - - return defaults; - } - - private IDictionary CreateRouteConstraints(RouteSpecification routeSpec) - { - var constraints = new Dictionary(); - - // Default constraints - if (routeSpec.HttpMethods.Any()) - constraints.Add("inboundHttpMethod", _routeConstraintFactory.CreateInboundHttpMethodConstraint(routeSpec.HttpMethods)); - - // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. - var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); - var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); - - // Need to keep track of query params. - // Can do this by detokenizing URL (which strips query), - // and then taking all the URL parameters except those from the path part of the URL. - var pathOnlyUrl = RemoveQueryString(tokenizedUrl); - var pathOnlyUrlParameters = GetUrlParameterContents(pathOnlyUrl); - var queryStringParameters = urlParameters.Except(pathOnlyUrlParameters).ToList(); - - // Inline constraints - foreach (var parameter in urlParameters) - { - // Keep track of whether this param is optional or in the querystring, - // because we wrap the final constraint if so. - var parameterIsOptional = parameter.EndsWith("?"); - var parameterIsInQueryString = queryStringParameters.Contains(parameter); - - // If this is a path parameter and doesn't have a constraint, then skip it. - if (!parameterIsInQueryString && !parameter.Contains(":")) - continue; - - // Strip off everything related to defaults. - var cleanParameter = parameter.TrimEnd('?').Split('=').FirstOrDefault(); - - var sections = cleanParameter.SplitAndTrim(":"); - var parameterName = sections.First(); - - // Do not override default or legacy inline constraints - if (constraints.ContainsKey(parameterName)) - continue; - - // Add constraints for each inline definition - var inlineConstraints = new List(); - var constraintDefinitions = sections.Skip(1); - foreach (var definition in constraintDefinitions) - { - string constraintName; - object constraint; - - if (Regex.IsMatch(definition, @"^.*\(.*\)$")) - { - // Constraint of the form "firstName:string(50)" - var indexOfOpenParen = definition.IndexOf('('); - constraintName = definition.Substring(0, indexOfOpenParen); - - // Parse constraint params. - // NOTE: Splitting on commas only applies to non-regex constraints. - var constraintParamsRaw = definition.Substring(indexOfOpenParen + 1, definition.Length - indexOfOpenParen - 2); - var constraintParams = constraintName.ValueEquals("regex") - ? new[] {constraintParamsRaw} - : constraintParamsRaw.SplitAndTrim(","); - - constraint = _routeConstraintFactory.CreateInlineRouteConstraint(constraintName, constraintParams); - } - else - { - // Constraint of the form "id:int" - constraintName = definition; - constraint = _routeConstraintFactory.CreateInlineRouteConstraint(constraintName); - } - - if (constraint == null) - throw new AttributeRoutingException( - "Could not find an available inline constraint for \"{0}\".".FormatWith(constraintName)); - - inlineConstraints.Add(constraint); - } - - // Apply the constraint to the parameter, and wrap constraints in the following priority: - object finalConstraint; - - // 1. If more than one constraint, wrap in a compound constraint. - if (inlineConstraints.Count > 1) - { - finalConstraint = _routeConstraintFactory.CreateCompoundRouteConstraint(inlineConstraints.ToArray()); - } - else - { - finalConstraint = inlineConstraints.FirstOrDefault(); - } - - // 2. If the constraint is in the querystring, wrap in a query string constraint. - if (parameterIsInQueryString) - { - finalConstraint = _routeConstraintFactory.CreateQueryStringRouteConstraint(finalConstraint); - } - - // 3. If the constraint is optional, wrap in an optional constraint. - if (parameterIsOptional) - { - finalConstraint = _routeConstraintFactory.CreateOptionalRouteConstraint(finalConstraint); - } - - constraints.Add(parameterName, finalConstraint); - - } // ... go to next parameter - - // Globally configured constraints - var detokenizedPrefixedUrl = DetokenizeUrl(tokenizedUrl); - var urlParameterNames = GetUrlParameterContents(detokenizedPrefixedUrl).ToList(); - foreach (var defaultConstraint in _configuration.DefaultRouteConstraints) - { - var pattern = defaultConstraint.Key; - - foreach (var urlParameterName in urlParameterNames.Where(n => Regex.IsMatch(n, pattern))) - { - if (constraints.ContainsKey(urlParameterName)) - continue; - - constraints.Add(urlParameterName, defaultConstraint.Value); - } - } - - return constraints; - } - - private string BuildTokenizedUrl(string routeUrl, string routePrefixUrl, string areaUrl, RouteSpecification routeSpec) - { - var delimitedUrl = routeUrl + "/"; - - // Prepend prefix if available - if (routePrefixUrl.HasValue() && !routeSpec.IgnoreRoutePrefix) - { - var delimitedRoutePrefix = routePrefixUrl + "/"; - if (!delimitedUrl.StartsWith(delimitedRoutePrefix)) - delimitedUrl = delimitedRoutePrefix + delimitedUrl; - } - - // Prepend area url if available - if (areaUrl.HasValue() && !routeSpec.IgnoreAreaUrl) - { - var delimitedAreaUrl = areaUrl + "/"; - if (!delimitedUrl.StartsWith(delimitedAreaUrl)) - delimitedUrl = delimitedAreaUrl + delimitedUrl; - } - - return delimitedUrl.Trim('/'); - } - - private IDictionary CreateRouteDataTokens(RouteSpecification routeSpec) - { - var dataTokens = new Dictionary - { - { "namespaces", new[] { routeSpec.ControllerType.Namespace } }, - { "actionMethod", routeSpec.ActionMethod } - }; - - if (routeSpec.HttpMethods.Any()) - { - dataTokens.Add("httpMethods", routeSpec.HttpMethods); - } - - if (routeSpec.AreaName.HasValue()) - { - dataTokens.Add("area", routeSpec.AreaName); - dataTokens.Add("UseNamespaceFallback", false); - } - - return dataTokens; - } - - private static string DetokenizeUrl(string url) - { - var patterns = new List - { - @"(?<=\{)\?", // leading question mark (used to specify optional param) - @"\?(?=\})", // trailing question mark (used to specify optional param) - @"\(.*?\)(?=\})", // stuff inside parens (used to specify inline regex route constraint) - @"\:(.*?)(\(.*?\))?((?=\})|(?=\?\}))", // new inline constraint syntax - @"(?<=\{.*)=.*?(?=\})", // equals and value (used to specify inline parameter default value) - }; - - return Regex.Replace(url, String.Join("|", patterns), ""); - } - - private static string RemoveQueryString(string url) - { - // Must honor ? in regex expressions and as used to specify optional params, - // So run through the url chars and fast forward when inside a url param (eg: {...}) - for (int i = 0, length = url.Length; i < length; i++) - { - var c = url[i]; - if (c == '?') - { - return url.Substring(0, i); - } - - // Fast-forward past url param contents - if (c == '{') - { - while (url[i] != '}' && i < length) - i++; - } - } - - return url; - } - - private IEnumerable CreateRouteTranslations(RouteSpecification routeSpec) - { - // If no translation provider, then get out of here. - if (!_configuration.TranslationProviders.Any()) - yield break; - - // Merge all the culture names from the various providers. - var cultureNames = _configuration.GetTranslationProviderCultureNames(); - - // Built the route translations, - // choosing the first available translated route component from among the providers - foreach (var cultureName in cultureNames) - { - string translatedRouteUrl = null, - translatedRoutePrefix = null, - translatedAreaUrl = null; - - foreach (var provider in _configuration.TranslationProviders) - { - translatedRouteUrl = translatedRouteUrl ?? provider.TranslateRouteUrl(cultureName, routeSpec); - translatedRoutePrefix = translatedRoutePrefix ?? provider.TranslateRoutePrefix(cultureName, routeSpec); - translatedAreaUrl = translatedAreaUrl ?? provider.TranslateAreaUrl(cultureName, routeSpec); - } - - // If nothing is translated, then bail. - if (translatedRouteUrl == null && translatedRoutePrefix == null && translatedAreaUrl == null) - continue; - - //********************************************* - // Otherwise, build a translated route - - var defaults = CreateRouteDefaults(routeSpec); - var constraints = CreateRouteConstraints(routeSpec); - var dataTokens = CreateRouteDataTokens(routeSpec); - var routeUrl = CreateRouteUrl(translatedRouteUrl ?? routeSpec.RouteUrl, - translatedRoutePrefix ?? routeSpec.RoutePrefixUrl, - translatedAreaUrl ?? routeSpec.AreaUrl, - defaults, - routeSpec); - - var translatedRoutes = _routeFactory.CreateAttributeRoutes(routeUrl, defaults, constraints, dataTokens); - - foreach (var translatedRoute in translatedRoutes) - { - var routeName = CreateRouteName(routeSpec); - if (routeName != null) - { - translatedRoute.RouteName = routeName; - translatedRoute.DataTokens.Add("routeName", routeName); - } - - translatedRoute.CultureName = cultureName; - translatedRoute.DataTokens.Add("cultureName", cultureName); - - yield return translatedRoute; - } - } - } - - private static IEnumerable GetUrlParameterContents(string url) - { - if (!url.HasValue()) - yield break; - - var urlSegments = url.SplitAndTrim(new[] { "/" }); - foreach (var urlSegment in urlSegments) - { - // Find an open curly in the segment, and if none, then move on to the next. - var iOpenCurly = urlSegment.IndexOf('{'); - if (iOpenCurly == -1) continue; - - var i = iOpenCurly + 1; - while (i < urlSegment.Length) - { - if (urlSegment[i] == '}') - { - // If we find the closing curly, then yield the contents of the url param. - yield return urlSegment.Substring(iOpenCurly + 1, i - iOpenCurly - 1); - - // Fast-forward to the next open curly brace. - iOpenCurly = urlSegment.IndexOf('{', i); - if (iOpenCurly == -1) break; - i = iOpenCurly; - } - else if (urlSegment[i] == '{') - { - // If we find an inner open curly (due to inner regex patterns), - // then fast-forward beyond it. - i = urlSegment.IndexOf('}', i); - } - - i++; - } - } - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using AttributeRouting.Framework.Factories; +using AttributeRouting.Helpers; + +namespace AttributeRouting.Framework +{ + /// + /// Creates objects according to the + /// options set in implementations of . + /// + public class RouteBuilder + { + private readonly ConfigurationBase _configuration; + private readonly IAttributeRouteFactory _routeFactory; + private readonly IRouteConstraintFactory _routeConstraintFactory; + private readonly IParameterFactory _parameterFactory; + + public RouteBuilder(ConfigurationBase configuration) + { + if (configuration == null) throw new ArgumentNullException("configuration"); + + _configuration = configuration; + _routeFactory = configuration.AttributeRouteFactory; + _routeConstraintFactory = configuration.RouteConstraintFactory; + _parameterFactory = configuration.ParameterFactory; + } + + /// + /// Yields all the routes to register in the route table. + /// + public IEnumerable BuildAllRoutes() + { + var routeReflector = new RouteReflector(_configuration); + var routeSpecs = routeReflector.BuildRouteSpecifications().ToList(); + + var mappedSubdomains = (from s in routeSpecs + where s.Subdomain.HasValue() + select s.Subdomain).Distinct().ToList(); + + foreach (var routeSpec in routeSpecs) + { + foreach (var route in Build(routeSpec)) + { + route.MappedSubdomains = mappedSubdomains; + yield return route; + } + } + } + + private IEnumerable Build(RouteSpecification routeSpec) + { + var defaults = CreateRouteDefaults(routeSpec); + var constraints = CreateRouteConstraints(routeSpec); + var dataTokens = CreateRouteDataTokens(routeSpec); + var url = CreateRouteUrl(defaults, routeSpec); + + var routes = _routeFactory.CreateAttributeRoutes(url, defaults, constraints, dataTokens); + + foreach (var route in routes) + { + var routeName = CreateRouteName(routeSpec); + if (routeName.HasValue()) + { + route.RouteName = routeName; + route.DataTokens.Add("routeName", routeName); + } + + route.Translations = CreateRouteTranslations(routeSpec); + route.Subdomain = routeSpec.Subdomain; + route.UseLowercaseRoute = routeSpec.UseLowercaseRoute; + route.PreserveCaseForUrlParameters = routeSpec.PreserveCaseForUrlParameters; + route.AppendTrailingSlash = routeSpec.AppendTrailingSlash; + + // Yield the default route first + yield return route; + + // Then yield the translations + if (route.Translations == null) + yield break; + + foreach (var translation in route.Translations) + { + // Backreference the default route. + translation.DefaultRouteContainer = route; + + yield return translation; + } + } + } + + private string CreateRouteName(RouteSpecification routeSpec) + { + if (routeSpec.RouteName.HasValue()) + return routeSpec.RouteName; + + return _configuration.AutoGenerateRouteNames ? _configuration.RouteNameBuilder(routeSpec) : null; + } + + private string CreateRouteUrl(IDictionary defaults, RouteSpecification routeSpec) + { + return CreateRouteUrl(routeSpec.RouteUrl, + routeSpec.RoutePrefixUrl, + routeSpec.AreaUrl, + defaults, + routeSpec); + } + + private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUrl, IDictionary defaults, RouteSpecification routeSpec) + { + var tokenizedUrl = BuildTokenizedUrl(routeUrl, routePrefix, areaUrl, routeSpec); + var tokenizedPath = RemoveQueryString(tokenizedUrl); + var detokenizedPath = DetokenizeUrl(tokenizedPath); + + var urlParameterNames = GetUrlParameterContents(detokenizedPath).ToList(); + + var urlBuilder = new StringBuilder(detokenizedPath); + + // Replace {controller} URL param with default value. + if (urlParameterNames.Any(n => n.ValueEquals("controller"))) + urlBuilder.Replace("{controller}", (string)defaults["controller"]); + + // Replace {action} URL param with default value. + if (urlParameterNames.Any(n => n.ValueEquals("action"))) + urlBuilder.Replace("{action}", (string)defaults["action"]); + + // Explicitly defined area routes are not valid + if (urlParameterNames.Any(n => n.ValueEquals("area"))) + throw new AttributeRoutingException( + "{area} url parameters are not allowed. Specify the area name by using the RouteAreaAttribute."); + + // If we are lowercasing routes, then lowercase everything but the route params + var lower = routeSpec.UseLowercaseRoute.GetValueOrDefault(_configuration.UseLowercaseRoutes); + if (lower) + { + for (var i = 0; i < urlBuilder.Length; i++) + { + var c = urlBuilder[i]; + if (Char.IsUpper(c)) + { + urlBuilder[i] = Char.ToLower(c); + } + else if (c == '{') + { + while (urlBuilder[i] != '}' && i < urlBuilder.Length) + i++; + } + } + } + + return urlBuilder.ToString().Trim('/'); + } + + private IDictionary CreateRouteDefaults(RouteSpecification routeSpec) + { + var defaults = new Dictionary + { + { "controller", routeSpec.ControllerName }, + { "action", routeSpec.ActionName } + }; + + var urlParameters = GetUrlParameterContents(routeSpec.RouteUrl).ToList(); + + // Inspect the url for optional parameters, specified with a trailing ? + foreach (var parameter in urlParameters.Where(p => p.EndsWith("?"))) + { + var parameterName = parameter.TrimEnd('?'); + + if (parameterName.Contains(':')) + parameterName = parameterName.Substring(0, parameterName.IndexOf(':')); + + if (defaults.ContainsKey(parameterName)) + continue; + + defaults.Add(parameterName, _parameterFactory.Optional()); + } + + // Inline defaults + foreach (var parameter in urlParameters.Where(p => p.Contains('='))) + { + var indexOfEquals = parameter.IndexOf('='); + var parameterName = parameter.Substring(0, indexOfEquals); + + if (parameterName.Contains(':')) + parameterName = parameterName.Substring(0, parameterName.IndexOf(':')); + + if (defaults.ContainsKey(parameterName)) + continue; + + var defaultValue = parameter.Substring(indexOfEquals + 1, parameter.Length - indexOfEquals - 1); + defaults.Add(parameterName, defaultValue); + } + + return defaults; + } + + private IDictionary CreateRouteConstraints(RouteSpecification routeSpec) + { + var constraints = new Dictionary(); + + // Default constraints + if (routeSpec.HttpMethods.Any()) + constraints.Add("inboundHttpMethod", _routeConstraintFactory.CreateInboundHttpMethodConstraint(routeSpec.HttpMethods)); + + // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. + var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); + var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); + + // Need to keep track of query params. + // Can do this by detokenizing URL (which strips query), + // and then taking all the URL parameters except those from the path part of the URL. + var pathOnlyUrl = RemoveQueryString(tokenizedUrl); + var pathOnlyUrlParameters = GetUrlParameterContents(pathOnlyUrl); + var queryStringParameters = urlParameters.Except(pathOnlyUrlParameters).ToList(); + + // Inline constraints + foreach (var parameter in urlParameters) + { + // Keep track of whether this param is optional or in the querystring, + // because we wrap the final constraint if so. + var parameterIsOptional = parameter.EndsWith("?"); + var parameterIsInQueryString = queryStringParameters.Contains(parameter); + + // If this is a path parameter and doesn't have a constraint, then skip it. + if (!parameterIsInQueryString && !parameter.Contains(":")) + continue; + + // Strip off everything related to defaults. + var cleanParameter = parameter.TrimEnd('?').Split('=').FirstOrDefault(); + + var sections = cleanParameter.SplitAndTrim(":"); + var parameterName = sections.First(); + + // Do not override default or legacy inline constraints + if (constraints.ContainsKey(parameterName)) + continue; + + // Add constraints for each inline definition + var inlineConstraints = new List(); + var constraintDefinitions = sections.Skip(1); + foreach (var definition in constraintDefinitions) + { + string constraintName; + object constraint; + + if (Regex.IsMatch(definition, @"^.*\(.*\)$")) + { + // Constraint of the form "firstName:string(50)" + var indexOfOpenParen = definition.IndexOf('('); + constraintName = definition.Substring(0, indexOfOpenParen); + + // Parse constraint params. + // NOTE: Splitting on commas only applies to non-regex constraints. + var constraintParamsRaw = definition.Substring(indexOfOpenParen + 1, definition.Length - indexOfOpenParen - 2); + var constraintParams = constraintName.ValueEquals("regex") + ? new[] {constraintParamsRaw} + : constraintParamsRaw.SplitAndTrim(","); + + constraint = _routeConstraintFactory.CreateInlineRouteConstraint(constraintName, constraintParams); + } + else + { + // Constraint of the form "id:int" + constraintName = definition; + constraint = _routeConstraintFactory.CreateInlineRouteConstraint(constraintName); + } + + if (constraint == null) + throw new AttributeRoutingException( + "Could not find an available inline constraint for \"{0}\".".FormatWith(constraintName)); + + inlineConstraints.Add(constraint); + } + + // Apply the constraint to the parameter, and wrap constraints in the following priority: + object finalConstraint; + + // 1. If more than one constraint, wrap in a compound constraint. + if (inlineConstraints.Count > 1) + { + finalConstraint = _routeConstraintFactory.CreateCompoundRouteConstraint(inlineConstraints.ToArray()); + } + else + { + finalConstraint = inlineConstraints.FirstOrDefault(); + } + + // 2. If the constraint is in the querystring, wrap in a query string constraint. + if (parameterIsInQueryString) + { + finalConstraint = _routeConstraintFactory.CreateQueryStringRouteConstraint(finalConstraint); + } + + // 3. If the constraint is optional, wrap in an optional constraint. + if (parameterIsOptional) + { + finalConstraint = _routeConstraintFactory.CreateOptionalRouteConstraint(finalConstraint); + } + + constraints.Add(parameterName, finalConstraint); + + } // ... go to next parameter + + // Globally configured constraints + var detokenizedPrefixedUrl = DetokenizeUrl(tokenizedUrl); + var urlParameterNames = GetUrlParameterContents(detokenizedPrefixedUrl).ToList(); + foreach (var defaultConstraint in _configuration.DefaultRouteConstraints) + { + var pattern = defaultConstraint.Key; + + foreach (var urlParameterName in urlParameterNames.Where(n => Regex.IsMatch(n, pattern))) + { + if (constraints.ContainsKey(urlParameterName)) + continue; + + constraints.Add(urlParameterName, defaultConstraint.Value); + } + } + + return constraints; + } + + private string BuildTokenizedUrl(string routeUrl, string routePrefixUrl, string areaUrl, RouteSpecification routeSpec) + { + var delimitedUrl = routeUrl + "/"; + + // Prepend prefix if available + if (routePrefixUrl.HasValue() && !routeSpec.IgnoreRoutePrefix) + { + var delimitedRoutePrefix = routePrefixUrl + "/"; + if (!delimitedUrl.StartsWith(delimitedRoutePrefix)) + delimitedUrl = delimitedRoutePrefix + delimitedUrl; + } + + // Prepend area url if available + if (areaUrl.HasValue() && !routeSpec.IgnoreAreaUrl) + { + var delimitedAreaUrl = areaUrl + "/"; + if (!delimitedUrl.StartsWith(delimitedAreaUrl)) + delimitedUrl = delimitedAreaUrl + delimitedUrl; + } + + return delimitedUrl.Trim('/'); + } + + private IDictionary CreateRouteDataTokens(RouteSpecification routeSpec) + { + var dataTokens = new Dictionary + { + { "namespaces", new[] { routeSpec.ControllerType.Namespace } }, + { "actionMethod", routeSpec.ActionMethod }, + { "defaultSubdomain", _configuration.DefaultSubdomain} + }; + + if (routeSpec.HttpMethods.Any()) + { + dataTokens.Add("httpMethods", routeSpec.HttpMethods); + } + + if (routeSpec.AreaName.HasValue()) + { + dataTokens.Add("area", routeSpec.AreaName); + dataTokens.Add("UseNamespaceFallback", false); + } + + return dataTokens; + } + + private static string DetokenizeUrl(string url) + { + var patterns = new List + { + @"(?<=\{)\?", // leading question mark (used to specify optional param) + @"\?(?=\})", // trailing question mark (used to specify optional param) + @"\(.*?\)(?=\})", // stuff inside parens (used to specify inline regex route constraint) + @"\:(.*?)(\(.*?\))?((?=\})|(?=\?\}))", // new inline constraint syntax + @"(?<=\{.*)=.*?(?=\})", // equals and value (used to specify inline parameter default value) + }; + + return Regex.Replace(url, String.Join("|", patterns), ""); + } + + private static string RemoveQueryString(string url) + { + // Must honor ? in regex expressions and as used to specify optional params, + // So run through the url chars and fast forward when inside a url param (eg: {...}) + for (int i = 0, length = url.Length; i < length; i++) + { + var c = url[i]; + if (c == '?') + { + return url.Substring(0, i); + } + + // Fast-forward past url param contents + if (c == '{') + { + while (url[i] != '}' && i < length) + i++; + } + } + + return url; + } + + private IEnumerable CreateRouteTranslations(RouteSpecification routeSpec) + { + // If no translation provider, then get out of here. + if (!_configuration.TranslationProviders.Any()) + yield break; + + // Merge all the culture names from the various providers. + var cultureNames = _configuration.GetTranslationProviderCultureNames(); + + // Built the route translations, + // choosing the first available translated route component from among the providers + foreach (var cultureName in cultureNames) + { + string translatedRouteUrl = null, + translatedRoutePrefix = null, + translatedAreaUrl = null; + + foreach (var provider in _configuration.TranslationProviders) + { + translatedRouteUrl = translatedRouteUrl ?? provider.TranslateRouteUrl(cultureName, routeSpec); + translatedRoutePrefix = translatedRoutePrefix ?? provider.TranslateRoutePrefix(cultureName, routeSpec); + translatedAreaUrl = translatedAreaUrl ?? provider.TranslateAreaUrl(cultureName, routeSpec); + } + + // If nothing is translated, then bail. + if (translatedRouteUrl == null && translatedRoutePrefix == null && translatedAreaUrl == null) + continue; + + //********************************************* + // Otherwise, build a translated route + + var defaults = CreateRouteDefaults(routeSpec); + var constraints = CreateRouteConstraints(routeSpec); + var dataTokens = CreateRouteDataTokens(routeSpec); + var routeUrl = CreateRouteUrl(translatedRouteUrl ?? routeSpec.RouteUrl, + translatedRoutePrefix ?? routeSpec.RoutePrefixUrl, + translatedAreaUrl ?? routeSpec.AreaUrl, + defaults, + routeSpec); + + var translatedRoutes = _routeFactory.CreateAttributeRoutes(routeUrl, defaults, constraints, dataTokens); + + foreach (var translatedRoute in translatedRoutes) + { + var routeName = CreateRouteName(routeSpec); + if (routeName != null) + { + translatedRoute.RouteName = routeName; + translatedRoute.DataTokens.Add("routeName", routeName); + } + + translatedRoute.CultureName = cultureName; + translatedRoute.DataTokens.Add("cultureName", cultureName); + + yield return translatedRoute; + } + } + } + + private static IEnumerable GetUrlParameterContents(string url) + { + if (!url.HasValue()) + yield break; + + var urlSegments = url.SplitAndTrim(new[] { "/" }); + foreach (var urlSegment in urlSegments) + { + // Find an open curly in the segment, and if none, then move on to the next. + var iOpenCurly = urlSegment.IndexOf('{'); + if (iOpenCurly == -1) continue; + + var i = iOpenCurly + 1; + while (i < urlSegment.Length) + { + if (urlSegment[i] == '}') + { + // If we find the closing curly, then yield the contents of the url param. + yield return urlSegment.Substring(iOpenCurly + 1, i - iOpenCurly - 1); + + // Fast-forward to the next open curly brace. + iOpenCurly = urlSegment.IndexOf('{', i); + if (iOpenCurly == -1) break; + i = iOpenCurly; + } + else if (urlSegment[i] == '{') + { + // If we find an inner open curly (due to inner regex patterns), + // then fast-forward beyond it. + i = urlSegment.IndexOf('}', i); + } + + i++; + } + } + } + } } \ No newline at end of file From fdeca1f012e56d32fcad3243d8cee0b2daf25793 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Thu, 31 Jan 2013 08:59:48 -0700 Subject: [PATCH 31/97] reorganized sln and .nuget folder locations --- {src/.nuget => .nuget}/NuGet.Config | 0 {src/.nuget => .nuget}/NuGet.exe | Bin {src/.nuget => .nuget}/NuGet.targets | 0 ...tributeRouting.sln => AttributeRouting.sln | 29 ++-- src/AttributeRouting.VS2010.sln | 155 ------------------ 5 files changed, 15 insertions(+), 169 deletions(-) rename {src/.nuget => .nuget}/NuGet.Config (100%) rename {src/.nuget => .nuget}/NuGet.exe (100%) rename {src/.nuget => .nuget}/NuGet.targets (100%) rename src/AttributeRouting.sln => AttributeRouting.sln (87%) delete mode 100644 src/AttributeRouting.VS2010.sln diff --git a/src/.nuget/NuGet.Config b/.nuget/NuGet.Config similarity index 100% rename from src/.nuget/NuGet.Config rename to .nuget/NuGet.Config diff --git a/src/.nuget/NuGet.exe b/.nuget/NuGet.exe similarity index 100% rename from src/.nuget/NuGet.exe rename to .nuget/NuGet.exe diff --git a/src/.nuget/NuGet.targets b/.nuget/NuGet.targets similarity index 100% rename from src/.nuget/NuGet.targets rename to .nuget/NuGet.targets diff --git a/src/AttributeRouting.sln b/AttributeRouting.sln similarity index 87% rename from src/AttributeRouting.sln rename to AttributeRouting.sln index 3fdfcd2..f094917 100644 --- a/src/AttributeRouting.sln +++ b/AttributeRouting.sln @@ -3,6 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{38404E6F-56AC-458E-B0A6-3620FEA10A5C}" ProjectSection(SolutionItems) = preProject + .nuget\NuGet.Config = .nuget\NuGet.Config .nuget\NuGet.exe = .nuget\NuGet.exe .nuget\NuGet.targets = .nuget\NuGet.targets EndProjectSection @@ -11,28 +12,32 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{840281A8 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{DE34F753-6251-493F-902B-D520D655F69A}" ProjectSection(SolutionItems) = preProject - SharedAssemblyInfo.cs = SharedAssemblyInfo.cs + src\SharedAssemblyInfo.cs = src\SharedAssemblyInfo.cs EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting", "AttributeRouting\AttributeRouting.csproj", "{871A79CF-C705-4C6B-8938-F9AA1E02AEA4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting", "src\AttributeRouting\AttributeRouting.csproj", "{871A79CF-C705-4C6B-8938-F9AA1E02AEA4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Specs", "AttributeRouting.Specs\AttributeRouting.Specs.csproj", "{E832A82B-2302-4113-83E3-261446E22C87}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Specs", "src\AttributeRouting.Specs\AttributeRouting.Specs.csproj", "{E832A82B-2302-4113-83E3-261446E22C87}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Tests.Web", "AttributeRouting.Tests.Web\AttributeRouting.Tests.Web.csproj", "{486087C6-E95B-4FE2-8263-7B6B2DF81888}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Tests.Web", "src\AttributeRouting.Tests.Web\AttributeRouting.Tests.Web.csproj", "{486087C6-E95B-4FE2-8263-7B6B2DF81888}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web.Http.WebHost", "AttributeRouting.Web.Http.WebHost\AttributeRouting.Web.Http.WebHost.csproj", "{A018FEC5-45F8-44FB-BB6C-33697B418434}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web.Http.WebHost", "src\AttributeRouting.Web.Http.WebHost\AttributeRouting.Web.Http.WebHost.csproj", "{A018FEC5-45F8-44FB-BB6C-33697B418434}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web", "AttributeRouting.Web\AttributeRouting.Web.csproj", "{C91C065B-A821-4890-9F31-F9E245D804D1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web", "src\AttributeRouting.Web\AttributeRouting.Web.csproj", "{C91C065B-A821-4890-9F31-F9E245D804D1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web.Mvc", "AttributeRouting.Web.Mvc\AttributeRouting.Web.Mvc.csproj", "{4604C450-EBF8-4A7F-BD3A-A24655C41FA4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web.Mvc", "src\AttributeRouting.Web.Mvc\AttributeRouting.Web.Mvc.csproj", "{4604C450-EBF8-4A7F-BD3A-A24655C41FA4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web.Http.SelfHost", "AttributeRouting.Web.Http.SelfHost\AttributeRouting.Web.Http.SelfHost.csproj", "{246F7AEC-9429-4FBB-8747-92A3F025C711}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web.Http.SelfHost", "src\AttributeRouting.Web.Http.SelfHost\AttributeRouting.Web.Http.SelfHost.csproj", "{246F7AEC-9429-4FBB-8747-92A3F025C711}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Tests.SelfHost", "AttributeRouting.Tests.SelfHost\AttributeRouting.Tests.SelfHost.csproj", "{0D2A13E6-A092-402E-B8FE-035F3A620B86}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Tests.SelfHost", "src\AttributeRouting.Tests.SelfHost\AttributeRouting.Tests.SelfHost.csproj", "{0D2A13E6-A092-402E-B8FE-035F3A620B86}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web.Http", "AttributeRouting.Web.Http\AttributeRouting.Web.Http.csproj", "{CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web.Http", "src\AttributeRouting.Web.Http\AttributeRouting.Web.Http.csproj", "{CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}" EndProject Global + GlobalSection(SubversionScc) = preSolution + Svn-Managed = True + Manager = AnkhSVN - Subversion Support for Visual Studio + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|Mixed Platforms = Debug|Mixed Platforms @@ -141,8 +146,4 @@ Global {486087C6-E95B-4FE2-8263-7B6B2DF81888} = {840281A8-8870-417F-9B24-ED0E866608A2} {0D2A13E6-A092-402E-B8FE-035F3A620B86} = {840281A8-8870-417F-9B24-ED0E866608A2} EndGlobalSection - GlobalSection(SubversionScc) = preSolution - Svn-Managed = True - Manager = AnkhSVN - Subversion Support for Visual Studio - EndGlobalSection EndGlobal diff --git a/src/AttributeRouting.VS2010.sln b/src/AttributeRouting.VS2010.sln deleted file mode 100644 index d7f2999..0000000 --- a/src/AttributeRouting.VS2010.sln +++ /dev/null @@ -1,155 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting", "AttributeRouting\AttributeRouting.csproj", "{871A79CF-C705-4C6B-8938-F9AA1E02AEA4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Specs", "AttributeRouting.Specs\AttributeRouting.Specs.csproj", "{E832A82B-2302-4113-83E3-261446E22C87}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Tests.Web", "AttributeRouting.Tests.Web\AttributeRouting.Tests.Web.csproj", "{486087C6-E95B-4FE2-8263-7B6B2DF81888}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{38404E6F-56AC-458E-B0A6-3620FEA10A5C}" - ProjectSection(SolutionItems) = preProject - .nuget\NuGet.exe = .nuget\NuGet.exe - .nuget\NuGet.targets = .nuget\NuGet.targets - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web.Http.WebHost", "AttributeRouting.Web.Http.WebHost\AttributeRouting.Web.Http.WebHost.csproj", "{A018FEC5-45F8-44FB-BB6C-33697B418434}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web", "AttributeRouting.Web\AttributeRouting.Web.csproj", "{C91C065B-A821-4890-9F31-F9E245D804D1}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{840281A8-8870-417F-9B24-ED0E866608A2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web.Mvc", "AttributeRouting.Web.Mvc\AttributeRouting.Web.Mvc.csproj", "{4604C450-EBF8-4A7F-BD3A-A24655C41FA4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web.Http.SelfHost", "AttributeRouting.Web.Http.SelfHost\AttributeRouting.Web.Http.SelfHost.csproj", "{246F7AEC-9429-4FBB-8747-92A3F025C711}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Tests.SelfHost", "AttributeRouting.Tests.SelfHost\AttributeRouting.Tests.SelfHost.csproj", "{0D2A13E6-A092-402E-B8FE-035F3A620B86}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AttributeRouting.Web.Http", "AttributeRouting.Web.Http\AttributeRouting.Web.Http.csproj", "{CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{DE34F753-6251-493F-902B-D520D655F69A}" - ProjectSection(SolutionItems) = preProject - AttributeRouting.Shared.nuspec = AttributeRouting.Shared.nuspec - SharedAssemblyInfo.cs = SharedAssemblyInfo.cs - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{01EC4F0A-A27A-45B8-9DB5-BA447A9F4A6F}" - ProjectSection(SolutionItems) = preProject - ..\build.bat = ..\build.bat - ..\build.xml = ..\build.xml - EndProjectSection -EndProject -Global - GlobalSection(SubversionScc) = preSolution - Svn-Managed = True - Manager = AnkhSVN - Subversion Support for Visual Studio - EndGlobalSection - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|Mixed Platforms = Debug|Mixed Platforms - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|Mixed Platforms = Release|Mixed Platforms - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4}.Debug|x86.ActiveCfg = Debug|Any CPU - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4}.Release|Any CPU.Build.0 = Release|Any CPU - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4}.Release|x86.ActiveCfg = Release|Any CPU - {E832A82B-2302-4113-83E3-261446E22C87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E832A82B-2302-4113-83E3-261446E22C87}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E832A82B-2302-4113-83E3-261446E22C87}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {E832A82B-2302-4113-83E3-261446E22C87}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {E832A82B-2302-4113-83E3-261446E22C87}.Debug|x86.ActiveCfg = Debug|Any CPU - {E832A82B-2302-4113-83E3-261446E22C87}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E832A82B-2302-4113-83E3-261446E22C87}.Release|Any CPU.Build.0 = Release|Any CPU - {E832A82B-2302-4113-83E3-261446E22C87}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {E832A82B-2302-4113-83E3-261446E22C87}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {E832A82B-2302-4113-83E3-261446E22C87}.Release|x86.ActiveCfg = Release|Any CPU - {486087C6-E95B-4FE2-8263-7B6B2DF81888}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {486087C6-E95B-4FE2-8263-7B6B2DF81888}.Debug|Any CPU.Build.0 = Debug|Any CPU - {486087C6-E95B-4FE2-8263-7B6B2DF81888}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {486087C6-E95B-4FE2-8263-7B6B2DF81888}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {486087C6-E95B-4FE2-8263-7B6B2DF81888}.Debug|x86.ActiveCfg = Debug|Any CPU - {486087C6-E95B-4FE2-8263-7B6B2DF81888}.Release|Any CPU.ActiveCfg = Release|Any CPU - {486087C6-E95B-4FE2-8263-7B6B2DF81888}.Release|Any CPU.Build.0 = Release|Any CPU - {486087C6-E95B-4FE2-8263-7B6B2DF81888}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {486087C6-E95B-4FE2-8263-7B6B2DF81888}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {486087C6-E95B-4FE2-8263-7B6B2DF81888}.Release|x86.ActiveCfg = Release|Any CPU - {A018FEC5-45F8-44FB-BB6C-33697B418434}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A018FEC5-45F8-44FB-BB6C-33697B418434}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A018FEC5-45F8-44FB-BB6C-33697B418434}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {A018FEC5-45F8-44FB-BB6C-33697B418434}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {A018FEC5-45F8-44FB-BB6C-33697B418434}.Debug|x86.ActiveCfg = Debug|Any CPU - {A018FEC5-45F8-44FB-BB6C-33697B418434}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A018FEC5-45F8-44FB-BB6C-33697B418434}.Release|Any CPU.Build.0 = Release|Any CPU - {A018FEC5-45F8-44FB-BB6C-33697B418434}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {A018FEC5-45F8-44FB-BB6C-33697B418434}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {A018FEC5-45F8-44FB-BB6C-33697B418434}.Release|x86.ActiveCfg = Release|Any CPU - {C91C065B-A821-4890-9F31-F9E245D804D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C91C065B-A821-4890-9F31-F9E245D804D1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C91C065B-A821-4890-9F31-F9E245D804D1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {C91C065B-A821-4890-9F31-F9E245D804D1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {C91C065B-A821-4890-9F31-F9E245D804D1}.Debug|x86.ActiveCfg = Debug|Any CPU - {C91C065B-A821-4890-9F31-F9E245D804D1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C91C065B-A821-4890-9F31-F9E245D804D1}.Release|Any CPU.Build.0 = Release|Any CPU - {C91C065B-A821-4890-9F31-F9E245D804D1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {C91C065B-A821-4890-9F31-F9E245D804D1}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {C91C065B-A821-4890-9F31-F9E245D804D1}.Release|x86.ActiveCfg = Release|Any CPU - {4604C450-EBF8-4A7F-BD3A-A24655C41FA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4604C450-EBF8-4A7F-BD3A-A24655C41FA4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4604C450-EBF8-4A7F-BD3A-A24655C41FA4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {4604C450-EBF8-4A7F-BD3A-A24655C41FA4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {4604C450-EBF8-4A7F-BD3A-A24655C41FA4}.Debug|x86.ActiveCfg = Debug|Any CPU - {4604C450-EBF8-4A7F-BD3A-A24655C41FA4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4604C450-EBF8-4A7F-BD3A-A24655C41FA4}.Release|Any CPU.Build.0 = Release|Any CPU - {4604C450-EBF8-4A7F-BD3A-A24655C41FA4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {4604C450-EBF8-4A7F-BD3A-A24655C41FA4}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {4604C450-EBF8-4A7F-BD3A-A24655C41FA4}.Release|x86.ActiveCfg = Release|Any CPU - {246F7AEC-9429-4FBB-8747-92A3F025C711}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {246F7AEC-9429-4FBB-8747-92A3F025C711}.Debug|Any CPU.Build.0 = Debug|Any CPU - {246F7AEC-9429-4FBB-8747-92A3F025C711}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {246F7AEC-9429-4FBB-8747-92A3F025C711}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {246F7AEC-9429-4FBB-8747-92A3F025C711}.Debug|x86.ActiveCfg = Debug|Any CPU - {246F7AEC-9429-4FBB-8747-92A3F025C711}.Release|Any CPU.ActiveCfg = Release|Any CPU - {246F7AEC-9429-4FBB-8747-92A3F025C711}.Release|Any CPU.Build.0 = Release|Any CPU - {246F7AEC-9429-4FBB-8747-92A3F025C711}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {246F7AEC-9429-4FBB-8747-92A3F025C711}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {246F7AEC-9429-4FBB-8747-92A3F025C711}.Release|x86.ActiveCfg = Release|Any CPU - {0D2A13E6-A092-402E-B8FE-035F3A620B86}.Debug|Any CPU.ActiveCfg = Debug|x86 - {0D2A13E6-A092-402E-B8FE-035F3A620B86}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 - {0D2A13E6-A092-402E-B8FE-035F3A620B86}.Debug|Mixed Platforms.Build.0 = Debug|x86 - {0D2A13E6-A092-402E-B8FE-035F3A620B86}.Debug|x86.ActiveCfg = Debug|x86 - {0D2A13E6-A092-402E-B8FE-035F3A620B86}.Debug|x86.Build.0 = Debug|x86 - {0D2A13E6-A092-402E-B8FE-035F3A620B86}.Release|Any CPU.ActiveCfg = Release|x86 - {0D2A13E6-A092-402E-B8FE-035F3A620B86}.Release|Mixed Platforms.ActiveCfg = Release|x86 - {0D2A13E6-A092-402E-B8FE-035F3A620B86}.Release|Mixed Platforms.Build.0 = Release|x86 - {0D2A13E6-A092-402E-B8FE-035F3A620B86}.Release|x86.ActiveCfg = Release|x86 - {0D2A13E6-A092-402E-B8FE-035F3A620B86}.Release|x86.Build.0 = Release|x86 - {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}.Debug|x86.ActiveCfg = Debug|Any CPU - {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}.Release|Any CPU.Build.0 = Release|Any CPU - {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D}.Release|x86.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {486087C6-E95B-4FE2-8263-7B6B2DF81888} = {840281A8-8870-417F-9B24-ED0E866608A2} - {E832A82B-2302-4113-83E3-261446E22C87} = {840281A8-8870-417F-9B24-ED0E866608A2} - {0D2A13E6-A092-402E-B8FE-035F3A620B86} = {840281A8-8870-417F-9B24-ED0E866608A2} - EndGlobalSection -EndGlobal From d198cb4d1c01edd33f71b057da1920e6b8420b0c Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Thu, 31 Jan 2013 09:48:33 -0700 Subject: [PATCH 32/97] Fixed broken build and project references due to 2368438e2dfee872cfd5d35087c67cb3c98099d4. --- default.ps1 | 2 +- .../AttributeRouting.Specs.csproj | 513 +++++++++--------- src/AttributeRouting.Specs/packages.config | 10 +- .../AttributeRouting.Tests.SelfHost.csproj | 13 +- .../packages.config | 4 +- .../AttributeRouting.Tests.Web.csproj | 15 +- src/AttributeRouting.Tests.Web/Web.config | 2 +- .../packages.config | 2 +- .../AttributeRouting.Web.Http.SelfHost.csproj | 13 +- .../packages.config | 4 +- .../AttributeRouting.Web.Http.WebHost.csproj | 15 +- .../packages.config | 2 +- .../AttributeRouting.Web.Http.csproj | 11 +- src/AttributeRouting.Web.Http/packages.config | 2 +- .../Extensions/UrlHelperExtensions.cs | 215 +++----- src/SharedAssemblyInfo.cs | 24 +- 16 files changed, 394 insertions(+), 453 deletions(-) diff --git a/default.ps1 b/default.ps1 index a3054e2..612e6aa 100644 --- a/default.ps1 +++ b/default.ps1 @@ -37,7 +37,7 @@ Task CreateSharedAssemblyInfo { } Task Rebuild { - $solution_file = "$source_dir\AttributeRouting.sln" + $solution_file = "$base_dir\AttributeRouting.sln" Write-Host "Building $solution_file" -ForegroundColor Green Exec { msbuild $solution_file /t:Rebuild /p:Configuration=Release /p:OutDir=$bin_dir /v:minimal /nologo } } diff --git a/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj b/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj index 76b6429..3cf15a9 100644 --- a/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj +++ b/src/AttributeRouting.Specs/AttributeRouting.Specs.csproj @@ -1,263 +1,264 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {E832A82B-2302-4113-83E3-261446E22C87} - Library - Properties - AttributeRouting.Specs - AttributeRouting.Specs - v4.0 - 512 - ..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - True - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - - False - ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll - - - ..\packages\MvcContrib.Mvc3.TestHelper-ci.3.0.100.0\lib\MvcContrib.TestHelper.dll - - - False - ..\packages\Newtonsoft.Json.4.5.9\lib\net40\Newtonsoft.Json.dll - - - False - ..\packages\NUnit.2.6.0.12054\lib\nunit.framework.dll - - - ..\packages\RhinoMocks.3.6.1\lib\net\Rhino.Mocks.dll - - - - - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll - - - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll - - - - - ..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.20710.0\lib\net40\System.Web.Http.WebHost.dll - - - - - - - - - - ..\packages\SpecFlow.1.9.0\lib\net35\TechTalk.SpecFlow.dll - - - - - - True - True - InheritingActions.feature - - - True - True - Logging.feature - - - True - True - RouteAreas.feature - - - True - True - RoutePrefixes.feature - - - True - True - RouteConstraints.feature - - - True - True - RouteConventions.feature - - - True - True - RouteDefaults.feature - - - True - True - RoutePrecedence.feature - - - True - True - StandardUsage.feature - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SpecFlowSingleFileGenerator - InheritingActions.feature.cs - - - SpecFlowSingleFileGenerator - Logging.feature.cs - - - SpecFlowSingleFileGenerator - RouteAreas.feature.cs - - - SpecFlowSingleFileGenerator - RoutePrefixes.feature.cs - - - SpecFlowSingleFileGenerator - RouteConstraints.feature.cs - - - SpecFlowSingleFileGenerator - RouteConventions.feature.cs - - - SpecFlowSingleFileGenerator - RouteDefaults.feature.cs - - - SpecFlowSingleFileGenerator - RoutePrecedence.feature.cs - - - SpecFlowSingleFileGenerator - StandardUsage.feature.cs - - - Designer - - - - - {246F7AEC-9429-4FBB-8747-92A3F025C711} - AttributeRouting.Web.Http.SelfHost - - - {A018FEC5-45F8-44FB-BB6C-33697B418434} - AttributeRouting.Web.Http.WebHost - - - {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D} - AttributeRouting.Web.Http - - - {C91C065B-A821-4890-9F31-F9E245D804D1} - AttributeRouting.Web - - - {4604C450-EBF8-4A7F-BD3A-A24655C41FA4} - AttributeRouting.Web.Mvc - - - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4} - AttributeRouting - - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {E832A82B-2302-4113-83E3-261446E22C87} + Library + Properties + AttributeRouting.Specs + AttributeRouting.Specs + v4.0 + 512 + ..\..\src\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + True + ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + + False + ..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll + + + False + ..\..\packages\MvcContrib.Mvc3.TestHelper-ci.3.0.100.0\lib\MvcContrib.TestHelper.dll + + + False + ..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll + + + False + ..\..\packages\NUnit.2.6.2\lib\nunit.framework.dll + + + False + ..\..\packages\RhinoMocks.3.6.1\lib\net\Rhino.Mocks.dll + + + + + ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll + + + ..\..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll + + + ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + + + + + ..\..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll + + + ..\..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.20710.0\lib\net40\System.Web.Http.WebHost.dll + + + + + + + + + + False + ..\..\packages\SpecFlow.1.9.0\lib\net35\TechTalk.SpecFlow.dll + + + + + + True + True + InheritingActions.feature + + + True + True + Logging.feature + + + True + True + RouteAreas.feature + + + True + True + RoutePrefixes.feature + + + True + True + RouteConstraints.feature + + + True + True + RouteConventions.feature + + + True + True + RouteDefaults.feature + + + True + True + RoutePrecedence.feature + + + True + True + StandardUsage.feature + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SpecFlowSingleFileGenerator + InheritingActions.feature.cs + + + SpecFlowSingleFileGenerator + Logging.feature.cs + + + SpecFlowSingleFileGenerator + RouteAreas.feature.cs + + + SpecFlowSingleFileGenerator + RoutePrefixes.feature.cs + + + SpecFlowSingleFileGenerator + RouteConstraints.feature.cs + + + SpecFlowSingleFileGenerator + RouteConventions.feature.cs + + + SpecFlowSingleFileGenerator + RouteDefaults.feature.cs + + + SpecFlowSingleFileGenerator + RoutePrecedence.feature.cs + + + SpecFlowSingleFileGenerator + StandardUsage.feature.cs + + + + + + {246F7AEC-9429-4FBB-8747-92A3F025C711} + AttributeRouting.Web.Http.SelfHost + + + {A018FEC5-45F8-44FB-BB6C-33697B418434} + AttributeRouting.Web.Http.WebHost + + + {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D} + AttributeRouting.Web.Http + + + {C91C065B-A821-4890-9F31-F9E245D804D1} + AttributeRouting.Web + + + {4604C450-EBF8-4A7F-BD3A-A24655C41FA4} + AttributeRouting.Web.Mvc + + + {871A79CF-C705-4C6B-8938-F9AA1E02AEA4} + AttributeRouting + + + + + + --> \ No newline at end of file diff --git a/src/AttributeRouting.Specs/packages.config b/src/AttributeRouting.Specs/packages.config index a5c49be..af16271 100644 --- a/src/AttributeRouting.Specs/packages.config +++ b/src/AttributeRouting.Specs/packages.config @@ -6,10 +6,10 @@ - - - - - + + + + + \ No newline at end of file diff --git a/src/AttributeRouting.Tests.SelfHost/AttributeRouting.Tests.SelfHost.csproj b/src/AttributeRouting.Tests.SelfHost/AttributeRouting.Tests.SelfHost.csproj index 41df9f5..e3bb70d 100644 --- a/src/AttributeRouting.Tests.SelfHost/AttributeRouting.Tests.SelfHost.csproj +++ b/src/AttributeRouting.Tests.SelfHost/AttributeRouting.Tests.SelfHost.csproj @@ -42,24 +42,25 @@ - ..\packages\Newtonsoft.Json.4.5.8\lib\net40\Newtonsoft.Json.dll + False + ..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll + ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll - ..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll + ..\..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll - ..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll + ..\..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll - ..\packages\Microsoft.AspNet.WebApi.SelfHost.4.0.20710.0\lib\net40\System.Web.Http.SelfHost.dll + ..\..\packages\Microsoft.AspNet.WebApi.SelfHost.4.0.20918.0\lib\net40\System.Web.Http.SelfHost.dll diff --git a/src/AttributeRouting.Tests.SelfHost/packages.config b/src/AttributeRouting.Tests.SelfHost/packages.config index 1ba9e00..6c98029 100644 --- a/src/AttributeRouting.Tests.SelfHost/packages.config +++ b/src/AttributeRouting.Tests.SelfHost/packages.config @@ -2,7 +2,7 @@ - + - + \ No newline at end of file diff --git a/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj b/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj index b36614c..52d0ae9 100644 --- a/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj +++ b/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj @@ -57,22 +57,23 @@ True - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - ..\packages\Newtonsoft.Json.4.5.8\lib\net40\Newtonsoft.Json.dll + False + ..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll + ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll - ..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll + ..\..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll @@ -88,10 +89,10 @@ - ..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll + ..\..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll - ..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.20710.0\lib\net40\System.Web.Http.WebHost.dll + ..\..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.20710.0\lib\net40\System.Web.Http.WebHost.dll diff --git a/src/AttributeRouting.Tests.Web/Web.config b/src/AttributeRouting.Tests.Web/Web.config index 9b153a4..032c063 100644 --- a/src/AttributeRouting.Tests.Web/Web.config +++ b/src/AttributeRouting.Tests.Web/Web.config @@ -73,4 +73,4 @@ - + \ No newline at end of file diff --git a/src/AttributeRouting.Tests.Web/packages.config b/src/AttributeRouting.Tests.Web/packages.config index ba9ece5..76f6b20 100644 --- a/src/AttributeRouting.Tests.Web/packages.config +++ b/src/AttributeRouting.Tests.Web/packages.config @@ -5,5 +5,5 @@ - + \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj index 2d38ede..6b2456d 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj +++ b/src/AttributeRouting.Web.Http.SelfHost/AttributeRouting.Web.Http.SelfHost.csproj @@ -44,24 +44,25 @@ - ..\packages\Newtonsoft.Json.4.5.8\lib\net40\Newtonsoft.Json.dll + False + ..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll + ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll - ..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll + ..\..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll - ..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll + ..\..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll - ..\packages\Microsoft.AspNet.WebApi.SelfHost.4.0.20710.0\lib\net40\System.Web.Http.SelfHost.dll + ..\..\packages\Microsoft.AspNet.WebApi.SelfHost.4.0.20918.0\lib\net40\System.Web.Http.SelfHost.dll diff --git a/src/AttributeRouting.Web.Http.SelfHost/packages.config b/src/AttributeRouting.Web.Http.SelfHost/packages.config index 1ba9e00..6c98029 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/packages.config +++ b/src/AttributeRouting.Web.Http.SelfHost/packages.config @@ -2,7 +2,7 @@ - + - + \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj b/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj index 4251784..61a1673 100644 --- a/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj +++ b/src/AttributeRouting.Web.Http.WebHost/AttributeRouting.Web.Http.WebHost.csproj @@ -45,28 +45,29 @@ True - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - ..\packages\Newtonsoft.Json.4.5.8\lib\net40\Newtonsoft.Json.dll + False + ..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll + ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll - ..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll + ..\..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll - ..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll + ..\..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll - ..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.20710.0\lib\net40\System.Web.Http.WebHost.dll + ..\..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.20710.0\lib\net40\System.Web.Http.WebHost.dll diff --git a/src/AttributeRouting.Web.Http.WebHost/packages.config b/src/AttributeRouting.Web.Http.WebHost/packages.config index ba9ece5..76f6b20 100644 --- a/src/AttributeRouting.Web.Http.WebHost/packages.config +++ b/src/AttributeRouting.Web.Http.WebHost/packages.config @@ -5,5 +5,5 @@ - + \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj b/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj index 7a09a9f..ceaee36 100644 --- a/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj +++ b/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj @@ -44,22 +44,23 @@ - ..\packages\Newtonsoft.Json.4.5.8\lib\net40\Newtonsoft.Json.dll + False + ..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll + ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll - ..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll + ..\..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll - ..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll + ..\..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll diff --git a/src/AttributeRouting.Web.Http/packages.config b/src/AttributeRouting.Web.Http/packages.config index 6551089..5973fb4 100644 --- a/src/AttributeRouting.Web.Http/packages.config +++ b/src/AttributeRouting.Web.Http/packages.config @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/src/AttributeRouting.Web.Mvc/Extensions/UrlHelperExtensions.cs b/src/AttributeRouting.Web.Mvc/Extensions/UrlHelperExtensions.cs index 8f233ac..a62869a 100644 --- a/src/AttributeRouting.Web.Mvc/Extensions/UrlHelperExtensions.cs +++ b/src/AttributeRouting.Web.Mvc/Extensions/UrlHelperExtensions.cs @@ -11,77 +11,23 @@ namespace AttributeRouting.Web.Mvc.Extensions { public static class UrlHelperExtensions { - public static string SubdomainAction(this UrlHelper urlHelper, string actionName, - RouteValueDictionary routeValues) - { - string baseUrl = GetDomainBase(urlHelper, null, actionName, null, routeValues); - return BuildUri(baseUrl, urlHelper.Action(actionName, routeValues)); - } - - public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName, - string areaName = "") - { - var routeValues = new RouteValueDictionary {{"area", areaName}}; - string baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, routeValues); - return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); - } - - public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName, - object routeValues) - { - string baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, - new RouteValueDictionary(routeValues)); - return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); - } - - public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName, - RouteValueDictionary routeValues) - { - string baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, routeValues); + public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName = null, object routeValues = null, string protocol = null) + { + var routeValueDictionary = new RouteValueDictionary(routeValues); + var baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, routeValueDictionary, protocol); return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); - } - - public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName, - object routeValues, string protocol) - { - string baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, - new RouteValueDictionary(routeValues), protocol); - return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); - } - - public static string SubdomainRouteUrl(this UrlHelper urlHelper, object routeValues) + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, object routeValues, string protocol = null) { - return SubdomainRouteUrl(urlHelper, null, routeValues); + return SubdomainRouteUrl(urlHelper, null, routeValues, protocol); } - public static string SubdomainRouteUrl(this UrlHelper urlHelper, RouteValueDictionary routeValues) + public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName, object routeValues = null, string protocol = null) { - return SubdomainRouteUrl(urlHelper, null, routeValues); - } - - public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName) - { - return SubdomainRouteUrl(urlHelper, routeName, (object) null); - } - - public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName, object routeValues) - { - return SubdomainRouteUrl(urlHelper, routeName, routeValues, null); - } - - public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName, - RouteValueDictionary routeValues) - { - string baseUrl = GetDomainBase(urlHelper, routeName, null, null, routeValues); - return BuildUri(baseUrl, urlHelper.RouteUrl(routeName, routeValues)); - } - - public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName, object routeValues, - string protocol) - { - var r = new RouteValueDictionary(routeValues); - string baseUrl = GetDomainBase(urlHelper, routeName, null, null, r, protocol); - return BuildUri(baseUrl, urlHelper.RouteUrl(routeName, routeValues)); + var routeValueDictionary = new RouteValueDictionary(routeValues); + var baseUrl = GetDomainBase(urlHelper, routeName, null, null, routeValueDictionary, protocol); + return BuildUri(baseUrl, urlHelper.RouteUrl(routeName, routeValueDictionary)); } /// @@ -94,134 +40,130 @@ public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeNam /// The route values. /// The schema. /// - private static string GetDomainBase(UrlHelper urlHelper, string routeName, string actionName, - string controllerName, RouteValueDictionary routeValues, - string schema = null) + private static string GetDomainBase(UrlHelper urlHelper, string routeName, string actionName, string controllerName, RouteValueDictionary routeValues, string schema = null) { - //baseUrl is the return value which by default is an empty string - string baseUrl = string.Empty; + // baseUrl is the return value which by default is an empty string. + var baseUrl = String.Empty; - //just a shortcut variable so we don't have to have this the below line eight million times - Uri currentUrl = urlHelper.RequestContext.HttpContext.Request.Url; + // Just a shortcut variable so we don't have to have this the below line eight million times. + var currentUrl = urlHelper.RequestContext.HttpContext.Request.Url; //get the desired route using a copy of MS internal methods - RouteValueDictionary values = MergeRouteValues(actionName, controllerName, urlHelper.RequestContext.RouteData.Values, routeValues, true); - VirtualPathData virtualPathForArea = urlHelper.RouteCollection.GetVirtualPathForArea(urlHelper.RequestContext, routeName, values); + var values = MergeRouteValues(actionName, controllerName, urlHelper.RequestContext.RouteData.Values, routeValues, true); + var virtualPathForArea = urlHelper.RouteCollection.GetVirtualPathForArea(urlHelper.RequestContext, routeName, values); if (virtualPathForArea == null) { return baseUrl; } - //if not a AttributeRoute or the current url is funny then nothing we can do so move on + // If not an AttributeRoute or the current url is funny then nothing we can do so move on. var route = virtualPathForArea.Route as IAttributeRoute; - if (route != null && currentUrl != null && !string.IsNullOrWhiteSpace(currentUrl.OriginalString)) + if (route != null && currentUrl != null && currentUrl.OriginalString.HasValue()) { - //get the current domain via the current Uri. - string host = currentUrl.GetLeftPart(UriPartial.Authority).Replace(currentUrl.GetLeftPart(UriPartial.Scheme), string.Empty); - + // Get the current domain via the current Uri. + var host = currentUrl.GetLeftPart(UriPartial.Authority).Replace(currentUrl.GetLeftPart(UriPartial.Scheme), string.Empty); + + // If the port exists in the host remove it so that we don't run into trouble with the IPAddress parsing IPAddress ip; - //if the port exists in the host remove it so that we don't run into trouble with the IPAddress parsing if (host.Contains(":")) { host = host.Substring(0, host.IndexOf(":", StringComparison.Ordinal)); } - //if an ip then no point in building a subdomain for it + // If an ip then no point in building a subdomain for it if (IPAddress.TryParse(host, out ip)) { return string.Empty; } - //save the current host for comparisons later - string currentHost = host; + // Save the current host for comparisons later + var currentHost = host; - //which protocol schema to use. i.e. http, https - string scheme = schema ?? currentUrl.Scheme; + // Which protocol schema to use. i.e. http, https + var scheme = schema ?? currentUrl.Scheme; - //what is the current port. needed if non-standard - int port = currentUrl.Port; + // What is the current port. needed if non-standard + var port = currentUrl.Port; - //is the port a standard port? - bool useDefaultPort = port == 80 || port == 443; + // Is the port a standard port? + var useDefaultPort = port == 80 || port == 443; - //need the default subdomain incase we are going from one subdomain method to a non-subdomain method - string defaultSubdomain = string.Empty; - if (route.DataTokens.Any(x => x.Key.Equals("defaultSubdomain"))) + // Need the default subdomain incase we are going from one subdomain method to a non-subdomain method + var defaultSubdomain = String.Empty; + object defaultSubdomainValue; + if (route.DataTokens.TryGetValue("defaultSubdomain", out defaultSubdomainValue)) { - defaultSubdomain = route.DataTokens["defaultSubdomain"].ToString(); + defaultSubdomain = defaultSubdomainValue.ToString(); } - //if the host contains a dot we need to remove the subdomain if it is in the list of ones to remove + // If the host contains a dot we need to remove the subdomain if it is in the list of ones to remove if (host.Contains(".")) { - //get all registered subdomains - List subdomains = - urlHelper.RouteCollection.Where(x => x is IAttributeRoute) - .Cast() - .Where(x => x.Subdomain.HasValue()) - .Select(x => x.Subdomain) - .Distinct() - .ToList(); - - //also add the default subdomain from the current route + // Get all registered subdomains + var subdomains = urlHelper.RouteCollection + .Where(x => x is IAttributeRoute) + .Cast() + .Where(x => x.Subdomain.HasValue()) + .Select(x => x.Subdomain) + .Distinct() + .ToList(); + + // Also add the default subdomain from the current route if (!string.IsNullOrWhiteSpace(defaultSubdomain) && !subdomains.Contains(defaultSubdomain)) { subdomains.Add(defaultSubdomain); } - //strips subdomain information off of current (if matching a current one) - string subDomainSection = host.Split('.')[0]; - foreach ( - string subdomain in - subdomains.Where( - subdomain => - subDomainSection.Equals(subdomain, StringComparison.InvariantCultureIgnoreCase))) + // Strips subdomain information off of current (if matching a current one) + var subdomainSection = host.Split('.')[0]; + var subdomain = subdomains.FirstOrDefault(subdomainSection.ValueEquals); + if (subdomain.HasValue()) { - host = host.Replace(string.Format("{0}.", subdomain), string.Empty); - break; + host = host.Replace(subdomain + ".", null); } } - //if not a subdomain then don't build the url. instead build it to the default subdomain - if (!string.IsNullOrWhiteSpace(route.Subdomain)) + // If not a subdomain then don't build the url; instead build it to the default subdomain. + if (route.Subdomain.HasValue()) { - //if host already starts with subdomain then skip building the url + // If host already starts with subdomain then skip building the url if (!currentHost.StartsWith(route.Subdomain)) { - baseUrl = string.Format("{0}://{1}.{2}", scheme, route.Subdomain, host); + baseUrl = "{0}://{1}.{2}".FormatWith(scheme, route.Subdomain, host); } } else { - //no subdomain so we should add the default subdomain unless it is localhost - var gotoSubdomain = string.Empty; - if (!host.Equals("localhost", StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrWhiteSpace(defaultSubdomain)) + // No subdomain so we should add the default subdomain unless it is localhost + var subdomain = string.Empty; + if (!host.ValueEquals("localhost") && defaultSubdomain.HasValue()) { - gotoSubdomain = string.Format("{0}.", defaultSubdomain); + subdomain = defaultSubdomain + "."; } - baseUrl = string.Format("{0}://{1}{2}", scheme, gotoSubdomain, host); + baseUrl = "{0}://{1}{2}".FormatWith(scheme, subdomain, host); } //not using a standard port so if the baseurl has a value then append on the port - if (!string.IsNullOrWhiteSpace(baseUrl) && !useDefaultPort) + if (baseUrl.HasValue() && !useDefaultPort) { - baseUrl = string.Format("{0}:{1}", baseUrl, port); + baseUrl = "{0}:{1}".FormatWith(baseUrl, port); } } + return baseUrl; } private static string BuildUri(string baseUrl, string relativeUrl) { - if (string.IsNullOrWhiteSpace(baseUrl) && string.IsNullOrWhiteSpace(relativeUrl)) + if (baseUrl.HasNoValue() && relativeUrl.HasNoValue()) { - return string.Empty; + return String.Empty; } - if (string.IsNullOrWhiteSpace(baseUrl)) + if (baseUrl.HasNoValue()) { return relativeUrl; } - if (string.IsNullOrWhiteSpace(relativeUrl)) + if (relativeUrl.HasNoValue()) { return baseUrl; } @@ -229,18 +171,10 @@ private static string BuildUri(string baseUrl, string relativeUrl) { relativeUrl = "/" + relativeUrl; } - return string.Format("{0}{1}", baseUrl, relativeUrl); - } - - public static RouteValueDictionary GetRouteValues(RouteValueDictionary routeValues) - { - return routeValues == null ? new RouteValueDictionary() : new RouteValueDictionary(routeValues); + return "{0}{1}".FormatWith(baseUrl, relativeUrl); } - public static RouteValueDictionary MergeRouteValues(string actionName, string controllerName, - RouteValueDictionary implicitRouteValues, - RouteValueDictionary routeValues, - bool includeImplicitMvcValues) + public static RouteValueDictionary MergeRouteValues(string actionName, string controllerName, RouteValueDictionary implicitRouteValues, RouteValueDictionary routeValues, bool includeImplicitMvcValues) { var routeValueDictionary = new RouteValueDictionary(); if (includeImplicitMvcValues) @@ -257,7 +191,8 @@ public static RouteValueDictionary MergeRouteValues(string actionName, string co } if (routeValues != null) { - foreach (var keyValuePair in GetRouteValues(routeValues)) + var newRouteValueDictionary = new RouteValueDictionary(routeValues); + foreach (var keyValuePair in newRouteValueDictionary) { routeValueDictionary[keyValuePair.Key] = keyValuePair.Value; } diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index 29fcad0..2ddb08b 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -1,13 +1,13 @@ -using System; -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: ComVisible(false)] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("AttributeRouting")] -[assembly: AssemblyCopyright("Copyright 2010-2012 Tim McCall")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.4.1")] -[assembly: AssemblyFileVersion("3.4.1")] -[assembly: AssemblyInformationalVersion("3.4.1")] +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AttributeRouting")] +[assembly: AssemblyCopyright("Copyright 2010-2013 Tim McCall")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyVersion("3.4.1")] +[assembly: AssemblyFileVersion("3.4.1")] +[assembly: AssemblyInformationalVersion("3.4.1")] [assembly: AssemblyConfiguration("Release")] From 5e1f2ffffb974c3111e6849fd478ea283fc84dda Mon Sep 17 00:00:00 2001 From: Shawn Carr Date: Fri, 1 Feb 2013 14:46:57 -0500 Subject: [PATCH 33/97] More Testing of the Subdomain Routing events --- .../Extensions/UrlHelperExtensionTests.cs | 265 +++++++++++++++++- 1 file changed, 262 insertions(+), 3 deletions(-) diff --git a/src/AttributeRouting.Specs/Tests/Extensions/UrlHelperExtensionTests.cs b/src/AttributeRouting.Specs/Tests/Extensions/UrlHelperExtensionTests.cs index dfb9810..327d877 100644 --- a/src/AttributeRouting.Specs/Tests/Extensions/UrlHelperExtensionTests.cs +++ b/src/AttributeRouting.Specs/Tests/Extensions/UrlHelperExtensionTests.cs @@ -24,6 +24,8 @@ private UrlHelper GetUrlHelper(RouteCollection routes, string host, string schem return new UrlHelper(new RequestContext(httpContextMock.Object, new RouteData()), routes); } + #region Action + [Test] public void Local_Host_Action_Will_Have_A_Subdomain_Prepended() { @@ -37,7 +39,6 @@ public void Local_Host_Action_Will_Have_A_Subdomain_Prepended() Assert.AreEqual("http://users.localhost/", path); } - [Test] public void Top_Level_Domain_Action_Will_Have_A_Subdomain_Prepended() { @@ -221,7 +222,7 @@ public void HTTPS_URL_Will_Change_To_HTTP() routes.MapAttributeRoutes(config => config.AddRoutesFromController()); var helper = GetUrlHelper(routes, "localhost", "https"); Assert.That(helper, Is.Not.Null); - var path = helper.SubdomainAction("Index", "Subdomain", new { area = "Users" }, "http"); + var path = helper.SubdomainAction("Index", "Subdomain", new {area = "Users"}, "http"); Console.WriteLine(path); Assert.AreEqual("http://users.localhost/", path); } @@ -234,9 +235,267 @@ public void Host_With_Port_Will_Have_A_Subdomain_Prepended() routes.MapAttributeRoutes(config => config.AddRoutesFromController()); var helper = GetUrlHelper(routes, "example.com:81"); Assert.That(helper, Is.Not.Null); - var path = helper.SubdomainAction("Index", "Subdomain", new { area = "Users" }); + var path = helper.SubdomainAction("Index", "Subdomain", new {area = "Users"}); + Console.WriteLine(path); + Assert.AreEqual("http://users.example.com:81/", path); + } + + #endregion + + #region RouteURL + + [Test] + public void RouteURL_Local_Host_Action_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "localhost"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("Users_Subdomain_Index"); + Console.WriteLine(path); + Assert.AreEqual("http://users.localhost/", path); + } + + + [Test] + public void RouteURL_Top_Level_Domain_Action_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "example.com"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("Users_Subdomain_Index"); + Console.WriteLine(path); + Assert.AreEqual("http://users.example.com/", path); + } + + [Test] + public void RouteURL_Second_Level_Domain_Action_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "example.co.uk"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("Users_Subdomain_Index"); + Console.WriteLine(path); + Assert.AreEqual("http://users.example.co.uk/", path); + } + + [Test] + public void RouteURL_Top_Level_Domain_With_Subdomain_Action_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "private.example.com"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("Users_Subdomain_Index"); + Console.WriteLine(path); + Assert.AreEqual("http://users.example.com/", path); + } + + [Test] + public void RouteURL_Second_Level_Domain_With_Subdomain_Action_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "private.example.co.ok"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("Users_Subdomain_Index"); + Console.WriteLine(path); + Assert.AreEqual("http://users.example.co.ok/", path); + } + + [Test] + public void RouteURL_Current_Subdomain_Action_To_Same_Subdomain_Will_Only_Be_The_Relative_Url() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "users.example.com"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("Users_Subdomain_Index"); + Console.WriteLine(path); + Assert.AreEqual("/", path); + } + + [Test] + public void RouteURL_Current_Subdomain_Action_To_No_Subdomain_And_Default_Subdomain_Will_Return_Full_Url() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "users.example.com"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("StandardUsage_AnyVerb"); + Console.WriteLine(path); + Assert.AreEqual("http://www.example.com/AnyVerb", path); + } + + [Test] + public void RouteURL_Current_Subdomain_Action_To_No_Subdomain_And_Custom_Default_Subdomain_Will_Return_Full_Url() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + config.DefaultSubdomain = "xyz"; + }); + var helper = GetUrlHelper(routes, "users.example.com"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("StandardUsage_AnyVerb"); + Console.WriteLine(path); + Assert.AreEqual("http://xyz.example.com/AnyVerb", path); + } + + [Test] + public void RouteURL_Current_Subdomain_Action_To_No_Subdomain_And_No_Default_Subdomain_Will_Return_Full_Url() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + config.DefaultSubdomain = ""; + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "users.example.com"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("StandardUsage_AnyVerb"); + Console.WriteLine(path); + Assert.AreEqual("http://example.com/AnyVerb", path); + } + + [Test] + public void RouteURL_Local_Host_With_Subdomain_Action_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "private.localhost"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("Users_Subdomain_Index"); + Console.WriteLine(path); + Assert.AreEqual("http://users.localhost/", path); + } + + + [Test] + public void + RouteURL_Local_Host_With_Subdomain_Action_To_Non_Area_Method_Will_Return_Full_Url_Without_Default_Subdomain() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "users.localhost"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("StandardUsage_Index"); + Console.WriteLine(path); + Assert.AreEqual("http://localhost/", path); + } + + + [Test] + public void RouteURL_HTTP_URL_Will_Change_To_HTTPS() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "localhost"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("Users_Subdomain_Index", null, "https"); + Console.WriteLine(path); + Assert.AreEqual("https://users.localhost/", path); + } + + [Test] + public void RouteURL_HTTPS_URL_Will_Change_To_HTTP() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "localhost", "https"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("Users_Subdomain_Index", null, "http"); + Console.WriteLine(path); + Assert.AreEqual("http://users.localhost/", path); + } + + [Test] + public void RouteURL_Host_With_Port_Will_Have_A_Subdomain_Prepended() + { + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AutoGenerateRouteNames = true; + }); + var helper = GetUrlHelper(routes, "example.com:81"); + Assert.That(helper, Is.Not.Null); + var path = helper.SubdomainRouteUrl("Users_Subdomain_Index"); Console.WriteLine(path); Assert.AreEqual("http://users.example.com:81/", path); } + + #endregion + } } \ No newline at end of file From 886dce739cc07f1710e1c7cb5e5f4764b4dd6357 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Wed, 13 Feb 2013 12:55:32 -0700 Subject: [PATCH 34/97] #192 - fixed bug that caused optional params in asp.net web api routes to fail. --- .../HttpRouteCollectionExtensions.cs | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs b/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs index d8d2abb..2697b79 100644 --- a/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs +++ b/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs @@ -50,28 +50,12 @@ public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, HttpW routes.MapHttpAttributeRoutesInternal(configuration); } - private static void MapHttpAttributeRoutesInternal(this HttpRouteCollection routes, HttpWebConfiguration configuration) - { - var generatedRoutes = new RouteBuilder(configuration).BuildAllRoutes().Cast().ToList(); - - // If providing a custom IRouteHandler via config, add the routes to the RouteCollection. - // Have to do this because the HttpRoutes do not support the functionality. - var routeHandler = configuration.RouteHandlerFactory(); - if (routeHandler != null) - { - var mvcRoutes = RouteTable.Routes; - generatedRoutes.ForEach(r => - { - var mvcRoute = mvcRoutes.MapHttpRoute(r.RouteName, r.Url, r.Defaults, r.Constraints, r.Handler); - mvcRoute.DataTokens = new RouteValueDictionary(r.DataTokens); - mvcRoute.RouteHandler = routeHandler; - }); - } - else - { - // Otherwise, add them to the HttpRouteCollection. - generatedRoutes.ForEach(r => routes.Add(r.RouteName, r)); - } + private static void MapHttpAttributeRoutesInternal(this HttpRouteCollection routes, HttpWebConfiguration configuration) + { + new RouteBuilder(configuration).BuildAllRoutes() + .Cast() + .ToList() + .ForEach(r => routes.Add(r.RouteName, r)); } } } \ No newline at end of file From 275b208a67da4fbb254fce7ad340747f29813850 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Wed, 13 Feb 2013 13:04:55 -0700 Subject: [PATCH 35/97] Restored original subdomain url helpers. --- .../Extensions/UrlHelperExtensions.cs | 479 ++++++++++-------- 1 file changed, 272 insertions(+), 207 deletions(-) diff --git a/src/AttributeRouting.Web.Mvc/Extensions/UrlHelperExtensions.cs b/src/AttributeRouting.Web.Mvc/Extensions/UrlHelperExtensions.cs index a62869a..239067b 100644 --- a/src/AttributeRouting.Web.Mvc/Extensions/UrlHelperExtensions.cs +++ b/src/AttributeRouting.Web.Mvc/Extensions/UrlHelperExtensions.cs @@ -1,211 +1,276 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Web.Mvc; -using System.Web.Routing; -using AttributeRouting.Framework; -using AttributeRouting.Helpers; - -namespace AttributeRouting.Web.Mvc.Extensions -{ - public static class UrlHelperExtensions - { - public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName = null, object routeValues = null, string protocol = null) +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Web.Mvc; +using System.Web.Routing; +using AttributeRouting.Framework; +using AttributeRouting.Helpers; + +namespace AttributeRouting.Web.Mvc.Extensions +{ + public static class UrlHelperExtensions + { + public static string SubdomainAction(this UrlHelper urlHelper, string actionName, + RouteValueDictionary routeValues) + { + string baseUrl = GetDomainBase(urlHelper, null, actionName, null, routeValues); + return BuildUri(baseUrl, urlHelper.Action(actionName, routeValues)); + } + + public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName, + string areaName = "") { - var routeValueDictionary = new RouteValueDictionary(routeValues); - var baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, routeValueDictionary, protocol); - return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); + var routeValues = new RouteValueDictionary { { "area", areaName } }; + string baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, routeValues); + return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); } - public static string SubdomainRouteUrl(this UrlHelper urlHelper, object routeValues, string protocol = null) - { - return SubdomainRouteUrl(urlHelper, null, routeValues, protocol); - } - - public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName, object routeValues = null, string protocol = null) - { - var routeValueDictionary = new RouteValueDictionary(routeValues); - var baseUrl = GetDomainBase(urlHelper, routeName, null, null, routeValueDictionary, protocol); - return BuildUri(baseUrl, urlHelper.RouteUrl(routeName, routeValueDictionary)); - } - - /// - /// Gets the domain base url. - /// - /// The URL helper. - /// Name of the route. - /// Name of the action. - /// Name of the controller. - /// The route values. - /// The schema. - /// - private static string GetDomainBase(UrlHelper urlHelper, string routeName, string actionName, string controllerName, RouteValueDictionary routeValues, string schema = null) - { - // baseUrl is the return value which by default is an empty string. - var baseUrl = String.Empty; - - // Just a shortcut variable so we don't have to have this the below line eight million times. - var currentUrl = urlHelper.RequestContext.HttpContext.Request.Url; - - //get the desired route using a copy of MS internal methods - var values = MergeRouteValues(actionName, controllerName, urlHelper.RequestContext.RouteData.Values, routeValues, true); - var virtualPathForArea = urlHelper.RouteCollection.GetVirtualPathForArea(urlHelper.RequestContext, routeName, values); - if (virtualPathForArea == null) - { - return baseUrl; - } - - // If not an AttributeRoute or the current url is funny then nothing we can do so move on. - var route = virtualPathForArea.Route as IAttributeRoute; - if (route != null && currentUrl != null && currentUrl.OriginalString.HasValue()) - { - // Get the current domain via the current Uri. - var host = currentUrl.GetLeftPart(UriPartial.Authority).Replace(currentUrl.GetLeftPart(UriPartial.Scheme), string.Empty); - - // If the port exists in the host remove it so that we don't run into trouble with the IPAddress parsing - IPAddress ip; - if (host.Contains(":")) - { - host = host.Substring(0, host.IndexOf(":", StringComparison.Ordinal)); - } - - // If an ip then no point in building a subdomain for it - if (IPAddress.TryParse(host, out ip)) - { - return string.Empty; - } - - // Save the current host for comparisons later - var currentHost = host; - - // Which protocol schema to use. i.e. http, https - var scheme = schema ?? currentUrl.Scheme; - - // What is the current port. needed if non-standard - var port = currentUrl.Port; - - // Is the port a standard port? - var useDefaultPort = port == 80 || port == 443; - - // Need the default subdomain incase we are going from one subdomain method to a non-subdomain method - var defaultSubdomain = String.Empty; - object defaultSubdomainValue; - if (route.DataTokens.TryGetValue("defaultSubdomain", out defaultSubdomainValue)) - { - defaultSubdomain = defaultSubdomainValue.ToString(); - } - - // If the host contains a dot we need to remove the subdomain if it is in the list of ones to remove - if (host.Contains(".")) - { - // Get all registered subdomains - var subdomains = urlHelper.RouteCollection - .Where(x => x is IAttributeRoute) - .Cast() - .Where(x => x.Subdomain.HasValue()) - .Select(x => x.Subdomain) - .Distinct() - .ToList(); - - // Also add the default subdomain from the current route - if (!string.IsNullOrWhiteSpace(defaultSubdomain) && !subdomains.Contains(defaultSubdomain)) - { - subdomains.Add(defaultSubdomain); - } - - // Strips subdomain information off of current (if matching a current one) - var subdomainSection = host.Split('.')[0]; - var subdomain = subdomains.FirstOrDefault(subdomainSection.ValueEquals); - if (subdomain.HasValue()) - { - host = host.Replace(subdomain + ".", null); - } - } - - // If not a subdomain then don't build the url; instead build it to the default subdomain. - if (route.Subdomain.HasValue()) - { - // If host already starts with subdomain then skip building the url - if (!currentHost.StartsWith(route.Subdomain)) - { - baseUrl = "{0}://{1}.{2}".FormatWith(scheme, route.Subdomain, host); - } - } - else - { - // No subdomain so we should add the default subdomain unless it is localhost - var subdomain = string.Empty; - if (!host.ValueEquals("localhost") && defaultSubdomain.HasValue()) - { - subdomain = defaultSubdomain + "."; - } - baseUrl = "{0}://{1}{2}".FormatWith(scheme, subdomain, host); - } - - //not using a standard port so if the baseurl has a value then append on the port - if (baseUrl.HasValue() && !useDefaultPort) - { - baseUrl = "{0}:{1}".FormatWith(baseUrl, port); - } - } - - return baseUrl; - } - - private static string BuildUri(string baseUrl, string relativeUrl) - { - if (baseUrl.HasNoValue() && relativeUrl.HasNoValue()) - { - return String.Empty; - } - if (baseUrl.HasNoValue()) - { - return relativeUrl; - } - if (relativeUrl.HasNoValue()) - { - return baseUrl; - } - if (!relativeUrl.StartsWith("/")) - { - relativeUrl = "/" + relativeUrl; - } - return "{0}{1}".FormatWith(baseUrl, relativeUrl); - } - - public static RouteValueDictionary MergeRouteValues(string actionName, string controllerName, RouteValueDictionary implicitRouteValues, RouteValueDictionary routeValues, bool includeImplicitMvcValues) - { - var routeValueDictionary = new RouteValueDictionary(); - if (includeImplicitMvcValues) - { - object obj; - if (implicitRouteValues != null && implicitRouteValues.TryGetValue("action", out obj)) - { - routeValueDictionary["action"] = obj; - } - if (implicitRouteValues != null && implicitRouteValues.TryGetValue("controller", out obj)) - { - routeValueDictionary["controller"] = obj; - } - } - if (routeValues != null) - { - var newRouteValueDictionary = new RouteValueDictionary(routeValues); - foreach (var keyValuePair in newRouteValueDictionary) - { - routeValueDictionary[keyValuePair.Key] = keyValuePair.Value; - } - } - if (actionName != null) - { - routeValueDictionary["action"] = actionName; - } - if (controllerName != null) - { - routeValueDictionary["controller"] = controllerName; - } - return routeValueDictionary; - } - } + public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName, + object routeValues) + { + string baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, + new RouteValueDictionary(routeValues)); + return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); + } + + public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName, + RouteValueDictionary routeValues) + { + string baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, routeValues); + return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); + } + + public static string SubdomainAction(this UrlHelper urlHelper, string actionName, string controllerName, + object routeValues, string protocol) + { + string baseUrl = GetDomainBase(urlHelper, null, actionName, controllerName, + new RouteValueDictionary(routeValues), protocol); + return BuildUri(baseUrl, urlHelper.Action(actionName, controllerName, routeValues)); + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, object routeValues) + { + return SubdomainRouteUrl(urlHelper, null, routeValues); + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, RouteValueDictionary routeValues) + { + return SubdomainRouteUrl(urlHelper, null, routeValues); + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName) + { + return SubdomainRouteUrl(urlHelper, routeName, (object)null); + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName, object routeValues) + { + return SubdomainRouteUrl(urlHelper, routeName, routeValues, null); + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName, + RouteValueDictionary routeValues) + { + string baseUrl = GetDomainBase(urlHelper, routeName, null, null, routeValues); + return BuildUri(baseUrl, urlHelper.RouteUrl(routeName, routeValues)); + } + + public static string SubdomainRouteUrl(this UrlHelper urlHelper, string routeName, object routeValues, + string protocol) + { + var r = new RouteValueDictionary(routeValues); + string baseUrl = GetDomainBase(urlHelper, routeName, null, null, r, protocol); + return BuildUri(baseUrl, urlHelper.RouteUrl(routeName, routeValues)); + } + + /// + /// Gets the domain base url. + /// + /// The URL helper. + /// Name of the route. + /// Name of the action. + /// Name of the controller. + /// The route values. + /// The schema. + /// + private static string GetDomainBase(UrlHelper urlHelper, string routeName, string actionName, + string controllerName, RouteValueDictionary routeValues, + string schema = null) + { + //baseUrl is the return value which by default is an empty string + string baseUrl = string.Empty; + + //just a shortcut variable so we don't have to have this the below line eight million times + Uri currentUrl = urlHelper.RequestContext.HttpContext.Request.Url; + + //get the desired route using a copy of MS internal methods + RouteValueDictionary values = MergeRouteValues(actionName, controllerName, urlHelper.RequestContext.RouteData.Values, routeValues, true); + VirtualPathData virtualPathForArea = urlHelper.RouteCollection.GetVirtualPathForArea(urlHelper.RequestContext, routeName, values); + if (virtualPathForArea == null) + { + return baseUrl; + } + + //if not a AttributeRoute or the current url is funny then nothing we can do so move on + var route = virtualPathForArea.Route as IAttributeRoute; + if (route != null && currentUrl != null && !string.IsNullOrWhiteSpace(currentUrl.OriginalString)) + { + //get the current domain via the current Uri. + string host = currentUrl.GetLeftPart(UriPartial.Authority).Replace(currentUrl.GetLeftPart(UriPartial.Scheme), string.Empty); + + IPAddress ip; + //if the port exists in the host remove it so that we don't run into trouble with the IPAddress parsing + if (host.Contains(":")) + { + host = host.Substring(0, host.IndexOf(":", StringComparison.Ordinal)); + } + + //if an ip then no point in building a subdomain for it + if (IPAddress.TryParse(host, out ip)) + { + return string.Empty; + } + + //save the current host for comparisons later + string currentHost = host; + + //which protocol schema to use. i.e. http, https + string scheme = schema ?? currentUrl.Scheme; + + //what is the current port. needed if non-standard + int port = currentUrl.Port; + + //is the port a standard port? + bool useDefaultPort = port == 80 || port == 443; + + //need the default subdomain incase we are going from one subdomain method to a non-subdomain method + string defaultSubdomain = string.Empty; + if (route.DataTokens.Any(x => x.Key.Equals("defaultSubdomain"))) + { + defaultSubdomain = route.DataTokens["defaultSubdomain"].ToString(); + } + + //if the host contains a dot we need to remove the subdomain if it is in the list of ones to remove + if (host.Contains(".")) + { + //get all registered subdomains + List subdomains = + urlHelper.RouteCollection.Where(x => x is IAttributeRoute) + .Cast() + .Where(x => x.Subdomain.HasValue()) + .Select(x => x.Subdomain) + .Distinct() + .ToList(); + + //also add the default subdomain from the current route + if (!string.IsNullOrWhiteSpace(defaultSubdomain) && !subdomains.Contains(defaultSubdomain)) + { + subdomains.Add(defaultSubdomain); + } + + //strips subdomain information off of current (if matching a current one) + string subDomainSection = host.Split('.')[0]; + foreach ( + string subdomain in + subdomains.Where( + subdomain => + subDomainSection.Equals(subdomain, StringComparison.InvariantCultureIgnoreCase))) + { + host = host.Replace(string.Format("{0}.", subdomain), string.Empty); + break; + } + } + + //if not a subdomain then don't build the url. instead build it to the default subdomain + if (!string.IsNullOrWhiteSpace(route.Subdomain)) + { + //if host already starts with subdomain then skip building the url + if (!currentHost.StartsWith(route.Subdomain)) + { + baseUrl = string.Format("{0}://{1}.{2}", scheme, route.Subdomain, host); + } + } + else + { + //no subdomain so we should add the default subdomain unless it is localhost + var gotoSubdomain = string.Empty; + if (!host.Equals("localhost", StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrWhiteSpace(defaultSubdomain)) + { + gotoSubdomain = string.Format("{0}.", defaultSubdomain); + } + baseUrl = string.Format("{0}://{1}{2}", scheme, gotoSubdomain, host); + } + + //not using a standard port so if the baseurl has a value then append on the port + if (!string.IsNullOrWhiteSpace(baseUrl) && !useDefaultPort) + { + baseUrl = string.Format("{0}:{1}", baseUrl, port); + } + } + return baseUrl; + } + + private static string BuildUri(string baseUrl, string relativeUrl) + { + if (string.IsNullOrWhiteSpace(baseUrl) && string.IsNullOrWhiteSpace(relativeUrl)) + { + return string.Empty; + } + if (string.IsNullOrWhiteSpace(baseUrl)) + { + return relativeUrl; + } + if (string.IsNullOrWhiteSpace(relativeUrl)) + { + return baseUrl; + } + if (!relativeUrl.StartsWith("/")) + { + relativeUrl = "/" + relativeUrl; + } + return string.Format("{0}{1}", baseUrl, relativeUrl); + } + + public static RouteValueDictionary GetRouteValues(RouteValueDictionary routeValues) + { + return routeValues == null ? new RouteValueDictionary() : new RouteValueDictionary(routeValues); + } + + public static RouteValueDictionary MergeRouteValues(string actionName, string controllerName, + RouteValueDictionary implicitRouteValues, + RouteValueDictionary routeValues, + bool includeImplicitMvcValues) + { + var routeValueDictionary = new RouteValueDictionary(); + if (includeImplicitMvcValues) + { + object obj; + if (implicitRouteValues != null && implicitRouteValues.TryGetValue("action", out obj)) + { + routeValueDictionary["action"] = obj; + } + if (implicitRouteValues != null && implicitRouteValues.TryGetValue("controller", out obj)) + { + routeValueDictionary["controller"] = obj; + } + } + if (routeValues != null) + { + foreach (var keyValuePair in GetRouteValues(routeValues)) + { + routeValueDictionary[keyValuePair.Key] = keyValuePair.Value; + } + } + if (actionName != null) + { + routeValueDictionary["action"] = actionName; + } + if (controllerName != null) + { + routeValueDictionary["controller"] = controllerName; + } + return routeValueDictionary; + } + } } \ No newline at end of file From 051654764a3ed1e670fe74f6d79a2751d25b211f Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Wed, 13 Feb 2013 13:36:14 -0700 Subject: [PATCH 36/97] #185 - fixed namespace of app_start files dropped via nuget. --- .../AttributeRouting.cs.pp | 11 +++++---- .../AttributeRoutingHttp.cs.pp | 15 +++++++----- .../AttributeRoutingHttp.vb.pp | 4 ++-- nuget/AttributeRouting/AttributeRouting.cs.pp | 15 +++++++----- nuget/AttributeRouting/AttributeRouting.vb.pp | 4 ++-- .../AttributeRouting.Tests.Web.csproj | 1 + .../Controllers/InheritActionsControllers.cs | 24 +++++++++++++++++++ 7 files changed, 53 insertions(+), 21 deletions(-) create mode 100644 src/AttributeRouting.Tests.Web/Controllers/InheritActionsControllers.cs diff --git a/nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.cs.pp b/nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.cs.pp index d8a30d8..5d727f7 100644 --- a/nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.cs.pp +++ b/nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.cs.pp @@ -1,13 +1,14 @@ using System.Web.Http.SelfHost; using AttributeRouting.Web.Http.SelfHost; -namespace $rootnamespace$ { - public static class AttributeRouting { - +namespace $rootnamespace$ +{ + public static class AttributeRouting + { // Call this static method from a start up class in your applicaton (e.g.Program.cs) // Pass in the configuration you're using for your self-hosted Web API - public static void RegisterRoutes(HttpSelfHostConfiguration config) { - + public static void RegisterRoutes(HttpSelfHostConfiguration config) + { // See http://github.com/mccalltd/AttributeRouting/wiki for more options. // To debug routes locally, you can log to Console.Out (or any other TextWriter) like so: // config.Routes.Cast().LogTo(Console.Out); diff --git a/nuget/AttributeRouting.WebApi/AttributeRoutingHttp.cs.pp b/nuget/AttributeRouting.WebApi/AttributeRoutingHttp.cs.pp index 03bdcfc..30685a9 100644 --- a/nuget/AttributeRouting.WebApi/AttributeRoutingHttp.cs.pp +++ b/nuget/AttributeRouting.WebApi/AttributeRoutingHttp.cs.pp @@ -1,19 +1,22 @@ using System.Web.Http; using AttributeRouting.Web.Http.WebHost; -[assembly: WebActivator.PreApplicationStartMethod(typeof($rootnamespace$.App_Start.AttributeRoutingHttp), "Start")] +[assembly: WebActivator.PreApplicationStartMethod(typeof($rootnamespace$.AttributeRoutingHttp), "Start")] -namespace $rootnamespace$.App_Start { - public static class AttributeRoutingHttp { - public static void RegisterRoutes(HttpRouteCollection routes) { - +namespace $rootnamespace$ +{ + public static class AttributeRoutingHttp + { + public static void RegisterRoutes(HttpRouteCollection routes) + { // See http://github.com/mccalltd/AttributeRouting/wiki for more options. // To debug routes locally using the built in ASP.NET development server, go to /routes.axd routes.MapHttpAttributeRoutes(); } - public static void Start() { + public static void Start() + { RegisterRoutes(GlobalConfiguration.Configuration.Routes); } } diff --git a/nuget/AttributeRouting.WebApi/AttributeRoutingHttp.vb.pp b/nuget/AttributeRouting.WebApi/AttributeRoutingHttp.vb.pp index 676e9fb..43c1cbe 100644 --- a/nuget/AttributeRouting.WebApi/AttributeRoutingHttp.vb.pp +++ b/nuget/AttributeRouting.WebApi/AttributeRoutingHttp.vb.pp @@ -1,9 +1,9 @@ Imports System.Web.Http Imports AttributeRouting.Web.Http.WebHost - + -Namespace $rootnamespace$.App_Start +Namespace $rootnamespace$ Public Class AttributeRoutingHttp Public Shared Sub RegisterRoutes(routes As HttpRouteCollection) diff --git a/nuget/AttributeRouting/AttributeRouting.cs.pp b/nuget/AttributeRouting/AttributeRouting.cs.pp index 85cda11..56d76cf 100644 --- a/nuget/AttributeRouting/AttributeRouting.cs.pp +++ b/nuget/AttributeRouting/AttributeRouting.cs.pp @@ -1,19 +1,22 @@ using System.Web.Routing; using AttributeRouting.Web.Mvc; -[assembly: WebActivator.PreApplicationStartMethod(typeof($rootnamespace$.App_Start.AttributeRouting), "Start")] +[assembly: WebActivator.PreApplicationStartMethod(typeof($rootnamespace$.AttributeRouting), "Start")] -namespace $rootnamespace$.App_Start { - public static class AttributeRouting { - public static void RegisterRoutes(RouteCollection routes) { - +namespace $rootnamespace$ +{ + public static class AttributeRouting + { + public static void RegisterRoutes(RouteCollection routes) + { // See http://github.com/mccalltd/AttributeRouting/wiki for more options. // To debug routes locally using the built in ASP.NET development server, go to /routes.axd routes.MapAttributeRoutes(); } - public static void Start() { + public static void Start() + { RegisterRoutes(RouteTable.Routes); } } diff --git a/nuget/AttributeRouting/AttributeRouting.vb.pp b/nuget/AttributeRouting/AttributeRouting.vb.pp index 160e322..5c44b81 100644 --- a/nuget/AttributeRouting/AttributeRouting.vb.pp +++ b/nuget/AttributeRouting/AttributeRouting.vb.pp @@ -1,9 +1,9 @@ Imports System.Web.Routing Imports AttributeRouting.Web.Mvc - + -Namespace $rootnamespace$.App_Start +Namespace $rootnamespace$ Public Class AttributeRouting Public Shared Sub RegisterRoutes(routes As RouteCollection) diff --git a/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj b/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj index 52d0ae9..48fb7e7 100644 --- a/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj +++ b/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj @@ -126,6 +126,7 @@ + diff --git a/src/AttributeRouting.Tests.Web/Controllers/InheritActionsControllers.cs b/src/AttributeRouting.Tests.Web/Controllers/InheritActionsControllers.cs new file mode 100644 index 0000000..f57c106 --- /dev/null +++ b/src/AttributeRouting.Tests.Web/Controllers/InheritActionsControllers.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using AttributeRouting.Web.Mvc; + +namespace AttributeRouting.Tests.Web.Controllers +{ + public abstract class InheritActionsControllerBase : Controller + { + [GET("Base-Method")] + public string Index() + { + return "Base Index"; + } + } + + [RoutePrefix("Inherit/Derived")] + public class InheritActionsDerivedController : InheritActionsControllerBase + { + + } +} From 5b30293bc580441ae8c65f5d51e9088ae31e703c Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Wed, 13 Feb 2013 13:55:25 -0700 Subject: [PATCH 37/97] #183 - now allowing route conventions to be applied on the base controller. --- .../Features/RouteConventions.feature | 11 ++---- .../Features/RouteConventions.feature.cs | 20 ++++------ .../Subjects/RouteConventionsControllers.cs | 39 ++++++++++--------- .../Framework/RouteBuilder.cs | 4 +- .../Framework/RouteReflector.cs | 2 +- 5 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/AttributeRouting.Specs/Features/RouteConventions.feature b/src/AttributeRouting.Specs/Features/RouteConventions.feature index 3714479..5e422f7 100644 --- a/src/AttributeRouting.Specs/Features/RouteConventions.feature +++ b/src/AttributeRouting.Specs/Features/RouteConventions.feature @@ -97,10 +97,7 @@ Scenario: Generating routes using the DefaultHttpRouteConvention on actions with Then the 1st route url is "DefaultHttpRouteConventionWithExplicitOrderedRoute/Primary" And the 2nd route url is "DefaultHttpRouteConventionWithExplicitOrderedRoute" -Scenario: Generating routes using the conventions that define areas on controllers - Given I have registered the routes for the AreaRouteConventionController - When I fetch the routes for the AreaRouteConvention controller's Index action - Then the route url is "Subjects/Index" - And the default for "controller" is "AreaRouteConvention" - And the default for "action" is "Index" - And the route area is "Subjects" +Scenario: Generating routes using the conventions defined on base controllers + Given I have registered the routes for the DerivedFakeConventionController + When I fetch the routes for the DerivedFakeConventionController's Index action + Then the route url is "Yowza/Index" diff --git a/src/AttributeRouting.Specs/Features/RouteConventions.feature.cs b/src/AttributeRouting.Specs/Features/RouteConventions.feature.cs index 101219c..354e53e 100644 --- a/src/AttributeRouting.Specs/Features/RouteConventions.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteConventions.feature.cs @@ -3,7 +3,7 @@ // This code was generated by SpecFlow (http://www.specflow.org/). // SpecFlow Version:1.9.0.77 // SpecFlow Generator Version:1.9.0.0 -// Runtime Version:4.0.30319.18010 +// Runtime Version:4.0.30319.18034 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -284,24 +284,18 @@ public virtual void GeneratingRoutesUsingTheDefaultHttpRouteConventionOnActionsW } [NUnit.Framework.TestAttribute()] - [NUnit.Framework.DescriptionAttribute("Generating routes using the conventions that define areas on controllers")] - public virtual void GeneratingRoutesUsingTheConventionsThatDefineAreasOnControllers() + [NUnit.Framework.DescriptionAttribute("Generating routes using the conventions defined on base controllers")] + public virtual void GeneratingRoutesUsingTheConventionsDefinedOnBaseControllers() { - TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes using the conventions that define areas on controllers", ((string[])(null))); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Generating routes using the conventions defined on base controllers", ((string[])(null))); #line 100 this.ScenarioSetup(scenarioInfo); #line 101 - testRunner.Given("I have registered the routes for the AreaRouteConventionController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); + testRunner.Given("I have registered the routes for the DerivedFakeConventionController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line 102 - testRunner.When("I fetch the routes for the AreaRouteConvention controller\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); + testRunner.When("I fetch the routes for the DerivedFakeConventionController\'s Index action", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 103 - testRunner.Then("the route url is \"Subjects/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 104 - testRunner.And("the default for \"controller\" is \"AreaRouteConvention\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 105 - testRunner.And("the default for \"action\" is \"Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 106 - testRunner.And("the route area is \"Subjects\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); + testRunner.Then("the route url is \"Yowza/Index\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Subjects/RouteConventionsControllers.cs b/src/AttributeRouting.Specs/Subjects/RouteConventionsControllers.cs index b71ffb7..e79e646 100644 --- a/src/AttributeRouting.Specs/Subjects/RouteConventionsControllers.cs +++ b/src/AttributeRouting.Specs/Subjects/RouteConventionsControllers.cs @@ -122,28 +122,31 @@ public ActionResult Index() { return Content(""); } - } - - [AreaRouteConvention] - public class AreaRouteConventionController : Controller - { - public ActionResult Index() - { - return Content(""); - } - } - - public class AreaRouteConventionAttribute : RouteConventionAttributeBase + } + + public class FakeConventionAttribute : RouteConventionAttributeBase { public override IEnumerable GetRouteAttributes(MethodInfo actionMethod) { yield return new GETAttribute(actionMethod.Name); - } - - public override RouteAreaAttribute GetDefaultRouteArea(Type controllerType) + } + + public override IEnumerable GetDefaultRoutePrefixes(Type controllerType) + { + yield return new RoutePrefixAttribute("Yowza"); + } + } + + [FakeConvention] + public abstract class FakeConventionControllerBase : Controller + { + } + + public class DerivedFakeConventionController : FakeConventionControllerBase + { + public ActionResult Index() { - var areaName = controllerType.Namespace.ValueOr("").Split('.').Last(); - return new RouteAreaAttribute(areaName); + return Content(""); } - } + } } \ No newline at end of file diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index ee8a6d8..bf3367c 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -43,7 +43,7 @@ where s.Subdomain.HasValue() foreach (var routeSpec in routeSpecs) { - foreach (var route in Build(routeSpec)) + foreach (var route in BuildRoutes(routeSpec)) { route.MappedSubdomains = mappedSubdomains; yield return route; @@ -51,7 +51,7 @@ where s.Subdomain.HasValue() } } - private IEnumerable Build(RouteSpecification routeSpec) + private IEnumerable BuildRoutes(RouteSpecification routeSpec) { var defaults = CreateRouteDefaults(routeSpec); var constraints = CreateRouteConstraints(routeSpec); diff --git a/src/AttributeRouting/Framework/RouteReflector.cs b/src/AttributeRouting/Framework/RouteReflector.cs index a0a86b9..ae36f0e 100644 --- a/src/AttributeRouting/Framework/RouteReflector.cs +++ b/src/AttributeRouting/Framework/RouteReflector.cs @@ -56,7 +56,7 @@ private IEnumerable BuildRouteSpecifications(IEnumerable(false); + var convention = controllerType.GetCustomAttribute(true); var routeAreaAttribute = GetRouteAreaAttribute(controllerType, convention); // For each action method on the controller: From 52d0708005600055e91e3f7868ba46df24260a50 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Wed, 13 Feb 2013 14:27:26 -0700 Subject: [PATCH 38/97] #182 - inlined scripts used by the routes.axd to eliminate reliance on CDN. --- src/AttributeRouting.Web.Mvc/Configuration.cs | 4 +- .../AttributeRouting.Web.csproj | 4 + .../Logging/LogRoutes.html | 9 +- .../Logging/LogRoutesHandler.cs | 44 +++++++++- .../Logging/LoggingExtensions.cs | 15 ++-- .../Logging/jquery-1.9.1.min.js | 5 ++ .../Logging/knockout-2.2.1.js | 85 +++++++++++++++++++ 7 files changed, 148 insertions(+), 18 deletions(-) create mode 100644 src/AttributeRouting.Web/Logging/jquery-1.9.1.min.js create mode 100644 src/AttributeRouting.Web/Logging/knockout-2.2.1.js diff --git a/src/AttributeRouting.Web.Mvc/Configuration.cs b/src/AttributeRouting.Web.Mvc/Configuration.cs index 3ac3913..33bf721 100644 --- a/src/AttributeRouting.Web.Mvc/Configuration.cs +++ b/src/AttributeRouting.Web.Mvc/Configuration.cs @@ -21,8 +21,8 @@ public Configuration() RegisterDefaultInlineRouteConstraints(typeof(RegexRouteConstraint).Assembly); } - public Func RouteHandlerFactory { get; set; } - + public Func RouteHandlerFactory { get; set; } + /// /// The controller type applicable to this context. /// diff --git a/src/AttributeRouting.Web/AttributeRouting.Web.csproj b/src/AttributeRouting.Web/AttributeRouting.Web.csproj index 423f01d..93c34cf 100644 --- a/src/AttributeRouting.Web/AttributeRouting.Web.csproj +++ b/src/AttributeRouting.Web/AttributeRouting.Web.csproj @@ -88,6 +88,10 @@ + + + + + --> \ No newline at end of file diff --git a/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs b/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs index 1f6cf70..c19849d 100644 --- a/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs +++ b/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs @@ -1,8 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Web; -using System.Web.Routing; -using AttributeRouting.Constraints; +using System.Web.Routing; using AttributeRouting.Framework; using AttributeRouting.Helpers; diff --git a/src/AttributeRouting.Web/AttributeRouting.Web.csproj b/src/AttributeRouting.Web/AttributeRouting.Web.csproj index 93c34cf..8d8e811 100644 --- a/src/AttributeRouting.Web/AttributeRouting.Web.csproj +++ b/src/AttributeRouting.Web/AttributeRouting.Web.csproj @@ -62,6 +62,7 @@ + @@ -76,12 +77,6 @@ - - - {871A79CF-C705-4C6B-8938-F9AA1E02AEA4} - AttributeRouting - - @@ -92,6 +87,12 @@ + + + {871a79cf-c705-4c6b-8938-f9aa1e02aea4} + AttributeRouting + + + --> \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs b/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs index d61f1da..6756823 100644 --- a/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs +++ b/src/AttributeRouting.Web.Http.WebHost/HttpRouteCollectionExtensions.cs @@ -1,61 +1,70 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Web.Http; -using System.Web.Routing; -using AttributeRouting.Framework; -using AttributeRouting.Web.Http.Framework; - -namespace AttributeRouting.Web.Http.WebHost -{ - /// - /// Extensions to the System.Web.Http.HttpRouteCollection - /// - public static class HttpRouteCollectionExtensions - { - /// - /// Scans the calling assembly for all routes defined with AttributeRouting attributes, - /// using the default conventions. - /// - public static void MapHttpAttributeRoutes(this HttpRouteCollection routes) - { - var configuration = new HttpWebConfiguration(); - configuration.AddRoutesFromAssembly(Assembly.GetCallingAssembly()); - - routes.MapHttpAttributeRoutesInternal(configuration); - } - - /// - /// Scans the specified assemblies for all routes defined with AttributeRouting attributes, - /// and applies configuration options against the routes found. - /// - /// - /// The initialization action that builds the configuration object - public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, Action configurationAction) - { - var configuration = new HttpWebConfiguration(); - configurationAction.Invoke(configuration); - - routes.MapHttpAttributeRoutesInternal(configuration); - } - - /// - /// Scans the specified assemblies for all routes defined with AttributeRouting attributes, - /// and applies configuration options against the routes found. - /// - /// - /// The configuration object - public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, HttpWebConfiguration configuration) - { - routes.MapHttpAttributeRoutesInternal(configuration); - } - - private static void MapHttpAttributeRoutesInternal(this HttpRouteCollection routes, HttpWebConfiguration configuration) - { - new RouteBuilder(configuration).BuildAllRoutes() - .Cast() - .ToList() - .ForEach(r => routes.Add(r.RouteName, r)); - } - } +using System; +using System.Linq; +using System.Reflection; +using System.Web.Http; +using System.Web.Routing; +using AttributeRouting.Framework; +using AttributeRouting.Web.Http.Framework; + +namespace AttributeRouting.Web.Http.WebHost +{ + /// + /// Extensions to the System.Web.Http.HttpRouteCollection + /// + public static class HttpRouteCollectionExtensions + { + /// + /// Scans the calling assembly for all routes defined with AttributeRouting attributes, + /// using the default conventions. + /// + public static void MapHttpAttributeRoutes(this HttpRouteCollection routes) + { + var configuration = new HttpWebConfiguration(); + configuration.AddRoutesFromAssembly(Assembly.GetCallingAssembly()); + + routes.MapHttpAttributeRoutesInternal(configuration); + } + + /// + /// Scans the specified assemblies for all routes defined with AttributeRouting attributes, + /// and applies configuration options against the routes found. + /// + /// + /// The initialization action that builds the configuration object + public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, Action configurationAction) + { + var configuration = new HttpWebConfiguration(); + configurationAction.Invoke(configuration); + + if (configuration.InMemory) + { + var newConfig = new HttpWebConfiguration(inMemory: true); + configurationAction.Invoke(newConfig); + routes.MapHttpAttributeRoutesInternal(newConfig); + } + else + { + routes.MapHttpAttributeRoutesInternal(configuration); + } + } + + /// + /// Scans the specified assemblies for all routes defined with AttributeRouting attributes, + /// and applies configuration options against the routes found. + /// + /// + /// The configuration object + public static void MapHttpAttributeRoutes(this HttpRouteCollection routes, HttpWebConfiguration configuration) + { + routes.MapHttpAttributeRoutesInternal(configuration); + } + + private static void MapHttpAttributeRoutesInternal(this HttpRouteCollection routes, HttpWebConfiguration configuration) + { + new RouteBuilder(configuration).BuildAllRoutes() + .Cast() + .ToList() + .ForEach(r => routes.Add(r.RouteName, r)); + } + } } \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http.WebHost/HttpWebConfiguration.cs b/src/AttributeRouting.Web.Http.WebHost/HttpWebConfiguration.cs index 6f595b3..5b67417 100644 --- a/src/AttributeRouting.Web.Http.WebHost/HttpWebConfiguration.cs +++ b/src/AttributeRouting.Web.Http.WebHost/HttpWebConfiguration.cs @@ -6,14 +6,22 @@ namespace AttributeRouting.Web.Http.WebHost { public class HttpWebConfiguration : HttpConfigurationBase { - public HttpWebConfiguration() + public HttpWebConfiguration(bool inMemory = false) { - AttributeRouteFactory = new AttributeRouteFactory(this); - ParameterFactory = new RouteParameterFactory(); - RouteConstraintFactory = new RouteConstraintFactory(this); + if (inMemory) + { + Init(); + } + else + { + AttributeRouteFactory = new AttributeRouteFactory(this); + ParameterFactory = new RouteParameterFactory(); + RouteConstraintFactory = new RouteConstraintFactory(this); - RouteHandlerFactory = () => null; - RegisterDefaultInlineRouteConstraints(typeof(Web.Constraints.RegexRouteConstraint).Assembly); + RouteHandlerFactory = () => null; + RegisterDefaultInlineRouteConstraints( + typeof(Web.Constraints.RegexRouteConstraint).Assembly); + } } /// diff --git a/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj b/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj index 3434b26..a24caf7 100644 --- a/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj +++ b/src/AttributeRouting.Web.Http/AttributeRouting.Web.Http.csproj @@ -1,117 +1,123 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D} - Library - Properties - AttributeRouting.Web.Http - AttributeRouting.Web.Http - v4.0 - 512 - ..\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\AttributeRouting.Web.Http.xml - 1591, 1587 - - - true - - - AttributeRouting.snk - - - - False - ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll - - - - - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll - - - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll - - - - ..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll - - - - - - - - - - SharedAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {871a79cf-c705-4c6b-8938-f9aa1e02aea4} - AttributeRouting - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {CCDE9AD7-3822-4B0B-AA19-DF6698A85D3D} + Library + Properties + AttributeRouting.Web.Http + AttributeRouting.Web.Http + v4.0 + 512 + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\AttributeRouting.Web.Http.xml + 1591, 1587 + + + true + + + AttributeRouting.snk + + + + False + ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll + + + + + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll + + + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + + + + ..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll + + + + + + + + + + SharedAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {871a79cf-c705-4c6b-8938-f9aa1e02aea4} + AttributeRouting + + + + + --> \ No newline at end of file diff --git a/src/AttributeRouting.Web.Http.SelfHost/Constraints/InboundHttpMethodConstraint.cs b/src/AttributeRouting.Web.Http/Constraints/InboundHttpMethodConstraint.cs similarity index 92% rename from src/AttributeRouting.Web.Http.SelfHost/Constraints/InboundHttpMethodConstraint.cs rename to src/AttributeRouting.Web.Http/Constraints/InboundHttpMethodConstraint.cs index e4148ca..dd88006 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Constraints/InboundHttpMethodConstraint.cs +++ b/src/AttributeRouting.Web.Http/Constraints/InboundHttpMethodConstraint.cs @@ -5,7 +5,7 @@ using System.Web.Http.Routing; using AttributeRouting.Constraints; -namespace AttributeRouting.Web.Http.SelfHost.Constraints +namespace AttributeRouting.Web.Http.Constraints { public class InboundHttpMethodConstraint : HttpMethodConstraint, IInboundHttpMethodConstraint { diff --git a/src/AttributeRouting.Web.Http.SelfHost/Constraints/OptionalRouteConstraint.cs b/src/AttributeRouting.Web.Http/Constraints/OptionalRouteConstraint.cs similarity index 95% rename from src/AttributeRouting.Web.Http.SelfHost/Constraints/OptionalRouteConstraint.cs rename to src/AttributeRouting.Web.Http/Constraints/OptionalRouteConstraint.cs index f441268..e2a5500 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Constraints/OptionalRouteConstraint.cs +++ b/src/AttributeRouting.Web.Http/Constraints/OptionalRouteConstraint.cs @@ -5,7 +5,7 @@ using AttributeRouting.Constraints; using AttributeRouting.Helpers; -namespace AttributeRouting.Web.Http.SelfHost.Constraints +namespace AttributeRouting.Web.Http.Constraints { public class OptionalRouteConstraint : IOptionalRouteConstraint, IHttpRouteConstraint { diff --git a/src/AttributeRouting.Web.Http.SelfHost/Framework/AttributeRouteFactory.cs b/src/AttributeRouting.Web.Http/Framework/AttributeRouteFactory.cs similarity index 78% rename from src/AttributeRouting.Web.Http.SelfHost/Framework/AttributeRouteFactory.cs rename to src/AttributeRouting.Web.Http/Framework/AttributeRouteFactory.cs index b18ae51..b1c6f68 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Framework/AttributeRouteFactory.cs +++ b/src/AttributeRouting.Web.Http/Framework/AttributeRouteFactory.cs @@ -1,15 +1,14 @@ using System.Collections.Generic; using System.Web.Http.Routing; using AttributeRouting.Framework; -using AttributeRouting.Web.Http.Framework; -namespace AttributeRouting.Web.Http.SelfHost.Framework +namespace AttributeRouting.Web.Http.Framework { internal class AttributeRouteFactory : IAttributeRouteFactory { - private readonly HttpConfiguration _configuration; + private readonly HttpConfigurationBase _configuration; - public AttributeRouteFactory(HttpConfiguration configuration) + public AttributeRouteFactory(HttpConfigurationBase configuration) { _configuration = configuration; } diff --git a/src/AttributeRouting.Web.Http.SelfHost/Framework/RouteConstraintFactory.cs b/src/AttributeRouting.Web.Http/Framework/RouteConstraintFactory.cs similarity index 90% rename from src/AttributeRouting.Web.Http.SelfHost/Framework/RouteConstraintFactory.cs rename to src/AttributeRouting.Web.Http/Framework/RouteConstraintFactory.cs index 95494cc..9a1ff37 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Framework/RouteConstraintFactory.cs +++ b/src/AttributeRouting.Web.Http/Framework/RouteConstraintFactory.cs @@ -7,15 +7,14 @@ using AttributeRouting.Framework; using AttributeRouting.Helpers; using AttributeRouting.Web.Http.Constraints; -using AttributeRouting.Web.Http.SelfHost.Constraints; -namespace AttributeRouting.Web.Http.SelfHost.Framework +namespace AttributeRouting.Web.Http.Framework { public class RouteConstraintFactory : IRouteConstraintFactory { - private readonly HttpConfiguration _configuration; + private readonly HttpConfigurationBase _configuration; - public RouteConstraintFactory(HttpConfiguration configuration) + public RouteConstraintFactory(HttpConfigurationBase configuration) { _configuration = configuration; } diff --git a/src/AttributeRouting.Web.Http.SelfHost/Framework/RouteParameterFactory.cs b/src/AttributeRouting.Web.Http/Framework/RouteParameterFactory.cs similarity index 80% rename from src/AttributeRouting.Web.Http.SelfHost/Framework/RouteParameterFactory.cs rename to src/AttributeRouting.Web.Http/Framework/RouteParameterFactory.cs index b4676d7..c615801 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Framework/RouteParameterFactory.cs +++ b/src/AttributeRouting.Web.Http/Framework/RouteParameterFactory.cs @@ -1,7 +1,7 @@ using System.Web.Http; using AttributeRouting.Framework; -namespace AttributeRouting.Web.Http.SelfHost.Framework +namespace AttributeRouting.Web.Http.Framework { internal class RouteParameterFactory : IParameterFactory { diff --git a/src/AttributeRouting.Web.Http.SelfHost/HttpConfiguration.cs b/src/AttributeRouting.Web.Http/HttpConfiguration.cs similarity index 55% rename from src/AttributeRouting.Web.Http.SelfHost/HttpConfiguration.cs rename to src/AttributeRouting.Web.Http/HttpConfiguration.cs index 366db2e..adea2f5 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/HttpConfiguration.cs +++ b/src/AttributeRouting.Web.Http/HttpConfiguration.cs @@ -1,23 +1,15 @@ using System.Web.Http.Routing; using AttributeRouting.Framework; using AttributeRouting.Web.Http.Constraints; -using AttributeRouting.Web.Http.SelfHost.Framework; +using AttributeRouting.Web.Http.Framework; -namespace AttributeRouting.Web.Http.SelfHost +namespace AttributeRouting.Web.Http { public class HttpConfiguration : HttpConfigurationBase { public HttpConfiguration() { - AttributeRouteFactory = new AttributeRouteFactory(this); - RouteConstraintFactory = new RouteConstraintFactory(this); - ParameterFactory = new RouteParameterFactory(); - - RegisterDefaultInlineRouteConstraints(typeof(RegexRouteConstraint).Assembly); - - // Must turn on AutoGenerateRouteNames and use the Unique RouteNameBuilder for this to work out-of-the-box. - AutoGenerateRouteNames = true; - RouteNameBuilder = RouteNameBuilders.Unique; + Init(); } /// diff --git a/src/AttributeRouting.Web.Http/HttpConfigurationBase.cs b/src/AttributeRouting.Web.Http/HttpConfigurationBase.cs index 451132b..b03e095 100644 --- a/src/AttributeRouting.Web.Http/HttpConfigurationBase.cs +++ b/src/AttributeRouting.Web.Http/HttpConfigurationBase.cs @@ -3,6 +3,9 @@ using System.Threading; using System.Web.Http.Controllers; using System.Web.Http.Routing; +using AttributeRouting.Framework; +using AttributeRouting.Web.Http.Constraints; +using AttributeRouting.Web.Http.Framework; namespace AttributeRouting.Web.Http { @@ -13,6 +16,24 @@ protected HttpConfigurationBase() CurrentUICultureResolver = (ctx, data) => Thread.CurrentThread.CurrentUICulture.Name; } + public void Init() + { + AttributeRouteFactory = new AttributeRouteFactory(this); + RouteConstraintFactory = new RouteConstraintFactory(this); + ParameterFactory = new RouteParameterFactory(); + + RegisterDefaultInlineRouteConstraints(typeof(RegexRouteConstraint).Assembly); + + // Must turn on AutoGenerateRouteNames and use the Unique RouteNameBuilder for this to work out-of-the-box. + AutoGenerateRouteNames = true; + RouteNameBuilder = RouteNameBuilders.Unique; + } + + /// + /// Defines whether the Web API pipeline will run in memory or not. + /// + public bool InMemory { get; set; } + /// /// The message handler that will be the recipient of the request. /// From c072da88eaa48f26a92fc134f1387c9b206a6b9b Mon Sep 17 00:00:00 2001 From: Filip W Date: Thu, 21 Feb 2013 20:57:19 -0500 Subject: [PATCH 63/97] added tests tests for issue 191 --- .../Tests/BugFixTests.cs | 312 ++++++++++-------- 1 file changed, 177 insertions(+), 135 deletions(-) diff --git a/src/AttributeRouting.Specs/Tests/BugFixTests.cs b/src/AttributeRouting.Specs/Tests/BugFixTests.cs index 6e5f728..e1579e1 100644 --- a/src/AttributeRouting.Specs/Tests/BugFixTests.cs +++ b/src/AttributeRouting.Specs/Tests/BugFixTests.cs @@ -1,135 +1,177 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Web.Http; -using System.Web.Mvc; -using System.Web.Routing; -using AttributeRouting.Framework.Localization; -using AttributeRouting.Specs.Subjects; -using AttributeRouting.Specs.Subjects.Http; -using AttributeRouting.Web.Http.WebHost; -using AttributeRouting.Web.Logging; -using AttributeRouting.Web.Mvc; -using MvcContrib.TestHelper; -using NUnit.Framework; - -namespace AttributeRouting.Specs.Tests -{ - public class BugFixTests - { - [Test] - public void Issue161_Querystring_param_constraints_mucks_up_url_generation() - { - // re: issue #161 - - var routes = RouteTable.Routes; - routes.Clear(); - routes.MapAttributeRoutes(config => config.AddRoutesFromController()); - - var urlHelper = new UrlHelper(MockBuilder.BuildRequestContext()); - var routeValues = new { area = "Cms", culture = "en", p = 1 }; - var expectedUrl = urlHelper.Action("Index", "Issue161Test", routeValues); - - Assert.That(expectedUrl, Is.EqualTo("/en/Cms/Content/Items?p=1")); - } - - [Test] - public void Issue120_OData_style_http_url_bonks() - { - // re: issue #120 - - var httpRoutes = GlobalConfiguration.Configuration.Routes; - httpRoutes.Clear(); - httpRoutes.MapHttpAttributeRoutes(config => config.AddRoutesFromController()); - - // Just make sure we don't get an exception - Assert.That(httpRoutes.Count, Is.EqualTo(1)); - } - - [Test] - public void Issue102_Generating_two_routes_for_api_get_requests() - { - // re: issue #102 - - var httpRoutes = GlobalConfiguration.Configuration.Routes; - httpRoutes.Clear(); - httpRoutes.MapHttpAttributeRoutes(config => config.AddRoutesFromController()); - - var routes = RouteTable.Routes.Cast().ToList(); - - Assert.That(routes.Count, Is.EqualTo(2)); - } - - [Test] - public void Issue25_Ensure_that_incompletely_mocked_request_context_does_not_generate_error_in_determining_http_method() - { - // re: issue #25 - - RouteTable.Routes.Clear(); - RouteTable.Routes.MapAttributeRoutes(config => config.AddRoutesFromController()); - - "~/Index" - .ShouldMapTo( - x => x.Index()); - } - - [Test] - public void Issue43_Ensure_that_routes_with_optional_url_params_are_correctly_matched() - { - // re: issue #43 - - RouteTable.Routes.Clear(); - RouteTable.Routes.MapAttributeRoutes(config => config.AddRoutesFromController()); - - RouteTable.Routes.Cast().LogTo(Console.Out); - - "~/BugFixes/Gallery/_CenterImage" - .ShouldMapTo( - x => x.Issue43_OptionalParamsAreMucky(null, null, null, null)); - } - - [Test] - public void Issue53_Ensure_that_inbound_routing_works_when_contraining_by_culture() - { - // re: issue #53 - - var translations = new FluentTranslationProvider(); - translations.AddTranslations().ForController().RouteUrl(x => x.Index(), new Dictionary { { "pt", "Inicio" } }); - translations.AddTranslations().ForController().RouteUrl(x => x.Index(), new Dictionary { { "en", "Home" } }); - - RouteTable.Routes.Clear(); - RouteTable.Routes.MapAttributeRoutes(config => - { - config.AddRoutesFromController(); - config.AddTranslationProvider(translations); - config.ConstrainTranslatedRoutesByCurrentUICulture = true; - config.CurrentUICultureResolver = (httpContext, routeData) => - { - return (string)routeData.Values["culture"] - ?? Thread.CurrentThread.CurrentUICulture.Name; - }; - }); - - RouteTable.Routes.Cast().LogTo(Console.Out); - - "~/en/cms/home".ShouldMapTo(x => x.Index()); - Assert.That("~/en/cms/inicio".Route(), Is.Null); - Assert.That("~/pt/cms/home".Route(), Is.Null); - "~/pt/cms/inicio".ShouldMapTo(x => x.Index()); - } - - [Test] - public void Issue84_Ensure_that_async_controller_action_can_be_mapped() - { - // re: issue #84 - RouteTable.Routes.Clear(); - RouteTable.Routes.MapAttributeRoutes(config => config.AddRoutesFromController()); - - "~/WithAsync/Synchronous".ShouldMapTo(x => x.Test1()); - var asyncRouteData = "~/WithAsync/NotSynchronous".Route(); - asyncRouteData.Values["controller"].ShouldEqual("AsyncAction", "Asynchronous route does not map to the AsyncActionController."); - asyncRouteData.Values["action"].ShouldEqual("Test2", "Asynchronous route does not map to the correct action method."); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Web.Http; +using System.Web.Mvc; +using System.Web.Routing; +using AttributeRouting.Framework.Localization; +using AttributeRouting.Specs.Subjects; +using AttributeRouting.Specs.Subjects.Http; +using AttributeRouting.Web.Http.Constraints; +using AttributeRouting.Web.Http.WebHost; +using AttributeRouting.Web.Logging; +using AttributeRouting.Web.Mvc; +using MvcContrib.TestHelper; +using NUnit.Framework; + +namespace AttributeRouting.Specs.Tests +{ + public class BugFixTests + { + [Test] + public void Issue161_Querystring_param_constraints_mucks_up_url_generation() + { + // re: issue #161 + + var routes = RouteTable.Routes; + routes.Clear(); + routes.MapAttributeRoutes(config => config.AddRoutesFromController()); + + var urlHelper = new UrlHelper(MockBuilder.BuildRequestContext()); + var routeValues = new { area = "Cms", culture = "en", p = 1 }; + var expectedUrl = urlHelper.Action("Index", "Issue161Test", routeValues); + + Assert.That(expectedUrl, Is.EqualTo("/en/Cms/Content/Items?p=1")); + } + + [Test] + public void Issue120_OData_style_http_url_bonks() + { + // re: issue #120 + + var httpRoutes = GlobalConfiguration.Configuration.Routes; + httpRoutes.Clear(); + httpRoutes.MapHttpAttributeRoutes(config => config.AddRoutesFromController()); + + // Just make sure we don't get an exception + Assert.That(httpRoutes.Count, Is.EqualTo(1)); + } + + [Test] + public void Issue102_Generating_two_routes_for_api_get_requests() + { + // re: issue #102 + + var httpRoutes = GlobalConfiguration.Configuration.Routes; + httpRoutes.Clear(); + httpRoutes.MapHttpAttributeRoutes(config => config.AddRoutesFromController()); + + var routes = RouteTable.Routes.Cast().ToList(); + + Assert.That(routes.Count, Is.EqualTo(2)); + } + + [Test] + public void Issue25_Ensure_that_incompletely_mocked_request_context_does_not_generate_error_in_determining_http_method() + { + // re: issue #25 + + RouteTable.Routes.Clear(); + RouteTable.Routes.MapAttributeRoutes(config => config.AddRoutesFromController()); + + "~/Index" + .ShouldMapTo( + x => x.Index()); + } + + [Test] + public void Issue43_Ensure_that_routes_with_optional_url_params_are_correctly_matched() + { + // re: issue #43 + + RouteTable.Routes.Clear(); + RouteTable.Routes.MapAttributeRoutes(config => config.AddRoutesFromController()); + + RouteTable.Routes.Cast().LogTo(Console.Out); + + "~/BugFixes/Gallery/_CenterImage" + .ShouldMapTo( + x => x.Issue43_OptionalParamsAreMucky(null, null, null, null)); + } + + [Test] + public void Issue53_Ensure_that_inbound_routing_works_when_contraining_by_culture() + { + // re: issue #53 + + var translations = new FluentTranslationProvider(); + translations.AddTranslations().ForController().RouteUrl(x => x.Index(), new Dictionary { { "pt", "Inicio" } }); + translations.AddTranslations().ForController().RouteUrl(x => x.Index(), new Dictionary { { "en", "Home" } }); + + RouteTable.Routes.Clear(); + RouteTable.Routes.MapAttributeRoutes(config => + { + config.AddRoutesFromController(); + config.AddTranslationProvider(translations); + config.ConstrainTranslatedRoutesByCurrentUICulture = true; + config.CurrentUICultureResolver = (httpContext, routeData) => + { + return (string)routeData.Values["culture"] + ?? Thread.CurrentThread.CurrentUICulture.Name; + }; + }); + + RouteTable.Routes.Cast().LogTo(Console.Out); + + "~/en/cms/home".ShouldMapTo(x => x.Index()); + Assert.That("~/en/cms/inicio".Route(), Is.Null); + Assert.That("~/pt/cms/home".Route(), Is.Null); + "~/pt/cms/inicio".ShouldMapTo(x => x.Index()); + } + + [Test] + public void Issue84_Ensure_that_async_controller_action_can_be_mapped() + { + // re: issue #84 + RouteTable.Routes.Clear(); + RouteTable.Routes.MapAttributeRoutes(config => config.AddRoutesFromController()); + + "~/WithAsync/Synchronous".ShouldMapTo(x => x.Test1()); + var asyncRouteData = "~/WithAsync/NotSynchronous".Route(); + asyncRouteData.Values["controller"].ShouldEqual("AsyncAction", "Asynchronous route does not map to the AsyncActionController."); + asyncRouteData.Values["action"].ShouldEqual("Test2", "Asynchronous route does not map to the correct action method."); + } + + [Test] + public void Issue191_in_memory_config_initializes_routes_with_general_http_constraints() + { + var inMemoryConfig = new HttpConfiguration(); + + inMemoryConfig.Routes.MapHttpAttributeRoutes(x => + { + x.InMemory = true; + x.AddRoutesFromController(); + }); + + Assert.AreEqual(6, inMemoryConfig.Routes.Count); + Assert.True(inMemoryConfig.Routes.All(x => x.Constraints.All(c => c.Value.GetType() == typeof(InboundHttpMethodConstraint)))); + } + + [Test] + public void Issue191_default_web_config_initializes_routes_with_web_http_constraints() + { + var inMemoryConfig = GlobalConfiguration.Configuration; + inMemoryConfig.Routes.Clear(); + + inMemoryConfig.Routes.MapHttpAttributeRoutes(x => x.AddRoutesFromController()); + + Assert.AreEqual(6, inMemoryConfig.Routes.Count); + Assert.True(inMemoryConfig.Routes.All(x => x.Constraints.All(c => c.Value.GetType() == typeof(Web.Http.WebHost.Constraints.InboundHttpMethodConstraint)))); + } + + [Test] + public void Issue191_in_memory_web_config_inits_general_http_constraint_factory() + { + var inMemoryConfig = new HttpWebConfiguration(inMemory:true); + Assert.IsAssignableFrom(inMemoryConfig.RouteConstraintFactory); + } + + [Test] + public void Issue191_default_web_config_inits_web_http_constraint_factory() + { + var inMemoryConfig = new HttpWebConfiguration(); + Assert.IsAssignableFrom(inMemoryConfig.RouteConstraintFactory); + } + } +} From f43eab03021ffdb490381ab55d8b204b109ffcc1 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sat, 23 Feb 2013 19:04:45 -0700 Subject: [PATCH 64/97] cleaning up --- .../Framework/AttributeRouteVisitor.cs | 35 +++++++++++------ .../Framework/RouteBuilder.cs | 39 ++++++++++++------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/AttributeRouting/Framework/AttributeRouteVisitor.cs b/src/AttributeRouting/Framework/AttributeRouteVisitor.cs index 567c02e..739c1aa 100644 --- a/src/AttributeRouting/Framework/AttributeRouteVisitor.cs +++ b/src/AttributeRouting/Framework/AttributeRouteVisitor.cs @@ -16,10 +16,14 @@ namespace AttributeRouting.Framework /// System.Web.Routing (used for MVC controller routes) and /// System.Web.Http.Routing (used for Web API controller routes). /// - public class AttributeRouteVisitor - { + public class AttributeRouteVisitor + { + private static readonly Lazy PathAndQueryRegex = + new Lazy(() => new Regex(@"(?[^\?]*)(?\?.*)?")); + private readonly IAttributeRoute _route; - private readonly ConfigurationBase _configuration; + private readonly ConfigurationBase _configuration; + private string _staticLeftPartOfUrl; /// /// Creates a new visitor extending implementations of IAttributeRoute with common logic. @@ -34,6 +38,21 @@ public AttributeRouteVisitor(IAttributeRoute route, ConfigurationBase configurat _route = route; _configuration = configuration; } + + private string StaticLeftPartOfUrl + { + get + { + if (_staticLeftPartOfUrl == null) + { + var routePath = _route.Url; + var indexOfFirstParam = routePath.IndexOf("{", StringComparison.OrdinalIgnoreCase); + var leftPart = (indexOfFirstParam == -1) ? routePath : routePath.Substring(0, indexOfFirstParam); + _staticLeftPartOfUrl = leftPart.TrimEnd('/'); + } + return _staticLeftPartOfUrl; + } + } /// /// Performs lowercasing and appends trailing slash if this route is so configured. @@ -226,15 +245,9 @@ public bool IsCultureNameMatched(string cultureName) /// Thanks: http://samsaffron.com/archive/2011/10/13/optimising-asp-net-mvc3-routing public bool IsStaticLeftPartOfUrlMatched(string requestedPath) { - // Get the static left part of the route's url. - var routePath = _route.Url; - var indexOfFirstParam = routePath.IndexOf("{", StringComparison.OrdinalIgnoreCase); - var leftPart = indexOfFirstParam == -1 ? routePath : routePath.Substring(0, indexOfFirstParam); - var staticLeftPartOfUrl = leftPart.TrimEnd('/'); - // Compare the left part with the requested path var comparableRequestedPath = requestedPath.TrimEnd('/'); - return comparableRequestedPath.StartsWith(staticLeftPartOfUrl, StringComparison.OrdinalIgnoreCase); + return comparableRequestedPath.StartsWith(StaticLeftPartOfUrl, StringComparison.OrdinalIgnoreCase); } /// @@ -283,7 +296,7 @@ private static string AppendTrailingSlashToVirtualPath(string virtualPath) private static void GetPathAndQuery(string virtualPath, out string path, out string query) { // NOTE: Do not lowercase the querystring vals - var match = Regex.Match(virtualPath, @"(?[^\?]*)(?\?.*)?"); + var match = PathAndQueryRegex.Value.Match(virtualPath); // Just covering my backside here in case the regex fails for some reason. if (!match.Success) diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 8170dbc..5b9f212 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -11,8 +11,26 @@ namespace AttributeRouting.Framework /// Creates objects according to the /// options set in implementations of . /// - public class RouteBuilder - { + public class RouteBuilder + { + private static readonly Lazy ConstraintParamsRegex = + new Lazy(() => new Regex(@"^.*\(.*\)$", RegexOptions.Compiled)); + + private static readonly Lazy DetokenizeUrlRegex = + new Lazy(() => + { + var patterns = new List + { + @"(?<=\{)\?", // leading question mark (used to specify optional param) + @"\?(?=\})", // trailing question mark (used to specify optional param) + @"\(.*?\)(?=\})", // stuff inside parens (used to specify inline regex route constraint) + @"\:(.*?)(\(.*?\))?((?=\})|(?=\?\}))", // new inline constraint syntax + @"(?<=\{.*)=.*?(?=\})", // equals and value (used to specify inline parameter default value) + }; + var pattern = String.Join("|", patterns); + return new Regex(pattern); + }); + private readonly ConfigurationBase _configuration; private readonly IParameterFactory _parameterFactory; private readonly IAttributeRouteFactory _routeFactory; @@ -167,7 +185,7 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro string constraintName; object constraint; - if (Regex.IsMatch(definition, @"^.*\(.*\)$")) + if (ConstraintParamsRegex.Value.IsMatch(definition)) { // Constraint of the form "firstName:string(50)" var indexOfOpenParen = definition.IndexOf('('); @@ -458,16 +476,7 @@ private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUr private static string DetokenizeUrl(string url) { - var patterns = new List - { - @"(?<=\{)\?", // leading question mark (used to specify optional param) - @"\?(?=\})", // trailing question mark (used to specify optional param) - @"\(.*?\)(?=\})", // stuff inside parens (used to specify inline regex route constraint) - @"\:(.*?)(\(.*?\))?((?=\})|(?=\?\}))", // new inline constraint syntax - @"(?<=\{.*)=.*?(?=\})", // equals and value (used to specify inline parameter default value) - }; - - return Regex.Replace(url, String.Join("|", patterns), ""); + return DetokenizeUrlRegex.Value.Replace(url, ""); } private static IEnumerable GetUrlParameterContents(string url) @@ -507,8 +516,8 @@ private static IEnumerable GetUrlParameterContents(string url) } } - private static string RemoveQueryString(string url) - { + private static string RemoveQueryString(string url) + { // Must honor ? in regex expressions and as used to specify optional params, // So run through the url chars and fast forward when inside a url param (eg: {...}) for (int i = 0, length = url.Length; i < length; i++) From 4884f580abdfa0506293a303d90b72d2b438cf81 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Thu, 28 Feb 2013 11:07:26 -0700 Subject: [PATCH 65/97] updated readme and asembly info for 3.5 --- README.textile | 5 +++++ src/SharedAssemblyInfo.cs | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.textile b/README.textile index 142ff3d..0c0676d 100644 --- a/README.textile +++ b/README.textile @@ -4,6 +4,11 @@ h2. Please refer to "attributerouting.net":http://attributerouting.net/ for docu h3. Changelog +_3.5_ + +* #191 - Enabled in-memory hosting of web-hosted web api routes. +* Various perf enhancements and code reorganization. + _3.4.2_ * #192 - BUG FIX: Optional parameters weren't working in asp.net web api action urls. diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index b19e4c9..ae5bfcc 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyProduct("AttributeRouting")] [assembly: AssemblyCopyright("Copyright 2010-2013 Tim McCall")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.4.2")] -[assembly: AssemblyFileVersion("3.4.2")] -[assembly: AssemblyInformationalVersion("3.4.2")] +[assembly: AssemblyVersion("3.5")] +[assembly: AssemblyFileVersion("3.5")] +[assembly: AssemblyInformationalVersion("3.5")] [assembly: AssemblyConfiguration("Release")] From 59230ea7ec49bb5d9202e80f31f4085df46dca39 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sat, 2 Mar 2013 11:24:39 -0700 Subject: [PATCH 66/97] #206 - changed the name of the app start files to prevent ns conflicts. --- .../AttributeRouting.WebApi.Hosted.nutrans | 4 ++-- .../{AttributeRouting.cs.pp => AttributeRoutingConfig.cs.pp} | 2 +- .../{AttributeRouting.vb.pp => AttributeRoutingConfig.vb.pp} | 2 +- nuget/AttributeRouting.WebApi.Hosted/install.ps1 | 4 ++-- nuget/AttributeRouting.WebApi/AttributeRouting.WebApi.nutrans | 4 ++-- ...buteRoutingHttp.cs.pp => AttributeRoutingHttpConfig.cs.pp} | 4 ++-- ...buteRoutingHttp.vb.pp => AttributeRoutingHttpConfig.vb.pp} | 4 ++-- nuget/AttributeRouting.WebApi/install.ps1 | 4 ++-- nuget/AttributeRouting/AttributeRouting.nutrans | 4 ++-- .../{AttributeRouting.cs.pp => AttributeRoutingConfig.cs.pp} | 4 ++-- .../{AttributeRouting.vb.pp => AttributeRoutingConfig.vb.pp} | 4 ++-- nuget/AttributeRouting/install.ps1 | 4 ++-- 12 files changed, 22 insertions(+), 22 deletions(-) rename nuget/AttributeRouting.WebApi.Hosted/{AttributeRouting.cs.pp => AttributeRoutingConfig.cs.pp} (93%) rename nuget/AttributeRouting.WebApi.Hosted/{AttributeRouting.vb.pp => AttributeRoutingConfig.vb.pp} (94%) rename nuget/AttributeRouting.WebApi/{AttributeRoutingHttp.cs.pp => AttributeRoutingHttpConfig.cs.pp} (85%) rename nuget/AttributeRouting.WebApi/{AttributeRoutingHttp.vb.pp => AttributeRoutingHttpConfig.vb.pp} (87%) rename nuget/AttributeRouting/{AttributeRouting.cs.pp => AttributeRoutingConfig.cs.pp} (85%) rename nuget/AttributeRouting/{AttributeRouting.vb.pp => AttributeRoutingConfig.vb.pp} (87%) diff --git a/nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.WebApi.Hosted.nutrans b/nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.WebApi.Hosted.nutrans index 36bc6ff..ee78294 100644 --- a/nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.WebApi.Hosted.nutrans +++ b/nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.WebApi.Hosted.nutrans @@ -16,8 +16,8 @@ - - + + \ No newline at end of file diff --git a/nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.cs.pp b/nuget/AttributeRouting.WebApi.Hosted/AttributeRoutingConfig.cs.pp similarity index 93% rename from nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.cs.pp rename to nuget/AttributeRouting.WebApi.Hosted/AttributeRoutingConfig.cs.pp index 5d727f7..6ffbb81 100644 --- a/nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.cs.pp +++ b/nuget/AttributeRouting.WebApi.Hosted/AttributeRoutingConfig.cs.pp @@ -3,7 +3,7 @@ namespace $rootnamespace$ { - public static class AttributeRouting + public static class AttributeRoutingConfig { // Call this static method from a start up class in your applicaton (e.g.Program.cs) // Pass in the configuration you're using for your self-hosted Web API diff --git a/nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.vb.pp b/nuget/AttributeRouting.WebApi.Hosted/AttributeRoutingConfig.vb.pp similarity index 94% rename from nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.vb.pp rename to nuget/AttributeRouting.WebApi.Hosted/AttributeRoutingConfig.vb.pp index 870bf2d..5d1921c 100644 --- a/nuget/AttributeRouting.WebApi.Hosted/AttributeRouting.vb.pp +++ b/nuget/AttributeRouting.WebApi.Hosted/AttributeRoutingConfig.vb.pp @@ -2,7 +2,7 @@ Imports AttributeRouting.Web.Http.SelfHost Namespace $rootnamespace$ - Public Class AttributeRouting + Public Class AttributeRoutingConfig ' Call this static method from a start up class in your applicaton (e.g.Program.cs) ' Pass in the configuration you're using for your self-hosted Web API diff --git a/nuget/AttributeRouting.WebApi.Hosted/install.ps1 b/nuget/AttributeRouting.WebApi.Hosted/install.ps1 index 0871aa9..7ff35bd 100644 --- a/nuget/AttributeRouting.WebApi.Hosted/install.ps1 +++ b/nuget/AttributeRouting.WebApi.Hosted/install.ps1 @@ -2,7 +2,7 @@ # Remove the App_Start file for other languages if ($project.Type -eq "C#") { - $project.ProjectItems.Item("AttributeRouting.vb").Delete(); + $project.ProjectItems.Item("AttributeRoutingConfig.vb").Delete(); } elseif ($project.Type -eq "VB.NET") { - $project.ProjectItems.Item("AttributeRouting.cs").Delete(); + $project.ProjectItems.Item("AttributeRoutingConfig.cs").Delete(); } \ No newline at end of file diff --git a/nuget/AttributeRouting.WebApi/AttributeRouting.WebApi.nutrans b/nuget/AttributeRouting.WebApi/AttributeRouting.WebApi.nutrans index 9669fa3..8abb26b 100644 --- a/nuget/AttributeRouting.WebApi/AttributeRouting.WebApi.nutrans +++ b/nuget/AttributeRouting.WebApi/AttributeRouting.WebApi.nutrans @@ -18,8 +18,8 @@ - - + + diff --git a/nuget/AttributeRouting.WebApi/AttributeRoutingHttp.cs.pp b/nuget/AttributeRouting.WebApi/AttributeRoutingHttpConfig.cs.pp similarity index 85% rename from nuget/AttributeRouting.WebApi/AttributeRoutingHttp.cs.pp rename to nuget/AttributeRouting.WebApi/AttributeRoutingHttpConfig.cs.pp index 30685a9..dee2cb4 100644 --- a/nuget/AttributeRouting.WebApi/AttributeRoutingHttp.cs.pp +++ b/nuget/AttributeRouting.WebApi/AttributeRoutingHttpConfig.cs.pp @@ -1,11 +1,11 @@ using System.Web.Http; using AttributeRouting.Web.Http.WebHost; -[assembly: WebActivator.PreApplicationStartMethod(typeof($rootnamespace$.AttributeRoutingHttp), "Start")] +[assembly: WebActivator.PreApplicationStartMethod(typeof($rootnamespace$.AttributeRoutingHttpConfig), "Start")] namespace $rootnamespace$ { - public static class AttributeRoutingHttp + public static class AttributeRoutingHttpConfig { public static void RegisterRoutes(HttpRouteCollection routes) { diff --git a/nuget/AttributeRouting.WebApi/AttributeRoutingHttp.vb.pp b/nuget/AttributeRouting.WebApi/AttributeRoutingHttpConfig.vb.pp similarity index 87% rename from nuget/AttributeRouting.WebApi/AttributeRoutingHttp.vb.pp rename to nuget/AttributeRouting.WebApi/AttributeRoutingHttpConfig.vb.pp index 43c1cbe..d2847bb 100644 --- a/nuget/AttributeRouting.WebApi/AttributeRoutingHttp.vb.pp +++ b/nuget/AttributeRouting.WebApi/AttributeRoutingHttpConfig.vb.pp @@ -1,10 +1,10 @@ Imports System.Web.Http Imports AttributeRouting.Web.Http.WebHost - + Namespace $rootnamespace$ - Public Class AttributeRoutingHttp + Public Class AttributeRoutingHttpConfig Public Shared Sub RegisterRoutes(routes As HttpRouteCollection) ' See http://github.com/mccalltd/AttributeRouting/wiki for more options. diff --git a/nuget/AttributeRouting.WebApi/install.ps1 b/nuget/AttributeRouting.WebApi/install.ps1 index 93dbf73..b979922 100644 --- a/nuget/AttributeRouting.WebApi/install.ps1 +++ b/nuget/AttributeRouting.WebApi/install.ps1 @@ -4,7 +4,7 @@ $appStartTemplatesFolder = $project.ProjectItems.Item("App_Start"); # Remove the App_Start file for other languages if ($project.Type -eq "C#") { - $appStartTemplatesFolder.ProjectItems.Item("AttributeRoutingHttp.vb").Delete(); + $appStartTemplatesFolder.ProjectItems.Item("AttributeRoutingHttpConfig.vb").Delete(); } elseif ($project.Type -eq "VB.NET") { - $appStartTemplatesFolder.ProjectItems.Item("AttributeRoutingHttp.cs").Delete(); + $appStartTemplatesFolder.ProjectItems.Item("AttributeRoutingHttpConfig.cs").Delete(); } \ No newline at end of file diff --git a/nuget/AttributeRouting/AttributeRouting.nutrans b/nuget/AttributeRouting/AttributeRouting.nutrans index 6adfdb6..7629458 100644 --- a/nuget/AttributeRouting/AttributeRouting.nutrans +++ b/nuget/AttributeRouting/AttributeRouting.nutrans @@ -16,8 +16,8 @@ - - + + diff --git a/nuget/AttributeRouting/AttributeRouting.cs.pp b/nuget/AttributeRouting/AttributeRoutingConfig.cs.pp similarity index 85% rename from nuget/AttributeRouting/AttributeRouting.cs.pp rename to nuget/AttributeRouting/AttributeRoutingConfig.cs.pp index 56d76cf..8458862 100644 --- a/nuget/AttributeRouting/AttributeRouting.cs.pp +++ b/nuget/AttributeRouting/AttributeRoutingConfig.cs.pp @@ -1,11 +1,11 @@ using System.Web.Routing; using AttributeRouting.Web.Mvc; -[assembly: WebActivator.PreApplicationStartMethod(typeof($rootnamespace$.AttributeRouting), "Start")] +[assembly: WebActivator.PreApplicationStartMethod(typeof($rootnamespace$.AttributeRoutingConfig), "Start")] namespace $rootnamespace$ { - public static class AttributeRouting + public static class AttributeRoutingConfig { public static void RegisterRoutes(RouteCollection routes) { diff --git a/nuget/AttributeRouting/AttributeRouting.vb.pp b/nuget/AttributeRouting/AttributeRoutingConfig.vb.pp similarity index 87% rename from nuget/AttributeRouting/AttributeRouting.vb.pp rename to nuget/AttributeRouting/AttributeRoutingConfig.vb.pp index 5c44b81..91ff486 100644 --- a/nuget/AttributeRouting/AttributeRouting.vb.pp +++ b/nuget/AttributeRouting/AttributeRoutingConfig.vb.pp @@ -1,10 +1,10 @@ Imports System.Web.Routing Imports AttributeRouting.Web.Mvc - + Namespace $rootnamespace$ - Public Class AttributeRouting + Public Class AttributeRoutingConfig Public Shared Sub RegisterRoutes(routes As RouteCollection) ' See http://github.com/mccalltd/AttributeRouting/wiki for more options. diff --git a/nuget/AttributeRouting/install.ps1 b/nuget/AttributeRouting/install.ps1 index b97e57d..d1b6d64 100644 --- a/nuget/AttributeRouting/install.ps1 +++ b/nuget/AttributeRouting/install.ps1 @@ -4,7 +4,7 @@ $appStartTemplatesFolder = $project.ProjectItems.Item("App_Start"); # Remove the App_Start file for other languages if ($project.Type -eq "C#") { - $appStartTemplatesFolder.ProjectItems.Item("AttributeRouting.vb").Delete(); + $appStartTemplatesFolder.ProjectItems.Item("AttributeRoutingConfig.vb").Delete(); } elseif ($project.Type -eq "VB.NET") { - $appStartTemplatesFolder.ProjectItems.Item("AttributeRouting.cs").Delete(); + $appStartTemplatesFolder.ProjectItems.Item("AttributeRoutingConfig.cs").Delete(); } \ No newline at end of file From 31f894f0c95fd91afbcaeea8416205d355e5ca6b Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sat, 2 Mar 2013 12:29:00 -0700 Subject: [PATCH 67/97] #199 - when applying inbound http method constraint, now checking the unvalidated form/query collection. --- .../Controllers/DangerController.cs | 3 +- .../InboundHttpMethodConstraint.cs | 8 ++ .../AttributeRouting.Web.Mvc.csproj | 25 ++++++ .../InboundHttpMethodConstraint.cs | 54 +++++++++++-- src/AttributeRouting.Web.Mvc/packages.config | 6 ++ .../AttributeRouting.Web.csproj | 1 - .../Helpers/HttpRequestBaseExtensions.cs | 80 ------------------- 7 files changed, 87 insertions(+), 90 deletions(-) create mode 100644 src/AttributeRouting.Web.Mvc/packages.config delete mode 100644 src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs diff --git a/src/AttributeRouting.Tests.Web/Controllers/DangerController.cs b/src/AttributeRouting.Tests.Web/Controllers/DangerController.cs index 0906b04..9d4b0b7 100644 --- a/src/AttributeRouting.Tests.Web/Controllers/DangerController.cs +++ b/src/AttributeRouting.Tests.Web/Controllers/DangerController.cs @@ -13,7 +13,8 @@ public ActionResult Index() } [POST("")] - public ActionResult Index_Post() + [ValidateInput(false)] + public ActionResult Index_Post(string badstuff) { return Content("You survived the danger!"); } diff --git a/src/AttributeRouting.Web.Http/Constraints/InboundHttpMethodConstraint.cs b/src/AttributeRouting.Web.Http/Constraints/InboundHttpMethodConstraint.cs index dd88006..f24b482 100644 --- a/src/AttributeRouting.Web.Http/Constraints/InboundHttpMethodConstraint.cs +++ b/src/AttributeRouting.Web.Http/Constraints/InboundHttpMethodConstraint.cs @@ -20,6 +20,14 @@ public InboundHttpMethodConstraint(params HttpMethod[] allowedMethods) ICollection IInboundHttpMethodConstraint.AllowedMethods { get { return new ReadOnlyCollection(AllowedMethods.Select(method => method.Method).ToList()); } + } + + protected override bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary values, HttpRouteDirection routeDirection) + { + if (routeDirection == HttpRouteDirection.UriGeneration) + return true; + + return base.Match(request, route, parameterName, values, routeDirection); } } } \ No newline at end of file diff --git a/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj b/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj index cd3d920..3445cce 100644 --- a/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj +++ b/src/AttributeRouting.Web.Mvc/AttributeRouting.Web.Mvc.csproj @@ -43,12 +43,36 @@ AttributeRouting.snk + + True + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.1.0.20105.408\lib\net40\System.Web.Helpers.dll + + + True + ..\packages\Microsoft.AspNet.Razor.1.0.20105.408\lib\net40\System.Web.Razor.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.1.0.20105.408\lib\net40\System.Web.WebPages.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.1.0.20105.408\lib\net40\System.Web.WebPages.Deployment.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.1.0.20105.408\lib\net40\System.Web.WebPages.Razor.dll + @@ -78,6 +102,7 @@ + diff --git a/src/AttributeRouting.Web.Mvc/Constraints/InboundHttpMethodConstraint.cs b/src/AttributeRouting.Web.Mvc/Constraints/InboundHttpMethodConstraint.cs index 0b48fd3..610347c 100644 --- a/src/AttributeRouting.Web.Mvc/Constraints/InboundHttpMethodConstraint.cs +++ b/src/AttributeRouting.Web.Mvc/Constraints/InboundHttpMethodConstraint.cs @@ -1,10 +1,10 @@ using System.Linq; -using System.Web; -using System.Web.Mvc; +using System.Web; +using System.Web.Helpers; using System.Web.Routing; using AttributeRouting.Constraints; -using AttributeRouting.Helpers; - +using AttributeRouting.Helpers; + namespace AttributeRouting.Web.Mvc.Constraints { public class InboundHttpMethodConstraint : HttpMethodConstraint, IInboundHttpMethodConstraint @@ -17,16 +17,54 @@ public InboundHttpMethodConstraint(params string[] allowedMethods) { } - protected override bool Match(HttpContextBase httpContext, Route route, string parameterName, - RouteValueDictionary values, RouteDirection routeDirection) + protected override bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { if (routeDirection == RouteDirection.UrlGeneration) return true; - // Make sure to check for HTTP method overrides! - var httpMethod = httpContext.Request.GetHttpMethodOverride(); + // Make sure to check for HTTP method overrides! + var httpMethod = GetUnvalidatedHttpMethodOverride(httpContext.Request); return AllowedMethods.Any(m => m.ValueEquals(httpMethod)); + } + + /// + /// The reason we have our own is to provide support for System.Web.Helpers.Validation.Unvalidated calls. + /// NOTE: This won't be needed once AR targets .NET 4.5. + /// + private static string GetUnvalidatedHttpMethodOverride(HttpRequestBase request) + { + var httpMethod = request.HttpMethod; + + // If not a post, method overrides don't apply. + if (!httpMethod.ValueEquals("POST")) + { + return httpMethod; + } + + // Get the method override and if it's not for a GET or POST, then apply it. + var methodOverride = request.SafeGet(r => r.Headers["X-HTTP-Method-Override"]) ?? + request.SafeGet(r => GetFormValue(r, "X-HTTP-Method-Override")) ?? + request.SafeGet(r => GetQueryStringValue(r, "X-HTTP-Method-Override")); + + if (methodOverride != null && + (!methodOverride.ValueEquals("GET") && !methodOverride.ValueEquals("POST"))) + { + return methodOverride; + } + + // Otherwise, just return the http method. + return httpMethod; + } + + private static string GetFormValue(HttpRequestBase request, string key) + { + return request.Unvalidated().QueryString[key] ?? request.Form[key]; + } + + private static string GetQueryStringValue(HttpRequestBase request, string key) + { + return request.Unvalidated().Form[key] ?? request.QueryString[key]; } } } \ No newline at end of file diff --git a/src/AttributeRouting.Web.Mvc/packages.config b/src/AttributeRouting.Web.Mvc/packages.config new file mode 100644 index 0000000..77411dd --- /dev/null +++ b/src/AttributeRouting.Web.Mvc/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/AttributeRouting.Web/AttributeRouting.Web.csproj b/src/AttributeRouting.Web/AttributeRouting.Web.csproj index 7d13799..9bb3a21 100644 --- a/src/AttributeRouting.Web/AttributeRouting.Web.csproj +++ b/src/AttributeRouting.Web/AttributeRouting.Web.csproj @@ -74,7 +74,6 @@ - diff --git a/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs b/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs deleted file mode 100644 index 6f48dbe..0000000 --- a/src/AttributeRouting.Web/Helpers/HttpRequestBaseExtensions.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Collections.Specialized; -using System.Reflection; -using System.Web; -using AttributeRouting.Helpers; - -namespace AttributeRouting.Web.Helpers -{ - public static class HttpRequestBaseExtensions - { - private static bool _isSystemWebWebPagesUnavailable; - - public static string GetFormValue(this HttpRequestBase request, string key) - { - return request.GetUnvalidatedCollectionValue("Form", key) ?? request.Form[key]; - } - - public static string GetQueryStringValue(this HttpRequestBase request, string key) - { - return request.GetUnvalidatedCollectionValue("QueryString", key) ?? request.QueryString[key]; - } - - /// - /// Loads the Form or QueryString collection value from the unvalidated object in System.Web.Webpages, - /// if that assembly is available. - /// - private static string GetUnvalidatedCollectionValue(this HttpRequestBase request, string unvalidatedObjectPropertyName, string key) - { - if (_isSystemWebWebPagesUnavailable) - { - return null; - } - - try - { - var webPagesAssembly = Assembly.Load("System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"); - var validationType = webPagesAssembly.GetType("System.Web.Helpers.Validation"); - var unvalidatedMethod = validationType.GetMethod("Unvalidated", new[] { request.GetType() }); - var unvalidatedObject = unvalidatedMethod.Invoke(null, new[] { request }); - var collectionProperty = unvalidatedObject.GetType().GetProperty(unvalidatedObjectPropertyName); - var collection = (NameValueCollection)collectionProperty.GetValue(unvalidatedObject, null); - - return collection[key]; - } - catch - { - _isSystemWebWebPagesUnavailable = true; - - return null; - } - } - - /// - /// The reason we have our own is to provide support for System.Web.Helpers.Validation.Unvalidated calls. - /// - public static string GetHttpMethod(this HttpRequestBase request) - { - var httpMethod = request.HttpMethod; - - // If not a post, method overrides don't apply. - if (!httpMethod.ValueEquals("POST")) - { - return httpMethod; - } - - // Get the method override and if it's not for a GET or POST, then apply it. - var methodOverride = request.SafeGet(r => r.Headers["X-HTTP-Method-Override"]) ?? - request.SafeGet(r => GetFormValue(r, "X-HTTP-Method-Override")) ?? - request.SafeGet(r => GetQueryStringValue(r, "X-HTTP-Method-Override")); - - if (methodOverride != null && - (!methodOverride.ValueEquals("GET") && !methodOverride.ValueEquals("POST"))) - { - return methodOverride; - } - - // Otherwise, just return the http method. - return httpMethod; - } - } -} From 7d77f7b6cbbe35252773b2665111facea64c38cd Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sat, 2 Mar 2013 13:31:25 -0700 Subject: [PATCH 68/97] #199 - added nuget dependency on webpages for mvc package. --- nuget/AttributeRouting/AttributeRouting.nutrans | 1 + 1 file changed, 1 insertion(+) diff --git a/nuget/AttributeRouting/AttributeRouting.nutrans b/nuget/AttributeRouting/AttributeRouting.nutrans index 7629458..1fe56e8 100644 --- a/nuget/AttributeRouting/AttributeRouting.nutrans +++ b/nuget/AttributeRouting/AttributeRouting.nutrans @@ -8,6 +8,7 @@ + From ad153213504b97b67e9d136440af31f947f49e7b Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sat, 2 Mar 2013 14:04:57 -0700 Subject: [PATCH 69/97] test controllers for web api action inheritence. --- .../Controllers/InheritActionsControllers.cs | 17 +++++++++++++++++ .../AttributeRouting.Tests.Web.csproj | 1 + 2 files changed, 18 insertions(+) create mode 100644 src/AttributeRouting.Tests.Web/Areas/Api/Controllers/InheritActionsControllers.cs diff --git a/src/AttributeRouting.Tests.Web/Areas/Api/Controllers/InheritActionsControllers.cs b/src/AttributeRouting.Tests.Web/Areas/Api/Controllers/InheritActionsControllers.cs new file mode 100644 index 0000000..550f022 --- /dev/null +++ b/src/AttributeRouting.Tests.Web/Areas/Api/Controllers/InheritActionsControllers.cs @@ -0,0 +1,17 @@ +using System.Web.Http; +using AttributeRouting.Web.Http; + +namespace AttributeRouting.Tests.Web.Areas.Api.Controllers +{ + public abstract class InheritActionsApiControllerBase : BaseApiController + { + [GET("Base-Method"), HttpGet] + public string Index() + { + return "Base Index"; + } + } + + [RoutePrefix("Inherit/Derived")] + public class InheritActionsDerivedApiController : InheritActionsApiControllerBase {} +} \ No newline at end of file diff --git a/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj b/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj index 2e8a215..145b078 100644 --- a/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj +++ b/src/AttributeRouting.Tests.Web/AttributeRouting.Tests.Web.csproj @@ -119,6 +119,7 @@ + From b9e35f580b87f5ea1bad3c04e5e06d22b5c4acd1 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sat, 2 Mar 2013 14:09:38 -0700 Subject: [PATCH 70/97] updated readme/assemblyinfo for 3.5.1 --- README.textile | 5 +++++ src/SharedAssemblyInfo.cs | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.textile b/README.textile index 0c0676d..bf02d57 100644 --- a/README.textile +++ b/README.textile @@ -4,6 +4,11 @@ h2. Please refer to "attributerouting.net":http://attributerouting.net/ for docu h3. Changelog +_3.5.1_ + +* #199 - when applying inbound http method constraint, now checking the unvalidated form/query collection. +* #206 - changed the name of the app start files to prevent ns conflicts. + _3.5_ * #191 - Enabled in-memory hosting of web-hosted web api routes. diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index ae5bfcc..494d17d 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyProduct("AttributeRouting")] [assembly: AssemblyCopyright("Copyright 2010-2013 Tim McCall")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.5")] -[assembly: AssemblyFileVersion("3.5")] -[assembly: AssemblyInformationalVersion("3.5")] +[assembly: AssemblyVersion("3.5.1")] +[assembly: AssemblyFileVersion("3.5.1")] +[assembly: AssemblyInformationalVersion("3.5.1")] [assembly: AssemblyConfiguration("Release")] From 01574c67e3be637383f84cfbdc1d5d287d535ece Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Fri, 8 Mar 2013 12:12:11 -0700 Subject: [PATCH 71/97] fixed logic for creating and testing query string constraints so thread safety is not violated. --- .../Features/RouteConstraints.feature | 24 +++-- .../Features/RouteConstraints.feature.cs | 40 +++++---- .../Steps/RouteConstraintSteps.cs | 5 +- .../Framework/HttpAttributeRoute.cs | 16 +++- .../Framework/AttributeRoute.cs | 18 ++-- .../Constraints/RegexRouteConstraintBase.cs | 4 +- .../Framework/AttributeRouteVisitor.cs | 90 ++++++++----------- .../Framework/IAttributeRoute.cs | 5 ++ .../Framework/RouteBuilder.cs | 76 +++++++++------- 9 files changed, 156 insertions(+), 122 deletions(-) diff --git a/src/AttributeRouting.Specs/Features/RouteConstraints.feature b/src/AttributeRouting.Specs/Features/RouteConstraints.feature index 9300fce..f4086f5 100644 --- a/src/AttributeRouting.Specs/Features/RouteConstraints.feature +++ b/src/AttributeRouting.Specs/Features/RouteConstraints.feature @@ -41,12 +41,12 @@ Scenario: Inline constraints in the querystring Then the route url is "Inline-Constraints/Querystring" And the parameter "x" is constrained by an inline AttributeRouting.Web.Constraints.IntRouteConstraint And the parameter "y" is constrained by an inline AttributeRouting.Web.Constraints.QueryStringRouteConstraint - # Web API - Given I have registered the routes for the HttpInlineRouteConstraintsController - When I fetch the routes for the HttpInlineRouteConstraints controller's Querystring action - Then the route url is "Http-Inline-Constraints/Querystring" - And the parameter "x" is constrained by an inline AttributeRouting.Web.Constraints.IntRouteConstraint - And the parameter "y" is constrained by an inline AttributeRouting.Web.Constraints.QueryStringRouteConstraint + # Web API - NOTE: this won't work until web api vNext + #Given I have registered the routes for the HttpInlineRouteConstraintsController + #When I fetch the routes for the HttpInlineRouteConstraints controller's Querystring action + #Then the route url is "Http-Inline-Constraints/Querystring" + #And the parameter "x" is constrained by an inline AttributeRouting.Web.Constraints.IntRouteConstraint + #And the parameter "y" is constrained by an inline AttributeRouting.Web.Constraints.QueryStringRouteConstraint Scenario: Multiple inline constraints per url segment # MVC @@ -147,6 +147,18 @@ Scenario Outline: Matching inline route constraints | EnumValue/10 | EnumValue | is not | | WithOptional | WithOptional | is | | WithDefault | WithDefault | is | + +Scenario Outline: Matching inline route constraints in the querystring + # MVC + Given I have registered the routes for the InlineRouteConstraintsController + When a request for "Inline-Constraints/" is made + Then the action matched + # Web API - NOTE: these won't work until web api vNext. + #Given I have registered the routes for the HttpInlineRouteConstraintsController + #When a request for "Http-Inline-Constraints/" is made + #Then the action matched + Examples: + | url | action | condition | | Querystring?x=123&y=hello | Querystring | is | | Querystring?x=abc&y=hello | Querystring | is not | | Querystring?x=abc | Querystring | is not | diff --git a/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs b/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs index 6af704a..6585750 100644 --- a/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteConstraints.feature.cs @@ -3,7 +3,7 @@ // This code was generated by SpecFlow (http://www.specflow.org/). // SpecFlow Version:1.9.0.77 // SpecFlow Generator Version:1.9.0.0 -// Runtime Version:4.0.30319.18010 +// Runtime Version:4.0.30319.18034 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -133,19 +133,6 @@ public virtual void InlineConstraintsInTheQuerystring() #line 43 testRunner.And("the parameter \"y\" is constrained by an inline AttributeRouting.Web.Constraints.Qu" + "eryStringRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 45 - testRunner.Given("I have registered the routes for the HttpInlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); -#line 46 - testRunner.When("I fetch the routes for the HttpInlineRouteConstraints controller\'s Querystring ac" + - "tion", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); -#line 47 - testRunner.Then("the route url is \"Http-Inline-Constraints/Querystring\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); -#line 48 - testRunner.And("the parameter \"x\" is constrained by an inline AttributeRouting.Web.Constraints.In" + - "tRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); -#line 49 - testRunner.And("the parameter \"y\" is constrained by an inline AttributeRouting.Web.Constraints.Qu" + - "eryStringRouteConstraint", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden this.ScenarioCleanup(); } @@ -301,10 +288,6 @@ public virtual void InlineConstraintsSpecifiedInTheRouteAreaAttribute() [NUnit.Framework.TestCaseAttribute("EnumValue/10", "EnumValue", "is not", null)] [NUnit.Framework.TestCaseAttribute("WithOptional", "WithOptional", "is", null)] [NUnit.Framework.TestCaseAttribute("WithDefault", "WithDefault", "is", null)] - [NUnit.Framework.TestCaseAttribute("Querystring?x=123&y=hello", "Querystring", "is", null)] - [NUnit.Framework.TestCaseAttribute("Querystring?x=abc&y=hello", "Querystring", "is not", null)] - [NUnit.Framework.TestCaseAttribute("Querystring?x=abc", "Querystring", "is not", null)] - [NUnit.Framework.TestCaseAttribute("Querystring?y=hello", "Querystring", "is not", null)] public virtual void MatchingInlineRouteConstraints(string url, string action, string condition, string[] exampleTags) { TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Matching inline route constraints", exampleTags); @@ -322,6 +305,27 @@ public virtual void MatchingInlineRouteConstraints(string url, string action, st testRunner.When(string.Format("a request for \"Http-Inline-Constraints/{0}\" is made", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line 97 testRunner.Then(string.Format("the {0} action {1} matched", action, condition), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Matching inline route constraints in the querystring")] + [NUnit.Framework.TestCaseAttribute("Querystring?x=123&y=hello", "Querystring", "is", null)] + [NUnit.Framework.TestCaseAttribute("Querystring?x=abc&y=hello", "Querystring", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Querystring?x=abc", "Querystring", "is not", null)] + [NUnit.Framework.TestCaseAttribute("Querystring?y=hello", "Querystring", "is not", null)] + public virtual void MatchingInlineRouteConstraintsInTheQuerystring(string url, string action, string condition, string[] exampleTags) + { + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Matching inline route constraints in the querystring", exampleTags); +#line 151 +this.ScenarioSetup(scenarioInfo); +#line 153 + testRunner.Given("I have registered the routes for the InlineRouteConstraintsController", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line 154 + testRunner.When(string.Format("a request for \"Inline-Constraints/{0}\" is made", url), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line 155 + testRunner.Then(string.Format("the {0} action {1} matched", action, condition), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden this.ScenarioCleanup(); } diff --git a/src/AttributeRouting.Specs/Steps/RouteConstraintSteps.cs b/src/AttributeRouting.Specs/Steps/RouteConstraintSteps.cs index 943df39..f48541a 100644 --- a/src/AttributeRouting.Specs/Steps/RouteConstraintSteps.cs +++ b/src/AttributeRouting.Specs/Steps/RouteConstraintSteps.cs @@ -34,7 +34,10 @@ public void ThenTheParameterIsConstrainedByAnInline(string key, string type) Assert.That(route, Is.Not.Null); var constraint = route.Constraints[key]; - + if (constraint == null && route is IAttributeRoute) + { + constraint = ((IAttributeRoute)route).QueryStringConstraints[key]; + } Assert.That(constraint, Is.Not.Null); // If this is a querystring route constraint wrapper, then unwrap it. diff --git a/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs b/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs index 602653b..b56ec0a 100644 --- a/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs +++ b/src/AttributeRouting.Web.Http/Framework/HttpAttributeRoute.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net.Http; using System.Web.Http.Routing; +using System.Web.Routing; using AttributeRouting.Framework; using AttributeRouting.Helpers; @@ -32,6 +32,7 @@ public HttpAttributeRoute(string url, { _configuration = configuration; _visitor = new AttributeRouteVisitor(this, configuration); + QueryStringConstraints = new RouteValueDictionary(); } public bool? AppendTrailingSlash { get; set; } @@ -59,6 +60,8 @@ IDictionary IAttributeRoute.Defaults public List MappedSubdomains { get; set; } public bool? PreserveCaseForUrlParameters { get; set; } + + public IDictionary QueryStringConstraints { get; set; } public string RouteName { get; set; } @@ -92,6 +95,13 @@ public override IHttpRouteData GetRouteData(string virtualPathRoot, HttpRequestM return null; } + // Constrain by querystring param if there are any. + var routeValues = new HttpRouteValueDictionary(routeData.Values); + if (!_visitor.ProcessQueryStringConstraints((constraint, parameterName) => ProcessConstraint(request, constraint, parameterName, routeValues, HttpRouteDirection.UriResolution))) + { + return null; + } + // Constrain by subdomain if configured var requestedSubdomain = GetCachedValue(request, RequestedSubdomainKey, () => _configuration.SubdomainParser(request.SafeGet(r => r.Headers.Host))); if (!_visitor.IsSubdomainMatched(requestedSubdomain)) @@ -111,8 +121,8 @@ public override IHttpRouteData GetRouteData(string virtualPathRoot, HttpRequestM public override IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary values) { - // Let the underlying route do its thing, and if it does, then add some functionality on top. - var virtualPathData = _visitor.GetVirtualPath(() => base.GetVirtualPath(request, values)); + // Let the underlying route do its thing. + var virtualPathData = base.GetVirtualPath(request, values); if (virtualPathData == null) { return null; diff --git a/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs b/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs index 8d387dc..a0e0406 100644 --- a/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs +++ b/src/AttributeRouting.Web.Mvc/Framework/AttributeRoute.cs @@ -31,6 +31,7 @@ public AttributeRoute(string url, { _configuration = configuration; _visitor = new AttributeRouteVisitor(this, configuration); + QueryStringConstraints = new RouteValueDictionary(); } public bool? AppendTrailingSlash { get; set; } @@ -58,6 +59,8 @@ IDictionary IAttributeRoute.Defaults public List MappedSubdomains { get; set; } public bool? PreserveCaseForUrlParameters { get; set; } + + public IDictionary QueryStringConstraints { get; set; } public string RouteName { get; set; } @@ -85,15 +88,20 @@ public override RouteData GetRouteData(HttpContextBase httpContext) return null; } - // Constrain by subdomain if configured - // Get the subdomain from the requested hostname. + // Constrain by querystring param if there are any. + if (!_visitor.ProcessQueryStringConstraints((constraint, parameterName) => ProcessConstraint(httpContext, constraint, parameterName, routeData.Values, RouteDirection.IncomingRequest))) + { + return null; + } + + // Constrain by subdomain if configured. var requestedSubdomain = GetCachedValue(httpContext, RequestedSubdomainKey, () => _configuration.SubdomainParser(httpContext.SafeGet(c => c.Request.Headers["host"]))); if (!_visitor.IsSubdomainMatched(requestedSubdomain)) { return null; } - // Constrain by culture name if configured + // Constrain by culture name if configured. var currentUICultureName = GetCachedValue(httpContext, CurrentUICultureNameKey, () => _configuration.CurrentUICultureResolver(httpContext, routeData)); if (!_visitor.IsCultureNameMatched(currentUICultureName)) { @@ -105,8 +113,8 @@ public override RouteData GetRouteData(HttpContextBase httpContext) public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { - // Let the underlying route do its thing, and if it does, then add some functionality on top. - var virtualPathData = _visitor.GetVirtualPath(() => base.GetVirtualPath(requestContext, values)); + // Let the underlying route do its thing. + var virtualPathData = base.GetVirtualPath(requestContext, values); if (virtualPathData == null) { return null; diff --git a/src/AttributeRouting/Constraints/RegexRouteConstraintBase.cs b/src/AttributeRouting/Constraints/RegexRouteConstraintBase.cs index 2b4334e..4eb6fa8 100644 --- a/src/AttributeRouting/Constraints/RegexRouteConstraintBase.cs +++ b/src/AttributeRouting/Constraints/RegexRouteConstraintBase.cs @@ -16,8 +16,8 @@ protected RegexRouteConstraintBase(string pattern) protected RegexRouteConstraintBase(string pattern, RegexOptions options) { Pattern = pattern; - // shouldn't these be included in the derrived classes by default: RegexOptions.CultureInvariant | RegexOptions.IgnoreCase? - Options = options; //no need to tell user that it is 'compiled' option...so do not include in public options + // REVIEW: Shouldn't these be included in the derived classes by default: RegexOptions.CultureInvariant | RegexOptions.IgnoreCase? + Options = options; // No need to tell user that it is 'compiled' option...so do not include in public options CompiledExpression = new Regex(pattern, options | RegexOptions.Compiled); } diff --git a/src/AttributeRouting/Framework/AttributeRouteVisitor.cs b/src/AttributeRouting/Framework/AttributeRouteVisitor.cs index 739c1aa..cfb4f36 100644 --- a/src/AttributeRouting/Framework/AttributeRouteVisitor.cs +++ b/src/AttributeRouting/Framework/AttributeRouteVisitor.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; +using System; using System.Linq; using System.Text.RegularExpressions; -using System.Threading; -using AttributeRouting.Constraints; +using System.Threading; using AttributeRouting.Helpers; namespace AttributeRouting.Framework @@ -18,8 +16,7 @@ namespace AttributeRouting.Framework /// public class AttributeRouteVisitor { - private static readonly Lazy PathAndQueryRegex = - new Lazy(() => new Regex(@"(?[^\?]*)(?\?.*)?")); + private static readonly Regex PathAndQueryRegex = new Regex(@"(?[^\?]*)(?\?.*)?"); private readonly IAttributeRoute _route; private readonly ConfigurationBase _configuration; @@ -124,52 +121,8 @@ public TVirtualPathData GetTranslatedVirtualPath(Func - /// Wraps calls to the GetVirtualPath method of routes, removing querystring route constraints - /// that would otherwise lead to generated routes with invalid urls. - /// - /// The type of virtual path data to be returned. - /// This varies based on whether the route is a - /// System.Web.Routing.Route or System.Web.Http.Routing.HttpRoute. - /// The base GetVirtualPath method call - /// The result from the base call - public TVirtualPathData GetVirtualPath(Func fromBaseMethod) - where TVirtualPathData : class - { - // Remove querystring route constraints: - // the base GetVirtualPath will not inject route params that have constraints into the querystring. - var queryStringConstraints = new Dictionary(); - var constraintKeys = _route.Constraints.Keys.Select(k => k).ToList(); - foreach (var constraintKey in constraintKeys) - { - var constraint = _route.Constraints[constraintKey]; - var constraintToTest = constraint is IOptionalRouteConstraint - ? ((IOptionalRouteConstraint)constraint).Constraint - : constraint; - - if (!(constraintToTest is IQueryStringRouteConstraint)) - { - continue; - } - - queryStringConstraints.Add(constraintKey, constraint); - _route.Constraints.Remove(constraintKey); - } - - // Let the underlying route do its thing. - var virtualPathData = fromBaseMethod(); - - // Add the querystring constraints back in. - foreach (var queryStringConstraint in queryStringConstraints) - { - _route.Constraints.Add(queryStringConstraint.Key, queryStringConstraint.Value); - } - - return virtualPathData; - } - + } + /// /// Tests whether the route matches the current UI culture. /// @@ -280,6 +233,37 @@ public bool IsSubdomainMatched(string requestedSubdomain) return false; } + /// + /// Processes query constraints separately from route constraints. + /// + /// + /// Delegate used to process the query constraints according to the underlying route framework. + /// Accepts a constraint and parameter name and returns tru if the constraint passes. + /// + /// True if all query string constraints pass or if there are none to test. + /// + /// Need to separate path and query constraints because methods in the web stack + /// will not add query params to generated urls if there is a constraint for the param name + /// that is not present in the url template. See logic in: + /// - System.Web.Http.Routing.HttpParsedRoute.Bind(...) + /// - System.Web.Routing.ParsedRoute.Bind(...) + /// + public bool ProcessQueryStringConstraints(Func processConstraint) + { + foreach (var queryStringConstraint in _route.QueryStringConstraints) + { + var parameterName = queryStringConstraint.Key; + var constraint = queryStringConstraint.Value; + + if (!processConstraint(constraint, parameterName)) + { + return false; + } + } + + return true; + } + private static string AppendTrailingSlashToVirtualPath(string virtualPath) { string path, query; @@ -296,7 +280,7 @@ private static string AppendTrailingSlashToVirtualPath(string virtualPath) private static void GetPathAndQuery(string virtualPath, out string path, out string query) { // NOTE: Do not lowercase the querystring vals - var match = PathAndQueryRegex.Value.Match(virtualPath); + var match = PathAndQueryRegex.Match(virtualPath); // Just covering my backside here in case the regex fails for some reason. if (!match.Success) diff --git a/src/AttributeRouting/Framework/IAttributeRoute.cs b/src/AttributeRouting/Framework/IAttributeRoute.cs index 4474fd9..eac3f4f 100644 --- a/src/AttributeRouting/Framework/IAttributeRoute.cs +++ b/src/AttributeRouting/Framework/IAttributeRoute.cs @@ -45,6 +45,11 @@ public interface IAttributeRoute /// bool? PreserveCaseForUrlParameters { get; set; } + /// + /// Constraints dictionary for querystring constraints. + /// + IDictionary QueryStringConstraints { get; set; } + /// /// The name of this route, for supporting named routes. /// diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index 5b9f212..ca8371b 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -13,23 +13,15 @@ namespace AttributeRouting.Framework /// public class RouteBuilder { - private static readonly Lazy ConstraintParamsRegex = - new Lazy(() => new Regex(@"^.*\(.*\)$", RegexOptions.Compiled)); + private static readonly Regex ConstraintParamsRegex = new Regex(@"^.*\(.*\)$"); - private static readonly Lazy DetokenizeUrlRegex = - new Lazy(() => - { - var patterns = new List - { - @"(?<=\{)\?", // leading question mark (used to specify optional param) - @"\?(?=\})", // trailing question mark (used to specify optional param) - @"\(.*?\)(?=\})", // stuff inside parens (used to specify inline regex route constraint) - @"\:(.*?)(\(.*?\))?((?=\})|(?=\?\}))", // new inline constraint syntax - @"(?<=\{.*)=.*?(?=\})", // equals and value (used to specify inline parameter default value) - }; - var pattern = String.Join("|", patterns); - return new Regex(pattern); - }); + private static readonly Regex DetokenizeUrlRegex = + new Regex(@"(?<=\{)\?" + // leading question mark (used to specify optional param) + @"|\?(?=\})" + // trailing question mark (used to specify optional param) + @"|\(.*?\)(?=\})" + // stuff inside parens (used to specify inline regex route constraint) + @"|\:(.*?)(\(.*?\))?((?=\})|(?=\?\}))" + // new inline constraint syntax + @"|(?<=\{.*)=.*?(?=\})" // equals and value (used to specify inline parameter default value) + ); private readonly ConfigurationBase _configuration; private readonly IParameterFactory _parameterFactory; @@ -92,8 +84,10 @@ private string BuildTokenizedUrl(string routeUrl, string routePrefixUrl, string private IEnumerable BuildRoutes(RouteSpecification routeSpec) { - var defaults = CreateRouteDefaults(routeSpec); - var constraints = CreateRouteConstraints(routeSpec); + var defaults = CreateRouteDefaults(routeSpec); + IDictionary constraints; + IDictionary queryStringConstraints; + CreateRouteConstraints(routeSpec, out constraints, out queryStringConstraints); var dataTokens = CreateRouteDataTokens(routeSpec); var url = CreateRouteUrl(defaults, routeSpec); @@ -106,8 +100,9 @@ private IEnumerable BuildRoutes(RouteSpecification routeSpec) { route.RouteName = routeName; route.DataTokens.Add("routeName", routeName); - } - + } + + route.QueryStringConstraints = queryStringConstraints; route.Translations = CreateRouteTranslations(routeSpec); route.Subdomain = routeSpec.Subdomain; route.UseLowercaseRoute = routeSpec.UseLowercaseRoute; @@ -131,9 +126,13 @@ private IEnumerable BuildRoutes(RouteSpecification routeSpec) } } - private IDictionary CreateRouteConstraints(RouteSpecification routeSpec) + private void CreateRouteConstraints(RouteSpecification routeSpec, out IDictionary constraints, out IDictionary queryStringConstraints) { - var constraints = new Dictionary(); + // Going to return individual collections for: + // - path routes constraints (which will go into the generated route's Constraints prop), + // - and query string route constraints (which will not work perfectly with the MS bits, and need special treatment by IAttributeRoute impls). + constraints = new Dictionary(); + queryStringConstraints = new Dictionary(); // Default constraints if (routeSpec.HttpMethods.Any()) @@ -170,7 +169,7 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro var cleanParameter = parameter.TrimEnd('?').Split('=').FirstOrDefault(); var sections = cleanParameter.SplitAndTrim(":"); - // Do not override default or legacy inline constraints + // Do not override default constraints var parameterName = sections.First(); if (constraints.ContainsKey(parameterName)) { @@ -185,7 +184,7 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro string constraintName; object constraint; - if (ConstraintParamsRegex.Value.IsMatch(definition)) + if (ConstraintParamsRegex.IsMatch(definition)) { // Constraint of the form "firstName:string(50)" var indexOfOpenParen = definition.IndexOf('('); @@ -241,9 +240,15 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro finalConstraint = _routeConstraintFactory.CreateOptionalRouteConstraint(finalConstraint); } - // Apply the constraint to the parameter. - constraints.Add(parameterName, finalConstraint); - + // Add the constraints to the appropriate collection. + if (parameterIsInQueryString) + { + queryStringConstraints.Add(parameterName, finalConstraint); + } + else + { + constraints.Add(parameterName, finalConstraint); + } } // ... go to next parameter // Globally configured constraints @@ -263,8 +268,6 @@ private IDictionary CreateRouteConstraints(RouteSpecification ro constraints.Add(urlParameterName, defaultConstraint.Value); } } - - return constraints; } private IDictionary CreateRouteDataTokens(RouteSpecification routeSpec) @@ -384,8 +387,12 @@ private IEnumerable CreateRouteTranslations(RouteSpecification //********************************************* // Otherwise, build a translated route - var defaults = CreateRouteDefaults(routeSpec); - var constraints = CreateRouteConstraints(routeSpec); + // REVIEW: Could probably forgo processing defaults, constraints, and data tokens for translated routes. + + var defaults = CreateRouteDefaults(routeSpec); + IDictionary constraints; + IDictionary queryStringConstraints; + CreateRouteConstraints(routeSpec, out constraints, out queryStringConstraints); var dataTokens = CreateRouteDataTokens(routeSpec); var routeUrl = CreateRouteUrl(translatedRouteUrl ?? routeSpec.RouteUrl, translatedRoutePrefix ?? routeSpec.RoutePrefixUrl, @@ -402,8 +409,9 @@ private IEnumerable CreateRouteTranslations(RouteSpecification { translatedRoute.RouteName = routeName; translatedRoute.DataTokens.Add("routeName", routeName); - } - + } + + translatedRoute.QueryStringConstraints = queryStringConstraints; translatedRoute.CultureName = cultureName; translatedRoute.DataTokens.Add("cultureName", cultureName); @@ -476,7 +484,7 @@ private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUr private static string DetokenizeUrl(string url) { - return DetokenizeUrlRegex.Value.Replace(url, ""); + return DetokenizeUrlRegex.Replace(url, ""); } private static IEnumerable GetUrlParameterContents(string url) From 5f2833a97d51e49df8a63d570f1bc37a496902a7 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Fri, 8 Mar 2013 12:16:27 -0700 Subject: [PATCH 72/97] updated readme and assembly info for 3.5.2 --- README.textile | 4 ++++ src/SharedAssemblyInfo.cs | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.textile b/README.textile index bf02d57..ff7db76 100644 --- a/README.textile +++ b/README.textile @@ -4,6 +4,10 @@ h2. Please refer to "attributerouting.net":http://attributerouting.net/ for docu h3. Changelog +_3.5.2_ + +* 214 - BUG FIX: fixed thread safety issues encountered when dealing with query string route constraints." + _3.5.1_ * #199 - when applying inbound http method constraint, now checking the unvalidated form/query collection. diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index 494d17d..af6e550 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyProduct("AttributeRouting")] [assembly: AssemblyCopyright("Copyright 2010-2013 Tim McCall")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.5.1")] -[assembly: AssemblyFileVersion("3.5.1")] -[assembly: AssemblyInformationalVersion("3.5.1")] +[assembly: AssemblyVersion("3.5.2")] +[assembly: AssemblyFileVersion("3.5.2")] +[assembly: AssemblyInformationalVersion("3.5.2")] [assembly: AssemblyConfiguration("Release")] From 5267a439a6db955d0a64f16d4161ae5ff212444b Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Fri, 8 Mar 2013 15:54:06 -0700 Subject: [PATCH 73/97] put the query constraints back in the route debugger output. --- .../Program.cs | 2 +- .../Logging/LoggingExtensions.cs | 29 ++-- .../Logging/LogRoutesHandler.cs | 7 +- .../Logging/LoggingExtensions.cs | 19 ++- src/AttributeRouting/AttributeRouting.csproj | 2 +- .../Logging/AttributeRouteInfo.cs | 128 ---------------- src/AttributeRouting/Logging/LogWriter.cs | 2 +- .../Logging/RouteLoggingInfo.cs | 142 ++++++++++++++++++ 8 files changed, 181 insertions(+), 150 deletions(-) delete mode 100644 src/AttributeRouting/Logging/AttributeRouteInfo.cs create mode 100644 src/AttributeRouting/Logging/RouteLoggingInfo.cs diff --git a/src/AttributeRouting.Tests.SelfHost/Program.cs b/src/AttributeRouting.Tests.SelfHost/Program.cs index 128cf30..60409fd 100644 --- a/src/AttributeRouting.Tests.SelfHost/Program.cs +++ b/src/AttributeRouting.Tests.SelfHost/Program.cs @@ -23,7 +23,7 @@ static void Main(string[] args) Console.WriteLine("Routes:"); - config.Routes.Cast().LogTo(Console.Out); + config.Routes.Cast().ToArray().LogTo(Console.Out); Console.WriteLine("Routes:"); Console.WriteLine("Press Enter to quit."); diff --git a/src/AttributeRouting.Web.Http.SelfHost/Logging/LoggingExtensions.cs b/src/AttributeRouting.Web.Http.SelfHost/Logging/LoggingExtensions.cs index 4087659..5e6a3e9 100644 --- a/src/AttributeRouting.Web.Http.SelfHost/Logging/LoggingExtensions.cs +++ b/src/AttributeRouting.Web.Http.SelfHost/Logging/LoggingExtensions.cs @@ -1,29 +1,34 @@ -using System.Collections.Generic; -using System.IO; +using System.IO; using System.Linq; -using System.Web.Http.Routing; -using AttributeRouting.Framework; +using System.Web.Http.Routing; +using AttributeRouting.Framework; +using AttributeRouting.Helpers; using AttributeRouting.Logging; namespace AttributeRouting.Web.Http.SelfHost.Logging { public static class LoggingExtensions { - public static void LogTo(this IEnumerable routes, TextWriter writer) + public static void LogTo(this HttpRoute[] routes, TextWriter writer) { LogWriter.LogNumberOfRoutes(routes.Count(), writer); - foreach (var route in routes) - route.LogTo(writer); + foreach (var route in routes) + { + route.LogTo(writer); + } } public static void LogTo(this HttpRoute route, TextWriter writer) { - string name = route is IAttributeRoute - ? ((IAttributeRoute)route).RouteName - : null; - - LogWriter.LogRoute(writer, route.RouteTemplate, AttributeRouteInfo.GetRouteInfo(route.RouteTemplate, route.Defaults, route.Constraints, route.DataTokens)); + var attributeRoute = route as IAttributeRoute; + var info = RouteLoggingInfo.GetRouteInfo(route.RouteTemplate, + route.Defaults, + route.Constraints, + attributeRoute.SafeGet(x => x.QueryStringConstraints), + route.DataTokens); + + LogWriter.LogRoute(writer, route.RouteTemplate, info); } } } \ No newline at end of file diff --git a/src/AttributeRouting.Web/Logging/LogRoutesHandler.cs b/src/AttributeRouting.Web/Logging/LogRoutesHandler.cs index 6284863..a630758 100644 --- a/src/AttributeRouting.Web/Logging/LogRoutesHandler.cs +++ b/src/AttributeRouting.Web/Logging/LogRoutesHandler.cs @@ -96,7 +96,12 @@ private string GetOutput() private static IEnumerable GetRouteInfo() { return from r in RouteTable.Routes.OfType() - let routeInfo = AttributeRouteInfo.GetRouteInfo(r.Url, r.Defaults, r.Constraints, r.DataTokens) + let ar = r as IAttributeRoute + let routeInfo = RouteLoggingInfo.GetRouteInfo(r.Url, + r.Defaults, + r.Constraints, + ar.SafeGet(x => x.QueryStringConstraints), + r.DataTokens) select new { methods = routeInfo.HttpMethods, diff --git a/src/AttributeRouting.Web/Logging/LoggingExtensions.cs b/src/AttributeRouting.Web/Logging/LoggingExtensions.cs index 96acb9f..a6e1ad9 100644 --- a/src/AttributeRouting.Web/Logging/LoggingExtensions.cs +++ b/src/AttributeRouting.Web/Logging/LoggingExtensions.cs @@ -2,7 +2,8 @@ using System.IO; using System.Linq; using System.Web.Routing; -using AttributeRouting.Framework; +using AttributeRouting.Framework; +using AttributeRouting.Helpers; using AttributeRouting.Logging; namespace AttributeRouting.Web.Logging @@ -21,11 +22,17 @@ public static void LogTo(this IEnumerable routes, TextWriter writer) } } - public static void LogTo(this Route route, TextWriter writer) - { - var name = route is IAttributeRoute ? ((IAttributeRoute)route).RouteName : null; - - LogWriter.LogRoute(writer, name, AttributeRouteInfo.GetRouteInfo(route.Url, route.Defaults, route.Constraints, route.DataTokens)); + public static void LogTo(this Route route, TextWriter writer) + { + var attributeRoute = route as IAttributeRoute; + var name = attributeRoute.SafeGet(r => r.RouteName); + var info = RouteLoggingInfo.GetRouteInfo(route.Url, + route.Defaults, + route.Constraints, + attributeRoute.SafeGet(r => r.QueryStringConstraints), + route.DataTokens); + + LogWriter.LogRoute(writer, name, info); } } } \ No newline at end of file diff --git a/src/AttributeRouting/AttributeRouting.csproj b/src/AttributeRouting/AttributeRouting.csproj index 2b7e3ef..e666f46 100644 --- a/src/AttributeRouting/AttributeRouting.csproj +++ b/src/AttributeRouting/AttributeRouting.csproj @@ -105,7 +105,7 @@ - + diff --git a/src/AttributeRouting/Logging/AttributeRouteInfo.cs b/src/AttributeRouting/Logging/AttributeRouteInfo.cs deleted file mode 100644 index 5be63b6..0000000 --- a/src/AttributeRouting/Logging/AttributeRouteInfo.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AttributeRouting.Constraints; -using AttributeRouting.Helpers; - -namespace AttributeRouting.Logging -{ - public class AttributeRouteInfo - { - public AttributeRouteInfo() - { - Defaults = new Dictionary(); - Constraints = new Dictionary(); - DataTokens = new Dictionary(); - } - - public string Url { get; set; } - public string HttpMethods { get; set; } - public IDictionary Defaults { get; set; } - public IDictionary Constraints { get; set; } - public IDictionary DataTokens { get; set; } - - public static AttributeRouteInfo GetRouteInfo(string url, - IDictionary defaults, - IDictionary constraints, - IDictionary dataTokens) - { - - var item = new AttributeRouteInfo { Url = url }; - - //************************ - // Defaults - - if (defaults != null) - { - foreach (var @default in defaults) - { - var defaultValue = @default.Value.ToString(); - item.Defaults.Add(@default.Key, defaultValue.ValueOr("Optional")); - } - } - - //************************ - // Constraints - - if (constraints != null) - { - foreach (var constraint in constraints) - { - if (constraint.Value == null || constraint.Value is IInboundHttpMethodConstraint) - continue; - - if (constraint.Value is RegexRouteConstraintBase) - { - item.Constraints.Add(constraint.Key, ((RegexRouteConstraintBase)constraint.Value).Pattern); - } - else - { - var constraintValue = constraint.Value; - var constraintDescriptions = new List(); - - // Simple string regex constraint - from ASP.NET routing features - if (constraintValue is string) - { - constraintDescriptions.Add(constraintValue.ToString()); - } - else - { - // Optional constraint - unwrap it and continue - var optionalConstraint = constraintValue as IOptionalRouteConstraint; - if (optionalConstraint != null) - { - constraintValue = optionalConstraint.Constraint; - } - - // QueryString constraint - unwrap it and continue - var queryStringConstraint = constraintValue as IQueryStringRouteConstraint; - if (queryStringConstraint != null) - { - constraintValue = queryStringConstraint.Constraint; - constraintDescriptions.Add("QueryStringRouteConstraint"); - } - - // Compound constraint - join type names of the inner constraints - var compoundConstraint = constraintValue as ICompoundRouteConstraint; - if (compoundConstraint != null) - { - constraintDescriptions.AddRange(compoundConstraint.Constraints.Select(c => c.GetType().Name)); - } - else if (constraintValue != null) - { - // Single constraint type - constraintDescriptions.Add(constraintValue.GetType().Name); - } - } - - item.Constraints.Add(constraint.Key, String.Join(", ", constraintDescriptions)); - } - } - } - - //************************ - // DataTokens - - if (dataTokens != null) - { - foreach (var token in dataTokens) - { - if (token.Key.ValueEquals("namespaces")) - { - item.DataTokens.Add(token.Key, String.Join(", ", (string[])token.Value)); - } - else if (token.Key.ValueEquals("httpMethods")) - { - item.HttpMethods = String.Join(", ", (string[])token.Value); - } - else if (!token.Key.ValueEquals("actionMethod")) - { - item.DataTokens.Add(token.Key, token.Value.ToString()); - } - } - } - - return item; - } - } -} \ No newline at end of file diff --git a/src/AttributeRouting/Logging/LogWriter.cs b/src/AttributeRouting/Logging/LogWriter.cs index c2ef733..c08dc78 100644 --- a/src/AttributeRouting/Logging/LogWriter.cs +++ b/src/AttributeRouting/Logging/LogWriter.cs @@ -13,7 +13,7 @@ public static void LogNumberOfRoutes(int count, TextWriter writer) writer.WriteLine(" "); } - public static void LogRoute(TextWriter writer, string name, AttributeRouteInfo routeInfo) + public static void LogRoute(TextWriter writer, string name, RouteLoggingInfo routeInfo) { writer.WriteLine("URL: {0} {1}", routeInfo.Url, routeInfo.HttpMethods); diff --git a/src/AttributeRouting/Logging/RouteLoggingInfo.cs b/src/AttributeRouting/Logging/RouteLoggingInfo.cs new file mode 100644 index 0000000..dc3ad61 --- /dev/null +++ b/src/AttributeRouting/Logging/RouteLoggingInfo.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AttributeRouting.Constraints; +using AttributeRouting.Helpers; + +namespace AttributeRouting.Logging +{ + public class RouteLoggingInfo + { + public RouteLoggingInfo() + { + Defaults = new Dictionary(); + Constraints = new Dictionary(); + DataTokens = new Dictionary(); + } + + public string Url { get; set; } + public string HttpMethods { get; set; } + public IDictionary Defaults { get; set; } + public IDictionary Constraints { get; set; } + public IDictionary DataTokens { get; set; } + + public static RouteLoggingInfo GetRouteInfo(string url, + IDictionary defaults, + IDictionary constraints, + IDictionary queryStringConstraints, + IDictionary dataTokens) + { + + var item = new RouteLoggingInfo { Url = url }; + + //************************ + // Defaults + + if (defaults != null) + { + foreach (var @default in defaults) + { + var defaultValue = @default.Value.ToString(); + item.Defaults.Add(@default.Key, defaultValue.ValueOr("Optional")); + } + } + + //************************ + // Constraints + + var allConstraints = new Dictionary(); + if (constraints != null) + { + foreach (var constraint in constraints) + { + allConstraints.Add(constraint.Key, constraint.Value); + } + } + if (queryStringConstraints != null) + { + foreach (var constraint in queryStringConstraints) + { + allConstraints.Add(constraint.Key, constraint.Value); + } + } + + foreach (var constraint in allConstraints) + { + if (constraint.Value == null || constraint.Value is IInboundHttpMethodConstraint) + continue; + + if (constraint.Value is RegexRouteConstraintBase) + { + item.Constraints.Add(constraint.Key, ((RegexRouteConstraintBase)constraint.Value).Pattern); + } + else + { + var constraintValue = constraint.Value; + var constraintDescriptions = new List(); + + // Simple string regex constraint - from ASP.NET routing features + if (constraintValue is string) + { + constraintDescriptions.Add(constraintValue.ToString()); + } + else + { + // Optional constraint - unwrap it and continue + var optionalConstraint = constraintValue as IOptionalRouteConstraint; + if (optionalConstraint != null) + { + constraintValue = optionalConstraint.Constraint; + } + + // QueryString constraint - unwrap it and continue + var queryStringConstraint = constraintValue as IQueryStringRouteConstraint; + if (queryStringConstraint != null) + { + constraintValue = queryStringConstraint.Constraint; + constraintDescriptions.Add("QueryStringRouteConstraint"); + } + + // Compound constraint - join type names of the inner constraints + var compoundConstraint = constraintValue as ICompoundRouteConstraint; + if (compoundConstraint != null) + { + constraintDescriptions.AddRange(compoundConstraint.Constraints.Select(c => c.GetType().Name)); + } + else if (constraintValue != null) + { + // Single constraint type + constraintDescriptions.Add(constraintValue.GetType().Name); + } + } + + item.Constraints.Add(constraint.Key, String.Join(", ", constraintDescriptions)); + } + } + + //************************ + // DataTokens + + if (dataTokens != null) + { + foreach (var token in dataTokens) + { + if (token.Key.ValueEquals("namespaces")) + { + item.DataTokens.Add(token.Key, String.Join(", ", (string[])token.Value)); + } + else if (token.Key.ValueEquals("httpMethods")) + { + item.HttpMethods = String.Join(", ", (string[])token.Value); + } + else if (!token.Key.ValueEquals("actionMethod")) + { + item.DataTokens.Add(token.Key, token.Value.ToString()); + } + } + } + + return item; + } + } +} \ No newline at end of file From 00e50861420b6517c9d9ef35df624b162c865a95 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Fri, 8 Mar 2013 16:45:27 -0700 Subject: [PATCH 74/97] #216 #211 - testing optional constraint for mvc against null/empty in addition to UrlParameter.Optional; parsing defaults from complete url, including areas and prefixes. --- .../Features/RouteDefaults.feature | 1 + .../Features/RouteDefaults.feature.cs | 2 +- .../Steps/RouteDefaultsSteps.cs | 1 - .../Steps/StandardUsageSteps.cs | 12 +++++++++--- .../Subjects/RouteDefaultsController.cs | 6 ++++++ .../Constraints/OptionalRouteConstraint.cs | 12 +++++++----- .../Framework/RouteBuilder.cs | 19 +++++++++---------- .../Logging/RouteLoggingInfo.cs | 5 +++-- 8 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/AttributeRouting.Specs/Features/RouteDefaults.feature b/src/AttributeRouting.Specs/Features/RouteDefaults.feature index be34fe1..2166809 100644 --- a/src/AttributeRouting.Specs/Features/RouteDefaults.feature +++ b/src/AttributeRouting.Specs/Features/RouteDefaults.feature @@ -29,3 +29,4 @@ Scenario: Using the controller and action url params Then the route url is "RouteDefaults/TheActionName" When I fetch the routes for the HttpRouteDefaults controller's TheActionName action Then the route url is "HttpRouteDefaults/TheActionName" + diff --git a/src/AttributeRouting.Specs/Features/RouteDefaults.feature.cs b/src/AttributeRouting.Specs/Features/RouteDefaults.feature.cs index e7552b2..66688d1 100644 --- a/src/AttributeRouting.Specs/Features/RouteDefaults.feature.cs +++ b/src/AttributeRouting.Specs/Features/RouteDefaults.feature.cs @@ -3,7 +3,7 @@ // This code was generated by SpecFlow (http://www.specflow.org/). // SpecFlow Version:1.9.0.77 // SpecFlow Generator Version:1.9.0.0 -// Runtime Version:4.0.30319.18010 +// Runtime Version:4.0.30319.18034 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/src/AttributeRouting.Specs/Steps/RouteDefaultsSteps.cs b/src/AttributeRouting.Specs/Steps/RouteDefaultsSteps.cs index ce507c1..452558d 100644 --- a/src/AttributeRouting.Specs/Steps/RouteDefaultsSteps.cs +++ b/src/AttributeRouting.Specs/Steps/RouteDefaultsSteps.cs @@ -2,7 +2,6 @@ using System.Web.Http; using System.Web.Mvc; using AttributeRouting.Framework; -using AttributeRouting.Web.Mvc.Framework; using NUnit.Framework; using TechTalk.SpecFlow; diff --git a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs index c68d4d4..decf0df 100644 --- a/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs +++ b/src/AttributeRouting.Specs/Steps/StandardUsageSteps.cs @@ -1,10 +1,7 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Web.Routing; using AttributeRouting.Constraints; -using AttributeRouting.Framework; -using AttributeRouting.Web.Constraints; using NUnit.Framework; using TechTalk.SpecFlow; @@ -36,6 +33,15 @@ public void ThenTheDefaultForIs(string key, object value) Assert.That(route.Defaults[key], Is.EqualTo(value)); } + [Then(@"a default for ""(.*?)"" does not exist")] + public void ThenTheDefaultForDoesNotExist(string key) + { + var route = ScenarioContext.Current.GetFetchedRoutes().FirstOrDefault(); + + Assert.That(route, Is.Not.Null); + Assert.That(route.Defaults[key], Is.Null); + } + [Then(@"the route area is ""(.*?)""")] public void ThenTheRouteAreaIs(string area) { diff --git a/src/AttributeRouting.Specs/Subjects/RouteDefaultsController.cs b/src/AttributeRouting.Specs/Subjects/RouteDefaultsController.cs index e79638e..cdefd87 100644 --- a/src/AttributeRouting.Specs/Subjects/RouteDefaultsController.cs +++ b/src/AttributeRouting.Specs/Subjects/RouteDefaultsController.cs @@ -22,5 +22,11 @@ public string TheActionName() { return "is joe"; } + + [GET("Optionals/In/Query?{x?}")] + public string OptionalsInQuery() + { + return "optionals in query"; + } } } \ No newline at end of file diff --git a/src/AttributeRouting.Web.Mvc/Constraints/OptionalRouteConstraint.cs b/src/AttributeRouting.Web.Mvc/Constraints/OptionalRouteConstraint.cs index 95697ac..e6fc342 100644 --- a/src/AttributeRouting.Web.Mvc/Constraints/OptionalRouteConstraint.cs +++ b/src/AttributeRouting.Web.Mvc/Constraints/OptionalRouteConstraint.cs @@ -1,8 +1,9 @@ using System.Web; using System.Web.Mvc; using System.Web.Routing; -using AttributeRouting.Constraints; - +using AttributeRouting.Constraints; +using AttributeRouting.Helpers; + namespace AttributeRouting.Web.Mvc.Constraints { public class OptionalRouteConstraint : IOptionalRouteConstraint, IRouteConstraint @@ -23,9 +24,10 @@ public bool Match(HttpContextBase httpContext, Route route, string parameterName { // If the param is optional and has no value, then pass the constraint if (route.Defaults.ContainsKey(parameterName) - && route.Defaults[parameterName] == UrlParameter.Optional) - { - if (values[parameterName] == UrlParameter.Optional) + && route.Defaults[parameterName] == UrlParameter.Optional) + { + var value = values[parameterName]; + if (value == UrlParameter.Optional || value.HasNoValue()) return true; } diff --git a/src/AttributeRouting/Framework/RouteBuilder.cs b/src/AttributeRouting/Framework/RouteBuilder.cs index ca8371b..31dbe2b 100644 --- a/src/AttributeRouting/Framework/RouteBuilder.cs +++ b/src/AttributeRouting/Framework/RouteBuilder.cs @@ -144,9 +144,7 @@ private void CreateRouteConstraints(RouteSpecification routeSpec, out IDictionar var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); - // Need to keep track of query params. - // Can do this by detokenizing URL (which strips query), - // and then taking all the URL parameters except those from the path part of the URL. + // Need to keep track of which are path and query params. var pathOnlyUrl = RemoveQueryString(tokenizedUrl); var pathOnlyUrlParameters = GetUrlParameterContents(pathOnlyUrl); var queryStringParameters = urlParameters.Except(pathOnlyUrlParameters).ToList(); @@ -299,11 +297,13 @@ private IDictionary CreateRouteDefaults(RouteSpecification route { { "controller", routeSpec.ControllerName }, { "action", routeSpec.ActionName } - }; - - var urlParameters = GetUrlParameterContents(routeSpec.RouteUrl).ToList(); - - // Inspect the url for optional parameters, specified with a trailing ? + }; + + // Work from a complete, tokenized url; ie: support defaults in area urls, route prefix urls, and route urls. + var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); + var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); + + // Inspect the url path for optional parameters, specified with a trailing ? foreach (var parameter in urlParameters.Where(p => p.EndsWith("?"))) { var parameterName = parameter.TrimEnd('?'); @@ -430,11 +430,10 @@ private string CreateRouteUrl(IDictionary defaults, RouteSpecifi } private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUrl, IDictionary defaults, RouteSpecification routeSpec) - { + { var tokenizedUrl = BuildTokenizedUrl(routeUrl, routePrefix, areaUrl, routeSpec); var tokenizedPath = RemoveQueryString(tokenizedUrl); var detokenizedPath = DetokenizeUrl(tokenizedPath); - var urlParameterNames = GetUrlParameterContents(detokenizedPath).ToList(); var urlBuilder = new StringBuilder(detokenizedPath); diff --git a/src/AttributeRouting/Logging/RouteLoggingInfo.cs b/src/AttributeRouting/Logging/RouteLoggingInfo.cs index dc3ad61..aa2d93a 100644 --- a/src/AttributeRouting/Logging/RouteLoggingInfo.cs +++ b/src/AttributeRouting/Logging/RouteLoggingInfo.cs @@ -66,9 +66,10 @@ public static RouteLoggingInfo GetRouteInfo(string url, if (constraint.Value == null || constraint.Value is IInboundHttpMethodConstraint) continue; - if (constraint.Value is RegexRouteConstraintBase) + var regexRouteConstraint = constraint.Value as RegexRouteConstraintBase; + if (regexRouteConstraint != null) { - item.Constraints.Add(constraint.Key, ((RegexRouteConstraintBase)constraint.Value).Pattern); + item.Constraints.Add(constraint.Key, regexRouteConstraint.Pattern); } else { From 8f0f7951fe758768f00a83edff130cd733dbd376 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Fri, 8 Mar 2013 16:48:02 -0700 Subject: [PATCH 75/97] updated readme and assembly info for 2.5.3 --- README.textile | 7 ++++++- src/SharedAssemblyInfo.cs | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.textile b/README.textile index ff7db76..ec79586 100644 --- a/README.textile +++ b/README.textile @@ -4,9 +4,14 @@ h2. Please refer to "attributerouting.net":http://attributerouting.net/ for docu h3. Changelog +_3.5.3_ + +* #216 - testing optional constraint for mvc against null/empty in addition to UrlParameter.Optional. +* #211 - parsing defaults from complete url, including areas and prefixes. + _3.5.2_ -* 214 - BUG FIX: fixed thread safety issues encountered when dealing with query string route constraints." +* #214 - BUG FIX: fixed thread safety issues encountered when dealing with query string route constraints." _3.5.1_ diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index af6e550..2e2e156 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyProduct("AttributeRouting")] [assembly: AssemblyCopyright("Copyright 2010-2013 Tim McCall")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("3.5.2")] -[assembly: AssemblyFileVersion("3.5.2")] -[assembly: AssemblyInformationalVersion("3.5.2")] +[assembly: AssemblyVersion("2.5.3")] +[assembly: AssemblyFileVersion("2.5.3")] +[assembly: AssemblyInformationalVersion("2.5.3")] [assembly: AssemblyConfiguration("Release")] From 73d2cd89d5a6d6235a153345ff74aaf459b2c3f4 Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Fri, 8 Mar 2013 16:49:54 -0700 Subject: [PATCH 76/97] fixed assembly info version: end of day/week confusion with numbers :) --- src/SharedAssemblyInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index 2e2e156..f506102 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -7,7 +7,7 @@ [assembly: AssemblyProduct("AttributeRouting")] [assembly: AssemblyCopyright("Copyright 2010-2013 Tim McCall")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.5.3")] -[assembly: AssemblyFileVersion("2.5.3")] -[assembly: AssemblyInformationalVersion("2.5.3")] +[assembly: AssemblyVersion("3.5.3")] +[assembly: AssemblyFileVersion("3.5.3")] +[assembly: AssemblyInformationalVersion("3.5.3")] [assembly: AssemblyConfiguration("Release")] From 2cbef22058735061b28e96e3ada7535502b82b4c Mon Sep 17 00:00:00 2001 From: Tim McCall Date: Sun, 10 Mar 2013 12:16:07 -0600 Subject: [PATCH 77/97] Started refactoring tests by writing some jasmine integration tests. --- .../AttributeRouting.Tests.csproj | 205 ++ src/AttributeRouting.Tests/Global.asax | 1 + src/AttributeRouting.Tests/Global.asax.cs | 18 + .../Integration/css/jasmine.css | 82 + .../Integration/css/runner.css | 9 + .../Integration/js/lib/jasmine-html.js | 681 +++++ .../Integration/js/lib/jasmine.js | 2600 +++++++++++++++++ .../Integration/js/lib/jquery-1.9.1.min.js | 5 + .../Integration/js/specs/basic-usage.js | 16 + .../Integration/js/specs/specs.js | 46 + .../Integration/runner.html | 47 + .../Properties/AssemblyInfo.cs | 35 + .../Subjects/BasicUsageController.cs | 34 + .../Subjects/HttpBasicUsageController.cs | 34 + src/AttributeRouting.Tests/Web.Debug.config | 30 + src/AttributeRouting.Tests/Web.Release.config | 31 + src/AttributeRouting.Tests/Web.config | 48 + src/AttributeRouting.Tests/packages.config | 16 + src/AttributeRouting.sln | 13 + 19 files changed, 3951 insertions(+) create mode 100644 src/AttributeRouting.Tests/AttributeRouting.Tests.csproj create mode 100644 src/AttributeRouting.Tests/Global.asax create mode 100644 src/AttributeRouting.Tests/Global.asax.cs create mode 100644 src/AttributeRouting.Tests/Integration/css/jasmine.css create mode 100644 src/AttributeRouting.Tests/Integration/css/runner.css create mode 100644 src/AttributeRouting.Tests/Integration/js/lib/jasmine-html.js create mode 100644 src/AttributeRouting.Tests/Integration/js/lib/jasmine.js create mode 100644 src/AttributeRouting.Tests/Integration/js/lib/jquery-1.9.1.min.js create mode 100644 src/AttributeRouting.Tests/Integration/js/specs/basic-usage.js create mode 100644 src/AttributeRouting.Tests/Integration/js/specs/specs.js create mode 100644 src/AttributeRouting.Tests/Integration/runner.html create mode 100644 src/AttributeRouting.Tests/Properties/AssemblyInfo.cs create mode 100644 src/AttributeRouting.Tests/Subjects/BasicUsageController.cs create mode 100644 src/AttributeRouting.Tests/Subjects/HttpBasicUsageController.cs create mode 100644 src/AttributeRouting.Tests/Web.Debug.config create mode 100644 src/AttributeRouting.Tests/Web.Release.config create mode 100644 src/AttributeRouting.Tests/Web.config create mode 100644 src/AttributeRouting.Tests/packages.config diff --git a/src/AttributeRouting.Tests/AttributeRouting.Tests.csproj b/src/AttributeRouting.Tests/AttributeRouting.Tests.csproj new file mode 100644 index 0000000..f128f7d --- /dev/null +++ b/src/AttributeRouting.Tests/AttributeRouting.Tests.csproj @@ -0,0 +1,205 @@ + + + + + Debug + AnyCPU + + + 2.0 + {2A6712B2-0912-457D-A66E-982640E9F411} + {E3E379DF-F4C6-4180-9B81-6769533ABE47};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + AttributeRouting.Tests + AttributeRouting.Tests + v4.5 + false + true + + + + + ..\ + true + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + True + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + + True + ..\packages\Microsoft.AspNet.Mvc.FixedDisplayModes.1.0.0\lib\net40\Microsoft.Web.Mvc.FixedDisplayModes.dll + + + ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll + + + + + True + ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.Helpers.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.20710.0\lib\net40\System.Web.Http.WebHost.dll + + + True + ..\packages\Microsoft.AspNet.Mvc.4.0.20710.0\lib\net40\System.Web.Mvc.dll + + + True + ..\packages\Microsoft.AspNet.Razor.2.0.20715.0\lib\net40\System.Web.Razor.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.Deployment.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.Razor.dll + + + + + Global.asax + + + + + + + + + + + + + + + + + Web.config + + + Web.config + + + + + + + + + + + + {246f7aec-9429-4fbb-8747-92a3f025c711} + AttributeRouting.Web.Http.SelfHost + + + {a018fec5-45f8-44fb-bb6c-33697b418434} + AttributeRouting.Web.Http.WebHost + + + {ccde9ad7-3822-4b0b-aa19-df6698a85d3d} + AttributeRouting.Web.Http + + + {4604c450-ebf8-4a7f-bd3a-a24655c41fa4} + AttributeRouting.Web.Mvc + + + {c91c065b-a821-4890-9f31-f9e245d804d1} + AttributeRouting.Web + + + {871a79cf-c705-4c6b-8938-f9aa1e02aea4} + AttributeRouting + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + + + + True + True + 49551 + / + http://localhost:49540/ + False + False + + + False + + + + + + + \ No newline at end of file diff --git a/src/AttributeRouting.Tests/Global.asax b/src/AttributeRouting.Tests/Global.asax new file mode 100644 index 0000000..4da0999 --- /dev/null +++ b/src/AttributeRouting.Tests/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="AttributeRouting.Tests.MvcApplication" Language="C#" %> diff --git a/src/AttributeRouting.Tests/Global.asax.cs b/src/AttributeRouting.Tests/Global.asax.cs new file mode 100644 index 0000000..d5b54c4 --- /dev/null +++ b/src/AttributeRouting.Tests/Global.asax.cs @@ -0,0 +1,18 @@ +using System.Web.Http; +using System.Web.Routing; +using AttributeRouting.Web.Http.WebHost; +using AttributeRouting.Web.Mvc; + +namespace AttributeRouting.Tests +{ + // Note: For instructions on enabling IIS6 or IIS7 classic mode, + // visit http://go.microsoft.com/?LinkId=9394801 + public class MvcApplication : System.Web.HttpApplication + { + protected void Application_Start() + { + RouteTable.Routes.MapAttributeRoutes(); + GlobalConfiguration.Configuration.Routes.MapHttpAttributeRoutes(); + } + } +} \ No newline at end of file diff --git a/src/AttributeRouting.Tests/Integration/css/jasmine.css b/src/AttributeRouting.Tests/Integration/css/jasmine.css new file mode 100644 index 0000000..8c008dc --- /dev/null +++ b/src/AttributeRouting.Tests/Integration/css/jasmine.css @@ -0,0 +1,82 @@ +body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } + +#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } +#HTMLReporter a { text-decoration: none; } +#HTMLReporter a:hover { text-decoration: underline; } +#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } +#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } +#HTMLReporter #jasmine_content { position: fixed; right: 100%; } +#HTMLReporter .version { color: #aaaaaa; } +#HTMLReporter .banner { margin-top: 14px; } +#HTMLReporter .duration { color: #aaaaaa; float: right; } +#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } +#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } +#HTMLReporter .symbolSummary li.passed { font-size: 14px; } +#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } +#HTMLReporter .symbolSummary li.failed { line-height: 9px; } +#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } +#HTMLReporter .symbolSummary li.skipped { font-size: 14px; } +#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } +#HTMLReporter .symbolSummary li.pending { line-height: 11px; } +#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } +#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } +#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } +#HTMLReporter .runningAlert { background-color: #666666; } +#HTMLReporter .skippedAlert { background-color: #aaaaaa; } +#HTMLReporter .skippedAlert:first-child { background-color: #333333; } +#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } +#HTMLReporter .passingAlert { background-color: #a6b779; } +#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } +#HTMLReporter .failingAlert { background-color: #cf867e; } +#HTMLReporter .failingAlert:first-child { background-color: #b03911; } +#HTMLReporter .results { margin-top: 14px; } +#HTMLReporter #details { display: none; } +#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } +#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } +#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } +#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter.showDetails .summary { display: none; } +#HTMLReporter.showDetails #details { display: block; } +#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter .summary { margin-top: 14px; } +#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } +#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } +#HTMLReporter .summary .specSummary.failed a { color: #b03911; } +#HTMLReporter .description + .suite { margin-top: 0; } +#HTMLReporter .suite { margin-top: 14px; } +#HTMLReporter .suite a { color: #333333; } +#HTMLReporter #details .specDetail { margin-bottom: 28px; } +#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } +#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } +#HTMLReporter .resultMessage span.result { display: block; } +#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } + +#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } +#TrivialReporter a:visited, #TrivialReporter a { color: #303; } +#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } +#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } +#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } +#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } +#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } +#TrivialReporter .runner.running { background-color: yellow; } +#TrivialReporter .options { text-align: right; font-size: .8em; } +#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } +#TrivialReporter .suite .suite { margin: 5px; } +#TrivialReporter .suite.passed { background-color: #dfd; } +#TrivialReporter .suite.failed { background-color: #fdd; } +#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } +#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } +#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } +#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } +#TrivialReporter .spec.skipped { background-color: #bbb; } +#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } +#TrivialReporter .passed { background-color: #cfc; display: none; } +#TrivialReporter .failed { background-color: #fbb; } +#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } +#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } +#TrivialReporter .resultMessage .mismatch { color: black; } +#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } +#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } +#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } +#TrivialReporter #jasmine_content { position: fixed; right: 100%; } +#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } diff --git a/src/AttributeRouting.Tests/Integration/css/runner.css b/src/AttributeRouting.Tests/Integration/css/runner.css new file mode 100644 index 0000000..08d5025 --- /dev/null +++ b/src/AttributeRouting.Tests/Integration/css/runner.css @@ -0,0 +1,9 @@ +.view-all-routes { + display: block; + padding: 5px; + font: 1em Helvetica, Arial, sans-serif; + background-color: #333; + color: white; + text-decoration: none; + text-align: center; +} \ No newline at end of file diff --git a/src/AttributeRouting.Tests/Integration/js/lib/jasmine-html.js b/src/AttributeRouting.Tests/Integration/js/lib/jasmine-html.js new file mode 100644 index 0000000..543d569 --- /dev/null +++ b/src/AttributeRouting.Tests/Integration/js/lib/jasmine-html.js @@ -0,0 +1,681 @@ +jasmine.HtmlReporterHelpers = {}; + +jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { + var results = child.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + + return status; +}; + +jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { + var parentDiv = this.dom.summary; + var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; + var parent = child[parentSuite]; + + if (parent) { + if (typeof this.views.suites[parent.id] == 'undefined') { + this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); + } + parentDiv = this.views.suites[parent.id].element; + } + + parentDiv.appendChild(childElement); +}; + + +jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { + for(var fn in jasmine.HtmlReporterHelpers) { + ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; + } +}; + +jasmine.HtmlReporter = function(_doc) { + var self = this; + var doc = _doc || window.document; + + var reporterView; + + var dom = {}; + + // Jasmine Reporter Public Interface + self.logRunningSpecs = false; + + self.reportRunnerStarting = function(runner) { + var specs = runner.specs() || []; + + if (specs.length == 0) { + return; + } + + createReporterDom(runner.env.versionString()); + doc.body.appendChild(dom.reporter); + setExceptionHandling(); + + reporterView = new jasmine.HtmlReporter.ReporterView(dom); + reporterView.addSpecs(specs, self.specFilter); + }; + + self.reportRunnerResults = function(runner) { + reporterView && reporterView.complete(); + }; + + self.reportSuiteResults = function(suite) { + reporterView.suiteComplete(suite); + }; + + self.reportSpecStarting = function(spec) { + if (self.logRunningSpecs) { + self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } + }; + + self.reportSpecResults = function(spec) { + reporterView.specComplete(spec); + }; + + self.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } + }; + + self.specFilter = function(spec) { + if (!focusedSpecName()) { + return true; + } + + return spec.getFullName().indexOf(focusedSpecName()) === 0; + }; + + return self; + + function focusedSpecName() { + var specName; + + (function memoizeFocusedSpec() { + if (specName) { + return; + } + + var paramMap = []; + var params = jasmine.HtmlReporter.parameters(doc); + + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + specName = paramMap.spec; + })(); + + return specName; + } + + function createReporterDom(version) { + dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, + dom.banner = self.createDom('div', { className: 'banner' }, + self.createDom('span', { className: 'title' }, "Jasmine "), + self.createDom('span', { className: 'version' }, version)), + + dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), + dom.alert = self.createDom('div', {className: 'alert'}, + self.createDom('span', { className: 'exceptions' }, + self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'), + self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), + dom.results = self.createDom('div', {className: 'results'}, + dom.summary = self.createDom('div', { className: 'summary' }), + dom.details = self.createDom('div', { id: 'details' })) + ); + } + + function noTryCatch() { + return window.location.search.match(/catch=false/); + } + + function searchWithCatch() { + var params = jasmine.HtmlReporter.parameters(window.document); + var removed = false; + var i = 0; + + while (!removed && i < params.length) { + if (params[i].match(/catch=/)) { + params.splice(i, 1); + removed = true; + } + i++; + } + if (jasmine.CATCH_EXCEPTIONS) { + params.push("catch=false"); + } + + return params.join("&"); + } + + function setExceptionHandling() { + var chxCatch = document.getElementById('no_try_catch'); + + if (noTryCatch()) { + chxCatch.setAttribute('checked', true); + jasmine.CATCH_EXCEPTIONS = false; + } + chxCatch.onclick = function() { + window.location.search = searchWithCatch(); + }; + } +}; +jasmine.HtmlReporter.parameters = function(doc) { + var paramStr = doc.location.search.substring(1); + var params = []; + + if (paramStr.length > 0) { + params = paramStr.split('&'); + } + return params; +} +jasmine.HtmlReporter.sectionLink = function(sectionName) { + var link = '?'; + var params = []; + + if (sectionName) { + params.push('spec=' + encodeURIComponent(sectionName)); + } + if (!jasmine.CATCH_EXCEPTIONS) { + params.push("catch=false"); + } + if (params.length > 0) { + link += params.join("&"); + } + + return link; +}; +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); +jasmine.HtmlReporter.ReporterView = function(dom) { + this.startedAt = new Date(); + this.runningSpecCount = 0; + this.completeSpecCount = 0; + this.passedCount = 0; + this.failedCount = 0; + this.skippedCount = 0; + + this.createResultsMenu = function() { + this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, + this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), + ' | ', + this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); + + this.summaryMenuItem.onclick = function() { + dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); + }; + + this.detailsMenuItem.onclick = function() { + showDetails(); + }; + }; + + this.addSpecs = function(specs, specFilter) { + this.totalSpecCount = specs.length; + + this.views = { + specs: {}, + suites: {} + }; + + for (var i = 0; i < specs.length; i++) { + var spec = specs[i]; + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); + if (specFilter(spec)) { + this.runningSpecCount++; + } + } + }; + + this.specComplete = function(spec) { + this.completeSpecCount++; + + if (isUndefined(this.views.specs[spec.id])) { + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); + } + + var specView = this.views.specs[spec.id]; + + switch (specView.status()) { + case 'passed': + this.passedCount++; + break; + + case 'failed': + this.failedCount++; + break; + + case 'skipped': + this.skippedCount++; + break; + } + + specView.refresh(); + this.refresh(); + }; + + this.suiteComplete = function(suite) { + var suiteView = this.views.suites[suite.id]; + if (isUndefined(suiteView)) { + return; + } + suiteView.refresh(); + }; + + this.refresh = function() { + + if (isUndefined(this.resultsMenu)) { + this.createResultsMenu(); + } + + // currently running UI + if (isUndefined(this.runningAlert)) { + this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); + dom.alert.appendChild(this.runningAlert); + } + this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); + + // skipped specs UI + if (isUndefined(this.skippedAlert)) { + this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); + } + + this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.skippedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.skippedAlert); + } + + // passing specs UI + if (isUndefined(this.passedAlert)) { + this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); + } + this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); + + // failing specs UI + if (isUndefined(this.failedAlert)) { + this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); + } + this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); + + if (this.failedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.failedAlert); + dom.alert.appendChild(this.resultsMenu); + } + + // summary info + this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); + this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; + }; + + this.complete = function() { + dom.alert.removeChild(this.runningAlert); + + this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.failedCount === 0) { + dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); + } else { + showDetails(); + } + + dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); + }; + + return this; + + function showDetails() { + if (dom.reporter.className.search(/showDetails/) === -1) { + dom.reporter.className += " showDetails"; + } + } + + function isUndefined(obj) { + return typeof obj === 'undefined'; + } + + function isDefined(obj) { + return !isUndefined(obj); + } + + function specPluralizedFor(count) { + var str = count + " spec"; + if (count > 1) { + str += "s" + } + return str; + } + +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); + + +jasmine.HtmlReporter.SpecView = function(spec, dom, views) { + this.spec = spec; + this.dom = dom; + this.views = views; + + this.symbol = this.createDom('li', { className: 'pending' }); + this.dom.symbolSummary.appendChild(this.symbol); + + this.summary = this.createDom('div', { className: 'specSummary' }, + this.createDom('a', { + className: 'description', + href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.description) + ); + + this.detail = this.createDom('div', { className: 'specDetail' }, + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.getFullName()) + ); +}; + +jasmine.HtmlReporter.SpecView.prototype.status = function() { + return this.getSpecStatus(this.spec); +}; + +jasmine.HtmlReporter.SpecView.prototype.refresh = function() { + this.symbol.className = this.status(); + + switch (this.status()) { + case 'skipped': + break; + + case 'passed': + this.appendSummaryToSuiteDiv(); + break; + + case 'failed': + this.appendSummaryToSuiteDiv(); + this.appendFailureDetail(); + break; + } +}; + +jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { + this.summary.className += ' ' + this.status(); + this.appendToSummary(this.spec, this.summary); +}; + +jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { + this.detail.className += ' ' + this.status(); + + var resultItems = this.spec.results().getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + this.detail.appendChild(messagesDiv); + this.dom.details.appendChild(this.detail); + } +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { + this.suite = suite; + this.dom = dom; + this.views = views; + + this.element = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) + ); + + this.appendToSummary(this.suite, this.element); +}; + +jasmine.HtmlReporter.SuiteView.prototype.status = function() { + return this.getSpecStatus(this.suite); +}; + +jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { + this.element.className += " " + this.status(); +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); + +/* @deprecated Use jasmine.HtmlReporter instead + */ +jasmine.TrivialReporter = function(doc) { + this.document = doc || document; + this.suiteDivs = {}; + this.logRunningSpecs = false; +}; + +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { el.appendChild(child); } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { + var showPassed, showSkipped; + + this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, + this.createDom('div', { className: 'banner' }, + this.createDom('div', { className: 'logo' }, + this.createDom('span', { className: 'title' }, "Jasmine"), + this.createDom('span', { className: 'version' }, runner.env.versionString())), + this.createDom('div', { className: 'options' }, + "Show ", + showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), + showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") + ) + ), + + this.runnerDiv = this.createDom('div', { className: 'runner running' }, + this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), + this.runnerMessageSpan = this.createDom('span', {}, "Running..."), + this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) + ); + + this.document.body.appendChild(this.outerDiv); + + var suites = runner.suites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + var suiteDiv = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); + this.suiteDivs[suite.id] = suiteDiv; + var parentDiv = this.outerDiv; + if (suite.parentSuite) { + parentDiv = this.suiteDivs[suite.parentSuite.id]; + } + parentDiv.appendChild(suiteDiv); + } + + this.startedAt = new Date(); + + var self = this; + showPassed.onclick = function(evt) { + if (showPassed.checked) { + self.outerDiv.className += ' show-passed'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); + } + }; + + showSkipped.onclick = function(evt) { + if (showSkipped.checked) { + self.outerDiv.className += ' show-skipped'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); + } + }; +}; + +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { + var results = runner.results(); + var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; + this.runnerDiv.setAttribute("class", className); + //do it twice for IE + this.runnerDiv.setAttribute("className", className); + var specs = runner.specs(); + var specCount = 0; + for (var i = 0; i < specs.length; i++) { + if (this.specFilter(specs[i])) { + specCount++; + } + } + var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); + message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; + this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); + + this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); +}; + +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { + var results = suite.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.totalCount === 0) { // todo: change this to check results.skipped + status = 'skipped'; + } + this.suiteDivs[suite.id].className += " " + status; +}; + +jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { + if (this.logRunningSpecs) { + this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } +}; + +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { + var results = spec.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + var specDiv = this.createDom('div', { className: 'spec ' + status }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(spec.getFullName()), + title: spec.getFullName() + }, spec.description)); + + + var resultItems = results.getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + specDiv.appendChild(messagesDiv); + } + + this.suiteDivs[spec.suite.id].appendChild(specDiv); +}; + +jasmine.TrivialReporter.prototype.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } +}; + +jasmine.TrivialReporter.prototype.getLocation = function() { + return this.document.location; +}; + +jasmine.TrivialReporter.prototype.specFilter = function(spec) { + var paramMap = {}; + var params = this.getLocation().search.substring(1).split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + if (!paramMap.spec) { + return true; + } + return spec.getFullName().indexOf(paramMap.spec) === 0; +}; diff --git a/src/AttributeRouting.Tests/Integration/js/lib/jasmine.js b/src/AttributeRouting.Tests/Integration/js/lib/jasmine.js new file mode 100644 index 0000000..6b3459b --- /dev/null +++ b/src/AttributeRouting.Tests/Integration/js/lib/jasmine.js @@ -0,0 +1,2600 @@ +var isCommonJS = typeof window == "undefined" && typeof exports == "object"; + +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; +if (isCommonJS) exports.jasmine = jasmine; +/** + * @private + */ +jasmine.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); +}; + +/** + * Use jasmine.undefined instead of undefined, since undefined is just + * a plain old variable and may be redefined by somebody else. + * + * @private + */ +jasmine.undefined = jasmine.___undefined___; + +/** + * Show diagnostic messages in the console if set to true + * + */ +jasmine.VERBOSE = false; + +/** + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. + * + */ +jasmine.DEFAULT_UPDATE_INTERVAL = 250; + +/** + * Maximum levels of nesting that will be included when an object is pretty-printed + */ +jasmine.MAX_PRETTY_PRINT_DEPTH = 40; + +/** + * Default timeout interval in milliseconds for waitsFor() blocks. + */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +/** + * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite. + * Set to false to let the exception bubble up in the browser. + * + */ +jasmine.CATCH_EXCEPTIONS = true; + +jasmine.getGlobal = function() { + function getGlobal() { + return this; + } + + return getGlobal(); +}; + +/** + * Allows for bound functions to be compared. Internal use only. + * + * @ignore + * @private + * @param base {Object} bound 'this' for the function + * @param name {Function} function to find + */ +jasmine.bindOriginal_ = function(base, name) { + var original = base[name]; + if (original.apply) { + return function() { + return original.apply(base, arguments); + }; + } else { + // IE support + return jasmine.getGlobal()[name]; + } +}; + +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); + +jasmine.MessageResult = function(values) { + this.type = 'log'; + this.values = values; + this.trace = new Error(); // todo: test better +}; + +jasmine.MessageResult.prototype.toString = function() { + var text = ""; + for (var i = 0; i < this.values.length; i++) { + if (i > 0) text += " "; + if (jasmine.isString_(this.values[i])) { + text += this.values[i]; + } else { + text += jasmine.pp(this.values[i]); + } + } + return text; +}; + +jasmine.ExpectationResult = function(params) { + this.type = 'expect'; + this.matcherName = params.matcherName; + this.passed_ = params.passed; + this.expected = params.expected; + this.actual = params.actual; + this.message = this.passed_ ? 'Passed.' : params.message; + + var trace = (params.trace || new Error(this.message)); + this.trace = this.passed_ ? '' : trace; +}; + +jasmine.ExpectationResult.prototype.toString = function () { + return this.message; +}; + +jasmine.ExpectationResult.prototype.passed = function () { + return this.passed_; +}; + +/** + * Getter for the Jasmine environment. Ensures one gets created + */ +jasmine.getEnv = function() { + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); + return env; +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isArray_ = function(value) { + return jasmine.isA_("Array", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function(value) { + return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function(value) { + return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; +}; + +/** + * Pretty printer for expecations. Takes any object and turns it into a human-readable string. + * + * @param value {Object} an object to be outputted + * @returns {String} + */ +jasmine.pp = function(value) { + var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; +}; + +/** + * Returns true if the object is a DOM Node. + * + * @param {Object} obj object to check + * @returns {Boolean} + */ +jasmine.isDomNode = function(obj) { + return obj.nodeType > 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function(clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the + * attributes on the object. + * + * @example + * // don't care about any other attributes than foo. + * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"}); + * + * @param sample {Object} sample + * @returns matchable object for the sample + */ +jasmine.objectContaining = function (sample) { + return new jasmine.Matchers.ObjectContaining(sample); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). + * + * Spies are torn down at the end of every spec. + * + * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).toHaveBeenCalled(); + * expect(foo.not).toHaveBeenCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function(name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function() { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function() { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function(value) { + this.plan = function() { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function(exceptionMsg) { + this.plan = function() { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function(fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function() { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function(name) { + + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object: this, args: args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function(putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function(baseName, methodNames) { + if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { + throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the current spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.log = function() { + var spec = jasmine.getEnv().currentSpec; + spec.log.apply(spec, arguments); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function(obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; +if (isCommonJS) exports.spyOn = spyOn; + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function(desc, func) { + return jasmine.getEnv().it(desc, func); +}; +if (isCommonJS) exports.it = it; + +/** + * Creates a disabled Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function(desc, func) { + return jasmine.getEnv().xit(desc, func); +}; +if (isCommonJS) exports.xit = xit; + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + * @return {jasmine.Matchers} + */ +var expect = function(actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; +if (isCommonJS) exports.expect = expect; + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function(func) { + jasmine.getEnv().currentSpec.runs(func); +}; +if (isCommonJS) exports.runs = runs; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +var waits = function(timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; +if (isCommonJS) exports.waits = waits; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); +}; +if (isCommonJS) exports.waitsFor = waitsFor; + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function(beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; +if (isCommonJS) exports.beforeEach = beforeEach; + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function(afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; +if (isCommonJS) exports.afterEach = afterEach; + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function(description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; +if (isCommonJS) exports.describe = describe; + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function(description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; +if (isCommonJS) exports.xdescribe = xdescribe; + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { + function tryIt(f) { + try { + return f(); + } catch(e) { + } + return null; + } + + var xhr = tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP"); + }) || + tryIt(function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }); + + if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); + + return xhr; +} : XMLHttpRequest; +/** + * @namespace + */ +jasmine.util = {}; + +/** + * Declare that a child class inherit it's prototype from the parent class. + * + * @private + * @param {Function} childClass + * @param {Function} parentClass + */ +jasmine.util.inherit = function(childClass, parentClass) { + /** + * @private + */ + var subclass = function() { + }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass(); +}; + +jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +jasmine.util.htmlEscape = function(str) { + if (!str) return str; + return str.replace(/&/g, '&') + .replace(//g, '>'); +}; + +jasmine.util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); + return arrayOfArgs; +}; + +jasmine.util.extend = function(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; +}; + +/** + * Environment for Jasmine + * + * @constructor + */ +jasmine.Env = function() { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner_ = new jasmine.Runner(this); + + this.reporter = new jasmine.MultiReporter(); + + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; + this.lastUpdate = 0; + this.specFilter = function() { + return true; + }; + + this.nextSpecId_ = 0; + this.nextSuiteId_ = 0; + this.equalityTesters_ = []; + + // wrap matchers + this.matchersClass = function() { + jasmine.Matchers.apply(this, arguments); + }; + jasmine.util.inherit(this.matchersClass, jasmine.Matchers); + + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); +}; + + +jasmine.Env.prototype.setTimeout = jasmine.setTimeout; +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; +jasmine.Env.prototype.setInterval = jasmine.setInterval; +jasmine.Env.prototype.clearInterval = jasmine.clearInterval; + +/** + * @returns an object containing jasmine version build info, if set. + */ +jasmine.Env.prototype.version = function () { + if (jasmine.version_) { + return jasmine.version_; + } else { + throw new Error('Version not set'); + } +}; + +/** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function() { + if (!jasmine.version_) { + return "version unknown"; + } + + var version = this.version(); + var versionString = version.major + "." + version.minor + "." + version.build; + if (version.release_candidate) { + versionString += ".rc" + version.release_candidate; + } + versionString += " revision " + version.revision; + return versionString; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSpecId = function () { + return this.nextSpecId_++; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSuiteId = function () { + return this.nextSuiteId_++; +}; + +/** + * Register a reporter to receive status updates from Jasmine. + * @param {jasmine.Reporter} reporter An object which will receive status updates. + */ +jasmine.Env.prototype.addReporter = function(reporter) { + this.reporter.addReporter(reporter); +}; + +jasmine.Env.prototype.execute = function() { + this.currentRunner_.execute(); +}; + +jasmine.Env.prototype.describe = function(description, specDefinitions) { + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); + + var parentSuite = this.currentSuite; + if (parentSuite) { + parentSuite.add(suite); + } else { + this.currentRunner_.add(suite); + } + + this.currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch(e) { + declarationError = e; + } + + if (declarationError) { + this.it("encountered a declaration exception", function() { + throw declarationError; + }); + } + + this.currentSuite = parentSuite; + + return suite; +}; + +jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { + if (this.currentSuite) { + this.currentSuite.beforeEach(beforeEachFunction); + } else { + this.currentRunner_.beforeEach(beforeEachFunction); + } +}; + +jasmine.Env.prototype.currentRunner = function () { + return this.currentRunner_; +}; + +jasmine.Env.prototype.afterEach = function(afterEachFunction) { + if (this.currentSuite) { + this.currentSuite.afterEach(afterEachFunction); + } else { + this.currentRunner_.afterEach(afterEachFunction); + } + +}; + +jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { + return { + execute: function() { + } + }; +}; + +jasmine.Env.prototype.it = function(description, func) { + var spec = new jasmine.Spec(this, this.currentSuite, description); + this.currentSuite.add(spec); + this.currentSpec = spec; + + if (func) { + spec.runs(func); + } + + return spec; +}; + +jasmine.Env.prototype.xit = function(desc, func) { + return { + id: this.nextSpecId(), + runs: function() { + } + }; +}; + +jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.source != b.source) + mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/"); + + if (a.ignoreCase != b.ignoreCase) + mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.global != b.global) + mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.multiline != b.multiline) + mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier"); + + if (a.sticky != b.sticky) + mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier"); + + return (mismatchValues.length === 0); +}; + +jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { + return true; + } + + a.__Jasmine_been_here_before__ = b; + b.__Jasmine_been_here_before__ = a; + + var hasKey = function(obj, keyName) { + return obj !== null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (property == '__Jasmine_been_here_before__') continue; + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); + } + } + + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { + mismatchValues.push("arrays were not the same length"); + } + + delete a.__Jasmine_been_here_before__; + delete b.__Jasmine_been_here_before__; + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + for (var i = 0; i < this.equalityTesters_.length; i++) { + var equalityTester = this.equalityTesters_[i]; + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); + if (result !== jasmine.undefined) return result; + } + + if (a === b) return true; + + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { + return (a == jasmine.undefined && b == jasmine.undefined); + } + + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { + return a === b; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() == b.getTime(); + } + + if (a.jasmineMatches) { + return a.jasmineMatches(b); + } + + if (b.jasmineMatches) { + return b.jasmineMatches(a); + } + + if (a instanceof jasmine.Matchers.ObjectContaining) { + return a.matches(b); + } + + if (b instanceof jasmine.Matchers.ObjectContaining) { + return b.matches(a); + } + + if (jasmine.isString_(a) && jasmine.isString_(b)) { + return (a == b); + } + + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { + return (a == b); + } + + if (a instanceof RegExp && b instanceof RegExp) { + return this.compareRegExps_(a, b, mismatchKeys, mismatchValues); + } + + if (typeof a === "object" && typeof b === "object") { + return this.compareObjects_(a, b, mismatchKeys, mismatchValues); + } + + //Straight check + return (a === b); +}; + +jasmine.Env.prototype.contains_ = function(haystack, needle) { + if (jasmine.isArray_(haystack)) { + for (var i = 0; i < haystack.length; i++) { + if (this.equals_(haystack[i], needle)) return true; + } + return false; + } + return haystack.indexOf(needle) >= 0; +}; + +jasmine.Env.prototype.addEqualityTester = function(equalityTester) { + this.equalityTesters_.push(equalityTester); +}; +/** No-op base class for Jasmine reporters. + * + * @constructor + */ +jasmine.Reporter = function() { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerResults = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecStarting = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecResults = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.log = function(str) { +}; + +/** + * Blocks are functions with executable code that make up a spec. + * + * @constructor + * @param {jasmine.Env} env + * @param {Function} func + * @param {jasmine.Spec} spec + */ +jasmine.Block = function(env, func, spec) { + this.env = env; + this.func = func; + this.spec = spec; +}; + +jasmine.Block.prototype.execute = function(onComplete) { + if (!jasmine.CATCH_EXCEPTIONS) { + this.func.apply(this.spec); + } + else { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } + } + onComplete(); +}; +/** JavaScript API reporter. + * + * @constructor + */ +jasmine.JsApiReporter = function() { + this.started = false; + this.finished = false; + this.suites_ = []; + this.results_ = {}; +}; + +jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { + this.started = true; + var suites = runner.topLevelSuites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + this.suites_.push(this.summarize_(suite)); + } +}; + +jasmine.JsApiReporter.prototype.suites = function() { + return this.suites_; +}; + +jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { + var isSuite = suiteOrSpec instanceof jasmine.Suite; + var summary = { + id: suiteOrSpec.id, + name: suiteOrSpec.description, + type: isSuite ? 'suite' : 'spec', + children: [] + }; + + if (isSuite) { + var children = suiteOrSpec.children(); + for (var i = 0; i < children.length; i++) { + summary.children.push(this.summarize_(children[i])); + } + } + return summary; +}; + +jasmine.JsApiReporter.prototype.results = function() { + return this.results_; +}; + +jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { + return this.results_[specId]; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { + this.finished = true; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { + this.results_[spec.id] = { + messages: spec.results().getItems(), + result: spec.results().failedCount > 0 ? "failed" : "passed" + }; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.log = function(str) { +}; + +jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ + var results = {}; + for (var i = 0; i < specIds.length; i++) { + var specId = specIds[i]; + results[specId] = this.summarizeResult_(this.results_[specId]); + } + return results; +}; + +jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ + var summaryMessages = []; + var messagesLength = result.messages.length; + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { + var resultMessage = result.messages[messageIndex]; + summaryMessages.push({ + text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, + passed: resultMessage.passed ? resultMessage.passed() : true, + type: resultMessage.type, + message: resultMessage.message, + trace: { + stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined + } + }); + } + + return { + result : result.result, + messages : summaryMessages + }; +}; + +/** + * @constructor + * @param {jasmine.Env} env + * @param actual + * @param {jasmine.Spec} spec + */ +jasmine.Matchers = function(env, actual, spec, opt_isNot) { + this.env = env; + this.actual = actual; + this.spec = spec; + this.isNot = opt_isNot || false; + this.reportWasCalled_ = false; +}; + +// todo: @deprecated as of Jasmine 0.11, remove soon [xw] +jasmine.Matchers.pp = function(str) { + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +}; + +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] +jasmine.Matchers.prototype.report = function(result, failing_message, details) { + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); +}; + +jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { + for (var methodName in prototype) { + if (methodName == 'report') continue; + var orig = prototype[methodName]; + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); + } +}; + +jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { + return function() { + var matcherArgs = jasmine.util.argsToArray(arguments); + var result = matcherFunction.apply(this, arguments); + + if (this.isNot) { + result = !result; + } + + if (this.reportWasCalled_) return result; + + var message; + if (!result) { + if (this.message) { + message = this.message.apply(this, arguments); + if (jasmine.isArray_(message)) { + message = message[this.isNot ? 1 : 0]; + } + } else { + var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; + if (matcherArgs.length > 0) { + for (var i = 0; i < matcherArgs.length; i++) { + if (i > 0) message += ","; + message += " " + jasmine.pp(matcherArgs[i]); + } + } + message += "."; + } + } + var expectationResult = new jasmine.ExpectationResult({ + matcherName: matcherName, + passed: result, + expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], + actual: this.actual, + message: message + }); + this.spec.addMatcherResult(expectationResult); + return jasmine.undefined; + }; +}; + + + + +/** + * toBe: compares the actual to the expected using === + * @param expected + */ +jasmine.Matchers.prototype.toBe = function(expected) { + return this.actual === expected; +}; + +/** + * toNotBe: compares the actual to the expected using !== + * @param expected + * @deprecated as of 1.0. Use not.toBe() instead. + */ +jasmine.Matchers.prototype.toNotBe = function(expected) { + return this.actual !== expected; +}; + +/** + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. + * + * @param expected + */ +jasmine.Matchers.prototype.toEqual = function(expected) { + return this.env.equals_(this.actual, expected); +}; + +/** + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual + * @param expected + * @deprecated as of 1.0. Use not.toEqual() instead. + */ +jasmine.Matchers.prototype.toNotEqual = function(expected) { + return !this.env.equals_(this.actual, expected); +}; + +/** + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes + * a pattern or a String. + * + * @param expected + */ +jasmine.Matchers.prototype.toMatch = function(expected) { + return new RegExp(expected).test(this.actual); +}; + +/** + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch + * @param expected + * @deprecated as of 1.0. Use not.toMatch() instead. + */ +jasmine.Matchers.prototype.toNotMatch = function(expected) { + return !(new RegExp(expected).test(this.actual)); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeDefined = function() { + return (this.actual !== jasmine.undefined); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeUndefined = function() { + return (this.actual === jasmine.undefined); +}; + +/** + * Matcher that compares the actual to null. + */ +jasmine.Matchers.prototype.toBeNull = function() { + return (this.actual === null); +}; + +/** + * Matcher that compares the actual to NaN. + */ +jasmine.Matchers.prototype.toBeNaN = function() { + this.message = function() { + return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ]; + }; + + return (this.actual !== this.actual); +}; + +/** + * Matcher that boolean not-nots the actual. + */ +jasmine.Matchers.prototype.toBeTruthy = function() { + return !!this.actual; +}; + + +/** + * Matcher that boolean nots the actual. + */ +jasmine.Matchers.prototype.toBeFalsy = function() { + return !this.actual; +}; + + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called. + */ +jasmine.Matchers.prototype.toHaveBeenCalled = function() { + if (arguments.length > 0) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to have been called.", + "Expected spy " + this.actual.identity + " not to have been called." + ]; + }; + + return this.actual.wasCalled; +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was not called. + * + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead + */ +jasmine.Matchers.prototype.wasNotCalled = function() { + if (arguments.length > 0) { + throw new Error('wasNotCalled does not take arguments'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to not have been called.", + "Expected spy " + this.actual.identity + " to have been called." + ]; + }; + + return !this.actual.wasCalled; +}; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. + * + * @example + * + */ +jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + this.message = function() { + var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."; + var positiveMessage = ""; + if (this.actual.callCount === 0) { + positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called."; + } else { + positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '') + } + return [positiveMessage, invertedMessage]; + }; + + return this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; + +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasNotCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" + ]; + }; + + return !this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** + * Matcher that checks that the expected item is an element in the actual Array. + * + * @param {Object} expected + */ +jasmine.Matchers.prototype.toContain = function(expected) { + return this.env.contains_(this.actual, expected); +}; + +/** + * Matcher that checks that the expected item is NOT an element in the actual Array. + * + * @param {Object} expected + * @deprecated as of 1.0. Use not.toContain() instead. + */ +jasmine.Matchers.prototype.toNotContain = function(expected) { + return !this.env.contains_(this.actual, expected); +}; + +jasmine.Matchers.prototype.toBeLessThan = function(expected) { + return this.actual < expected; +}; + +jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { + return this.actual > expected; +}; + +/** + * Matcher that checks that the expected item is equal to the actual item + * up to a given level of decimal precision (default 2). + * + * @param {Number} expected + * @param {Number} precision, as number of decimal places + */ +jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { + if (!(precision === 0)) { + precision = precision || 2; + } + return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2); +}; + +/** + * Matcher that checks that the expected exception was thrown by the actual. + * + * @param {String} [expected] + */ +jasmine.Matchers.prototype.toThrow = function(expected) { + var result = false; + var exception; + if (typeof this.actual != 'function') { + throw new Error('Actual is not a function'); + } + try { + this.actual(); + } catch (e) { + exception = e; + } + if (exception) { + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); + } + + var not = this.isNot ? "not " : ""; + + this.message = function() { + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { + return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); + } else { + return "Expected function to throw an exception."; + } + }; + + return result; +}; + +jasmine.Matchers.Any = function(expectedClass) { + this.expectedClass = expectedClass; +}; + +jasmine.Matchers.Any.prototype.jasmineMatches = function(other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedClass == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedClass == Object) { + return typeof other == 'object'; + } + + return other instanceof this.expectedClass; +}; + +jasmine.Matchers.Any.prototype.jasmineToString = function() { + return ''; +}; + +jasmine.Matchers.ObjectContaining = function (sample) { + this.sample = sample; +}; + +jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + var env = jasmine.getEnv(); + + var hasKey = function(obj, keyName) { + return obj != null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in this.sample) { + if (!hasKey(other, property) && hasKey(this.sample, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual."); + } + } + + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () { + return ""; +}; +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +jasmine.FakeTimer = function() { + this.reset(); + + var self = this; + self.setTimeout = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); + return self.timeoutsMade; + }; + + self.setInterval = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); + return self.timeoutsMade; + }; + + self.clearTimeout = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + + self.clearInterval = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + +}; + +jasmine.FakeTimer.prototype.reset = function() { + this.timeoutsMade = 0; + this.scheduledFunctions = {}; + this.nowMillis = 0; +}; + +jasmine.FakeTimer.prototype.tick = function(millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; +}; + +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != jasmine.undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = jasmine.undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + var funcToRun = funcsToRun[i]; + this.nowMillis = funcToRun.runAtMillis; + funcToRun.funcToCall(); + if (funcToRun.recurring) { + this.scheduleFunction(funcToRun.timeoutKey, + funcToRun.funcToCall, + funcToRun.millis, + true); + } + } catch(e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } +}; + +jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { + this.scheduledFunctions[timeoutKey] = { + runAtMillis: this.nowMillis + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis + }; +}; + +/** + * @namespace + */ +jasmine.Clock = { + defaultFakeTimer: new jasmine.FakeTimer(), + + reset: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.reset(); + }, + + tick: function(millis) { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.tick(millis); + }, + + runFunctionsWithinRange: function(oldMillis, nowMillis) { + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); + }, + + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); + }, + + useMock: function() { + if (!jasmine.Clock.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Clock.uninstallMock); + + jasmine.Clock.installMock(); + } + }, + + installMock: function() { + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; + }, + + uninstallMock: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.installed = jasmine.Clock.real; + }, + + real: { + setTimeout: jasmine.getGlobal().setTimeout, + clearTimeout: jasmine.getGlobal().clearTimeout, + setInterval: jasmine.getGlobal().setInterval, + clearInterval: jasmine.getGlobal().clearInterval + }, + + assertInstalled: function() { + if (!jasmine.Clock.isInstalled()) { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }, + + isInstalled: function() { + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; + }, + + installed: null +}; +jasmine.Clock.installed = jasmine.Clock.real; + +//else for IE support +jasmine.getGlobal().setTimeout = function(funcToCall, millis) { + if (jasmine.Clock.installed.setTimeout.apply) { + return jasmine.Clock.installed.setTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.setTimeout(funcToCall, millis); + } +}; + +jasmine.getGlobal().setInterval = function(funcToCall, millis) { + if (jasmine.Clock.installed.setInterval.apply) { + return jasmine.Clock.installed.setInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.setInterval(funcToCall, millis); + } +}; + +jasmine.getGlobal().clearTimeout = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearTimeout(timeoutKey); + } +}; + +jasmine.getGlobal().clearInterval = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearInterval(timeoutKey); + } +}; + +/** + * @constructor + */ +jasmine.MultiReporter = function() { + this.subReporters_ = []; +}; +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); + +jasmine.MultiReporter.prototype.addReporter = function(reporter) { + this.subReporters_.push(reporter); +}; + +(function() { + var functionNames = [ + "reportRunnerStarting", + "reportRunnerResults", + "reportSuiteResults", + "reportSpecStarting", + "reportSpecResults", + "log" + ]; + for (var i = 0; i < functionNames.length; i++) { + var functionName = functionNames[i]; + jasmine.MultiReporter.prototype[functionName] = (function(functionName) { + return function() { + for (var j = 0; j < this.subReporters_.length; j++) { + var subReporter = this.subReporters_[j]; + if (subReporter[functionName]) { + subReporter[functionName].apply(subReporter, arguments); + } + } + }; + })(functionName); + } +})(); +/** + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults + * + * @constructor + */ +jasmine.NestedResults = function() { + /** + * The total count of results + */ + this.totalCount = 0; + /** + * Number of passed results + */ + this.passedCount = 0; + /** + * Number of failed results + */ + this.failedCount = 0; + /** + * Was this suite/spec skipped? + */ + this.skipped = false; + /** + * @ignore + */ + this.items_ = []; +}; + +/** + * Roll up the result counts. + * + * @param result + */ +jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +/** + * Adds a log message. + * @param values Array of message parts which will be concatenated later. + */ +jasmine.NestedResults.prototype.log = function(values) { + this.items_.push(new jasmine.MessageResult(values)); +}; + +/** + * Getter for the results: message & results. + */ +jasmine.NestedResults.prototype.getItems = function() { + return this.items_; +}; + +/** + * Adds a result, tracking counts (total, passed, & failed) + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result + */ +jasmine.NestedResults.prototype.addResult = function(result) { + if (result.type != 'log') { + if (result.items_) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed()) { + this.passedCount++; + } else { + this.failedCount++; + } + } + } + this.items_.push(result); +}; + +/** + * @returns {Boolean} True if everything below passed + */ +jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; +/** + * Base class for pretty printing for expectation results. + */ +jasmine.PrettyPrinter = function() { + this.ppNestLevel_ = 0; +}; + +/** + * Formats a value in a nice, human-readable string. + * + * @param value + */ +jasmine.PrettyPrinter.prototype.format = function(value) { + this.ppNestLevel_++; + try { + if (value === jasmine.undefined) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === jasmine.getGlobal()) { + this.emitScalar(''); + } else if (value.jasmineToString) { + this.emitScalar(value.jasmineToString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (jasmine.isSpy(value)) { + this.emitScalar("spy on " + value.identity); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.__Jasmine_been_here_before__) { + this.emitScalar(''); + } else if (jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_been_here_before__ = true; + if (jasmine.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } +}; + +jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (!obj.hasOwnProperty(property)) continue; + if (property == '__Jasmine_been_here_before__') continue; + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && + obj.__lookupGetter__(property) !== null) : false); + } +}; + +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; + +jasmine.StringPrettyPrinter = function() { + jasmine.PrettyPrinter.call(this); + + this.string = ''; +}; +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); + +jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); +}; + +jasmine.StringPrettyPrinter.prototype.emitString = function(value) { + this.append("'" + value + "'"); +}; + +jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { + if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { + this.append("Array"); + return; + } + + this.append('[ '); + for (var i = 0; i < array.length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + this.append(' ]'); +}; + +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { + this.append("Object"); + return; + } + + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(' : '); + if (isGetter) { + self.append(''); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); +}; + +jasmine.StringPrettyPrinter.prototype.append = function(value) { + this.string += value; +}; +jasmine.Queue = function(env) { + this.env = env; + + // parallel to blocks. each true value in this array means the block will + // get executed even if we abort + this.ensured = []; + this.blocks = []; + this.running = false; + this.index = 0; + this.offset = 0; + this.abort = false; +}; + +jasmine.Queue.prototype.addBefore = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.blocks.unshift(block); + this.ensured.unshift(ensure); +}; + +jasmine.Queue.prototype.add = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.blocks.push(block); + this.ensured.push(ensure); +}; + +jasmine.Queue.prototype.insertNext = function(block, ensure) { + if (ensure === jasmine.undefined) { + ensure = false; + } + + this.ensured.splice((this.index + this.offset + 1), 0, ensure); + this.blocks.splice((this.index + this.offset + 1), 0, block); + this.offset++; +}; + +jasmine.Queue.prototype.start = function(onComplete) { + this.running = true; + this.onComplete = onComplete; + this.next_(); +}; + +jasmine.Queue.prototype.isRunning = function() { + return this.running; +}; + +jasmine.Queue.LOOP_DONT_RECURSE = true; + +jasmine.Queue.prototype.next_ = function() { + var self = this; + var goAgain = true; + + while (goAgain) { + goAgain = false; + + if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) { + var calledSynchronously = true; + var completedSynchronously = false; + + var onComplete = function () { + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { + completedSynchronously = true; + return; + } + + if (self.blocks[self.index].abort) { + self.abort = true; + } + + self.offset = 0; + self.index++; + + var now = new Date().getTime(); + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { + self.env.lastUpdate = now; + self.env.setTimeout(function() { + self.next_(); + }, 0); + } else { + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { + goAgain = true; + } else { + self.next_(); + } + } + }; + self.blocks[self.index].execute(onComplete); + + calledSynchronously = false; + if (completedSynchronously) { + onComplete(); + } + + } else { + self.running = false; + if (self.onComplete) { + self.onComplete(); + } + } + } +}; + +jasmine.Queue.prototype.results = function() { + var results = new jasmine.NestedResults(); + for (var i = 0; i < this.blocks.length; i++) { + if (this.blocks[i].results) { + results.addResult(this.blocks[i].results()); + } + } + return results; +}; + + +/** + * Runner + * + * @constructor + * @param {jasmine.Env} env + */ +jasmine.Runner = function(env) { + var self = this; + self.env = env; + self.queue = new jasmine.Queue(env); + self.before_ = []; + self.after_ = []; + self.suites_ = []; +}; + +jasmine.Runner.prototype.execute = function() { + var self = this; + if (self.env.reporter.reportRunnerStarting) { + self.env.reporter.reportRunnerStarting(this); + } + self.queue.start(function () { + self.finishCallback(); + }); +}; + +jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.splice(0,0,beforeEachFunction); +}; + +jasmine.Runner.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.splice(0,0,afterEachFunction); +}; + + +jasmine.Runner.prototype.finishCallback = function() { + this.env.reporter.reportRunnerResults(this); +}; + +jasmine.Runner.prototype.addSuite = function(suite) { + this.suites_.push(suite); +}; + +jasmine.Runner.prototype.add = function(block) { + if (block instanceof jasmine.Suite) { + this.addSuite(block); + } + this.queue.add(block); +}; + +jasmine.Runner.prototype.specs = function () { + var suites = this.suites(); + var specs = []; + for (var i = 0; i < suites.length; i++) { + specs = specs.concat(suites[i].specs()); + } + return specs; +}; + +jasmine.Runner.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Runner.prototype.topLevelSuites = function() { + var topLevelSuites = []; + for (var i = 0; i < this.suites_.length; i++) { + if (!this.suites_[i].parentSuite) { + topLevelSuites.push(this.suites_[i]); + } + } + return topLevelSuites; +}; + +jasmine.Runner.prototype.results = function() { + return this.queue.results(); +}; +/** + * Internal representation of a Jasmine specification, or test. + * + * @constructor + * @param {jasmine.Env} env + * @param {jasmine.Suite} suite + * @param {String} description + */ +jasmine.Spec = function(env, suite, description) { + if (!env) { + throw new Error('jasmine.Env() required'); + } + if (!suite) { + throw new Error('jasmine.Suite() required'); + } + var spec = this; + spec.id = env.nextSpecId ? env.nextSpecId() : null; + spec.env = env; + spec.suite = suite; + spec.description = description; + spec.queue = new jasmine.Queue(env); + + spec.afterCallbacks = []; + spec.spies_ = []; + + spec.results_ = new jasmine.NestedResults(); + spec.results_.description = description; + spec.matchersClass = null; +}; + +jasmine.Spec.prototype.getFullName = function() { + return this.suite.getFullName() + ' ' + this.description + '.'; +}; + + +jasmine.Spec.prototype.results = function() { + return this.results_; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.Spec.prototype.log = function() { + return this.results_.log(arguments); +}; + +jasmine.Spec.prototype.runs = function (func) { + var block = new jasmine.Block(this.env, func, this); + this.addToQueue(block); + return this; +}; + +jasmine.Spec.prototype.addToQueue = function (block) { + if (this.queue.isRunning()) { + this.queue.insertNext(block); + } else { + this.queue.add(block); + } +}; + +/** + * @param {jasmine.ExpectationResult} result + */ +jasmine.Spec.prototype.addMatcherResult = function(result) { + this.results_.addResult(result); +}; + +jasmine.Spec.prototype.expect = function(actual) { + var positive = new (this.getMatchersClass_())(this.env, actual, this); + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); + return positive; +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +jasmine.Spec.prototype.waits = function(timeout) { + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); + this.addToQueue(waitsFunc); + return this; +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + var latchFunction_ = null; + var optional_timeoutMessage_ = null; + var optional_timeout_ = null; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + switch (typeof arg) { + case 'function': + latchFunction_ = arg; + break; + case 'string': + optional_timeoutMessage_ = arg; + break; + case 'number': + optional_timeout_ = arg; + break; + } + } + + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); + this.addToQueue(waitsForFunc); + return this; +}; + +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception', + trace: { stack: e.stack } + }); + this.results_.addResult(expectationResult); +}; + +jasmine.Spec.prototype.getMatchersClass_ = function() { + return this.matchersClass || this.env.matchersClass; +}; + +jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { + var parent = this.getMatchersClass_(); + var newMatchersClass = function() { + parent.apply(this, arguments); + }; + jasmine.util.inherit(newMatchersClass, parent); + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); + this.matchersClass = newMatchersClass; +}; + +jasmine.Spec.prototype.finishCallback = function() { + this.env.reporter.reportSpecResults(this); +}; + +jasmine.Spec.prototype.finish = function(onComplete) { + this.removeAllSpies(); + this.finishCallback(); + if (onComplete) { + onComplete(); + } +}; + +jasmine.Spec.prototype.after = function(doAfter) { + if (this.queue.isRunning()) { + this.queue.add(new jasmine.Block(this.env, doAfter, this), true); + } else { + this.afterCallbacks.unshift(doAfter); + } +}; + +jasmine.Spec.prototype.execute = function(onComplete) { + var spec = this; + if (!spec.env.specFilter(spec)) { + spec.results_.skipped = true; + spec.finish(onComplete); + return; + } + + this.env.reporter.reportSpecStarting(this); + + spec.env.currentSpec = spec; + + spec.addBeforesAndAftersToQueue(); + + spec.queue.start(function () { + spec.finish(onComplete); + }); +}; + +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { + var runner = this.env.currentRunner(); + var i; + + for (var suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); + } + } + for (i = 0; i < runner.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); + } + for (i = 0; i < this.afterCallbacks.length; i++) { + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this), true); + } + for (suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this), true); + } + } + for (i = 0; i < runner.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this), true); + } +}; + +jasmine.Spec.prototype.explodes = function() { + throw 'explodes function should not have been called'; +}; + +jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { + if (obj == jasmine.undefined) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { + throw methodName + '() method does not exist'; + } + + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = jasmine.createSpy(methodName); + + this.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +jasmine.Spec.prototype.removeAllSpies = function() { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +/** + * Internal representation of a Jasmine suite. + * + * @constructor + * @param {jasmine.Env} env + * @param {String} description + * @param {Function} specDefinitions + * @param {jasmine.Suite} parentSuite + */ +jasmine.Suite = function(env, description, specDefinitions, parentSuite) { + var self = this; + self.id = env.nextSuiteId ? env.nextSuiteId() : null; + self.description = description; + self.queue = new jasmine.Queue(env); + self.parentSuite = parentSuite; + self.env = env; + self.before_ = []; + self.after_ = []; + self.children_ = []; + self.suites_ = []; + self.specs_ = []; +}; + +jasmine.Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + return fullName; +}; + +jasmine.Suite.prototype.finish = function(onComplete) { + this.env.reporter.reportSuiteResults(this); + this.finished = true; + if (typeof(onComplete) == 'function') { + onComplete(); + } +}; + +jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.unshift(beforeEachFunction); +}; + +jasmine.Suite.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.unshift(afterEachFunction); +}; + +jasmine.Suite.prototype.results = function() { + return this.queue.results(); +}; + +jasmine.Suite.prototype.add = function(suiteOrSpec) { + this.children_.push(suiteOrSpec); + if (suiteOrSpec instanceof jasmine.Suite) { + this.suites_.push(suiteOrSpec); + this.env.currentRunner().addSuite(suiteOrSpec); + } else { + this.specs_.push(suiteOrSpec); + } + this.queue.add(suiteOrSpec); +}; + +jasmine.Suite.prototype.specs = function() { + return this.specs_; +}; + +jasmine.Suite.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Suite.prototype.children = function() { + return this.children_; +}; + +jasmine.Suite.prototype.execute = function(onComplete) { + var self = this; + this.queue.start(function () { + self.finish(onComplete); + }); +}; +jasmine.WaitsBlock = function(env, timeout, spec) { + this.timeout = timeout; + jasmine.Block.call(this, env, null, spec); +}; + +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); + +jasmine.WaitsBlock.prototype.execute = function (onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); + } + this.env.setTimeout(function () { + onComplete(); + }, this.timeout); +}; +/** + * A block which waits for some condition to become true, with timeout. + * + * @constructor + * @extends jasmine.Block + * @param {jasmine.Env} env The Jasmine environment. + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. + * @param {Function} latchFunction A function which returns true when the desired condition has been met. + * @param {String} message The message to display if the desired condition hasn't been met within the given time period. + * @param {jasmine.Spec} spec The Jasmine spec. + */ +jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { + this.timeout = timeout || env.defaultTimeoutInterval; + this.latchFunction = latchFunction; + this.message = message; + this.totalTimeSpentWaitingForLatch = 0; + jasmine.Block.call(this, env, null, spec); +}; +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); + +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; + +jasmine.WaitsForBlock.prototype.execute = function(onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); + } + var latchFunctionResult; + try { + latchFunctionResult = this.latchFunction.apply(this.spec); + } catch (e) { + this.spec.fail(e); + onComplete(); + return; + } + + if (latchFunctionResult) { + onComplete(); + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); + this.spec.fail({ + name: 'timeout', + message: message + }); + + this.abort = true; + onComplete(); + } else { + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; + var self = this; + this.env.setTimeout(function() { + self.execute(onComplete); + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); + } +}; + +jasmine.version_= { + "major": 1, + "minor": 3, + "build": 1, + "revision": 1354556913 +}; diff --git a/src/AttributeRouting.Tests/Integration/js/lib/jquery-1.9.1.min.js b/src/AttributeRouting.Tests/Integration/js/lib/jquery-1.9.1.min.js new file mode 100644 index 0000000..39aae4e --- /dev/null +++ b/src/AttributeRouting.Tests/Integration/js/lib/jquery-1.9.1.min.js @@ -0,0 +1,5 @@ +/*! jQuery v1.9.1 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license +//@ sourceMappingURL=jquery.min.map +*/(function(e,t){var n,r,i=typeof t,o=e.document,a=e.location,s=e.jQuery,u=e.$,l={},c=[],p="1.9.1",f=c.concat,d=c.push,h=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,b=function(e,t){return new b.fn.init(e,t,r)},x=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^[\],:{}\s]*$/,E=/(?:^|:|,)(?:\s*\[)+/g,S=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,A=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,j=/^-ms-/,D=/-([\da-z])/gi,L=function(e,t){return t.toUpperCase()},H=function(e){(o.addEventListener||"load"===e.type||"complete"===o.readyState)&&(q(),b.ready())},q=function(){o.addEventListener?(o.removeEventListener("DOMContentLoaded",H,!1),e.removeEventListener("load",H,!1)):(o.detachEvent("onreadystatechange",H),e.detachEvent("onload",H))};b.fn=b.prototype={jquery:p,constructor:b,init:function(e,n,r){var i,a;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof b?n[0]:n,b.merge(this,b.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:o,!0)),C.test(i[1])&&b.isPlainObject(n))for(i in n)b.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(a=o.getElementById(i[2]),a&&a.parentNode){if(a.id!==i[2])return r.find(e);this.length=1,this[0]=a}return this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):b.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),b.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return h.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=b.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return b.each(this,e,t)},ready:function(e){return b.ready.promise().done(e),this},slice:function(){return this.pushStack(h.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(b.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:d,sort:[].sort,splice:[].splice},b.fn.init.prototype=b.fn,b.extend=b.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||b.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(o=arguments[u]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(b.isPlainObject(r)||(n=b.isArray(r)))?(n?(n=!1,a=e&&b.isArray(e)?e:[]):a=e&&b.isPlainObject(e)?e:{},s[i]=b.extend(c,a,r)):r!==t&&(s[i]=r));return s},b.extend({noConflict:function(t){return e.$===b&&(e.$=u),t&&e.jQuery===b&&(e.jQuery=s),b},isReady:!1,readyWait:1,holdReady:function(e){e?b.readyWait++:b.ready(!0)},ready:function(e){if(e===!0?!--b.readyWait:!b.isReady){if(!o.body)return setTimeout(b.ready);b.isReady=!0,e!==!0&&--b.readyWait>0||(n.resolveWith(o,[b]),b.fn.trigger&&b(o).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===b.type(e)},isArray:Array.isArray||function(e){return"array"===b.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==b.type(e)||e.nodeType||b.isWindow(e))return!1;try{if(e.constructor&&!y.call(e,"constructor")&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||y.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=b.buildFragment([e],t,i),i&&b(i).remove(),b.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=b.trim(n),n&&k.test(n.replace(S,"@").replace(A,"]").replace(E,"")))?Function("return "+n)():(b.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||b.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&b.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(j,"ms-").replace(D,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:v&&!v.call("\ufeff\u00a0")?function(e){return null==e?"":v.call(e)}:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?b.merge(n,"string"==typeof e?[e]:e):d.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(g)return g.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return f.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),b.isFunction(e)?(r=h.call(arguments,2),i=function(){return e.apply(n||this,r.concat(h.call(arguments)))},i.guid=e.guid=e.guid||b.guid++,i):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===b.type(r)){o=!0;for(u in r)b.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,b.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(b(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),b.ready.promise=function(t){if(!n)if(n=b.Deferred(),"complete"===o.readyState)setTimeout(b.ready);else if(o.addEventListener)o.addEventListener("DOMContentLoaded",H,!1),e.addEventListener("load",H,!1);else{o.attachEvent("onreadystatechange",H),e.attachEvent("onload",H);var r=!1;try{r=null==e.frameElement&&o.documentElement}catch(i){}r&&r.doScroll&&function a(){if(!b.isReady){try{r.doScroll("left")}catch(e){return setTimeout(a,50)}q(),b.ready()}}()}return n.promise(t)},b.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=b.type(e);return b.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=b(o);var _={};function F(e){var t=_[e]={};return b.each(e.match(w)||[],function(e,n){t[n]=!0}),t}b.Callbacks=function(e){e="string"==typeof e?_[e]||F(e):b.extend({},e);var n,r,i,o,a,s,u=[],l=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=u.length,n=!0;u&&o>a;a++)if(u[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,u&&(l?l.length&&c(l.shift()):r?u=[]:p.disable())},p={add:function(){if(u){var t=u.length;(function i(t){b.each(t,function(t,n){var r=b.type(n);"function"===r?e.unique&&p.has(n)||u.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=u.length:r&&(s=t,c(r))}return this},remove:function(){return u&&b.each(arguments,function(e,t){var r;while((r=b.inArray(t,u,r))>-1)u.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?b.inArray(e,u)>-1:!(!u||!u.length)},empty:function(){return u=[],this},disable:function(){return u=l=r=t,this},disabled:function(){return!u},lock:function(){return l=t,r||p.disable(),this},locked:function(){return!l},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!u||i&&!l||(n?l.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},b.extend({Deferred:function(e){var t=[["resolve","done",b.Callbacks("once memory"),"resolved"],["reject","fail",b.Callbacks("once memory"),"rejected"],["notify","progress",b.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return b.Deferred(function(n){b.each(t,function(t,o){var a=o[0],s=b.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&b.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?b.extend(e,r):r}},i={};return r.pipe=r.then,b.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=h.call(arguments),r=n.length,i=1!==r||e&&b.isFunction(e.promise)?r:0,o=1===i?e:b.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?h.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,u,l;if(r>1)for(s=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&b.isFunction(n[t].promise)?n[t].promise().done(a(t,l,n)).fail(o.reject).progress(a(t,u,s)):--i;return i||o.resolveWith(l,n),o.promise()}}),b.support=function(){var t,n,r,a,s,u,l,c,p,f,d=o.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
a",n=d.getElementsByTagName("*"),r=d.getElementsByTagName("a")[0],!n||!r||!n.length)return{};s=o.createElement("select"),l=s.appendChild(o.createElement("option")),a=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={getSetAttribute:"t"!==d.className,leadingWhitespace:3===d.firstChild.nodeType,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:"/a"===r.getAttribute("href"),opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:!!a.value,optSelected:l.selected,enctype:!!o.createElement("form").enctype,html5Clone:"<:nav>"!==o.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===o.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},a.checked=!0,t.noCloneChecked=a.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!l.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}a=o.createElement("input"),a.setAttribute("value",""),t.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),t.radioValue="t"===a.value,a.setAttribute("checked","t"),a.setAttribute("name","t"),u=o.createDocumentFragment(),u.appendChild(a),t.appendChecked=a.checked,t.checkClone=u.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;return d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip,b(function(){var n,r,a,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",u=o.getElementsByTagName("body")[0];u&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",u.appendChild(n).appendChild(d),d.innerHTML="
t
",a=d.getElementsByTagName("td"),a[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===a[0].offsetHeight,a[0].style.display="",a[1].style.display="none",t.reliableHiddenOffsets=p&&0===a[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=4===d.offsetWidth,t.doesNotIncludeMarginInBodyOffset=1!==u.offsetTop,e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(o.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(u.style.zoom=1)),u.removeChild(n),n=d=a=r=null)}),n=s=u=l=r=a=null,t}();var O=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,B=/([A-Z])/g;function P(e,n,r,i){if(b.acceptData(e)){var o,a,s=b.expando,u="string"==typeof n,l=e.nodeType,p=l?b.cache:e,f=l?e[s]:e[s]&&s;if(f&&p[f]&&(i||p[f].data)||!u||r!==t)return f||(l?e[s]=f=c.pop()||b.guid++:f=s),p[f]||(p[f]={},l||(p[f].toJSON=b.noop)),("object"==typeof n||"function"==typeof n)&&(i?p[f]=b.extend(p[f],n):p[f].data=b.extend(p[f].data,n)),o=p[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[b.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[b.camelCase(n)])):a=o,a}}function R(e,t,n){if(b.acceptData(e)){var r,i,o,a=e.nodeType,s=a?b.cache:e,u=a?e[b.expando]:b.expando;if(s[u]){if(t&&(o=n?s[u]:s[u].data)){b.isArray(t)?t=t.concat(b.map(t,b.camelCase)):t in o?t=[t]:(t=b.camelCase(t),t=t in o?[t]:t.split(" "));for(r=0,i=t.length;i>r;r++)delete o[t[r]];if(!(n?$:b.isEmptyObject)(o))return}(n||(delete s[u].data,$(s[u])))&&(a?b.cleanData([e],!0):b.support.deleteExpando||s!=s.window?delete s[u]:s[u]=null)}}}b.extend({cache:{},expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?b.cache[e[b.expando]]:e[b.expando],!!e&&!$(e)},data:function(e,t,n){return P(e,t,n)},removeData:function(e,t){return R(e,t)},_data:function(e,t,n){return P(e,t,n,!0)},_removeData:function(e,t){return R(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&b.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),b.fn.extend({data:function(e,n){var r,i,o=this[0],a=0,s=null;if(e===t){if(this.length&&(s=b.data(o),1===o.nodeType&&!b._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>a;a++)i=r[a].name,i.indexOf("data-")||(i=b.camelCase(i.slice(5)),W(o,i,s[i]));b._data(o,"parsedAttrs",!0)}return s}return"object"==typeof e?this.each(function(){b.data(this,e)}):b.access(this,function(n){return n===t?o?W(o,e,b.data(o,e)):null:(this.each(function(){b.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){b.removeData(this,e)})}});function W(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(B,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:O.test(r)?b.parseJSON(r):r}catch(o){}b.data(e,n,r)}else r=t}return r}function $(e){var t;for(t in e)if(("data"!==t||!b.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}b.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=b._data(e,n),r&&(!i||b.isArray(r)?i=b._data(e,n,b.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=b.queue(e,t),r=n.length,i=n.shift(),o=b._queueHooks(e,t),a=function(){b.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return b._data(e,n)||b._data(e,n,{empty:b.Callbacks("once memory").add(function(){b._removeData(e,t+"queue"),b._removeData(e,n)})})}}),b.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?b.queue(this[0],e):n===t?this:this.each(function(){var t=b.queue(this,e,n);b._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&b.dequeue(this,e)})},dequeue:function(e){return this.each(function(){b.dequeue(this,e)})},delay:function(e,t){return e=b.fx?b.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=b.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=b._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var I,z,X=/[\t\r\n]/g,U=/\r/g,V=/^(?:input|select|textarea|button|object)$/i,Y=/^(?:a|area)$/i,J=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,G=/^(?:checked|selected)$/i,Q=b.support.getSetAttribute,K=b.support.input;b.fn.extend({attr:function(e,t){return b.access(this,b.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e)})},prop:function(e,t){return b.access(this,b.prop,e,t,arguments.length>1)},removeProp:function(e){return e=b.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,u="string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=b.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?b.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return b.isFunction(e)?this.each(function(n){b(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=b(this),u=t,l=e.match(w)||[];while(o=l[a++])u=r?u:!s.hasClass(o),s[u?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&b._data(this,"__className__",this.className),this.className=this.className||e===!1?"":b._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(X," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=b.isFunction(e),this.each(function(n){var o,a=b(this);1===this.nodeType&&(o=i?e.call(this,n,a.val()):e,null==o?o="":"number"==typeof o?o+="":b.isArray(o)&&(o=b.map(o,function(e){return null==e?"":e+""})),r=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=b.valHooks[o.type]||b.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(U,""):null==n?"":n)}}}),b.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,u=0>i?s:o?i:0;for(;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(b.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&b.nodeName(n.parentNode,"optgroup"))){if(t=b(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=b.makeArray(t);return b(e).find("option").each(function(){this.selected=b.inArray(b(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var o,a,s,u=e.nodeType;if(e&&3!==u&&8!==u&&2!==u)return typeof e.getAttribute===i?b.prop(e,n,r):(a=1!==u||!b.isXMLDoc(e),a&&(n=n.toLowerCase(),o=b.attrHooks[n]||(J.test(n)?z:I)),r===t?o&&a&&"get"in o&&null!==(s=o.get(e,n))?s:(typeof e.getAttribute!==i&&(s=e.getAttribute(n)),null==s?t:s):null!==r?o&&a&&"set"in o&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r):(b.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=b.propFix[n]||n,J.test(n)?!Q&&G.test(n)?e[b.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:b.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!b.support.radioValue&&"radio"===t&&b.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!b.isXMLDoc(e),a&&(n=b.propFix[n]||n,o=b.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):V.test(e.nodeName)||Y.test(e.nodeName)&&e.href?0:t}}}}),z={get:function(e,n){var r=b.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?K&&Q?null!=i:G.test(n)?e[b.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?b.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&b.propFix[n]||n,n):e[b.camelCase("default-"+n)]=e[n]=!0,n}},K&&Q||(b.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return b.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t},set:function(e,n,r){return b.nodeName(e,"input")?(e.defaultValue=n,t):I&&I.set(e,n,r)}}),Q||(I=b.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},b.attrHooks.contenteditable={get:I.get,set:function(e,t,n){I.set(e,""===t?!1:t,n)}},b.each(["width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),b.support.hrefNormalized||(b.each(["href","src","width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),b.each(["href","src"],function(e,t){b.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),b.support.style||(b.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),b.support.optSelected||(b.propHooks.selected=b.extend(b.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),b.support.enctype||(b.propFix.enctype="encoding"),b.support.checkOn||b.each(["radio","checkbox"],function(){b.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),b.each(["radio","checkbox"],function(){b.valHooks[this]=b.extend(b.valHooks[this],{set:function(e,n){return b.isArray(n)?e.checked=b.inArray(b(e).val(),n)>=0:t}})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}b.event={global:{},add:function(e,n,r,o,a){var s,u,l,c,p,f,d,h,g,m,y,v=b._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=b.guid++),(u=v.events)||(u=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof b===i||e&&b.event.triggered===e.type?t:b.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(w)||[""],l=n.length;while(l--)s=rt.exec(n[l])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),p=b.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=b.event.special[g]||{},d=b.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&b.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=u[g])||(h=u[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),b.event.global[g]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,p,f,d,h,g,m=b.hasData(e)&&b._data(e);if(m&&(c=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(s=rt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=b.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),u=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));u&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||b.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)b.event.remove(e,d+t[l],n,r,!0);b.isEmptyObject(c)&&(delete m.handle,b._removeData(e,"events"))}},trigger:function(n,r,i,a){var s,u,l,c,p,f,d,h=[i||o],g=y.call(n,"type")?n.type:n,m=y.call(n,"namespace")?n.namespace.split("."):[];if(l=f=i=i||o,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+b.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),u=0>g.indexOf(":")&&"on"+g,n=n[b.expando]?n:new b.Event(g,"object"==typeof n&&n),n.isTrigger=!0,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:b.makeArray(r,[n]),p=b.event.special[g]||{},a||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!a&&!p.noBubble&&!b.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(l=l.parentNode);l;l=l.parentNode)h.push(l),f=l;f===(i.ownerDocument||o)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((l=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(b._data(l,"events")||{})[n.type]&&b._data(l,"handle"),s&&s.apply(l,r),s=u&&l[u],s&&b.acceptData(l)&&s.apply&&s.apply(l,r)===!1&&n.preventDefault();if(n.type=g,!(a||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===g&&b.nodeName(i,"a")||!b.acceptData(i)||!u||!i[g]||b.isWindow(i))){f=i[u],f&&(i[u]=null),b.event.triggered=g;try{i[g]()}catch(v){}b.event.triggered=t,f&&(i[u]=f)}return n.result}},dispatch:function(e){e=b.event.fix(e);var n,r,i,o,a,s=[],u=h.call(arguments),l=(b._data(this,"events")||{})[e.type]||[],c=b.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=b.event.handlers.call(this,e,l),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((b.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,u),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==e.type)){for(o=[],a=0;u>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?b(r,this).index(l)>=0:b.find(r,this,null,[l]).length),o[r]&&o.push(i);o.length&&s.push({elem:l,handlers:o})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[b.expando])return e;var t,n,r,i=e.type,a=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new b.Event(a),t=r.length;while(t--)n=r[t],e[n]=a[n];return e.target||(e.target=a.srcElement||o),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,a):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,a,s=n.button,u=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||o,a=i.documentElement,r=i.body,e.pageX=n.clientX+(a&&a.scrollLeft||r&&r.scrollLeft||0)-(a&&a.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(a&&a.scrollTop||r&&r.scrollTop||0)-(a&&a.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&u&&(e.relatedTarget=u===e.target?n.toElement:u),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return b.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==o.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===o.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=b.extend(new b.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?b.event.trigger(i,null,t):b.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},b.removeEvent=o.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},b.Event=function(e,n){return this instanceof b.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&b.extend(this,n),this.timeStamp=e&&e.timeStamp||b.now(),this[b.expando]=!0,t):new b.Event(e,n)},b.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},b.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){b.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj; +return(!i||i!==r&&!b.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),b.support.submitBubbles||(b.event.special.submit={setup:function(){return b.nodeName(this,"form")?!1:(b.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=b.nodeName(n,"input")||b.nodeName(n,"button")?n.form:t;r&&!b._data(r,"submitBubbles")&&(b.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),b._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&b.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return b.nodeName(this,"form")?!1:(b.event.remove(this,"._submit"),t)}}),b.support.changeBubbles||(b.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(b.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),b.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),b.event.simulate("change",this,e,!0)})),!1):(b.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!b._data(t,"changeBubbles")&&(b.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||b.event.simulate("change",this.parentNode,e,!0)}),b._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return b.event.remove(this,"._change"),!Z.test(this.nodeName)}}),b.support.focusinBubbles||b.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){b.event.simulate(t,e.target,b.event.fix(e),!0)};b.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),b.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return b().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=b.guid++)),this.each(function(){b.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,b(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){b.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){b.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?b.event.trigger(e,n,r,!0):t}}),function(e,t){var n,r,i,o,a,s,u,l,c,p,f,d,h,g,m,y,v,x="sizzle"+-new Date,w=e.document,T={},N=0,C=0,k=it(),E=it(),S=it(),A=typeof t,j=1<<31,D=[],L=D.pop,H=D.push,q=D.slice,M=D.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},_="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=F.replace("w","w#"),B="([*^$|!~]?=)",P="\\["+_+"*("+F+")"+_+"*(?:"+B+_+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+O+")|)|)"+_+"*\\]",R=":("+F+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+P.replace(3,8)+")*)|.*)\\)|)",W=RegExp("^"+_+"+|((?:^|[^\\\\])(?:\\\\.)*)"+_+"+$","g"),$=RegExp("^"+_+"*,"+_+"*"),I=RegExp("^"+_+"*([\\x20\\t\\r\\n\\f>+~])"+_+"*"),z=RegExp(R),X=RegExp("^"+O+"$"),U={ID:RegExp("^#("+F+")"),CLASS:RegExp("^\\.("+F+")"),NAME:RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:RegExp("^("+F.replace("w","w*")+")"),ATTR:RegExp("^"+P),PSEUDO:RegExp("^"+R),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+_+"*(even|odd|(([+-]|)(\\d*)n|)"+_+"*(?:([+-]|)"+_+"*(\\d+)|))"+_+"*\\)|)","i"),needsContext:RegExp("^"+_+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+_+"*((?:-\\d)?\\d*)"+_+"*\\)|)(?=[^-]|$)","i")},V=/[\x20\t\r\n\f]*[+~]/,Y=/^[^{]+\{\s*\[native code/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,G=/^(?:input|select|textarea|button)$/i,Q=/^h\d$/i,K=/'|\\/g,Z=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,et=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{q.call(w.documentElement.childNodes,0)[0].nodeType}catch(nt){q=function(e){var t,n=[];while(t=this[e++])n.push(t);return n}}function rt(e){return Y.test(e+"")}function it(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>i.cacheLength&&delete e[t.shift()],e[n]=r}}function ot(e){return e[x]=!0,e}function at(e){var t=p.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function st(e,t,n,r){var i,o,a,s,u,l,f,g,m,v;if((t?t.ownerDocument||t:w)!==p&&c(t),t=t||p,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!d&&!r){if(i=J.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&y(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return H.apply(n,q.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&T.getByClassName&&t.getElementsByClassName)return H.apply(n,q.call(t.getElementsByClassName(a),0)),n}if(T.qsa&&!h.test(e)){if(f=!0,g=x,m=t,v=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){l=ft(e),(f=t.getAttribute("id"))?g=f.replace(K,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=l.length;while(u--)l[u]=g+dt(l[u]);m=V.test(e)&&t.parentNode||t,v=l.join(",")}if(v)try{return H.apply(n,q.call(m.querySelectorAll(v),0)),n}catch(b){}finally{f||t.removeAttribute("id")}}}return wt(e.replace(W,"$1"),t,n,r)}a=st.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},c=st.setDocument=function(e){var n=e?e.ownerDocument||e:w;return n!==p&&9===n.nodeType&&n.documentElement?(p=n,f=n.documentElement,d=a(n),T.tagNameNoComments=at(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),T.attributes=at(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),T.getByClassName=at(function(e){return e.innerHTML="",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),T.getByName=at(function(e){e.id=x+0,e.innerHTML="
",f.insertBefore(e,f.firstChild);var t=n.getElementsByName&&n.getElementsByName(x).length===2+n.getElementsByName(x+0).length;return T.getIdNotName=!n.getElementById(x),f.removeChild(e),t}),i.attrHandle=at(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==A&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},T.getIdNotName?(i.find.ID=function(e,t){if(typeof t.getElementById!==A&&!d){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){return e.getAttribute("id")===t}}):(i.find.ID=function(e,n){if(typeof n.getElementById!==A&&!d){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==A&&r.getAttributeNode("id").value===e?[r]:t:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){var n=typeof e.getAttributeNode!==A&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=T.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==A?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.NAME=T.getByName&&function(e,n){return typeof n.getElementsByName!==A?n.getElementsByName(name):t},i.find.CLASS=T.getByClassName&&function(e,n){return typeof n.getElementsByClassName===A||d?t:n.getElementsByClassName(e)},g=[],h=[":focus"],(T.qsa=rt(n.querySelectorAll))&&(at(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||h.push("\\["+_+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){e.innerHTML="",e.querySelectorAll("[i^='']").length&&h.push("[*^$]="+_+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(T.matchesSelector=rt(m=f.matchesSelector||f.mozMatchesSelector||f.webkitMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){T.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",R)}),h=RegExp(h.join("|")),g=RegExp(g.join("|")),y=rt(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},v=f.compareDocumentPosition?function(e,t){var r;return e===t?(u=!0,0):(r=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&r||e.parentNode&&11===e.parentNode.nodeType?e===n||y(w,e)?-1:t===n||y(w,t)?1:0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return u=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:0;if(o===a)return ut(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?ut(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},u=!1,[0,0].sort(v),T.detectDuplicates=u,p):p},st.matches=function(e,t){return st(e,null,null,t)},st.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Z,"='$1']"),!(!T.matchesSelector||d||g&&g.test(t)||h.test(t)))try{var n=m.call(e,t);if(n||T.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return st(t,p,null,[e]).length>0},st.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},st.attr=function(e,t){var n;return(e.ownerDocument||e)!==p&&c(e),d||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):d||T.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},st.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},st.uniqueSort=function(e){var t,n=[],r=1,i=0;if(u=!T.detectDuplicates,e.sort(v),u){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e};function ut(e,t){var n=t&&e,r=n&&(~t.sourceIndex||j)-(~e.sourceIndex||j);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function lt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ct(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pt(e){return ot(function(t){return t=+t,ot(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}o=st.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=st.selectors={cacheLength:50,createPseudo:ot,match:U,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(et,tt),e[3]=(e[4]||e[5]||"").replace(et,tt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||st.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&st.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return U.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&z.test(n)&&(t=ft(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(et,tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[e+" "];return t||(t=RegExp("(^|"+_+")"+e+"("+_+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==A&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=st.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[x]||(m[x]={}),l=c[e]||[],d=l[0]===N&&l[1],f=l[0]===N&&l[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[N,d,f];break}}else if(v&&(l=(t[x]||(t[x]={}))[e])&&l[0]===N)f=l[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[x]||(p[x]={}))[e]=[N,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||st.error("unsupported pseudo: "+e);return r[x]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?ot(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=M.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ot(function(e){var t=[],n=[],r=s(e.replace(W,"$1"));return r[x]?ot(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ot(function(e){return function(t){return st(e,t).length>0}}),contains:ot(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:ot(function(e){return X.test(e||"")||st.error("unsupported lang: "+e),e=e.replace(et,tt).toLowerCase(),function(t){var n;do if(n=d?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.lang)return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return Q.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:pt(function(){return[0]}),last:pt(function(e,t){return[t-1]}),eq:pt(function(e,t,n){return[0>n?n+t:n]}),even:pt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:pt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:pt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:pt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[n]=lt(n);for(n in{submit:!0,reset:!0})i.pseudos[n]=ct(n);function ft(e,t){var n,r,o,a,s,u,l,c=E[e+" "];if(c)return t?0:c.slice(0);s=e,u=[],l=i.preFilter;while(s){(!n||(r=$.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(o=[])),n=!1,(r=I.exec(s))&&(n=r.shift(),o.push({value:n,type:r[0].replace(W," ")}),s=s.slice(n.length));for(a in i.filter)!(r=U[a].exec(s))||l[a]&&!(r=l[a](r))||(n=r.shift(),o.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?st.error(e):E(e,u).slice(0)}function dt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function ht(e,t,n){var i=t.dir,o=n&&"parentNode"===i,a=C++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,s){var u,l,c,p=N+" "+a;if(s){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[x]||(t[x]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,s)||r,l[1]===!0)return!0}}function gt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function mt(e,t,n,r,i){var o,a=[],s=0,u=e.length,l=null!=t;for(;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function yt(e,t,n,r,i,o){return r&&!r[x]&&(r=yt(r)),i&&!i[x]&&(i=yt(i,o)),ot(function(o,a,s,u){var l,c,p,f=[],d=[],h=a.length,g=o||xt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:mt(g,f,e,s,u),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,u),r){l=mt(y,d),r(l,[],s,u),c=l.length;while(c--)(p=l[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?M.call(o,p):f[c])>-1&&(o[l]=!(a[l]=p))}}else y=mt(y===a?y.splice(h,y.length):y),i?i(null,a,y,u):H.apply(a,y)})}function vt(e){var t,n,r,o=e.length,a=i.relative[e[0].type],s=a||i.relative[" "],u=a?1:0,c=ht(function(e){return e===t},s,!0),p=ht(function(e){return M.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>u;u++)if(n=i.relative[e[u].type])f=[ht(gt(f),n)];else{if(n=i.filter[e[u].type].apply(null,e[u].matches),n[x]){for(r=++u;o>r;r++)if(i.relative[e[r].type])break;return yt(u>1&>(f),u>1&&dt(e.slice(0,u-1)).replace(W,"$1"),n,r>u&&vt(e.slice(u,r)),o>r&&vt(e=e.slice(r)),o>r&&dt(e))}f.push(n)}return gt(f)}function bt(e,t){var n=0,o=t.length>0,a=e.length>0,s=function(s,u,c,f,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,T=l,C=s||a&&i.find.TAG("*",d&&u.parentNode||u),k=N+=null==T?1:Math.random()||.1;for(w&&(l=u!==p&&u,r=n);null!=(h=C[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,u,c)){f.push(h);break}w&&(N=k,r=++n)}o&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,o&&b!==v){g=0;while(m=t[g++])m(x,y,u,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=L.call(f));y=mt(y)}H.apply(f,y),w&&!s&&y.length>0&&v+t.length>1&&st.uniqueSort(f)}return w&&(N=k,l=T),x};return o?ot(s):s}s=st.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=ft(e)),n=t.length;while(n--)o=vt(t[n]),o[x]?r.push(o):i.push(o);o=S(e,bt(i,r))}return o};function xt(e,t,n){var r=0,i=t.length;for(;i>r;r++)st(e,t[r],n);return n}function wt(e,t,n,r){var o,a,u,l,c,p=ft(e);if(!r&&1===p.length){if(a=p[0]=p[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&!d&&i.relative[a[1].type]){if(t=i.find.ID(u.matches[0].replace(et,tt),t)[0],!t)return n;e=e.slice(a.shift().value.length)}o=U.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],i.relative[l=u.type])break;if((c=i.find[l])&&(r=c(u.matches[0].replace(et,tt),V.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=r.length&&dt(a),!e)return H.apply(n,q.call(r,0)),n;break}}}return s(e,p)(r,t,d,n,V.test(e)),n}i.pseudos.nth=i.pseudos.eq;function Tt(){}i.filters=Tt.prototype=i.pseudos,i.setFilters=new Tt,c(),st.attr=b.attr,b.find=st,b.expr=st.selectors,b.expr[":"]=b.expr.pseudos,b.unique=st.uniqueSort,b.text=st.getText,b.isXMLDoc=st.isXML,b.contains=st.contains}(e);var at=/Until$/,st=/^(?:parents|prev(?:Until|All))/,ut=/^.[^:#\[\.,]*$/,lt=b.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};b.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return r=this,this.pushStack(b(e).filter(function(){for(t=0;i>t;t++)if(b.contains(r[t],this))return!0}));for(n=[],t=0;i>t;t++)b.find(e,this[t],n);return n=this.pushStack(i>1?b.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=b(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(b.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1))},filter:function(e){return this.pushStack(ft(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?lt.test(e)?b(e,this.context).index(this[0])>=0:b.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],a=lt.test(e)||"string"!=typeof e?b(e,t||this.context):0;for(;i>r;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&11!==n.nodeType){if(a?a.index(n)>-1:b.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}}return this.pushStack(o.length>1?b.unique(o):o)},index:function(e){return e?"string"==typeof e?b.inArray(this[0],b(e)):b.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?b(e,t):b.makeArray(e&&e.nodeType?[e]:e),r=b.merge(this.get(),n);return this.pushStack(b.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),b.fn.andSelf=b.fn.addBack;function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}b.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return b.dir(e,"parentNode")},parentsUntil:function(e,t,n){return b.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return b.dir(e,"nextSibling")},prevAll:function(e){return b.dir(e,"previousSibling")},nextUntil:function(e,t,n){return b.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return b.dir(e,"previousSibling",n)},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return b.sibling(e.firstChild)},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.merge([],e.childNodes)}},function(e,t){b.fn[e]=function(n,r){var i=b.map(this,t,n);return at.test(e)||(r=n),r&&"string"==typeof r&&(i=b.filter(r,i)),i=this.length>1&&!ct[e]?b.unique(i):i,this.length>1&&st.test(e)&&(i=i.reverse()),this.pushStack(i)}}),b.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?b.find.matchesSelector(t[0],e)?[t[0]]:[]:b.find.matches(e,t)},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!b(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(t=t||0,b.isFunction(t))return b.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return b.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=b.grep(e,function(e){return 1===e.nodeType});if(ut.test(t))return b.filter(t,r,!n);t=b.filter(t,r)}return b.grep(e,function(e){return b.inArray(e,t)>=0===n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:b.support.htmlSerialize?[0,"",""]:[1,"X
","
"]},jt=dt(o),Dt=jt.appendChild(o.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,b.fn.extend({text:function(e){return b.access(this,function(e){return e===t?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(b.isFunction(e))return this.each(function(t){b(this).wrapAll(e.call(this,t))});if(this[0]){var t=b(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return b.isFunction(e)?this.each(function(t){b(this).wrapInner(e.call(this,t))}):this.each(function(){var t=b(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=b.isFunction(e);return this.each(function(n){b(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){b.nodeName(this,"body")||b(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=0;for(;null!=(n=this[r]);r++)(!e||b.filter(e,[n]).length>0)&&(t||1!==n.nodeType||b.cleanData(Ot(n)),n.parentNode&&(t&&b.contains(n.ownerDocument,n)&&Mt(Ot(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&b.cleanData(Ot(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&b.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return b.clone(this,e,t)})},html:function(e){return b.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!b.support.htmlSerialize&&mt.test(e)||!b.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(b.cleanData(Ot(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=b.isFunction(e);return t||"string"==typeof e||(e=b(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;n&&(b(this).remove(),n.insertBefore(e,t))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=f.apply([],e);var i,o,a,s,u,l,c=0,p=this.length,d=this,h=p-1,g=e[0],m=b.isFunction(g);if(m||!(1>=p||"string"!=typeof g||b.support.checkClone)&&Ct.test(g))return this.each(function(i){var o=d.eq(i);m&&(e[0]=g.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(p&&(l=b.buildFragment(e,this[0].ownerDocument,!1,this),i=l.firstChild,1===l.childNodes.length&&(l=i),i)){for(n=n&&b.nodeName(i,"tr"),s=b.map(Ot(l,"script"),Ht),a=s.length;p>c;c++)o=l,c!==h&&(o=b.clone(o,!0,!0),a&&b.merge(s,Ot(o,"script"))),r.call(n&&b.nodeName(this[c],"table")?Lt(this[c],"tbody"):this[c],o,c);if(a)for(u=s[s.length-1].ownerDocument,b.map(s,qt),c=0;a>c;c++)o=s[c],kt.test(o.type||"")&&!b._data(o,"globalEval")&&b.contains(u,o)&&(o.src?b.ajax({url:o.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):b.globalEval((o.text||o.textContent||o.innerHTML||"").replace(St,"")));l=i=null}return this}});function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function Ht(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function Mt(e,t){var n,r=0;for(;null!=(n=e[r]);r++)b._data(n,"globalEval",!t||b._data(t[r],"globalEval"))}function _t(e,t){if(1===t.nodeType&&b.hasData(e)){var n,r,i,o=b._data(e),a=b._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)b.event.add(t,n,s[n][r])}a.data&&(a.data=b.extend({},a.data))}}function Ft(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!b.support.noCloneEvent&&t[b.expando]){i=b._data(t);for(r in i.events)b.removeEvent(t,r,i.handle);t.removeAttribute(b.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),b.support.html5Clone&&e.innerHTML&&!b.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Nt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}b.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){b.fn[e]=function(e){var n,r=0,i=[],o=b(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),b(o[r])[t](n),d.apply(i,n.get());return this.pushStack(i)}});function Ot(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||b.nodeName(o,n)?s.push(o):b.merge(s,Ot(o,n));return n===t||n&&b.nodeName(e,n)?b.merge([e],s):s}function Bt(e){Nt.test(e.type)&&(e.defaultChecked=e.checked)}b.extend({clone:function(e,t,n){var r,i,o,a,s,u=b.contains(e.ownerDocument,e);if(b.support.html5Clone||b.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(b.support.noCloneEvent&&b.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||b.isXMLDoc(e)))for(r=Ot(o),s=Ot(e),a=0;null!=(i=s[a]);++a)r[a]&&Ft(i,r[a]);if(t)if(n)for(s=s||Ot(e),r=r||Ot(o),a=0;null!=(i=s[a]);a++)_t(i,r[a]);else _t(e,o);return r=Ot(o,"script"),r.length>0&&Mt(r,!u&&Ot(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,u,l,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===b.type(o))b.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),u=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[u]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!b.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!b.support.tbody){o="table"!==u||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)b.nodeName(l=o.childNodes[i],"tbody")&&!l.childNodes.length&&o.removeChild(l) +}b.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),b.support.appendChecked||b.grep(Ot(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===b.inArray(o,r))&&(a=b.contains(o.ownerDocument,o),s=Ot(f.appendChild(o),"script"),a&&Mt(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,u=b.expando,l=b.cache,p=b.support.deleteExpando,f=b.event.special;for(;null!=(n=e[s]);s++)if((t||b.acceptData(n))&&(o=n[u],a=o&&l[o])){if(a.events)for(r in a.events)f[r]?b.event.remove(n,r):b.removeEvent(n,r,a.handle);l[o]&&(delete l[o],p?delete n[u]:typeof n.removeAttribute!==i?n.removeAttribute(u):n[u]=null,c.push(o))}}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+x+")(.*)$","i"),Yt=RegExp("^("+x+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+x+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===b.css(e,"display")||!b.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=b._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=b._data(r,"olddisplay",un(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&b._data(r,"olddisplay",i?n:b.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}b.fn.extend({css:function(e,n){return b.access(this,function(e,n,r){var i,o,a={},s=0;if(b.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=b.css(e,n[s],!1,o);return a}return r!==t?b.style(e,n,r):b.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?b(this).show():b(this).hide()})}}),b.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=b.camelCase(n),l=e.style;if(n=b.cssProps[u]||(b.cssProps[u]=tn(l,u)),s=b.cssHooks[n]||b.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(b.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||b.cssNumber[u]||(r+="px"),b.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=b.camelCase(n);return n=b.cssProps[u]||(b.cssProps[u]=tn(e.style,u)),s=b.cssHooks[n]||b.cssHooks[u],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||b.isNumeric(o)?o||0:a):a},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||b.contains(e.ownerDocument,e)||(u=b.style(e,n)),Yt.test(u)&&Ut.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):o.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),Yt.test(u)&&!zt.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=b.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=b.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=b.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=b.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=b.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(b.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function un(e){var t=o,n=Gt[e];return n||(n=ln(e,t),"none"!==n&&n||(Pt=(Pt||b("