ant-design-blazor
ant-design-blazor copied to clipboard
Table does not work when item type is an interface
Describe the bug
I tried binding a list of interfaces like this:
public List<IJob> Items { get; private set; }
Blazor
<Table TItem="IJob"
DataSource="@Items">
<Column Title="Name"
Field="@context.Name" />
</Table>
Whenever I change the TItem to a class (Job), it starts working.
Exceptions (if any)
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Exception has been thrown by the target of an invocation.
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> System.TypeInitializationException: The type initializer for 'AntDesign.Table`1' threw an exception.
---> System.MemberAccessException: Cannot access member.
at System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObjectInternal(Type type)
at System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(Type type)
at AntDesign.Table`1[[IJob, MyProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]..cctor()
--- End of inner exception stack trace ---
at System.Reflection.RuntimeConstructorInfo.InternalInvoke(Object obj, Object[] parameters, Boolean wrapExceptions)
--- End of inner exception stack trace ---
at System.Reflection.RuntimeConstructorInfo.InternalInvoke(Object obj, Object[] parameters, Boolean wrapExceptions)
at System.RuntimeType.CreateInstanceMono(Boolean nonPublic, Boolean wrapExceptions)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean wrapExceptions, Boolean skipCheckThis, Boolean fillCache)
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, Boolean wrapExceptions)
at System.Activator.CreateInstance(Type type, Boolean nonPublic, Boolean wrapExceptions)
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at System.Activator.CreateInstance(Type type)
at Microsoft.AspNetCore.Components.DefaultComponentActivator.CreateInstance(Type componentType)
at Microsoft.AspNetCore.Components.ComponentFactory.InstantiateComponent(IServiceProvider serviceProvider, Type componentType)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateComponent(Type componentType)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateChildComponentOnFrame(RenderTreeFrame& frame, Int32 parentComponentId)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(DiffContext& diffContext, Int32 frameIndex)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(DiffContext& diffContext, Int32 frameIndex)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(DiffContext& diffContext, Int32 newFrameIndex)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(DiffContext& diffContext, Int32 oldStartIndex, Int32 oldEndIndexExcl, Int32 newStartIndex, Int32 newEndIndexExcl)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, Int32 componentId, ArrayRange`1 oldTree, ArrayRange`1 newTree)
at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
Further technical details
- AntDesign Nuget Package version
0.6.0-nightly-2101210934
- Include the output of
dotnet --info
Microsoft.NETCore.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Thank you for contacting us @GeertvanHorrik
That's right, you can't use the interface. That's a limitation of c#. I wonder what your thoughts are on this?
I was wondering why it is creating an instance of the model. The data source already contains an instance of the model (so should work against C#).
Could you explain why it's trying to do this? I think property expressions, reflections, etc all work against interface, as long as you (re)use the existing model instances.
This is related to our design of Column, need to pass an instance of TItem
to it.
https://github.com/ant-design-blazor/ant-design-blazor/blob/efaf3854ef5f73a88d2c2dca74873f5bb711dfff/components/table/Table.razor#L22
But with the addition of the DataIndex feature, it is possible to do this in the future.
Hopefully, this is fixed soon as I'm using interfaces due to decoupling.
@Zonciu cc
Table's design is not good, it needs to be redesigned to achieve this.
For workaround, you can use Decorator Pattern
, create a table model class that implemented IJob
then wrap all IJob
instances. Or you can copy all data to the table model class instance
Decorator pattern example:
<Table TItem="JobModel" DataSource="TableData">
<Column Title="Name" TData="string" Field="context.Name"></Column>
<Column Title="Progress" TData="int" @bind-Field="context.Progress"></Column>
<Column Title="Time" TData="DateTime" DataIndex="Time"></Column>
</Table>
@code {
public List<JobModel> TableData = new();
public List<RealJob> Jobs;
protected override void OnInitialized()
{
Jobs = new()
{
new RealJob() {Name = "AAA", Progress = 16, Time = DateTime.Today - TimeSpan.FromDays(4)},
new RealJob() {Name = "BBB", Progress = 61, Time = DateTime.Today - TimeSpan.FromDays(3)},
new RealJob() {Name = "CCC", Progress = 89, Time = DateTime.Today - TimeSpan.FromDays(2)}
};
TableData.AddRange(Jobs.Select(job => new JobModel(job)));
}
public interface IJob
{
string Name { get; }
int Progress { get; set; }
DateTime Time { get; set; }
}
public class RealJob : IJob
{
public string Name { get; set; }
public int Progress { get; set; }
public DateTime Time { get; set; }
}
public class JobModel : IJob
{
public IJob Instance;
public JobModel(IJob job)
{
Instance = job;
}
public string Name
{
get => Instance?.Name ?? default;
}
public int Progress
{
get => Instance?.Progress ?? default;
set => Instance.Progress = value;
}
public DateTime Time
{
get => Instance?.Time ?? default;
set => Instance.Time = value;
}
}
}
Any updates on this?
I am also exploring AntBlazor in a new project that uses interfaces to represent data models which are generated from GraphQl queries and mutations (https://chillicream.com/docs/strawberryshake/), but these interfaces can't be used easily with this library.
Wrapping these interfaces with classes using a decorator pattern isn't an option because it takes away the advantage of using Strawberry Shake (and GraphQl, since the schema may change quite frequently).
@MarkoH17 Sorry, this is a component design limitation. We rely too much on the Column
to render some has nothing to do with RenderFargment content. We hope someone can improve that.