In our article A Few Great Ways to Consume RESTful API in C# we introduced a few different ways to consume a Restful API.

This article is about going into details of Flurl library and giving a few examples of how to authenticate and consume a restful API such as GitHub’s.

The source code for this article is located here: Flurl Examples. Be sure to fork it and follow along with this article to make the most out of it.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

In this article you’ll learn:

So, let’s drill down into it.

So, What is Flurl?

Flurl stands for the Fluent URL.

Quoting Flurl’s home page: Flurl is a modern, fluent, asynchronous, testable, portable, buzzword-laden URL builder and HTTP client library for .NET. 

It’s simple as that.

Flurl has been recommended by a few of our readers, and upon further investigation and usage, we’ve concluded that it is indeed library worth using or at least trying. Our experience with Flurl was positive, it’s really easy to set up and use, and the documentation is digestible and on point.

Under the polished hood of a modern library lies HttpClient. That means all methods are by default asynchronous, but with the added goodness of fluent execution.

Flurl also has its own mechanisms for testing, so writing complicated mocks and test cases has become a non-issue. We’ll get to that later on.

Since Flurl is completely fluent and relies on extension methods, that means, you’ve guessed it, it’s completely extensible.

It has it’s own configuration and error handling mechanisms, which makes it a complete and very intuitive library.

So, that’s about it in short terms, now let’s get started, and see what Flurl can do for us.

Setting up Flurl

You can add Flurl to your project by typing Install-Package Flurl -Version 2.8.0 in your package manager console, or by using .NET CLI: dotnet add package Flurl --version 2.8.0.

And you’re good to go! Easy as that.

What does Flurl Offer?

Flurl consists of two main modules: URL builder and some utility methods as well as Flurl.HTTP which is the main part and we’ll be using that to consume our APIs.

URL Builder

Flurl’s URL API can help you easily construct your URLs.

For example, you can do something like this to construct a complicated URL:

using Flurl;

var url = "http://www.some-api.com"
    .AppendPathSegment("endpoint")
    .SetQueryParams(new {
        api_key = ConfigurationManager.AppSettings["SomeApiKey"],
        max_results = 20,
        q = "Don't worry, I'll get encoded!"
    })
    .SetFragment("after-hash");

Flurl offers great ways to parse, encode and combine URLs. There is a list of methods you can utilize here.

So that’s how you manipulate URLs with Flurl.

HTTP

Once that you have your URL neatly prepared, you can dive into Flurl’s HTTP features.

With Flurl, you can consume any REST endpoint, whether public or private. You can send GET, POST, PUT, PATCH or DELETE requests and set up headers, cookies and much more.

All responses are awaitable and you can force a strongly-typed response or just return a result as a dynamic object if you prefer it that way.

Flurl works well with strings, bytes, streams or any other media type, and you can both return HttpResponseMessage or the data from its body.

We’ll get to that in the examples section.

Configuration

Let’s get on with the configuration.

Now, the configuration of Flurl is available at different levels. While most people don’t need to configure their applications and are pretty much satisfied with the default library behavior, there are different levels of configuration in Flurl that will let you mold it exactly how you like it.

The stuff you can configure:

  • Client settings
  • Requests settings
  • Tests settings
  • The way HttpClient is handled
  • Serializers
  • Caching strategy

As we mentioned, it’s highly unlikely that you’ll need to configure some of these settings, especially if you are not sure what they do. This goes beyond the scope of this article and it can be a whole topic to itself.

But this goes to show just how powerful Flurl can be despite the simplistic view on the surface.

Eventing

Besides these settings, Flurl provides a way to decouple your business logic from Flurl itself through the Event mechanism. Events are available through the HttpCall class.

There is an example in official docs that show how to implement a global error handling mechanism with events (very useful).

private async Task HandleFlurlErrorAsync(HttpCall call) {
    await LogErrorAsync(call.Exception.Message);
    MessageBox.Show(call.Exception.Message);
    call.ExceptionHandled = true;
}

FlurlHttp.Configure(settings => settings.OnErrorAsync = HandleFlurlErrorAsync);

Once you start changing different settings all over the place, you can revert them to default values using Settings.ResetDefaults() method.

Extensibility

Since Flurl is fluent by nature, which means it uses extension methods, we can add our own extensions as well if needed.

This gives Flurl almost god-like flexibility and natural extensibility that can fit everyone’s needs.

You can extend both the Url builder and Flurl.Http parts of the Flurl library.

Let’s extend Url builder:

public static Url MyExtensionMethod(this Url url) {
    // do something interesting with url
    return url;
}

Now we can do something like this:

