Generisk HttpClient i ASP.NET Core

I det här inlägget beskriver vi hur du kan skapa en förinställd generisk HttpClient i ASP.NET Core. En förinställd generisk HttpClient kan vara användbar om du vill ansluta till ett Api som använder samma metodnamn för alla åtkomstpunkter (uri:s).

En HttpClient används för att skicka HTTP-förfrågningar till en webbadress och för att ta emot HTTP-svar från en webbadress. Du kan använda en HttpClient för att ansluta till olika Apier. Vår HttpClient ska användas för att anropa metoder i Fortnox Api.

Det är inte rekommenderat att frekvent skapa och kassera HttpClients i ditt program. Du bör använda statiska http-klienter eller IHttpClientFactory. IHttpClientFactory har lagts till i ASP.NET Core 2.1 och används för att hantera skapandet av HttpClients på ett effektivt sätt. Du kan registrera namngivna klienter och förinställda klienter i ASP.NET Core, detta för att kunna hantera dina klienter på ett bekvämt sätt. Du kan också skapa nya ej namngivna klienter via IHttpClientFactory.

Gränssnitt

Vi har skapat ett generiskt gränssnitt för vår HttpClient. Den generiska datatypen betecknas som R (Root), beteckningen kan vara vilken bokstav som helst eller ett ord. En klass som implementerar detta gränssnitt kan vara asynkron, alla metoder i det här gränssnittet returnerar en Task.

public interface IFortnoxClient
{
    Task<FortnoxResponse<R>> Add<R>(R root, string uri);
    Task<FortnoxResponse<R>> Update<R>(R root, string uri);
    Task<FortnoxResponse<R>> Action<R>(string uri);
    Task<FortnoxResponse<R>> Get<R>(string uri);
    Task<FortnoxResponse<bool>> Delete(string uri);
    Task<FortnoxResponse<R>> UploadFile<R>(Stream stream, string file_name, string uri);
    Task<FortnoxResponse<bool>> DownloadFile(Stream stream, string uri);

} // End of the interface

Vi har använt FortnoxResponse som modell runt vår generiska datatyp, detta gör det möjligt att returnera flera objekt/värden från metoder i klasser som implementerar detta gränssnitt.

public class FortnoxResponse<R>
{
    #region Variables

    public R model { get; set; }
    public string error { get; set; }

    #endregion

    #region Constructors

    public FortnoxResponse()
    {
        // Set values for instance variables
        this.model = default(R);
        this.error = null;

    } // End of the constructor

    #endregion

    #region Get methods

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);

    } // End of the ToString method

    #endregion

} // End of the class

Generisk Fortnox-klient

Vår generiska Fortnox-klient innehåller alla metoder som vi behöver för att kommunicera med Fortnox Api. Alla metoder tar en uri som parameter och kan hantera olika datatyper som indata och utdata. Denna klient har beroenden för HttpClient och FortnoxOptions, det här är en förinställd klient eftersom sätter värden för HttpClient i konstruktorn.

public class FortnoxClient : IFortnoxClient
{
    #region Variables

    private readonly HttpClient client;
    private readonly FortnoxOptions options;

    #endregion

    #region Constructors

    public FortnoxClient(HttpClient http_client, IOptions<FortnoxOptions> options)
    {
        // Set values for instance variables
        this.client = http_client;
        this.options = options.Value;

        // Set values for the client
        this.client.BaseAddress = new Uri("https://api.fortnox.se/3/");
        this.client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        this.client.DefaultRequestHeaders.Add("Client-Secret", this.options.ClientSecret);
        this.client.DefaultRequestHeaders.Add("Access-Token", this.options.AccessToken);
            
    } // End of the constructor

    #endregion

    #region Add methods

    public async Task<FortnoxResponse<R>> Add<R>(R root, string uri)
    {
        // Convert the post to json
        string json = JsonConvert.SerializeObject(root, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });

