There's a lot of exciting stuff going on lately when it comes to C# and the web, mostly thanks to the great work of the Mono team in bringing mono to WebAssembly and a few pioneers in the space, like Frank Krueger
Woe, PlatformNotSupportedException
But it's the web! How can I not do http on the web? The built-in Blazor sample is doing it, so why can't I? Actually, it makes plenty of sense after thinking about it, and the hint was to be found in the surprisingly-already-quite-detailed tutorials at learn-blazor.com:
The browser’s firewall would not allow Blazor to directly do network communication. All network traffic has to go through the browser. Therefore, Blazor’s C# implementation has to pass our HTTP request to Blazor’s JavaScript-part [...] Blazor contains an implementation of HttpMessageHandler that hands over HTTP requests to JavaScript.
Of course! Whilst running C# client-side in the browser is basically magic, the realities of the sandbox persist - we need to work within the platform's restrictions. The magic happens in Blazor's BrowserHttpMessageHandler (also a good example of the Blazor's javascript interop via RegisteredFunction.Invoke
), which is what we need to get Refit to use.
Luckily for us, Refit allows you to provide your own HttpClient
for requests - highly recommended for tasks like (re)authentication, diagnostics and cleanly dealing with badly behaved apis). We can use this feature to switch to using BrowserHttpMessageHandler
very easily:
After switching over the handler and rebuilding, we can see in the network tab of the browser that requests are being made!
Note that if you do need to do additional customisation of requests you should be able use System.Net.Http.DelegatingHandler
to do your bits beforehand - but I haven't tested this yet.
Not out of the woods yet?
Unfortunately, the rollercoaster of emotion was not over for me yet. The request was sent, but my data was not displayed... so back to the console to see what was up.
Blazor currently ships with SimpleJson built in - among the motivations for this choice is size, but others are also listed - and it is used in the sample project. Refit uses Json.NET for deserialization, which appears to not quite work yet. I opened an issue here - it has been closed in Blazor but referred to the Mono team as a WASM-y issue. As an aside, I'm sure that Json.NET working in Ooui, but that project is somewhere between ConsoleApp13 and ConsoleApp52 and I managed to break them all testing various bits, so I'm forced to consider that I just dreamt about it working 😴.
An interim workaround - HttpResponseMessage
This is the 'most of' part referenced in the title. Refit does give us a mechanism that we can use as an escape hatch for handling deserialization ourselves (not to be confused with 'first class' support for custom deserialization - which has been proposed a few times in the past). By specifying the return type of your interface methods as HttpResponseMessage
, Refit hands over the raw response - letting us to do things like inspect headers and deserialize the content in whatever manner we need.[*] Unfortunately, falling back to HttpResponseMessage
is a significant reduction in the declarativeness of your API definition - one of the nicest benefits of Refit. Still, if you want to take that route in the short term, you can alter your interface definitions in this manner:
With these changes, deserialization is performed outside of Refit, using the already-Blazor-friendly SimpleJson library.
And that's it! We have Refit mostly working with Blazor. Clearly, the deserialization workaround is a pain, but I'm confident that whatever issue is impeding Json.NET use will be resolved soon - many .NET libraries have taken it as a dependency and without it they will not be usable. In the meantime, if nothing else, this mobile/desktop developer has learned that he clearly needs to learn some CSS. At least I'll be able to use it with Xamarin Forms too...
[*] In the olden days, returning HttpResponseMessage
used to be a reasonable fallback for scenarios requiring acccess to non-body response information like headers, but Refit recently added the ApiResponse<T>
class which does exactly what you'd expect and gives access to both the response info and the deserialized content - a good, clean approach.