{"id":21730072,"url":"https://github.com/nventive/mallardmessagehandlers","last_synced_at":"2025-04-13T00:14:32.965Z","repository":{"id":47048845,"uuid":"269419141","full_name":"nventive/MallardMessageHandlers","owner":"nventive","description":"Suite of useful HttpMessageHandlers for dotnet applications","archived":false,"fork":false,"pushed_at":"2025-03-29T05:22:34.000Z","size":90,"stargazers_count":0,"open_issues_count":4,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-13T00:14:26.254Z","etag":null,"topics":["delegatinghandler","dotnet","http","mobile","netstandard20"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nventive.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-04T17:11:58.000Z","updated_at":"2024-12-04T13:27:08.000Z","dependencies_parsed_at":"2024-11-22T21:15:57.425Z","dependency_job_id":null,"html_url":"https://github.com/nventive/MallardMessageHandlers","commit_stats":{"total_commits":22,"total_committers":9,"mean_commits":"2.4444444444444446","dds":0.7727272727272727,"last_synced_commit":"84e21dbce12d13c0bdcbe5a1cdfd5b0f521f52d4"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":"nventive/Template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nventive%2FMallardMessageHandlers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nventive%2FMallardMessageHandlers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nventive%2FMallardMessageHandlers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nventive%2FMallardMessageHandlers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nventive","download_url":"https://codeload.github.com/nventive/MallardMessageHandlers/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248647274,"owners_count":21139086,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["delegatinghandler","dotnet","http","mobile","netstandard20"],"created_at":"2024-11-26T04:11:48.586Z","updated_at":"2025-04-13T00:14:32.945Z","avatar_url":"https://github.com/nventive.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MallardMessageHandlers 🦆\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](LICENSE) ![Version](https://img.shields.io/nuget/v/MallardMessageHandlers?style=flat-square) ![Downloads](https://img.shields.io/nuget/dt/MallardMessageHandlers?style=flat-square)\n\n`MallardMessageHandlers` offers `DelegatingHandlers` which will be handy in many projects which use the HTTP stack.\n\n## Getting Started\n\nAdd to your project a reference to the `MallardMessageHandlers` nuget package.\n\n`DelegatingHandlers` are decorators of `HttpMessageHandler`. They are used to add logic to `HttpRequests` **before** their execution and **after** their execution. You would generally create a pipeline of `DelegatingHandlers` to send and interpret `HttpRequests`.\n\n```\nHttpClient -\u003e Handler1 -\u003e Handler2 -\u003e Handler3 -\u003e (...before)\n                                                               Network\nHttpClient \u003c- Handler1 \u003c- Handler2 \u003c- Handler3 \u003c- (...after)\n```\n\nYou can create a pipeline of `DelegatingHandlers` using the `IHttpClientFactory`. \n\n**The order is extremely important as it will define the sequence of execution**.\n\nThe following example shows how you would create the above pipeline.\n\n```csharp\npublic IHttpClientBuilder ConfigureMyEndpoint(IServiceCollection services)\n{\n  return services\n    .AddHttpClient(\"MyHttpClient\")\n    .AddHttpMessageHandler\u003cHandler1\u003e()\n    .AddHttpMessageHandler\u003cHandler2\u003e()\n    .AddHttpMessageHandler\u003cHandler3\u003e()\n}\n```\n\nThis repository contains multiple implementations of `DelegatingHandlers` for different purposes. \n\nHere is a list of the `DelegatingHandlers` provided.\n\n- [NetworkExceptionHandler](#NetworkExceptionHandler) : Throws an exception if the HttpRequest fails and there is no network.\n- [SimpleCacheHandler](#SimpleCacheHandler) : Implement simple application caching using instructions from custom HTTP headers.\n- [ExceptionHubHandler](#ExceptionHubHandler) : Reports all exceptions that occur on the pipeline.\n- [ExceptionInterpreterHandler](#ExceptionInterpreterHandler) : Interprets error responses and converts them to exceptions.\n- [AuthenticationTokenHandler](#AuthenticationTokenHandler) : Adds the authentication token to the authorization header.\n\nYou can find official documentation on using delegating handlers here: https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/http-message-handlers.\n\n## Features\n\n### NetworkExceptionHandler\n\nThe `NetworkExceptionHandler` is a `DelegatingHandler` that throws a specific type of exception if an exception occurs during the `HttpRequest` execution and the network availability check returns false.\n\nBy default, the `NetworkExceptionHandler` will throw a `NoNetworkException`.\n\nTo create a `NetworkExceptionHandler`, you provide a delegate that will check the network availability.\n\n_Note: The network availability check will only be called **if an exception occurs** during the `HttpRequest` execution._\n\n```csharp\nvar networkAvailabilityChecker = new NetworkAvailabilityChecker(GetIsNetworkAvailable);\nvar networkExceptionHandler = new NetworkExceptionHandler(networkAvailabilityChecker);\n\nprivate Task\u003cbool\u003e GetIsNetworkAvailable(CancellationToken ct)\n{\n  // Add your network connectivity check here.\n}\n```\n\nYou can set your own type of exception returned by the handler by implementing  `INetworkExceptionFactory`.\n\n```csharp\nvar exceptionFactory = new MyNetworkExceptionFactory();\nvar handler = new NetworkExceptionHandler(networkAvailabilityChecker, exceptionFactory);\n\nprivate class MyNetworkExceptionFactory : INetworkExceptionFactory\n{\n  ...\n}\n```\n\nYou would generally register the `NetworkExceptionHandler` on your `IServiceProvider`.\n\n```csharp\nprivate void ConfigureNetworkExceptionHandler(IServiceCollection services)\n{\n  // The NetworkAvailabilityChecker must be shared for all HttpRequests so we add it as singleton.\n  services.AddSingleton\u003cINetworkAvailabilityChecker\u003e(s =\u003e new NetworkAvailabilityChecker(GetIsNetworkAvailable));\n\n  // The NetworkExceptionHandler must be recreated for all HttpRequests so we add it as transient.\n  services.AddTransient\u003cNetworkExceptionHandler\u003e();\n}\n```\n\n### SimpleCacheHandler\n\nThe `SimpleCacheHandler` is a `DelegatingHandler` that executes custom caching instructions.\n\nWhen you use Refit for your endpoints declaration, you can neatly specify caching instructions with attributes.\nJust install the the `MallardMessageHandler.Refit` package to get those Refit-compatible attributes.\n\n- You can specify time-to-live at different levels.\n  - On a per-call level (using attributes)\n  - Globally (using default headers)\n- You can support force-refresh scenarios (i.e. don't read the cache, but update it).\nThis can be useful for things like pull-to-refresh.\n- You can disable the cache on a per-call level.\nThis only makes sense when you define default caching globally.\n\n```csharp\nusing MallardMessageHandlers.SimpleCaching;\n\npublic interface ISampleEndpoint\n{\n  [Get(\"/sample\")]\n  Task\u003cstring\u003e GetSampleDefault(CancellationToken ct, [ForceRefresh] bool forceRefresh = false);\n\n  [Get(\"/sample\")]\n  [TimeToLive(totalMinutes: 5)] // You can customize the TTL on a per-call basis.\n  Task\u003cstring\u003e GetSampleCustomTTL(CancellationToken ct, [ForceRefresh] bool forceRefresh = false);\n\n  [Get(\"/sample\")]\n  [NoCache] // When you have a default TTL, you can bypass it on a per-call basis.\n  Task\u003cstring\u003e GetSampleNoCache(CancellationToken ct);\n}\n```\n\nHere's how you can configure a default time-to-live for all calls.\n```csharp\nserviceCollection\n  .AddRefitClient\u003cISampleEndpoint\u003e()\n  .ConfigureHttpClient((client) =\u003e\n  {\n    // You can configure a default time-to-live for all calls.\n    // \"600\" represents 10 minutes (600 seconds).\n    client.DefaultRequestHeaders.Add(SimpleCacheHandler.CacheTimeToLiveHeaderName, \"600\");\n  });\n```\n\nThe `SimpleCacheHandler` has a few dependencies.\n- `ISimpleCacheService` which implements the actual caching of data.\n  - The interface is pretty simple, so you can easily create implementations.\n  - You can also use our `MemorySimpleCacheService` implementation.\n- `ISimpleCacheKeyProvider` which generates the cache keys from the `HttpMessageRequest` objects.\n  - You can use the `SimpleCacheKeyProvider` to create your keys using a custom `Func\u003cHttpRequestMessage,string\u003e`.\n  - You can also use one of our built-in implementations:\n    - `SimpleCacheKeyProvider.FromUriOnly`\n    - `SimpleCacheKeyProvider.FromUriAndAuthorizationHash`\n\n```csharp\nprivate void ConfigureCacheHandler(IServiceCollection services)\n{\n  // The ISimpleCacheService and ISimpleCacheKeyProvider are shared for all HttpRequests so we add them as singleton.\n  services\n    .AddSingleton\u003cISimpleCacheService, MemorySimpleCacheService\u003e();\n    .AddSingleton\u003cISimpleCacheKeyProvider\u003e(CacheKeyProvider.FromUriOnly);\n\n  // The SimpleCacheHandler must be recreated for all HttpRequests so we add it as transient.\n  services.AddTransient\u003cSimpleCacheHandler\u003e();\n}\n```\n\n### ExceptionHubHandler\n\nThe `ExceptionHubHandler` is a `DelegatingHandler` that will report all exceptions thrown during the execution of the `HttpRequest` to an `IExceptionHub`.\n\nTo create a `ExceptionHubHandler`, you provide a `IExceptionHub` that you will use to receive the exceptions.\n\n```csharp\nvar exceptionHub = new ExceptionHub();\nvar handler = new exceptionHubHandler(exceptionHub);\n\nexceptionHub.OnExceptionReported += OnExceptionReported;\n\nvoid OnExceptionReported(object sender, Exception e)\n{\n  // This will be called everytime an exception occurs during the execution of the HttpRequests.\n}\n```\n\nYou would generally register the `ExceptionHubHandler` on your `IServiceProvider`.\n\n```csharp\nprivate void ConfigureExceptionHubHandler(IServiceCollection services)\n{\n  // The ExceptionHub must be shared for all HttpRequests so we add it as singleton.\n  services.AddSingleton\u003cIExceptionHub, ExceptionHub\u003e();\n\n  // The ExceptionHubHandler must be recreated for all HttpRequests so we add it as transient.\n  services.AddTransient\u003cExceptionHubHandler\u003e();\n}\n```\n\n### ExceptionInterpreterHandler\n\nThe `ExceptionInterpreterHandler` is a `DelegatingHandler` that will interpret the response of an `HttpRequest` and throw a specific type of exception if the response is considered in error.\n\nTo create a `ExceptionInterpreterHandler`, you provide a response interpreter and a deserializer.\n\n```csharp\nvar interpreter = new ErrorResponseInterpreter\u003cTestResponse\u003e(\n  // Whether or not this interpreter should throw an exception.\n  (request, response, deserializedResponse) =\u003e deserializedResponse.Error != null, \n\n  // The exception that should be thrown.\n  (request, response, deserializedResponse) =\u003e new TestException(deserializedResponse.Error) \n);\n\n// Use your response deserializer.\nvar deserializer = new ResponseContentDeserializer();\n\nvar handler = new ExceptionInterpreterHandler\u003cTestResponse\u003e(interpreter, deserializer);\n```\n\nYou would generally register the `ExceptionInterpreterHandler` on your `IServiceProvider`.\n\n```csharp\nprivate void ConfigureExceptionInterpreterHandler(IServiceCollection services)\n{\n  // The ResponseContentDeserializer must be shared for all HttpRequests so we add it as singleton.\n  services.AddSingleton\u003cIResponseContentDeserializer, ResponseContentDeserializer\u003e();\n\n  // The ErrorResponseInterpreter must be shared for all HttpRequests so we add it as singleton.\n  services.AddSingleton\u003cIErrorResponseInterpreter\u003cTestResponse\u003e\u003e(s =\u003e ...);\n\n  // The ExceptionInterpreterHandler must be recreated for all HttpRequests so we add it as transient.\n  services.AddTransient\u003cExceptionInterpreterHandler\u003cTestResponse\u003e\u003e();\n}\n```\n\n### AuthenticationTokenHandler\n\nThe `AuthenticationTokenHandler` is a `DelegatingHandler` that will add the value of the authentication token to the `Authorization` header if the header is present. It will also refresh the token if possible and notify if the authenticated session should be considered as expired.\n\nFor example, if you have a Refit endpoint with the following header, the authentication token will automatically be added to the `Authorization` header of the `HttpRequest`.\n\n```csharp\n// This adds the Authorization header to all API calls of this endpoint.\n[Headers(\"Authorization: Bearer\")]\npublic interface IMyEndpoint\n{\n  [Get(\"/categories\")]\n  Task\u003cCategory[]\u003e GetCategories(CancellationToken ct);\n}\n```\n\nTo create a `AuthenticationTokenHandler`, you provide an `IAuthenticationTokenProvider`.\n\nThere is an implementation of `IAuthenticationTokenProvider` that receives the different delegates as parameters but you can create your own implementation.\n\n```csharp\nvar authenticationService = new AuthenticationService();\n\nvar authenticationTokenProvider = new ConcurrentAuthenticationTokenProvider\u003cMyAuthenticationToken\u003e(\n  loggerFactory: null,\n  getToken: (ct, request) =\u003e authenticationService.GetToken(ct, request),\n  notifySessionExpired: (ct, request, token) =\u003e authenticationService.NotifySessionExpired(ct, request, token),\n  refreshToken: (ct, request, token) =\u003e authenticationService.RefreshToken(ct, request, token)  // Optional\n);\n\nvar authenticationHandler = new AuthenticationTokenHandler\u003cMyAuthenticationToken\u003e(authenticationTokenProvider);\n\npublic class MyAuthenticationToken : IAuthenticationToken\n{\n  public string AccessToken { get; set; } // Access token used for the header.\n\n  public string RefreshToken { get; set; } // Refresh token used to refresh the access token.\n\n  public bool CanBeRefreshed =\u003e RefreshToken != null; // Whether or not the access token can be refreshed.\n}\n\npublic class MyAuthenticationService\n{\n  public Task\u003cMyAuthenticationToken\u003e GetToken(CancellationToken ct, HttpRequestMessage request)\n  {\n    // Return the authentication token from your app settings.\n  }\n\n  public Task\u003cMyAuthenticationToken\u003e RefreshToken(CancellationToken ct, HttpRequestMessage request, MyAuthenticationToken unauthorizedToken)\n  {\n    // Refresh the authentication token with your API.\n  }\n\n  public Task NotifySessionExpired(CancellationToken ct, HttpRequestMessage request, MyAuthenticationToken unauthorizedToken)\n  {\n    // This will occur if the token is expired and it couldn't be refreshed.\n    // This should generally result in a user logout.\n  }\n}\n```\n\nYou would generally register the `AuthenticationTokenHandler` on your `IServiceProvider`.\n\n```csharp\nprivate void ConfigureAuthenticationTokenHandler(IServiceCollection services)\n{\n  // The AuthenticationTokenProvider must be shared for all HttpRequests so we add it as singleton.\n  services.AddSingleton\u003cIAuthenticationTokenProvider\u003cMyAuthenticationToken\u003e, MyAuthenticationTokenProvider\u003e();\n\n  // The AuthenticationTokenHandler must be recreated for all HttpRequests so we add it as transient.\n  services.AddTransient\u003cAuthenticationTokenHandler\u003cMyAuthenticationToken\u003e\u003e();\n}\n```\n\n## Breaking Changes\n\nPlease consult the [BREAKING_CHANGES.md](BREAKING_CHANGES.md) for more information about breaking changes and version history.\n\n## License\n\nThis project is licensed under the Apache 2.0 license - see the\n[LICENSE](LICENSE) file for details.\n\n## Contributing\n\nPlease read [CONTRIBUTING.md](CONTRIBUTING.md) for details on the process for\ncontributing to this project.\n\nBe mindful of our [Code of Conduct](CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnventive%2Fmallardmessagehandlers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnventive%2Fmallardmessagehandlers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnventive%2Fmallardmessagehandlers/lists"}