TreeRouterTest.cs 72 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. using System.Globalization;
  4. using Microsoft.AspNetCore.Http;
  5. using Microsoft.AspNetCore.Routing.Template;
  6. using Microsoft.AspNetCore.Routing.TestObjects;
  7. using Microsoft.Extensions.Logging;
  8. using Microsoft.Extensions.Logging.Abstractions;
  9. using Microsoft.Extensions.ObjectPool;
  10. using Microsoft.Extensions.Options;
  11. using Moq;
  12. namespace Microsoft.AspNetCore.Routing.Tree;
  13. public class TreeRouterTest
  14. {
  15. private static readonly RequestDelegate NullHandler = (c) => Task.CompletedTask;
  16. [Theory]
  17. [InlineData("template/5", "template/{parameter:int}")]
  18. [InlineData("template/5", "template/{parameter}")]
  19. [InlineData("template/5", "template/{*parameter:int}")]
  20. [InlineData("template/5", "template/{*parameter}")]
  21. [InlineData("template/{parameter}", "template/{parameter:alpha}")] // constraint doesn't match
  22. [InlineData("template/{parameter:int}", "template/{parameter}")]
  23. [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
  24. [InlineData("template/{parameter:int}", "template/{*parameter}")]
  25. [InlineData("template/{parameter}", "template/{*parameter:int}")]
  26. [InlineData("template/{parameter}", "template/{*parameter}")]
  27. [InlineData("template/{*parameter:int}", "template/{*parameter}")]
  28. public async Task TreeRouter_RouteAsync_RespectsPrecedence(
  29. string firstTemplate,
  30. string secondTemplate)
  31. {
  32. // Arrange
  33. var expectedRouteGroup = CreateRouteGroup(0, firstTemplate);
  34. var builder = CreateBuilder();
  35. // We setup the route entries in reverse order of precedence to ensure that when we
  36. // try to route the request, the route with a higher precedence gets tried first.
  37. MapInboundEntry(builder, secondTemplate);
  38. MapInboundEntry(builder, firstTemplate);
  39. var route = builder.Build();
  40. var context = CreateRouteContext("/template/5");
  41. // Act
  42. await route.RouteAsync(context);
  43. // Assert
  44. Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
  45. }
  46. [Theory]
  47. [InlineData("/", "")]
  48. [InlineData("/Literal1", "Literal1")]
  49. [InlineData("/Literal1/Literal2", "Literal1/Literal2")]
  50. [InlineData("/Literal1/Literal2/Literal3", "Literal1/Literal2/Literal3")]
  51. [InlineData("/Literal1/Literal2/Literal3/4", "Literal1/Literal2/Literal3/{*constrainedCatchAll:int}")]
  52. [InlineData("/Literal1/Literal2/Literal3/Literal4", "Literal1/Literal2/Literal3/{*catchAll}")]
  53. [InlineData("/1", "{constrained1:int}")]
  54. [InlineData("/1/2", "{constrained1:int}/{constrained2:int}")]
  55. [InlineData("/1/2/3", "{constrained1:int}/{constrained2:int}/{constrained3:int}")]
  56. [InlineData("/1/2/3/4", "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*constrainedCatchAll:int}")]
  57. [InlineData("/1/2/3/CatchAll4", "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*catchAll}")]
  58. [InlineData("/parameter1", "{parameter1}")]
  59. [InlineData("/parameter1/parameter2", "{parameter1}/{parameter2}")]
  60. [InlineData("/parameter1/parameter2/parameter3", "{parameter1}/{parameter2}/{parameter3}")]
  61. [InlineData("/parameter1/parameter2/parameter3/4", "{parameter1}/{parameter2}/{parameter3}/{*constrainedCatchAll:int}")]
  62. [InlineData("/parameter1/parameter2/parameter3/CatchAll4", "{parameter1}/{parameter2}/{parameter3}/{*catchAll}")]
  63. public async Task TreeRouter_RouteAsync_MatchesRouteWithTheRightLength(string url, string expected)
  64. {
  65. // Arrange
  66. var routes = new[] {
  67. "",
  68. "Literal1",
  69. "Literal1/Literal2",
  70. "Literal1/Literal2/Literal3",
  71. "Literal1/Literal2/Literal3/{*constrainedCatchAll:int}",
  72. "Literal1/Literal2/Literal3/{*catchAll}",
  73. "{constrained1:int}",
  74. "{constrained1:int}/{constrained2:int}",
  75. "{constrained1:int}/{constrained2:int}/{constrained3:int}",
  76. "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*constrainedCatchAll:int}",
  77. "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*catchAll}",
  78. "{parameter1}",
  79. "{parameter1}/{parameter2}",
  80. "{parameter1}/{parameter2}/{parameter3}",
  81. "{parameter1}/{parameter2}/{parameter3}/{*constrainedCatchAll:int}",
  82. "{parameter1}/{parameter2}/{parameter3}/{*catchAll}",
  83. };
  84. var expectedRouteGroup = CreateRouteGroup(0, expected);
  85. var builder = CreateBuilder();
  86. // We setup the route entries in reverse order of precedence to ensure that when we
  87. // try to route the request, the route with a higher precedence gets tried first.
  88. foreach (var template in routes.Reverse())
  89. {
  90. MapInboundEntry(builder, template);
  91. }
  92. var route = builder.Build();
  93. var context = CreateRouteContext(url);
  94. // Act
  95. await route.RouteAsync(context);
  96. // Assert
  97. Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
  98. }
  99. public static TheoryData<string, object[]> MatchesRoutesWithDefaultsData =>
  100. new TheoryData<string, object[]>
  101. {
  102. { "/", new object[] { "1", "2", "3", "4" } },
  103. { "/a", new object[] { "a", "2", "3", "4" } },
  104. { "/a/b", new object[] { "a", "b", "3", "4" } },
  105. { "/a/b/c", new object[] { "a", "b", "c", "4" } },
  106. { "/a/b/c/d", new object[] { "a", "b", "c", "d" } }
  107. };
  108. [Theory]
  109. [MemberData(nameof(MatchesRoutesWithDefaultsData))]
  110. public async Task TreeRouter_RouteAsync_MatchesRoutesWithDefaults(string url, object[] routeValues)
  111. {
  112. // Arrange
  113. var routes = new[] {
  114. "{parameter1=1}/{parameter2=2}/{parameter3=3}/{parameter4=4}",
  115. };
  116. var expectedRouteGroup = CreateRouteGroup(0, "{parameter1=1}/{parameter2=2}/{parameter3=3}/{parameter4=4}");
  117. var routeValueKeys = new[] { "parameter1", "parameter2", "parameter3", "parameter4" };
  118. var expectedRouteValues = new RouteValueDictionary();
  119. for (var i = 0; i < routeValueKeys.Length; i++)
  120. {
  121. expectedRouteValues.Add(routeValueKeys[i], routeValues[i]);
  122. }
  123. var builder = CreateBuilder();
  124. // We setup the route entries in reverse order of precedence to ensure that when we
  125. // try to route the request, the route with a higher precedence gets tried first.
  126. foreach (var template in routes.Reverse())
  127. {
  128. MapInboundEntry(builder, template);
  129. }
  130. var route = builder.Build();
  131. var context = CreateRouteContext(url);
  132. // Act
  133. await route.RouteAsync(context);
  134. // Assert
  135. Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
  136. foreach (var entry in expectedRouteValues)
  137. {
  138. var data = Assert.Single(context.RouteData.Values, v => v.Key == entry.Key);
  139. Assert.Equal(entry.Value, data.Value);
  140. }
  141. }
  142. public static TheoryData<string, object[]> MatchesConstrainedRoutesWithDefaultsData =>
  143. new TheoryData<string, object[]>
  144. {
  145. { "/", new object[] { "1", "2", "3", "4" } },
  146. { "/10", new object[] { "10", "2", "3", "4" } },
  147. { "/10/11", new object[] { "10", "11", "3", "4" } },
  148. { "/10/11/12", new object[] { "10", "11", "12", "4" } },
  149. { "/10/11/12/13", new object[] { "10", "11", "12", "13" } }
  150. };
  151. [Theory]
  152. [MemberData(nameof(MatchesConstrainedRoutesWithDefaultsData))]
  153. public async Task TreeRouter_RouteAsync_MatchesConstrainedRoutesWithDefaults(string url, object[] routeValues)
  154. {
  155. // Arrange
  156. var routes = new[] {
  157. "{parameter1:int=1}/{parameter2:int=2}/{parameter3:int=3}/{parameter4:int=4}",
  158. };
  159. var expectedRouteGroup = CreateRouteGroup(0, "{parameter1:int=1}/{parameter2:int=2}/{parameter3:int=3}/{parameter4:int=4}");
  160. var routeValueKeys = new[] { "parameter1", "parameter2", "parameter3", "parameter4" };
  161. var expectedRouteValues = new RouteValueDictionary();
  162. for (var i = 0; i < routeValueKeys.Length; i++)
  163. {
  164. expectedRouteValues.Add(routeValueKeys[i], routeValues[i]);
  165. }
  166. var builder = CreateBuilder();
  167. // We setup the route entries in reverse order of precedence to ensure that when we
  168. // try to route the request, the route with a higher precedence gets tried first.
  169. foreach (var template in routes.Reverse())
  170. {
  171. MapInboundEntry(builder, template);
  172. }
  173. var route = builder.Build();
  174. var context = CreateRouteContext(url);
  175. // Act
  176. await route.RouteAsync(context);
  177. // Assert
  178. Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
  179. foreach (var entry in expectedRouteValues)
  180. {
  181. var data = Assert.Single(context.RouteData.Values, v => v.Key == entry.Key);
  182. Assert.Equal(entry.Value, data.Value);
  183. }
  184. }
  185. [Fact]
  186. public async Task TreeRouter_RouteAsync_MatchesCatchAllRoutesWithDefaults()
  187. {
  188. // Arrange
  189. var routes = new[] {
  190. "{parameter1=1}/{parameter2=2}/{parameter3=3}/{*parameter4=4}",
  191. };
  192. var url = "/a/b/c";
  193. var routeValues = new[] { "a", "b", "c", "4" };
  194. var expectedRouteGroup = CreateRouteGroup(0, "{parameter1=1}/{parameter2=2}/{parameter3=3}/{*parameter4=4}");
  195. var routeValueKeys = new[] { "parameter1", "parameter2", "parameter3", "parameter4" };
  196. var expectedRouteValues = new RouteValueDictionary();
  197. for (var i = 0; i < routeValueKeys.Length; i++)
  198. {
  199. expectedRouteValues.Add(routeValueKeys[i], routeValues[i]);
  200. }
  201. var builder = CreateBuilder();
  202. // We setup the route entries in reverse order of precedence to ensure that when we
  203. // try to route the request, the route with a higher precedence gets tried first.
  204. foreach (var template in routes.Reverse())
  205. {
  206. MapInboundEntry(builder, template);
  207. }
  208. var route = builder.Build();
  209. var context = CreateRouteContext(url);
  210. // Act
  211. await route.RouteAsync(context);
  212. // Assert
  213. Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
  214. foreach (var entry in expectedRouteValues)
  215. {
  216. var data = Assert.Single(context.RouteData.Values, v => v.Key == entry.Key);
  217. Assert.Equal(entry.Value, data.Value);
  218. }
  219. }
  220. [Fact]
  221. public async Task TreeRouter_RouteAsync_DoesNotMatchRoutesWithIntermediateDefaultRouteValues()
  222. {
  223. // Arrange
  224. var url = "/a/b";
  225. var builder = CreateBuilder();
  226. MapInboundEntry(builder, "a/b/{parameter3=3}/d");
  227. var route = builder.Build();
  228. var context = CreateRouteContext(url);
  229. // Act
  230. await route.RouteAsync(context);
  231. // Assert
  232. Assert.Null(context.Handler);
  233. }
  234. [Theory]
  235. [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a")]
  236. [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b")]
  237. [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c")]
  238. [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d")]
  239. public async Task TreeRouter_RouteAsync_DoesNotMatchRoutesWithMultipleIntermediateDefaultOrOptionalRouteValues(string template, string url)
  240. {
  241. // Arrange
  242. var builder = CreateBuilder();
  243. MapInboundEntry(builder, template);
  244. var route = builder.Build();
  245. var context = CreateRouteContext(url);
  246. // Act
  247. await route.RouteAsync(context);
  248. // Assert
  249. Assert.Null(context.Handler);
  250. }
  251. [Theory]
  252. [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d/e")]
  253. [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d/e/f")]
  254. public async Task RouteAsync_MatchRoutesWithMultipleIntermediateDefaultOrOptionalRouteValues_WhenAllIntermediateValuesAreProvided(string template, string url)
  255. {
  256. // Arrange
  257. var builder = CreateBuilder();
  258. MapInboundEntry(builder, template);
  259. var route = builder.Build();
  260. var context = CreateRouteContext(url);
  261. // Act
  262. await route.RouteAsync(context);
  263. // Assert
  264. Assert.NotNull(context.Handler);
  265. }
  266. [Fact]
  267. public async Task TreeRouter_RouteAsync_DoesNotMatchShorterUrl()
  268. {
  269. // Arrange
  270. var routes = new[] {
  271. "Literal1/Literal2/Literal3",
  272. };
  273. var builder = CreateBuilder();
  274. // We setup the route entries in reverse order of precedence to ensure that when we
  275. // try to route the request, the route with a higher precedence gets tried first.
  276. foreach (var template in routes.Reverse())
  277. {
  278. MapInboundEntry(builder, template);
  279. }
  280. var route = builder.Build();
  281. var context = CreateRouteContext("/Literal1");
  282. // Act
  283. await route.RouteAsync(context);
  284. // Assert
  285. Assert.Null(context.Handler);
  286. }
  287. [Theory]
  288. [InlineData("template/5", "template/{parameter:int}")]
  289. [InlineData("template/5", "template/{parameter}")]
  290. [InlineData("template/5", "template/{*parameter:int}")]
  291. [InlineData("template/5", "template/{*parameter}")]
  292. [InlineData("template/{parameter:int}", "template/{parameter}")]
  293. [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
  294. [InlineData("template/{parameter:int}", "template/{*parameter}")]
  295. [InlineData("template/{parameter}", "template/{*parameter:int}")]
  296. [InlineData("template/{parameter}", "template/{*parameter}")]
  297. [InlineData("template/{*parameter:int}", "template/{*parameter}")]
  298. public async Task TreeRouter_RouteAsync_RespectsOrderOverPrecedence(
  299. string firstTemplate,
  300. string secondTemplate)
  301. {
  302. // Arrange
  303. var expectedRouteGroup = CreateRouteGroup(0, secondTemplate);
  304. var builder = CreateBuilder();
  305. // We setup the route entries with a lower relative order and higher relative precedence
  306. // first to ensure that when we try to route the request, the route with the higher
  307. // relative order gets tried first.
  308. MapInboundEntry(builder, firstTemplate, order: 1);
  309. MapInboundEntry(builder, secondTemplate, order: 0);
  310. var route = builder.Build();
  311. var context = CreateRouteContext("/template/5");
  312. // Act
  313. await route.RouteAsync(context);
  314. // Assert
  315. Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
  316. }
  317. [Theory]
  318. [InlineData("///")]
  319. [InlineData("/a//")]
  320. [InlineData("/a/b//")]
  321. [InlineData("//b//")]
  322. [InlineData("///c")]
  323. [InlineData("///c/")]
  324. public async Task TryMatch_MultipleOptionalParameters_WithEmptyIntermediateSegmentsDoesNotMatch(string url)
  325. {
  326. // Arrange
  327. var builder = CreateBuilder();
  328. MapInboundEntry(builder, "{controller?}/{action?}/{id?}");
  329. var route = builder.Build();
  330. var context = CreateRouteContext(url);
  331. // Act
  332. await route.RouteAsync(context);
  333. // Assert
  334. Assert.Null(context.Handler);
  335. }
  336. [Theory]
  337. [InlineData("")]
  338. [InlineData("/")]
  339. [InlineData("/a")]
  340. [InlineData("/a/")]
  341. [InlineData("/a/b")]
  342. [InlineData("/a/b/")]
  343. [InlineData("/a/b/c")]
  344. [InlineData("/a/b/c/")]
  345. public async Task TryMatch_MultipleOptionalParameters_WithIncrementalOptionalValues(string url)
  346. {
  347. // Arrange
  348. var builder = CreateBuilder();
  349. MapInboundEntry(builder, "{controller?}/{action?}/{id?}");
  350. var route = builder.Build();
  351. var context = CreateRouteContext(url);
  352. // Act
  353. await route.RouteAsync(context);
  354. // Assert
  355. Assert.NotNull(context.Handler);
  356. }
  357. [Theory]
  358. [InlineData("///")]
  359. [InlineData("////")]
  360. [InlineData("/a//")]
  361. [InlineData("/a///")]
  362. [InlineData("//b/")]
  363. [InlineData("//b//")]
  364. [InlineData("///c")]
  365. [InlineData("///c/")]
  366. public async Task TryMatch_MultipleParameters_WithEmptyValues(string url)
  367. {
  368. // Arrange
  369. var builder = CreateBuilder();
  370. MapInboundEntry(builder, "{controller}/{action}/{id}");
  371. var route = builder.Build();
  372. var context = CreateRouteContext(url);
  373. // Act
  374. await route.RouteAsync(context);
  375. // Assert
  376. Assert.Null(context.Handler);
  377. }
  378. [Theory]
  379. [InlineData("/a/b/c//")]
  380. [InlineData("/a/b/c/////")]
  381. public async Task TryMatch_CatchAllParameters_WithEmptyValuesAtTheEnd(string url)
  382. {
  383. // Arrange
  384. var builder = CreateBuilder();
  385. MapInboundEntry(builder, "{controller}/{action}/{*id}");
  386. var route = builder.Build();
  387. var context = CreateRouteContext(url);
  388. // Act
  389. await route.RouteAsync(context);
  390. // Assert
  391. Assert.NotNull(context.Handler);
  392. }
  393. [Theory]
  394. [InlineData("/a/b//")]
  395. [InlineData("/a/b///c")]
  396. public async Task TryMatch_CatchAllParameters_WithEmptyValues(string url)
  397. {
  398. // Arrange
  399. var builder = CreateBuilder();
  400. MapInboundEntry(builder, "{controller}/{action}/{*id}");
  401. var route = builder.Build();
  402. var context = CreateRouteContext(url);
  403. // Act
  404. await route.RouteAsync(context);
  405. // Assert
  406. Assert.Null(context.Handler);
  407. }
  408. [Theory]
  409. [InlineData("{*path}", "/a", "a")]
  410. [InlineData("{*path}", "/a/b/c", "a/b/c")]
  411. [InlineData("a/{*path}", "/a/b", "b")]
  412. [InlineData("a/{*path}", "/a/b/c/d", "b/c/d")]
  413. [InlineData("a/{*path:regex(10/20/30)}", "/a/10/20/30", "10/20/30")]
  414. public async Task TreeRouter_RouteAsync_MatchesWildCard_ForLargerPathSegments(
  415. string template,
  416. string requestPath,
  417. string expectedResult)
  418. {
  419. // Arrange
  420. var builder = CreateBuilder();
  421. MapInboundEntry(builder, template);
  422. var route = builder.Build();
  423. var context = CreateRouteContext(requestPath);
  424. // Act
  425. await route.RouteAsync(context);
  426. // Assert
  427. Assert.NotNull(context.Handler);
  428. Assert.Equal(expectedResult, context.RouteData.Values["path"]);
  429. }
  430. [Theory]
  431. [InlineData("a/{*path}", "/a")]
  432. [InlineData("a/{*path}", "/a/")]
  433. public async Task TreeRouter_RouteAsync_MatchesCatchAll_NullValue(
  434. string template,
  435. string requestPath)
  436. {
  437. // Arrange
  438. var builder = CreateBuilder();
  439. MapInboundEntry(builder, template);
  440. var route = builder.Build();
  441. var context = CreateRouteContext(requestPath);
  442. // Act
  443. await route.RouteAsync(context);
  444. // Assert
  445. Assert.NotNull(context.Handler);
  446. Assert.Null(context.RouteData.Values["path"]);
  447. }
  448. [Theory]
  449. [InlineData("a/{*path}", "/a")]
  450. [InlineData("a/{*path}", "/a/")]
  451. public async Task TreeRouter_RouteAsync_MatchesCatchAll_NullValue_DoesNotReplaceExistingValue(
  452. string template,
  453. string requestPath)
  454. {
  455. // Arrange
  456. var builder = CreateBuilder();
  457. MapInboundEntry(builder, template);
  458. var route = builder.Build();
  459. var context = CreateRouteContext(requestPath);
  460. context.RouteData.Values["path"] = "existing-value";
  461. // Act
  462. await route.RouteAsync(context);
  463. // Assert
  464. Assert.NotNull(context.Handler);
  465. Assert.Equal("existing-value", context.RouteData.Values["path"]);
  466. }
  467. [Theory]
  468. [InlineData("a/{*path=default}", "/a")]
  469. [InlineData("a/{*path=default}", "/a/")]
  470. public async Task TreeRouter_RouteAsync_MatchesCatchAll_UsesDefaultValue(
  471. string template,
  472. string requestPath)
  473. {
  474. // Arrange
  475. var builder = CreateBuilder();
  476. MapInboundEntry(builder, template);
  477. var route = builder.Build();
  478. var context = CreateRouteContext(requestPath);
  479. context.RouteData.Values["path"] = "existing-value";
  480. // Act
  481. await route.RouteAsync(context);
  482. // Assert
  483. Assert.NotNull(context.Handler);
  484. Assert.Equal("default", context.RouteData.Values["path"]);
  485. }
  486. [Theory]
  487. [InlineData("template/5")]
  488. [InlineData("template/{parameter:int}")]
  489. [InlineData("template/{parameter}")]
  490. [InlineData("template/{*parameter:int}")]
  491. [InlineData("template/{*parameter}")]
  492. public async Task TreeRouter_RouteAsync_RespectsOrder(string template)
  493. {
  494. // Arrange
  495. var expectedRouteGroup = CreateRouteGroup(0, template);
  496. var builder = CreateBuilder();
  497. // We setup the route entries with a lower relative order first to ensure that when
  498. // we try to route the request, the route with the higher relative order gets tried first.
  499. MapInboundEntry(builder, template, order: 1);
  500. MapInboundEntry(builder, template, order: 0);
  501. var route = builder.Build();
  502. var context = CreateRouteContext("/template/5");
  503. // Act
  504. await route.RouteAsync(context);
  505. // Assert
  506. Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
  507. }
  508. [Theory]
  509. [InlineData("template/{first:int}", "template/{second:int}")]
  510. [InlineData("template/{first}", "template/{second}")]
  511. [InlineData("template/{*first:int}", "template/{*second:int}")]
  512. [InlineData("template/{*first}", "template/{*second}")]
  513. public async Task TreeRouter_RouteAsync_EnsuresStableOrdering(string first, string second)
  514. {
  515. // Arrange
  516. var expectedRouteGroup = CreateRouteGroup(0, first);
  517. var builder = CreateBuilder();
  518. // We setup the route entries with a lower relative template order first to ensure that when
  519. // we try to route the request, the route with the higher template order gets tried first.
  520. MapInboundEntry(builder, first);
  521. MapInboundEntry(builder, second);
  522. var route = builder.Build();
  523. var context = CreateRouteContext("/template/5");
  524. // Act
  525. await route.RouteAsync(context);
  526. // Assert
  527. Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
  528. }
  529. [Theory]
  530. [InlineData("template/{parameter:int}", "/template/5", true)]
  531. [InlineData("template/{parameter:int?}", "/template/5", true)]
  532. [InlineData("template/{parameter:int?}", "/template", true)]
  533. [InlineData("template/{parameter:int?}", "/template/qwer", false)]
  534. public async Task TreeRouter_WithOptionalInlineConstraint(
  535. string template,
  536. string request,
  537. bool expectedResult)
  538. {
  539. // Arrange
  540. var expectedRouteGroup = CreateRouteGroup(0, template);
  541. var builder = CreateBuilder();
  542. MapInboundEntry(builder, template);
  543. var route = builder.Build();
  544. var context = CreateRouteContext(request);
  545. // Act
  546. await route.RouteAsync(context);
  547. // Assert
  548. if (expectedResult)
  549. {
  550. Assert.NotNull(context.Handler);
  551. Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
  552. }
  553. else
  554. {
  555. Assert.Null(context.Handler);
  556. }
  557. }
  558. [Theory]
  559. [InlineData("moo/{p1}.{p2?}", "/moo/foo.bar", "foo", "bar", null)]
  560. [InlineData("moo/{p1?}", "/moo/foo", "foo", null, null)]
  561. [InlineData("moo/{p1?}", "/moo", null, null, null)]
  562. [InlineData("moo/{p1}.{p2?}", "/moo/foo", "foo", null, null)]
  563. [InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", "foo.", "bar", null)]
  564. [InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", "foo.moo", "bar", null)]
  565. [InlineData("moo/{p1}.{p2}", "/moo/foo.bar", "foo", "bar", null)]
  566. [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo.bar", "moo", "bar", null)]
  567. [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", "moo", null, null)]
  568. [InlineData("moo/.{p2?}", "/moo/.foo", null, "foo", null)]
  569. [InlineData("moo/{p1}.{p2?}", "/moo/....", "..", ".", null)]
  570. [InlineData("moo/{p1}.{p2?}", "/moo/.bar", ".bar", null, null)]
  571. [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
  572. [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo", "foo", "moo", null)]
  573. [InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
  574. [InlineData("{p1}.{p2?}/{p3}", "/foo.moo/bar", "foo", "moo", "bar")]
  575. [InlineData("{p1}.{p2?}/{p3}", "/foo/bar", "foo", null, "bar")]
  576. [InlineData("{p1}.{p2?}/{p3}", "/.foo/bar", ".foo", null, "bar")]
  577. public async Task TreeRouter_WithOptionalCompositeParameter_Valid(
  578. string template,
  579. string request,
  580. string p1,
  581. string p2,
  582. string p3)
  583. {
  584. // Arrange
  585. var expectedRouteGroup = CreateRouteGroup(0, template);
  586. var builder = CreateBuilder();
  587. MapInboundEntry(builder, template);
  588. var route = builder.Build();
  589. var context = CreateRouteContext(request);
  590. // Act
  591. await route.RouteAsync(context);
  592. // Assert
  593. Assert.NotNull(context.Handler);
  594. if (p1 != null)
  595. {
  596. Assert.Equal(p1, context.RouteData.Values["p1"]);
  597. }
  598. if (p2 != null)
  599. {
  600. Assert.Equal(p2, context.RouteData.Values["p2"]);
  601. }
  602. if (p3 != null)
  603. {
  604. Assert.Equal(p3, context.RouteData.Values["p3"]);
  605. }
  606. }
  607. [Theory]
  608. [InlineData("moo/{p1}.{p2?}", "/moo/foo.")]
  609. [InlineData("moo/{p1}.{p2?}", "/moo/.")]
  610. [InlineData("moo/{p1}.{p2}", "/foo.")]
  611. [InlineData("moo/{p1}.{p2}", "/foo")]
  612. [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.")]
  613. [InlineData("moo/foo.{p2}.{p3?}", "/moo/bar.foo.moo")]
  614. [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo.bar")]
  615. [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo")]
  616. [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo")]
  617. [InlineData("{p1}.{p2?}/{p3}", "/foo./bar")]
  618. [InlineData("moo/.{p2?}", "/moo/.")]
  619. [InlineData("{p1}.{p2}/{p3}", "/.foo/bar")]
  620. public async Task TreeRouter_WithOptionalCompositeParameter_Invalid(
  621. string template,
  622. string request)
  623. {
  624. // Arrange
  625. var expectedRouteGroup = CreateRouteGroup(0, template);
  626. var builder = CreateBuilder();
  627. MapInboundEntry(builder, template);
  628. var route = builder.Build();
  629. var context = CreateRouteContext(request);
  630. // Act
  631. await route.RouteAsync(context);
  632. // Assert
  633. Assert.Null(context.Handler);
  634. }
  635. [Theory]
  636. [InlineData("template", "{*url:alpha}", "/template?url=dingo&id=5")]
  637. [InlineData("{*url:alpha}", "{*url}", "/dingo?id=5")]
  638. [InlineData("{id}", "{*url}", "/5?url=dingo")]
  639. [InlineData("{id}", "{*url:alpha}", "/5?url=dingo")]
  640. [InlineData("{id:int}", "{id}", "/5?url=dingo")]
  641. [InlineData("{id}", "{id:alpha}/{url}", "/5?url=dingo")] // constraint doesn't match
  642. [InlineData("template/api/{*url}", "template/api", "/template/api/dingo?id=5")]
  643. [InlineData("template/api", "template/{*url}", "/template/api?url=dingo&id=5")]
  644. [InlineData("template/api", "template/api{id}location", "/template/api?url=dingo&id=5")]
  645. [InlineData("template/api{id}location", "template/{id:int}", "/template/api5location?url=dingo")]
  646. public void TreeRouter_GenerateLink(string firstTemplate, string secondTemplate, string expectedPath)
  647. {
  648. // Arrange
  649. var values = new Dictionary<string, object>
  650. {
  651. {"url", "dingo" },
  652. {"id", 5 }
  653. };
  654. var route = CreateTreeRouter(firstTemplate, secondTemplate);
  655. var context = CreateVirtualPathContext(
  656. values: values,
  657. ambientValues: null);
  658. // Act
  659. var result = route.GetVirtualPath(context);
  660. // Assert
  661. Assert.NotNull(result);
  662. Assert.Equal(expectedPath, result.VirtualPath);
  663. Assert.Same(route, result.Router);
  664. Assert.Empty(result.DataTokens);
  665. }
  666. [Fact]
  667. public void TreeRouter_GenerateLink_LongerTemplateWithDefaultIsMoreSpecific()
  668. {
  669. // Arrange
  670. var firstTemplate = "template";
  671. var secondTemplate = "template/{parameter:int=1003}";
  672. var route = CreateTreeRouter(firstTemplate, secondTemplate);
  673. var context = CreateVirtualPathContext(
  674. values: null,
  675. ambientValues: null);
  676. // Act
  677. var result = route.GetVirtualPath(context);
  678. // Assert
  679. Assert.NotNull(result);
  680. // The Binder binds to /template
  681. Assert.Equal("/template", result.VirtualPath);
  682. Assert.Same(route, result.Router);
  683. Assert.Empty(result.DataTokens);
  684. }
  685. [Theory]
  686. [InlineData("template/{parameter:int=5}", "template", "/template/5")]
  687. [InlineData("template/{parameter}", "template", "/template/5")]
  688. [InlineData("template/{parameter}/{id}", "template/{parameter}", "/template/5/1234")]
  689. public void TreeRouter_GenerateLink_OrderingAgnostic(
  690. string firstTemplate,
  691. string secondTemplate,
  692. string expectedPath)
  693. {
  694. // Arrange
  695. var route = CreateTreeRouter(firstTemplate, secondTemplate);
  696. var parameter = 5;
  697. var id = 1234;
  698. var values = new Dictionary<string, object>
  699. {
  700. { nameof(parameter) , parameter},
  701. { nameof(id), id }
  702. };
  703. var context = CreateVirtualPathContext(
  704. values: null,
  705. ambientValues: values);
  706. // Act
  707. var result = route.GetVirtualPath(context);
  708. // Assert
  709. Assert.NotNull(result);
  710. Assert.Equal(expectedPath, result.VirtualPath);
  711. Assert.Same(route, result.Router);
  712. Assert.Empty(result.DataTokens);
  713. }
  714. [Theory]
  715. [InlineData("template", "template/{parameter}", "/template/5")]
  716. [InlineData("template/{parameter}", "template/{parameter}/{id}", "/template/5/1234")]
  717. [InlineData("template", "template/{parameter:int=5}", "/template/5")]
  718. public void TreeRouter_GenerateLink_UseAvailableVariables(
  719. string firstTemplate,
  720. string secondTemplate,
  721. string expectedPath)
  722. {
  723. // Arrange
  724. var route = CreateTreeRouter(firstTemplate, secondTemplate);
  725. var parameter = 5;
  726. var id = 1234;
  727. var values = new Dictionary<string, object>
  728. {
  729. { nameof(parameter) , parameter},
  730. { nameof(id), id }
  731. };
  732. var context = CreateVirtualPathContext(
  733. values: null,
  734. ambientValues: values);
  735. // Act
  736. var result = route.GetVirtualPath(context);
  737. // Assert
  738. Assert.NotNull(result);
  739. Assert.Equal(expectedPath, result.VirtualPath);
  740. Assert.Same(route, result.Router);
  741. Assert.Empty(result.DataTokens);
  742. }
  743. [Theory]
  744. [InlineData("template/5", "template/{parameter:int}")]
  745. [InlineData("template/5", "template/{parameter}")]
  746. [InlineData("template/5", "template/{*parameter:int}")]
  747. [InlineData("template/5", "template/{*parameter}")]
  748. [InlineData("template/{parameter:int}", "template/{parameter}")]
  749. [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
  750. [InlineData("template/{parameter:int}", "template/{*parameter}")]
  751. [InlineData("template/{parameter}", "template/{*parameter:int}")]
  752. [InlineData("template/{parameter}", "template/{*parameter}")]
  753. [InlineData("template/{*parameter:int}", "template/{*parameter}")]
  754. public void TreeRouter_GenerateLink_RespectsPrecedence(string firstTemplate, string secondTemplate)
  755. {
  756. // Arrange
  757. var builder = CreateBuilder();
  758. // We setup the route entries in reverse order of precedence to ensure that when we
  759. // try to generate a link, the route with a higher precedence gets tried first.
  760. MapOutboundEntry(builder, secondTemplate);
  761. MapOutboundEntry(builder, firstTemplate);
  762. var route = builder.Build();
  763. var context = CreateVirtualPathContext(values: null, ambientValues: new { parameter = 5 });
  764. // Act
  765. var result = route.GetVirtualPath(context);
  766. // Assert
  767. Assert.NotNull(result);
  768. Assert.Equal("/template/5", result.VirtualPath);
  769. Assert.Same(route, result.Router);
  770. Assert.Empty(result.DataTokens);
  771. }
  772. [Theory]
  773. [InlineData("template/{parameter:int}", "/template/5", 5)]
  774. [InlineData("template/{parameter:int?}", "/template/5", 5)]
  775. [InlineData("template/{parameter:int?}", "/template", null)]
  776. [InlineData("template/{parameter:int?}", null, "asdf")]
  777. [InlineData("template/{parameter:alpha?}", "/template/asdf", "asdf")]
  778. [InlineData("template/{parameter:alpha?}", "/template", null)]
  779. [InlineData("template/{parameter:int:range(1,20)?}", "/template", null)]
  780. [InlineData("template/{parameter:int:range(1,20)?}", "/template/5", 5)]
  781. [InlineData("template/{parameter:int:range(1,20)?}", null, 21)]
  782. public void TreeRouter_GenerateLink_OptionalInlineParameter(
  783. string template,
  784. string expectedPath,
  785. object parameter)
  786. {
  787. // Arrange
  788. var builder = CreateBuilder();
  789. MapOutboundEntry(builder, template);
  790. var route = builder.Build();
  791. VirtualPathContext context;
  792. if (parameter != null)
  793. {
  794. context = CreateVirtualPathContext(values: null, ambientValues: new { parameter = parameter });
  795. }
  796. else
  797. {
  798. context = CreateVirtualPathContext(values: null, ambientValues: null);
  799. }
  800. // Act
  801. var result = route.GetVirtualPath(context);
  802. // Assert
  803. if (expectedPath == null)
  804. {
  805. Assert.Null(result);
  806. }
  807. else
  808. {
  809. Assert.NotNull(result);
  810. Assert.Equal(expectedPath, result.VirtualPath);
  811. Assert.Same(route, result.Router);
  812. Assert.Empty(result.DataTokens);
  813. }
  814. }
  815. [Theory]
  816. [InlineData("template/5", "template/{parameter:int}")]
  817. [InlineData("template/5", "template/{parameter}")]
  818. [InlineData("template/5", "template/{*parameter:int}")]
  819. [InlineData("template/5", "template/{*parameter}")]
  820. [InlineData("template/{parameter:int}", "template/{parameter}")]
  821. [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
  822. [InlineData("template/{parameter:int}", "template/{*parameter}")]
  823. [InlineData("template/{parameter}", "template/{*parameter:int}")]
  824. [InlineData("template/{parameter}", "template/{*parameter}")]
  825. [InlineData("template/{*parameter:int}", "template/{*parameter}")]
  826. public void TreeRouter_GenerateLink_RespectsOrderOverPrecedence(string firstTemplate, string secondTemplate)
  827. {
  828. // Arrange
  829. var builder = CreateBuilder();
  830. // We setup the route entries with a lower relative order and higher relative precedence
  831. // first to ensure that when we try to generate a link, the route with the higher
  832. // relative order gets tried first.
  833. MapOutboundEntry(builder, firstTemplate, order: 1);
  834. MapOutboundEntry(builder, secondTemplate, order: 0);
  835. var route = builder.Build();
  836. var context = CreateVirtualPathContext(null, ambientValues: new { parameter = 5 });
  837. // Act
  838. var result = route.GetVirtualPath(context);
  839. // Assert
  840. Assert.NotNull(result);
  841. Assert.Equal("/template/5", result.VirtualPath);
  842. Assert.Same(route, result.Router);
  843. Assert.Empty(result.DataTokens);
  844. }
  845. [Theory]
  846. [InlineData("template/5", "template/5")]
  847. [InlineData("template/{first:int}", "template/{second:int}")]
  848. [InlineData("template/{first}", "template/{second}")]
  849. [InlineData("template/{*first:int}", "template/{*second:int}")]
  850. [InlineData("template/{*first}", "template/{*second}")]
  851. public void TreeRouter_GenerateLink_RespectsOrder(string firstTemplate, string secondTemplate)
  852. {
  853. // Arrange
  854. var builder = CreateBuilder();
  855. // We setup the route entries with a lower relative order first to ensure that when
  856. // we try to generate a link, the route with the higher relative order gets tried first.
  857. MapOutboundEntry(builder, firstTemplate, requiredValues: null, order: 1);
  858. MapOutboundEntry(builder, secondTemplate, requiredValues: null, order: 0);
  859. var route = builder.Build();
  860. var context = CreateVirtualPathContext(values: null, ambientValues: new { first = 5, second = 5 });
  861. // Act
  862. var result = route.GetVirtualPath(context);
  863. // Assert
  864. Assert.NotNull(result);
  865. Assert.Equal("/template/5", result.VirtualPath);
  866. Assert.Same(route, result.Router);
  867. Assert.Empty(result.DataTokens);
  868. }
  869. [Theory]
  870. [InlineData("first/5", "second/5")]
  871. [InlineData("first/{first:int}", "second/{second:int}")]
  872. [InlineData("first/{first}", "second/{second}")]
  873. [InlineData("first/{*first:int}", "second/{*second:int}")]
  874. [InlineData("first/{*first}", "second/{*second}")]
  875. public void TreeRouter_GenerateLink_EnsuresStableOrder(string firstTemplate, string secondTemplate)
  876. {
  877. // Arrange
  878. var builder = CreateBuilder();
  879. // We setup the route entries with a lower relative template order first to ensure that when
  880. // we try to generate a link, the route with the higher template order gets tried first.
  881. MapOutboundEntry(builder, secondTemplate, requiredValues: null, order: 0);
  882. MapOutboundEntry(builder, firstTemplate, requiredValues: null, order: 0);
  883. var route = builder.Build();
  884. var context = CreateVirtualPathContext(values: null, ambientValues: new { first = 5, second = 5 });
  885. // Act
  886. var result = route.GetVirtualPath(context);
  887. // Assert
  888. Assert.NotNull(result);
  889. Assert.Equal("/first/5", result.VirtualPath);
  890. Assert.Same(route, result.Router);
  891. Assert.Empty(result.DataTokens);
  892. }
  893. [Fact]
  894. public void TreeRouter_GenerateLink_CreatesLinksForRoutesWithIntermediateDefaultRouteValues()
  895. {
  896. // Arrange
  897. var builder = CreateBuilder();
  898. MapOutboundEntry(builder, template: "a/b/{parameter3=3}/d", requiredValues: null, order: 0);
  899. var route = builder.Build();
  900. var context = CreateVirtualPathContext(values: null, ambientValues: null);
  901. // Act
  902. var result = route.GetVirtualPath(context);
  903. // Assert
  904. Assert.NotNull(result);
  905. Assert.Equal("/a/b/3/d", result.VirtualPath);
  906. }
  907. [Fact]
  908. public void TreeRouter_GeneratesLink_ForMultipleNamedEntriesWithTheSameTemplate()
  909. {
  910. // Arrange
  911. var builder = CreateBuilder();
  912. MapOutboundEntry(builder, "Template", name: "NamedEntry", order: 1);
  913. MapOutboundEntry(builder, "TEMPLATE", name: "NamedEntry", order: 2);
  914. // Act & Assert (does not throw)
  915. builder.Build();
  916. }
  917. [Fact]
  918. public void TreeRouter_GenerateLink_WithName()
  919. {
  920. // Arrange
  921. var builder = CreateBuilder();
  922. // The named route has a lower order which will ensure that we aren't trying the route as
  923. // if it were an unnamed route.
  924. MapOutboundEntry(builder, "named", requiredValues: null, order: 1, name: "NamedRoute");
  925. MapOutboundEntry(builder, "unnamed", requiredValues: null, order: 0);
  926. var route = builder.Build();
  927. var context = CreateVirtualPathContext(values: null, ambientValues: null, name: "NamedRoute");
  928. // Act
  929. var result = route.GetVirtualPath(context);
  930. // Assert
  931. Assert.NotNull(result);
  932. Assert.Equal("/named", result.VirtualPath);
  933. Assert.Same(route, result.Router);
  934. Assert.Empty(result.DataTokens);
  935. }
  936. [Fact]
  937. public void TreeRouter_DoesNotGenerateLink_IfThereIsNoRouteForAGivenName()
  938. {
  939. // Arrange
  940. var builder = CreateBuilder();
  941. // The named route has a lower order which will ensure that we aren't trying the route as
  942. // if it were an unnamed route.
  943. MapOutboundEntry(builder, "named", requiredValues: null, order: 1, name: "NamedRoute");
  944. // Add an unnamed entry to ensure we don't fall back to generating a link for an unnamed route.
  945. MapOutboundEntry(builder, "unnamed", requiredValues: null, order: 0);
  946. var route = builder.Build();
  947. var context = CreateVirtualPathContext(values: null, ambientValues: null, name: "NonExistingNamedRoute");
  948. // Act
  949. var result = route.GetVirtualPath(context);
  950. // Assert
  951. Assert.Null(result);
  952. }
  953. [Theory]
  954. [InlineData("template/{parameter:int}", null)]
  955. [InlineData("template/{parameter:int}", "NaN")]
  956. [InlineData("template/{parameter}", null)]
  957. [InlineData("template/{*parameter:int}", null)]
  958. [InlineData("template/{*parameter:int}", "NaN")]
  959. public void TreeRouter_DoesNotGenerateLink_IfValuesDoNotMatchNamedEntry(string template, string value)
  960. {
  961. // Arrange
  962. var builder = CreateBuilder();
  963. // The named route has a lower order which will ensure that we aren't trying the route as
  964. // if it were an unnamed route.
  965. MapOutboundEntry(builder, template, requiredValues: null, order: 1, name: "NamedRoute");
  966. // Add an unnamed entry to ensure we don't fall back to generating a link for an unnamed route.
  967. MapOutboundEntry(builder, "unnamed", requiredValues: null, order: 0);
  968. var route = builder.Build();
  969. var ambientValues = value == null ? null : new { parameter = value };
  970. var context = CreateVirtualPathContext(values: null, ambientValues: ambientValues, name: "NamedRoute");
  971. // Act
  972. var result = route.GetVirtualPath(context);
  973. // Assert
  974. Assert.Null(result);
  975. }
  976. [Theory]
  977. [InlineData("template/{parameter:int}", "5")]
  978. [InlineData("template/{parameter}", "5")]
  979. [InlineData("template/{*parameter:int}", "5")]
  980. [InlineData("template/{*parameter}", "5")]
  981. public void TreeRouter_GeneratesLink_IfValuesMatchNamedEntry(string template, string value)
  982. {
  983. // Arrange
  984. var builder = CreateBuilder();
  985. // The named route has a lower order which will ensure that we aren't trying the route as
  986. // if it were an unnamed route.
  987. MapOutboundEntry(builder, template, requiredValues: null, order: 1, name: "NamedRoute");
  988. // Add an unnamed entry to ensure we don't fall back to generating a link for an unnamed route.
  989. MapOutboundEntry(builder, "unnamed", requiredValues: null, order: 0);
  990. var route = builder.Build();
  991. var ambientValues = value == null ? null : new { parameter = value };
  992. var context = CreateVirtualPathContext(values: null, ambientValues: ambientValues, name: "NamedRoute");
  993. // Act
  994. var result = route.GetVirtualPath(context);
  995. // Assert
  996. Assert.NotNull(result);
  997. Assert.Equal("/template/5", result.VirtualPath);
  998. Assert.Same(route, result.Router);
  999. Assert.Empty(result.DataTokens);
  1000. }
  1001. [Fact]
  1002. public void TreeRouter_GenerateLink_NoRequiredValues()
  1003. {
  1004. // Arrange
  1005. var builder = CreateBuilder();
  1006. MapOutboundEntry(builder, "api/Store", new { });
  1007. var route = builder.Build();
  1008. var context = CreateVirtualPathContext(new { });
  1009. // Act
  1010. var pathData = route.GetVirtualPath(context);
  1011. // Assert
  1012. Assert.NotNull(pathData);
  1013. Assert.Equal("/api/Store", pathData.VirtualPath);
  1014. Assert.Same(route, pathData.Router);
  1015. Assert.Empty(pathData.DataTokens);
  1016. }
  1017. [Fact]
  1018. public void TreeRouter_GenerateLink_Match()
  1019. {
  1020. // Arrange
  1021. var builder = CreateBuilder();
  1022. MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
  1023. var route = builder.Build();
  1024. var context = CreateVirtualPathContext(new { action = "Index", controller = "Store" });
  1025. // Act
  1026. var pathData = route.GetVirtualPath(context);
  1027. // Assert
  1028. Assert.NotNull(pathData);
  1029. Assert.Equal("/api/Store", pathData.VirtualPath);
  1030. Assert.Same(route, pathData.Router);
  1031. Assert.Empty(pathData.DataTokens);
  1032. }
  1033. [Fact]
  1034. public void TreeRouter_GenerateLink_NoMatch()
  1035. {
  1036. // Arrange
  1037. var builder = CreateBuilder();
  1038. MapOutboundEntry(builder, "api/Store", new { action = "Details", controller = "Store" });
  1039. var route = builder.Build();
  1040. var context = CreateVirtualPathContext(new { action = "Index", controller = "Store" });
  1041. // Act
  1042. var path = route.GetVirtualPath(context);
  1043. // Assert
  1044. Assert.Null(path);
  1045. }
  1046. [Fact]
  1047. public void TreeRouter_GenerateLink_Match_WithAmbientValues()
  1048. {
  1049. // Arrange
  1050. var builder = CreateBuilder();
  1051. MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
  1052. var route = builder.Build();
  1053. var context = CreateVirtualPathContext(new { }, new { action = "Index", controller = "Store" });
  1054. // Act
  1055. var pathData = route.GetVirtualPath(context);
  1056. // Assert
  1057. Assert.NotNull(pathData);
  1058. Assert.Equal("/api/Store", pathData.VirtualPath);
  1059. Assert.Same(route, pathData.Router);
  1060. Assert.Empty(pathData.DataTokens);
  1061. }
  1062. [Fact]
  1063. public void TreeRouter_GenerateLink_Match_HasTwoOptionalParametersWithoutValues()
  1064. {
  1065. // Arrange
  1066. var builder = CreateBuilder();
  1067. MapOutboundEntry(builder, "Customers/SeparatePageModels/{handler?}/{id?}", new { page = "/Customers/SeparatePageModels/Index" });
  1068. var route = builder.Build();
  1069. var context = CreateVirtualPathContext(new { page = "/Customers/SeparatePageModels/Index" }, new { page = "/Customers/SeparatePageModels/Edit", id = "17" });
  1070. // Act
  1071. var pathData = route.GetVirtualPath(context);
  1072. // Assert
  1073. Assert.NotNull(pathData);
  1074. Assert.Equal("/Customers/SeparatePageModels", pathData.VirtualPath);
  1075. Assert.Same(route, pathData.Router);
  1076. Assert.Empty(pathData.DataTokens);
  1077. }
  1078. [Fact]
  1079. public void TreeRouter_GenerateLink_Match_WithParameters()
  1080. {
  1081. // Arrange
  1082. var builder = CreateBuilder();
  1083. MapOutboundEntry(builder, "api/Store/{action}", new { action = "Index", controller = "Store" });
  1084. var route = builder.Build();
  1085. var context = CreateVirtualPathContext(new { action = "Index", controller = "Store" });
  1086. // Act
  1087. var pathData = route.GetVirtualPath(context);
  1088. // Assert
  1089. Assert.NotNull(pathData);
  1090. Assert.Equal("/api/Store/Index", pathData.VirtualPath);
  1091. Assert.Same(route, pathData.Router);
  1092. Assert.Empty(pathData.DataTokens);
  1093. }
  1094. [Fact]
  1095. public void TreeRouter_GenerateLink_Match_WithMoreParameters()
  1096. {
  1097. // Arrange
  1098. var builder = CreateBuilder();
  1099. MapOutboundEntry(builder,
  1100. "api/{area}/dosomething/{controller}/{action}",
  1101. new { action = "Index", controller = "Store", area = "AwesomeCo" });
  1102. var route = builder.Build();
  1103. var context = CreateVirtualPathContext(
  1104. new { action = "Index", controller = "Store" },
  1105. new { area = "AwesomeCo" });
  1106. // Act
  1107. var pathData = route.GetVirtualPath(context);
  1108. // Assert
  1109. Assert.NotNull(pathData);
  1110. Assert.Equal("/api/AwesomeCo/dosomething/Store/Index", pathData.VirtualPath);
  1111. Assert.Same(route, pathData.Router);
  1112. Assert.Empty(pathData.DataTokens);
  1113. }
  1114. [Fact]
  1115. public void TreeRouter_GenerateLink_Match_WithDefault()
  1116. {
  1117. // Arrange
  1118. var builder = CreateBuilder();
  1119. MapOutboundEntry(builder, "api/Store/{action=Index}", new { action = "Index", controller = "Store" });
  1120. var route = builder.Build();
  1121. var context = CreateVirtualPathContext(new { action = "Index", controller = "Store" });
  1122. // Act
  1123. var pathData = route.GetVirtualPath(context);
  1124. // Assert
  1125. Assert.NotNull(pathData);
  1126. Assert.Equal("/api/Store", pathData.VirtualPath);
  1127. Assert.Same(route, pathData.Router);
  1128. Assert.Empty(pathData.DataTokens);
  1129. }
  1130. [Fact]
  1131. public void TreeRouter_GenerateLink_Match_WithConstraint()
  1132. {
  1133. // Arrange
  1134. var builder = CreateBuilder();
  1135. MapOutboundEntry(builder, "api/Store/{action}/{id:int}", new { action = "Index", controller = "Store" });
  1136. var route = builder.Build();
  1137. var context = CreateVirtualPathContext(new { action = "Index", controller = "Store", id = 5 });
  1138. // Act
  1139. var pathData = route.GetVirtualPath(context);
  1140. // Assert
  1141. Assert.NotNull(pathData);
  1142. Assert.Equal("/api/Store/Index/5", pathData.VirtualPath);
  1143. Assert.Same(route, pathData.Router);
  1144. Assert.Empty(pathData.DataTokens);
  1145. }
  1146. [Fact]
  1147. public void TreeRouter_GenerateLink_NoMatch_WithConstraint()
  1148. {
  1149. // Arrange
  1150. var builder = CreateBuilder();
  1151. MapOutboundEntry(builder, "api/Store/{action}/{id:int}", new { action = "Index", controller = "Store" });
  1152. var route = builder.Build();
  1153. var next = new StubRouter();
  1154. var context = CreateVirtualPathContext(new { action = "Index", controller = "Store", id = "heyyyy" });
  1155. // Act
  1156. var path = route.GetVirtualPath(context);
  1157. // Assert
  1158. Assert.Null(path);
  1159. }
  1160. [Fact]
  1161. public void TreeRouter_GenerateLink_Match_WithMixedAmbientValues()
  1162. {
  1163. // Arrange
  1164. var builder = CreateBuilder();
  1165. MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
  1166. var route = builder.Build();
  1167. var context = CreateVirtualPathContext(new { action = "Index" }, new { controller = "Store" });
  1168. // Act
  1169. var pathData = route.GetVirtualPath(context);
  1170. // Assert
  1171. Assert.NotNull(pathData);
  1172. Assert.Equal("/api/Store", pathData.VirtualPath);
  1173. Assert.Same(route, pathData.Router);
  1174. Assert.Empty(pathData.DataTokens);
  1175. }
  1176. [Fact]
  1177. public void TreeRouter_GenerateLink_Match_WithQueryString()
  1178. {
  1179. // Arrange
  1180. var builder = CreateBuilder();
  1181. MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
  1182. var route = builder.Build();
  1183. var context = CreateVirtualPathContext(new { action = "Index", id = 5 }, new { controller = "Store" });
  1184. // Act
  1185. var pathData = route.GetVirtualPath(context);
  1186. // Assert
  1187. Assert.NotNull(pathData);
  1188. Assert.Equal("/api/Store?id=5", pathData.VirtualPath);
  1189. Assert.Same(route, pathData.Router);
  1190. Assert.Empty(pathData.DataTokens);
  1191. }
  1192. [Fact]
  1193. public void TreeRouter_GenerateLink_RejectedByFirstRoute()
  1194. {
  1195. // Arrange
  1196. var builder = CreateBuilder();
  1197. MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
  1198. MapOutboundEntry(builder, "api2/{controller}", new { action = "Index", controller = "Blog" });
  1199. var route = builder.Build();
  1200. var context = CreateVirtualPathContext(new { action = "Index", controller = "Blog" });
  1201. // Act
  1202. var pathData = route.GetVirtualPath(context);
  1203. // Assert
  1204. Assert.NotNull(pathData);
  1205. Assert.Equal("/api2/Blog", pathData.VirtualPath);
  1206. Assert.Same(route, pathData.Router);
  1207. Assert.Empty(pathData.DataTokens);
  1208. }
  1209. [Fact]
  1210. public void TreeRouter_GenerateLink_ToArea()
  1211. {
  1212. // Arrange
  1213. var builder = CreateBuilder();
  1214. var entry1 = MapOutboundEntry(builder, "Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
  1215. entry1.Precedence = 2;
  1216. var entry2 = MapOutboundEntry(builder, "Store", new { area = (string)null, action = "Edit", controller = "Store" });
  1217. entry2.Precedence = 1;
  1218. var route = builder.Build();
  1219. var context = CreateVirtualPathContext(new { area = "Help", action = "Edit", controller = "Store" });
  1220. // Act
  1221. var pathData = route.GetVirtualPath(context);
  1222. // Assert
  1223. Assert.NotNull(pathData);
  1224. Assert.Equal("/Help/Store", pathData.VirtualPath);
  1225. Assert.Same(route, pathData.Router);
  1226. Assert.Empty(pathData.DataTokens);
  1227. }
  1228. [Fact]
  1229. public void TreeRouter_GenerateLink_ToArea_PredecedenceReversed()
  1230. {
  1231. // Arrange
  1232. var builder = CreateBuilder();
  1233. var entry1 = MapOutboundEntry(builder, "Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
  1234. entry1.Precedence = 1;
  1235. var entry2 = MapOutboundEntry(builder, "Store", new { area = (string)null, action = "Edit", controller = "Store" });
  1236. entry2.Precedence = 2;
  1237. var route = builder.Build();
  1238. var context = CreateVirtualPathContext(new { area = "Help", action = "Edit", controller = "Store" });
  1239. // Act
  1240. var pathData = route.GetVirtualPath(context);
  1241. // Assert
  1242. Assert.NotNull(pathData);
  1243. Assert.Equal("/Help/Store", pathData.VirtualPath);
  1244. Assert.Same(route, pathData.Router);
  1245. Assert.Empty(pathData.DataTokens);
  1246. }
  1247. [Fact]
  1248. public void TreeRouter_GenerateLink_ToArea_WithAmbientValues()
  1249. {
  1250. // Arrange
  1251. var builder = CreateBuilder();
  1252. var entry1 = MapOutboundEntry(builder, "Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
  1253. entry1.Precedence = 2;
  1254. var entry2 = MapOutboundEntry(builder, "Store", new { area = (string)null, action = "Edit", controller = "Store" });
  1255. entry2.Precedence = 1;
  1256. var route = builder.Build();
  1257. var context = CreateVirtualPathContext(
  1258. values: new { action = "Edit", controller = "Store" },
  1259. ambientValues: new { area = "Help" });
  1260. // Act
  1261. var pathData = route.GetVirtualPath(context);
  1262. // Assert
  1263. Assert.NotNull(pathData);
  1264. Assert.Equal("/Help/Store", pathData.VirtualPath);
  1265. Assert.Same(route, pathData.Router);
  1266. Assert.Empty(pathData.DataTokens);
  1267. }
  1268. [Fact]
  1269. public void TreeRouter_GenerateLink_OutOfArea_IgnoresAmbientValue()
  1270. {
  1271. // Arrange
  1272. var builder = CreateBuilder();
  1273. var entry1 = MapOutboundEntry(builder, "Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
  1274. entry1.Precedence = 2;
  1275. var entry2 = MapOutboundEntry(builder, "Store", new { area = (string)null, action = "Edit", controller = "Store" });
  1276. entry2.Precedence = 1;
  1277. var route = builder.Build();
  1278. var context = CreateVirtualPathContext(
  1279. values: new { action = "Edit", controller = "Store" },
  1280. ambientValues: new { area = "Blog" });
  1281. // Act
  1282. var pathData = route.GetVirtualPath(context);
  1283. // Assert
  1284. Assert.NotNull(pathData);
  1285. Assert.Equal("/Store", pathData.VirtualPath);
  1286. Assert.Same(route, pathData.Router);
  1287. Assert.Empty(pathData.DataTokens);
  1288. }
  1289. public static IEnumerable<object[]> OptionalParamValues
  1290. {
  1291. get
  1292. {
  1293. return new object[][]
  1294. {
  1295. // defaults
  1296. // ambient values
  1297. // values
  1298. new object[]
  1299. {
  1300. "Test/{val1}/{val2}.{val3?}",
  1301. new {val1 = "someval1", val2 = "someval2", val3 = "someval3a"},
  1302. new {val3 = "someval3v"},
  1303. "/Test/someval1/someval2.someval3v",
  1304. },
  1305. new object[]
  1306. {
  1307. "Test/{val1}/{val2}.{val3?}",
  1308. new {val3 = "someval3a"},
  1309. new {val1 = "someval1", val2 = "someval2", val3 = "someval3v" },
  1310. "/Test/someval1/someval2.someval3v",
  1311. },
  1312. new object[]
  1313. {
  1314. "Test/{val1}/{val2}.{val3?}",
  1315. null,
  1316. new {val1 = "someval1", val2 = "someval2" },
  1317. "/Test/someval1/someval2",
  1318. },
  1319. new object[]
  1320. {
  1321. "Test/{val1}.{val2}.{val3}.{val4?}",
  1322. new {val1 = "someval1", val2 = "someval2" },
  1323. new {val4 = "someval4", val3 = "someval3" },
  1324. "/Test/someval1.someval2.someval3.someval4",
  1325. },
  1326. new object[]
  1327. {
  1328. "Test/{val1}.{val2}.{val3}.{val4?}",
  1329. new {val1 = "someval1", val2 = "someval2" },
  1330. new {val3 = "someval3" },
  1331. "/Test/someval1.someval2.someval3",
  1332. },
  1333. new object[]
  1334. {
  1335. "Test/.{val2?}",
  1336. null,
  1337. new {val2 = "someval2" },
  1338. "/Test/.someval2",
  1339. },
  1340. new object[]
  1341. {
  1342. "Test/.{val2?}",
  1343. null,
  1344. null,
  1345. "/Test/",
  1346. },
  1347. new object[]
  1348. {
  1349. "Test/{val1}.{val2}",
  1350. new {val1 = "someval1", val2 = "someval2" },
  1351. new {val3 = "someval3" },
  1352. "/Test/someval1.someval2?val3=someval3",
  1353. },
  1354. };
  1355. }
  1356. }
  1357. [Theory]
  1358. [MemberData(nameof(OptionalParamValues))]
  1359. public void TreeRouter_GenerateLink_Match_WithOptionalParameters(
  1360. string template,
  1361. object ambientValues,
  1362. object values,
  1363. string expected)
  1364. {
  1365. // Arrange
  1366. var builder = CreateBuilder();
  1367. MapOutboundEntry(builder, template);
  1368. var route = builder.Build();
  1369. var context = CreateVirtualPathContext(values, ambientValues);
  1370. // Act
  1371. var pathData = route.GetVirtualPath(context);
  1372. // Assert
  1373. Assert.NotNull(pathData);
  1374. Assert.Equal(expected, pathData.VirtualPath);
  1375. Assert.Same(route, pathData.Router);
  1376. Assert.Empty(pathData.DataTokens);
  1377. }
  1378. [Fact]
  1379. public async Task TreeRouter_ReplacesExistingRouteValues_IfNotNull()
  1380. {
  1381. // Arrange
  1382. var builder = CreateBuilder();
  1383. MapInboundEntry(builder, "Foo/{*path}");
  1384. var route = builder.Build();
  1385. var context = CreateRouteContext("/Foo/Bar");
  1386. var originalRouteData = context.RouteData;
  1387. originalRouteData.Values.Add("path", "default");
  1388. // Act
  1389. await route.RouteAsync(context);
  1390. // Assert
  1391. Assert.Equal("Bar", context.RouteData.Values["path"]);
  1392. }
  1393. [Fact]
  1394. public async Task TreeRouter_DoesNotReplaceExistingRouteValues_IfNull()
  1395. {
  1396. // Arrange
  1397. var builder = CreateBuilder();
  1398. MapInboundEntry(builder, "Foo/{*path}");
  1399. var route = builder.Build();
  1400. var context = CreateRouteContext("/Foo/");
  1401. var originalRouteData = context.RouteData;
  1402. originalRouteData.Values.Add("path", "default");
  1403. // Act
  1404. await route.RouteAsync(context);
  1405. // Assert
  1406. Assert.Equal("default", context.RouteData.Values["path"]);
  1407. }
  1408. [Fact]
  1409. public async Task TreeRouter_SnapshotsRouteData()
  1410. {
  1411. // Arrange
  1412. RouteValueDictionary nestedValues = null;
  1413. List<IRouter> nestedRouters = null;
  1414. var next = new Mock<IRouter>();
  1415. next
  1416. .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
  1417. .Callback<RouteContext>(c =>
  1418. {
  1419. nestedValues = new RouteValueDictionary(c.RouteData.Values);
  1420. nestedRouters = new List<IRouter>(c.RouteData.Routers);
  1421. c.Handler = null; // Not a match
  1422. })
  1423. .Returns(Task.CompletedTask);
  1424. var builder = CreateBuilder();
  1425. MapInboundEntry(builder, "api/Store", handler: next.Object);
  1426. var route = builder.Build();
  1427. var context = CreateRouteContext("/api/Store");
  1428. var routeData = context.RouteData;
  1429. routeData.Values.Add("action", "Index");
  1430. var originalValues = new RouteValueDictionary(context.RouteData.Values);
  1431. // Act
  1432. await route.RouteAsync(context);
  1433. // Assert
  1434. Assert.Equal(originalValues, context.RouteData.Values);
  1435. Assert.NotEqual(nestedValues, context.RouteData.Values);
  1436. }
  1437. [Fact]
  1438. public async Task TreeRouter_SnapshotsRouteData_ResetsWhenNotMatched()
  1439. {
  1440. // Arrange
  1441. RouteValueDictionary nestedValues = null;
  1442. List<IRouter> nestedRouters = null;
  1443. var next = new Mock<IRouter>();
  1444. next
  1445. .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
  1446. .Callback<RouteContext>(c =>
  1447. {
  1448. nestedValues = new RouteValueDictionary(c.RouteData.Values);
  1449. nestedRouters = new List<IRouter>(c.RouteData.Routers);
  1450. c.Handler = null; // Not a match
  1451. })
  1452. .Returns(Task.CompletedTask);
  1453. var builder = CreateBuilder();
  1454. MapInboundEntry(builder, "api/Store", handler: next.Object);
  1455. var route = builder.Build();
  1456. var context = CreateRouteContext("/api/Store");
  1457. context.RouteData.Values.Add("action", "Index");
  1458. // Act
  1459. await route.RouteAsync(context);
  1460. // Assert
  1461. Assert.NotEqual(nestedValues, context.RouteData.Values);
  1462. // The new routedata is a copy
  1463. Assert.Equal("Index", context.RouteData.Values["action"]);
  1464. Assert.Equal("Index", nestedValues["action"]);
  1465. Assert.DoesNotContain(context.RouteData.Values, kvp => kvp.Key == "test_route_group");
  1466. Assert.Single(nestedValues, kvp => kvp.Key == "test_route_group");
  1467. Assert.Empty(context.RouteData.Routers);
  1468. Assert.Single(nestedRouters);
  1469. Assert.Equal(next.Object.GetType(), nestedRouters[0].GetType());
  1470. }
  1471. [Fact]
  1472. public async Task TreeRouter_SnapshotsRouteData_ResetsWhenThrows()
  1473. {
  1474. // Arrange
  1475. RouteValueDictionary nestedValues = null;
  1476. List<IRouter> nestedRouters = null;
  1477. var next = new Mock<IRouter>();
  1478. next
  1479. .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
  1480. .Callback<RouteContext>(c =>
  1481. {
  1482. nestedValues = new RouteValueDictionary(c.RouteData.Values);
  1483. nestedRouters = new List<IRouter>(c.RouteData.Routers);
  1484. throw new Exception();
  1485. })
  1486. .Returns(Task.CompletedTask);
  1487. var builder = CreateBuilder();
  1488. MapInboundEntry(builder, "api/Store", handler: next.Object);
  1489. var route = builder.Build();
  1490. var context = CreateRouteContext("/api/Store");
  1491. context.RouteData.Values.Add("action", "Index");
  1492. // Act
  1493. await Assert.ThrowsAsync<Exception>(() => route.RouteAsync(context));
  1494. // Assert
  1495. Assert.NotEqual(nestedValues, context.RouteData.Values);
  1496. Assert.Equal("Index", context.RouteData.Values["action"]);
  1497. Assert.Equal("Index", nestedValues["action"]);
  1498. Assert.DoesNotContain(context.RouteData.Values, kvp => kvp.Key == "test_route_group");
  1499. Assert.Single(nestedValues, kvp => kvp.Key == "test_route_group");
  1500. Assert.Empty(context.RouteData.Routers);
  1501. Assert.Single(nestedRouters);
  1502. Assert.Equal(next.Object.GetType(), nestedRouters[0].GetType());
  1503. }
  1504. [Fact]
  1505. public async Task TreeRouter_SnapshotsRouteData_ResetsBeforeMatchingEachRouteEntry()
  1506. {
  1507. // This test replicates a scenario raised as issue https://github.com/aspnet/Routing/issues/394
  1508. // The RouteValueDictionary entries populated while matching route entries should not be left
  1509. // in place if the route entry turns out not to match, because that would leak unwanted state
  1510. // to subsequent route entries and might cause "An element with the key ... already exists"
  1511. // exceptions.
  1512. // Arrange
  1513. RouteValueDictionary nestedValues = null;
  1514. var next = new Mock<IRouter>();
  1515. next
  1516. .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
  1517. .Callback<RouteContext>(c =>
  1518. {
  1519. nestedValues = new RouteValueDictionary(c.RouteData.Values);
  1520. c.Handler = NullHandler;
  1521. })
  1522. .Returns(Task.CompletedTask);
  1523. var builder = CreateBuilder();
  1524. MapInboundEntry(builder, "cat_{category1}/prod1_{product}"); // Matches on first segment but not on second
  1525. MapInboundEntry(builder, "cat_{category2}/prod2_{product}", handler: next.Object);
  1526. var route = builder.Build();
  1527. var context = CreateRouteContext("/cat_examplecategory/prod2_exampleproduct");
  1528. // Act
  1529. await route.RouteAsync(context);
  1530. // Assert
  1531. Assert.NotNull(nestedValues);
  1532. Assert.Equal("examplecategory", nestedValues["category2"]);
  1533. Assert.Equal("exampleproduct", nestedValues["product"]);
  1534. Assert.DoesNotContain(nestedValues, kvp => kvp.Key == "category1");
  1535. }
  1536. [Fact]
  1537. public void TreeRouter_GenerateLink_MatchesNullRequiredValue_WithNullRequestValueString()
  1538. {
  1539. // Arrange
  1540. var builder = CreateBuilder();
  1541. var entry = MapOutboundEntry(
  1542. builder,
  1543. "Help/Store",
  1544. requiredValues: new { area = (string)null, action = "Edit", controller = "Store" });
  1545. var route = builder.Build();
  1546. var context = CreateVirtualPathContext(new { area = (string)null, action = "Edit", controller = "Store" });
  1547. // Act
  1548. var pathData = route.GetVirtualPath(context);
  1549. // Assert
  1550. Assert.NotNull(pathData);
  1551. Assert.Equal("/Help/Store", pathData.VirtualPath);
  1552. Assert.Same(route, pathData.Router);
  1553. Assert.Empty(pathData.DataTokens);
  1554. }
  1555. [Fact]
  1556. public void TreeRouter_GenerateLink_MatchesNullRequiredValue_WithEmptyRequestValueString()
  1557. {
  1558. // Arrange
  1559. var builder = CreateBuilder();
  1560. var entry = MapOutboundEntry(
  1561. builder,
  1562. "Help/Store",
  1563. requiredValues: new { area = (string)null, action = "Edit", controller = "Store" });
  1564. var route = builder.Build();
  1565. var context = CreateVirtualPathContext(new { area = "", action = "Edit", controller = "Store" });
  1566. // Act
  1567. var pathData = route.GetVirtualPath(context);
  1568. // Assert
  1569. Assert.NotNull(pathData);
  1570. Assert.Equal("/Help/Store", pathData.VirtualPath);
  1571. Assert.Same(route, pathData.Router);
  1572. Assert.Empty(pathData.DataTokens);
  1573. }
  1574. [Fact]
  1575. public void TreeRouter_GenerateLink_MatchesEmptyStringRequiredValue_WithNullRequestValueString()
  1576. {
  1577. // Arrange
  1578. var builder = CreateBuilder();
  1579. var entry = MapOutboundEntry(
  1580. builder,
  1581. "Help/Store",
  1582. requiredValues: new { foo = "", action = "Edit", controller = "Store" });
  1583. var route = builder.Build();
  1584. var context = CreateVirtualPathContext(new { foo = (string)null, action = "Edit", controller = "Store" });
  1585. // Act
  1586. var pathData = route.GetVirtualPath(context);
  1587. // Assert
  1588. Assert.NotNull(pathData);
  1589. Assert.Equal("/Help/Store", pathData.VirtualPath);
  1590. Assert.Same(route, pathData.Router);
  1591. Assert.Empty(pathData.DataTokens);
  1592. }
  1593. [Fact]
  1594. public void TreeRouter_GenerateLink_MatchesEmptyStringRequiredValue_WithEmptyRequestValueString()
  1595. {
  1596. // Arrange
  1597. var builder = CreateBuilder();
  1598. var entry = MapOutboundEntry(
  1599. builder,
  1600. "Help/Store",
  1601. requiredValues: new { foo = "", action = "Edit", controller = "Store" });
  1602. var route = builder.Build();
  1603. var context = CreateVirtualPathContext(new { foo = "", action = "Edit", controller = "Store" });
  1604. // Act
  1605. var pathData = route.GetVirtualPath(context);
  1606. // Assert
  1607. Assert.NotNull(pathData);
  1608. Assert.Equal("/Help/Store", pathData.VirtualPath);
  1609. Assert.Same(route, pathData.Router);
  1610. Assert.Empty(pathData.DataTokens);
  1611. }
  1612. private static RouteContext CreateRouteContext(string requestPath)
  1613. {
  1614. var request = new Mock<HttpRequest>(MockBehavior.Strict);
  1615. request.SetupGet(r => r.Path).Returns(new PathString(requestPath));
  1616. var context = new Mock<HttpContext>(MockBehavior.Strict);
  1617. context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
  1618. .Returns(NullLoggerFactory.Instance);
  1619. context.SetupGet(c => c.Request).Returns(request.Object);
  1620. return new RouteContext(context.Object);
  1621. }
  1622. private static VirtualPathContext CreateVirtualPathContext(
  1623. object values,
  1624. object ambientValues = null,
  1625. string name = null)
  1626. {
  1627. var mockHttpContext = new Mock<HttpContext>();
  1628. mockHttpContext.Setup(h => h.RequestServices.GetService(typeof(ILoggerFactory)))
  1629. .Returns(NullLoggerFactory.Instance);
  1630. return new VirtualPathContext(
  1631. mockHttpContext.Object,
  1632. new RouteValueDictionary(ambientValues),
  1633. new RouteValueDictionary(values),
  1634. name);
  1635. }
  1636. private static InboundRouteEntry MapInboundEntry(
  1637. TreeRouteBuilder builder,
  1638. string template,
  1639. int order = 0,
  1640. IRouter handler = null)
  1641. {
  1642. var entry = builder.MapInbound(
  1643. handler ?? new StubRouter(),
  1644. TemplateParser.Parse(template),
  1645. routeName: null,
  1646. order: order);
  1647. // Add a generated 'route group' so we can identify later which entry matched.
  1648. entry.Defaults["test_route_group"] = CreateRouteGroup(order, template);
  1649. return entry;
  1650. }
  1651. private static OutboundRouteEntry MapOutboundEntry(
  1652. TreeRouteBuilder builder,
  1653. string template,
  1654. object requiredValues = null,
  1655. int order = 0,
  1656. string name = null,
  1657. IRouter handler = null)
  1658. {
  1659. var entry = builder.MapOutbound(
  1660. handler ?? new StubRouter(),
  1661. TemplateParser.Parse(template),
  1662. requiredLinkValues: new RouteValueDictionary(requiredValues),
  1663. routeName: name,
  1664. order: order);
  1665. // Add a generated 'route group' so we can identify later which entry matched.
  1666. entry.Defaults["test_route_group"] = CreateRouteGroup(order, template);
  1667. return entry;
  1668. }
  1669. private static string CreateRouteGroup(int order, string template)
  1670. {
  1671. return string.Format(CultureInfo.InvariantCulture, "{0}&{1}", order, template);
  1672. }
  1673. private static DefaultInlineConstraintResolver CreateConstraintResolver()
  1674. {
  1675. var options = new RouteOptions();
  1676. var optionsMock = new Mock<IOptions<RouteOptions>>();
  1677. optionsMock.SetupGet(o => o.Value).Returns(options);
  1678. return new DefaultInlineConstraintResolver(optionsMock.Object, new TestServiceProvider());
  1679. }
  1680. private static TreeRouteBuilder CreateBuilder()
  1681. {
  1682. var objectPoolProvider = new DefaultObjectPoolProvider();
  1683. var objectPolicy = new UriBuilderContextPooledObjectPolicy();
  1684. var objectPool = objectPoolProvider.Create<UriBuildingContext>(objectPolicy);
  1685. var constraintResolver = CreateConstraintResolver();
  1686. var builder = new TreeRouteBuilder(
  1687. NullLoggerFactory.Instance,
  1688. objectPool,
  1689. constraintResolver);
  1690. return builder;
  1691. }
  1692. private static TreeRouter CreateTreeRouter(
  1693. string firstTemplate,
  1694. string secondTemplate)
  1695. {
  1696. var builder = CreateBuilder();
  1697. MapOutboundEntry(builder, firstTemplate);
  1698. MapOutboundEntry(builder, secondTemplate);
  1699. return builder.Build();
  1700. }
  1701. private class StubRouter : IRouter
  1702. {
  1703. public VirtualPathContext GenerationContext { get; set; }
  1704. public RouteContext MatchingContext { get; set; }
  1705. public Func<RouteContext, bool> MatchingDelegate { get; set; }
  1706. public VirtualPathData GetVirtualPath(VirtualPathContext context)
  1707. {
  1708. GenerationContext = context;
  1709. return null;
  1710. }
  1711. public Task RouteAsync(RouteContext context)
  1712. {
  1713. if (MatchingDelegate == null)
  1714. {
  1715. context.Handler = NullHandler;
  1716. }
  1717. else
  1718. {
  1719. context.Handler = MatchingDelegate(context) ? NullHandler : null;
  1720. }
  1721. return Task.FromResult(true);
  1722. }
  1723. }
  1724. }