2

I'm working on a Blazor Wasm Hosted project that uses dynamic razor components for rendering the UI. The components are created using the IDynamicComponent interface. At server-side, the dlls are loaded and stored into IEnumerable<Type>. From this IEnumerable<Type> I can get individual component by name and casted to IDynamicComponent. The issue is that I can't be able to send the component in this format to the client via SignalR.

IDynamicComponent Interface

public interface IDynamicComponent
{
    IDictionary<Type,Type> InjectableDependencies { get; }
    IDictionary<string,string> Parameters { get; }
    string Name { get; }
    Type Component { get;}
}

Example of component

The component is created separately in a Razor Class Library project which consist of "MyComponent.cs" and "RazorComponent1.razor". The dll is then loaded to get MyComponent.

public class MyComponent : IDynamicComponent
{
    public IDictionary<Type, Type> InjectableDependencies => new Dictionary<Type, Type>();
    public IDictionary<string, string> Parameters => new Dictionary<string, string>();
    public string Name => "ComponentName";
    public Type Component => typeof(RazorComponent1); // RazorComponent1.razor
}

A) Server-side: Load the Component Dlls

public IEnumerable<Type> Components { get; private set; }

// Loads the dlls and stores the components in 'IEnumerable<Type>'
public void LoadComponents(string path)
{
    var components = new List<Type>();
    var assemblies = LoadAssemblies(path);

    foreach (var asm in assemblies)
    {
        var types = GetTypesWithInterface(asm);
        foreach (var typ in types) components.Add(typ);
    }
    Components = components;
}

B) Server-side: Function to get individual Component by name

// Gets the component by name
public IDynamicComponent GetComponentByName(string name)
{
     return Components.Select(x => (IDynamicComponent)Activator.CreateInstance(x))
            .SingleOrDefault(x => x.Name == name);
}

C) Server-side: Send to Client via SignalR

// Sends a component to client via SignalR when requested.
 public async Task GetPlugin()
 {
    IDynamicComponent component = ComponentService.GetComponentByName("Counter");
    string comp = JsonConvert.SerializeObject(component);

    await myHub.Clients.All.SendAsync("Plugin", comp);
 }

D) Client-side: Receive component and cast to IDynamicComponent

hubConnection.On<string>("Plugin", (comp) =>
{
    IDynamicComponent component = JsonConvert.DeserializeObject<IDynamicComponent>(comp);

    UpdateBlazorUIEvent?.Invoke();
});

Error at part D) shown in browser console

Could not create an instance of type Blazor.Shared.Plugin.IDynamicComponent. Type is an interface or abstract class and cannot be instantiated. Path 'InjectableDependencies', line 1, position 26.
Ibrahim Timimi
  • 2,656
  • 5
  • 19
  • 31

2 Answers2

0

I'm not sure what you mean by serialize the components and why you want to do that, but one thing you can do is pass the Type as a string and then, when creating the component, you get the System.Type from that string.

Where is how you can Convert String to Type in C#.

You can either use Type.GetType or from Assembly.GetType.

For example, instead of having

public Type Component => typeof(RazorComponent1);

You would have

public string Component => typeof(RazorComponent1).AssemblyQualifiedName;

And in a place that you didn't mention how it would work, you would do something like.

Type.GetType(myComponent.Component)

To get the System.Type that you actually wanted.

Vencovsky
  • 28,550
  • 17
  • 109
  • 176
0
hubConnection.On<string>("Plugin", (comp) =>
{
    IDynamicComponent component = JsonConvert.DeserializeObject<IDynamicComponent>(comp);

    UpdateBlazorUIEvent?.Invoke();
});

At no point in the above code do you specify the concrete type you are trying to deserialize.

There are a few options you can do. The simpliest one would be to use a discriminator.

To paraphrase StevenLiekens:

[JsonConverter(typeof(DiscriminatedJsonConverter), typeof(DynamicComponentDiscriminatorOptions))]
public interface IDynamicComponent 
{
    void Invoke();
}

// MUST have a parameterless ctor so it can be instantiated with Activator.CreateInstance
public class DynamicComponentDiscriminatorOptions : DiscriminatorOptions
{
    public override Type BaseType => typeof(IDynamicComponent );

    public override string DiscriminatorFieldName => "className";

    // true if you want to set the value of ClassName, false to skip it
    public override bool SerializeDiscriminator => false;

    public override IEnumerable<(string TypeName, Type Type)> GetDiscriminatedTypes()
    {
        //List all possible IDynamicComponent, or use reflection.
        yield return (nameof(FooComponent), typeof(FooComponent));
        yield return (nameof(BarComponent), typeof(BarComponent));
    }
}
Aron
  • 15,464
  • 3
  • 31
  • 64
  • Hi Aron. The dynamic components are separate projects and I'm not sure how I can list them in `DynamicComponentDiscriminatorOptions`. – Ibrahim Timimi Feb 10 '21 at 08:10
  • You will have to. My advice would be to look into MEF, and having some plugin initialization code that registers the classes with the serialiser. The serialiser needs to know all the possible types that it can deserialise. – Aron Feb 15 '21 at 20:00