Home > Net >  How do you create a component that allows the consumer to extend the interfaces for parameter inputs
How do you create a component that allows the consumer to extend the interfaces for parameter inputs

Time:05-06

This may be a dumb question, but my search so far has come up short and I worry I might have too specific of a use case to find a clear answer anywhere else.

I am working on creating a Blazor component that utilizes a Binding List as an input parameter. This is specifically so the component can detect changes to the list from outside the component, and respond accordingly. The list implements an interface for list items thusly:

[Parameter]
public BindingList<IPointOfInterest> PointsOfInterest { get => ...; set => ...; }

The interface, meanwhile, looks like this:

public interface IPointOfInterest
{
    public float[] position { get; set; }
}

From there I instantiate it as a property of a parent object , like this:

var currentImage = new Image { DataUri = $"images/image-001.jpg", PointsOfInterest = new BindingList<IPointOfInterest>() };

And then based on the currently selected object I simply bind it to the component like this:

<ImageViewer Image="@currentImage"
             PointsOfInterest="@currentImage.PointsOfInterest">
</ImageViewer>

My goal here is to make it so that the IPointOfInterest interface could be extended/inherited from/transmogrified somehow so that the consumer of the Blazor component could tack on their own properties as desired and leverage those in the same code without having to maintain separate objects. An example might be something like this:

public class PointOfInterest : IPointOfInterest
{
    public float[] position { get; set; }
    public int id { get; set; }
    public string name { get; set; }
    public string description { get; set; }
    public float entityId { get; set; }
}

Unfortunately, while this seems like a simple enough thing, using this class in a Binding List as an input to the component simply doesn't work, because the component expects the interface, and even though the class implements the interface, the compiler can't convert between the two.

Possible solutions I've thought of so far which have not worked:

  1. Partial interfaces: create a partial interface in the component and another of the same interface in the project using the component. This fails right off the bat because partial interfaces, as with classes, have to be a part of the same assembly.
  2. Inheriting from and extending the interface: create an interface in the consuming project that inherits from the component's interface, and use that. Unfortunately this also presents a type mismatch error at compile time.

I do feel as though I'm missing something fundamental about how dotnet treats these types of things, but I also feel as though I'm trying to do something pretty straightforward and not terribly outrageous and it ought not to be as difficult as I'm finding it. If I'm wrong about that, please do let me know, and in any case I'd appreciate any suggestions about how to go about what I'm doing better and/or differently in a way that actually works.

Update

It seems I'm way better off showing this than trying to describe it, so here's a working example of what I'm trying to achieve.

The point here is that the Image Viewer only cares about certain properties on the PointOfInterest class - specifically the position property. All other properties would only matter outside of the Image Viewer.

Note that this code compiles and runs properly only because I'm leveraging the class instead of the interface. If I were to use the interface, I would get a conversion error that would prevent the code from compiling altogether.

CodePudding user response:

I don't think I have enough information to fully understand, but maybe you can try the following

<ImageViewer Image="@currentImage"
             PointsOfInterest="@(currentImage.PointsOfInterest as BindingList<IPointOfInterest>)">
</ImageViewer>

Or an approach with an explicit or implicit cast could be good too.

CodePudding user response:

You should be able to use interfaces and generics. Here's a demo:

Data classes and Interface

public interface IPointOfInterest
{
    public decimal Lat { get; }
    public decimal Long { get; }
}

public class DataA : IPointOfInterest
{
    public decimal Lat { get; set; }
    public decimal Long { get; set; }
}

public class DataB : IPointOfInterest
{
public decimal Lat { get; set; }
public decimal Long { get; set; }
}

public class DataC
{
    public decimal Lat { get; set; }
    public decimal Long { get; set; }
}

Component:

@typeparam TItem
<h3>PointComponent</h3>

@if (list is not null)
{
    @foreach (var item in list)
    {
        <div >
            <div >
                Lat: @item.Lat
            </div>
            <div >
                Long: @item.Long
            </div>
        </div>
    }
}
else
{
    <div >No Data</div>
}

@code {
    [Parameter] public IEnumerable<TItem>? DataList { get; set; }

    private IEnumerable<IPointOfInterest>? list
    {
        get
        {
            if (DataList is not null && DataList is IEnumerable<IPointOfInterest>)
                return new List<IPointOfInterest>(DataList!.Cast<IPointOfInterest>());

            return null;
        }
    }

}

And Demo page:

@page "/"

<PageTitle>Index</PageTitle>

<PointComponent DataList=dataA />

<PointComponent DataList=dataB />

<PointComponent DataList=dataC />

@code {
    public IEnumerable<DataA> dataA = new List<DataA>
    {
        new DataA {Lat = 20, Long=10 },
        new DataA {Lat = 20, Long=10 }
    }; 

    public IEnumerable<DataB> dataB = new List<DataB>
    {
        new DataB {Lat = 20, Long=10 },
        new DataB {Lat = 20, Long=10 }
    }; 

    public IEnumerable<DataC> dataC = new List<DataC>
    {
        new DataC {Lat = 20, Long=10 },
        new DataC {Lat = 20, Long=10 }
    }; 
}
  • Related