[MVC] Add MVC integration with Razor Components
* Adds RenderComponentAsync extension methods to IHtmlHelper that allow for prerrendering of Razor components within MVC views.
This commit is contained in:
parent
5199cce626
commit
15edd84d40
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Html;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for rendering components.
|
||||||
|
/// </summary>
|
||||||
|
public static class HtmlHelperComponentExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the <typeparamref name="TComponent"/> <see cref="IComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="htmlHelper">The <see cref="IHtmlHelper"/>.</param>
|
||||||
|
/// <returns>The HTML produced by the rendered <typeparamref name="TComponent"/>.</returns>
|
||||||
|
public static Task<IHtmlContent> RenderComponentAsync<TComponent>(this IHtmlHelper htmlHelper) where TComponent : IComponent
|
||||||
|
{
|
||||||
|
if (htmlHelper == null)
|
||||||
|
{
|
||||||
|
throw new System.ArgumentNullException(nameof(htmlHelper));
|
||||||
|
}
|
||||||
|
|
||||||
|
return htmlHelper.RenderComponentAsync<TComponent>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the <typeparamref name="TComponent"/> <see cref="IComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="htmlHelper">The <see cref="IHtmlHelper"/>.</param>
|
||||||
|
/// <param name="parameters">An <see cref="object"/> containing the parameters to pass
|
||||||
|
/// to the component.</param>
|
||||||
|
/// <returns>The HTML produced by the rendered <typeparamref name="TComponent"/>.</returns>
|
||||||
|
public static async Task<IHtmlContent> RenderComponentAsync<TComponent>(
|
||||||
|
this IHtmlHelper htmlHelper,
|
||||||
|
object parameters) where TComponent : IComponent
|
||||||
|
{
|
||||||
|
if (htmlHelper == null)
|
||||||
|
{
|
||||||
|
throw new System.ArgumentNullException(nameof(htmlHelper));
|
||||||
|
}
|
||||||
|
|
||||||
|
var serviceProvider = htmlHelper.ViewContext.HttpContext.RequestServices;
|
||||||
|
var encoder = serviceProvider.GetRequiredService<HtmlEncoder>();
|
||||||
|
using (var htmlRenderer = new HtmlRenderer(serviceProvider, encoder.Encode))
|
||||||
|
{
|
||||||
|
var result = await htmlRenderer.RenderComponentAsync<TComponent>(
|
||||||
|
parameters == null ?
|
||||||
|
ParameterCollection.Empty :
|
||||||
|
ParameterCollection.FromDictionary(HtmlHelper.ObjectToDictionary(parameters)));
|
||||||
|
|
||||||
|
return new ComponentHtmlContent(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ComponentHtmlContent : IHtmlContent
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<string> _componentResult;
|
||||||
|
|
||||||
|
public ComponentHtmlContent(IEnumerable<string> componentResult)
|
||||||
|
{
|
||||||
|
_componentResult = componentResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
|
||||||
|
{
|
||||||
|
foreach (var element in _componentResult)
|
||||||
|
{
|
||||||
|
writer.Write(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ Microsoft.AspNetCore.Mvc.ViewComponent</Description>
|
||||||
<Reference Include="Microsoft.AspNetCore.Diagnostics.Abstractions" />
|
<Reference Include="Microsoft.AspNetCore.Diagnostics.Abstractions" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Html.Abstractions" />
|
<Reference Include="Microsoft.AspNetCore.Html.Abstractions" />
|
||||||
<Reference Include="Microsoft.Extensions.WebEncoders" />
|
<Reference Include="Microsoft.Extensions.WebEncoders" />
|
||||||
|
<Reference Include="Microsoft.AspNetCore.Components" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AngleSharp.Parser.Html;
|
||||||
|
using BasicWebSite.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class ComponentRenderingFunctionalTests : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
|
||||||
|
{
|
||||||
|
public ComponentRenderingFunctionalTests(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||||
|
{
|
||||||
|
Client = Client ?? CreateClient(fixture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Renders_BasicComponent()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var response = await Client.GetAsync("http://localhost/components");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
AssertComponent("\n <p>Hello world!</p>\n", "Greetings", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Renders_AsyncComponent()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var expectedHtml = @"
|
||||||
|
<h1>Weather forecast</h1>
|
||||||
|
|
||||||
|
<p>This component demonstrates fetching data from the server.</p>
|
||||||
|
|
||||||
|
<p>Weather data for 01/15/2019</p>
|
||||||
|
<table class=""table"">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Temp. (C)</th>
|
||||||
|
<th>Temp. (F)</th>
|
||||||
|
<th>Summary</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>06/05/2018</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>33</td>
|
||||||
|
<td>Freezing</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>07/05/2018</td>
|
||||||
|
<td>14</td>
|
||||||
|
<td>57</td>
|
||||||
|
<td>Bracing</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>08/05/2018</td>
|
||||||
|
<td>-13</td>
|
||||||
|
<td>9</td>
|
||||||
|
<td>Freezing</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>09/05/2018</td>
|
||||||
|
<td>-16</td>
|
||||||
|
<td>4</td>
|
||||||
|
<td>Balmy</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>10/05/2018</td>
|
||||||
|
<td>2</td>
|
||||||
|
<td>29</td>
|
||||||
|
<td>Chilly</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
";
|
||||||
|
|
||||||
|
var response = await Client.GetAsync("http://localhost/components");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
AssertComponent(expectedHtml, "FetchData", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertComponent(string expectedConent, string divId, string responseContent)
|
||||||
|
{
|
||||||
|
var parser = new HtmlParser();
|
||||||
|
var htmlDocument = parser.Parse(responseContent);
|
||||||
|
var div = htmlDocument.Body.QuerySelector($"#{divId}");
|
||||||
|
Assert.Equal(
|
||||||
|
expectedConent.Replace("\r\n","\n"),
|
||||||
|
div.InnerHtml.Replace("\r\n","\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple delegating handler used in setting up test services so that we can configure
|
||||||
|
// services that talk back to the TestServer using HttpClient.
|
||||||
|
private class LoopHttpHandler : DelegatingHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpClient CreateClient(MvcTestFixture<BasicWebSite.Startup> fixture)
|
||||||
|
{
|
||||||
|
var loopHandler = new LoopHttpHandler();
|
||||||
|
|
||||||
|
var client = fixture
|
||||||
|
.WithWebHostBuilder(builder => builder.ConfigureServices(ConfigureTestWeatherForecastService))
|
||||||
|
.CreateClient();
|
||||||
|
|
||||||
|
// We configure the inner handler with a handler to this TestServer instance so that calls to the
|
||||||
|
// server can get routed properly.
|
||||||
|
loopHandler.InnerHandler = fixture.Server.CreateHandler();
|
||||||
|
|
||||||
|
void ConfigureTestWeatherForecastService(IServiceCollection services) =>
|
||||||
|
// We configure the test service here with an HttpClient that uses this loopback handler to talk
|
||||||
|
// to this TestServer instance.
|
||||||
|
services.AddSingleton(new WeatherForecastService(new HttpClient(loopHandler)
|
||||||
|
{
|
||||||
|
BaseAddress = fixture.ClientOptions.BaseAddress
|
||||||
|
}));
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,309 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Test
|
||||||
|
{
|
||||||
|
public class HtmlHelperComponentExtensionsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task CanRender_ParameterlessComponent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var helper = CreateHelper();
|
||||||
|
var writer = new StringWriter();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await helper.RenderComponentAsync<TestComponent>();
|
||||||
|
result.WriteTo(writer, HtmlEncoder.Default);
|
||||||
|
var content = writer.ToString();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("<h1>Hello world!</h1>", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanRender_ComponentWithParametersObject()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var helper = CreateHelper();
|
||||||
|
var writer = new StringWriter();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await helper.RenderComponentAsync<GreetingComponent>(new
|
||||||
|
{
|
||||||
|
Name = "Steve"
|
||||||
|
});
|
||||||
|
result.WriteTo(writer, HtmlEncoder.Default);
|
||||||
|
var content = writer.ToString();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("<p>Hello Steve!</p>", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanRender_AsyncComponent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var helper = CreateHelper();
|
||||||
|
var writer = new StringWriter();
|
||||||
|
var expectedContent = @"<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Summary</th>
|
||||||
|
<th>F</th>
|
||||||
|
<th>C</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>06/05/2018</td>
|
||||||
|
<td>Freezing</td>
|
||||||
|
<td>33</td>
|
||||||
|
<td>33</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>07/05/2018</td>
|
||||||
|
<td>Bracing</td>
|
||||||
|
<td>57</td>
|
||||||
|
<td>57</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>08/05/2018</td>
|
||||||
|
<td>Freezing</td>
|
||||||
|
<td>9</td>
|
||||||
|
<td>9</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>09/05/2018</td>
|
||||||
|
<td>Balmy</td>
|
||||||
|
<td>4</td>
|
||||||
|
<td>4</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>10/05/2018</td>
|
||||||
|
<td>Chilly</td>
|
||||||
|
<td>29</td>
|
||||||
|
<td>29</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await helper.RenderComponentAsync<AsyncComponent>();
|
||||||
|
result.WriteTo(writer, HtmlEncoder.Default);
|
||||||
|
var content = writer.ToString();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedContent.Replace("\r\n","\n"), content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IHtmlHelper CreateHelper(Action<IServiceCollection> configureServices = null)
|
||||||
|
{
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
serviceCollection.AddSingleton<HtmlEncoder>(HtmlEncoder.Default);
|
||||||
|
configureServices?.Invoke(serviceCollection);
|
||||||
|
|
||||||
|
var helper = new Mock<IHtmlHelper>();
|
||||||
|
helper.Setup(h => h.ViewContext)
|
||||||
|
.Returns(new ViewContext()
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext()
|
||||||
|
{
|
||||||
|
RequestServices = serviceCollection.BuildServiceProvider()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return helper.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestComponent : IComponent
|
||||||
|
{
|
||||||
|
private RenderHandle _renderHandle;
|
||||||
|
|
||||||
|
public void Configure(RenderHandle renderHandle)
|
||||||
|
{
|
||||||
|
_renderHandle = renderHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SetParametersAsync(ParameterCollection parameters)
|
||||||
|
{
|
||||||
|
_renderHandle.Render(builder =>
|
||||||
|
{
|
||||||
|
var s = 0;
|
||||||
|
builder.OpenElement(s++, "h1");
|
||||||
|
builder.AddContent(s++, "Hello world!");
|
||||||
|
builder.CloseElement();
|
||||||
|
});
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class GreetingComponent : ComponentBase
|
||||||
|
{
|
||||||
|
[Parameter] public string Name { get; set; }
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
base.OnParametersSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
var s = 0;
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenElement(s++, "p");
|
||||||
|
builder.AddContent(s++, $"Hello {Name}!");
|
||||||
|
builder.CloseElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AsyncComponent : ComponentBase
|
||||||
|
{
|
||||||
|
private static WeatherRow[] _weatherData = new[]
|
||||||
|
{
|
||||||
|
new WeatherRow
|
||||||
|
{
|
||||||
|
DateFormatted = "06/05/2018",
|
||||||
|
TemperatureC = 1,
|
||||||
|
Summary = "Freezing",
|
||||||
|
TemperatureF = 33
|
||||||
|
},
|
||||||
|
new WeatherRow
|
||||||
|
{
|
||||||
|
DateFormatted = "07/05/2018",
|
||||||
|
TemperatureC = 14,
|
||||||
|
Summary = "Bracing",
|
||||||
|
TemperatureF = 57
|
||||||
|
},
|
||||||
|
new WeatherRow
|
||||||
|
{
|
||||||
|
DateFormatted = "08/05/2018",
|
||||||
|
TemperatureC = -13,
|
||||||
|
Summary = "Freezing",
|
||||||
|
TemperatureF = 9
|
||||||
|
},
|
||||||
|
new WeatherRow
|
||||||
|
{
|
||||||
|
DateFormatted = "09/05/2018",
|
||||||
|
TemperatureC = -16,
|
||||||
|
Summary = "Balmy",
|
||||||
|
TemperatureF = 4
|
||||||
|
},
|
||||||
|
new WeatherRow
|
||||||
|
{
|
||||||
|
DateFormatted = "10/05/2018",
|
||||||
|
TemperatureC = 2,
|
||||||
|
Summary = "Chilly",
|
||||||
|
TemperatureF = 29
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public class WeatherRow
|
||||||
|
{
|
||||||
|
public string DateFormatted { get; set; }
|
||||||
|
public int TemperatureC { get; set; }
|
||||||
|
public string Summary { get; set; }
|
||||||
|
public int TemperatureF { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public WeatherRow[] RowsToDisplay { get; set; }
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
// Simulate an async workflow.
|
||||||
|
await Task.Yield();
|
||||||
|
RowsToDisplay = _weatherData;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
var s = 0;
|
||||||
|
builder.OpenElement(s++, "table");
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
builder.OpenElement(s++, "thead");
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
builder.OpenElement(s++, "tr");
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
|
||||||
|
builder.OpenElement(s++, "th");
|
||||||
|
builder.AddContent(s++, "Date");
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
|
||||||
|
builder.OpenElement(s++, "th");
|
||||||
|
builder.AddContent(s++, "Summary");
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
|
||||||
|
builder.OpenElement(s++, "th");
|
||||||
|
builder.AddContent(s++, "F");
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
|
||||||
|
builder.OpenElement(s++, "th");
|
||||||
|
builder.AddContent(s++, "C");
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
builder.OpenElement(s++, "tbody");
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
if (RowsToDisplay != null)
|
||||||
|
{
|
||||||
|
var s2 = s;
|
||||||
|
foreach (var element in RowsToDisplay)
|
||||||
|
{
|
||||||
|
s = s2;
|
||||||
|
builder.OpenElement(s++, "tr");
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
|
||||||
|
builder.OpenElement(s++, "td");
|
||||||
|
builder.AddContent(s++, element.DateFormatted);
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
|
||||||
|
builder.OpenElement(s++, "td");
|
||||||
|
builder.AddContent(s++, element.Summary);
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
|
||||||
|
builder.OpenElement(s++, "td");
|
||||||
|
builder.AddContent(s++, element.TemperatureF);
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
|
||||||
|
builder.OpenElement(s++, "td");
|
||||||
|
builder.AddContent(s++, element.TemperatureF);
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.AddMarkupContent(s++, "\n");
|
||||||
|
|
||||||
|
builder.CloseElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace BasicWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class ComponentsController : Controller
|
||||||
|
{
|
||||||
|
private static WeatherRow[] _weatherData = new[]
|
||||||
|
{
|
||||||
|
new WeatherRow
|
||||||
|
{
|
||||||
|
DateFormatted = "06/05/2018",
|
||||||
|
TemperatureC = 1,
|
||||||
|
Summary = "Freezing",
|
||||||
|
TemperatureF = 33
|
||||||
|
},
|
||||||
|
new WeatherRow
|
||||||
|
{
|
||||||
|
DateFormatted = "07/05/2018",
|
||||||
|
TemperatureC = 14,
|
||||||
|
Summary = "Bracing",
|
||||||
|
TemperatureF = 57
|
||||||
|
},
|
||||||
|
new WeatherRow
|
||||||
|
{
|
||||||
|
DateFormatted = "08/05/2018",
|
||||||
|
TemperatureC = -13,
|
||||||
|
Summary = "Freezing",
|
||||||
|
TemperatureF = 9
|
||||||
|
},
|
||||||
|
new WeatherRow
|
||||||
|
{
|
||||||
|
DateFormatted = "09/05/2018",
|
||||||
|
TemperatureC = -16,
|
||||||
|
Summary = "Balmy",
|
||||||
|
TemperatureF = 4
|
||||||
|
},
|
||||||
|
new WeatherRow
|
||||||
|
{
|
||||||
|
DateFormatted = "10/05/2018",
|
||||||
|
TemperatureC = 2,
|
||||||
|
Summary = "Chilly",
|
||||||
|
TemperatureF = 29
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[HttpGet("/components")]
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("/WeatherData")]
|
||||||
|
[Produces("application/json")]
|
||||||
|
public IActionResult WeatherData()
|
||||||
|
{
|
||||||
|
return Ok(_weatherData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WeatherRow
|
||||||
|
{
|
||||||
|
public string DateFormatted { get; internal set; }
|
||||||
|
public int TemperatureC { get; internal set; }
|
||||||
|
public string Summary { get; internal set; }
|
||||||
|
public int TemperatureF { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
@using BasicWebSite.Services
|
||||||
|
@inject WeatherForecastService ForecastService
|
||||||
|
|
||||||
|
<h1>Weather forecast</h1>
|
||||||
|
|
||||||
|
<p>This component demonstrates fetching data from the server.</p>
|
||||||
|
|
||||||
|
@if (forecasts == null)
|
||||||
|
{
|
||||||
|
<p><em>Loading...</em></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>Weather data for @(StartDate.ToString("MM/dd/yyyy"))</p>
|
||||||
|
<table class='table'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Temp. (C)</th>
|
||||||
|
<th>Temp. (F)</th>
|
||||||
|
<th>Summary</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var forecast in forecasts)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>@forecast.DateFormatted</td>
|
||||||
|
<td>@forecast.TemperatureC</td>
|
||||||
|
<td>@forecast.TemperatureF</td>
|
||||||
|
<td>@forecast.Summary</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
[Parameter] DateTime StartDate { get; set; }
|
||||||
|
|
||||||
|
WeatherForecast[] forecasts;
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
// If no value was given in the URL for StartDate, apply a default
|
||||||
|
if (StartDate == default)
|
||||||
|
{
|
||||||
|
StartDate = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
forecasts = await ForecastService.GetForecastAsync(StartDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<p>Hello world!</p>
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
|
||||||
|
namespace BasicWebSite.Services
|
||||||
|
{
|
||||||
|
public class WeatherForecastService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
|
public WeatherForecastService(HttpClient httpClient)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
|
||||||
|
{
|
||||||
|
var result = await _httpClient.GetAsync("/WeatherData");
|
||||||
|
result.EnsureSuccessStatusCode();
|
||||||
|
var dataString = await result.Content.ReadAsStringAsync();
|
||||||
|
var weatherData = JsonConvert.DeserializeObject<WeatherForecast[]>(
|
||||||
|
dataString, new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
ContractResolver = new DefaultContractResolver
|
||||||
|
{
|
||||||
|
NamingStrategy = new CamelCaseNamingStrategy()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return weatherData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WeatherForecast
|
||||||
|
{
|
||||||
|
public string DateFormatted { get; set; }
|
||||||
|
public int TemperatureC { get; set; }
|
||||||
|
public string Summary { get; set; }
|
||||||
|
public int TemperatureF { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using BasicWebSite.Services;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
||||||
namespace BasicWebSite
|
namespace BasicWebSite
|
||||||
{
|
{
|
||||||
|
|
@ -48,6 +53,22 @@ namespace BasicWebSite
|
||||||
services.AddTransient<ServiceActionFilter>();
|
services.AddTransient<ServiceActionFilter>();
|
||||||
services.AddScoped<TestResponseGenerator>();
|
services.AddScoped<TestResponseGenerator>();
|
||||||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||||
|
services.TryAddSingleton(CreateWeatherForecastService);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For manual debug only (running this test site with F5)
|
||||||
|
// This needs to be changed to match the site host
|
||||||
|
private WeatherForecastService CreateWeatherForecastService(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
var contextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
|
||||||
|
var httpContext = contextAccessor.HttpContext;
|
||||||
|
if (httpContext == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Needs a request context!");
|
||||||
|
}
|
||||||
|
var client = new HttpClient();
|
||||||
|
client.BaseAddress = new Uri($"{httpContext.Request.Scheme}://{httpContext.Request.Host}");
|
||||||
|
return new WeatherForecastService(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app)
|
public void Configure(IApplicationBuilder app)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
@using BasicWebSite.RazorComponents;
|
||||||
|
<h1>Razor components</h1>
|
||||||
|
<div id="Greetings">
|
||||||
|
@(await Html.RenderComponentAsync<Greetings>())
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="FetchData">
|
||||||
|
@(await Html.RenderComponentAsync<FetchData>(new { StartDate = new DateTime(2019, 01, 15) }))
|
||||||
|
</div>
|
||||||
Loading…
Reference in New Issue