Aspnetcore: Allow Blazor components injection

Created on 30 Nov 2019  路  11Comments  路  Source: dotnet/aspnetcore

I think we should be able to abstract components into interfaces and inject them into other components using DI, the same way we inject other dependencies. Something like this:

TextBox.razor

@inject ILabel Label
@inject IInput Input

<div class="textbox">
    <Label Text="..."/> 
    <Input Value="..." />
</div>

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ILabel, MyLabelImplementation>();
    services.AddSingleton<IInput, MyInputImplementation>();
}

I know that it's already possible to render the injected instance of IComponent using RenderTreeBuilder, but the syntax required to do that is incredibly verbose. It would be so much cooler if we could simply use the injected components with the standard syntax we use for any other component.

Author Feedback area-blazor

Most helpful comment

@javiercn imagine that over time you start building your own library of reusable components, and some of these components internally use other components. For instance: you build a LoginForm component that internally uses a custom Input component. Such LoginForm component will be used many times across different projects, and in some occasions you might want to customize the way the internal Input component looks and behaves without having to re-write the entire LoginForm component.

Now, in my opinion, the most obvious way to do so would be to treat the Input component as a dependency of LoginForm, and to inject such dependency in the DI container. That way, when you want to swap their implementation to achieve a different result, you would simply do:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IInput, MyNewInputImplementation>();
}

Since abstraction is a basic design principle when it comes to services, I don't see why we shouldn't use it for UI components... And again, what I'm asking can already be achieved, the only issue is that the syntax is incredibly verbose:

@inject IInput Input

<div class="login-form">
   @RenderInjected(Input, new[]
    {
        new KeyValuePair<string, object>(nameof(IInput .Value), "Some value")
    })
</div>

@code {

    private static RenderFragment RenderInjected(IComponent component, IEnumerable<KeyValuePair<string, object>> attributes = null) => builder =>
    {
        builder.OpenComponent(0, component.GetType());
        builder.AddMultipleAttributes(1, attributes);
        builder.CloseComponent();
    };
}

So I'm saying, instead of resorting to using RenderTreeBuilder, it would be great if this was an accepted Blazor syntax:

@inject IInput Input

<div class="login-form">
   <Input Value="some value" />
</div>

All 11 comments

@FrancescoLorenzetti84 thanks for contacting us.

The framework is responsible for the lifetime of components, as such they can not be part of the DI container.

I'm not sure what specific API you are referring to, but it is likely that if you are working at that level you will likely be interfering with the Blazor rendering algorithm and you are going to get undefined/unpredictable behavior.

We are not sure what you are trying to achieve by having an abstraction over a component. You can make any component implement as many interfaces as you want or declare the methods in the component virtual if you want to have a specialized component hierarchy.

@javiercn imagine that over time you start building your own library of reusable components, and some of these components internally use other components. For instance: you build a LoginForm component that internally uses a custom Input component. Such LoginForm component will be used many times across different projects, and in some occasions you might want to customize the way the internal Input component looks and behaves without having to re-write the entire LoginForm component.

Now, in my opinion, the most obvious way to do so would be to treat the Input component as a dependency of LoginForm, and to inject such dependency in the DI container. That way, when you want to swap their implementation to achieve a different result, you would simply do:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IInput, MyNewInputImplementation>();
}

Since abstraction is a basic design principle when it comes to services, I don't see why we shouldn't use it for UI components... And again, what I'm asking can already be achieved, the only issue is that the syntax is incredibly verbose:

@inject IInput Input

<div class="login-form">
   @RenderInjected(Input, new[]
    {
        new KeyValuePair<string, object>(nameof(IInput .Value), "Some value")
    })
</div>

@code {

    private static RenderFragment RenderInjected(IComponent component, IEnumerable<KeyValuePair<string, object>> attributes = null) => builder =>
    {
        builder.OpenComponent(0, component.GetType());
        builder.AddMultipleAttributes(1, attributes);
        builder.CloseComponent();
    };
}

So I'm saying, instead of resorting to using RenderTreeBuilder, it would be great if this was an accepted Blazor syntax:

@inject IInput Input

<div class="login-form">
   <Input Value="some value" />
</div>

@FrancescoLorenzetti84 thanks for the clarification.

The way you are rendering the component currently is causing you to perform additional work. I believe the core of your scenario involves simply having a "Name" you can use as an abstraction within your components to reference a "primitive" in markup without referring to a specific implementation.

I think that you can achieve this without relying on the component itself being registered in DI and by simply keeping the mappings separate.

Something like this should likely do the trick

public class DynamicComponentBase<T> where T : IComponent
{
  [Inject] public ComponentMapping { get; set; }

  public void BuildRenderTree(RenderTreeBuilder builder)
  {
      builder.OpenComponent(0, ComponentMapping[typeof(T)]);
      builder.AddMultipleAttributes(1, paramsAsAttributes);
      builder.CloseComponent();
  }
}
public class Input : DynamicComponentBase<Input>
{
    [Parameter] public string PlaceHolder { get; set; }
}

CustomInput.razor

@inherits Input