url = "https://api.github.com/"
    .AppendPathSegment("endpoint")
    .DoMyThing(); // uses Url extension

Pretty slick.

We can also extend the Flurl.Http part. Flurl chainable methods come in sets of 3 or 4, so we can do something like this:

public static IFlurlRequest DoMyThing(this IFlurlRequest req) {
    // do something interesting with req.Settings, req.Headers, req.Url, etc.
    return req;
}

public static IFlurlRequest DoMyThing(this Url url) => new FlurlRequest(url).DoMyThing();

public static IFlurlRequest DoMyThing(this string url) => new FlurlRequest(url).DoMyThing();

And then, our method is available through the fluent interface as a string, Url or IFlurlRequest extension:

result = await "http://api.com"
    .DoMyThing() // string extension
    .GetAsync();

result = "http://api.com"
    .AppendPathSegment("endpoint")
    .DoMyThing() // Url extension
    .GetAsync();

result = "http://api.com"
    .AppendPathSegment("endpoint")
    .WithBasicAuth(u, p)
    .DoMyThing() // IFlurlRequest extension
    .GetAsync();

If you are unfamiliar with extension methods, check out our tutorial about them.

Two Ways To Authenticate in Flurl

Of course, many APIs hide confidential resources behind protected endpoints. To get to that endpoints you need to have certain rights (authorization), and you do that by authenticating yourself.

Flurl offers two ways of authenticating: Basic Authentication and OAuth 2.0 bearer token.

Here’s how you do it:

await url.WithBasicAuth("username", "password").GetJsonAsync();
await url.WithOAuthBearerToken("mytoken").GetJsonAsync();

A very easy and intuitive way to authenticate!

Here is how it might look like in a real example:

public class FlurlRequestHandler : IRequestHandler
{
    private static string _githubToken;

    public FlurlRequestHandler()
    {
        _githubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
    }

    public Task<List<Repository>> GetRepositories()
    {
        var result = RequestConstants.BaseUrl
            .AppendPathSegments("user", "repos")
            .WithHeader(RequestConstants.UserAgent, RequestConstants.UserAgentValue)
            .WithOAuthBearerToken(_githubToken)
            .GetJsonAsync<List<Repository>>();

        return result;
    }
}

This example shows us how to authenticate using Bearer token and retrieve the list of GitHub repositories.

The important thing to note here is that we store token in the environment variable. You should never, ever, store sensitive information in your code or configuration files. That’s a serious security risk!

Here is how authentication looks like using the Basic Authentication mechanism:

public class FlurlRequestHandler : IRequestHandler
{
    private static string _githubUsername;
    private static string _githubPassword;

    public FlurlRequestHandler()
    {
        _githubUsername = Environment.GetEnvironmentVariable("GITHUB_USERNAME");
        _githubPassword = Environment.GetEnvironmentVariable("GITHUB_PASS");
    }

    public Task<List<Repository>> GetRepositories()
    {
        var result = RequestConstants.BaseUrl
            .AppendPathSegments("user", "repos")
            .WithHeader(RequestConstants.UserAgent, RequestConstants.UserAgentValue)
            .WithBasicAuth(_githubUsername, _githubPassword)
            .GetJsonAsync<List<Repository>>();

        return result;
    }
}

You see how easily we changed the authentication mechanism?

Very cool.

The same warning applies to storing usernames and passwords too. Keep them safe in your environment variables!

How to Write Get, Post, Put/Patch, and Delete Requests

Now to the main event, writing requests in Flurl.

This is where Flurl really shines. Fluent syntax, combined with the asynchronous execution and readability to match it really does the job.

Writing requests is really easy and the learning curve is almost non-existent!

Let’s see what it looks like in our example (we will use GitHub bearer token, but you can use the other mechanism too):

public class FlurlRequestHandler : IRequestHandler
{
    private static string _githubToken;

    public FlurlRequestHandler()
    {
        _githubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
    }

    public async Task<List<Repository>> GetRepositories()
    {
        var result = await RequestConstants.BaseUrl
            .AppendPathSegments("user", "repos")
            .WithHeader(RequestConstants.UserAgent, RequestConstants.UserAgentValue)
            .WithOAuthBearerToken(_githubToken)
            .GetJsonAsync<List<Repository>>();

        return result;
    }

    public async Task<Repository> CreateRepository(string user, string repository)
    {
        var repo = new Repository
        {
            Name = repository,
            FullName = $"{user}/{repository}",
            Description = "Generic description",
            Private = false
        };

        var result = await RequestConstants.BaseUrl
            .AppendPathSegments("user", "repos")
            .WithHeader(RequestConstants.UserAgent, RequestConstants.UserAgentValue)
            .WithOAuthBearerToken(_githubToken)
            .PostJsonAsync(repo)
            .ReceiveJson<Repository>();

        return result;
    }

