|
1 | 1 | namespace JamieMagee.MicrosoftSecurityUpdates.Client; |
2 | 2 |
|
3 | 3 | using System.Reflection; |
| 4 | +using System.Text.Json; |
4 | 5 | using JamieMagee.MicrosoftSecurityUpdates.Client.Models; |
5 | 6 |
|
6 | 7 | public sealed class MicrosoftSecurityUpdatesClient : IMicrosoftSecurityUpdatesClient, IDisposable |
7 | 8 | { |
8 | | - private readonly RestClient client; |
| 9 | + private readonly HttpClient httpClient; |
| 10 | + private readonly bool shouldDisposeHttpClient; |
| 11 | + private readonly JsonSerializerOptions jsonSerializerOptions; |
9 | 12 |
|
10 | | - public MicrosoftSecurityUpdatesClient(string baseUri) |
| 13 | + public MicrosoftSecurityUpdatesClient(HttpClient httpClient) |
11 | 14 | { |
12 | | - var version = typeof(MicrosoftSecurityUpdatesClient).GetTypeInfo().Assembly.GetName().Version; |
13 | | - |
14 | | - var clientOptions = new RestClientOptions(baseUri) |
15 | | - { |
16 | | - UserAgent = $"JamieMagee.MicrosoftSecurityUpdates.Client/{version}", |
17 | | - }; |
| 15 | + this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); |
| 16 | + this.shouldDisposeHttpClient = false; |
| 17 | + this.jsonSerializerOptions = CreateJsonSerializerOptions(); |
18 | 18 |
|
19 | | - this.client = new RestClient(clientOptions) |
20 | | - .AddDefaultHeader("Accept", "application/json"); |
| 19 | + this.ConfigureHttpClient(); |
21 | 20 | } |
22 | 21 |
|
23 | | - public MicrosoftSecurityUpdatesClient() |
24 | | - : this("https://api.msrc.microsoft.com/cvrf/v2.0") |
| 22 | + public MicrosoftSecurityUpdatesClient(string baseUri = "https://api.msrc.microsoft.com/cvrf/v3.0/") |
25 | 23 | { |
| 24 | + this.httpClient = new HttpClient(); |
| 25 | + this.shouldDisposeHttpClient = true; |
| 26 | + this.jsonSerializerOptions = CreateJsonSerializerOptions(); |
| 27 | + |
| 28 | + this.httpClient.BaseAddress = new Uri(baseUri); |
| 29 | + this.ConfigureHttpClient(); |
26 | 30 | } |
27 | 31 |
|
28 | | - public void Dispose() => this.client.Dispose(); |
| 32 | + public void Dispose() |
| 33 | + { |
| 34 | + if (this.shouldDisposeHttpClient) |
| 35 | + { |
| 36 | + this.httpClient.Dispose(); |
| 37 | + } |
| 38 | + } |
29 | 39 |
|
30 | | - public Task<CvrfDocument> GetCvrfByIdAsync(string id, CancellationToken cancellationToken = default) |
| 40 | + public async Task<CvrfDocument> GetCvrfByIdAsync(string id, CancellationToken cancellationToken = default) |
31 | 41 | { |
32 | | - var request = new RestRequest($"cvrf/{id}"); |
33 | | - return this.ExecuteAsync<CvrfDocument>(request, cancellationToken); |
| 42 | + if (string.IsNullOrWhiteSpace(id)) |
| 43 | + { |
| 44 | + throw new ArgumentException("ID cannot be null or whitespace.", nameof(id)); |
| 45 | + } |
| 46 | + |
| 47 | + var response = await this.httpClient.GetAsync($"cvrf/{id}", cancellationToken).ConfigureAwait(false); |
| 48 | + response.EnsureSuccessStatusCode(); |
| 49 | + |
| 50 | + var jsonContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); |
| 51 | + return JsonSerializer.Deserialize<CvrfDocument>(jsonContent, this.jsonSerializerOptions) |
| 52 | + ?? throw new InvalidOperationException("Failed to deserialize response."); |
34 | 53 | } |
35 | 54 |
|
36 | 55 | public async Task<IEnumerable<Update>> GetUpdatesAsync(CancellationToken cancellationToken = default) |
37 | 56 | { |
38 | | - var request = new RestRequest("updates"); |
39 | | - return (await this.ExecuteAsync<OdataWrapper<Update>>(request, cancellationToken)).Value; |
| 57 | + var response = await this.httpClient.GetAsync("updates", cancellationToken).ConfigureAwait(false); |
| 58 | + response.EnsureSuccessStatusCode(); |
| 59 | + |
| 60 | + var jsonContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); |
| 61 | + var wrapper = JsonSerializer.Deserialize<OdataWrapper<Update>>(jsonContent, this.jsonSerializerOptions) |
| 62 | + ?? throw new InvalidOperationException("Failed to deserialize response."); |
| 63 | + |
| 64 | + return wrapper.Value; |
40 | 65 | } |
41 | 66 |
|
42 | 67 | public async Task<IEnumerable<Update>> GetUpdateByIdAsync(string id, CancellationToken cancellationToken = default) |
43 | 68 | { |
44 | | - var request = new RestRequest($"updates('{id}')"); |
45 | | - return (await this.ExecuteAsync<OdataWrapper<Update>>(request, cancellationToken)).Value; |
| 69 | + if (string.IsNullOrWhiteSpace(id)) |
| 70 | + { |
| 71 | + throw new ArgumentException("ID cannot be null or whitespace.", nameof(id)); |
| 72 | + } |
| 73 | + |
| 74 | + var response = await this.httpClient.GetAsync($"updates/{id}", cancellationToken).ConfigureAwait(false); |
| 75 | + response.EnsureSuccessStatusCode(); |
| 76 | + |
| 77 | + var jsonContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); |
| 78 | + var wrapper = JsonSerializer.Deserialize<OdataWrapper<Update>>(jsonContent, this.jsonSerializerOptions) |
| 79 | + ?? throw new InvalidOperationException("Failed to deserialize response."); |
| 80 | + |
| 81 | + return wrapper.Value; |
46 | 82 | } |
47 | 83 |
|
48 | | - private async Task<T> ExecuteAsync<T>(RestRequest request, CancellationToken cancellationToken = default) |
49 | | - where T : new() |
| 84 | + private static JsonSerializerOptions CreateJsonSerializerOptions() => new() |
50 | 85 | { |
51 | | - T data; |
52 | | - |
53 | | - var response = await this.client.ExecuteAsync<T>(request, cancellationToken).ConfigureAwait(false); |
54 | | - if (!response.IsSuccessful) |
55 | | - { |
56 | | - throw new Exception(null); |
57 | | - } |
| 86 | + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, |
| 87 | + PropertyNameCaseInsensitive = true, |
| 88 | + }; |
58 | 89 |
|
59 | | - data = response.ThrowIfError().Data!; |
| 90 | + private void ConfigureHttpClient() |
| 91 | + { |
| 92 | + var version = typeof(MicrosoftSecurityUpdatesClient).GetTypeInfo().Assembly.GetName().Version; |
60 | 93 |
|
61 | | - return data; |
| 94 | + this.httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); |
| 95 | + this.httpClient.DefaultRequestHeaders.Add("User-Agent", $"JamieMagee.MicrosoftSecurityUpdates.Client/{version}"); |
62 | 96 | } |
63 | 97 | } |
0 commit comments