        // Create the response to return
        FortnoxResponse<R> fr = new FortnoxResponse<R>();

        // Send data as application/json data
        using (StringContent content = new StringContent(json, Encoding.UTF8, "application/json"))
        {
            try
            {
                // Get the response
                HttpResponseMessage response = await this.client.PostAsync(uri, content);

                // Check the status code for the response
                if (response.IsSuccessStatusCode == true)
                {
                    // Get string data
                    string data = await response.Content.ReadAsStringAsync();

                    // Deserialize the data
                    fr.model = JsonConvert.DeserializeObject<R>(data);
                }
                else
                {
                    // Get string data
                    string data = await response.Content.ReadAsStringAsync();

                    // Add error data
                    fr.error = $"Add: {uri}. {Regex.Unescape(data)}";
                }
            }
            catch (Exception ex)
            {
                // Add exception data
                fr.error = $"Add: {uri}. {ex.ToString()}";
            }
        }

        // Return the response
        return fr;

    } // End of the Add method

    #endregion

    #region Update methods

    public async Task<FortnoxResponse<R>> Update<R>(R root, string uri)
    {
        // Convert the post to json
        string json = JsonConvert.SerializeObject(root, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });

        // Create the response to return
        FortnoxResponse<R> fr = new FortnoxResponse<R>();

        // Send data as application/json data
        using (StringContent content = new StringContent(json, Encoding.UTF8, "application/json"))
        {
            try
            {
                // Get the response
                HttpResponseMessage response = await this.client.PutAsync(uri, content);

                // Check the status code for the response
                if (response.IsSuccessStatusCode == true)
                {
                    // Get string data
                    string data = await response.Content.ReadAsStringAsync();

                    // Deserialize the data
                    fr.model = JsonConvert.DeserializeObject<R>(data);
                }
                else
                {
                    // Get string data
                    string data = await response.Content.ReadAsStringAsync();

                    // Add error data
                    fr.error = $"Update: {uri}. {Regex.Unescape(data)}";
                }
            }
            catch (Exception ex)
            {
                // Add exception data
                fr.error = $"Update: {uri}. {ex.ToString()}";
            }
        }

        // Return the response
        return fr;

    } // End of the Update method

    public async Task<FortnoxResponse<R>> Action<R>(string uri)
    {
        // Create the response to return
        FortnoxResponse<R> fr = new FortnoxResponse<R>();

        try
        {
            // Get the response
            HttpResponseMessage response = await this.client.PutAsync(uri, null);

            // Check the status code for the response
            if (response.IsSuccessStatusCode == true)
            {
                // Get string data
                string data = await response.Content.ReadAsStringAsync();

                // Deserialize the data
                fr.model = JsonConvert.DeserializeObject<R>(data);
            }
            else
            {
                // Get string data
                string data = await response.Content.ReadAsStringAsync();

                // Add error data
                fr.error = $"Action: {uri}. {Regex.Unescape(data)}";
            }
        }
        catch (Exception ex)
        {
            // Add exception data
            fr.error = $"Action: {uri}. {ex.ToString()}";
        }

        // Return the response
        return fr;

    } // End of the Action method

    #endregion

    #region Get methods

    public async Task<FortnoxResponse<R>> Get<R>(string uri)
    {
        // Create the response to return
        FortnoxResponse<R> fr = new FortnoxResponse<R>();

        try
        {
            // Get the response
            HttpResponseMessage response = await this.client.GetAsync(uri);

            // Check the status code for the response
            if (response.IsSuccessStatusCode == true)
            {
                // Get string data
                string data = await response.Content.ReadAsStringAsync();

                // Deserialize the data
                fr.model = JsonConvert.DeserializeObject<R>(data);
            }
            else
            {
                // Get string data
                string data = await response.Content.ReadAsStringAsync();

                // Add error data
                fr.error = $"Get: {uri}. {Regex.Unescape(data)}";
            }
        }
        catch (Exception ex)
        {
            // Add exception data
            fr.error = $"Get: {uri}. {ex.ToString()}";
        }

        // Return the post
        return fr;

    } // End of the Get method

    #endregion

    #region Delete methods

    public async Task<FortnoxResponse<bool>> Delete(string uri)
    {
        // Create the response to return
        FortnoxResponse<bool> fr = new FortnoxResponse<bool>();

        // Indicate success
        fr.model = true;

        try
        {
            // Get the response
            HttpResponseMessage response = await this.client.DeleteAsync(uri);

            // Check the status code for the response
            if (response.IsSuccessStatusCode == false)
            {
                // Get string data
                string data = await response.Content.ReadAsStringAsync();

                // Add error data
                fr.model = false;
                fr.error = $"Delete: {uri}. {Regex.Unescape(data)}";
            }
        }
        catch (Exception ex)
        {
            // Add exception data
            fr.model = false;
            fr.error = $"Delete: {uri}. {ex.ToString()}";
        }

        // Return the response
        return fr;

    } // End of the Delete method

    #endregion

    #region File methods

    public async Task<FortnoxResponse<R>> UploadFile<R>(Stream stream, string file_name, string uri)
    {
        // Create the response to return
        FortnoxResponse<R> fr = new FortnoxResponse<R>();

        // Send data as multipart/form-data content
        using (MultipartFormDataContent content = new MultipartFormDataContent())
        {
            // Add content
            content.Add(new StreamContent(stream), "file", file_name);

            try
            {
                // Get the response
                HttpResponseMessage response = await this.client.PostAsync(uri, content);

                // Check the status code for the response
                if (response.IsSuccessStatusCode == true)
                {
                    // Get the data
                    string data = await response.Content.ReadAsStringAsync();

                    // Deserialize the data
                    fr.model = JsonConvert.DeserializeObject<R>(data);
                }
                else
                {
                    // Get string data
                    string data = await response.Content.ReadAsStringAsync();

                    // Add error data
                    fr.error = $"UploadFile: {uri}. {Regex.Unescape(data)}";
                }
            }
            catch (Exception ex)
            {
                // Add exception data
                fr.error = $"UploadFile: {uri}. {ex.ToString()}";
            }
        }

        // Return the response
        return fr;

    } // End of the UploadFile method

    public async Task<FortnoxResponse<bool>> DownloadFile(Stream stream, string uri)
    {
        // Create the response to return
        FortnoxResponse<bool> fr = new FortnoxResponse<bool>();

        // Indicate success
        fr.model = true;

        try
        {
            // Get the response
            HttpResponseMessage response = await this.client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);

            // Check the status code for the response
            if (response.IsSuccessStatusCode == true)
            {
                // Get the stream
                await response.Content.CopyToAsync(stream);
            }
            else
            {
                // Get string data
                string data = await response.Content.ReadAsStringAsync();

                // Add error data
                fr.model = false;
                fr.error = $"DownloadFile: {uri}. {Regex.Unescape(data)}";
            }
        }
        catch (Exception ex)
        {
            // Add exception data
            fr.model = false;
            fr.error = $"DownloadFile: {uri}. {ex.ToString()}";
        }

        // Return the response
        return fr;

    } // End of the DownloadFile method

    #endregion

} // End of the class

