The goal of Apizr v4.0 is still to get all ready to use for web api requesting, with the more resiliency we can, but without the boilerplate.
It’s based on Refit, so what we can do with Refit could be done with Apizr too.
But Apizr has more to offer, with at least:
- Working offline with cache management
- Handling errors with retry pattern and global catching
- Handling request priority
- Checking connectivity
- Tracing http traffic
- Handling authentication
- Mapping model with DTO
- Using Mediator pattern
- Using Optional pattern
Anyway, this post is about changes only, but we published a series about Apizr already which you can find here.
If you want to know how to get started or how to configure it, please read the brand new documentation:
Anytime, feel free to browse code and samples too:
LIBRAIRIES
Apizr features are still provided by several NuGet packages, depending on what you need, what you want to do, and how you want to achieve this.
Note that Apizr.Integrations.Shiny has been discontinued as Shiny no longer provide built-in caching and logging feature anymore. Instead, Apizr now relies on MS Caching extensions, Akavache or MonkeyCache for caching feature and MS Logging extensions for logging feature. You’ll have to provide a connectivity handler if you want Apizr to check it.
LOGGER
Apizr now fully relies on Microsoft.Extensions.Logging to log it all. It means that you can now register any compatible logging provider.
Static:
You can set logger configuration thanks to this option:
options => options.WithLoggerFactory(LoggerFactory.Create(loggingBuilder => { loggingBuilder.AddConsole(); loggingBuilder.AddDebug(); }))
Where LoggerFactory.Create
method is provided by Microsoft.Extensions.Logging.
Extended:
There’s nothing specific to do with Apizr about logger when using the extended approach. Just don’t forget to configure it like you usualy do:
loggingBuilder.AddConsole()
wherever in your app you get access to ILoggingBuilder
logging
You can still configure logging either by attribute decoration or by fluent configuration, but the attribute name changed back to just Log
Attribute:
namespace Apizr.Sample { [WebApi("https://reqres.in/"), Log(HttpTracerMode.Everything, HttpMessageParts.All, LogLevel.Information)] public interface IReqResService { [Get("/api/users")] Task<UserList> GetUsersAsync(); } }
Fluent:
options => options.WithLogging(HttpTracerMode.Everything, HttpMessageParts.All, LogLevel.Information)
In both cases (using attribute or fluent):
- The first parameter called
httpTracerMode
introduces a tracing mode like tracing everything everytime or only when an exception occurs - The second parameter called
trafficVerbosity
still adjust http traffic tracing verbosity - The last parameter called
logLevels
is now a parameter array. It lets you provide from 0 to 3 different MS log levels, as Apizr now needs to get corresponding log level to each internal severity:- Low: logs any internal and normal execution step
- Medium: logs all missconfigured things, like asking for cache without providing any cache provider
- High: logs errors and exceptions
Speaking of logLevels
parameter array:
- if you don’t provide any log level at all, default levels will be applied ([Low]
Trace
, [Medium]Information
and [High]Critical
) - if you provide only 1 log level like
Information
, it will be applied to all log entries ([Low]Information
, [Medium]Information
and [High]Information
). Up to you to catch exceptions and to log it at any level of your choice. - if you provide only 2 log levels like
Debug
andError
, the lowest will be applied to both Low and Medium ([Low]Debug
, [Medium]Debug
and [High]Error
) - if you provide 3 log levels like
Debug
,Warning
andCritical
, it will be applied like you said ([Low]Debug
, [Medium]Warning
and [High]Critical
) - if you provide more than 3 log levels, the lowest goes to Low, the highest to High and it will take the middle one for Medium
- if you provide a
None
at some point, it will disable logging for corresponding severity
exceptions
We can now handle request exceptions by providing an optional onException
catching action parameter:
var result = await _reqResManager.ExecuteAsync(api => api.GetUsersAsync(), clearCache: false, onException: OnGetUsersException); ... private void OnGetUsersException(Exception ex) { ... }
caching
First, you now get an optional boolean parameter while executing a request call. It’s called clearCache
and as you can expect, it ask Apizr to clear cache right before sending the request, forcing the fetch and updating the cache. No matter of cache settings. Useful when the user try to refresh displayed data.
Then, Akavache fluent configuration options now let you adjust its main settings like application name and blob cache type. You also get acces to a new WithAkavacheCacheHandler()
configuration option to get things shorter.
Finally, there’s a brand new caching intergration package with Microsoft.Extensions.Caching called Apizr.Extensions.Microsoft.Caching. It offers those using extended approach to relie on any compatible caching package. With the right package installed, you’ll find new configuration options like:
In-Memory:
options => options.WithInMemoryCacheHandler()
Distributed:
options => options.WithDistributedCacheHandler<TCacheType>()
where TCacheType
could be either string
or byte[]
, conforming to MS Extensions Distributed Cache definition.
Registering MS Extension Distributed Cache means that you have to install the distributed cache of your choice and register it too.
Mapping
The main improvement here is that we can use data mapping into both static and extended worlds. It doesn’t matter anymore.
Also, there’s no autoMapper factory parameter anymore while executing a request.
Instead, after defining your mapping profiles and registering AutoMapper, just set your mapping types withing the generic request like:
var result = await reqResManager.ExecuteAsync<MinUser, User>((api, user) => api.CreateUser(user), minUser);
Here we give a MinUser typed object to Apizr, which will be mapped to User type just before sending it. Then Apizr will map the User typed result back to MinUser type just before returning it.
There are many more overloads so you can map objects the way you need. The same while using MediatR and/or Optional.
Finaly, we can now register AutoMapper directly with the new WithAutoMapperMappingHandler()
configuration option to get things shorter.
Mediatr
MediatR get the same upgraded features than the core ones:
- We can now clear cached data right before sending a request thanks to the new optional
clearCache
boolean parameter (see Caching) - We can now map data by providing types while calling any of its Send methods (see Mapping)
- We can now handle request exceptions by providing an optional
onException
catching action (see Exceptions)
Also, MediatR integration now offers brand new IApizrMediator
and IApizrCrudMediator
interfaces to get things shorter than ever.
optional
Optional.Async get the same upgraded features than the core ones:
- We can now clear cached data right before sending a request thanks to the new optional
clearCache
boolean parameter (see Caching) - We can now map data by providing types while calling any of its Send methods (see Mapping)
- We can now handle request exceptions by providing an optional
onException
catching action (see Logging)
Also, Optional.Async integration now offers brand new IApizrOptionalMediator
and IApizrCrudOptionalMediator
interfaces to get things shorter than ever.
Registry
You may want to register multiple api interfaces within the same project. Also, you may want to share some common configuration between apis without repeating yourself, but at the same time, you may need to set some specific ones for some of it. This is where the ApizrRegistry comes on stage.
Here is an example with extended approach but the same could be done with the static one:
// Some policies var registry = new PolicyRegistry { { "TransientHttpError", HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) }) } }; services.AddPolicyRegistry(registry); // Apizr registration services.AddApizr( registry => registry .AddFor<IReqResService>() .AddFor<IHttpBinService>( options => options .WithLogging( HttpTracerMode.Everything, HttpMessageParts.All, LogLevel.Trace)) .AddCrudFor<User, int, PagedResult<User>, IDictionary<string, object>>( options => options .WithBaseAddress("https://reqres.in/api/users"))), config => config .WithPolicyRegistry(registry) .WithAkavacheCacheHandler() .WithLogging( HttpTracerMode.ExceptionsOnly, HttpMessageParts.ResponseAll, LogLevel.Error) );
And here is what we’re saying in this example:
- Add a manager for IReqResService api interface into the registry, to register it into the container
- Add a manager for IHttpBinService api interface into the registry, to register it into the container
- Apply logging options dedicated to IHttpBinService’s manager
- Add a manager for User entity with CRUD api interface and custom types into the registry, to register it into the container
- Apply address option dedicated to User’s manager
- Apply common options to all managers by:
- Providing a policy registry
- Providing a cache handler
- Providing some logging settings (won’t apply to IHttpBinService’s manager as we set some specific ones)
See? It’s like doing some shopping about what we want for all but this one, and so on…
There’s some other cool things comming with the registry.
With extended approach, everything is auto registered into your DI container, where with static approach, the returned registry instance let you populate everthing so that you could register all by yourself:
// Apizr registry var apizrRegistry = Apizr.Create( registry => registry ..., config => config ... ); // Container registration apizrRegistry.Populate((type, factory) => myContainer.RegistrationMethodFactory(type, factory) );
Also, with the registry itself registered (extended: auto, static: manual), you can definitly inject/resolve IApizrRegistry
and get your managed api interfaces from it, where and when you need it:
var reqResManager = apizrRegistry.GetFor<IReqResService>(); // OR var userManager = apizrRegistry.GetCrudFor<User>();
The same for IApizrMediationRegistry
if you’re using MediatR and IApizrOptionalMediationRegistry
if you’re using Optional.Async
A single interface to rule them all.
conclusion
I think I’m done with highlighting Apizr v4.0 features, but you should keep an eye on the changelog to get all the changes picture.
Feel free to ask me anything on twitter, or openning an issue or a PR on GitHub.
I get some ideas for what’s next 🙂