Skip to content

Commit 29ac9b3

Browse files
Ability to supply a pre-instantiated component as an API result (#12)
1 parent b44ea8c commit 29ac9b3

File tree

7 files changed

+155
-6
lines changed

7 files changed

+155
-6
lines changed

src/Components/Samples/BlazorUnitedApp/Pages/FetchData.razor

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ else
4141
@code {
4242
WeatherForecast[]? forecasts;
4343

44+
[Parameter] public DateTime ForDate { get; set; } = DateTime.Now;
45+
4446
protected override async Task OnInitializedAsync()
4547
{
46-
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
48+
forecasts = await ForecastService.GetForecastAsync(ForDate);
4749
}
4850
}

src/Components/Samples/BlazorUnitedApp/Program.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using BlazorUnitedApp.Data;
5+
using BlazorUnitedApp.Pages;
6+
using Microsoft.AspNetCore.Mvc.RazorPages;
57

68
var builder = WebApplication.CreateBuilder(args);
79

@@ -29,4 +31,12 @@
2931
app.MapRazorComponents();
3032
app.MapBlazorHub();
3133

34+
app.Map("/mycomponent", () =>
35+
{
36+
return new RazorComponentResult
37+
(
38+
new FetchData { ForDate = DateTime.Now.AddYears(1000) }
39+
);
40+
});
41+
3242
app.Run();
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
4+
using Microsoft.AspNetCore.Components;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using System.Diagnostics.CodeAnalysis;
7+
8+
namespace Microsoft.AspNetCore.Mvc.RazorPages;
9+
10+
// This wraps any other registered component activator (or the default), adding the behavior
11+
// that a particular component instance can be pre-registered as the instance we'll use the
12+
// first time that component type is required.
13+
//
14+
// This is a hack to allow endpoints to return pre-constructed component instances and have
15+
// them inserted into the render tree inside their layouts, etc. For a real implementation,
16+
// this technique might not be too bad, or we might choose to change the renderer and diffing
17+
// system to have a concept of some whole new frame type describing pre-instantiated components.
18+
19+
internal sealed class PassiveComponentInstanceActivator : IComponentActivator
20+
{
21+
private readonly IComponentActivator _underlyingActivator;
22+
private readonly IComponent? _componentInstance;
23+
private Type? _componentInstanceType;
24+
25+
public PassiveComponentInstanceActivator(IServiceProvider requestServices, IComponent componentInstance)
26+
{
27+
_underlyingActivator = requestServices.GetService<IComponentActivator>()
28+
?? DefaultComponentActivator.Instance;
29+
_componentInstanceType = componentInstance.GetType();
30+
_componentInstance = componentInstance;
31+
}
32+
33+
public IComponent CreateInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type componentType)
34+
{
35+
// If it's the first time we're asked for the pre-supplied instance, supply it
36+
if (componentType == _componentInstanceType)
37+
{
38+
_componentInstanceType = null;
39+
return _componentInstance!;
40+
}
41+
42+
// Otherwise fall back on the underlying implementation
43+
return _underlyingActivator.CreateInstance(componentType);
44+
}
45+
46+
// TODO: Instead of duplicating this code, put it somewhere we can share the type
47+
internal sealed class DefaultComponentActivator : IComponentActivator
48+
{
49+
public static IComponentActivator Instance { get; } = new DefaultComponentActivator();
50+
51+
public IComponent CreateInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type componentType)
52+
{
53+
if (!typeof(IComponent).IsAssignableFrom(componentType))
54+
{
55+
throw new ArgumentException($"The type {componentType.FullName} does not implement {nameof(IComponent)}.", nameof(componentType));
56+
}
57+
58+
return (IComponent)Activator.CreateInstance(componentType)!;
59+
}
60+
}
61+
}

src/Mvc/Mvc.RazorPages/src/PassiveComponentRenderer.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,21 @@ public PassiveComponentRenderer(
3636
_htmlEncoder = htmlEncoder;
3737
}
3838

39-
public async Task HandleRequest(HttpContext httpContext, Type componentType, IReadOnlyDictionary<string, object?>? parameters)
39+
public Task HandleRequest(HttpContext httpContext, IComponent componentInstance, IReadOnlyDictionary<string, object?>? parameters)
4040
{
41-
using var htmlRenderer = new HtmlRenderer(httpContext.RequestServices, _loggerFactory, _viewBufferScope);
41+
return HandleRequest(httpContext, componentInstance, componentInstance.GetType(), parameters);
42+
}
43+
44+
public Task HandleRequest(HttpContext httpContext, Type componentType, IReadOnlyDictionary<string, object?>? parameters)
45+
{
46+
return HandleRequest(httpContext, null, componentType, parameters);
47+
}
48+
49+
private async Task HandleRequest(HttpContext httpContext, IComponent? componentInstance, Type componentType, IReadOnlyDictionary<string, object?>? parameters)
50+
{
51+
using var htmlRenderer = componentInstance is null
52+
? new HtmlRenderer(httpContext.RequestServices, _loggerFactory, _viewBufferScope)
53+
: new HtmlRenderer(httpContext.RequestServices, _loggerFactory, _viewBufferScope, new PassiveComponentInstanceActivator(httpContext.RequestServices, componentInstance));
4254
var staticComponentRenderer = new StaticComponentRenderer(htmlRenderer);
4355

4456
var routeData = httpContext.GetRouteData();

src/Mvc/Mvc.RazorPages/src/PublicAPI.Unshipped.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@ Microsoft.AspNetCore.Mvc.RazorPages.PageBase.TryUpdateModelAsync<TModel>(TModel!
1010
Microsoft.AspNetCore.Mvc.RazorPages.PageModel.TryUpdateModelAsync<TModel>(TModel! model, string! name, Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider! valueProvider, params System.Linq.Expressions.Expression<System.Func<TModel!, object?>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
1111
Microsoft.AspNetCore.Mvc.RazorPages.PageModel.TryUpdateModelAsync<TModel>(TModel! model, string! name, params System.Linq.Expressions.Expression<System.Func<TModel!, object?>!>![]! includeExpressions) -> System.Threading.Tasks.Task<bool>!
1212
Microsoft.AspNetCore.Mvc.RazorPages.RazorComponentResult
13+
Microsoft.AspNetCore.Mvc.RazorPages.RazorComponentResult.RazorComponentResult(Microsoft.AspNetCore.Components.IComponent! component) -> void
14+
Microsoft.AspNetCore.Mvc.RazorPages.RazorComponentResult.RazorComponentResult(Microsoft.AspNetCore.Components.IComponent! component, System.Collections.Generic.IReadOnlyDictionary<string!, object?>? parameters) -> void
1315
Microsoft.AspNetCore.Mvc.RazorPages.RazorComponentResult.RazorComponentResult(System.Type! componentType) -> void
1416
Microsoft.AspNetCore.Mvc.RazorPages.RazorComponentResult.RazorComponentResult(System.Type! componentType, System.Collections.Generic.IReadOnlyDictionary<string!, object?>? parameters) -> void
1517
Microsoft.AspNetCore.Mvc.RazorPages.RazorComponentResult.WithParameter(string! name, object? value) -> Microsoft.AspNetCore.Mvc.RazorPages.RazorComponentResult!
18+
Microsoft.AspNetCore.Mvc.RazorPages.RazorComponentResult<TComponent>
19+
Microsoft.AspNetCore.Mvc.RazorPages.RazorComponentResult<TComponent>.RazorComponentResult() -> void
20+
Microsoft.AspNetCore.Mvc.RazorPages.RazorComponentResult<TComponent>.RazorComponentResult(System.Collections.Generic.IReadOnlyDictionary<string!, object?>? parameters) -> void
21+
Microsoft.AspNetCore.Mvc.RazorPages.RazorComponentResult<TComponent>.RazorComponentResult(TComponent component, System.Collections.Generic.IReadOnlyDictionary<string!, object?>? parameters = null) -> void
1622
static Microsoft.AspNetCore.Builder.RazorComponentsEndpointRouteBuilderExtensions.AddRazorComponents(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> void
1723
static Microsoft.AspNetCore.Builder.RazorComponentsEndpointRouteBuilderExtensions.MapRazorComponents(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints) -> void

src/Mvc/Mvc.RazorPages/src/RazorComponentResult.cs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.AspNetCore.Components;
45
using Microsoft.AspNetCore.Http;
56
using Microsoft.Extensions.DependencyInjection;
67

@@ -11,9 +12,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages;
1112
/// </summary>
1213
public class RazorComponentResult : IResult
1314
{
14-
private readonly Type _componentType;
15+
private readonly Type? _componentType;
1516
private readonly IReadOnlyDictionary<string, object?>? _parameters;
1617
private Dictionary<string, object?>? _modifiedParameters;
18+
private readonly IComponent? _componentInstance;
1719

1820
/// <summary>
1921
/// TODO
@@ -26,7 +28,25 @@ public RazorComponentResult(Type componentType)
2628
/// <summary>
2729
/// TODO
2830
/// </summary>
29-
public RazorComponentResult(Type componentType, IReadOnlyDictionary<string, object?>? parameters) : this(componentType)
31+
public RazorComponentResult(Type componentType, IReadOnlyDictionary<string, object?>? parameters)
32+
: this(componentType)
33+
{
34+
_parameters = parameters;
35+
}
36+
37+
/// <summary>
38+
/// TODO
39+
/// </summary>
40+
public RazorComponentResult(IComponent component)
41+
{
42+
_componentInstance = component;
43+
}
44+
45+
/// <summary>
46+
/// TODO
47+
/// </summary>
48+
public RazorComponentResult(IComponent component, IReadOnlyDictionary<string, object?>? parameters)
49+
: this(component)
3050
{
3151
_parameters = parameters;
3252
}
@@ -49,6 +69,37 @@ public RazorComponentResult WithParameter(string name, object? value)
4969
Task IResult.ExecuteAsync(HttpContext httpContext)
5070
{
5171
var renderer = httpContext.RequestServices.GetRequiredService<PassiveComponentRenderer>();
52-
return renderer.HandleRequest(httpContext, _componentType, _modifiedParameters ?? _parameters);
72+
var parameters = _modifiedParameters ?? _parameters;
73+
return _componentInstance is not null
74+
? renderer.HandleRequest(httpContext, _componentInstance, parameters)
75+
: renderer.HandleRequest(httpContext, _componentType!, parameters);
76+
}
77+
}
78+
79+
/// <summary>
80+
/// TODO
81+
/// </summary>
82+
public class RazorComponentResult<TComponent> : RazorComponentResult where TComponent : IComponent
83+
{
84+
/// <summary>
85+
/// TODO
86+
/// </summary>
87+
public RazorComponentResult() : base(typeof(TComponent))
88+
{
89+
}
90+
91+
/// <summary>
92+
/// TODO
93+
/// </summary>
94+
public RazorComponentResult(IReadOnlyDictionary<string, object?>? parameters) : base(typeof(TComponent), parameters)
95+
{
96+
}
97+
98+
/// <summary>
99+
/// TODO
100+
/// </summary>
101+
public RazorComponentResult(TComponent component, IReadOnlyDictionary<string, object?>? parameters = null)
102+
: base(component, parameters)
103+
{
53104
}
54105
}

src/Mvc/Mvc.ViewFeatures/src/RazorComponents/HtmlRenderer.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ public HtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFacto
3535
_serviceProvider = serviceProvider;
3636
}
3737

38+
public HtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, IViewBufferScope viewBufferScope, IComponentActivator componentActivator)
39+
: base(serviceProvider, loggerFactory, componentActivator)
40+
{
41+
_viewBufferScope = viewBufferScope;
42+
_serviceProvider = serviceProvider;
43+
}
44+
3845
public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault();
3946

4047
public ChannelReader<RenderBatch> StreamingRenderBatches { get; private set; }

0 commit comments

Comments
 (0)