Invoke methods of a class that has dependency injection C# .NET Core 6.0

403 Views Asked by At

I have a controller in C# that invokes a class, which in turn makes HTTP requests and returns its response, according to the code sequence below:

I added the following line in Program.cs:

builder.Services.AddHttpClient();

I created a class that receives the controller invocation and makes the HTTP request to the external website.

public class Users
{
    public async Task<string> GetUsers(HttpClient _httpClient )
    {
        var request = new HttpRequestMessage(new HttpMethod("get"), "https://gorest.co.in/public/v2/users");

        var res = await _httpClient.SendAsync(request);

        var content = await res.Content.ReadAsStringAsync();
        return content;
    }
}

I created a controller to receive my HTTP requests, and this controller instantiates a client through the IHttpClientFactory, with the aim of avoiding exhaustion of HTTP sockets...

[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IHttpClientFactory _client;

    public UsersController(IHttpClientFactory client)
    {
        _client = client;
    }

    public class UsersModel
    {
        public int id { get; set; }
        public string name { get; set; }
        public string email { get; set; }
        public string gender { get; set; }
        public string status {  get; set; }
    }

    [HttpGet("GetUsers")]
    public async Task<List<UsersModel>> GetUsers()
    {
        var myClient = _client.CreateClient();
        return Newtonsoft.Json.JsonConvert.DeserializeObject<List<UsersModel>>(await new Users().GetUsers(myClient));
    }
}

Ok, the code sequence above does the job correctly, but there is one thing that bothers me a lot, which is this dependency injection and creation of an HTTP client in each controller that calls this class.

I would like to create a class that accepts being invoked by the controller, but this class itself creates its HTTP client, without it having to be passed as a parameter by the controller...

I tried something like this: I modified my controller to no longer have dependency injection, but just invoke the class:

[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    public class UsersModel
    {
        public int id { get; set; }
        public string name { get; set; }
        public string email { get; set; }
        public string gender { get; set; }
        public string status {  get; set; }
    }

    [HttpGet("GetUsers")]
    public async Task<List<UsersModel>> GetUsers()
    {
        return Newtonsoft.Json.JsonConvert.DeserializeObject<List<UsersModel>>(await new Users().GetUsers());
    }
}

The class, in turn, would be responsible for creating the HTTP client.

public class Users
{
    private readonly IHttpClientFactory _client;

    public Users(IHttpClientFactory client)
    {
        _client = client;
    }

    public async Task<string> GetUsers()
    {
        var _httpClient = _client.CreateClient();
        var request = new HttpRequestMessage(new HttpMethod("get"), "https://gorest.co.in/public/v2/users");

        var res = await _httpClient.SendAsync(request);

        var content = await res.Content.ReadAsStringAsync();
        return content;
    }
}

But I now get this error: there is no argument given that corresponds to the required parameter...

I've seen some people saying that they have to implement a constructor, insert a : base()... but I confess that I'm not at a sufficient level to solve this problem on my own. I would really appreciate it if anyone can help me with this task.

1

There are 1 best solutions below

0
Lei On

There are many problems with your code:

  1. dependency injection: all items in the service pool should be a service. Your Users class is actually a service, but obviously you did not register your service into DI. This often occurs in Program.cs (or .NET Core 3 and below, in StartUp). And should be like this:
builder.Services.AddTransient<Users>(); // Transient due to HttpClient

Besides, a service class usually have the name convention: XxxService. And usually has an Interface behind it for best practice of DI. but no big deal for your case.

  1. You can directly inject the HttpClient into Users, not the factory. You already have DI, it will handle the instantiation of HttpClient.

  2. After you register your service into DI, you can call it from your Controller:

private readonly Users _users; // here no more HttpClient since you injected it into Users.

    public UsersController(Users users)
    {
        _users= users;
    }
  1. Your GetUsers method in the service should be like this:
public async Task<string> GetUsersAsync() // async method should end up with 'Async'
    {
        var _httpClient = _client.CreateClient(); // as mentioned in 2, you could directly use _httpClient, instead of create a new instance using factory pattern.

        var res = await _httpClient.GetAsync("https://gorest.co.in/public/v2/users");

        var content = await res.Content.ReadAsStringAsync();
        return content;
        // error handling?
    }
  1. you inject your Users service into the Controller, just like how you inject the HttpClient into Users. and no more new()!
return Newtonsoft.Json.JsonConvert.DeserializeObject<List<UsersModel>>(await _users.GetUsers()); // here _users is the instance of `Users` from DI

Please learn the basis before you contribute code for production.