Tomato
Tomato

Reputation: 122

Move component's Dependency Injected methods to separate CS library in Blazor Server-Side

Would like to make reusable functions/methods in separate .cs files and/or in a separate library. Of course i know how to do this, my problem is i don't know how to do this in the case where i need to use dependency injected elements. For example, here is a very easy function about getting a user's property:

[Inject]
public UserManager<IdentityUser> UserManager { get; set; }

public async Task<string> GetUserId(string emailName)
{
    var user = await userManager.FindByNameAsync(emailName);
    if (user == null)
        return null;
    return user.Id;
}

This is working in every razor file/component, if (!) the component initialized. If not, the injected services also not initialized and get a null-exception error.

I don't want to rewrite/copy this code-snippet into every component where i would like to use, so i would like to create a class or library for it. What should be the right way to do it? The best thing would be if i can move these kind of functions into a separate Class Library or Razor Class Library.

UPDATE:

@Nkosi provide the prefect solution, but i would like to think forward a little bit. That previous code sample was really small, so what's about if i have 2-3-4 or more DI needed for my custom method? For example (in the Razor component):

[Inject]
public UserManager<IdentityUser> UserManager { get; set; }
[Inject]
public SignInManager<IdentityUser> SignInManager { get; init; }
[Inject]
public IJSRuntime jsRuntime { get; init; }
[Inject]
public CookieAuthenticationOptions cookieAuthenticationOptions { get; set; }
[Inject]
public IOptionsMonitor<CookieAuthenticationOptions> c_options { get; set; }


public async Task<string> GetUserWithOtherStuff(string email, string psw)
{
    cookieAuthenticationOptions = c_options.Get("schema");
    var user = await UserManager.FindByNameAsync(email);
    var valid = await SignInManager.UserManager.CheckPasswordAsync(user, psw);
    // etc..

    return something;
}

Upvotes: 3

Views: 978

Answers (2)

Bennyboy1973
Bennyboy1973

Reputation: 4208

Be careful with common injections like UserManager.

If they are injected into multiple other services, and those services are in turn injected onto the same page, you will likely end up with threading exceptions blowing up your page.

For example, in a market page, you might have a service that looks up the existing funds for the current user, and another service that checks for the user's added items, and several other things that all depend on knowing the current User's ID.

If you just go happily injecting a custom service that accesses the UserManager into a lot of nested components, you may end up in grief like I did when I was first learning Blazor.

Upvotes: 0

Nkosi
Nkosi

Reputation: 247018

Move it to separate class / library

public interface IUserService {
    Task<string> GetUserId(string emailName);
}

public class UserService : IUserService {
    private readonly UserManager<IdentityUser> userManager;
    
    public UserService(UserManager<IdentityUser> userManager) {
        this.userManager = userManager;
    }

    public async Task<string> GetUserId(string emailName) {
        var user = await userManager.FindByNameAsync(emailName);
        if (user == null)
            return null;
        return user.Id;
    }
}    

and inject the encapsulated service where needed

[Inject]
public IUserService  UserService { get; set; }

Make sure that all the necessary dependencies are registered with the service collection.

an extension method can be made to group the needed dependency registration

public static IServiceCollection AddUserServices(this IServiceCollection services) {
    services
        .AddScoped<IUserService, UserService>()
        .AddIdentity<.....>()
        //... add what is needed for this library library to function

    return services;
}

and invoked/reused where needed

//...

services.AddUserServices();

//...

Upvotes: 3

Related Questions