<input type=text placeholder="@Placeholder" />

Keep in mind that this is just a sketch and that there are likely other aspects I did not take into account. The main point is that there are alternative and better ways to achieve this that don't involve injecting the component itself into another component and simply keeping a map of types and using that to drive the customizations.

In general doing something like this will hurt linking a lot (as it likely looses the ability to detect things being used).

This is not something that we plan to do in the near future. I'm leaving the issue up for discussion within the team, but I anticipate this is not something we'll include in the core framework.

@javiercn I also think the use-case that @FrancescoLorenzetti84 is proposing should be able to do. I would like to be able to define custom implementation of the existing components in my library. Just like in the example above.

What I don't agree with, is injecting components via @inject IInput Input. I think Blazor should be able to do that by it'self if we told it in advance.

So for example for Input component we can _tell_ to Blazor that MyInputComponent is an actual implementation. Then the Blazor will do it's magic in behind.

<div class="login-form">
   <Input Value="some value" /> // instead of Input a Blazor would call MyInputComponent
</div>

This was already requested previously and there was also a PR that was put behind: #7962

@javiercn I see your point, but the "name" thing feels a bit like a hack and sometimes just doesn't work simply because you want to have 2 different implementations of the same interface being used in the same app in different contexts and you cannot have 2 files with the same name (not to mention that you wouldn't be able to decide according to what logic one should be used over the other).

I really hope that the team will agree with me on this one, I am working on a big library written entirely in Blazor and I have to say that not having this feature is kind of crippling the ability to make components really reusable while customizable. It feels weird to me that the bridge design pattern, which is so well established for services, is not adopted for UI components too.

We've discussed this within the team and we don't think we'll implement this in the core framework, given that it has issues with linking and that this can be built on top of the framework if needed.

I would really like for you guys to consider this feature once again. The problem with having a list of mapped components in our own code and then rendering them through BuildRenderTree(...) it that there is going to be created two instanced of the same thing but only one will be actually used. What I mean by that?

Lets follow your example @javiercn. The similar thing/problem I also have in my components library.

step by step:

  1. So, when Blazor goes and render <Input>, it will create an instance of it.
  2. Than in that instance it will check if it have a mapped custom implementation.
  3. If it does, it will call BuildRenderTree(...)
  4. here we're calling builder.OpenComponent(0, ComponentMapping[typeof(T)]); to create <SomeCustomInput>
  5. Now the <SomeCustomInput> is actually rendered but <Input> is still alive
  6. All of the parameters also have to be copied and passed from <Input> into <SomeCustomInput>

There is also some other problems with these kind of workaround but I will not mention them as this post would be too long.

Anyways, I think the Blazor itself should have to have a mapped list of components and then create them as they go. That way the problems above would be eliminated with all other possible ones.

Possible Blazor mapping syntax

componentMapper.Map<Input, SomeCustomInput>();

I just started learning the Blazor and I also had the thought about dynamic generate of a component.
I tried to create a "helper" component and something worked. I would be appreciated if you'll say what's wrong and what performance impact could be made.

  1. Create our magic component, as shown below:
public class DynamicComponent<T> : ComponentBase where T : IComponent
{
    [Inject]
    protected T Component { get; set; }

    protected IEnumerable<KeyValuePair<string, object>> Attributes { get; set; }

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenComponent(0, Component.GetType());
        builder.AddMultipleAttributes(1, Attributes);
        builder.CloseComponent();
    }

    protected override void OnParametersSet()
    {
        base.OnParametersSet();
        this.Attributes = this.GetType()
            .GetProperties()
            .Where(w => w.DeclaringType != typeof(DynamicComponent<T>) && w.GetCustomAttributes(typeof(ParameterAttribute), true)?.Length > 0)
            .Select(s => new KeyValuePair<string, object>(s.Name, s.GetValue(this)))
            .ToList();
    }
}
  1. Create an interface which inherits from IComponent interface, as shown below:
public interface IInput: IComponent
{
    string Value { get; set; } // our attribute
}
  1. Create implementation of our interface:
@inherits IInput

<div class="my-component">
    My value: @Value
</div>

@code{ 

    [Parameter]
    public string Value { get; set; }
}
  1. Register our component:
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IInput, MyComponent>();
    }
}
  1. Create a container:
public class InputContainer : DynamicComponent<IInput>, IInput
{
    [Parameter]
    public string Value { get; set; }
}
  1. Usage:
<InputContainer Value="Some Data"/>

@dorin182 I don't see anything particularly wrong with your technique, except that as I mentioned before the amount of code required to make it work is absolutely ridiculous... Who is going to do that for every component of a large code base? That is why I suggested that the Blazor team should allow components DI without any extra level of indirection.

well, taking into account that Blazor team has done huge great work, this feature could be done in the near future. Meanwhile could create a lib which does a part of the job alike MediatR. After a while maybe will implement it as well as Autofac.

And I don't think it would be that difficult for Blazor team to implement this feature. I think they would just need to extent current ComponentFactory with dictionary of components and their custom counterpart.

Was this page helpful?
0 / 5 - 0 ratings