Flurl: AppendPathSegments mutates the Url object

Created on 8 Oct 2018  路  6Comments  路  Source: tmenier/Flurl

Suppose I have initialization code like this:

public const string ApiUrl = "https://some.service.local";

protected Url GetEndpoint(string name)
{
    return ApiUrl.AppendPathSegments("api", name);
}

...

var productEndpoint = GetEndpoint("product");

When I try to use the endpoint like this:

    .AppendPathSegments("customer1", "product1")
    .DeleteAsync()
    .ReceiveString()
    .Result;

productEndpoint
    .AppendPathSegments("customer1", "product2")
    .DeleteAsync()
    .ReceiveString()
    .Result;

...the first call will pass and will look like this:

DELETE https://some.service.local/api/product/customer1/product1

but the second one will fail because now it looks like this:

DELETE https://some.service.local/api/product/customer1/product1/customer1/product2

The AppendPathSegments function (and others) would behave much better if it was a pure function free of side effects and returned a copy instead of mutating the instance itself.

All 6 comments

Url is a builder object, effectively, and builders are mutable. It's not about better or worse, it's just about understanding its nature. Change the return type of GetEndpoint to string and everything will work exactly as you expect here.

I see it's a builder pattern but does it really have to be mutable?

Perhaps I'm a bit too spoiled by languages like F# where you expect a function to do only one thing and not violate the command鈥搎uery separation principle. In this case the "function" does two things: it both mutates current state and returns it.
But never mind, I'll just use it as you suggest.

Does StringBuilder have to be mutable? No, but it was designed that way on purpose and if it were to change, it would break lots of existing code.

I've found command鈥搎uery separation to be more useful at the application level, but that's just my opinion. If you're looking for it out of the libraries you use, Flurl might not be the best choice for you. Url, FlurlClient, and FlurlRequest all violate the principles you refer to in that their methods both mutate their own state and return themselves, in support of a fluent API and minimizing keystrokes. It's an opinionated library and I get that it's not for everybody.

StringBuilder really has to be mutable because it can't afford copying itself on each text append - the total text size can be huge.

But StringBuilder (a builder-only class) is not what came to my mind when I saw Flurl API first time. I saw LINQ (a multipurpose set of extension functions) which again led to a pretty strong assumption about immutability and CQS.

This way or another, Flurl is a very convenient tool compared to raw HttpClient and I will be very happy to use it now when I know how exactly it's supposed to be used. Thank you for your comments.

I just started using FlUrl and I'm facing the same misunderstanding. In HTTPClient, I set a base url and reuse httpclient with Base Address, and path1 and path2 being different.

HttpClient client = new HttpClient;
client.BaseAddress = new Uri("http://basesurl/");
HttpResponseMessage response = await client.GetAsync("path1");
HttpResponseMessage response = await client.GetAsync("path2");

From what I'm seeing (correct me if I'm wrong), Flurl is caching baseurl/path1 and baseurl/path2 separately so I can't store 1 single instance of HttpClient for baseurl for all the endpoints. Each endpoint will be opening a new connection?

@imtrobin The Url object is completely independent of the HttpClient caching mechanism. When you make HTTP calls off a Url, the HttpClient instance is looked up based on the host part of the URL (and created lazily if necessary) on the fly. It never becomes tied to the Url instance in any way. Hope that helps.

Was this page helpful?
0 / 5 - 0 ratings