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 API:er. 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
Modeller
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
namespace Annytab.Fortnox.Client.V3
{
public class FortnoxOptions
{
#region Variables
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string AuthorizationCode { get; set; }
public string AccessToken { get; set; }
#endregion
#region Constructors
public FortnoxOptions()
{
// Set values for instance variables
this.ClientId = "";
this.ClientSecret = "";
this.AuthorizationCode = "";
this.AccessToken = "";
} // End of the constructor
#endregion
} // End of the class
} // End of the namespace
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