I have a custom component InlineAutocomplete. No matter what I do my pic value and SelectedPIC value are always null when I do validation and unsure why it is.
The component does render correctly, but can't get my parameter back with the values set correctly.
Usage as follow:
<InlineAutocomplete TItem="PIC"
Value="#pic"
Selected="#SelectedPIC"
Data="#PICs"
Property="#((item) => item.PICNumber)" />
My component:
<div class="inline-autocomplete">
<input #oninput="(e)=> SuggestItem(e)" type="text" #bind-value="#Value" class="overlap inline-autocomplete-input " />
<input readonly class="overlap inline-autocomplete-label" value="#_suggestedValue" />
</div>
#code {
string _value;
string _suggestedValue;
[Parameter]
public string Value
{
get => _value;
set
{
if (value == null)
return;
_value = value;
}
}
[Parameter]
public List<TItem> Data { get; set; }
[Parameter]
public Func<TItem, string> Property { get; set; }
[Parameter]
public TItem Selected { get; set; }
void SuggestItem(ChangeEventArgs e)
{
if (string.IsNullOrEmpty(e.Value.ToString()))
return;
Value = e.Value.ToString().ToUpper();
if (string.IsNullOrEmpty(Value))
{
_suggestedValue = string.Empty;
}
else
{
var selectedItem = Data.Select(Property).FirstOrDefault(x => x.StartsWith(Value, StringComparison.OrdinalIgnoreCase));
_suggestedValue = !string.IsNullOrWhiteSpace(selectedItem) ? selectedItem : string.Empty;
Selected = Data.FirstOrDefault(x => string.Equals(Property(x), Value, StringComparison.OrdinalIgnoreCase));
StateHasChanged();
}
// State has changed
StateHasChanged();
}
}
The solution to this was to pass down a function that changes the property in the Parent instead of the Child.
Child:
[Parameter]
public Action<string, TItem> ValueChanged { get; set; }
...
#code {
void SomeFunction() {
ValueChanged(Value, Selected);
}
}
Parent:
<InlineAutocomplete ... ValueChanged="FireEvent" />
void FireEvent(string stringVal, MyClass c)
{
// ... Do Something
}
Related
I am developing a custom blazor select component. The component has a property named "Value" which in most cases is a string value which I can bind to using #bind-Value without any issues.
However in some situations the property which it binds to may be an integer (outside the component) in which case I need to convert from a string to an integer. I initially assumed simply using Int32.Parse would accomplish this as follows:
Select.razor.cs
namespace Accounting.Web.Components
{
public partial class Select
{
[Parameter]
public string Value { get; set; } = default!;
[Parameter]
public EventCallback<string>? ValueChanged { get; set; }
public void OnClick()
{
Value = "2";
ValueChanged?.InvokeAsync(Value);
}
}
}
Index.razor.cs
<Select Id="InvestmentEntitySelect" #bind-Value="#Int32.Parse(AddDto.InvestmentEntityId)">
#foreach (var entity in Entities)
{
<SelectOption Value="#entity.InvestmentEntityId.ToString()">#entity.Name</SelectOption>
}
</Select>
AddDto.cs
namespace Accounting.Shared.Dtos.Request.Investment
{
public class AddDto
{
[Required]
public int? InvestmentEntityId { get; set; } = 0;
[Required]
public string Description { get; set; } = String.Empty;
}
}
The line of interest is:
#bind-Value="#Int32.Parse(AddDto.InvestmentEntityId)"
But unfortunately this produces an error:
Argument 1: cannot convert from 'int' to 'System.ReadOnlySpan'
So how can I converted the bind value to an integer in the above example?
You need to create a custom input select like below and override the TryParseValueFromString method of it:
public class CustomInputSelect<TValue> : InputSelect<TValue>
{
protected override bool TryParseValueFromString(
string? value,
out TValue result,
out string? validationErrorMessage)
{
if (typeof(TValue) == typeof(int))
{
if (int.TryParse(value, out var resultInt))
{
result = (TValue)(object)resultInt;
validationErrorMessage = null;
return true;
}
else
{
result = default;
validationErrorMessage =
$"The selected value {value} is not a valid number.";
return false;
}
}
else
{
return base.TryParseValueFromString(value, out result,
out validationErrorMessage);
}
}
}
You have to do it like this:
<Select Id="InvestmentEntitySelect" Value="#AddDto.InvestmentEntityId.ToString()" ValueChanged="OnValueChanged">
#foreach (var entity in Entities)
{
<SelectOption Value="#entity.InvestmentEntityId.ToString()">#entity.Name</SelectOption>
}
</Select>
#code {
private void OnValueChanged(string selectedValue)
{
AddDto.InvestmentEntityId = int.Parse(selectedValue);
}
}
I'm doing a Blazor app, although I have the impression the question is C# and independent to blazor.
I have a class with some strings (like Person.Name, Person.Surname, etc)
I have a person viewer component that shows those strings. And a "string editor" component so when the user clicks on the name of the patient, the string editor component will appear and allow you to edit the name (It is not exactly like that, but will help us simplify the scenario and find a solution)
The question is how to send a reference to Person.Name to the StringEditor component, so the original string is also edited when it is edited there. If I was an object is simple, you send the object and just a reference is sent so changes done in the component apply to the original source object but for strings... this does not work like this. A copy is sent, and the changes are not carried over to the original component.
The scenario would look a bit like :
Person class
public class Person{
public string Name {get;set;} = "";
public string Surname {get;set;} = "";
}
Main razor component
<div>
<div #onclick=()=>{OpenStringEditor(user.Name)}>#user.Name</div>
<div #onclick=()=>{OpenStringEditor(user.Surname)}>#user.Surname</div>
</div>
<div class=#(ShowEditor ? "ShowEditor" : "HideEditor")>
<StringEditor StringToEdit EditFinished=whenFinished/>
</div>
code{
[Parameter] pubic Person user {get; set;}
private void OpenStringEditor(string ToEdit)
{
StringToEdit = ToEdit; // This is where I'd need to keep a reference
// to the original source, so when the
// edit is finished I can store the end product
ShowEditor = true;
}
private void whenFinished()
{
Person.StoreToDB(); //whatever
ShowEditor = false;
}
}
StringEditor
<input #bind=ToBeEdited #onfocusout=EditFinished />
code
{
[Parameter] public string ToBeEdited;
[Parameter] public EventCallback EditFinished;
}
So you see, this would work for objects, but not for raw strings
I guess the bottom question would be: how to keep a reference of "where to store the changes when the edit is finished."
Do this fit the bill?
First you need a Modal Dialog Framework. I've included a simple one - the code is in appendix to this answer, but you can use any of those available that are suitable.
A "Text Editor" component which we will use in the modal dialog. If you're editing other data types you will need different ones. It interacts with the cascaded instance of the Modal Dialog wrapper.
TextEditor.razor
<div class="mb-3">
<label class="form-label small text-muted">#this.Label</label>
<div class="input-group">
<input class="form-control" value="#this.Value" #oninput=this.Changed />
<button class="btn btn-success" #onclick=Exit>Set</button>
<button class="btn btn-danger" #onclick=Cancel>Cancel</button>
</div>
</div>
#code {
[CascadingParameter] private IModalDialog? Modal { get; set; }
[Parameter] public string Label { get; set; } = "Field";
[Parameter] public string? Value { get; set; }
[Parameter] public EventCallback<string> ValueChanged { get; set; }
[Parameter] public Expression<Func<string>>? ValueExpression { get; set; }
private string? _value;
protected override void OnInitialized()
=> _value = Value;
private void Changed(ChangeEventArgs e)
=> this.ValueChanged.InvokeAsync(e.Value?.ToString() ?? null);
private void Exit()
=> Modal?.Close(ModalResult.OK());
private async Task Cancel()
{
await this.ValueChanged.InvokeAsync(_value);
Modal?.Close(ModalResult.Cancel());
}
}
And then our "Special Format" page:
#page "/"
<PageTitle>Index</PageTitle>
<h1 class="mb-5">Hello, world!</h1>
<div class="mt-5 mb-5" >
Editor
</div>
<div class="mt-5 border border-1 border-dark p-1" style="cursor:pointer;" #onclick="() => Edit(CountryModalOptions)">
#model.Country
</div>
<div class="mt-2 border border-1 border-dark p-1" style="cursor:pointer;" #onclick="() => Edit(ContinentModalOptions)">
#model.Continent
</div>
<BaseModalDialog #ref=this.Modal />
#code {
private IModalDialog? Modal;
private Model model = new();
private async Task Edit(ModalOptions options)
{
if (Modal is not null)
{
var result = await Modal.ShowAsync<TextEditor>(options);
// do things
}
}
private ModalOptions CountryModalOptions
{
get
{
var options = new ModalOptions();
options.ControlParameters.Add("Label", "Field Name");
options.ControlParameters.Add("Value", model.Country);
options.ControlParameters.Add("ValueChanged", EventCallback.Factory.Create<String>(this, RuntimeHelpers.CreateInferredEventCallback(this, __value => model.Country = __value, model.Country)));
options.ControlParameters.Add("ValueExpression", RuntimeHelpers.TypeCheck<System.Linq.Expressions.Expression<System.Func<System.String>>>(() => model.Country));
return options;
}
}
private ModalOptions ContinentModalOptions
{
get
{
var options = new ModalOptions();
options.ControlParameters.Add("Label", "Field Name");
options.ControlParameters.Add("Value", model.Continent);
options.ControlParameters.Add("ValueChanged", EventCallback.Factory.Create<String>(this, RuntimeHelpers.CreateInferredEventCallback(this, __value => model.Continent = __value, model.Continent)));
options.ControlParameters.Add("ValueExpression", RuntimeHelpers.TypeCheck<System.Linq.Expressions.Expression<System.Func<System.String>>>(() => model.Continent));
return options;
}
}
public class Model
{
public string Country { get; set; } = "No Country Set";
public string Continent { get; set; } = "No Continent Set";
}
}
So what's going on.
We have our BaseModalDialog component registered on the page with a global variable reference. When you click on an editable field the onclick handler opens the modal dialog by calling ShowAsync. It sets the component as T and passes in a set of Parameters to apply to T when it's defined. The standard bind Parameters are mapped to the model field using some RuntimeHelpers.
The dialog opens and you can edit the value. I've wired it up to update on each keypress (oninput). Click on either Set or Cancel to exit the model as pass "control" back to the main page.
I've kept everything as basic as I can, there are all sorts of Css and formating options you can apply to improve the UX.
Appendix The Modal Dialog
ModalOptions
public sealed class ModalOptions : IEnumerable<KeyValuePair<string, object>>
{
public string Width { get; set; } = "50%";
public Dictionary<string, object> ControlParameters { get; } = new Dictionary<string, object>();
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
foreach (var item in ControlParameters)
yield return item;
}
IEnumerator IEnumerable.GetEnumerator()
=> this.GetEnumerator();
public T? Get<T>(string key)
{
if (this.ControlParameters.ContainsKey(key))
{
if (this.ControlParameters[key] is T t) return t;
}
return default;
}
public bool TryGet<T>(string key, [NotNullWhen(true)] out T? value)
{
value = default;
if (this.ControlParameters.ContainsKey(key))
{
if (this.ControlParameters[key] is T t)
{
value = t;
return true;
}
}
return false;
}
public bool Set(string key, object value)
{
if (this.ControlParameters.ContainsKey(key))
{
this.ControlParameters[key] = value;
return false;
}
this.ControlParameters.Add(key, value);
return true;
}
}
ModalResult
public enum ModalResultType { NoSet, OK, Cancel, Exit }
public sealed class ModalResult
{
public ModalResultType ResultType { get; private set; } = ModalResultType.NoSet;
public object? Data { get; set; } = null;
public static ModalResult OK() => new ModalResult() { ResultType = ModalResultType.OK };
public static ModalResult Exit() => new ModalResult() { ResultType = ModalResultType.Exit };
public static ModalResult Cancel() => new ModalResult() { ResultType = ModalResultType.Cancel };
public static ModalResult OK(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.OK };
public static ModalResult Exit(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.Exit };
public static ModalResult Cancel(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.Cancel };
}
IModalDialog
public interface IModalDialog
{
public ModalOptions Options { get; }
public bool IsActive { get; }
public bool Display { get; }
public Task<ModalResult> ShowAsync<TModal>(ModalOptions options) where TModal : IComponent;
public void Dismiss();
public void Close(ModalResult result);
public void Update(ModalOptions? options = null);
}
BaseModalDialog.razor
#namespace Blazr.UI
#implements IModalDialog
#if (this.Display)
{
<CascadingValue Value="(IModalDialog)this">
<div class="base-modal-background">
<div class="base-modal-content" style="width:#this.Options.Width;" #onclick:stopPropagation="true">
<DynamicComponent Type=this.ModalContentType Parameters=this.Options.ControlParameters />
</div>
</div>
</CascadingValue>
}
#code {
public ModalOptions Options { get; protected set; } = new ModalOptions();
public bool Display { get; protected set; }
public bool IsActive => this.ModalContentType is not null;
protected TaskCompletionSource<ModalResult> _ModalTask { get; set; } = new TaskCompletionSource<ModalResult>();
protected Type? ModalContentType = null;
public Task<ModalResult> ShowAsync<TModal>(ModalOptions options) where TModal : IComponent
{
this.ModalContentType = typeof(TModal);
this.Options = options ??= this.Options;
this._ModalTask = new TaskCompletionSource<ModalResult>();
this.Display = true;
InvokeAsync(StateHasChanged);
return this._ModalTask.Task;
}
public void Update(ModalOptions? options = null)
{
this.Options = options ??= this.Options;
InvokeAsync(StateHasChanged);
}
public async void Dismiss()
{
this._ModalTask.TrySetResult(ModalResult.Cancel());
await Reset();
}
public async void Close(ModalResult result)
{
this._ModalTask.TrySetResult(result);
await Reset();
}
private async Task Reset()
{
this.Display = false;
this.ModalContentType = null;
await InvokeAsync(StateHasChanged);
}
}
BaseModalDialog.razor.css
div.base-modal-background {
display: block;
position: fixed;
z-index: 101; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
div.base-modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 10px;
border: 2px solid #888;
width: 50%;
}
i have component WHERE T can be int or int? with params like
#typeparam T
#inject DictService _dhttp;
<MudAutocomplete T="string" #bind-Value="ValueString" Label="#Label" For="()=>ValueString" SearchFunc="#SearchFunc"
ResetValueOnEmptyText="true" CoerceValue="true"
OpenIcon="#Icons.Material.Filled.Search" AdornmentColor="Color.Primary"
#attributes=AllOtherAttributes />
[Parameter]
public Expression<Func<T>>? For { get; set; }
[Parameter]
public string? Label { get; set; }
private T _value = default!;
[Parameter]
public T Value
{
get => _value;
set
{
if (!Equals(value, _value))
{
_value = value;
if (ValueChanged.HasDelegate) ValueChanged.InvokeAsync(_value);
}
}
}
[Parameter]
public EventCallback<T?> ValueChanged { get; set; }
private string? _valueString ;
public string? ValueString
{
get{
return _valueString!;
}
set
{
if(!Equals(value, _valueString))
{
_valueString = value;
int? valueInt = _dict!.Values.Where(... some logic to get in val or not)
if (valueInt is null)
{
ValueString = null;
and now this should work for both cases?!? set 0 if int and null if int? ??
this.Value = (T)(object)default!;
but instead of i have to do
if (typeof(T) == typeof(int))
this.Value = (T)(object)0;
else
this.Value = (T)(object)default!;
}
else this.Value = (T)(object)valueInt;
if (ValueChanged.HasDelegate) ValueChanged.InvokeAsync(_value);
}
}
}
if i do not do this way then in debug i see that if T is int then (T)(object)default! like crashes? it should set it to 0 !?
it throws no error. it not go to next line , it just returns to app without changing value of this.Value witch is int in this case. value stays as it was from previous run
is it a .net blazor bug? or am missing something here ?
Here is some githup repo that shows this issue
https://github.com/d00lar/TconverterFails
in this line
<MudSelectItem Value="TConverter.ChangeType<T>(item.Key)">#item.Value</MudSelectItem>
if i do casting based on this int/int? switch then it will wotk fine otherwise throw as in topic
You are overcomplicating your cast.
Just use default which will set type int to 0, and type int? to null.
Index.razor:
#page "/"
<MyComponent T="int" />
<MyComponent T="int?" />
Component:
#using System.ComponentModel
#typeparam T
<div>
<input type="text" #bind-value="#ValueString" />
<span>#(Value == null ? "null" : Value)</span>
</div>
#code {
T Value { get; set; }
string _valueString = "";
string? ValueString
{
get
{
return _valueString!;
}
set
{
_valueString = value;
if (string.IsNullOrEmpty(value))
{
this.Value = default;
}
else this.Value = TConverter.ChangeType<T>(value); ;
Console.WriteLine(_valueString);
}
}
public static class TConverter
{
public static T ChangeType<T>(object value)
{
return (T)ChangeType(typeof(T), value);
}
public static object ChangeType(Type t, object value)
{
TypeConverter tc = TypeDescriptor.GetConverter(t);
return tc.ConvertFrom(value);
}
public static void RegisterTypeConverter<T, TC>() where TC : TypeConverter
{
TypeDescriptor.AddAttributes(typeof(T), new TypeConverterAttribute(typeof(TC)));
}
}
}
TConverter source: https://stackoverflow.com/a/1833128/2286743
Fix to provided link:
I can see in your approach that your understanding of how to implement components needs to be expanded. I suggest you go through the documentation provided by Microsoft, and improve your understanding.
As a side note, using a dictionary is not the right way of doing it, since the Key in a Dictionary cannot be null. This can however be overcome as mentioned here: Why doesn't Dictionary<TKey, TValue> support null key?
index.razor
#page "/"
#using MyApplication.Shared
<PageTitle>Index</PageTitle>
<IntStrDictMudSelectComponent #bind-Value="#intvar" Data="#Data" For="#(()=>intvar)" />
#code {
private int intvar = 1;
Dictionary<int, string> Data = new Dictionary<int, string>() {
{ 1, "Value 1" },
{ 2, "Value 2" },
{ 3, "Value 3" },
{ 4, "Value 4" },
{ 5, "Value 5" },
{ 6, "Value 6" }
};
}
IntStrDictMudSelectComponent.razor
#using System.Linq.Expressions
#typeparam T
<MudSelect T="T" #bind-Value="#Value" For="#For">
#foreach (var item in Data)
{
<MudSelectItem Value="item.Key">#item.Value</MudSelectItem>
}
</MudSelect>
#code {
[EditorRequired]
[Parameter]
public Expression<Func<T>>? For { get; set; }
T _value = default!;
[EditorRequired]
[Parameter]
public T Value
{
get => _value;
set
{
if (!Equals(value, _value))
{
_value = value;
if (ValueChanged.HasDelegate)
ValueChanged.InvokeAsync(_value);
}
}
}
[EditorRequired]
[Parameter]
public EventCallback<T> ValueChanged { get; set; }
[EditorRequired]
[Parameter]
public Dictionary<T, string> Data { get; set; }
}
In the //this works, the item.isSelected is updated but the set in private List<CheckboxModel> checkboxes won't run. And in the //this causes infinite loop in this component, nothing is updated and the foreach loop keeps running infinitely.
#foreach (var item in checkboxes)
{
//this works
#*<div class="checkbox" #key="item">
<input type="checkbox" id="#item.text" #bind="item.isSelected">
<label for="#item.text">#item.text</label>
</div>*#
Console.WriteLine(item.text);
//this causes infinite loop in this component
<Checkbox id="#item.text" #key="item" label="#item.text" #bind-isSelected="item.isSelected" />
}
private List<CheckboxModel> _checkboxes { get; set; } = new List<CheckboxModel>();
private List<CheckboxModel> checkboxes
{
get => _checkboxes;
set
{
// won't run
_checkboxes = value;
Console.WriteLine(string.Join(',', value.Where(x => x.isSelected).Select(x => x.text)));
}
}
This is CheckboxModel.cs
public class CheckboxModel
{
public bool isSelected { get; set; }
public string text { get; set; }
}
Checkbox.razor
<div class="checkbox">
<input type="checkbox" id="#id" #bind="#isSelected">
<label for="#id">#label</label>
</div>
#code {
[Parameter]
public string id { get; set; }
[Parameter]
public string label { get; set; }
private bool _isSelected { get; set; }
[Parameter]
public bool isSelected
{
get => _isSelected;
set
{
isSelectedChanged.InvokeAsync(value);
}
}
[Parameter]
public EventCallback<bool> isSelectedChanged { get; set; }
}
What I end up doing was changing Checkbox.razor to
CheckboxList.razor
#foreach (var item in items)
{
<div class="checkbox" #key="item">
<input type="checkbox" id="#item.text" checked="#item.isSelected" #onchange="args=> checkedChanged(item,args)">
<label for="#item.text">#item.text</label>
</div>
}
#code {
[Parameter]
public List<CheckboxModel> items { get; set; }
[Parameter]
public EventCallback<List<CheckboxModel>> itemsChanged { get; set; }
async Task checkedChanged(CheckboxModel item, ChangeEventArgs args)
{
item.isSelected = (bool)args.Value;
await itemsChanged.InvokeAsync(items);
}
}
and in the parent file
<CheckboxList #bind-items="checkboxes" />
#code {
private List<CheckboxModel> checkboxes { get; set; }
}
You can find 2 links in here.
One of them is simpler and the other one has a select all button
In short: I let the child component handle the logic of looping and handling the List passed into it. The child component passes the List back instead of just a bool which fixes the problem where it doesn't trigger the setter function when I update an item in a List.
Im trying to build a dynamic list of input field for properties inside a class using Blazor but cant figur out how to bind/link the content of a input box to a property of a class. (the class have can have a large number of public props, not only Name and Description as in the below example, they are not always of the type "string")
lets say that I have this class/model:
public class customer{
public string Name { get; set; }
public int Age { get; set; }
public string Description { get; set; }
}
I got this blazor component (updateC.razor):
#inherits CLogic
#if (nfo != null)
{
#foreach (var obj in nfo)
{
<input type="text" class="form-control"
bind=#SCustomer.GetType().GetProperty(obj.ToString())/>
}
}
and finally Clogic:
public class Clogic: ComponentBase{
[Parameter]
public Customer SCustomer { get; set; } = new Customer();
[Parameter]
public PropertyInfo[] nfo { get; set; }
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
nfo = SCustomer.GetType().GetProperties();
StateHasChanged();
}
}
}
This is suppose to bind changes made in each input field to the correct property in the current instance of SCustomer (when input is made it is suppose to update the correct property of the class/object). This is not working, values inside of SCustomer are not changed after input are done. I'm guessing that i'm going about this completely wrong but can't seem to figure out how to make this work and can't find any examples doing this.
#foreach (var propertyInfo in nfo)
{
<input type="text" class="form-control"
value="#propertyInfo.GetValue(SCustomer)"
#onchange="#((ChangeEventArgs __e) =>
propertyInfo.SetValue(SCustomer, __e.Value.ToString()))" />
}
#foreach(propertyInfo in nfo)
{
<label>#propertyInfo.Name</label>
<input type="text" value="#propertyInfo.GetValue(SCustomer)" #onchange="#((ChangeEventArgs __e) =>
propertyInfo.SetValue(SCustomer,Convert.ChangeType(__e.Value,
propertyInfo.PropertyType,null))"/>
}
I finally found a way of doing this, here is my solution:
I created a helper class:
public class PropHolder
{
[Parameter]
public PropertyInfo info { get; set; }
[Parameter]
public string type { get; set; }
public PropHolder(PropertyInfo nfo, string propType)
{
info = nfo;
type = propType;
}
}
then i created a dictionary of this class and some cheking functions (this is inside of Clogic)
[Parameter]
public Dictionary<int, PropHolder> Props{ get; set; }
public void GetAllProps()
{
Props = new Dictionary<int, PropHolder>();
//nfo = SCustomer.GetType().GetProperties();
int Cid = 0;
foreach (PropertyInfo pif in SCustomer.GetType().GetProperties())
{
Props[Cid] = new PropHolder(pif, pif.PropertyType.Name);
Cid++;
}
}
public string CheckName(PropHolder propertyInfo)
{
if (propertyInfo.GetType() == typeof(PropHolder))
{
return propertyInfo.type;
}
else
{
return propertyInfo.GetType().Name.ToString();
}
}
public PropertyInfo getInfo(PropHolder propertyInfo)
{
if (propertyInfo.GetType() == typeof(PropHolder))
{
return propertyInfo.info;
}
else
{
return null;
}
}
and finally im able to loop over the keys of my dictionary and bind all values correctly (got lots of help figuring this out from the answere given by: "agua from mars")
here is the updateC.razor content:
#if (Props != null)
{
#foreach (int key in Props.Keys)
{
var pinfo = Props[key];
#if (CheckName(pinfo) == "String")
{
<input type="text" class="form-control" value=#(getInfo(pinfo).GetValue(SCustomer)) #onchange="#((ChangeEventArgs __e) => getInfo(pinfo).SetValue(SCustomer, __e.Value.ToString()))" />
}
}
}
this gives me a input box for every prop that is of the type String, if additional types are to be handled it can now easily be added. This is probably not the best sulution but it do work. I will update the answere if any better working solutions are posted.
I solved this using generics. This lets each input type pass on a generic type which defines what type it wants to use.
<input #bind="Name.Value" />
<input #bind="Age.Value" />
<div><code>Name</code>: #Name.Value</div>
<div><code>Age</code>: #Age.Value</div>
#code {
private FormField<string> Name { get; set; } = new FormField<string>();
private FormField<int> Age { get; set; } = new FormField<int>();
public class Form
{
public ICollection<IFormField> Fields { get; set; }
}
public interface IFormField
{
public int ControlType { get; set; }
}
public class FormField<T> : IFormField
{
public int ControlType { get; set; }
public T Value { get; set; }
}
}
Here it is on BlazorFiddle: https://blazorfiddle.com/s/wen1g26q