Episode 02 of an Apizr walkthrough building a MAUI app requesting a backend API.
Apizr is a Refit based web api client manager, but resilient (retry, connectivity, cache, auth, log, priority, etc…).
This episode is about adjusting some basic options.
APIZR
If you’re new to Apizr, you shoud know that it’s based on Refit and aims to provide many more features on top of it, like retry handling, connectivity check, cache management, authentication handling, priority management, and so on… It’s here to help me to don’t repeat myself into each and every projects while dealing with api request calls and resilience needs.
Get the big picture by reading the documentation:
Feel free to browse code, tests and samples and maybe to submit PRs or ideas:
Don’t forget the YouTube Channel Playlist about Apizr:
The starcellar series
The StarCellar walkthrough series is built thanks to multiple episodes, each with a dedicated source code branch, blog post and playlist video.
Feel free to fork the project and start from the main branch, so that you could follow this tutorial coding by yourself. If you do so, don’t forget to run the API through a Dev Tunnel and to update the MAUI app’s base address.
the starcellar episode 02
The current episode is covered by this video:
Adjusting basic options Without Apizr
First, the Without.Apizr project!
I’ll try to set the base address, not from any constant value but loaded from an appsettings.json file.
Start by referencing some NuGet packages:
- Microsoft.Extensions.Configuration.Binder
- Microsoft.Extensions.Configuration.Json
- Microsoft.Extensions.FileProviders.Embedded
Now add a json file and set it to EmbeddedResource (needed by MAUI but not for any other project type).
Then write something like this into it:
{ "AppSettings": { "BaseAddress": "https://YOUR_BASE_ADDRESS" } }
Now create an AppSettings class file looking like:
public class AppSettings { public string BaseAddress { get; set; } }
Then open the MauiProgram class and scroll to the Refit registration part.
There, replace all:
.ConfigureHttpClient(c => c.BaseAddress = new Uri(Constants.BaseAddress));
by:
.ConfigureHttpClient((sp, c) => c.BaseAddress = new Uri(sp.GetRequiredService<IConfiguration>() .GetRequiredSection("AppSettings").Get<AppSettings>().BaseAddress));
Here we set the base address from our appsettings instead of former constant.
You can run the app and see that everything’s still ok.
Adjusting basic options With Apizr
Now the With.Apizr project!
Repeat the exact same steps you did previously without Apizr but the MauiProgram’s registration part.
Insert into both ICellarApi and IFileApi registrations the following builder parameter:
options => options.ConfigureHttpClientBuilder( httpClientBuilder => httpClientBuilder.ConfigureHttpClient( (sp, c) => c.BaseAddress = new Uri(sp.GetRequiredService<IConfiguration>() .GetRequiredSection("AppSettings") .Get<AppSettings>() .BaseAddress)))
Here we set the base address fluently from our appsettings, instead of former constant previously set by attribute design.
You can run the app and see that everything’s still ok.
Sharing common options With Apizr registry
I don’t like to repeat myself while coding, really!
So when it came to Apizr, I managed to let it offer a way to share some common options between multiple apis.
That’s where the registry comes in.
Instead of repeating registrations despite the same options, replace it all by:
builder.Services.AddApizr( registry => registry .AddManagerFor<ICellarApi>() .AddManagerFor<IFileApi>(), options => options .WithBaseAddress(sp => sp.GetRequiredService<IConfiguration>() .GetRequiredSection("AppSettings") .Get<AppSettings>() .BaseAddress));
There, you can see that we added our cellar and file apis to the Apizr registry, then we provided a base address to be set to both of it.
You can run the app and see that everything’s still ok.
Sharing more options With Apizr
Previously, we shared options between different apis.
But we can share options between different requests of a single api too.
If you look at the ICellarApi interface, you’ll find some kind of duplicated code, the “/wines” path:
public interface ICellarApi { [Get("/wines")] Task<IEnumerable<Wine>> GetWinesAsync(); [Get("/wines/{id}")] Task<Wine> GetWineDetailsAsync(Guid id); [Post("/wines")] Task<Wine> CreateWineAsync(Wine item); [Put("/wines/{id}")] Task UpdateWineAsync(Guid id, Wine item); [Delete("/wines/{id}")] Task DeleteWineAsync(Guid id); }
There, we could adjust attributes like so:
[BaseAddress("/wines")] public interface ICellarApi { [Get("")] Task<IEnumerable<Wine>> GetWinesAsync(); [Get("/{id}")] Task<Wine> GetWineDetailsAsync(Guid id); [Post("")] Task<Wine> CreateWineAsync(Wine item); [Put("/{id}")] Task UpdateWineAsync(Guid id, Wine item); [Delete("/{id}")] Task DeleteWineAsync(Guid id); }
Here the common “/wines” path is shared at interface level to be set to all its requests.
Apizr will merge the base address with that common path, then with the request one.
You can run the app and see that everything’s still ok.
Note that it could be done by fluent configuration instead of attribute.
In such scenario, we would just remove the base address attribute from our latest design and adjust the registration like:
builder.Services.AddApizr( registry => registry .AddManagerFor<ICellarApi>(options => options.WithBasePath("/wines")) .AddManagerFor<IFileApi>(), options => options .WithBaseAddress(sp => sp.GetRequiredService<IConfiguration>() .GetRequiredSection("AppSettings") .Get<AppSettings>() .BaseAddress));
Two ways to do the same thing, depending on your needs.
You can run the app and see that everything’s still ok.
Running the app
Nothing’s changed on the user experience point of view. The same app, but with some options adjusted and shared thanks to Apizr:
get more of it
Feel free to ask me anything on twitter, or opening a discussion, issue or PR on GitHub.
Again, it’s basically all built for my own use and motivated by my needs. But it’s live on Nuget and opened to all so feel free to contribute.
Apizr brings many more features so you should head to the next episode to continue your walkthrough path.