asp.net core - HttpClient & IHttpClientFactory Tutorial & Tips (+ System.Net.Http.Json)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
welcome everyone today I'm gonna be explaining how to use the HTTP client in an asp.net core application some cool things you can use and about the IEEE HTTP client Factory so as you can see the setup here is we got an API and a consumer so an API is something that you can access it's not gonna be specifically approach to a project that you have or setup yourself it could be a third party API but I'm gonna be using this API as an example of the things that you can do if you're for example going into something like a micro services architecture or something like that but nevertheless let's go ahead and get started and we'll just start with the plain thing of what do you want to avoid and primarily why does the HTTP client factory exists and stuff like that so both of these are very simple setups they're essentially two API is just the consumer is gonna be calling the API and as you can see here in the bad action we are surfacing the HTTP client by just basically newing up an HTTP client we're putting the configuration that we need and what's called just call and we're just gonna be calling the API on the background now I do have both of these running so I have my windows terminal here on the left as my API on the right is my consumer so they're both running with wash run I might need to rerun them if I'm gonna be adding packages and stuff like that but essentially what this gives me is something like this so every time i refresh this I get a whole an object that has name of home and a good and this is because I am calling an endpoint on this controller right so all I'm doing is returning an object here okay and this is on the API side so the reason I'm gonna be putting some variables in here is to just explain the lifetimes of your HTTP client and where you where you can get values and where you shouldn't put values and stuff like that so this is bad because the HTTP client it has been notoriously bad for some time I haven't been aware of this maybe a year ago I wouldn't if you'd ask me something about the HTTP client wouldn't know this but that's kind of a short overview a problem I don't want involving it too much because I want to show at what to actually do it's essentially when you do when you spin up an HTTP client like this it will use up some DNS ports or some handlers on your actual hardware and what won't basically free them up because HTTP client really has to communicate with your network drive and stuff like that so there is a lot of like basically the sea and native bridges between the this actual HTTP client and the actual thing that is going to be happening in the interface that is exposed in your card so for this reason it's basically there is the HTTP message Handler and the HTTP client and is the documentation explained this guy quite well I am not too bothered about this as you just learn how to use the HTTP client factory and you forget about basically spinning up your own versions of HTTP client a primarily this is not a problem in older applications if you're not doing something like a thousand requests a minute basically if you're not doing too many requests it's not a problem your machine might die after some time but or stop doing making a request but the more requests per second you make the the faster you're gonna hit this wall of where your HTTP client is just gonna stop working okay and that's basically prime everything you want to be guarding against so enough rambling on this is a bad a solution anywhere where you have to new HTTP client in an action in a repository don't do it so a simple use case of how to actually service an HTTP client is on your consumer you would go into startup and as you can see simple setup just controllers and map default routes what we do in our services is add an HTTP client ok and this is the simplest thing you can do it's like a one-off thing you just have you just need an HTTP client somewhere this is where you do you add an HTTP client here let's copy the band let's remove that hashtag and we'll name this simple because it's a simple use case and what we're gonna do is we're gonna accept from services so this is just a dependency injection you can do it in the constructor if you want I'm just doing it in this specific method and what you do is you surface and I should be client factoring and now instead what you do is you have the client and you grab the factory and you call create client and this is going to create a client for you so now you don't have access to this sort of what's called to the setup as part of the newing up of the object so all you have to do is just set the base address either on the grid that with your calling or just you know shipped it just do what you need to do to set the base address any setup that you need to do here putting access tokens on your authorization header that stuff can be done so let's put a semicolon here save this and let's go ahead and try to hit this simple and make sure we're still doing this a we're still getting the same thing so here I'm gonna go to simple and yeah same thing still happens I get a new good every time every time that we basically call this function right so one thing that you can do is for example this configuration here you can see how we're essentially having to set the base address and anywhere where we if we basically have something like an interface that's hiding this HTTP client we can have some base configuration here we don't have it so this is what you generally do in your startup you would copy this you would cut this and you do your set up for your HTTP client here so there are a couple of overloads you can have 12 to see them and the primary one that we are after is is the one with just the action with the HTTP client you can have the one where you can have an eye service provider so you can access your dependency injection container essentially for this eye service provider and the builder function right where you configure the HTTP client and you can also have these names which which are named clients and we'll dive that into the in a second so we essentially do is you have your client here and you have a function where you basically say the things that you want to do with this client and sorry I probably picked out the wrong function there so again actually yeah I think it's worth just going to the named ones you can't have a configured client without a name so yeah let's just go ahead and call this simple okay so this will be named simple and all we're doing is we're basically having a client that we configured with a base address now since we've done this let's go ahead and try this out let's see if we actually just get our simple client through this bit here so going back to the browser let's go ahead refresh this and you can see that we provide an invalid URI so what we actually need to do is we actually do need to supply the symbol here okay so now if we go back refresh this and we're back at the point where we basically see the response and we're back to having an HTTP client but at this point it's named okay so the main point here is that you can have multiple clients with different configurations it depends on what name you give them and then you extract the actual client that you need through your factory okay and just a quick note on the lifetimes the factory itself is a singleton okay so do if you want a new client every time do not alike over every time you basically call it if you want a new client create client will give you a new client okay factory itself is a singleton be careful if you're going to be creating a client in the constructor or a singleton and basically be aware of the lifetimes and one thing that we could have is a for example in the configuration here you cannot have client and we can have default request headers and let's go ahead and add a start-up header okay and in the startup header we're just gonna surface a good we're gonna get a new good we're gonna string it so we can put it as the value here and all we're gonna do is we're gonna go to the API controller and this is primarily how we're gonna check our client behaves okay and we're gonna try to return this startup header here so let's go ahead and basically but the startup header on here and the way we would do this is we would grab the request headers and we would try get value or maybe not even that let's just go ahead because we know it's gonna be there we can go ahead and grab it from the header straight away hey so at this point let's go ahead and refresh this and here you can see the startup header it's an array currently because you can it is because basically the header the value that we get from here isn't really a string it's a string value but besides that the point here is that you get a new ID every single time for this and the startup header so the primary thing to know here is that this configuration in your startup runs every time that you call create client okay so for example if you have a singleton service do not call create client in the constructor register the factory in the constructor and then in the function that you're going to be executing create a client there so then you're gonna have a fresh instance of the client every single time because the singleton is gonna be created once okay that's just an example of where you might run into like some unexpected problems alright so that's pretty much the named client we can also do something along the lines of try to create a different clients so for example if we want to add another client we would go into services and again add an HTTP client we cannot do this on this client so you can see we cannot add another HTTP client because what this returns is an HTTP client builder so here when you register the client you are basically adding stuff onto the client okay if you want another client you go and specify add a CH a client and then you basically give it another name and based on which client you want to surface you do that so moving on kind of the things that you would expect you want to do is you want to be able to have your own HTTP client or maybe an adapter on the HTTP client so essentially you're gonna have something with predefined routes and you're gonna put the HTTP client in it so let's go ahead and see how that can work so let's go ahead and create the client folder here where I'm gonna be basically putting my custom HTTP client right so custom HTTP client that's yes and this is part of the typed client of what the HTTP factory provides and yeah basically what it allows you to do is have kind of like a constructor into which you are going to inject a HTTP client okay and we'll see how this works in just a second so let's go ahead and inject the client and for here I will just go ahead and I will basically offset the this string that we're getting here so I no longer need to create the client because that's what that that constructor is gonna do for me I'm just gonna go ahead and grab this bit and what's called task string that's what we need to return get home and go ahead and return this simple offset we all we have to do now is just register this custom HTTP client and we can inject that straightaway so let's go ahead and see what we can do we can grab the type we can go into startup and in services we can go ahead and say add HTTP client here we supply the type and we can go ahead and call it custom if in case we want to grab an HTTP client version of it with the same configuration but not this type if you don't want that you can go ahead and omit that so let's go ahead we'll keep custom but it is unlikely if you split if you specify your own type it is unlikely that you want name on it because then you can just inject the type by itself so here we can have the client and the same thing can happen here so but here what you get you get a split of the setup for the client because the constructor is gonna get called every single time so the way we can check this is we can have our GUI D and we can create this greeting here and we can to string it here let's go ahead and generate this field okay and instead we'll just pass a query here okay so treat this as base configuration so wherever you're gonna be surfacing this client and the in this particular consumer and then the second particular consumer this configuration should never change okay this is something that's always gonna be staying the same if it's some particular header or whatever know and probably I'm probably the answer to that one is you don't know but essentially treat this as base configuration this configuration will never change or should never change okay then you go into the startup yep any configuration that you need there you go ahead and you put it here we're gonna observe the change of the startup header so we can actually just let's just go ahead and put it in our customer HTTP controller as well just here so let's say that this is base configuration and then depends on what type of a consumer it is and where it's gonna be pointing this can be configured in the startup okay so let's go ahead and spin up a controller action for this so I'm gonna go ahead and say that it's a typed client and what I'm going to do is I'm gonna get a custom HTTP client I'm just gonna call it client and now I don't need this and I'm just going to be using my get home function okay and it is gonna be the same thing as these two but now I've sort of I've pushed the configuration from where we surface the client right into the either the startup or if it's the really base configuration that all versions of these clients should be it should have into the constructor okay so there are different levels at which you can configure it okay but nevertheless there is what we have let's go ahead and try this so make sure simple still works everything compiles and then let's go ahead check as a typed client okay and if we refresh here as the main thing to note is that the ID or the home still changes so the constructor is getting called every single time and as well as the configuration in the startup from what we've just seen seen with a simple one is gonna be changing every single time hey so if you are going to be surfacing some services in the actual setup make sure make sure to understand that it's gonna be doing it every single time it's not a one-time setup hey yeah oh you you get new objects every single time let's go ahead and talk about middleware okay this is a recent problem that I have stumbled upon and it took me a little bit of time to figure it out but nevertheless so how do you add middleware first of all you need a delegate handler object okay so here we're just gonna say that this is some kind of HTTP context middleware okay the reason I'm calling it HTTP context is because I want to demonstrate the lifetime of the middleware and primarily some things that you want to avoid doing with the middleware so what and HTTP context will stand for that I'm gonna be injecting the I HTTP context accessart into it and trying to access some things okay so let's inherit from a delegate handler I believe it is or may be delegating yeah so delegating handler is the object that you want you can then press alt enter on the end only basically on the body and you can generate overrides and the primary thing that you want to override is the send async function right so let's go ahead to select all other ones and we'll grab the send async function hey let's format this a little bit and if you want to know more about middleware in general I have a a middleware video I'll link a give a link in the description and the annotation above so the primary thing here you can do is you can do stuff like attaching your authentication tokens you can do stuff like caching for old ones or 403 s and trying to refresh your tokens and stuff like that if you're doing that sort of thing here I'm just gonna be doing a mock of basically trying to inject your HTTP context and then seeing some errors that could occur but first of all let's go ahead and just get our head around the lifetimes of this middleware okay so let's go ahead and put a constructor here and what we're gonna do is we're gonna add a middleware CTR and so this is gonna be CT oh our constructor now let's just call this CTO our value so we'll grab a good new gooood and we're gonna to string this let's go ahead and create a field and we're gonna have the same thing but this is going to be a method okay and for getting my vowels a little bit but all we're going to do is on the request here we're gonna add headers the same way that we did in the setup a we're just gonna do it through here so we're just gonna say middleware - CT or and we're gonna add the constructor value Hey and then we're gonna do the same but with the method okay and we're gonna add the method value and here all we want to do is we just want to know what change is where so we understand what to put where okay let's go ahead and grab the HTTP context middleware we're gonna go into startup and first of all what we have to do is we're gonna have to add transient this specific middleware and then what we will have to do is let's actually put it on our custom HTTP client because that's primarily what I'm expecting it to work with okay probably we could put it on both so let's go ahead and add HTTP message Handler and this is how you miss it how you register your middleware so basically it's going top from bottom so the top one executes first the bottom one executes last or the the bottom one is closest to the actual call going out and coming back okay so and let's go ahead and actually put this on both of them so in case we want to check other endpoints of how they behave with the other client we can and if there are any errors we can sort them so we have the HTTP context middleware registered and attached to a particular client okay let's go ahead and see what this produces and in order to see what it produces we actually have to go into the home controller of the API and surface the headers that we have attached I thought misspelled middleware there but nevermind yeah let's go ahead and close this as a gang a little bit confusing with the files all open all over the place so let's go ahead and we'll just call this C to our method a header and this will be the method header let's go ahead up b1 name ctrl tab to go to the previous one ctrl tab to go back yep ctrl tab to go back okay and there we go so added everything to the API endpoints let's go ahead and take a look at what a header values we're gonna get right so going back to the HTTP client let's go ahead and refresh this maybe zoom in a little bit so it's easier to see so if i refresh this a couple of times you can see that the constructor header isn't changing but the method the header is so it's not necessarily that the constructor has arisen gonna get triggered again it will trigger it again and we'll see what exactly controls that so because you know you remember we have indeed registered it as a transient right so we're expecting to get a fresh instance every time but here we can see that the instructor isn't really being triggered again and again and this will change in a bit hopefully from rambling but let's go ahead and see what happens on the simple client so we go duplicate this and we'll call for sang a single so if we refresh this we can see it's a 45 oh constructor header still doesn't change however it's different to the typed client here it's quite simple for 3e but still different so if we refresh this okay still hasn't changed but essentially what we get is every time a message HTTP message and ler is created that's when the middleware it gets attached sort of thing so the lifetime of the middleware lasts for as long as as the message handler basically lusts and message handler is separate from the HTTP client so HTTP client kind of sent stuff to the message handler okay so if we go to the type of client hopefully I think the default is set to like two minutes or something like that yep so here you can see the constructor has ER has changed this one still hasn't changed but there there is still a way that you can basically control how long a message handler gets cycled through but you don't really I don't think you really need to do that I'm not actually sure of why you might want to do this but just to sort of make a point or the asset handler a lifetime you can go ahead and set it to maybe like 1 seconds so let's go ahead and fresh this go back to the type client and if we keep refreshing you can see every basically every second the value of the constructor had our changes so the key takeaway here is that the middleware is not applied to the HTTP client at the middleware is applied to the HTTP message handler so doing stuff like injecting the I HTTP context accessor and saving your HTTP context in the constructor can cause some problems and let's actually take a look at what can happen so if we go into services and we aide add HTTP context accessor and we'll try to do something simple and don't even we don't even need to grab anything we can just say context accessor so let's say access ER and if you do something along the lines of HTTP context in the middle so we're so access ER will grab the HTTP context write a field and then maybe you want to do something along the lines of check if it is no we're not gonna be doing anything but primarily this is just a trigger an error so going back to Google Chrome if we refresh the first time everything's fine the second time or a couple of times after you will see that the I feature collection has been disposed of so essentially what we get here is if we refresh it fast enough we're gonna get this exception until a new instance of the middleware is triggered so it can go ahead and grab that specific HTTP context so what you and what you need to do is you need to put this HTTP context into the method body and then going back to the browser you should no longer see this error occur so looks like I have an error where the HTTP context doesn't investigate I have to put it in a variable accessart doesn't exist let's go ahead and generate a field for the access er again forget to do a bunch of stuff okay and now if we refresh okay so now it doesn't matter how often we refresh you know because we are getting a fresh instance of the HTTP context from the accessor and the accessor is a angleton HTTP context is kind of resolved internally okay so another thing I want to show is how you might want to abstract it because let's say this API right here okay it's gonna have its own models it's gonna inherit from some models and gonna be returning jason serialized versions of those models what you want to do is be able to share that HTTP client in a microservices scenario so you might have an API that three or five different types of services can call they're all written in c-sharp and maybe you have a blazer application or an exam or an application that also might want to call this API so you want to be able to share the HTTP client or this API so I just want to show you what you could do let's go ahead and create a new project we'll just call this a library next and let's go ahead call this entities so what you do is you basically create your entities class where let's say we will have a home dot CS or again we'll do something super simple we'll just have a pump so we can go ahead and return this so on the home controller I'm gonna skip all this stuff and I will leave it here commented but we will just return a new home and we need to grab the dependency for this the project reference we add a reference to entity we go ahead and using entities let's say o equals our home all right and hopefully from here on you get the gist we then go ahead and add another new project this is a library again and this is going to be the API client ok so we go ahead and create this ok and this will get a project reference to entities as well because this is kind of what we want to return so from here we're no longer returning a what's it called an object we're returning home and from our client here let's go ahead delete this and primarily what I want to do is just copy the custom HTTP client and HTTP context middleware and to the API client we will go ahead and rename this to API client namespace and we'll do the same for the HTTP context middleware okay so the primary thing that we're gonna lose is a references to a asp net core specific things so from the web framework like the I HTTP context accessor so here is where we want to add some additional packages and make the registration process a little bit easier okay so let's go ahead and first into the custom HTTP client what we want to do is keep this the same but what we're actually returning now is a home and the get string async here we go ahead and we say basically that it's a JSON object and again you can write some kind of middleware for this but I'm gonna show you an easier way that actually exists now so let's go ahead and grab a Jason convert we will need Mutants soft for this or rather no nevermind I don't want to use Newton soft for this because there's an easier way that exists for this so I'm just gonna show you a package that you can use this recently came out and it's using a system dot txt at Jason oh the new more faster jason serialization just uses not so what we're gonna do is we're gonna go to manage nikki packages and what i want you to search for is system dot nets HTTP jason I think that's the primary thing but yeah so this package system doesn't it HTTP Jason go ahead and install this we can then go ahead and add the namespace to here so using system dotnet HTTP jason and what this will allow us to do is instead of getstring async we can get from Jason acing okay and here what we can do is we can specify a type that we want to convert it to so this becomes this basically takes away of all the complexity of putting all your basically your custom JSON parsing is stuff like that you can still take advantage of the say of the filters that system dot X dot Jason provides etc right this is just a super simple way to just parse Jason if you want to use some other format I guess then this won't help you but if you're using Jason I think this should be your go-to now this is a recent library that from this point was released like 13 days ago from preview so it's no longer in preview it should be available and it's gonna be included in a dotnet 5 so going forward I think this should be the preferred way right so we have this custom HTTP client assuming dependency injection works the same and blazer as a dozen xamarin as it doesn't hispanic or what you can do is you can put these helpers for registering this into the API clients as well so for stuff like if we close this if we're going to start up this add HTTP client with the middleware will remove this span here because I am NOT a particular not a big fan of it right we can go ahead grab the grab this we can create a new file let's call it API client register let's see yes this is going to be a static class where we're gonna use I publicize service collection we will need to get Microsoft extensions dependency injection abstractions package so add API client now obviously if you have some sort of a name for your API client give it appropriate names we will say this I service collection services right and then we're gonna return services on the end here we will need a few more packages to basically to be able to register our custom HTTP client so if we go into the startup so wherever we did the startup everything so the startup here just up 12 on the ADA httpclient we can take a look at the a but sembly so Microsoft what's called the extensions HTTP but let's copy the name let's go into manage no K no get packages browse look for this package rabbit once this is here what we should be able to do is add the HTTP client obviously we will have the middleware this method also needs to be static we can just go ahead and do the registration of the middleware as well hey I'm good for this because the middleware is primarily going to be used by the custom HTTP client we're gonna remove this from here and I'm gonna move the middleware appropriately for this custom client here hey where it's gonna be consumed so saving this so moving forward from here what I don't want to have is this custom HTTP client here what I also don't want to do is surface the HTTP context if you want you will need to grab the appropriate namespaces and stuff like that I'm gonna remove it from here for now because I don't think this this is the point of this this is more about pushing down just the idea of being able to grab packages as you need them okay still the same middleware Apple ice kind of thing is the primary thing is that this client part is particularly gonna get the objects they're defined in your API okay so here we basically add the registration what we want to do now is let's go and to startup let's actually rename this from custom HTTP clients to API client okay let's rename the files as well API client make sure API client register is renamed as well it is let's save everything go into startup I apologize if jumping around the files is a little bit confusing hopefully you get the gist services we go ahead and add API client and to be able to add it we need a dependency so add project reference to API slash Klein or doc line we are just going to the namespace okay so one thing here is for example where we do the API clients registration where we have this action here we name it API client or whatever you don't have to name it at all as I've said before is it because you're just gonna be injecting the type this function here so if you need to push down the function as well just take a look at the signature action HTTP client and just pass it as part of this middleware here so let's go ahead and say the client here clients pump big duration and import HTTP client and then we can basically pass the clients configuration in here grab this client and then based on who is the consumer of the client they can specify their own configuration what server are they point to what basically if the load balancer IP changes whatever it happens just point it to a different place we then go to asp net car start up and we just again we preserve the pattern and we supply the configuration in the same way that it is done for every for all the other ones so it seems yeah the error here is just different package versions so let's go ahead and bump the abstraction ones down to zero here make sure it stops crying maybe the HTTP context one will have the same problem it doesn't seem like it I had format this so we add an API client again as the configuration is more or less the same where the API client is actually now living in a separate project and what is the problem here I think this should have been resolved now so this is I think this is just Visual Studio lagging here so it will catch up in a bit or maybe if we encounter an error it's always something we can solve well yeah the gist is that you have a container and you just call a specific function to add a very common API so all you do now is let's go ahead into our home controller let's go ahead and call this shared because it's a shared client it's a client that can be shared between this consumer and any other any other consumer right so API client using client get home again it's kind of the same definition except here we return a task of home so because we inherit from the entity project in our API client and basically we get the reference to home so this is something we should be able to reference and then reference to entities yep okay so that's that hopefully it still works let's check the console it looks like everything's broken because of the packages and stuff like that and unable to build again as I said might need to restart it because don't know eyewash runs isn't support well if packages are changing etc let's go ahead and stop all this we will do a rebuild here and let's go through these errors one by one so first error is in the middle where Microsoft has been a core HTTP package is no working okay that's fine build again maybe that's gonna be result okay so home home yeah we want to change this to name a bunch errors as I was speeding through this yup home there's no contain yep it does not it's a name now I rebuild this again first one is what does the air say Microsoft dependency both of these are downgrading so the I guess the initial error that caused me to put a zero there on the end is kind of was that was a trick it was an illusion okay all this running or all is building let's go ahead and call botnet wash run on both of these hopefully they will start and we can do a test on the shared client and make sure that it's working all right so here we are let's get Google Chrome up let's grab our shared instance okay and here you can see our home right so yeah primary things to make sure that you're basically following with the HTTP climb that's kind of an overview let's go to the start up and kind of go we'll go over our options what you can do is you can add an HTTP client without a configuration this is just if it's a one-time thing you just need to say that you just need a quick HTTP client you don't need to create any abstractions you just want pulp write some simple simple little scenarios no name no nothing just add a HTTP client get your HTTP client factory service your HTTP client if you need a bit a little bit a set up if you're gonna be using is this HTTP client of in more than one places and you just need a little bit of consistency and the configuration that you supply here is gonna be triggered it's a it's gonna be a triggered every time you're gonna call create client on the HTTP client factory if you need different configurations for different clients so if you're gonna be calling I don't know like github button the Facebook API you might want to give them different names but at that point if you're gonna be needing like static typing support and binding you probably want to go towards the typed client where again you can define specific methods that you want to return and again if you are going into sort of a micro services architecture where your API is being consumed by more than one consumer right you might want to create a separate project from here on out right you can use the system net HTTP JSON package to basically handle help you do the suited handle the serialization and again you can foil all the middleware stuff in here and as well as the registration with a container and the API that you create here because you inject the statically typed API you get the basically the static typing and the actual methods that you've set up here right but this was just hopefully you can see the difference between kind of like a simple the middle simple and the hard part so your simple one is to get your H to be clean real quick call the middle one is you have kind of like a base configuration because you might need to be doing multiple different calls but still do the same in endpoint and you might need this kind of simplest configuration for different endpoints then you can basically set up different clients and then you get into the type client which is a little bit more work and a little bit more maintainability but again all that logic is segregated into that one client and you can just inject that one type straightaway without using the I HTTP factoring ok and then the most complicated because it's sort of living in its other project and it has to synchronize with all consumers is when you're basically abstracted as the HTTP client into its own project but you can still basically integrate it with the registration of the.net container which is the I service collection hey well this is essentially it don't forget about the system dotnet HTTP Jason package I think it's gonna be the preferred way moving forward if you enjoyed this video leave a like don't forget to subscribe for more you have any questions as always leave them in the comments section I also stream on Sundays and Wednesday so if you would like to be notified of that join my discord server you can also ask for help there if you don't like the youtube comment section hopefully I'll see you in my other episodes okay
Info
Channel: Raw Coding
Views: 18,251
Rating: undefined out of 5
Keywords: c#, .net core, asp.net core, tutorial, explained, overview, example, httpclient, ihttpclientfactory, System.Net.Http.Json, api client
Id: B_4X3ltGCbY
Channel Id: undefined
Length: 41min 44sec (2504 seconds)
Published: Wed Jun 03 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.