From 23f65802f5599aa683e540f878d5500bbba546ed Mon Sep 17 00:00:00 2001 From: Alberto Spelta Date: Wed, 10 Jun 2026 17:23:02 +0200 Subject: [PATCH 1/2] Switch to new api.daxformatter.com endpoint, remove redirect handling Update service URIs to use the new API endpoint directly. The old www.daxformatter.com URLs are still online but permanently redirect (301) to the new host. We update the service URIs to point directly to the new endpoint and remove the redirect-resolution machinery that was needed to follow those redirects at runtime. --- .../Client/Http/DaxFormatterHttpClient.cs | 109 +----------------- .../Models/DaxFormatterMultipleRequest.cs | 2 +- .../Models/DaxFormatterSingleRequest.cs | 2 +- 3 files changed, 3 insertions(+), 110 deletions(-) diff --git a/src/Dax.Formatter/Client/Http/DaxFormatterHttpClient.cs b/src/Dax.Formatter/Client/Http/DaxFormatterHttpClient.cs index c74760b..b6a8ef3 100644 --- a/src/Dax.Formatter/Client/Http/DaxFormatterHttpClient.cs +++ b/src/Dax.Formatter/Client/Http/DaxFormatterHttpClient.cs @@ -18,17 +18,12 @@ internal sealed class DaxFormatterHttpClient : IDaxFormatterHttpClient, IDisposa private const string MediaTypeNamesApplicationJson = "application/json"; private const int DaxFormatterTimeoutSeconds = 60; - private readonly HashSet _locationChangedStatusCodes; private readonly JsonSerializerOptions _serializerOptions; - private readonly SemaphoreSlim _initializeServiceUriSemaphore; private readonly SemaphoreSlim _formatSemaphore; private readonly HttpClient _httpClient; private readonly string? _application; private readonly string? _version; - private Uri? _daxTextFormatSingleServiceUri; - private Uri? _daxTextFormatMultiServiceUri; - public DaxFormatterHttpClient(string? application, string? version) { _application = application; @@ -42,23 +37,10 @@ public DaxFormatterHttpClient(string? application, string? version) _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNamesApplicationJson)); _httpClient.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue(nameof(DecompressionMethods.GZip))); - _initializeServiceUriSemaphore = new SemaphoreSlim(1); _formatSemaphore = new SemaphoreSlim(1); _serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); _serializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; - - _locationChangedStatusCodes = new HashSet - { - HttpStatusCode.Moved, - HttpStatusCode.MovedPermanently, - HttpStatusCode.Found, - HttpStatusCode.Redirect, - HttpStatusCode.RedirectMethod, - HttpStatusCode.SeeOther, - HttpStatusCode.RedirectKeepVerb, - HttpStatusCode.TemporaryRedirect - }; } public async Task FormatAsync(DaxFormatterSingleRequest request, CancellationToken cancellationToken) @@ -112,7 +94,7 @@ private async Task FormatImplAsync(T request, CancellationToken cance cancellationToken.ThrowIfCancellationRequested(); var json = JsonSerializer.Serialize(request, _serializerOptions); - var uri = await GetServiceUriAsync(request, cancellationToken).ConfigureAwait(false); + var uri = request.DaxTextFormatUri; using var content = new StringContent(json, Encoding.UTF8, MediaTypeNamesApplicationJson); using var response = await _httpClient.PostAsync(uri, content, cancellationToken).ConfigureAwait(false); @@ -128,97 +110,8 @@ private async Task FormatImplAsync(T request, CancellationToken cance return message; } - private async Task GetServiceUriAsync(DaxFormatterRequest request, CancellationToken cancellationToken) - { - if (request is DaxFormatterMultipleRequest) - { - if (_daxTextFormatMultiServiceUri == null) - _daxTextFormatMultiServiceUri = await InitializeMultiServiceUriAsync().ConfigureAwait(false); - - return _daxTextFormatMultiServiceUri; - } - else if (request is DaxFormatterSingleRequest) - { - if (_daxTextFormatSingleServiceUri == null) - _daxTextFormatSingleServiceUri = await InitializeSingleServiceUriAsync().ConfigureAwait(false); - - return _daxTextFormatSingleServiceUri; - } - else - { - throw new NotSupportedException($"Uri not supported for { request.GetType().Name } request"); - } - - async Task InitializeSingleServiceUriAsync() - { - if (_daxTextFormatSingleServiceUri == null) - { - await _initializeServiceUriSemaphore.WaitAsync(CancellationToken.None).ConfigureAwait(false); - try - { - if (_daxTextFormatSingleServiceUri == null) - { - using var response = await _httpClient.GetAsync(request.DaxTextFormatUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var uri = request.DaxTextFormatUri; - - if (_locationChangedStatusCodes.Contains(response.StatusCode) && response.Headers.Location != null) - { - uri = response.Headers.Location; - } - else - { - response.EnsureSuccessStatusCode(); - } - - _daxTextFormatSingleServiceUri = uri; - } - } - finally - { - _initializeServiceUriSemaphore.Release(); - } - } - - return _daxTextFormatSingleServiceUri; - } - - async Task InitializeMultiServiceUriAsync() - { - if (_daxTextFormatMultiServiceUri == null) - { - await _initializeServiceUriSemaphore.WaitAsync(CancellationToken.None).ConfigureAwait(false); - try - { - if (_daxTextFormatMultiServiceUri == null) - { - using var response = await _httpClient.GetAsync(request.DaxTextFormatUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var uri = request.DaxTextFormatUri; - - if (_locationChangedStatusCodes.Contains(response.StatusCode) && response.Headers.Location != null) - { - uri = response.Headers.Location; - } - else - { - response.EnsureSuccessStatusCode(); - } - - _daxTextFormatMultiServiceUri = uri; - } - } - finally - { - _initializeServiceUriSemaphore.Release(); - } - } - - return _daxTextFormatMultiServiceUri; - } - } - public void Dispose() { - _initializeServiceUriSemaphore.Dispose(); _formatSemaphore.Dispose(); _httpClient.Dispose(); } diff --git a/src/Dax.Formatter/Models/DaxFormatterMultipleRequest.cs b/src/Dax.Formatter/Models/DaxFormatterMultipleRequest.cs index 6914df8..3caca39 100644 --- a/src/Dax.Formatter/Models/DaxFormatterMultipleRequest.cs +++ b/src/Dax.Formatter/Models/DaxFormatterMultipleRequest.cs @@ -13,7 +13,7 @@ internal static DaxFormatterMultipleRequest CreateFrom(IEnumerable expre return request; } - internal override Uri DaxTextFormatUri { get; } = new Uri("https://www.daxformatter.com/api/daxformatter/daxtextformatmulti"); + internal override Uri DaxTextFormatUri { get; } = new Uri("https://api.daxformatter.com/api/daxtextformatmulti"); public List Dax { get; set; } = new List(); } diff --git a/src/Dax.Formatter/Models/DaxFormatterSingleRequest.cs b/src/Dax.Formatter/Models/DaxFormatterSingleRequest.cs index 41865fb..48dd939 100644 --- a/src/Dax.Formatter/Models/DaxFormatterSingleRequest.cs +++ b/src/Dax.Formatter/Models/DaxFormatterSingleRequest.cs @@ -13,7 +13,7 @@ internal static DaxFormatterSingleRequest CreateFrom(string expression) return request; } - internal override Uri DaxTextFormatUri { get; } = new Uri("https://www.daxformatter.com/api/daxformatter/daxtextformat"); + internal override Uri DaxTextFormatUri { get; } = new Uri("https://api.daxformatter.com/api/daxtextformat"); public string? Dax { get; set; } } From ec2bd69f9f99c73a2811f165bfe33564d9f972e2 Mon Sep 17 00:00:00 2001 From: Alberto Spelta Date: Wed, 10 Jun 2026 18:06:20 +0200 Subject: [PATCH 2/2] Remove per-request semaphore and enable auto-redirect The _formatSemaphore that serialized all format calls one at a time was originally introduced to guard the redirect-resolution logic. Now that the library calls the stable api.daxformatter.com endpoint directly, there is nothing to serialize: HttpClient is thread-safe by design and concurrent requests are safe. AllowAutoRedirect is restored to the HttpClientHandler default (true) so any future redirect is followed transparently without custom handling. ReadAsStringAsync replaces the synchronous StreamReader.ReadToEnd so the response body is read fully asynchronously. --- .../Client/Http/DaxFormatterHttpClient.cs | 61 ++++--------------- .../DaxFormatterHttpClientMessageHandler.cs | 1 - 2 files changed, 13 insertions(+), 49 deletions(-) diff --git a/src/Dax.Formatter/Client/Http/DaxFormatterHttpClient.cs b/src/Dax.Formatter/Client/Http/DaxFormatterHttpClient.cs index b6a8ef3..0cf5566 100644 --- a/src/Dax.Formatter/Client/Http/DaxFormatterHttpClient.cs +++ b/src/Dax.Formatter/Client/Http/DaxFormatterHttpClient.cs @@ -1,9 +1,8 @@ -namespace Dax.Formatter.Client.Http +namespace Dax.Formatter.Client.Http { using Dax.Formatter.Models; using System; using System.Collections.Generic; - using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -19,7 +18,6 @@ internal sealed class DaxFormatterHttpClient : IDaxFormatterHttpClient, IDisposa private const int DaxFormatterTimeoutSeconds = 60; private readonly JsonSerializerOptions _serializerOptions; - private readonly SemaphoreSlim _formatSemaphore; private readonly HttpClient _httpClient; private readonly string? _application; private readonly string? _version; @@ -36,8 +34,6 @@ public DaxFormatterHttpClient(string? application, string? version) _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNamesApplicationJson)); _httpClient.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue(nameof(DecompressionMethods.GZip))); - - _formatSemaphore = new SemaphoreSlim(1); _serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); _serializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; @@ -45,48 +41,21 @@ public DaxFormatterHttpClient(string? application, string? version) public async Task FormatAsync(DaxFormatterSingleRequest request, CancellationToken cancellationToken) { -#if NETSTANDARD - await _formatSemaphore.WaitAsync().ConfigureAwait(false); -#elif NET6_0_OR_GREATER - await _formatSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); -#endif - try - { - request.CallerApp = _application; - request.CallerVersion = _version; + request.CallerApp = _application; + request.CallerVersion = _version; - var message = await FormatImplAsync(request, cancellationToken).ConfigureAwait(false); - var result = JsonSerializer.Deserialize(message, _serializerOptions); - - return result; - } - finally - { - _formatSemaphore.Release(); - } + var message = await FormatImplAsync(request, cancellationToken).ConfigureAwait(false); + return JsonSerializer.Deserialize(message, _serializerOptions); } public async Task> FormatAsync(DaxFormatterMultipleRequest request, CancellationToken cancellationToken) { -#if NETSTANDARD - await _formatSemaphore.WaitAsync().ConfigureAwait(false); -#elif NET6_0_OR_GREATER - await _formatSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); -#endif - try - { - request.CallerApp = _application; - request.CallerVersion = _version; - - var message = await FormatImplAsync(request, cancellationToken).ConfigureAwait(false); - var result = JsonSerializer.Deserialize>(message, _serializerOptions); + request.CallerApp = _application; + request.CallerVersion = _version; - return result ?? Array.Empty(); - } - finally - { - _formatSemaphore.Release(); - } + var message = await FormatImplAsync(request, cancellationToken).ConfigureAwait(false); + var result = JsonSerializer.Deserialize>(message, _serializerOptions); + return result ?? Array.Empty(); } private async Task FormatImplAsync(T request, CancellationToken cancellationToken) where T : DaxFormatterRequest @@ -99,20 +68,16 @@ private async Task FormatImplAsync(T request, CancellationToken cance using var content = new StringContent(json, Encoding.UTF8, MediaTypeNamesApplicationJson); using var response = await _httpClient.PostAsync(uri, content, cancellationToken).ConfigureAwait(false); response.EnsureSuccessStatusCode(); + #if NETSTANDARD - using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + return await response.Content.ReadAsStringAsync().ConfigureAwait(false); #elif NET6_0_OR_GREATER - using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); #endif - using var reader = new StreamReader(stream); - var message = reader.ReadToEnd(); - - return message; } public void Dispose() { - _formatSemaphore.Dispose(); _httpClient.Dispose(); } } diff --git a/src/Dax.Formatter/Client/Http/DaxFormatterHttpClientMessageHandler.cs b/src/Dax.Formatter/Client/Http/DaxFormatterHttpClientMessageHandler.cs index f1d2bad..900c168 100644 --- a/src/Dax.Formatter/Client/Http/DaxFormatterHttpClientMessageHandler.cs +++ b/src/Dax.Formatter/Client/Http/DaxFormatterHttpClientMessageHandler.cs @@ -7,7 +7,6 @@ internal class DaxFormatterHttpClientMessageHandler : HttpClientHandler { public DaxFormatterHttpClientMessageHandler() { - AllowAutoRedirect = false; AutomaticDecompression = DecompressionMethods.GZip; } }