• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

loresoft / MediatR.CommandQuery / 11937278774

20 Nov 2024 04:10PM UTC coverage: 59.17% (-0.1%) from 59.302%
11937278774

push

github

pwelter34
make HybridCache optional

389 of 745 branches covered (52.21%)

Branch coverage included in aggregate %.

0 of 6 new or added lines in 2 files covered. (0.0%)

1 existing line in 1 file now uncovered.

1208 of 1954 relevant lines covered (61.82%)

19.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/MediatR.CommandQuery/Dispatcher/RemoteDispatcher.cs
1
using System.Net.Http.Json;
2
using System.Text.Json;
3

4
using MediatR.CommandQuery.Definitions;
5
using MediatR.CommandQuery.Extensions;
6
using MediatR.CommandQuery.Models;
7

8
using Microsoft.Extensions.Caching.Hybrid;
9
using Microsoft.Extensions.Options;
10

11
namespace MediatR.CommandQuery.Dispatcher;
12

13
public class RemoteDispatcher : IDispatcher
14
{
15
    private readonly HttpClient _httpClient;
16
    private readonly JsonSerializerOptions _serializerOptions;
17
    private readonly DispatcherOptions _dispatcherOptions;
18
    private readonly HybridCache? _hybridCache;
19

NEW
20
    public RemoteDispatcher(HttpClient httpClient, JsonSerializerOptions serializerOptions, IOptions<DispatcherOptions> dispatcherOptions, HybridCache? hybridCache = null)
×
21
    {
22
        ArgumentNullException.ThrowIfNull(httpClient);
×
23
        ArgumentNullException.ThrowIfNull(serializerOptions);
×
24
        ArgumentNullException.ThrowIfNull(dispatcherOptions);
×
25

UNCOV
26
        _httpClient = httpClient;
×
27
        _serializerOptions = serializerOptions;
×
28
        _dispatcherOptions = dispatcherOptions.Value;
×
29
        _hybridCache = hybridCache;
×
30
    }
×
31

32
    public async Task<TResponse?> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default)
33
    {
34
        ArgumentNullException.ThrowIfNull(request);
×
35

36
        // cache only if implements interface
37
        var cacheRequest = request as ICacheResult;
×
NEW
38
        if (_hybridCache is null || cacheRequest?.IsCacheable() != true)
×
39
            return await SendCore(request, cancellationToken).ConfigureAwait(false);
×
40

41
        var cacheKey = cacheRequest.GetCacheKey();
×
42
        var cacheTag = cacheRequest.GetCacheTag();
×
43
        var cacheOptions = new HybridCacheEntryOptions
×
44
        {
×
45
            Expiration = cacheRequest.SlidingExpiration(),
×
46
        };
×
47

48
        return await _hybridCache
×
49
            .GetOrCreateAsync(
×
50
                key: cacheKey,
×
51
                factory: async token => await SendCore(request, token).ConfigureAwait(false),
×
52
                options: cacheOptions,
×
53
                tags: string.IsNullOrEmpty(cacheTag) ? null : [cacheTag],
×
54
                cancellationToken: cancellationToken)
×
55
            .ConfigureAwait(false);
×
56
    }
×
57

58
    private async Task<TResponse?> SendCore<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken)
59
    {
60
        var requestUri = _dispatcherOptions.FeaturePrefix
×
61
            .Combine(_dispatcherOptions.DispatcherPrefix)
×
62
            .Combine(_dispatcherOptions.SendRoute);
×
63

64
        var dispatchRequest = new DispatchRequest { Request = request };
×
65

66
        var responseMessage = await _httpClient
×
67
            .PostAsJsonAsync(
×
68
                requestUri: requestUri,
×
69
                value: dispatchRequest,
×
70
                options: _serializerOptions,
×
71
                cancellationToken: cancellationToken)
×
72
            .ConfigureAwait(false);
×
73

74
        await EnsureSuccessStatusCode(responseMessage, cancellationToken).ConfigureAwait(false);
×
75

76
        using var stream = await responseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
×
77

78
        // no content, return null
79
        if (stream.Length == 0)
×
80
            return default;
×
81

82
        var response = await JsonSerializer.DeserializeAsync<TResponse>(stream, _serializerOptions, cancellationToken).ConfigureAwait(false);
×
83

84
        // expire cache
NEW
85
        if (_hybridCache is null || request is not ICacheExpire cacheRequest)
×
86
            return response;
×
87

88
        var cacheTag = cacheRequest.GetCacheTag();
×
89
        if (!string.IsNullOrEmpty(cacheTag))
×
90
            await _hybridCache.RemoveByTagAsync(cacheTag, cancellationToken).ConfigureAwait(false);
×
91

92
        return response;
×
93
    }
×
94

95
    private async Task EnsureSuccessStatusCode(HttpResponseMessage responseMessage, CancellationToken cancellationToken = default)
96
    {
97
        if (responseMessage.IsSuccessStatusCode)
×
98
            return;
×
99

100
        var message = $"Response status code does not indicate success: {responseMessage.StatusCode} ({responseMessage.ReasonPhrase}).";
×
101

102
        var mediaType = responseMessage.Content.Headers.ContentType?.MediaType;
×
103
        if (!string.Equals(mediaType, "application/problem+json", StringComparison.OrdinalIgnoreCase))
×
104
            throw new HttpRequestException(message, inner: null, responseMessage.StatusCode);
×
105

106
        var problemDetails = await responseMessage.Content
×
107
            .ReadFromJsonAsync<ProblemDetails>(
×
108
                options: _serializerOptions,
×
109
                cancellationToken: cancellationToken)
×
110
            .ConfigureAwait(false);
×
111

112
        if (problemDetails == null)
×
113
            throw new HttpRequestException(message, inner: null, responseMessage.StatusCode);
×
114

115
        var status = (System.Net.HttpStatusCode?)problemDetails.Status;
×
116
        status ??= responseMessage.StatusCode;
×
117

118
        var problemMessage = problemDetails.Title
×
119
            ?? responseMessage.ReasonPhrase
×
120
            ?? "Internal Server Error";
×
121

122
        if (!string.IsNullOrEmpty(problemDetails.Detail))
×
123
            problemMessage = $"{problemMessage} {problemDetails.Detail}";
×
124

125
        throw new HttpRequestException(
×
126
            message: problemMessage,
×
127
            inner: null,
×
128
            statusCode: status);
×
129
    }
×
130

131
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc