public Task SetParametersAsync(ParameterView parameters) => throw new NotImplementedException();
/// <inheritdoc /> public Task SetParametersAsync(ParameterView parameters) { parameters.SetParameterProperties(this); Render(); return(Task.CompletedTask); }
static CascadingValue <T> CreateCascadingValueComponent <T>(T value, string name = null) { var supplier = new CascadingValue <T>(); var renderer = new TestRenderer(); supplier.Attach(new RenderHandle(renderer, 0)); var supplierParams = new Dictionary <string, object> { { "Value", value } }; if (name != null) { supplierParams.Add("Name", name); } renderer.Dispatcher.InvokeAsync((Action)(() => supplier.SetParametersAsync(ParameterView.FromDictionary(supplierParams)))); return(supplier); }
/// <inheritdoc /> public Task SetParametersAsync(ParameterView parameters) { // Implementing the parameter binding manually, instead of just calling // parameters.SetParameterProperties(this), is just a very slight perf optimization // and makes it simpler impose rules about the params being required or not. var hasSuppliedValue = false; var previousValue = Value; var previousFixed = IsFixed; Value = default; ChildContent = null; Name = null; IsFixed = false; foreach (var parameter in parameters) { if (parameter.Name.Equals(nameof(Value), StringComparison.OrdinalIgnoreCase)) { Value = (TValue)parameter.Value; hasSuppliedValue = true; } else if (parameter.Name.Equals(nameof(ChildContent), StringComparison.OrdinalIgnoreCase)) { ChildContent = (RenderFragment)parameter.Value; } else if (parameter.Name.Equals(nameof(Name), StringComparison.OrdinalIgnoreCase)) { Name = (string)parameter.Value; if (string.IsNullOrEmpty(Name)) { throw new ArgumentException($"The parameter '{nameof(Name)}' for component '{nameof(CascadingValue<TValue>)}' does not allow null or empty values."); } } else if (parameter.Name.Equals(nameof(IsFixed), StringComparison.OrdinalIgnoreCase)) { IsFixed = (bool)parameter.Value; } else { throw new ArgumentException($"The component '{nameof(CascadingValue<TValue>)}' does not accept a parameter with the name '{parameter.Name}'."); } } if (_hasSetParametersPreviously && IsFixed != previousFixed) { throw new InvalidOperationException($"The value of {nameof(IsFixed)} cannot be changed dynamically."); } _hasSetParametersPreviously = true; // It's OK for the value to be null, but some "Value" param must be supplied // because it serves no useful purpose to have a <CascadingValue> otherwise. if (!hasSuppliedValue) { throw new ArgumentException($"Missing required parameter '{nameof(Value)}' for component '{GetType().Name}'."); } // Rendering is most efficient when things are queued from rootmost to leafmost. // Given a components A (parent) -> B (child), you want them to be queued in order // [A, B] because if you had [B, A], then the render for A might change B's params // making it render again, so you'd render [B, A, B], which is wasteful. // At some point we might consider making the render queue actually enforce this // ordering during insertion. // // For the CascadingValue component, this observation is why it's important to render // ourself before notifying subscribers (which can be grandchildren or deeper). // If we rerendered subscribers first, then our own subsequent render might cause an // further update that makes those nested subscribers get rendered twice. _renderHandle.Render(Render); if (_subscribers != null && ChangeDetection.MayHaveChanged(previousValue, Value)) { NotifySubscribers(); } return(Task.CompletedTask); }
public Task SetParametersAsync(ParameterView parameters) { throw new System.NotImplementedException(); }
public static (IList <ComponentParameter> parameterDefinitions, IList <object> parameterValues) FromParameterView(ParameterView parameters) { var parameterDefinitions = new List <ComponentParameter>(); var parameterValues = new List <object>(); foreach (var kvp in parameters) { var valueType = kvp.Value?.GetType(); parameterDefinitions.Add(new ComponentParameter { Name = kvp.Name, TypeName = valueType?.FullName, Assembly = valueType?.Assembly?.GetName()?.Name }); parameterValues.Add(kvp.Value); } return(parameterDefinitions, parameterValues); }
public override Task SetParametersAsync(ParameterView parameters) => base.SetParametersAsync(parameters);
// It's internal because there isn't a known use case for user code comparing // ParameterView instances, and even if there was, it's unlikely it should // use these equality rules which are designed for their effect on rendering. internal bool DefinitelyEquals(ParameterView oldParameters) { // In general we can't detect mutations on arbitrary objects. We can't trust // things like .Equals or .GetHashCode because they usually only tell us about // shallow changes, not deep mutations. So we return false if both: // [1] All the parameters are known to be immutable (i.e., Type.IsPrimitive // or is in a known set of common immutable types) // [2] And all the parameter values are equal to their previous values // Otherwise be conservative and return false. // To make this check cheaper, since parameters are virtually always generated in // a deterministic order, we don't bother to account for reordering, so if any // of the names don't match sequentially we just return false too. // // The logic here may look kind of epic, and would certainly be simpler if we // used ParameterEnumerator.GetEnumerator(), but it's perf-critical and this // implementation requires a lot fewer instructions than a GetEnumerator-based one. var oldIndex = oldParameters._ownerIndex; var newIndex = _ownerIndex; var oldEndIndexExcl = oldIndex + oldParameters._frames[oldIndex].ComponentSubtreeLength; var newEndIndexExcl = newIndex + _frames[newIndex].ComponentSubtreeLength; while (true) { // First, stop if we've reached the end of either subtree oldIndex++; newIndex++; var oldFinished = oldIndex == oldEndIndexExcl; var newFinished = newIndex == newEndIndexExcl; if (oldFinished || newFinished) { return(oldFinished == newFinished); // Same only if we have same number of parameters } else { // Since neither subtree has finished, it's safe to read the next frame from both ref var oldFrame = ref oldParameters._frames[oldIndex]; ref var newFrame = ref _frames[newIndex]; // Stop if we've reached the end of either subtree's sequence of attributes oldFinished = oldFrame.FrameType != RenderTreeFrameType.Attribute; newFinished = newFrame.FrameType != RenderTreeFrameType.Attribute; if (oldFinished || newFinished) { return(oldFinished == newFinished); // Same only if we have same number of parameters } else { if (!string.Equals(oldFrame.AttributeName, newFrame.AttributeName, StringComparison.Ordinal)) { return(false); // Different names } var oldValue = oldFrame.AttributeValue; var newValue = newFrame.AttributeValue; if (ChangeDetection.MayHaveChanged(oldValue, newValue)) { return(false); } } }
public Task SetParametersAsync(ParameterView parameters) { parameters.SetParameterProperties(this); _registry.SetContent(Name, ChildContent); return(Task.CompletedTask); }
public override Task SetParametersAsync(ParameterView parameters) { ReplaceChildContent(ref parameters); return(base.SetParametersAsync(parameters)); }
internal static RenderTreeFrame[] GetFrames(ParameterView parameters) => (RenderTreeFrame[])_framesField.GetValue(parameters);
public void UpdatesOutputWhenRouteDataChanges() { // Arrange/Act 1: Start on some route // Not asserting about the initial output, as that is covered by other tests var routeData = new RouteData(typeof(TestPageWithNoAuthorization), EmptyParametersDictionary); _renderer.RenderRootComponent(_authorizeRouteViewComponentId, ParameterView.FromDictionary(new Dictionary <string, object> { { nameof(AuthorizeRouteView.RouteData), routeData }, { nameof(AuthorizeRouteView.DefaultLayout), typeof(TestLayout) }, })); // Act 2: Move to another route var routeData2 = new RouteData(typeof(TestPageRequiringAuthorization), EmptyParametersDictionary); var render2Task = _renderer.Dispatcher.InvokeAsync(() => _authorizeRouteViewComponent.SetParametersAsync(ParameterView.FromDictionary(new Dictionary <string, object> { { nameof(AuthorizeRouteView.RouteData), routeData2 }, }))); // Assert: we retain the layout instance, and mutate its contents Assert.True(render2Task.IsCompletedSuccessfully); Assert.Equal(2, _renderer.Batches.Count); var batch2 = _renderer.Batches[1]; var diff = batch2.DiffsInOrder.Where(d => d.Edits.Any()).Single(); Assert.Collection(diff.Edits, edit => { // Inside the layout, we add the new content Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); Assert.Equal(1, edit.SiblingIndex); AssertFrame.Text(batch2.ReferenceFrames[edit.ReferenceFrameIndex], "Not authorized"); }, edit => { // ... and remove the old content Assert.Equal(RenderTreeEditType.RemoveFrame, edit.Type); Assert.Equal(2, edit.SiblingIndex); }); }
public void WhenAuthorizing_RendersCustomAuthorizingContentInsideLayout() { // Arrange var routeData = new RouteData(typeof(TestPageRequiringAuthorization), EmptyParametersDictionary); var authStateTcs = new TaskCompletionSource <AuthenticationState>(); _authenticationStateProvider.CurrentAuthStateTask = authStateTcs.Task; RenderFragment customAuthorizing = builder => builder.AddContent(0, "Hold on, we're checking your papers."); // Act var firstRenderTask = _renderer.RenderRootComponentAsync(_authorizeRouteViewComponentId, ParameterView.FromDictionary(new Dictionary <string, object> { { nameof(AuthorizeRouteView.RouteData), routeData }, { nameof(AuthorizeRouteView.DefaultLayout), typeof(TestLayout) }, { nameof(AuthorizeRouteView.Authorizing), customAuthorizing }, })); // Assert: renders layout containing "authorizing" message Assert.False(firstRenderTask.IsCompleted); var batch = _renderer.Batches.Single(); var layoutDiff = batch.GetComponentDiffs <TestLayout>().Single(); Assert.Collection(layoutDiff.Edits, edit => AssertPrependText(batch, edit, "Layout starts here"), edit => AssertPrependText(batch, edit, "Hold on, we're checking your papers."), edit => AssertPrependText(batch, edit, "Layout ends here")); }
public async Task WhenAuthorizing_RendersDefaultAuthorizingContentInsideLayout() { // Arrange var routeData = new RouteData(typeof(TestPageRequiringAuthorization), EmptyParametersDictionary); var authStateTcs = new TaskCompletionSource <AuthenticationState>(); _authenticationStateProvider.CurrentAuthStateTask = authStateTcs.Task; RenderFragment <AuthenticationState> customNotAuthorized = state => builder => builder.AddContent(0, $"Go away, {state.User.Identity.Name}"); // Act var firstRenderTask = _renderer.RenderRootComponentAsync(_authorizeRouteViewComponentId, ParameterView.FromDictionary(new Dictionary <string, object> { { nameof(AuthorizeRouteView.RouteData), routeData }, { nameof(AuthorizeRouteView.DefaultLayout), typeof(TestLayout) }, { nameof(AuthorizeRouteView.NotAuthorized), customNotAuthorized }, })); // Assert: renders layout containing "authorizing" message Assert.False(firstRenderTask.IsCompleted); var batch = _renderer.Batches.Single(); var layoutDiff = batch.GetComponentDiffs <TestLayout>().Single(); Assert.Collection(layoutDiff.Edits, edit => AssertPrependText(batch, edit, "Layout starts here"), edit => AssertPrependText(batch, edit, "Authorizing..."), edit => AssertPrependText(batch, edit, "Layout ends here")); // Act 2: updates when authorization completes authStateTcs.SetResult(new AuthenticationState( new ClaimsPrincipal(new TestIdentity { Name = "Bert" }))); await firstRenderTask; // Assert 2: Only the layout is updated batch = _renderer.Batches.Skip(1).Single(); var nonEmptyDiff = batch.DiffsInOrder.Where(d => d.Edits.Any()).Single(); Assert.Equal(layoutDiff.ComponentId, nonEmptyDiff.ComponentId); Assert.Collection(nonEmptyDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); Assert.Equal(1, edit.SiblingIndex); AssertFrame.Text(batch.ReferenceFrames[edit.ReferenceFrameIndex], "Go away, Bert"); }); }