3

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;
}
Tomato
  • 102
  • 1
  • 11
  • If you have that many dependencies to inject then you have more of a design problem to deal with first. That is usually seen as a code smell that your class/component is doing too much and violates single responsibility principle. – Nkosi Mar 01 '21 at 13:16
  • The same approach provided in my answer can still be applied to this scenario. You will just end up with having more dependencies to inject into your service (however, note my previous comment). It will even simplify your component as all of those additional dependencies will be aggregated into the new abstraction. – Nkosi Mar 01 '21 at 13:17
  • You are right, usually use a few, but try to find an extreme example.... Thanks, try to play with them a little bit. – Tomato Mar 01 '21 at 13:22

2 Answers2

3

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();

//...
Nkosi
  • 235,767
  • 35
  • 427
  • 472
0

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.

Bennyboy1973
  • 3,413
  • 2
  • 11
  • 16
  • Thank you for the advice, try to keep in mind. I haven't encountered this problem (yet) and now I try to organize these things into one main service. – Tomato Mar 02 '21 at 10:05
  • Yeah, I don't have that problem anymore, either. It's been a while since I got Blazor to crash with anything but a null reference or a problem with a database query. – Bennyboy1973 Mar 02 '21 at 12:05