Aspnetcore.docs: Need clarification on OwningComponentBase usage

Created on 14 Sep 2019  Â·  7Comments  Â·  Source: dotnet/AspNetCore.Docs

There's a section on OwningComponentBase<T> but I'm a little confused about it. It states that there is a ScopedServices property of type IServiceProvider that is scoped to the component's creation. However, it also has a generic type parameter, and based on the example provided, I assume also a Service property of type T, which presumably is automatically injected? Is that correct?

Does OwningComponentBase only support a single service type to be injected or will it also let you inject multiple service types in addition to the Service property which is presumably automatically injected (is it automatically injected?)?

Is there a non-generic version of OwningComponentBase?

Is there a way to create scopes that last less than the lifetime of the component? For example, let's say I have a blazor server side app, and I want to have a Save method in my component that saves a record but stays on the page. Each time the Save method is executed I want a new instance of my DbContext to be created and injected (roughly equivalent to calling a REST service, where the server-side DbContext is scoped to that request). How is that accomplished?

Edit: I found some answers to the above questions (though I'll leave this issue open as a suggestion for clarification in the docs):

So, first thing I found out is that, contrary to what appears to be implied by that last section on OwningComponentBase, services injected into the component appear to already be scoped to that component's lifetime automatically. So if you have a transient service injected into the component, navigate away and back to the component, the service will be constructed each time the component is instantiated (though not if the URL changes but is mapped to the same component, as expected).

If you add @inherits OwningComponentBase<TServiceType> and use the Service property, it will be scoped the same way as if you injected it normally. Not sure what the purpose is, in this case. Additionally, contrary to the documentation, there is no ScopedServices property on the generic version of OwningComponentBase.

There is a non-generic version of OwningComponentBase which does have a ScopedServices property. This property can be used to inject services on demand, and when those services are transient they are constructed at the time they are requested - exactly what I wanted when injecting transient DbContext instances or transient services that inject transient DbContext instances.

Example:
Given this service definition (which should probably implement IDisposable):

    public interface IMyService
    {
        Task<bool> SaveUsingDbContext(string value);
    }

    public class MyService : IMyService
    {
        public MyService() => Console.WriteLine("MyService constructed!");
        public Task<bool> SaveUsingDbContext(string value)
        {
            Console.WriteLine($"TODO: Save value '{value}' to database...");
            return Task.FromResult(true);
        }
    }

And this component:

@page "/counter"
@inherits OwningComponentBase

<h1>Counter</h1>

<input @bind-value="Value" />

<button class="btn btn-primary" @onclick="Save">Save</button>

@code {
    string Value { get; set; } = "";

    /// <Summary>
    /// The value is saved using async IMyService injected on demand.
    /// Since IMyService is a transient service, it (and any services
    /// it depends on, such as a DbContext) will be created within this
    /// method.
    /// </Summary>
    void Save()
    {
        var myService = ScopedServices.GetService(typeof(IMyService)) as IMyService;
        myService.SaveUsingDbContext(Value);
    }
}

This is the result when you click the "Save" button:
image


Document Details

⚠ Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

Blazor P2 PU Source - Docs.ms area-mvc doc-enhancement

Most helpful comment

@guardrex, yes, I was just planning to focus on the DI topic, so I'll work on that this week. I agree that it will line up really well with the EF-in-Blazor topic. That's actually how I ended up down this path - trying to get EF.Core working in a server-side Blazor app. Want to make sure other people who go this path have the right docs to be successful :)

And thanks, @JeroMiya, for the link to the blog. That's similar to what I'll cover in the docs, I expect.

All 7 comments

@guardrex I can take a shot at updating the docs to cover this scenario a little better. I was just using OwningComponentBase in a side project and thought we could use a little more info here about the difference between OwningComponentBase and OwningComponentBase<T>, as @JeroMiya points out.

Let me know if you already have something in the works here, otherwise I'll plan to make some updates next week.

@mjrousos Sounds great ... thanks!

Is this just for the DI topic? If so, cool. If not tho, best to wait ... we have a little pile of PRs to merge first affecting other topics.

... and then after you work on this, I'll have something nice to link to for https://github.com/aspnet/AspNetCore.Docs/issues/16558, which is coming up soon-ish ...

If it's OK to post, after I filed this issue I investigated on my own and wrote a small blog post on what I found:
https://medium.com/@SleepyDaddySoft/scoped-dependency-injection-in-server-side-blazor-c759db967dc0

Here's the final working example code:

The data service:

public interface IDataService : IDisposable
{
  Task<string> GetValueAsync();
  Task<bool> SaveValueAsync(string value);
}
public class DataService : IDataService
{
  public DataService()
    => Console.WriteLine("DataService constructed!");
  // not a proper IDisposable implementation,
  // but it'll do for demo purposes
  public void Dispose()
  {
    Console.WriteLine("DataService disposed!");
  }
  public Task<string> GetValueAsync()
  {
    Console.WriteLine("TODO: Get value from database...");
    return Task.FromResult("Hello World!");
  }
  public Task<bool> SaveValueAsync(string value)
  {
    Console.WriteLine($"TODO: Save value '{value}' to database...");
    return Task.FromResult(true);
  }
}

The component:

@page "/edit"
@using Microsoft.Extensions.DependencyInjection
@inject IServiceScopeFactory ScopeFactory
<h1>Dependency Injection!</h1>
<input @bind-value="Value" />
<button @onclick="Save">Save</button>
@code {
  string Value { get; set; } = "";
  protected override async Task OnInitializedAsync()
  {
    using(var serviceScope = ScopeFactory.CreateScope())
    {
      var myService = serviceScope.ServiceProvider.GetService<IDataService>();
      Value = await myService.GetValueAsync();
    }
  }
  void Save()
  {
    using(var serviceScope = ScopeFactory.CreateScope())
    {
      var myService = serviceScope.ServiceProvider.GetService<IDataService>();
      myService.SaveValueAsync(Value);
    }
  }
}

And the output:

DataService constructed!
TODO: Get value from database...
DataService disposed!
DataService constructed!
TODO: Save value 'Hello World!' to database...
DataService disposed!
DataService constructed!
TODO: Save value 'Hello World!' to database...
DataService disposed!
DataService constructed!
TODO: Get value from database...
DataService disposed!

@guardrex, yes, I was just planning to focus on the DI topic, so I'll work on that this week. I agree that it will line up really well with the EF-in-Blazor topic. That's actually how I ended up down this path - trying to get EF.Core working in a server-side Blazor app. Want to make sure other people who go this path have the right docs to be successful :)

And thanks, @JeroMiya, for the link to the blog. That's similar to what I'll cover in the docs, I expect.

Sounds great ...... we're still working on merging a big pile of open Blazor PRs, but they don't touch the DI topic. I think it's safe for you to proceed now.

I've opened a PR for this. @guardrex, if you could take a look, that would be great. For some reason, GitHub isn't letting me request you as a reviewer on the PR.

Actually, I'm able to add you as a reviewer now, so I've done that.

Was this page helpful?
0 / 5 - 0 ratings