ComponentRendererTest.cs 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149
  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;
  4. using System.IO;
  5. using System.Text;
  6. using System.Text.Encodings.Web;
  7. using System.Text.Json;
  8. using System.Text.RegularExpressions;
  9. using System.Threading.Tasks;
  10. using Microsoft.AspNetCore.Components;
  11. using Microsoft.AspNetCore.Components.Infrastructure;
  12. using Microsoft.AspNetCore.Components.Rendering;
  13. using Microsoft.AspNetCore.DataProtection;
  14. using Microsoft.AspNetCore.Http;
  15. using Microsoft.AspNetCore.Http.Features;
  16. using Microsoft.AspNetCore.Mvc.Rendering;
  17. using Microsoft.AspNetCore.Testing;
  18. using Microsoft.Extensions.DependencyInjection;
  19. using Microsoft.Extensions.DependencyInjection.Extensions;
  20. using Microsoft.Extensions.Logging;
  21. using Microsoft.Extensions.Logging.Abstractions;
  22. using Microsoft.JSInterop;
  23. using Microsoft.Net.Http.Headers;
  24. using Moq;
  25. using Xunit;
  26. namespace Microsoft.AspNetCore.Mvc.ViewFeatures
  27. {
  28. public class ComponentRendererTest
  29. {
  30. private const string PrerenderedComponentPattern = "^<!--Blazor:(?<preamble>.*?)-->(?<content>.+?)<!--Blazor:(?<epilogue>.*?)-->$";
  31. private const string ComponentPattern = "^<!--Blazor:(.*?)-->$";
  32. private static readonly IDataProtectionProvider _dataprotectorProvider = new EphemeralDataProtectionProvider();
  33. private readonly IServiceProvider _services = CreateDefaultServiceCollection().BuildServiceProvider();
  34. private readonly ComponentRenderer renderer;
  35. public ComponentRendererTest()
  36. {
  37. renderer = GetComponentRenderer();
  38. }
  39. [Fact]
  40. public async Task CanRender_ParameterlessComponent_ClientMode()
  41. {
  42. // Arrange
  43. var viewContext = GetViewContext();
  44. var writer = new StringWriter();
  45. // Act
  46. var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.WebAssembly, null);
  47. result.WriteTo(writer, HtmlEncoder.Default);
  48. var content = writer.ToString();
  49. var match = Regex.Match(content, ComponentPattern);
  50. // Assert
  51. Assert.True(match.Success);
  52. var marker = JsonSerializer.Deserialize<WebAssemblyComponentMarker>(match.Groups[1].Value, ServerComponentSerializationSettings.JsonSerializationOptions);
  53. Assert.Null(marker.PrerenderId);
  54. Assert.Equal("webassembly", marker.Type);
  55. Assert.Equal(typeof(TestComponent).Assembly.GetName().Name, marker.Assembly);
  56. Assert.Equal(typeof(TestComponent).FullName, marker.TypeName);
  57. Assert.Empty(viewContext.Items);
  58. }
  59. [Fact]
  60. public async Task CanPrerender_ParameterlessComponent_ClientMode()
  61. {
  62. // Arrange
  63. var viewContext = GetViewContext();
  64. var writer = new StringWriter();
  65. // Act
  66. var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.WebAssemblyPrerendered, null);
  67. result.WriteTo(writer, HtmlEncoder.Default);
  68. var content = writer.ToString();
  69. var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);
  70. // Assert
  71. Assert.True(match.Success);
  72. var preamble = match.Groups["preamble"].Value;
  73. var preambleMarker = JsonSerializer.Deserialize<WebAssemblyComponentMarker>(preamble, ServerComponentSerializationSettings.JsonSerializationOptions);
  74. Assert.NotNull(preambleMarker.PrerenderId);
  75. Assert.Equal("webassembly", preambleMarker.Type);
  76. Assert.Equal(typeof(TestComponent).Assembly.GetName().Name, preambleMarker.Assembly);
  77. Assert.Equal(typeof(TestComponent).FullName, preambleMarker.TypeName);
  78. var prerenderedContent = match.Groups["content"].Value;
  79. Assert.Equal("<h1>Hello world!</h1>", prerenderedContent);
  80. var epilogue = match.Groups["epilogue"].Value;
  81. var epilogueMarker = JsonSerializer.Deserialize<WebAssemblyComponentMarker>(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
  82. Assert.Equal(preambleMarker.PrerenderId, epilogueMarker.PrerenderId);
  83. Assert.Null(epilogueMarker.Assembly);
  84. Assert.Null(epilogueMarker.TypeName);
  85. Assert.Null(epilogueMarker.Type);
  86. Assert.Null(epilogueMarker.ParameterDefinitions);
  87. Assert.Null(epilogueMarker.ParameterValues);
  88. var (_, mode) = Assert.Single(viewContext.Items);
  89. var invoked = Assert.IsType<InvokedRenderModes>(mode);
  90. Assert.Equal(InvokedRenderModes.Mode.WebAssembly, invoked.Value);
  91. }
  92. [Fact]
  93. public async Task CanRender_ComponentWithParameters_ClientMode()
  94. {
  95. // Arrange
  96. var viewContext = GetViewContext();
  97. var writer = new StringWriter();
  98. // Act
  99. var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent),
  100. RenderMode.WebAssembly,
  101. new
  102. {
  103. Name = "Daniel"
  104. });
  105. result.WriteTo(writer, HtmlEncoder.Default);
  106. var content = writer.ToString();
  107. var match = Regex.Match(content, ComponentPattern);
  108. // Assert
  109. Assert.True(match.Success);
  110. var marker = JsonSerializer.Deserialize<WebAssemblyComponentMarker>(match.Groups[1].Value, ServerComponentSerializationSettings.JsonSerializationOptions);
  111. Assert.Null(marker.PrerenderId);
  112. Assert.Equal("webassembly", marker.Type);
  113. Assert.Equal(typeof(GreetingComponent).Assembly.GetName().Name, marker.Assembly);
  114. Assert.Equal(typeof(GreetingComponent).FullName, marker.TypeName);
  115. var parameterDefinition = Assert.Single(
  116. JsonSerializer.Deserialize<ComponentParameter[]>(Convert.FromBase64String(marker.ParameterDefinitions), WebAssemblyComponentSerializationSettings.JsonSerializationOptions));
  117. Assert.Equal("Name", parameterDefinition.Name);
  118. Assert.Equal("System.String", parameterDefinition.TypeName);
  119. Assert.Equal("System.Private.CoreLib", parameterDefinition.Assembly);
  120. var value = Assert.Single(JsonSerializer.Deserialize<object[]>(Convert.FromBase64String(marker.ParameterValues), WebAssemblyComponentSerializationSettings.JsonSerializationOptions));
  121. var rawValue = Assert.IsType<JsonElement>(value);
  122. Assert.Equal("Daniel", rawValue.GetString());
  123. }
  124. [Fact]
  125. public async Task CanRender_ComponentWithNullParameters_ClientMode()
  126. {
  127. // Arrange
  128. var viewContext = GetViewContext();
  129. var writer = new StringWriter();
  130. // Act
  131. var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent),
  132. RenderMode.WebAssembly,
  133. new
  134. {
  135. Name = (string)null
  136. });
  137. result.WriteTo(writer, HtmlEncoder.Default);
  138. var content = writer.ToString();
  139. var match = Regex.Match(content, ComponentPattern);
  140. // Assert
  141. Assert.True(match.Success);
  142. var marker = JsonSerializer.Deserialize<WebAssemblyComponentMarker>(match.Groups[1].Value, ServerComponentSerializationSettings.JsonSerializationOptions);
  143. Assert.Null(marker.PrerenderId);
  144. Assert.Equal("webassembly", marker.Type);
  145. Assert.Equal(typeof(GreetingComponent).Assembly.GetName().Name, marker.Assembly);
  146. Assert.Equal(typeof(GreetingComponent).FullName, marker.TypeName);
  147. var parameterDefinition = Assert.Single(JsonSerializer.Deserialize<ComponentParameter[]>(Convert.FromBase64String(marker.ParameterDefinitions), WebAssemblyComponentSerializationSettings.JsonSerializationOptions));
  148. Assert.Equal("Name", parameterDefinition.Name);
  149. Assert.Null(parameterDefinition.TypeName);
  150. Assert.Null(parameterDefinition.Assembly);
  151. var value = Assert.Single(JsonSerializer.Deserialize<object[]>(Convert.FromBase64String(marker.ParameterValues), WebAssemblyComponentSerializationSettings.JsonSerializationOptions));
  152. Assert.Null(value);
  153. }
  154. [Fact]
  155. public async Task CanPrerender_ComponentWithParameters_ClientMode()
  156. {
  157. // Arrange
  158. var viewContext = GetViewContext();
  159. var writer = new StringWriter();
  160. // Act
  161. var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent),
  162. RenderMode.WebAssemblyPrerendered,
  163. new
  164. {
  165. Name = "Daniel"
  166. });
  167. result.WriteTo(writer, HtmlEncoder.Default);
  168. var content = writer.ToString();
  169. var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);
  170. // Assert
  171. Assert.True(match.Success);
  172. var preamble = match.Groups["preamble"].Value;
  173. var preambleMarker = JsonSerializer.Deserialize<WebAssemblyComponentMarker>(preamble, ServerComponentSerializationSettings.JsonSerializationOptions);
  174. Assert.NotNull(preambleMarker.PrerenderId);
  175. Assert.Equal("webassembly", preambleMarker.Type);
  176. Assert.Equal(typeof(GreetingComponent).Assembly.GetName().Name, preambleMarker.Assembly);
  177. Assert.Equal(typeof(GreetingComponent).FullName, preambleMarker.TypeName);
  178. var parameterDefinition = Assert.Single(JsonSerializer.Deserialize<ComponentParameter[]>(Convert.FromBase64String(preambleMarker.ParameterDefinitions), WebAssemblyComponentSerializationSettings.JsonSerializationOptions));
  179. Assert.Equal("Name", parameterDefinition.Name);
  180. Assert.Equal("System.String", parameterDefinition.TypeName);
  181. Assert.Equal("System.Private.CoreLib", parameterDefinition.Assembly);
  182. var value = Assert.Single(JsonSerializer.Deserialize<object[]>(Convert.FromBase64String(preambleMarker.ParameterValues), WebAssemblyComponentSerializationSettings.JsonSerializationOptions));
  183. var rawValue = Assert.IsType<JsonElement>(value);
  184. Assert.Equal("Daniel", rawValue.GetString());
  185. var prerenderedContent = match.Groups["content"].Value;
  186. Assert.Equal("<p>Hello Daniel!</p>", prerenderedContent);
  187. var epilogue = match.Groups["epilogue"].Value;
  188. var epilogueMarker = JsonSerializer.Deserialize<WebAssemblyComponentMarker>(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
  189. Assert.Equal(preambleMarker.PrerenderId, epilogueMarker.PrerenderId);
  190. Assert.Null(epilogueMarker.Assembly);
  191. Assert.Null(epilogueMarker.TypeName);
  192. Assert.Null(epilogueMarker.Type);
  193. Assert.Null(epilogueMarker.ParameterDefinitions);
  194. Assert.Null(epilogueMarker.ParameterValues);
  195. }
  196. [Fact]
  197. public async Task CanPrerender_ComponentWithNullParameters_ClientMode()
  198. {
  199. // Arrange
  200. var viewContext = GetViewContext();
  201. var writer = new StringWriter();
  202. // Act
  203. var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent),
  204. RenderMode.WebAssemblyPrerendered,
  205. new
  206. {
  207. Name = (string)null
  208. });
  209. result.WriteTo(writer, HtmlEncoder.Default);
  210. var content = writer.ToString();
  211. var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);
  212. // Assert
  213. Assert.True(match.Success);
  214. var preamble = match.Groups["preamble"].Value;
  215. var preambleMarker = JsonSerializer.Deserialize<WebAssemblyComponentMarker>(preamble, ServerComponentSerializationSettings.JsonSerializationOptions);
  216. Assert.NotNull(preambleMarker.PrerenderId);
  217. Assert.Equal("webassembly", preambleMarker.Type);
  218. Assert.Equal(typeof(GreetingComponent).Assembly.GetName().Name, preambleMarker.Assembly);
  219. Assert.Equal(typeof(GreetingComponent).FullName, preambleMarker.TypeName);
  220. var parameterDefinition = Assert.Single(JsonSerializer.Deserialize<ComponentParameter[]>(Convert.FromBase64String(preambleMarker.ParameterDefinitions), WebAssemblyComponentSerializationSettings.JsonSerializationOptions));
  221. Assert.Equal("Name", parameterDefinition.Name);
  222. Assert.Null(parameterDefinition.TypeName);
  223. Assert.Null(parameterDefinition.Assembly);
  224. var value = Assert.Single(JsonSerializer.Deserialize<object[]>(Convert.FromBase64String(preambleMarker.ParameterValues), WebAssemblyComponentSerializationSettings.JsonSerializationOptions));
  225. Assert.Null(value);
  226. var prerenderedContent = match.Groups["content"].Value;
  227. Assert.Equal("<p>Hello (null)!</p>", prerenderedContent);
  228. var epilogue = match.Groups["epilogue"].Value;
  229. var epilogueMarker = JsonSerializer.Deserialize<WebAssemblyComponentMarker>(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
  230. Assert.Equal(preambleMarker.PrerenderId, epilogueMarker.PrerenderId);
  231. Assert.Null(epilogueMarker.Assembly);
  232. Assert.Null(epilogueMarker.TypeName);
  233. Assert.Null(epilogueMarker.Type);
  234. Assert.Null(epilogueMarker.ParameterDefinitions);
  235. Assert.Null(epilogueMarker.ParameterValues);
  236. }
  237. [Fact]
  238. public async Task CanRender_ParameterlessComponent()
  239. {
  240. // Arrange
  241. var viewContext = GetViewContext();
  242. var writer = new StringWriter();
  243. // Act
  244. var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Static, null);
  245. result.WriteTo(writer, HtmlEncoder.Default);
  246. var content = writer.ToString();
  247. // Assert
  248. Assert.Equal("<h1>Hello world!</h1>", content);
  249. }
  250. [Fact]
  251. public async Task CanRender_ParameterlessComponent_ServerMode()
  252. {
  253. // Arrange
  254. var viewContext = GetViewContext();
  255. var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
  256. .ToTimeLimitedDataProtector();
  257. // Act
  258. var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Server, null);
  259. var content = HtmlContentUtilities.HtmlContentToString(result);
  260. var match = Regex.Match(content, ComponentPattern);
  261. // Assert
  262. Assert.True(match.Success);
  263. var marker = JsonSerializer.Deserialize<ServerComponentMarker>(match.Groups[1].Value, ServerComponentSerializationSettings.JsonSerializationOptions);
  264. Assert.Equal(0, marker.Sequence);
  265. Assert.Null(marker.PrerenderId);
  266. Assert.NotNull(marker.Descriptor);
  267. Assert.Equal("server", marker.Type);
  268. var unprotectedServerComponent = protector.Unprotect(marker.Descriptor);
  269. var serverComponent = JsonSerializer.Deserialize<ServerComponent>(unprotectedServerComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
  270. Assert.Equal(0, serverComponent.Sequence);
  271. Assert.Equal(typeof(TestComponent).Assembly.GetName().Name, serverComponent.AssemblyName);
  272. Assert.Equal(typeof(TestComponent).FullName, serverComponent.TypeName);
  273. Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);
  274. Assert.Equal("no-cache, no-store, max-age=0", viewContext.HttpContext.Response.Headers.CacheControl);
  275. Assert.DoesNotContain(viewContext.Items.Values, value => value is InvokedRenderModes);
  276. }
  277. [Fact]
  278. public async Task CanPrerender_ParameterlessComponent_ServerMode()
  279. {
  280. // Arrange
  281. var viewContext = GetViewContext();
  282. var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
  283. .ToTimeLimitedDataProtector();
  284. // Act
  285. var result = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.ServerPrerendered, null);
  286. var content = HtmlContentUtilities.HtmlContentToString(result);
  287. var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);
  288. // Assert
  289. Assert.True(match.Success);
  290. var preamble = match.Groups["preamble"].Value;
  291. var preambleMarker = JsonSerializer.Deserialize<ServerComponentMarker>(preamble, ServerComponentSerializationSettings.JsonSerializationOptions);
  292. Assert.Equal(0, preambleMarker.Sequence);
  293. Assert.NotNull(preambleMarker.PrerenderId);
  294. Assert.NotNull(preambleMarker.Descriptor);
  295. Assert.Equal("server", preambleMarker.Type);
  296. var unprotectedServerComponent = protector.Unprotect(preambleMarker.Descriptor);
  297. var serverComponent = JsonSerializer.Deserialize<ServerComponent>(unprotectedServerComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
  298. Assert.NotEqual(default, serverComponent);
  299. Assert.Equal(0, serverComponent.Sequence);
  300. Assert.Equal(typeof(TestComponent).Assembly.GetName().Name, serverComponent.AssemblyName);
  301. Assert.Equal(typeof(TestComponent).FullName, serverComponent.TypeName);
  302. Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);
  303. var prerenderedContent = match.Groups["content"].Value;
  304. Assert.Equal("<h1>Hello world!</h1>", prerenderedContent);
  305. var epilogue = match.Groups["epilogue"].Value;
  306. var epilogueMarker = JsonSerializer.Deserialize<ServerComponentMarker>(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
  307. Assert.Equal(preambleMarker.PrerenderId, epilogueMarker.PrerenderId);
  308. Assert.Null(epilogueMarker.Sequence);
  309. Assert.Null(epilogueMarker.Descriptor);
  310. Assert.Null(epilogueMarker.Type);
  311. Assert.Equal("no-cache, no-store, max-age=0", viewContext.HttpContext.Response.Headers.CacheControl);
  312. var (_, mode) = Assert.Single(viewContext.Items, (kvp) => kvp.Value is InvokedRenderModes);
  313. Assert.Equal(InvokedRenderModes.Mode.Server, ((InvokedRenderModes)mode).Value);
  314. }
  315. [Fact]
  316. public async Task Prerender_ServerAndClientComponentUpdatesInvokedPrerenderModes()
  317. {
  318. // Arrange
  319. var viewContext = GetViewContext();
  320. // Act
  321. var server = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, new { Name = "Steve" });
  322. var client = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.WebAssemblyPrerendered, new { Name = "Steve" });
  323. // Assert
  324. var (_, mode) = Assert.Single(viewContext.Items, (kvp) => kvp.Value is InvokedRenderModes);
  325. Assert.Equal(InvokedRenderModes.Mode.ServerAndWebAssembly, ((InvokedRenderModes)mode).Value);
  326. }
  327. [Fact]
  328. public async Task CanRenderMultipleServerComponents()
  329. {
  330. // Arrange
  331. var viewContext = GetViewContext();
  332. var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
  333. .ToTimeLimitedDataProtector();
  334. // Act
  335. var firstResult = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.ServerPrerendered, null);
  336. var firstComponent = HtmlContentUtilities.HtmlContentToString(firstResult);
  337. var firstMatch = Regex.Match(firstComponent, PrerenderedComponentPattern, RegexOptions.Multiline);
  338. var secondResult = await renderer.RenderComponentAsync(viewContext, typeof(TestComponent), RenderMode.Server, null);
  339. var secondComponent = HtmlContentUtilities.HtmlContentToString(secondResult);
  340. var secondMatch = Regex.Match(secondComponent, ComponentPattern);
  341. // Assert
  342. Assert.True(firstMatch.Success);
  343. var preamble = firstMatch.Groups["preamble"].Value;
  344. var preambleMarker = JsonSerializer.Deserialize<ServerComponentMarker>(preamble, ServerComponentSerializationSettings.JsonSerializationOptions);
  345. Assert.Equal(0, preambleMarker.Sequence);
  346. Assert.NotNull(preambleMarker.Descriptor);
  347. var unprotectedFirstServerComponent = protector.Unprotect(preambleMarker.Descriptor);
  348. var firstServerComponent = JsonSerializer.Deserialize<ServerComponent>(unprotectedFirstServerComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
  349. Assert.Equal(0, firstServerComponent.Sequence);
  350. Assert.NotEqual(Guid.Empty, firstServerComponent.InvocationId);
  351. Assert.True(secondMatch.Success);
  352. var marker = secondMatch.Groups[1].Value;
  353. var markerMarker = JsonSerializer.Deserialize<ServerComponentMarker>(marker, ServerComponentSerializationSettings.JsonSerializationOptions);
  354. Assert.Equal(1, markerMarker.Sequence);
  355. Assert.NotNull(markerMarker.Descriptor);
  356. var unprotectedSecondServerComponent = protector.Unprotect(markerMarker.Descriptor);
  357. var secondServerComponent = JsonSerializer.Deserialize<ServerComponent>(unprotectedSecondServerComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
  358. Assert.Equal(1, secondServerComponent.Sequence);
  359. Assert.Equal(firstServerComponent.InvocationId, secondServerComponent.InvocationId);
  360. }
  361. [Fact]
  362. public async Task CanRender_ComponentWithParametersObject()
  363. {
  364. // Arrange
  365. var viewContext = GetViewContext();
  366. // Act
  367. var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Static, new { Name = "Steve" });
  368. // Assert
  369. var content = HtmlContentUtilities.HtmlContentToString(result);
  370. Assert.Equal("<p>Hello Steve!</p>", content);
  371. }
  372. [Fact]
  373. public async Task CanRender_ComponentWithParameters_ServerMode()
  374. {
  375. // Arrange
  376. var viewContext = GetViewContext();
  377. var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
  378. .ToTimeLimitedDataProtector();
  379. // Act
  380. var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Server, new { Name = "Daniel" });
  381. var content = HtmlContentUtilities.HtmlContentToString(result);
  382. var match = Regex.Match(content, ComponentPattern);
  383. // Assert
  384. Assert.True(match.Success);
  385. var marker = JsonSerializer.Deserialize<ServerComponentMarker>(match.Groups[1].Value, ServerComponentSerializationSettings.JsonSerializationOptions);
  386. Assert.Equal(0, marker.Sequence);
  387. Assert.Null(marker.PrerenderId);
  388. Assert.NotNull(marker.Descriptor);
  389. Assert.Equal("server", marker.Type);
  390. var unprotectedServerComponent = protector.Unprotect(marker.Descriptor);
  391. var serverComponent = JsonSerializer.Deserialize<ServerComponent>(unprotectedServerComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
  392. Assert.Equal(0, serverComponent.Sequence);
  393. Assert.Equal(typeof(GreetingComponent).Assembly.GetName().Name, serverComponent.AssemblyName);
  394. Assert.Equal(typeof(GreetingComponent).FullName, serverComponent.TypeName);
  395. Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);
  396. var parameterDefinition = Assert.Single(serverComponent.ParameterDefinitions);
  397. Assert.Equal("Name", parameterDefinition.Name);
  398. Assert.Equal("System.String", parameterDefinition.TypeName);
  399. Assert.Equal("System.Private.CoreLib", parameterDefinition.Assembly);
  400. var value = Assert.Single(serverComponent.ParameterValues);
  401. var rawValue = Assert.IsType<JsonElement>(value);
  402. Assert.Equal("Daniel", rawValue.GetString());
  403. }
  404. [Fact]
  405. public async Task CanRender_ComponentWithNullParameters_ServerMode()
  406. {
  407. // Arrange
  408. var viewContext = GetViewContext();
  409. var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
  410. .ToTimeLimitedDataProtector();
  411. // Act
  412. var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.Server, new { Name = (string)null });
  413. var content = HtmlContentUtilities.HtmlContentToString(result);
  414. var match = Regex.Match(content, ComponentPattern);
  415. // Assert
  416. Assert.True(match.Success);
  417. var marker = JsonSerializer.Deserialize<ServerComponentMarker>(match.Groups[1].Value, ServerComponentSerializationSettings.JsonSerializationOptions);
  418. Assert.Equal(0, marker.Sequence);
  419. Assert.Null(marker.PrerenderId);
  420. Assert.NotNull(marker.Descriptor);
  421. Assert.Equal("server", marker.Type);
  422. var unprotectedServerComponent = protector.Unprotect(marker.Descriptor);
  423. var serverComponent = JsonSerializer.Deserialize<ServerComponent>(unprotectedServerComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
  424. Assert.Equal(0, serverComponent.Sequence);
  425. Assert.Equal(typeof(GreetingComponent).Assembly.GetName().Name, serverComponent.AssemblyName);
  426. Assert.Equal(typeof(GreetingComponent).FullName, serverComponent.TypeName);
  427. Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);
  428. Assert.NotNull(serverComponent.ParameterDefinitions);
  429. var parameterDefinition = Assert.Single(serverComponent.ParameterDefinitions);
  430. Assert.Equal("Name", parameterDefinition.Name);
  431. Assert.Null(parameterDefinition.TypeName);
  432. Assert.Null(parameterDefinition.Assembly);
  433. var value = Assert.Single(serverComponent.ParameterValues); ;
  434. Assert.Null(value);
  435. }
  436. [Fact]
  437. public async Task CanPrerender_ComponentWithParameters_ServerPrerenderedMode()
  438. {
  439. // Arrange
  440. var viewContext = GetViewContext();
  441. var writer = new StringWriter();
  442. var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
  443. .ToTimeLimitedDataProtector();
  444. // Act
  445. var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, new { Name = "Daniel" });
  446. var content = HtmlContentUtilities.HtmlContentToString(result);
  447. var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);
  448. // Assert
  449. Assert.True(match.Success);
  450. var preamble = match.Groups["preamble"].Value;
  451. var preambleMarker = JsonSerializer.Deserialize<ServerComponentMarker>(preamble, ServerComponentSerializationSettings.JsonSerializationOptions);
  452. Assert.Equal(0, preambleMarker.Sequence);
  453. Assert.NotNull(preambleMarker.PrerenderId);
  454. Assert.NotNull(preambleMarker.Descriptor);
  455. Assert.Equal("server", preambleMarker.Type);
  456. var unprotectedServerComponent = protector.Unprotect(preambleMarker.Descriptor);
  457. var serverComponent = JsonSerializer.Deserialize<ServerComponent>(unprotectedServerComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
  458. Assert.NotEqual(default, serverComponent);
  459. Assert.Equal(0, serverComponent.Sequence);
  460. Assert.Equal(typeof(GreetingComponent).Assembly.GetName().Name, serverComponent.AssemblyName);
  461. Assert.Equal(typeof(GreetingComponent).FullName, serverComponent.TypeName);
  462. Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);
  463. var parameterDefinition = Assert.Single(serverComponent.ParameterDefinitions);
  464. Assert.Equal("Name", parameterDefinition.Name);
  465. Assert.Equal("System.String", parameterDefinition.TypeName);
  466. Assert.Equal("System.Private.CoreLib", parameterDefinition.Assembly);
  467. var value = Assert.Single(serverComponent.ParameterValues);
  468. var rawValue = Assert.IsType<JsonElement>(value);
  469. Assert.Equal("Daniel", rawValue.GetString());
  470. var prerenderedContent = match.Groups["content"].Value;
  471. Assert.Equal("<p>Hello Daniel!</p>", prerenderedContent);
  472. var epilogue = match.Groups["epilogue"].Value;
  473. var epilogueMarker = JsonSerializer.Deserialize<ServerComponentMarker>(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
  474. Assert.Equal(preambleMarker.PrerenderId, epilogueMarker.PrerenderId);
  475. Assert.Null(epilogueMarker.Sequence);
  476. Assert.Null(epilogueMarker.Descriptor);
  477. Assert.Null(epilogueMarker.Type);
  478. }
  479. [Fact]
  480. public async Task CanPrerender_ComponentWithNullParameters_ServerPrerenderedMode()
  481. {
  482. // Arrange
  483. var viewContext = GetViewContext();
  484. var writer = new StringWriter();
  485. var protector = _dataprotectorProvider.CreateProtector(ServerComponentSerializationSettings.DataProtectionProviderPurpose)
  486. .ToTimeLimitedDataProtector();
  487. // Act
  488. var result = await renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), RenderMode.ServerPrerendered, new { Name = (string)null });
  489. var content = HtmlContentUtilities.HtmlContentToString(result);
  490. var match = Regex.Match(content, PrerenderedComponentPattern, RegexOptions.Multiline);
  491. // Assert
  492. Assert.True(match.Success);
  493. var preamble = match.Groups["preamble"].Value;
  494. var preambleMarker = JsonSerializer.Deserialize<ServerComponentMarker>(preamble, ServerComponentSerializationSettings.JsonSerializationOptions);
  495. Assert.Equal(0, preambleMarker.Sequence);
  496. Assert.NotNull(preambleMarker.PrerenderId);
  497. Assert.NotNull(preambleMarker.Descriptor);
  498. Assert.Equal("server", preambleMarker.Type);
  499. var unprotectedServerComponent = protector.Unprotect(preambleMarker.Descriptor);
  500. var serverComponent = JsonSerializer.Deserialize<ServerComponent>(unprotectedServerComponent, ServerComponentSerializationSettings.JsonSerializationOptions);
  501. Assert.NotEqual(default, serverComponent);
  502. Assert.Equal(0, serverComponent.Sequence);
  503. Assert.Equal(typeof(GreetingComponent).Assembly.GetName().Name, serverComponent.AssemblyName);
  504. Assert.Equal(typeof(GreetingComponent).FullName, serverComponent.TypeName);
  505. Assert.NotEqual(Guid.Empty, serverComponent.InvocationId);
  506. Assert.NotNull(serverComponent.ParameterDefinitions);
  507. var parameterDefinition = Assert.Single(serverComponent.ParameterDefinitions);
  508. Assert.Equal("Name", parameterDefinition.Name);
  509. Assert.Null(parameterDefinition.TypeName);
  510. Assert.Null(parameterDefinition.Assembly);
  511. var value = Assert.Single(serverComponent.ParameterValues);
  512. Assert.Null(value);
  513. var prerenderedContent = match.Groups["content"].Value;
  514. Assert.Equal("<p>Hello (null)!</p>", prerenderedContent);
  515. var epilogue = match.Groups["epilogue"].Value;
  516. var epilogueMarker = JsonSerializer.Deserialize<ServerComponentMarker>(epilogue, ServerComponentSerializationSettings.JsonSerializationOptions);
  517. Assert.Equal(preambleMarker.PrerenderId, epilogueMarker.PrerenderId);
  518. Assert.Null(epilogueMarker.Sequence);
  519. Assert.Null(epilogueMarker.Descriptor);
  520. Assert.Null(epilogueMarker.Type);
  521. }
  522. [Fact]
  523. public async Task ComponentWithInvalidRenderMode_Throws()
  524. {
  525. // Arrange
  526. var viewContext = GetViewContext();
  527. // Act & Assert
  528. var ex = await ExceptionAssert.ThrowsArgumentAsync(
  529. () => renderer.RenderComponentAsync(viewContext, typeof(GreetingComponent), default, new { Name = "Daniel" }),
  530. "renderMode",
  531. $"Unsupported RenderMode '{(RenderMode)default}'");
  532. }
  533. [Fact]
  534. public async Task RenderComponent_DoesNotInvokeOnAfterRenderInComponent()
  535. {
  536. // Arrange
  537. var viewContext = GetViewContext();
  538. // Act
  539. var state = new OnAfterRenderState();
  540. var result = await renderer.RenderComponentAsync(viewContext, typeof(OnAfterRenderComponent), RenderMode.Static, new { state });
  541. // Assert
  542. var content = HtmlContentUtilities.HtmlContentToString(result);
  543. Assert.Equal("<p>Hello</p>", content);
  544. Assert.False(state.OnAfterRenderRan);
  545. }
  546. [Fact]
  547. public async Task DisposableComponents_GetDisposedAfterScopeCompletes()
  548. {
  549. // Arrange
  550. var collection = CreateDefaultServiceCollection();
  551. collection.TryAddScoped<ComponentRenderer>();
  552. collection.TryAddScoped<StaticComponentRenderer>();
  553. collection.TryAddScoped<HtmlRenderer>();
  554. collection.TryAddSingleton(HtmlEncoder.Default);
  555. collection.TryAddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
  556. collection.TryAddSingleton<ServerComponentSerializer>();
  557. collection.TryAddSingleton(_dataprotectorProvider);
  558. collection.TryAddSingleton<WebAssemblyComponentSerializer>();
  559. var provider = collection.BuildServiceProvider();
  560. var scope = provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
  561. var scopedProvider = scope.ServiceProvider;
  562. var context = new DefaultHttpContext() { RequestServices = scopedProvider };
  563. var viewContext = GetViewContext(context);
  564. var renderer = scopedProvider.GetRequiredService<ComponentRenderer>();
  565. // Act
  566. var state = new AsyncDisposableState();
  567. var result = await renderer.RenderComponentAsync(viewContext, typeof(AsyncDisposableComponent), RenderMode.Static, new { state });
  568. // Assert
  569. var content = HtmlContentUtilities.HtmlContentToString(result);
  570. Assert.Equal("<p>Hello</p>", content);
  571. await ((IAsyncDisposable)scope).DisposeAsync();
  572. Assert.True(state.AsyncDisposableRan);
  573. }
  574. [Fact]
  575. public async Task CanCatch_ComponentWithSynchronousException()
  576. {
  577. // Arrange
  578. var viewContext = GetViewContext();
  579. // Act & Assert
  580. var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => renderer.RenderComponentAsync(
  581. viewContext,
  582. typeof(ExceptionComponent),
  583. RenderMode.Static,
  584. new
  585. {
  586. IsAsync = false
  587. }));
  588. // Assert
  589. Assert.Equal("Threw an exception synchronously", exception.Message);
  590. }
  591. [Fact]
  592. public async Task CanCatch_ComponentWithAsynchronousException()
  593. {
  594. // Arrange
  595. var viewContext = GetViewContext();
  596. // Act & Assert
  597. var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => renderer.RenderComponentAsync(
  598. viewContext,
  599. typeof(ExceptionComponent),
  600. RenderMode.Static,
  601. new
  602. {
  603. IsAsync = true
  604. }));
  605. // Assert
  606. Assert.Equal("Threw an exception asynchronously", exception.Message);
  607. }
  608. [Fact]
  609. public async Task Rendering_ComponentWithJsInteropThrows()
  610. {
  611. // Arrange
  612. var viewContext = GetViewContext();
  613. // Act & Assert
  614. var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => renderer.RenderComponentAsync(
  615. viewContext,
  616. typeof(ExceptionComponent),
  617. RenderMode.Static,
  618. new
  619. {
  620. JsInterop = true
  621. }
  622. ));
  623. // Assert
  624. Assert.Equal("JavaScript interop calls cannot be issued during server-side prerendering, " +
  625. "because the page has not yet loaded in the browser. Prerendered components must wrap any JavaScript " +
  626. "interop calls in conditional logic to ensure those interop calls are not attempted during prerendering.",
  627. exception.Message);
  628. }
  629. [Fact]
  630. public async Task UriHelperRedirect_ThrowsInvalidOperationException_WhenResponseHasAlreadyStarted()
  631. {
  632. // Arrange
  633. var ctx = new DefaultHttpContext();
  634. ctx.Request.Scheme = "http";
  635. ctx.Request.Host = new HostString("localhost");
  636. ctx.Request.PathBase = "/base";
  637. ctx.Request.Path = "/path";
  638. ctx.Request.QueryString = new QueryString("?query=value");
  639. var responseMock = new Mock<IHttpResponseFeature>();
  640. responseMock.Setup(r => r.HasStarted).Returns(true);
  641. ctx.Features.Set(responseMock.Object);
  642. var viewContext = GetViewContext(ctx);
  643. // Act
  644. var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => renderer.RenderComponentAsync(
  645. viewContext,
  646. typeof(RedirectComponent),
  647. RenderMode.Static,
  648. new
  649. {
  650. RedirectUri = "http://localhost/redirect"
  651. }));
  652. Assert.Equal("A navigation command was attempted during prerendering after the server already started sending the response. " +
  653. "Navigation commands can not be issued during server-side prerendering after the response from the server has started. Applications must buffer the" +
  654. "response and avoid using features like FlushAsync() before all components on the page have been rendered to prevent failed navigation commands.",
  655. exception.Message);
  656. }
  657. [Fact]
  658. public async Task HtmlHelper_Redirects_WhenComponentNavigates()
  659. {
  660. // Arrange
  661. var ctx = new DefaultHttpContext();
  662. ctx.Request.Scheme = "http";
  663. ctx.Request.Host = new HostString("localhost");
  664. ctx.Request.PathBase = "/base";
  665. ctx.Request.Path = "/path";
  666. ctx.Request.QueryString = new QueryString("?query=value");
  667. var viewContext = GetViewContext(ctx);
  668. // Act
  669. await renderer.RenderComponentAsync(
  670. viewContext,
  671. typeof(RedirectComponent),
  672. RenderMode.Static,
  673. new
  674. {
  675. RedirectUri = "http://localhost/redirect"
  676. });
  677. // Assert
  678. Assert.Equal(302, ctx.Response.StatusCode);
  679. Assert.Equal("http://localhost/redirect", ctx.Response.Headers.Location);
  680. }
  681. [Fact]
  682. public async Task CanRender_AsyncComponent()
  683. {
  684. // Arrange
  685. var viewContext = GetViewContext();
  686. var expectedContent = @"<table>
  687. <thead>
  688. <tr>
  689. <th>Date</th>
  690. <th>Summary</th>
  691. <th>F</th>
  692. <th>C</th>
  693. </tr>
  694. </thead>
  695. <tbody>
  696. <tr>
  697. <td>06/05/2018</td>
  698. <td>Freezing</td>
  699. <td>33</td>
  700. <td>33</td>
  701. </tr>
  702. <tr>
  703. <td>07/05/2018</td>
  704. <td>Bracing</td>
  705. <td>57</td>
  706. <td>57</td>
  707. </tr>
  708. <tr>
  709. <td>08/05/2018</td>
  710. <td>Freezing</td>
  711. <td>9</td>
  712. <td>9</td>
  713. </tr>
  714. <tr>
  715. <td>09/05/2018</td>
  716. <td>Balmy</td>
  717. <td>4</td>
  718. <td>4</td>
  719. </tr>
  720. <tr>
  721. <td>10/05/2018</td>
  722. <td>Chilly</td>
  723. <td>29</td>
  724. <td>29</td>
  725. </tr>
  726. </tbody>
  727. </table>";
  728. // Act
  729. var result = await renderer.RenderComponentAsync(viewContext, typeof(AsyncComponent), RenderMode.Static, null);
  730. var content = HtmlContentUtilities.HtmlContentToString(result);
  731. // Assert
  732. Assert.Equal(expectedContent.Replace("\r\n", "\n"), content);
  733. }
  734. private ComponentRenderer GetComponentRenderer(IServiceProvider services = null) =>
  735. new ComponentRenderer(
  736. new StaticComponentRenderer(new HtmlRenderer(services ?? _services, NullLoggerFactory.Instance, HtmlEncoder.Default)),
  737. new ServerComponentSerializer(_dataprotectorProvider),
  738. new WebAssemblyComponentSerializer());
  739. private ViewContext GetViewContext(HttpContext context = null)
  740. {
  741. context ??= new DefaultHttpContext();
  742. context.RequestServices ??= _services;
  743. context.Request.Scheme = "http";
  744. context.Request.Host = new HostString("localhost");
  745. context.Request.PathBase = "/base";
  746. context.Request.Path = "/path";
  747. context.Request.QueryString = QueryString.FromUriComponent("?query=value");
  748. return new ViewContext { HttpContext = context };
  749. }
  750. private static ServiceCollection CreateDefaultServiceCollection()
  751. {
  752. var services = new ServiceCollection();
  753. services.AddSingleton(_dataprotectorProvider);
  754. services.AddSingleton<IJSRuntime, UnsupportedJavaScriptRuntime>();
  755. services.AddSingleton<NavigationManager, HttpNavigationManager>();
  756. services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
  757. services.AddSingleton<ILogger<ComponentStatePersistenceManager>, NullLogger<ComponentStatePersistenceManager>>();
  758. services.AddSingleton<ComponentStatePersistenceManager>();
  759. services.AddSingleton(sp => sp.GetRequiredService<ComponentStatePersistenceManager>().State);
  760. return services;
  761. }
  762. private class TestComponent : IComponent
  763. {
  764. private RenderHandle _renderHandle;
  765. public void Attach(RenderHandle renderHandle)
  766. {
  767. _renderHandle = renderHandle;
  768. }
  769. public Task SetParametersAsync(ParameterView parameters)
  770. {
  771. _renderHandle.Render(builder =>
  772. {
  773. var s = 0;
  774. builder.OpenElement(s++, "h1");
  775. builder.AddContent(s++, "Hello world!");
  776. builder.CloseElement();
  777. });
  778. return Task.CompletedTask;
  779. }
  780. }
  781. private class RedirectComponent : ComponentBase
  782. {
  783. [Inject] NavigationManager NavigationManager { get; set; }
  784. [Parameter] public string RedirectUri { get; set; }
  785. [Parameter] public bool Force { get; set; }
  786. protected override void OnInitialized()
  787. {
  788. NavigationManager.NavigateTo(RedirectUri, Force);
  789. }
  790. }
  791. private class ExceptionComponent : ComponentBase
  792. {
  793. [Parameter] public bool IsAsync { get; set; }
  794. [Parameter] public bool JsInterop { get; set; }
  795. [Inject] IJSRuntime JsRuntime { get; set; }
  796. protected override async Task OnParametersSetAsync()
  797. {
  798. if (JsInterop)
  799. {
  800. await JsRuntime.InvokeAsync<int>("window.alert", "Interop!");
  801. }
  802. if (!IsAsync)
  803. {
  804. throw new InvalidOperationException("Threw an exception synchronously");
  805. }
  806. else
  807. {
  808. await Task.Yield();
  809. throw new InvalidOperationException("Threw an exception asynchronously");
  810. }
  811. }
  812. }
  813. private class OnAfterRenderComponent : ComponentBase
  814. {
  815. [Parameter] public OnAfterRenderState State { get; set; }
  816. protected override void OnAfterRender(bool firstRender)
  817. {
  818. State.OnAfterRenderRan = true;
  819. }
  820. protected override void BuildRenderTree(RenderTreeBuilder builder)
  821. {
  822. builder.AddMarkupContent(0, "<p>Hello</p>");
  823. }
  824. }
  825. private class OnAfterRenderState
  826. {
  827. public bool OnAfterRenderRan { get; set; }
  828. }
  829. private class AsyncDisposableComponent : ComponentBase, IAsyncDisposable
  830. {
  831. [Parameter] public AsyncDisposableState State { get; set; }
  832. protected override void BuildRenderTree(RenderTreeBuilder builder)
  833. {
  834. builder.AddMarkupContent(0, "<p>Hello</p>");
  835. }
  836. public ValueTask DisposeAsync()
  837. {
  838. State.AsyncDisposableRan = true;
  839. return default;
  840. }
  841. }
  842. private class AsyncDisposableState
  843. {
  844. public bool AsyncDisposableRan { get; set; }
  845. }
  846. private class GreetingComponent : ComponentBase
  847. {
  848. [Parameter] public string Name { get; set; }
  849. protected override void OnParametersSet()
  850. {
  851. base.OnParametersSet();
  852. }
  853. protected override void BuildRenderTree(RenderTreeBuilder builder)
  854. {
  855. var s = 0;
  856. base.BuildRenderTree(builder);
  857. builder.OpenElement(s++, "p");
  858. builder.AddContent(s++, $"Hello {Name ?? ("(null)")}!");
  859. builder.CloseElement();
  860. }
  861. }
  862. private class AsyncComponent : ComponentBase
  863. {
  864. private static readonly WeatherRow[] _weatherData = new[]
  865. {
  866. new WeatherRow
  867. {
  868. DateFormatted = "06/05/2018",
  869. TemperatureC = 1,
  870. Summary = "Freezing",
  871. TemperatureF = 33
  872. },
  873. new WeatherRow
  874. {
  875. DateFormatted = "07/05/2018",
  876. TemperatureC = 14,
  877. Summary = "Bracing",
  878. TemperatureF = 57
  879. },
  880. new WeatherRow
  881. {
  882. DateFormatted = "08/05/2018",
  883. TemperatureC = -13,
  884. Summary = "Freezing",
  885. TemperatureF = 9
  886. },
  887. new WeatherRow
  888. {
  889. DateFormatted = "09/05/2018",
  890. TemperatureC = -16,
  891. Summary = "Balmy",
  892. TemperatureF = 4
  893. },
  894. new WeatherRow
  895. {
  896. DateFormatted = "10/05/2018",
  897. TemperatureC = 2,
  898. Summary = "Chilly",
  899. TemperatureF = 29
  900. }
  901. };
  902. public class WeatherRow
  903. {
  904. public string DateFormatted { get; set; }
  905. public int TemperatureC { get; set; }
  906. public string Summary { get; set; }
  907. public int TemperatureF { get; set; }
  908. }
  909. public WeatherRow[] RowsToDisplay { get; set; }
  910. protected override async Task OnParametersSetAsync()
  911. {
  912. // Simulate an async workflow.
  913. await Task.Yield();
  914. RowsToDisplay = _weatherData;
  915. }
  916. protected override void BuildRenderTree(RenderTreeBuilder builder)
  917. {
  918. base.BuildRenderTree(builder);
  919. var s = 0;
  920. builder.OpenElement(s++, "table");
  921. builder.AddMarkupContent(s++, "\n");
  922. builder.OpenElement(s++, "thead");
  923. builder.AddMarkupContent(s++, "\n");
  924. builder.OpenElement(s++, "tr");
  925. builder.AddMarkupContent(s++, "\n");
  926. builder.OpenElement(s++, "th");
  927. builder.AddContent(s++, "Date");
  928. builder.CloseElement();
  929. builder.AddMarkupContent(s++, "\n");
  930. builder.OpenElement(s++, "th");
  931. builder.AddContent(s++, "Summary");
  932. builder.CloseElement();
  933. builder.AddMarkupContent(s++, "\n");
  934. builder.OpenElement(s++, "th");
  935. builder.AddContent(s++, "F");
  936. builder.CloseElement();
  937. builder.AddMarkupContent(s++, "\n");
  938. builder.OpenElement(s++, "th");
  939. builder.AddContent(s++, "C");
  940. builder.CloseElement();
  941. builder.AddMarkupContent(s++, "\n");
  942. builder.CloseElement();
  943. builder.AddMarkupContent(s++, "\n");
  944. builder.CloseElement();
  945. builder.AddMarkupContent(s++, "\n");
  946. builder.OpenElement(s++, "tbody");
  947. builder.AddMarkupContent(s++, "\n");
  948. if (RowsToDisplay != null)
  949. {
  950. var s2 = s;
  951. foreach (var element in RowsToDisplay)
  952. {
  953. s = s2;
  954. builder.OpenElement(s++, "tr");
  955. builder.AddMarkupContent(s++, "\n");
  956. builder.OpenElement(s++, "td");
  957. builder.AddContent(s++, element.DateFormatted);
  958. builder.CloseElement();
  959. builder.AddMarkupContent(s++, "\n");
  960. builder.OpenElement(s++, "td");
  961. builder.AddContent(s++, element.Summary);
  962. builder.CloseElement();
  963. builder.AddMarkupContent(s++, "\n");
  964. builder.OpenElement(s++, "td");
  965. builder.AddContent(s++, element.TemperatureF);
  966. builder.CloseElement();
  967. builder.AddMarkupContent(s++, "\n");
  968. builder.OpenElement(s++, "td");
  969. builder.AddContent(s++, element.TemperatureF);
  970. builder.CloseElement();
  971. builder.AddMarkupContent(s++, "\n");
  972. builder.CloseElement();
  973. builder.AddMarkupContent(s++, "\n");
  974. }
  975. }
  976. builder.CloseElement();
  977. builder.AddMarkupContent(s++, "\n");
  978. builder.CloseElement();
  979. }
  980. }
  981. }
  982. }