    public async Task<Repository> EditRepository(string user, string repository)
    {
        var repo = new Repository
        {
            Name = repository,
            FullName = $"{user}/{repository}",
            Description = "Modified repository",
            Private = false
        };

        var result = await RequestConstants.BaseUrl
            .AppendPathSegments("repos", user, repository)
            .WithHeader(RequestConstants.UserAgent, RequestConstants.UserAgentValue)
            .WithOAuthBearerToken(_githubToken)
            .PatchJsonAsync(repo)
            .ReceiveJson<Repository>();

        return result;
    }

    public async Task<HttpResponseMessage> DeleteRepository(string user, string repository)
    {
        var result = await RequestConstants.BaseUrl
            .AppendPathSegments("repos", user, repository)
            .WithHeader(RequestConstants.UserAgent, RequestConstants.UserAgentValue)
            .WithOAuthBearerToken(_githubToken)
            .DeleteAsync();

        return result;
    }
}

If you are unfamiliar with the GitHub tokens, there is a great resource about them.

Essentially, you need to follow the link above and generate one here:

Personal Access Tokens

Alternatively, you can use basic authentication as we described in the authentication section.

Exception Handling

Flurl.Http throws FlurlHttpException for every non-200 response.

You can easily deserialize Error response:

catch (FlurlHttpException ex) {
    // For error responses that take a known shape
    TError e = ex.GetResponseJson<TError>();

    // For error responses that take an unknown shape
    dynamic d = ex.GetResponseJson();
}

Or even allow other status codes:

url.AllowHttpStatus(HttpStatusCode.NotFound, HttpStatusCode.Conflict).GetAsync();
url.AllowHttpStatus("400-404,6xx").GetAsync();
url.AllowAnyHttpStatus().GetAsync();

Let’s see how a real-world example might look like:

try
{
    var newRepository = flurlRequestHandler.CreateRepository("CodeMazeBlog", "Test-Repository").Result;

    Console.ForegroundColor = color;
    Console.WriteLine("Repository created");
    Console.ResetColor();
	
    Console.WriteLine($"Name: {newRepository.Name}");
    Console.WriteLine($"Full name: {newRepository.FullName}");
    Console.WriteLine($"Description: {newRepository.Description ?? "None"}");
    Console.WriteLine($"Url: {newRepository.Url}");
    Console.WriteLine($"Private: {newRepository.Private}");
	
    Console.WriteLine();
}
catch (AggregateException ae)
{
     // in case the repository already exists, handle FlurlHttpException
    ae.Handle(ex =>
    {
        if (ex is FlurlHttpException)
        {
            Console.WriteLine(ex);
        }
        return ex is FlurlHttpException;
    });
}

In this scenario, we are trying to create a repository. If that repository already exists, we’ll get a FlurlHttpException wrapped in Aggregate exception back from GitHub API. So we need to cover that scenario and write our exception so we can figure out what happened.

And true enough, we get it:

FlurlHttpException

This is what we get as a result of trying to create the same repository twice.

Sweet.

Unit Testing in Flurl

Flurl has some “dead simple” ways of testing its functionalities, right out-of-the-box.

HttpTest class provides the mechanisms to test our API calls.

Here are some of the examples of a few tests using MSTest framework:

[TestClass]
public class Tests
{
    [TestMethod]
    public void GetRepositories_ShouldHaveBeenCalled_AtLeastOnce()
    {
        using (var httpTest = new HttpTest())
        {
            var flurlRequestHandler = new FlurlRequestHandler();
            var result = flurlRequestHandler.GetRepositories();

            httpTest.ShouldHaveCalled(Url.Combine(RequestConstants.BaseUrl, "user", "repos"))
                .WithVerb(HttpMethod.Get)
                .Times(1);
        }
    }

    [TestMethod]
    public void CreateRepository_ShouldHaveBeenCalled_AtLeastOnce()
    {
        using (var httpTest = new HttpTest())
        {
            var flurlRequestHandler = new FlurlRequestHandler();
            var result = flurlRequestHandler.CreateRepository("CodeMazeBlog", "Test");

            httpTest.ShouldHaveCalled(Url.Combine(RequestConstants.BaseUrl, "user", "repos"))
                .WithVerb(HttpMethod.Post)
                .Times(1);
        }
    }

