I have been following along with Create and use ASP.NET Core Razor components
I am having an issue with this section
<div class="panel panel-default">
<div class="panel-heading">#Title</div>
<div class="panel-body">#ChildContent</div>
<button class="btn btn-primary" #onclick="OnClick">
Trigger a Parent component method
</button>
</div>
#code {
[Parameter]
public string Title { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public EventCallback<MouseEventArgs> OnClick { get; set; }
}
I keep getting the following error
Severity Code Description Project File Line Suppression State
Error CS0246 The type or namespace name 'MouseEventArgs' could not be found (are you missing a using directive or an assembly reference?) BlazorList D:\Development\BlazorApp1\BlazorList\Pages\ShowListComponent.razor 19 Active
The event names have been changed in Preview 9
from the blog (https://devblogs.microsoft.com/aspnet/asp-net-core-and-blazor-updates-in-net-core-3-0-preview-9/):
Replace Microsoft.AspNetCore.Components.UIEventArgs with
System.EventArgs and remove the “UI” prefix from all EventArgs derived
types (UIChangeEventArgs -> ChangeEventArgs, etc.).
So preview 8 and below need the UI prefix on the event name and the Microsoft.AspNetCore.Components.UIEventArgs namespace.
preview 9 does not need the UI prefix on the event name. and requires the namespace System.EventArgs
Related
I'm developing a UI library on top of Blazor and I like to enable the bind* syntax for my components so consumers could use it too.
Context
I've read and seen plenty of examples so on the most basic level let's say we have the following custom component named RawCustomInput.razor:
<input type="text" value="#Value" #onchange="OnValueChanged" style="width: 5rem" />
#code {
[Parameter]
public string? Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
private async Task OnValueChanged(ChangeEventArgs args)
=> await ValueChanged.InvokeAsync(args.Value as string);
}
And another component that consumes it named InputPage.razor like this:
#page "/input"
<PageTitle>InputPage</PageTitle>
<RawCustomInput
#bind-Value="#_name" />
#if (!string.IsNullOrEmpty(_name))
{
<p>#_name</p>
}
#code {
private string? _name = null;
}
Problem
Now, the above works but say I want to change the event from onchange to oninput on the consumer side? so I tried to do something like this #bind-Value:event="oninput" but then I get the following error:
does not have a property matching the name 'oninput'
So I thought I could solve this by introducing #oninput="OnValueChanged" to RawCustomInput.razor which looks like this:
<input type="text" value="#Value" #onchange="OnValueChanged" #oninput="OnValueChanged" style="width: 5rem" />
#code {
[Parameter]
public string? Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
private async Task OnValueChanged(ChangeEventArgs args)
=> await ValueChanged.InvokeAsync(args.Value as string);
}
But this didn't work so I read the error again and realized that something like #bind-Value:event="ValueChanged" might work and it worked! so what I did is add another property that looks like this:
[Parameter]
public EventCallback<string> ValueChanging { get; set; }
So now my RawCustomInput.razor looks like this:
<input type="text" value="#Value" #onchange="OnChange" #oninput="OnInput" style="width: 5rem" />
#code {
[Parameter]
public string? Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
[Parameter]
public EventCallback<string> ValueChanging { get; set; }
private async Task OnChange(ChangeEventArgs args)
=> await ValueChanged.InvokeAsync(args.Value as string);
private async Task OnInput(ChangeEventArgs args)
=> await ValueChanging.InvokeAsync(args.Value as string);
}
And finally I could use the syntax below that worked too which is great but raises few questions.
<RawCustomInput
#bind-Value="#_name"
#bind-Value:event="ValueChanging" />
Questions
It's possible to use the syntax bind-{Property}="{Expression}" for binding but is it possible to enable this syntax bind="{Expression}" on custom components? because when I'm trying to do something like this <RawCustomInput #bind="#_name" /> it throws with the following error does not have a property matching the name '#bind' but this <input type="text" #bind="#_name" /> works, why?
It's possible to do <input type="text" #bind="#_name" #bind:event="oninput" /> but doing <RawCustomInput #bind-Value="#_name" #bind-Value:event="oninput" /> throws with the following error does not have a property matching the name 'oninput', why? to achieve a similar thing I have to introduce additional property as I pointed above but then it's seems a bit odd that for bind:event I need to provide the name of the event whereas for bind-{Property}:event I need to pass a property which might makes sense but I'm not sure I understand the reason.
First you need to understand the difference between components and elements.
<input type="text" value="#Value" .. is an element declaration. It's html code.
<RawCustomInput #bind-Value="#_name" /> is a component declaration.
Second, you are defining stuff in the Razor language, not true C#. #bind and #bind-value are directives to the Razor compiler, and are handled differently.
The #bind is used to bind elements. It gets compiled by the Razor compiler into C# code like this:
__builder.AddAttribute(19, "value", BindConverter.FormatValue(this.surName));
__builder.AddAttribute(20, "onchange", EventCallback.Factory.CreateBinder(this, __value => this.surName = __value, this.surName));
Note that it uses various factory classes to build the "getter" to provide a correctly formatted string to the input value and a "setter" to handle the generated onchange event from the input to update the variable.
In comparison, this is the code for the component #bind-value on a component
__builder.AddAttribute(11, "Value", RuntimeHelpers.TypeCheck<String>(this.firstName));
__builder.AddAttribute(12, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<String>>(EventCallback.Factory.Create<String>(this, RuntimeHelpers.CreateInferredEventCallback(this, __value => this.firstName = __value, this.firstName))));
It's setting the value of the Parameter Value and creating an anonymous function to set the value from the eventcallback ValueChanged. There is no direct wiring into the underlying input element declared within the component. It's setting the component parameter and sinking the component event. How they are wired up is up to you the designer.
To demonstrate, here's a very different component wiring for a "checkbox":
<button class="#this.ButtonCss" #onclick=this.OnValueChanged>#this.ButtonText</button>
#code {
[Parameter] public bool Value { get; set; }
[Parameter] public EventCallback<bool> ValueChanged { get; set; }
private string ButtonCss => Value ? "btn btn-success": "btn btn-danger";
private string ButtonText => Value ? "Enabled" : "Disabled";
private async Task OnValueChanged()
=> await ValueChanged.InvokeAsync(!this.Value);
}
When you declare your components as Razor Components you are restricted by the Razor language. For 99% of components this is Ok. But, if you want to do more exotic stuff, you need to drop back to writing your component directly as a C# class.
This shows one way of handling onchange/oninput.
#if(this.ChangeOnInput)
{
<input type="text" value="#Value" #oninput="OnValueChanged" class="form-control" />
}
else
{
<input type="text" value="#Value" #onchange="OnValueChanged" class="form-control" />
}
#code {
[Parameter] public string? Value { get; set; }
[Parameter] public bool ChangeOnInput { get; set; }
[Parameter] public EventCallback<string> ValueChanged { get; set; }
private async Task OnValueChanged(ChangeEventArgs args)
=> await ValueChanged.InvokeAsync(args.Value as string);
}
So I am building a component library in blazor and I am adding support for the input components to have Blazor built in validation.
#inherits WindyInputTextBase;
#if(!string.IsNullOrEmpty(Label))
{
<label for="#Id" class="block text-sm font-medium">#Label</label>
}
<input class="mt-3 p-2 focus:outline-none focus:ring focus:ring-primary-light focus:border-primary block w-full shadow-sm sm:text-sm border border-gray-300 rounded-md #CssClass" id="#Id" name="#Id" #bind="#CurrentValue" />
<!--Div is empty when validation is not done.-->
<div class="text-danger text-sm mb-3">
<ValidationMessage For="#ValidationFor" />
</div>
The base class for the input component:
public class WindyInputTextBase : WindyInputBase<string>
{
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out string result, [NotNullWhen(false)] out string? validationErrorMessage)
{
result = value is null ? string.Empty : value;
validationErrorMessage = null;
return true;
}
}
And the base class of that one:
public abstract class WindyInputBase<T> : InputBase<T>
{
[Parameter]
public string? Label { get; set; }
[Parameter]
public Expression<Func<T>>? ValidationFor { get; set; }
}
The component (and others using the same base) work fine, but I get this error when using them outside an EditForm component:
Unhandled exception rendering component: WindyUI.Form.EditForm.WindyInputText requires a cascading parameter of type EditContext. For example, you can use WindyUI.Form.EditForm.WindyInputText inside an EditForm.
System.InvalidOperationException: WindyUI.Form.EditForm.WindyInputText requires a cascading parameter of type EditContext. For example, you can use WindyUI.Form.EditForm.WindyInputText inside an EditForm.
This is fine and expected, but forces me to write two sets of Input components, one that has validation and one that doesn't. I know MudBlazor does what I am looking to do, but I don't know how they do it exactly and their source code isn't all to clear for me since there is no inheritance from InputBase.
Is there a way to create a component with blazor built-in validation that can be used both inside and outside an EditForm component? Of course, this means that no built in validation is fine when not usig the components inside an EditForm.
Thanks,
I'm playing around with the custom template in Blazor and I'm trying to find to a way to two-way bind a CascadingValue or achieve something similar. Right now I have the following template.
#if (PopupVisible)
{
<DxPopup>
<HeaderTemplate>
<h4 class="modal-title">#HeaderText</h4>
<button type="button" class="close" #onclick="#UpdatePopupVisible">×</button>
</HeaderTemplate>
<ChildContent>
<div class="modal-body">
<div class="container-fluid">
#bodyContent
</div>
</div>
<div class="modal-footer">
#footerContent
<button class="btn btn-secondary" #onclick="UpdatePopupVisible">Cancel</button>
</div>
</ChildContent>
</DxPopup>
}
#code {
[CascadingParameter] public bool PopupVisible { get; set; }
[CascadingParameter] public EventCallback<bool> PopupVisibleChanged { get; set; }
[Parameter] public RenderFragment HeaderText { get; set; }
[Parameter] public RenderFragment footerContent { get; set; }
[Parameter] public RenderFragment bodyContent { get; set; }
private async Task UpdatePopupVisible()
{
PopupVisible = false;
await PopupVisibleChanged.InvokeAsync(PopupVisible);
}
}
Then I have a component that implements this template(child), and then I have that component called with a button press(parent). What I want to know is if there is a way to bind the PopupVisible parameter from the parent without having to bind it the child and having the child pass it to the template. I haven't found a way to two-way bind a cascading parameter but if possible I think that would be the best way to do so. Outside of that, I'm not sure if there is another way or I'm going to have to go with my current idea of passing the value.
You can't do two-way binding with cascading parameters. Cascading means flowing downstream, from parent to child, and not the other way around.
I'm not sure I understand your question...however, if you wish to pass a value from a parent component and back; you can do the following:
Note: This is a two-way Component data binding
Child Component
#code
{
private bool visible;
[Parameter]
public bool PopupVisible
{
get { return visible }
set
{
if (visible != value)
{
visible = value;
}
}
}
[Parameter] public EventCallback<bool> PopupVisibleChanged { get; set; }
// Invoke the EventCallback to update the parent component' private field visible with the new value.
private Task UpdatePopupVisible()
{
PopupVisible = false;
return PopupVisibleChanged.InvokeAsync(PopupVisible);
}
}
Usage
#page "/"
<DxPopup #bind-PopupVisible="visible" />
#code {
private bool visible;
}
Note: If you need some explanation, and believe that I did not answer your question, don't hesitate to tell me, but please take the time to phrase your questions... I could not completely understand questions.
what you can do is, Cascade the parent component and in the child component, access the parent Property you want to change like this:
Parent:
<CascadingValue Value="this">
<Child />
</CascadingValue>
Child:
[CascadingParameter]
public Parent Parent { get; set; }
.....
private void ChangeParentProperty()
{
Parent.Property = ....;
}
Any doubt feel free to ask.
I was watching a tutorial in Blazor. Then I came across this code and I can't seem to find it in the Internet or I think I'm not using the right terms for searching atleast.
#code{
[Parameter]
public IList<Todo> Todo {get; set;}
}
Is it only exclusive in blazor or it is available in c#.
Kindly give some references. Thanks in advance.
This is explained in Create and use ASP.NET Core Razor components, specifically in the Component Parameters section.
[Parameter] is used to mark the component parameters that can be set when the component is used in another page. Borrowing from the doc example this component doesn't have any parameters :
<div class="panel panel-default">
<div class="panel-heading">#Title</div>
<div class="panel-body">#ChildContent</div>
<button class="btn btn-primary" #onclick="OnClick">
Trigger a Parent component method
</button>
</div>
#code {
public string Title { get; set; }
public RenderFragment ChildContent { get; set; }
public EventCallback<MouseEventArgs> OnClick { get; set; }
}
Without the [Parameter] attribute, those are just public properties that can't be set from other pages. The following line would be invalid :
<ChildComponent Title="Panel Title from Parent" />
While this :
<div class="panel panel-default">
<div class="panel-heading">#Title</div>
<div class="panel-body">#ChildContent</div>
<button class="btn btn-primary" #onclick="OnClick">
Trigger a Parent component method
</button>
</div>
#code {
[Parameter]
public string Title { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public EventCallback<MouseEventArgs> OnClick { get; set; }
}
Allows us to set the parameters whenever we use that component :
<ChildComponent Title="Panel Title from Parent"
OnClick="#ShowMessage">
Content of the child component is supplied
by the parent component.
</ChildComponent>
All attributes in C# have to reference a type defining that attribute somewhere. That Blazor code is still C#.
In this case, I believe it it refers to Microsoft.AspNetCore.Components.ParameterAttribute - the documentation is currently MIA, but that may well improve over time. There's more detail in the Blazor documentation.
In general, if you have the code in front of you (generally a good idea when watching a tutorial, if at all possible) you can hover over the attribute in Visual Studio to see its fully qualified name or navigate to it.
We are currently using the Razor View engine to render out HTML (outside of an ASP.NET MVC project). Everything has been working okay until today when I added an enum type to my model that resides in another assembly. Now when I compile, I get the following error:
"RazorEngine.Templating.TemplateCompilationException was unhandled
HResult=-2146233088 Message=Unable to compile template. The type
'ClassLibrary1.MyClass.MyEnum' is defined in an assembly that is not
referenced. You must add a reference to assembly 'ClassLibrary1,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'."
I was able to reproduce this error in a simply console application that references a type from an external DLL:
Template:
#model RazorExample.MyModel
<div>
#if(Model.EnumValue == ExternalLib.MyEnum.Val1) { <p>My value is 1</p> }
</div>
Enum (defined in ExternalLib):
namespace ExternalLib
{
public enum MyEnum
{
Val1 = 1,
Val2 = 2
}
}
Model:
using ExternalLib;
namespace RazorExample
{
public class MyModel
{
public String Name { get; set; }
public MyEnum EnumValue{ get; set; }
}
}
Code to compile the template:
Razor.Compile(template.ToString(), "MyTemplate");
I have also tried adding a "#using ExtneralLib;" to the template but that results in an error that the type "ExternalLib" could not be found. I have seen one or two other posts regarding something similar (Razor-based view doesn't see referenced assemblies) but this is not in the context of a web application. Any help is appreciated.
EDIT
I spoke a little too soon that this fixed my error yesterday. It fixed my error in my test app but when I went to update my code, it did not work. After looking at it a bit more, I realized my model was a little more complicated.
I have updated my sample app accordingly:
Model:
public class MyModel
{
public String Name { get; set; }
public ParentClass ParentClass { get; set; }
}
ParentClass:
namespace ExternalLib
{
public class ParentClass
{
public string Name { get; set; }
public ChildClass ChildClass { get; set; }
}
}
ChildClass:
namespace ExternalLib
{
public class ChildClass
{
public enum MyEnum
{
Val1 = 1,
Val2 = 2
}
}
}
Model:
#model RazorExample.MyModel
<div>
#if(Model.ParentClass.ChildClass.EnumValue == ExternalLib.ChildClass.MyEnum.Val1) { <p>My value is 1</p> }
</div>
With this code, if I add the #using ExternalLib; statement into the model, I get the error "Unable to compile template. The type or namespace name 'ExternalLib' could not be found (are you missing a using directive or an assembly reference?)"
If I leave the #using statement out, I get the exception "Unable to compile template. The type 'ExternalLib.ParentClass' is defined in an assembly that is not referenced. You must add a reference to assembly 'ExternalLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'."
FIX (or workaround at least)
I was able to fix this in the code I am trying to get working by creating a separate model and template for the child class and using an #Include statement in the template to import it in. I can post that code if anyone is interested but I fear I am already a little long on the post.
There are two things wrong in your code:
1: You need to specify the type of your model object when calling Razor.Compile(...):
Razor.Compile(template.ToString(), typeof(MyModel), "MyTemplate");
2: Your template checks the value of ExternalLib.MyEnum == ExternalLib.MyEnum.Val1 but your model's enum value property is MyNestedEnum, not MyEnum.
Change your template to the following:
#model RazorExample.MyModel
<div>
#if(Model.MyNestedEnum == ExternalLib.MyEnum.Val1) { <p>My value is 1</p> }
</div>
HTH.