Razor 705 B

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155
  1. commit 771a7e35a41bc5d884fa5a71dfbffe13f8adf36b
  2. Author: N. Taylor Mullen <[email protected]>
  3. Date: Mon Jan 22 18:10:21 2018 -0800
  4. Add MVC support for RazorProjectEngine.
  5. - Make `RazorProjectEngine` call paths for all feature registrations.
  6. - Add `DefaultMvcImportFeature` for latest and 1.X MVC.
  7. - Ported `AddTargetExtension` and `AddDirective` to `RazorProjectEngineBuilderExtensions`.
  8. - Added tests and a test file system project type.
  9. - Moved obsolete `IRazorEngineBuilder` methods to the bottom of each file. Will actually obsolete the methods once `RazorProjectEngine` is working end-to-end.
  10. #1828
  11. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/DefaultMvcImportFeature.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/DefaultMvcImportFeature.cs
  12. new file mode 100644
  13. index 00000000000..d630a70c72f
  14. --- /dev/null
  15. +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/DefaultMvcImportFeature.cs
  16. @@ -0,0 +1,84 @@
  17. +// Copyright (c) .NET Foundation. All rights reserved.
  18. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  19. +
  20. +using System;
  21. +using System.Collections.Generic;
  22. +using System.IO;
  23. +using System.Linq;
  24. +using System.Text;
  25. +using Microsoft.AspNetCore.Razor.Language;
  26. +
  27. +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
  28. +{
  29. + internal class DefaultMvcImportFeature : RazorProjectEngineFeatureBase, IRazorImportFeature
  30. + {
  31. + private const string ImportsFileName = "_ViewImports.cshtml";
  32. +
  33. + public IReadOnlyList<RazorSourceDocument> GetImports(string sourceFilePath)
  34. + {
  35. + if (string.IsNullOrEmpty(sourceFilePath))
  36. + {
  37. + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpy, nameof(sourceFilePath));
  38. + }
  39. +
  40. + var imports = new List<RazorSourceDocument>();
  41. + AddDefaultDirectivesImport(imports);
  42. +
  43. + // We add hierarchical imports second so any default directive imports can be overridden.
  44. + AddHierarchicalImports(sourceFilePath, imports);
  45. +
  46. + return imports;
  47. + }
  48. +
  49. + // Internal for testing
  50. + internal static void AddDefaultDirectivesImport(List<RazorSourceDocument> imports)
  51. + {
  52. + using (var stream = new MemoryStream())
  53. + using (var writer = new StreamWriter(stream, Encoding.UTF8))
  54. + {
  55. + writer.WriteLine("@using System");
  56. + writer.WriteLine("@using System.Collections.Generic");
  57. + writer.WriteLine("@using System.Linq");
  58. + writer.WriteLine("@using System.Threading.Tasks");
  59. + writer.WriteLine("@using Microsoft.AspNetCore.Mvc");
  60. + writer.WriteLine("@using Microsoft.AspNetCore.Mvc.Rendering");
  61. + writer.WriteLine("@using Microsoft.AspNetCore.Mvc.ViewFeatures");
  62. + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel> Html");
  63. + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json");
  64. + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component");
  65. + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.IUrlHelper Url");
  66. + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider");
  67. + writer.WriteLine("@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor");
  68. + writer.Flush();
  69. +
  70. + stream.Position = 0;
  71. + var defaultMvcImports = RazorSourceDocument.ReadFrom(stream, fileName: null, encoding: Encoding.UTF8);
  72. + imports.Add(defaultMvcImports);
  73. + }
  74. + }
  75. +
  76. + // Internal for testing
  77. + internal void AddHierarchicalImports(string sourceFilePath, List<RazorSourceDocument> imports)
  78. + {
  79. + // We want items in descending order. FindHierarchicalItems returns items in ascending order.
  80. + var importProjectItems = ProjectEngine.FileSystem.FindHierarchicalItems(sourceFilePath, ImportsFileName).Reverse();
  81. + foreach (var importProjectItem in importProjectItems)
  82. + {
  83. + RazorSourceDocument importSourceDocument;
  84. +
  85. + if (importProjectItem.Exists)
  86. + {
  87. + importSourceDocument = RazorSourceDocument.ReadFrom(importProjectItem);
  88. + }
  89. + else
  90. + {
  91. + // File doesn't exist on disk so just add a marker source document as an identifier for "there could be something here".
  92. + var sourceDocumentProperties = new RazorSourceDocumentProperties(importProjectItem.FilePath, importProjectItem.RelativePhysicalPath);
  93. + importSourceDocument = RazorSourceDocument.Create(string.Empty, sourceDocumentProperties);
  94. + }
  95. +
  96. + imports.Add(importSourceDocument);
  97. + }
  98. + }
  99. + }
  100. +}
  101. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/InjectDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/InjectDirective.cs
  102. index c5e84d3ede1..c1c348f8a96 100644
  103. --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/InjectDirective.cs
  104. +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/InjectDirective.cs
  105. @@ -24,8 +24,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
  106. builder.Description = Resources.InjectDirective_Description;
  107. });
  108. - public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
  109. + public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder)
  110. {
  111. + if (builder == null)
  112. + {
  113. + throw new ArgumentNullException(nameof(builder));
  114. + }
  115. +
  116. builder.AddDirective(Directive);
  117. builder.Features.Add(new Pass());
  118. builder.AddTargetExtension(new InjectTargetExtension());
  119. @@ -99,5 +104,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
  120. }
  121. }
  122. }
  123. +
  124. + #region Obsolete
  125. + public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
  126. + {
  127. + if (builder == null)
  128. + {
  129. + throw new ArgumentNullException(nameof(builder));
  130. + }
  131. +
  132. + builder.AddDirective(Directive);
  133. + builder.Features.Add(new Pass());
  134. + builder.AddTargetExtension(new InjectTargetExtension());
  135. + return builder;
  136. + }
  137. + #endregion
  138. }
  139. }
  140. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ModelDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ModelDirective.cs
  141. index 572d1b9ccb9..2b67228fae2 100644
  142. --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ModelDirective.cs
  143. +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ModelDirective.cs
  144. @@ -21,8 +21,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
  145. builder.Description = Resources.ModelDirective_Description;
  146. });
  147. - public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
  148. + public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder)
  149. {
  150. + if (builder == null)
  151. + {
  152. + throw new ArgumentNullException(nameof(builder));
  153. + }
  154. +
  155. builder.AddDirective(Directive);
  156. builder.Features.Add(new Pass(builder.DesignTime));
  157. return builder;
  158. @@ -128,5 +133,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
  159. }
  160. }
  161. }
  162. +
  163. + #region Obsolete
  164. + public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
  165. + {
  166. + if (builder == null)
  167. + {
  168. + throw new ArgumentNullException(nameof(builder));
  169. + }
  170. +
  171. + builder.AddDirective(Directive);
  172. + builder.Features.Add(new Pass(builder.DesignTime));
  173. + return builder;
  174. + }
  175. + #endregion
  176. }
  177. }
  178. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs
  179. index 341bb5d47a6..c9b0642ad50 100644
  180. --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs
  181. +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs
  182. @@ -9,8 +9,68 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
  183. {
  184. public static class RazorExtensions
  185. {
  186. + public static void Register(RazorProjectEngineBuilder builder)
  187. + {
  188. + if (builder == null)
  189. + {
  190. + throw new ArgumentNullException(nameof(builder));
  191. + }
  192. +
  193. + EnsureDesignTime(builder);
  194. +
  195. + InjectDirective.Register(builder);
  196. + ModelDirective.Register(builder);
  197. +
  198. + FunctionsDirective.Register(builder);
  199. + InheritsDirective.Register(builder);
  200. +
  201. + // Register section directive with the 1.x compatible target extension.
  202. + builder.AddDirective(SectionDirective.Directive);
  203. + builder.Features.Add(new SectionDirectivePass());
  204. + builder.AddTargetExtension(new LegacySectionTargetExtension());
  205. +
  206. + builder.AddTargetExtension(new TemplateTargetExtension()
  207. + {
  208. + TemplateTypeName = "global::Microsoft.AspNetCore.Mvc.Razor.HelperResult",
  209. + });
  210. +
  211. + builder.Features.Add(new ModelExpressionPass());
  212. + builder.Features.Add(new MvcViewDocumentClassifierPass());
  213. +
  214. + builder.SetImportFeature(new DefaultMvcImportFeature());
  215. + }
  216. +
  217. + public static void RegisterViewComponentTagHelpers(RazorProjectEngineBuilder builder)
  218. + {
  219. + if (builder == null)
  220. + {
  221. + throw new ArgumentNullException(nameof(builder));
  222. + }
  223. +
  224. + EnsureDesignTime(builder);
  225. +
  226. + builder.Features.Add(new ViewComponentTagHelperPass());
  227. + builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
  228. + }
  229. +
  230. + private static void EnsureDesignTime(RazorProjectEngineBuilder builder)
  231. + {
  232. + if (builder.DesignTime)
  233. + {
  234. + return;
  235. + }
  236. +
  237. + throw new NotSupportedException(Resources.RuntimeCodeGenerationNotSupported);
  238. + }
  239. +
  240. + #region Obsolete
  241. public static void Register(IRazorEngineBuilder builder)
  242. {
  243. + if (builder == null)
  244. + {
  245. + throw new ArgumentNullException(nameof(builder));
  246. + }
  247. +
  248. EnsureDesignTime(builder);
  249. InjectDirective.Register(builder);
  250. @@ -35,6 +95,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
  251. public static void RegisterViewComponentTagHelpers(IRazorEngineBuilder builder)
  252. {
  253. + if (builder == null)
  254. + {
  255. + throw new ArgumentNullException(nameof(builder));
  256. + }
  257. +
  258. EnsureDesignTime(builder);
  259. builder.Features.Add(new ViewComponentTagHelperPass());
  260. @@ -50,5 +115,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
  261. throw new NotSupportedException(Resources.RuntimeCodeGenerationNotSupported);
  262. }
  263. + #endregion
  264. }
  265. }
  266. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/DefaultMvcImportFeature.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/DefaultMvcImportFeature.cs
  267. new file mode 100644
  268. index 00000000000..8cbf0f11a15
  269. --- /dev/null
  270. +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/DefaultMvcImportFeature.cs
  271. @@ -0,0 +1,86 @@
  272. +// Copyright (c) .NET Foundation. All rights reserved.
  273. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  274. +
  275. +using System;
  276. +using System.Collections.Generic;
  277. +using System.IO;
  278. +using System.Linq;
  279. +using System.Text;
  280. +using Microsoft.AspNetCore.Razor.Language;
  281. +
  282. +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
  283. +{
  284. + internal class DefaultMvcImportFeature : RazorProjectEngineFeatureBase, IRazorImportFeature
  285. + {
  286. + private const string ImportsFileName = "_ViewImports.cshtml";
  287. +
  288. + public IReadOnlyList<RazorSourceDocument> GetImports(string sourceFilePath)
  289. + {
  290. + if (string.IsNullOrEmpty(sourceFilePath))
  291. + {
  292. + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpy, nameof(sourceFilePath));
  293. + }
  294. +
  295. + var imports = new List<RazorSourceDocument>();
  296. + AddDefaultDirectivesImport(imports);
  297. +
  298. + // We add hierarchical imports second so any default directive imports can be overridden.
  299. + AddHierarchicalImports(sourceFilePath, imports);
  300. +
  301. + return imports;
  302. + }
  303. +
  304. + // Internal for testing
  305. + internal static void AddDefaultDirectivesImport(List<RazorSourceDocument> imports)
  306. + {
  307. + using (var stream = new MemoryStream())
  308. + using (var writer = new StreamWriter(stream, Encoding.UTF8))
  309. + {
  310. + writer.WriteLine("@using System");
  311. + writer.WriteLine("@using System.Collections.Generic");
  312. + writer.WriteLine("@using System.Linq");
  313. + writer.WriteLine("@using System.Threading.Tasks");
  314. + writer.WriteLine("@using Microsoft.AspNetCore.Mvc");
  315. + writer.WriteLine("@using Microsoft.AspNetCore.Mvc.Rendering");
  316. + writer.WriteLine("@using Microsoft.AspNetCore.Mvc.ViewFeatures");
  317. + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel> Html");
  318. + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json");
  319. + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component");
  320. + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.IUrlHelper Url");
  321. + writer.WriteLine("@inject global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider");
  322. + writer.WriteLine("@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNetCore.Mvc.Razor");
  323. + writer.WriteLine("@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper, Microsoft.AspNetCore.Mvc.Razor");
  324. + writer.WriteLine("@addTagHelper Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper, Microsoft.AspNetCore.Mvc.Razor");
  325. + writer.Flush();
  326. +
  327. + stream.Position = 0;
  328. + var defaultMvcImports = RazorSourceDocument.ReadFrom(stream, fileName: null, encoding: Encoding.UTF8);
  329. + imports.Add(defaultMvcImports);
  330. + }
  331. + }
  332. +
  333. + // Internal for testing
  334. + internal void AddHierarchicalImports(string sourceFilePath, List<RazorSourceDocument> imports)
  335. + {
  336. + // We want items in descending order. FindHierarchicalItems returns items in ascending order.
  337. + var importProjectItems = ProjectEngine.FileSystem.FindHierarchicalItems(sourceFilePath, ImportsFileName).Reverse();
  338. + foreach (var importProjectItem in importProjectItems)
  339. + {
  340. + RazorSourceDocument importSourceDocument;
  341. +
  342. + if (importProjectItem.Exists)
  343. + {
  344. + importSourceDocument = RazorSourceDocument.ReadFrom(importProjectItem);
  345. + }
  346. + else
  347. + {
  348. + // File doesn't exist on disk so just add a marker source document as an identifier for "there could be something here".
  349. + var sourceDocumentProperties = new RazorSourceDocumentProperties(importProjectItem.FilePath, importProjectItem.RelativePhysicalPath);
  350. + importSourceDocument = RazorSourceDocument.Create(string.Empty, sourceDocumentProperties);
  351. + }
  352. +
  353. + imports.Add(importSourceDocument);
  354. + }
  355. + }
  356. + }
  357. +}
  358. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs
  359. index 81f1d5b8341..41bb56b57d8 100644
  360. --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs
  361. +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InjectDirective.cs
  362. @@ -24,8 +24,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
  363. builder.Description = Resources.InjectDirective_Description;
  364. });
  365. - public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
  366. + public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder)
  367. {
  368. + if (builder == null)
  369. + {
  370. + throw new ArgumentNullException(nameof(builder));
  371. + }
  372. +
  373. builder.AddDirective(Directive);
  374. builder.Features.Add(new Pass());
  375. builder.AddTargetExtension(new InjectTargetExtension());
  376. @@ -99,5 +104,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
  377. }
  378. }
  379. }
  380. +
  381. + #region Obsolete
  382. + public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
  383. + {
  384. + if (builder == null)
  385. + {
  386. + throw new ArgumentNullException(nameof(builder));
  387. + }
  388. +
  389. + builder.AddDirective(Directive);
  390. + builder.Features.Add(new Pass());
  391. + builder.AddTargetExtension(new InjectTargetExtension());
  392. + return builder;
  393. + }
  394. + #endregion
  395. }
  396. }
  397. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs
  398. index 17d98d72b44..a5d77b401e8 100644
  399. --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs
  400. +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelDirective.cs
  401. @@ -21,8 +21,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
  402. builder.Description = Resources.ModelDirective_Description;
  403. });
  404. - public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
  405. + public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder)
  406. {
  407. + if (builder == null)
  408. + {
  409. + throw new ArgumentNullException(nameof(builder));
  410. + }
  411. +
  412. builder.AddDirective(Directive);
  413. builder.Features.Add(new Pass(builder.DesignTime));
  414. return builder;
  415. @@ -135,5 +140,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
  416. }
  417. }
  418. }
  419. +
  420. + #region Obsolete
  421. + public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
  422. + {
  423. + if (builder == null)
  424. + {
  425. + throw new ArgumentNullException(nameof(builder));
  426. + }
  427. +
  428. + builder.AddDirective(Directive);
  429. + builder.Features.Add(new Pass(builder.DesignTime));
  430. + return builder;
  431. + }
  432. + #endregion
  433. }
  434. }
  435. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs
  436. index dec8c7894cd..b0c197f42b5 100644
  437. --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs
  438. +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/NamespaceDirective.cs
  439. @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
  440. builder.Description = Resources.NamespaceDirective_Description;
  441. });
  442. - public static void Register(IRazorEngineBuilder builder)
  443. + public static void Register(RazorProjectEngineBuilder builder)
  444. {
  445. if (builder == null)
  446. {
  447. @@ -186,5 +186,18 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
  448. base.VisitDirective(node);
  449. }
  450. }
  451. +
  452. + #region Obsolete
  453. + public static void Register(IRazorEngineBuilder builder)
  454. + {
  455. + if (builder == null)
  456. + {
  457. + throw new ArgumentNullException();
  458. + }
  459. +
  460. + builder.AddDirective(Directive);
  461. + builder.Features.Add(new Pass());
  462. + }
  463. + #endregion
  464. }
  465. }
  466. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs
  467. index 5f617a1ebd7..cd3624f4350 100644
  468. --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs
  469. +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/PageDirective.cs
  470. @@ -32,8 +32,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
  471. public IntermediateNode DirectiveNode { get; }
  472. - public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
  473. + public static RazorProjectEngineBuilder Register(RazorProjectEngineBuilder builder)
  474. {
  475. + if (builder == null)
  476. + {
  477. + throw new ArgumentNullException(nameof(builder));
  478. + }
  479. +
  480. builder.AddDirective(Directive);
  481. return builder;
  482. }
  483. @@ -98,5 +103,18 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
  484. }
  485. }
  486. }
  487. +
  488. + #region Obsolete
  489. + public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
  490. + {
  491. + if (builder == null)
  492. + {
  493. + throw new ArgumentNullException(nameof(builder));
  494. + }
  495. +
  496. + builder.AddDirective(Directive);
  497. + return builder;
  498. + }
  499. + #endregion
  500. }
  501. }
  502. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs
  503. index fc1ad0c1624..19aa2ced3c4 100644
  504. --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs
  505. +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs
  506. @@ -1,6 +1,7 @@
  507. // Copyright (c) .NET Foundation. All rights reserved.
  508. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  509. +using System;
  510. using Microsoft.AspNetCore.Razor.Language;
  511. using Microsoft.AspNetCore.Razor.Language.Extensions;
  512. @@ -8,8 +9,51 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
  513. {
  514. public static class RazorExtensions
  515. {
  516. + public static void Register(RazorProjectEngineBuilder builder)
  517. + {
  518. + if (builder == null)
  519. + {
  520. + throw new ArgumentNullException(nameof(builder));
  521. + }
  522. +
  523. + InjectDirective.Register(builder);
  524. + ModelDirective.Register(builder);
  525. + NamespaceDirective.Register(builder);
  526. + PageDirective.Register(builder);
  527. +
  528. + FunctionsDirective.Register(builder);
  529. + InheritsDirective.Register(builder);
  530. + SectionDirective.Register(builder);
  531. +
  532. + builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
  533. + builder.AddTargetExtension(new TemplateTargetExtension()
  534. + {
  535. + TemplateTypeName = "global::Microsoft.AspNetCore.Mvc.Razor.HelperResult",
  536. + });
  537. +
  538. + builder.Features.Add(new ModelExpressionPass());
  539. + builder.Features.Add(new PagesPropertyInjectionPass());
  540. + builder.Features.Add(new ViewComponentTagHelperPass());
  541. + builder.Features.Add(new RazorPageDocumentClassifierPass());
  542. + builder.Features.Add(new MvcViewDocumentClassifierPass());
  543. +
  544. + if (!builder.DesignTime)
  545. + {
  546. + builder.Features.Add(new AssemblyAttributeInjectionPass());
  547. + builder.Features.Add(new InstrumentationPass());
  548. + }
  549. +
  550. + builder.SetImportFeature(new DefaultMvcImportFeature());
  551. + }
  552. +
  553. + #region Obsolete
  554. public static void Register(IRazorEngineBuilder builder)
  555. {
  556. + if (builder == null)
  557. + {
  558. + throw new ArgumentNullException(nameof(builder));
  559. + }
  560. +
  561. InjectDirective.Register(builder);
  562. ModelDirective.Register(builder);
  563. NamespaceDirective.Register(builder);
  564. @@ -37,5 +81,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
  565. builder.Features.Add(new InstrumentationPass());
  566. }
  567. }
  568. + #endregion
  569. }
  570. }
  571. diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorImportFeature.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorImportFeature.cs
  572. index ed25827c629..3d5c015a263 100644
  573. --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorImportFeature.cs
  574. +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorImportFeature.cs
  575. @@ -6,10 +6,8 @@ using System.Collections.Generic;
  576. namespace Microsoft.AspNetCore.Razor.Language
  577. {
  578. - internal class DefaultRazorImportFeature : IRazorImportFeature
  579. + internal class DefaultRazorImportFeature : RazorProjectEngineFeatureBase, IRazorImportFeature
  580. {
  581. - public RazorProjectEngine ProjectEngine { get; set; }
  582. -
  583. public IReadOnlyList<RazorSourceDocument> GetImports(string sourceFilePath) => Array.Empty<RazorSourceDocument>();
  584. }
  585. }
  586. diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/FunctionsDirective.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/FunctionsDirective.cs
  587. index f9033b8a83c..0adb20aefba 100644
  588. --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/FunctionsDirective.cs
  589. +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/FunctionsDirective.cs
  590. @@ -1,6 +1,7 @@
  591. // Copyright (c) .NET Foundation. All rights reserved.
  592. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  593. +using System;
  594. using Microsoft.AspNetCore.Razor.Language.Legacy;
  595. namespace Microsoft.AspNetCore.Razor.Language.Extensions
  596. @@ -15,10 +16,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
  597. builder.Description = Resources.FunctionsDirective_Description;
  598. });
  599. + public static void Register(RazorProjectEngineBuilder builder)
  600. + {
  601. + if (builder == null)
  602. + {
  603. + throw new ArgumentNullException(nameof(builder));
  604. + }
  605. +
  606. + builder.AddDirective(Directive);
  607. + builder.Features.Add(new FunctionsDirectivePass());
  608. + }
  609. +
  610. + #region Obsolete
  611. public static void Register(IRazorEngineBuilder builder)
  612. {
  613. + if (builder == null)
  614. + {
  615. + throw new ArgumentNullException(nameof(builder));
  616. + }
  617. +
  618. builder.AddDirective(Directive);
  619. builder.Features.Add(new FunctionsDirectivePass());
  620. }
  621. + #endregion
  622. }
  623. }
  624. diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/InheritsDirective.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/InheritsDirective.cs
  625. index 289b5d853c2..6cef321c73c 100644
  626. --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/InheritsDirective.cs
  627. +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/InheritsDirective.cs
  628. @@ -1,6 +1,7 @@
  629. // Copyright (c) .NET Foundation. All rights reserved.
  630. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  631. +using System;
  632. using Microsoft.AspNetCore.Razor.Language.Legacy;
  633. namespace Microsoft.AspNetCore.Razor.Language.Extensions
  634. @@ -17,10 +18,28 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
  635. builder.Description = Resources.InheritsDirective_Description;
  636. });
  637. + public static void Register(RazorProjectEngineBuilder builder)
  638. + {
  639. + if (builder == null)
  640. + {
  641. + throw new ArgumentNullException(nameof(builder));
  642. + }
  643. +
  644. + builder.AddDirective(Directive);
  645. + builder.Features.Add(new InheritsDirectivePass());
  646. + }
  647. +
  648. + #region Obsolete
  649. public static void Register(IRazorEngineBuilder builder)
  650. {
  651. + if (builder == null)
  652. + {
  653. + throw new ArgumentNullException(nameof(builder));
  654. + }
  655. +
  656. builder.AddDirective(Directive);
  657. builder.Features.Add(new InheritsDirectivePass());
  658. }
  659. + #endregion
  660. }
  661. }
  662. diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/SectionDirective.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/SectionDirective.cs
  663. index 1542525a700..14c461d08ff 100644
  664. --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/SectionDirective.cs
  665. +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/SectionDirective.cs
  666. @@ -1,6 +1,7 @@
  667. // Copyright (c) .NET Foundation. All rights reserved.
  668. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  669. +using System;
  670. using Microsoft.AspNetCore.Razor.Language.Legacy;
  671. namespace Microsoft.AspNetCore.Razor.Language.Extensions
  672. @@ -16,11 +17,30 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
  673. builder.Description = Resources.SectionDirective_Description;
  674. });
  675. + public static void Register(RazorProjectEngineBuilder builder)
  676. + {
  677. + if (builder == null)
  678. + {
  679. + throw new ArgumentNullException(nameof(builder));
  680. + }
  681. +
  682. + builder.AddDirective(Directive);
  683. + builder.Features.Add(new SectionDirectivePass());
  684. + builder.AddTargetExtension(new SectionTargetExtension());
  685. + }
  686. +
  687. + #region Obsolete
  688. public static void Register(IRazorEngineBuilder builder)
  689. {
  690. + if (builder == null)
  691. + {
  692. + throw new ArgumentNullException(nameof(builder));
  693. + }
  694. +
  695. builder.AddDirective(Directive);
  696. builder.Features.Add(new SectionDirectivePass());
  697. builder.AddTargetExtension(new SectionTargetExtension());
  698. }
  699. + #endregion
  700. }
  701. }
  702. diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilderExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilderExtensions.cs
  703. index 7001e288a5f..8bd17e0dd67 100644
  704. --- a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilderExtensions.cs
  705. +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineBuilderExtensions.cs
  706. @@ -3,6 +3,7 @@
  707. using System;
  708. using System.Linq;
  709. +using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
  710. namespace Microsoft.AspNetCore.Razor.Language
  711. {
  712. @@ -29,5 +30,77 @@ namespace Microsoft.AspNetCore.Razor.Language
  713. builder.Features.Add(feature);
  714. }
  715. +
  716. + /// <summary>
  717. + /// Adds the specified <see cref="ICodeTargetExtension"/>.
  718. + /// </summary>
  719. + /// <param name="builder">The <see cref="RazorProjectEngineBuilder"/>.</param>
  720. + /// <param name="extension">The <see cref="ICodeTargetExtension"/> to add.</param>
  721. + /// <returns>The <see cref="RazorProjectEngineBuilder"/>.</returns>
  722. + public static RazorProjectEngineBuilder AddTargetExtension(this RazorProjectEngineBuilder builder, ICodeTargetExtension extension)
  723. + {
  724. + if (builder == null)
  725. + {
  726. + throw new ArgumentNullException(nameof(builder));
  727. + }
  728. +
  729. + if (extension == null)
  730. + {
  731. + throw new ArgumentNullException(nameof(extension));
  732. + }
  733. +
  734. + var targetExtensionFeature = GetTargetExtensionFeature(builder);
  735. + targetExtensionFeature.TargetExtensions.Add(extension);
  736. +
  737. + return builder;
  738. + }
  739. +
  740. + /// <summary>
  741. + /// Adds the specified <see cref="DirectiveDescriptor"/>.
  742. + /// </summary>
  743. + /// <param name="builder">The <see cref="RazorProjectEngineBuilder"/>.</param>
  744. + /// <param name="directive">The <see cref="DirectiveDescriptor"/> to add.</param>
  745. + /// <returns>The <see cref="RazorProjectEngineBuilder"/>.</returns>
  746. + public static RazorProjectEngineBuilder AddDirective(this RazorProjectEngineBuilder builder, DirectiveDescriptor directive)
  747. + {
  748. + if (builder == null)
  749. + {
  750. + throw new ArgumentNullException(nameof(builder));
  751. + }
  752. +
  753. + if (directive == null)
  754. + {
  755. + throw new ArgumentNullException(nameof(directive));
  756. + }
  757. +
  758. + var directiveFeature = GetDirectiveFeature(builder);
  759. + directiveFeature.Directives.Add(directive);
  760. +
  761. + return builder;
  762. + }
  763. +
  764. + private static IRazorDirectiveFeature GetDirectiveFeature(RazorProjectEngineBuilder builder)
  765. + {
  766. + var directiveFeature = builder.Features.OfType<IRazorDirectiveFeature>().FirstOrDefault();
  767. + if (directiveFeature == null)
  768. + {
  769. + directiveFeature = new DefaultRazorDirectiveFeature();
  770. + builder.Features.Add(directiveFeature);
  771. + }
  772. +
  773. + return directiveFeature;
  774. + }
  775. +
  776. + private static IRazorTargetExtensionFeature GetTargetExtensionFeature(RazorProjectEngineBuilder builder)
  777. + {
  778. + var targetExtensionFeature = builder.Features.OfType<IRazorTargetExtensionFeature>().FirstOrDefault();
  779. + if (targetExtensionFeature == null)
  780. + {
  781. + targetExtensionFeature = new DefaultRazorTargetExtensionFeature();
  782. + builder.Features.Add(targetExtensionFeature);
  783. + }
  784. +
  785. + return targetExtensionFeature;
  786. + }
  787. }
  788. }
  789. diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineFeatureBase.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineFeatureBase.cs
  790. new file mode 100644
  791. index 00000000000..f5944a83ffc
  792. --- /dev/null
  793. +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorProjectEngineFeatureBase.cs
  794. @@ -0,0 +1,31 @@
  795. +// Copyright (c) .NET Foundation. All rights reserved.
  796. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  797. +
  798. +using System;
  799. +
  800. +namespace Microsoft.AspNetCore.Razor.Language
  801. +{
  802. + public abstract class RazorProjectEngineFeatureBase : IRazorProjectEngineFeature
  803. + {
  804. + private RazorProjectEngine _projectEngine;
  805. +
  806. + public RazorProjectEngine ProjectEngine
  807. + {
  808. + get => _projectEngine;
  809. + set
  810. + {
  811. + if (value == null)
  812. + {
  813. + throw new ArgumentNullException(nameof(value));
  814. + }
  815. +
  816. + _projectEngine = value;
  817. + OnInitialized();
  818. + }
  819. + }
  820. +
  821. + protected virtual void OnInitialized()
  822. + {
  823. + }
  824. + }
  825. +}
  826. diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/DefaultMvcImportFeatureTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/DefaultMvcImportFeatureTest.cs
  827. new file mode 100644
  828. index 00000000000..32e254a45bd
  829. --- /dev/null
  830. +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/DefaultMvcImportFeatureTest.cs
  831. @@ -0,0 +1,77 @@
  832. +// Copyright (c) .NET Foundation. All rights reserved.
  833. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  834. +
  835. +using System.Collections.Generic;
  836. +using Microsoft.AspNetCore.Razor.Language;
  837. +using Moq;
  838. +using Xunit;
  839. +
  840. +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
  841. +{
  842. + public class DefaultMvcImportFeatureTest
  843. + {
  844. + [Fact]
  845. + public void AddDefaultDirectivesImport_AddsSingleDynamicImport()
  846. + {
  847. + // Arrange
  848. + var imports = new List<RazorSourceDocument>();
  849. +
  850. + // Act
  851. + DefaultMvcImportFeature.AddDefaultDirectivesImport(imports);
  852. +
  853. + // Assert
  854. + var import = Assert.Single(imports);
  855. + Assert.Null(import.FilePath);
  856. + }
  857. +
  858. + [Fact]
  859. + public void AddHierarchicalImports_AddsViewImportSourceDocumentsOnDisk()
  860. + {
  861. + // Arrange
  862. + var imports = new List<RazorSourceDocument>();
  863. + var testFileSystem = new TestRazorProjectFileSystem(new[]
  864. + {
  865. + new TestRazorProjectItem("/Index.cshtml"),
  866. + new TestRazorProjectItem("/_ViewImports.cshtml"),
  867. + new TestRazorProjectItem("/Contact/_ViewImports.cshtml"),
  868. + new TestRazorProjectItem("/Contact/Index.cshtml"),
  869. + });
  870. + var mvcImportFeature = new DefaultMvcImportFeature()
  871. + {
  872. + ProjectEngine = Mock.Of<RazorProjectEngine>(projectEngine => projectEngine.FileSystem == testFileSystem)
  873. + };
  874. +
  875. + // Act
  876. + mvcImportFeature.AddHierarchicalImports("/Contact/Index.cshtml", imports);
  877. +
  878. + // Assert
  879. + Assert.Collection(imports,
  880. + import => Assert.Equal("/_ViewImports.cshtml", import.FilePath),
  881. + import => Assert.Equal("/Contact/_ViewImports.cshtml", import.FilePath));
  882. + }
  883. +
  884. + [Fact]
  885. + public void AddHierarchicalImports_AddsViewImportSourceDocumentsNotOnDisk()
  886. + {
  887. + // Arrange
  888. + var imports = new List<RazorSourceDocument>();
  889. + var testFileSystem = new TestRazorProjectFileSystem(new[]
  890. + {
  891. + new TestRazorProjectItem("/Pages/Contact/Index.cshtml"),
  892. + });
  893. + var mvcImportFeature = new DefaultMvcImportFeature()
  894. + {
  895. + ProjectEngine = Mock.Of<RazorProjectEngine>(projectEngine => projectEngine.FileSystem == testFileSystem)
  896. + };
  897. +
  898. + // Act
  899. + mvcImportFeature.AddHierarchicalImports("/Pages/Contact/Index.cshtml", imports);
  900. +
  901. + // Assert
  902. + Assert.Collection(imports,
  903. + import => Assert.Equal("/_ViewImports.cshtml", import.FilePath),
  904. + import => Assert.Equal("/Pages/_ViewImports.cshtml", import.FilePath),
  905. + import => Assert.Equal("/Pages/Contact/_ViewImports.cshtml", import.FilePath));
  906. + }
  907. + }
  908. +}
  909. diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/DefaultMvcImportFeatureTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/DefaultMvcImportFeatureTest.cs
  910. new file mode 100644
  911. index 00000000000..31d66517a1a
  912. --- /dev/null
  913. +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test/DefaultMvcImportFeatureTest.cs
  914. @@ -0,0 +1,77 @@
  915. +// Copyright (c) .NET Foundation. All rights reserved.
  916. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  917. +
  918. +using System.Collections.Generic;
  919. +using Microsoft.AspNetCore.Razor.Language;
  920. +using Moq;
  921. +using Xunit;
  922. +
  923. +namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
  924. +{
  925. + public class DefaultMvcImportFeatureTest
  926. + {
  927. + [Fact]
  928. + public void AddDefaultDirectivesImport_AddsSingleDynamicImport()
  929. + {
  930. + // Arrange
  931. + var imports = new List<RazorSourceDocument>();
  932. +
  933. + // Act
  934. + DefaultMvcImportFeature.AddDefaultDirectivesImport(imports);
  935. +
  936. + // Assert
  937. + var import = Assert.Single(imports);
  938. + Assert.Null(import.FilePath);
  939. + }
  940. +
  941. + [Fact]
  942. + public void AddHierarchicalImports_AddsViewImportSourceDocumentsOnDisk()
  943. + {
  944. + // Arrange
  945. + var imports = new List<RazorSourceDocument>();
  946. + var testFileSystem = new TestRazorProjectFileSystem(new[]
  947. + {
  948. + new TestRazorProjectItem("/Index.cshtml"),
  949. + new TestRazorProjectItem("/_ViewImports.cshtml"),
  950. + new TestRazorProjectItem("/Contact/_ViewImports.cshtml"),
  951. + new TestRazorProjectItem("/Contact/Index.cshtml"),
  952. + });
  953. + var mvcImportFeature = new DefaultMvcImportFeature()
  954. + {
  955. + ProjectEngine = Mock.Of<RazorProjectEngine>(projectEngine => projectEngine.FileSystem == testFileSystem)
  956. + };
  957. +
  958. + // Act
  959. + mvcImportFeature.AddHierarchicalImports("/Contact/Index.cshtml", imports);
  960. +
  961. + // Assert
  962. + Assert.Collection(imports,
  963. + import => Assert.Equal("/_ViewImports.cshtml", import.FilePath),
  964. + import => Assert.Equal("/Contact/_ViewImports.cshtml", import.FilePath));
  965. + }
  966. +
  967. + [Fact]
  968. + public void AddHierarchicalImports_AddsViewImportSourceDocumentsNotOnDisk()
  969. + {
  970. + // Arrange
  971. + var imports = new List<RazorSourceDocument>();
  972. + var testFileSystem = new TestRazorProjectFileSystem(new[]
  973. + {
  974. + new TestRazorProjectItem("/Pages/Contact/Index.cshtml"),
  975. + });
  976. + var mvcImportFeature = new DefaultMvcImportFeature()
  977. + {
  978. + ProjectEngine = Mock.Of<RazorProjectEngine>(projectEngine => projectEngine.FileSystem == testFileSystem)
  979. + };
  980. +
  981. + // Act
  982. + mvcImportFeature.AddHierarchicalImports("/Pages/Contact/Index.cshtml", imports);
  983. +
  984. + // Assert
  985. + Assert.Collection(imports,
  986. + import => Assert.Equal("/_ViewImports.cshtml", import.FilePath),
  987. + import => Assert.Equal("/Pages/_ViewImports.cshtml", import.FilePath),
  988. + import => Assert.Equal("/Pages/Contact/_ViewImports.cshtml", import.FilePath));
  989. + }
  990. + }
  991. +}
  992. diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineBuilderExtensionsTest.cs
  993. index 44f93a970a1..d9c0630005d 100644
  994. --- a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineBuilderExtensionsTest.cs
  995. +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineBuilderExtensionsTest.cs
  996. @@ -1,6 +1,7 @@
  997. // Copyright (c) .NET Foundation. All rights reserved.
  998. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  999. +using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
  1000. using Moq;
  1001. using Xunit;
  1002. @@ -26,5 +27,77 @@ namespace Microsoft.AspNetCore.Razor.Language
  1003. var feature = Assert.Single(builder.Features);
  1004. Assert.Same(newFeature, feature);
  1005. }
  1006. +
  1007. + [Fact]
  1008. + public void AddTargetExtension_CreatesAndAddsToTargetExtensionFeatureIfItDoesNotExist()
  1009. + {
  1010. + // Arrange
  1011. + var builder = new DefaultRazorProjectEngineBuilder(false, Mock.Of<RazorProjectFileSystem>());
  1012. + var expectedExtension = Mock.Of<ICodeTargetExtension>();
  1013. +
  1014. + // Act
  1015. + builder.AddTargetExtension(expectedExtension);
  1016. +
  1017. + // Assert
  1018. + var feature = Assert.Single(builder.Features);
  1019. + var codeTargetExtensionFeature = Assert.IsAssignableFrom<IRazorTargetExtensionFeature>(feature);
  1020. + var extensions = Assert.Single(codeTargetExtensionFeature.TargetExtensions);
  1021. + Assert.Same(expectedExtension, extensions);
  1022. + }
  1023. +
  1024. + [Fact]
  1025. + public void AddTargetExtension_UsesExistingFeatureIfExistsAndAddsTo()
  1026. + {
  1027. + // Arrange
  1028. + var builder = new DefaultRazorProjectEngineBuilder(false, Mock.Of<RazorProjectFileSystem>());
  1029. + var codeTargetExtensionFeature = new DefaultRazorTargetExtensionFeature();
  1030. + builder.Features.Add(codeTargetExtensionFeature);
  1031. + var expectedExtension = Mock.Of<ICodeTargetExtension>();
  1032. +
  1033. + // Act
  1034. + builder.AddTargetExtension(expectedExtension);
  1035. +
  1036. + // Assert
  1037. + var feature = Assert.Single(builder.Features);
  1038. + Assert.Same(codeTargetExtensionFeature, feature);
  1039. + var extensions = Assert.Single(codeTargetExtensionFeature.TargetExtensions);
  1040. + Assert.Same(expectedExtension, extensions);
  1041. + }
  1042. +
  1043. + [Fact]
  1044. + public void AddDirective_CreatesAndAddsToDirectiveFeatureIfItDoesNotExist()
  1045. + {
  1046. + // Arrange
  1047. + var builder = new DefaultRazorProjectEngineBuilder(false, Mock.Of<RazorProjectFileSystem>());
  1048. + var expectedDirective = Mock.Of<DirectiveDescriptor>();
  1049. +
  1050. + // Act
  1051. + builder.AddDirective(expectedDirective);
  1052. +
  1053. + // Assert
  1054. + var feature = Assert.Single(builder.Features);
  1055. + var directiveFeature = Assert.IsAssignableFrom<IRazorDirectiveFeature>(feature);
  1056. + var directive = Assert.Single(directiveFeature.Directives);
  1057. + Assert.Same(expectedDirective, directive);
  1058. + }
  1059. +
  1060. + [Fact]
  1061. + public void AddDirective_UsesExistingFeatureIfExistsAndAddsTo()
  1062. + {
  1063. + // Arrange
  1064. + var builder = new DefaultRazorProjectEngineBuilder(false, Mock.Of<RazorProjectFileSystem>());
  1065. + var directiveFeature = new DefaultRazorDirectiveFeature();
  1066. + builder.Features.Add(directiveFeature);
  1067. + var expecteDirective = Mock.Of<DirectiveDescriptor>();
  1068. +
  1069. + // Act
  1070. + builder.AddDirective(expecteDirective);
  1071. +
  1072. + // Assert
  1073. + var feature = Assert.Single(builder.Features);
  1074. + Assert.Same(directiveFeature, feature);
  1075. + var directive = Assert.Single(directiveFeature.Directives);
  1076. + Assert.Same(expecteDirective, directive);
  1077. + }
  1078. }
  1079. }
  1080. diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineFeatureBaseTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineFeatureBaseTest.cs
  1081. new file mode 100644
  1082. index 00000000000..8344e787c7d
  1083. --- /dev/null
  1084. +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/RazorProjectEngineFeatureBaseTest.cs
  1085. @@ -0,0 +1,34 @@
  1086. +// Copyright (c) .NET Foundation. All rights reserved.
  1087. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  1088. +
  1089. +using Moq;
  1090. +using Xunit;
  1091. +
  1092. +namespace Microsoft.AspNetCore.Razor.Language
  1093. +{
  1094. + public class RazorProjectEngineFeatureBaseTest
  1095. + {
  1096. + [Fact]
  1097. + public void ProjectEngineSetter_CallsOnInitialized()
  1098. + {
  1099. + // Arrange
  1100. + var testFeature = new TestFeature();
  1101. +
  1102. + // Act
  1103. + testFeature.ProjectEngine = Mock.Of<RazorProjectEngine>();
  1104. +
  1105. + // Assert
  1106. + Assert.Equal(1, testFeature.InitializationCount);
  1107. + }
  1108. +
  1109. + private class TestFeature : RazorProjectEngineFeatureBase
  1110. + {
  1111. + public int InitializationCount { get; private set; }
  1112. +
  1113. + protected override void OnInitialized()
  1114. + {
  1115. + InitializationCount++;
  1116. + }
  1117. + }
  1118. + }
  1119. +}
  1120. diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/TestRazorProjectFileSystem.cs b/test/Microsoft.AspNetCore.Razor.Test.Common/Language/TestRazorProjectFileSystem.cs
  1121. similarity index 100%
  1122. rename from test/Microsoft.AspNetCore.Razor.Language.Test/TestRazorProjectFileSystem.cs
  1123. rename to test/Microsoft.AspNetCore.Razor.Test.Common/Language/TestRazorProjectFileSystem.cs