Tjänster

Du kan registrera FortnoxOptions och vår FortnoxClient som tjänster i metoden ConfigureServices i klassen StartUp i ditt projekt. När du anropar AddHttpClient lägger du även till IHttpClientFactory i din servicebehållare. FortnoxOptions skapas direkt från filen appsettings.json. Vi lägger också till en klienthanterare till klienten med automatisk dekompression, detta innebär också att headers kommer att läggas till för att acceptera gzip eller deflate i klientens inställningar.

// Create api options
services.Configure<FortnoxOptions>(configuration.GetSection("FortnoxOptions"));

// Add clients
services.AddHttpClient<IFortnoxClient, FortnoxClient>().ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate });

Du kan också skapa en FortnoxClient med hjälp av konstruktorn. Vi skapar en tom klient från IHttpClientFactory som en parameter i konstruktorn, använd en namngiven klient om du vill lägga till automatisk dekompression.

// Create api options
IOptions<FortnoxOptions> options = Options.Create<FortnoxOptions>(new FortnoxOptions
{
	ClientSecret = "1fBN6P7jRA",
	AccessToken = "asdfasdfasdfasdfasdf"
});

// Create a fortnox client
IFortnoxClient fortnox_client = new FortnoxClient(this.client_factory.CreateClient(), options);

