Fluent URL Building☍
Flurl's URL builder is best explained with an example:
using Flurl;
var url = "http://www.some-api.com"
.AppendPathSegment("endpoint")
.SetQueryParams(new {
api_key = _config.GetValue<string>["SomeApiKey"],
max_results = 20,
q = "Don't worry, I'll get encoded!"
})
.SetFragment("after-hash");
The example above (and most on this site) uses an extension method off String
to implicitly create a Url
object. You can do exactly the same explicitly if you prefer:
var url = new Url("http://www.some-api.com").AppendPathSegment(...
As of 3.0, all extension methods available on String
are also available on System.Uri
. In either case, you can use the builder methods and convert back to the original representation (using ToString()
or ToUri()
) in single fluent call chain.
In addition to the object notation above, SetQueryParams
also accepts any collection of key-value pairs, Tuples, or a Dictionary object. These alternatives are particularly useful for parameter names that are not valid C# identifiers. There's also a SetQueryParam
(singular) if you want to set them one by one. In any case, these methods overwrite any previously set values of the same name, but you can set multiple values of the same name by passing a collection:
var url = "http://www.mysite.com".SetQueryParam("x", new[] { 1, 2, 3 });
Assert.AreEqual("http://www.mysite.com?x=1&x=2&x=3", url)
Builder methods and their overloads are highly discoverable, intuitive, and always chainable. A few destructive methods are also included, such as RemoveQueryParam
, RemovePathSegment
, and ResetToRoot
.
Parsing☍
In addition to building URLs, Flurl.Url
is effective at decomposing an existing one:
var url = new Url("https://user:pass@www.mysite.com:1234/with/path?x=1&y=2#foo");
Assert.AreEqual("https", url.Scheme);
Assert.AreEqual("user:pass", url.UserInfo);
Assert.AreEqual("www.mysite.com", url.Host);
Assert.AreEqual(1234, url.Port);
Assert.AreEqual("user:pass@www.mysite.com:1234", url.Authority);
Assert.AreEqual("https://user:pass@www.mysite.com:1234", url.Root);
Assert.AreEqual("/with/path", url.Path);
Assert.AreEqual("x=1&y=2", url.Query);
Assert.AreEqual("foo", url.Fragment);
Although similar to the parsing capabilities of System.Uri
, Flurl aims to be more compliant with RFC 3986, and more true to the actual string provided, and therefore differs in the following ways:
Uri.Query
includes the?
character;Url.Query
does not.Uri.Fragment
includes the#
character;Url.Fragment
does not.Uri.AbsolutePath
always includes a leading/
character;Url.Path
includes it only if it's actually present in the original string, e.g. for"http://foo.com"
,Url.Path
is an empty string.Uri.Authority
does not include user info (i.e.user:pass@
);Url.Authority
does.Uri.Port
has a default value if not present;Url.Port
is nullable and is not defaulted.Uri
will make no attempt to parse a relative URL;Url
assumes that if the string doesn't start with{scheme}://
, then it starts with a path and parses it accordingly.
Url.QueryParams
is a special collection type that maintains order and allows duplicate names, but is optimized for the typical case of unique names:
var url = new Url("https://www.mysite.com?x=1&y=2&y=3");
Assert.AreEqual("1", url.QueryParams.FirstOrDefault("x"));
Assert.AreEqual(new[] { "2", "3" }, url.QueryParams.GetAll("y"));
Mutability☍
A Url
is effectively a mutable builder object that implicitly converts to a string. If you need an immutable URL, such as a base URL as a member variable of a class, a common pattern is to type it as a String
:
public class MyServiceClass
{
private readonly string _baseUrl;
public Task CallServiceAsync(string endpoint, object data) {
return _baseUrl
.AppendPathSegment(endpoint)
.PostAsync(data); // requires Flurl.Http package
}
}
Here the call to AppendPathSegment
creates a new Url
object. The result is that _baseUrl
remains unmodified, and you've added no additional "noise" compared to if you had declared it as a Url
.
Another way to get around the mutable nature of Url
when needed is to use the Clone()
method:
var url2 = url1.Clone().AppendPathSegment("next");
Here you get a new Url
object based on another, so you can modify it without changing the original.
Encoding☍
Flurl takes care of encoding characters in URLs but takes a different approach with path segments than it does with query string values. The assumption is that query string values are highly variable (such as from user input), whereas path segments tend to be more "fixed" and may already be encoded, in which case you don't want to double-encode. Here are the rules Flurl follows:
- Query string values are fully URL-encoded.
- For path segments, reserved characters such as
/
and%
are not encoded. - For path segments, illegal characters such as spaces are encoded.
- For path segments, the
?
character is encoded, since query strings get special treatment.
In some cases, you might want to set a query parameter that you know to be already encoded. SetQueryParam
has optional isEncoded
argument:
url.SetQueryParam("x", "don%27t%20touch%20me", true);
While the official URL encoding for the space character is %20
, it's very common to see +
used in query parameters. You can tell Url.ToString
to do this with its optional encodeSpaceAsPlus
argument:
var url = "http://foo.com".SetQueryParam("x", "hi there");
Assert.AreEqual("http://foo.com?x=hi%20there", url.ToString());
Assert.AreEqual("http://foo.com?x=hi+there", url.ToString(true));
Utility Methods☍
Url
also contains some handy static methods, such as Combine
, which is basically a Path.Combine for URLs, ensuring one and only one separator character between parts:
var url = Url.Combine(
"http://foo.com/",
"/too/", "/many/", "/slashes/",
"too", "few?",
"x=1", "y=2");
// result: "http://www.foo.com/too/many/slashes/too/few?x=1&y=2"
And to help you avoid some of the notorious quirks associated with the various URL encoding/decoding methods in .NET, Flurl provides some "quirk-free" alternatives:
Url.Encode(string s, bool encodeSpaceAsPlus); // includes reserved characters like / and ?
Url.EncodeIllegalCharacters(string s, bool encodeSpaceAsPlus); // reserved characters aren't touched
Url.Decode(string s, bool interpretPlusAsSpace);