Search code examples
c#nuget-package

How to tell self-created NuGet package that something changed in application?


We have created a simple NuGet Package which wraps our most used Request functions ( basically sending API-Requests with HttpClient) so we do not have to implement them in each project again. We use a class called RequestService, which handles the requests and holds the instance of HttpClient. Now in one app, I can set my API-URL dynamically in the settings, so I want to tell the RequestService that there were some changes in the config of the HttpClient.

I thought of raising an event at the time the settings are changed, but as the RequestService NuGet does not know about the app, it seems to be not the cleanest solution for me. Is there a nice solution on how to tell a NuGet package that there has something changed in my code?

Edit:
NuGet package - RequestService:

public class RequestService
{
     private readonly IHttpClientFactory _httpClientFactory;
     private HttpClient _httpClient;

     public RequestService(IHttpClientFactory httpClientFactory)
     {
        _httpClientFactory = httpClientFactory;
        _httpClient = _httpClientFactory.CreateClient("APIClient");
     }

    public async Task PostAsJson<T>(string url, T request)
    {
        HttpResponseMessage httpResponseMessage = await _httpClient.PostAsJsonAsync(url, request);
        if (httpResponseMessage.StatusCode == HttpStatusCode.BadRequest)
        {
            throw new Exception(await httpResponseMessage.Content.ReadAsStringAsync());
        }

        httpResponseMessage.EnsureSuccessStatusCode();
    }

     // more POST, GET and so on, with usage of _httpClient
}

MAUI app MAUIProgram.cs - The httpClientFactory is injected in this line:

builder.Services.AddHttpClient("APIClient", c => c.BaseAddress = new Uri(Preferences.Get(Constants.SETTING_SERVER, "http://192.168.10.125:5000/")));

MAUI app - SettingService - writes the URL of the HttpClient into Preferences, basically only this method is relevant (where _preferencesService only writes to Preferences:

public void SetUrl(string url)
{
    _preferencesService.SetStringValue(Constants.SETTING_SERVER, url);
}

Maui app - Sample user service (should mock a service, basically a few of those exist with different endpoints - but all of them use the GET and POST of the RequestService). Those Services are injected into DI as well:

public interface IUserService
{
    Task SaveUser(User user);
}

public class UserAPIService(IHttpClientFactory httpClient) : RequestService(httpClient), IUserService
{
    public async Task SaveUser(User user) => await PostAsJson(Constants.SaveUserPath, user);
}

Solution

  • Without seeing the code it is a bit hard to tell. Here are some options:

    You can create some abstraction allowing to inject url/settings in the RequestService via some intermediate object. For example something like the following:

    public interface IUrlProvider
    {
        string Url { get; }
    }
    
    public class RequestService
    {
        private readonly IUrlProvider _urlProvider;
    
        public RequestService(IUrlProvider urlProvider) => _urlProvider = urlProvider;
        
        // use _urlProvider.Url in the methods
    }
    
    // for non-dynamic urls
    public class ConstantUrlProvider : IUrlProvider
    {
        public ConstantUrlProvider(string url) => Url = url;
        
        public string Url { get; }
    }
    

    And another implementation which will be controlled by the app which will allow to manage/change the url when needed.

    Also you can consider using the Options pattern for managing settings which is used in .NET - it provides different options for handling such things, one of them being the IOptionsMonitor which has similar to previous approach IOptionsMonitor<TOptions>.CurrentValue property and allows to subscribe to changes via IOptionsMonitor<TOptions>.OnChange.

    Note that you can just create a client on the each call:

    public class RequestService
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private HttpClient GetHttpClient() => _httpClientFactory.CreateClient("APIClient");
    
        public RequestService(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }
    
        public async Task PostAsJson<T>(string url, T request)
        {
            using var httpClient = GetHttpClient();
            HttpResponseMessage httpResponseMessage = await httpClient.PostAsJsonAsync(url, request);
            if (httpResponseMessage.StatusCode == HttpStatusCode.BadRequest)
            {
                throw new Exception(await httpResponseMessage.Content.ReadAsStringAsync());
            }
    
            httpResponseMessage.EnsureSuccessStatusCode();
        }
    
        // more POST, GET and so on, with usage of _httpClient
    }
    

    Which is valid usage pattern.