    [TestMethod]
    public void EditRepository_ShouldHaveBeenCalled_AtLeastOnce()
    {
        using (var httpTest = new HttpTest())
        {
            var flurlRequestHandler = new FlurlRequestHandler();
            var result = flurlRequestHandler.EditRepository("CodeMazeBlog", "Test");

            httpTest.ShouldHaveCalled(Url.Combine(RequestConstants.BaseUrl, "repos", "CodeMazeBlog", "Test"))
                .WithVerb(new HttpMethod("PATCH"))
                .Times(1);
        }
    }

    [TestMethod]
    public void DeleteRepository_ShouldHaveBeenCalled_AtLeastOnce()
    {
        using (var httpTest = new HttpTest())
        {
            var flurlRequestHandler = new FlurlRequestHandler();
            var result = flurlRequestHandler.DeleteRepository("CodeMazeBlog", "Test");

            httpTest.ShouldHaveCalled(Url.Combine(RequestConstants.BaseUrl, "repos", "CodeMazeBlog", "Test"))
                .WithVerb(HttpMethod.Delete)
                .Times(1);
        }
    }
}

Despite what it looks like at first, these are all mock requests, and you won’t hit any real endpoints.

Very neat!

You can write tests in any other popular testing framework for .NET as well (NUnit, Xunit…). The choice is all yours.

If you prefer the notion of setting things up and tearing them down after the tests, you can do that too:

private HttpTest _httpTest;

[SetUp]
public void CreateHttpTest() {
    _httpTest = new HttpTest();
}

[TearDown]
public void DisposeHttpTest() {
    _httpTest.Dispose();
}

[Test]
public void Test_Some_Http_Calling_Method() {
    // Flurl is in test mode
}

Arrange and assert phases are pretty flexible too. You can pretty much execute any scenario you can think of.

As an exercise you can play around with HttpTest class more, to see what else it can do!

Flurl Lifetime

Since Flurl.Http uses HttpClient under the hood, it relies on its lifetime too.

If you’ve ever worked with HttpClient, you are probably aware that it’s recommended that you instantiate it once per application lifetime because it can easily exhaust the number of sockets available.

Flurl (from the v2 up) handles that problem by caching the HttpClient for the same host. That means every succeeding request to the same host will use the same HttpClient instance.

Note that you need to upgrade to version 2+ to have this functionality available (recommended).

FlurlClient is a wrapper around HttpClient, so consequently, it’s bound to its lifetime.

You can have more control over HttpClient instances and their lifetimes you can do something like this:

using (var cli = new FlurlClient("https://api.com").WithOAUthBearerToken(token))
{
    await cli.Request("path", "to", "endpoioint").PostJsonAsync(thing);
    var stuff = await cli.Request("things").SetQueryParam("id", thing.Id).GetAsync();
}

Flurl and IoC

Dependency Injection and IoC containers play a great role in modern application development.

Flurl is well suited for dependency injection. Trying to register IFlurlClient as a singleton and services as transient, or any other combination can result in less than optimal results or bad practices.

That’s where IFlurlClientFactory comes in. As the official documentation states, the only reason it exists is to override Flurl’s instance-per-host default behavior.

So we can do something like this:

public class MyService : IMyService
{
    private readonly IFlurlClient _flurlClient;

    public MyService(IFlurlClientFactory flurlClientFac) {
        _flurlClient = flurlClientFac.Get(SERVICE_BASE_URL);
        // configure _flurlClient as needed
    }

    public Task<Thing> GetThingAsync(int id) {
        return _flurlClient.Request("things", id).GetAsync<Thing>();
    }
}

Now we can simply register IFlurlClientFactory as a singleton and get a single instance of FlurlClient for this service.

Dependency Injection is a whole topic to itself, so we won’t go any deeper into it in this article.

This should be enough if you need to register Flurl through your IoC of choice.

Conclusion

This wraps it up for out Flurl library introduction.

Flurl is a very a neat little library that gives us both simplicity and flexibility that we need to work comfortably with REST in our C# applications.

It is simple enough to easily learn and use, and it is extensible enough to cover some pretty complex scenarios.

So we hope this little demonstration will help you understand how Flurl works and maybe even persuade you to try it out. We will definitely use it in our newest projects that require REST communication.

To learn about more useful ways to communicate with REST endpoints, you can check our article: A Few Great Ways to Consume RESTful API in C#.

What you’ve learned in this post:

  • What Flurl is and what it can do for you
  • How to set it up, configure it, and extend it
  • How to build URLs and write requests with Flurl
  • A bit about Flurl lifetime (HttpClient)
  • How to unit test your Flurl methods
Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!