Hur använder man klienten?

För att kunna använda klienten behöver vi först modeller, du kan kolla in vårt GitHub-projekt a-fortnox-client för fler exempel på modeller.

public class ModesOfPaymentsRoot
{
    #region Variables

    public IList<ModeOfPayment> ModesOfPayments { get; set; }
    public MetaInformation MetaInformation { get; set; }

    #endregion

    #region Constructors

    public ModesOfPaymentsRoot()
    {
        this.ModesOfPayments = null;
        this.MetaInformation = null;

    } // End of the constructor

    #endregion

} // End of the class

public class ModeOfPaymentRoot
{
    #region Variables

    public ModeOfPayment ModeOfPayment { get; set; }

    #endregion

    #region Constructors

    public ModeOfPaymentRoot()
    {
        this.ModeOfPayment = null;

    } // End of the constructor

    #endregion

} // End of the class

public class ModeOfPayment
{
    #region Variables

    [JsonProperty("@url")]
    public string Url { get; set; }
    public string Code { get; set; }
    public string Description { get; set; }
    public string AccountNumber { get; set; }

    #endregion

    #region Constructors

    public ModeOfPayment()
    {
        // Set values for instance variables
        this.Url = null;
        this.Code = null;
        this.Description = null;
        this.AccountNumber = null;

    } // End of the constructor

    #endregion

} // End of the class

Vi har modeller för betalningssätt och kan nu använda vår klient för att lägga till, uppdatera och hämta betalningsmetoder.

public async Task TestAddPost()
{
    // Create a post
    ModeOfPaymentRoot post = new ModeOfPaymentRoot
    {
        ModeOfPayment = new ModeOfPayment
        {
            Code = "LB",
            Description = "Bankgiro LB",
            AccountNumber = "1940"
        }
    };

    // Add the post
    FortnoxResponse<ModeOfPaymentRoot> fr = await this.fortnox_client.Add<ModeOfPaymentRoot>(post, "modesofpayments");

} // End of the TestAddPost method

public async Task TestUpdatePost()
{
    // Create a post
    ModeOfPaymentRoot post = new ModeOfPaymentRoot
    {
        ModeOfPayment = new ModeOfPayment
        {
            Code = "LB",
            Description = "Bankgiro LB",
            AccountNumber = "1930"
        }
    };

    // Update the post
    FortnoxResponse<ModeOfPaymentRoot> fr = await this.fortnox_client.Update<ModeOfPaymentRoot>(post, "modesofpayments/LB");

} // End of the TestUpdatePost method

public async Task TestGetPost()
{
    // Get a post
    FortnoxResponse<ModeOfPaymentRoot> fr = await this.fortnox_client.Get<ModeOfPaymentRoot>("modesofpayments/LB");

} // End of the TestGetPost method

public async Task TestGetList()
{
    // Get a list
    FortnoxResponse<ModesOfPaymentsRoot> fr = await this.fortnox_client.Get<ModesOfPaymentsRoot>("modesofpayments?limit=2&page=1");

} // End of the TestGetList method

Lämna ett svar

E-postadressen publiceras inte. Obligatoriska fält är märkta *