I'm trying to create a dynamic form in Blazor but I'm stuck. I have a FormFactory component that takes two input parameters. Model for my data binding and a dictionary that defines which elements should be created. The idea is to pass a model object
public class Data
{
public string Name { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
}
and a dictionary
{ "Name": "string" },
{ "Phone": "string" }
I want to create a form with two input fields for Name and Phone bound to the properties in my model.
<h3>Dynamic form</h3>
<EditForm Model="@DataContext">
@foreach (var field in FieldIdentifiers)
{
if (field.Value == "string")
{
<InputText @bind-Value="DataContext.GetType().GetProperty(field.Key).GetValue(DataContext, null).ToString()"></InputText>
}
}
</EditForm>
@code {
[Parameter] public object DataContext { get; set; }
[Parameter] public Dictionary<string, string> FieldIdentifiers { get; set; }
}
But this doesn't work. I get an error saying
"Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type" and "The left hnad side of an assignment must be a variable, property or indexer"
EDIT: I've found that Blazor generates a ValueChanged attribute that causes error
builder2.AddAttribute(8, "ValueChanged", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<System.String>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<System.String>(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => DataContext.GetType().GetProperty(field.Key).GetValue(DataContext, null).ToString() = __value, DataContext.GetType().GetProperty(field.Key).GetValue(DataContext, null).ToString()))));
That won't work so I tried creating a RenderFragment and changed my component as following
<h3>Dynamic form</h3>
@MyForm()
@code {
[Parameter] public object DataContext { get; set; }
[Parameter] public Dictionary<string, string> FieldIdentifiers { get; set; }
RenderFragment MyForm() => builder =>
{
builder.AddMarkupContent(0, "<h3>Dynamic form</h3>\r\n");
builder.OpenComponent<Microsoft.AspNetCore.Components.Forms.EditForm>(1);
builder.AddAttribute(2, "Model", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<System.Object>(DataContext));
builder.AddAttribute(3, "ChildContent", (Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.Forms.EditContext>)((context) => builder2 =>
{
builder2.AddMarkupContent(4, "\r\n");
foreach (var field in FieldIdentifiers)
{
if (field.Value == "string")
{
builder2.AddContent(5, " ");
builder2.OpenComponent<Microsoft.AspNetCore.Components.Forms.InputText>(6);
builder2.AddAttribute(7, "Value", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<System.String>(Microsoft.AspNetCore.Components.BindMethods.GetValue(DataContext.GetType().GetProperty(field.Key).GetValue(DataContext, null).ToString())));
builder2.AddAttribute(8, "ValueChanged", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<System.String>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<System.String>(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => DataContext.GetType().GetProperty(field.Key).SetValue(DataContext, __value), DataContext.GetType().GetProperty(field.Key).GetValue(DataContext, null).ToString()))));
builder2.AddAttribute(9, "ValueExpression", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<System.Linq.Expressions.Expression<System.Func<System.String>>>(() => Convert.ToString(DataContext.GetType().GetProperty(field.Key).GetValue(DataContext, null))));
builder2.CloseComponent();
builder2.AddMarkupContent(10, "\r\n");
}
}
builder2.AddMarkupContent(11, "\r\n");
}));
builder.CloseComponent();
};
}
This compiles but I get a runtime error
System.ArgumentException: The provided expression contains a MethodCallExpression1 which is not supported. FieldIdentifier only supports > simple member accessors (fields, properties) of an object.
Any help would be appreciated
You can redirect to a page in Blazor using the Navigation Manager's NavigateTo method. In the following code snippet, it will redirect to the home page when this page gets loaded. Similarly, you can call NavigateTo() method from NavigationManager class anywhere to redirect to another page.
Inject NavigationManager in razor. Use Uri from NavigationManager to get the current URL.
You can submit a Blazor form programmatically by using EditContent validation. In the following example, a keypress event function triggers when you press the Enter key. It validates the form editContent. Validate() and submits the form programmatically.
See BlazorFiddle for working sample.
Your problem was with ValueExpression
, that required it to be an expression to get a property (or similar).
I create this in my sample:
var constant = System.Linq.Expressions.Expression.Constant(DataContext, typeof(DataX));
var exp = System.Linq.Expressions.MemberExpression.Property(constant, fld);
var lamb = System.Linq.Expressions.Expression.Lambda<Func<string>>(exp);
builder.AddAttribute(4, "ValueExpression", lamb);
For Value
and ValueChanged
I use reflection to get and set the property values.
// Get the initial property value
var propInfoValue = typeof(DataX).GetProperty(fld);
var s = propInfoValue.GetValue(DataContext);
builder.AddAttribute(1, "Value", s);
// Create the handler for ValueChanged. I use reflection to the value.
builder.AddAttribute(3, "ValueChanged", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<System.String>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<System.String>(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => propInfoValue.SetValue(DataContext, __value), (string)propInfoValue.GetValue(DataContext)))));
I'm quite new working with Linq's Expression
classes so there may be better ways to do this.
Also the code for ValueChanged
is based on the generated code for a static InputText. There may be better ways for this as well.
Full code: Index.razor
@page "/"
<h3>Dynamic form</h3>
<EditForm Model="@DataContext">
@foreach (var field in FieldIdentifiers)
{
if (field.Value == "string")
{
@field.Key
@CreateComponent(field.Key);
<br />
}
}
<h3>Statically declared</h3>
<InputText @bind-Value="@DataContext.Name"></InputText> <br>
Name: @DataContext.Name
</EditForm>
@code {
[Parameter] public DataX DataContext { get; set; } = new DataX();
[Parameter] public Dictionary<string, string> FieldIdentifiers { get; set; } = new Dictionary<string, string> { { "Name", "string" }, { "Phone", "string" } };
public RenderFragment CreateComponent(string fld) => builder =>
{
builder.OpenComponent(0, typeof(InputText));
// Get the initial property value
var propInfoValue = typeof(DataX).GetProperty(fld);
var s = propInfoValue.GetValue(DataContext);
builder.AddAttribute(1, "Value", s);
// Create the handler for ValueChanged. I use reflection to the value.
builder.AddAttribute(3, "ValueChanged", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<System.String>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<System.String>(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => propInfoValue.SetValue(DataContext, __value), (string)propInfoValue.GetValue(DataContext)))));
// Create an expression to set the ValueExpression-attribute.
var constant = System.Linq.Expressions.Expression.Constant(DataContext, typeof(DataX));
var exp = System.Linq.Expressions.MemberExpression.Property(constant, fld);
var lamb = System.Linq.Expressions.Expression.Lambda<Func<string>>(exp);
builder.AddAttribute(4, "ValueExpression", lamb);
builder.CloseComponent();
};
}
DataX.cs
public class DataX
{
public string Name { get; set; } = "Jane Doe";
public string Phone { get; set; } = "123456789";
public string Address { get; set; } = "The Street";
}
Edit: I just saw that you had the "blazor-clientside" tag on the question. I have just tested this with server-side Blazor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With