Reputation: 1885
I'm trying to get a generic editor template using Blazor that binds to properties found by reflection as follows :
@typeparam TItem
@foreach (var propertyName in FieldsList) {
<div>
<InputText id="name" @bind-Value="@getBindProperty(propertyName)" />
</div>
}
@functions {
[Parameter]
public TItem ItemEditModel { get; set; }
public string[] FieldsList {
get {
// get all properties decorated with some custom attribute...
return typeof(TItem).GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(MyCustomAttribute))).Select(x => x.Name).ToArray();
}
}
public string getBindProperty(string propName) {
return string.Format("{0}.{1}", nameof(ItemEditModel), propName);
}
}
The above is not accepted as The left-hand side of an assignment must be a variable, property or indexer.
So I cannot bind to a property by its name, so I went on trying other syntax like
<InputText id="name" @bind-Value="ItemEditModel.propertyName" />
This is not accepted either as TItem does not contain a definition of 'propertyName'.
Well all the above make sense - but the key question - is there anything to be done about it, or is it impossible to bind a control to a property by its name?
BONUS QUESTION: If it is actually possible to do this, is there a way to make a switch depending on the property type (typically, only primitive types like ´string´, ´DateTime´, ´int´, ´double´, ´bool´...)?
Upvotes: 2
Views: 4225
Reputation: 233
If you use InputText Blazor's component you should wrap it with the EditForm Blazor's component.
An alternative way to do it could be probably as follows:
@typeparam TItem
@foreach (var propertyName in FieldsList)
{
<div>
<input @bind="PropertyVars[propertyName]" />
</div>
}
<button type="button" @onclick="OnSubmit">Reset</button>
@code {
[Parameter]
public TItem ItemEditModel { get; set; }
public Dictionary<string, string> PropertyVars { get; set; } = new
Dictionary<string, string>();
protected override void OnInitialized()
{
foreach (var propertyName in FieldsList)
{
var propertyInfo = ItemEditModel.GetType().GetProperty(propertyName);
PropertyVars.Add(propertyName,
propertyInfo?.GetValue(ItemEditModel).ToString());
}
}
public string[] FieldsList
{
get
{
return typeof(TItem).GetProperties().Select(x => x.Name).ToArray();
}
}
private void OnSubmit()
{
var tt = PropertyVars;
foreach (var propertyName in FieldsList)
{
var propertyInfo = ItemEditModel.GetType().GetProperty(propertyName);
var uu = PropertyVars[propertyName].GetType();
if (uu == propertyInfo?.PropertyType)
{
propertyInfo.SetValue(ItemEditModel,
Convert.ChangeType(PropertyVars[propertyName], propertyInfo.PropertyType),
null);
}
}
var yy = ItemEditModel;
}
}
Upvotes: 4
Reputation: 1885
To wrap up the solution I ended up with:
I created indexed property class wrappers that allow me to access values (note that I created 2 additional ones that are readonly and writeonly, in case it helps)
public class IndexedProperty<TIndex, TValue> {
readonly Action<TIndex, TValue> SetAction;
readonly Func<TIndex, TValue> GetFunc;
public IndexedProperty(Func<TIndex, TValue> getFunc, Action<TIndex, TValue> setAction) {
this.GetFunc = getFunc;
this.SetAction = setAction;
}
public TValue this[TIndex i] {
get {
return GetFunc(i);
}
set {
SetAction(i, value);
}
}
}
public class ReadOnlyIndexedProperty<TIndex, TValue> {
readonly Func<TIndex, TValue> GetFunc;
public ReadOnlyIndexedProperty(Func<TIndex, TValue> getFunc) {
this.GetFunc = getFunc;
}
public TValue this[TIndex i] {
get {
return GetFunc(i);
}
}
}
public class WriteOnlyIndexedProperty<TIndex, TValue> {
readonly Action<TIndex, TValue> SetAction;
public WriteOnlyIndexedProperty(Action<TIndex, TValue> setAction) {
this.SetAction = setAction;
}
public TValue this[TIndex i] {
set {
SetAction(i, value);
}
}
}
I created a "BindableBase" object that all my objects inherit from, in wich I added the index property :
IndexedProperty<string, string> _strValues;
[JsonIgnore]
public IndexedProperty<string, string> StrValues {
get {
if (_strValues == null) {
_strValues = new IndexedProperty<string, string>(GetStrValue, SetStrValue);
}
return _strValues;
}
}
private string GetStrValue(string propName) {
// use reflection here to get the property value as string
// --- NOTE: beware of performance here I suggest you take a look at related link
}
private void SetStrValue(string propName, string value) {
// use reflection here to SET the property value from string
// --- NOTE: beware of performance here I suggest you take a look at related link
}
Check this link if performance is of matter to you!
In my form, I bind using the indexed properties as follows :
@foreach (var propertyName in FieldsList) {
<div>
<InputText id="name" @bind-Value="@myContextObject.StrValues[propertyName]" />
</div>
}
Credits to @copycar_am and this link.
Upvotes: 1