[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.Html.Abstractions" />
|
||||
<Reference Include="Microsoft.Extensions.WebEncoders" />
|
||||
<Reference Include="Microsoft.AspNetCore.Components" />
|
||||
</ItemGroup>
|
||||
|
||||
</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.
|
||||
// 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.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace BasicWebSite
|
||||
{
|
||||
|
|
@ -48,6 +53,22 @@ namespace BasicWebSite
|
|||
services.AddTransient<ServiceActionFilter>();
|
||||
services.AddScoped<TestResponseGenerator>();
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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