diff --git a/src/RestApiClientSharp/Extensions/CookieCollectionExtension.cs b/src/RestApiClientSharp/Extensions/CookieCollectionExtension.cs index d890e9c..4ef4638 100644 --- a/src/RestApiClientSharp/Extensions/CookieCollectionExtension.cs +++ b/src/RestApiClientSharp/Extensions/CookieCollectionExtension.cs @@ -2,6 +2,7 @@ namespace AndreasReitberger.API.REST.Extensions { + [Obsolete("Use from the core library once available")] public static class CookieCollectionExtension { extension(CookieCollection collection) diff --git a/src/RestApiClientSharp/Extensions/TypeExtensions.cs b/src/RestApiClientSharp/Extensions/TypeExtensions.cs new file mode 100644 index 0000000..882a071 --- /dev/null +++ b/src/RestApiClientSharp/Extensions/TypeExtensions.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace AndreasReitberger.API.REST.Extensions +{ + [Obsolete("Use from the core library once available")] + public static class TypeExtensions + { + public static bool IsAnonymousType(this Type type) + { + if (type == null) + return false; + + return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false) + && type.IsGenericType + && type.Name.Contains("AnonymousType") + && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) + && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic; + } + + } +} diff --git a/src/RestApiClientSharp/Interfaces/IRestApiClient.cs b/src/RestApiClientSharp/Interfaces/IRestApiClient.cs index 7eb9e51..2ab09d9 100644 --- a/src/RestApiClientSharp/Interfaces/IRestApiClient.cs +++ b/src/RestApiClientSharp/Interfaces/IRestApiClient.cs @@ -76,6 +76,10 @@ public interface IRestApiClient : INotifyPropertyChanged, IDisposable, ICloneabl public Func? OnRefresh { get; set; } #endregion + #region JsonSerializer + public JsonSerializerContext JsonSerializerContext { get; set; } + #endregion + #endregion #region EventHandlers @@ -116,8 +120,14 @@ public interface IRestApiClient : INotifyPropertyChanged, IDisposable, ICloneabl #endregion #region Rest + [Obsolete("This method is deprecated. Please use SendRestApiRequestAsync instead.")] + public Task SendRestApiRequestLegacyAsync(string? requestTargetUri, Method method, string? command, + Dictionary authHeaders, object? jsonObject = null, RestBodyTarget target = RestBodyTarget.Json, CancellationTokenSource? cts = default, Dictionary? urlSegments = null, JsonSerializerContext? serializerContext = null + //string? contentType = null, string? accept = null + ); + public Task SendRestApiRequestAsync(string? requestTargetUri, Method method, string? command, - Dictionary authHeaders, object? jsonObject = null, RestBodyTarget target = RestBodyTarget.Json, CancellationTokenSource? cts = default, List>? urlSegments = null + Dictionary authHeaders, object? jsonObject = null, RestBodyTarget target = RestBodyTarget.Json, CancellationTokenSource? cts = default, List>? urlSegments = null, JsonSerializerContext? serializerContext = null //string? contentType = null, string? accept = null ); diff --git a/src/RestApiClientSharp/RestApiClient.JsonSerializer.cs b/src/RestApiClientSharp/RestApiClient.JsonSerializer.cs index 46db30f..ea18938 100644 --- a/src/RestApiClientSharp/RestApiClient.JsonSerializer.cs +++ b/src/RestApiClientSharp/RestApiClient.JsonSerializer.cs @@ -10,6 +10,11 @@ public partial class RestApiClient : ObservableObject, IRestApiClient [ObservableProperty] [JsonIgnore, XmlIgnore] + public partial JsonSerializerContext JsonSerializerContext { get; set; } = RestSourceGenerationContext.Default; + + [ObservableProperty] + [JsonIgnore, XmlIgnore] + [Obsolete("This property is deprecated. Use the `JsonSerializerContext` instead.")] public partial JsonSerializerOptions JsonSerializerSettings { get; set; } = RestSourceGenerationContext.Default.Options; #region SerializerSettings diff --git a/src/RestApiClientSharp/RestApiClient.Rest.cs b/src/RestApiClientSharp/RestApiClient.Rest.cs index 014ad4a..898a11c 100644 --- a/src/RestApiClientSharp/RestApiClient.Rest.cs +++ b/src/RestApiClientSharp/RestApiClient.Rest.cs @@ -12,6 +12,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using AndreasReitberger.API.REST.Extensions; namespace AndreasReitberger.API.REST { @@ -111,6 +112,7 @@ protected virtual IRestApiRequestRespone ValidateResponse(RestResponse? response #endregion #region Rest Api + [Obsolete("This method is deprecated. Please use SendRestApiRequestAsync instead.")] public virtual Task SendRestApiRequestLegacyAsync( string? requestTargetUri, Method method, @@ -119,10 +121,11 @@ protected virtual IRestApiRequestRespone ValidateResponse(RestResponse? response object? body = null, RestBodyTarget target = RestBodyTarget.Json, CancellationTokenSource? cts = default, - Dictionary? urlSegments = null + Dictionary? urlSegments = null, + JsonSerializerContext? serializerContext = null ) => SendRestApiRequestAsync(requestTargetUri, method, command, authHeaders, body, target, cts, - urlSegments is not null ? [.. urlSegments.Select(kvp => new Tuple(kvp.Key, kvp.Value))] : null); + urlSegments is not null ? [.. urlSegments.Select(kvp => new Tuple(kvp.Key, kvp.Value))] : null, serializerContext); public virtual async Task SendRestApiRequestAsync( string? requestTargetUri, @@ -132,13 +135,16 @@ protected virtual IRestApiRequestRespone ValidateResponse(RestResponse? response object? body = null, RestBodyTarget target = RestBodyTarget.Json, CancellationTokenSource? cts = default, - List>? urlSegments = null + List>? urlSegments = null, + JsonSerializerContext? serializerContext = null ) { RestApiRequestRespone apiRsponeResult = new() { IsOnline = IsOnline }; try { cts ??= new(TimeSpan.FromSeconds(DefaultTimeout)); + serializerContext ??= JsonSerializerContext ??= RestSourceGenerationContext.Default; + requestTargetUri ??= string.Empty; command ??= string.Empty; if (RestClient == null) @@ -188,8 +194,14 @@ protected virtual IRestApiRequestRespone ValidateResponse(RestResponse? response } else { + bodyContent = string.Empty; //bodyContent = JsonConvert.SerializeObject(body, DefaultNewtonsoftJsonSerializerSettings); - bodyContent = JsonSerializer.Serialize(body!, body.GetType(), RestSourceGenerationContext.Default); + if (body.GetType().IsAnonymousType()) +#pragma warning disable IL2026 // Disable warning for anonymous types, as they cannot be preserved with source generation and are only used for internal purposes. + bodyContent = JsonSerializer.Serialize(body!, body.GetType()); +#pragma warning restore IL2026 // Restore warnings + else + bodyContent = JsonSerializer.Serialize(body!, body.GetType(), context: serializerContext); request.AddJsonBody(bodyContent, RestContentType.Json); } break; @@ -321,7 +333,6 @@ protected virtual IRestApiRequestRespone ValidateResponse(RestResponse? response { RestApiRequestRespone apiRsponeResult = new(); if (!IsOnline) return apiRsponeResult; - try { // If there is no file specified @@ -344,10 +355,10 @@ protected virtual IRestApiRequestRespone ValidateResponse(RestResponse? response { switch (authHeader.Value.Target) { - case Enums.AuthenticationHeaderTarget.UrlSegment: + case AuthenticationHeaderTarget.UrlSegment: request.AddParameter(authHeader.Key, authHeader.Value.Token, ParameterType.QueryString); break; - case Enums.AuthenticationHeaderTarget.Header: + case AuthenticationHeaderTarget.Header: default: // Examples: // "X-Api-Key", $"{apiKey}" diff --git a/src/RestApiClientSharp/RestApiConnectionBuilder.cs b/src/RestApiClientSharp/RestApiConnectionBuilder.cs index 9d5268a..67a160c 100644 --- a/src/RestApiClientSharp/RestApiConnectionBuilder.cs +++ b/src/RestApiClientSharp/RestApiConnectionBuilder.cs @@ -14,26 +14,57 @@ public class RestApiConnectionBuilder #region Methods + /// + /// Builds the RestApiClient instance with the specified configuration. After calling this method, the builder should not be used anymore, as it does not create a new instance of the client but returns the same one. + /// + /// RestApiClient public RestApiClient Build() => _client; - + + /// + /// Sets the web address for the connection. This is the base address to which the method paths will be appended when making requests. + /// For instance, if the web address is `https://api.example.com` and a method has the path `get-data`, the full request URI will be `https://api.example.com/get-data` + /// (or `https://api.example.com/version/get-data` if an API version is set). + /// If not set, the client will throw an exception when trying to make a request, as it won't have a valid URI to send the request to. + /// + /// The web address for the connection + /// The updated connection builder public RestApiConnectionBuilder WithWebAddress(string webAddress) { _client.ApiTargetPath = webAddress; return this; } + /// + /// Sets the API version for the connection. This is used to build the full request URI, which is usually in the format `webAddress/version/method`. If not set, the version will be omitted from the request URI. + /// + /// The API version + /// RestApiConnectionBuilder public RestApiConnectionBuilder WithVersion(string version) { _client.ApiVersion = version; return this; } + /// + /// Sets the API key for the connection. The token name is the name of the header in which the token will be sent (for instance `apikey`). + /// The `AuthenticationHeader` contains the token and other information about how to send it. + /// + /// The name of the API key + /// The authentication header for the API key + /// RestApiConnectionBuilder public RestApiConnectionBuilder WithApiKey(string tokenName, IAuthenticationHeader authHeader) { _client.AuthHeaders = new Dictionary() { { tokenName, authHeader } }; return this; } + /// + /// Sets the web address and the api key for the connection. This is a shortcut for setting both values at once. + /// + /// The rest API web address + /// The name for the API key + /// The authentication header for the API key + /// RestApiConnectionBuilder public RestApiConnectionBuilder WithWebAddressAndApiKey(string webAddress, string tokenName, IAuthenticationHeader authHeader) { _client.ApiTargetPath = webAddress; @@ -95,21 +126,6 @@ public RestApiConnectionBuilder WithWebSocket(string webSocketAddress, string? t _client.WebSocketTargetUri = webSocketAddress; return this; } - /* - public RestApiConnectionBuilder WithWebSocket(string webSocketAddress, Dictionary? authentication = null, string pingCommand = "", int pingInterval = 0, bool enablePing = true) - { - if (authentication is not null) - foreach (KeyValuePair item in authentication) - { - _client.AuthHeaders.Add(item.Key, item.Value); - } - _client.EnablePing = enablePing; - _client.PingCommand = pingCommand; - _client.PingInterval = pingInterval; - _client.WebSocketTargetUri = webSocketAddress; - return this; - } - */ /// /// Sets the WebSocket address for the connection @@ -121,16 +137,27 @@ public RestApiConnectionBuilder WithWebSocket(string webSocketAddress, Dictionar /// RestApiConnectionBuilder public RestApiConnectionBuilder WithWebSocket(string webSocketAddress, string pingCommand, string? tokenName = null, IAuthenticationHeader? authentication = null, int pingInterval = 0, bool enablePing = true) => WithWebSocket(webSocketAddress, tokenName, authentication, pingCommand, pingInterval, enablePing); - /* - public RestApiConnectionBuilder WithWebSocket(string webSocketAddress, object pingCommand, Dictionary? authentication = null, int pingInterval = 0, bool enablePing = true) - => WithWebSocket(webSocketAddress, authentication, JsonConvert.SerializeObject(pingCommand), pingInterval, enablePing); - */ + /// + /// Sets default headers for the connection. These headers will be sent with every request. + /// + /// The headers to set + /// RestApiConnectionBuilder public RestApiConnectionBuilder WithDefaultHeaders(params RestHeader[] headers) { _client.DefaultHeaders = [.. headers]; return this; } + /// + /// Sets the JsonSerializerContext for the rest api methods. If not set, the default options will be used. + /// + /// The json serializer context + /// RestApiConnectionBuilder + public RestApiConnectionBuilder WithJsonSerializerContext(JsonSerializerContext serializerContext) + { + _client.JsonSerializerContext = serializerContext; + return this; + } #endregion } }