One strange feature of the Web.Contents() function in Power Query and Power BI is that it doesn’t respond in a consistent way to the standard error handling techniques used in M. I don’t know if this is a bug or a feature, but it’s certainly something I’ve run into a few times so I thought I would share a description of the problem and a way of working around it.
First of all, what’s the problem? Imagine that you wanted to import a list of training courses from that fine UK Microsoft BI and SQL Server training company Technitrain into Power Query or Power BI. You could do so using an M query that uses the Web.Contents() function to get the course RSS feed, like so:
let Source = Web.Contents("http://technitrain.com/feed/") in Source
But what happens if you get the URL wrong, or there’s some other problem with the site? For example, the following URL will give a 404 – Not Found error because the page doesn’t exist:
http://technitrain.com/blahblah
If you use it in an M query, like so:
let Source = Web.Contents("http://technitrain.com/blahblah") in Source
Unsurprisingly you get an error:
DataSource.Error: Web.Contents failed to get contents from ‘http://technitrain.com/blahblah’ (404): Not Found
The real issue is, though, when you attempt to handle this error with a try/otherwise statement like so:
let Source = try Web.Contents("http://technitrain.com/blahblah") otherwise "Error!" in Source
…it doesn’t work and you get the same error! What’s strange is that in some cases a try/otherwise block in more complex code will work, so for example in:
let Source = try Xml.Tables( Web.Contents("http://technitrain.com/blahblah") ) otherwise "Error!" in Source
… the error does get caught:
This thread on the Power Query forum suggests it’s something to do with lazy evaluation, but I haven’t been able to determine the situations when it does work and when it doesn’t.
Instead, it is possible to handle specific HTTP error codes using the ManualStatusHandling option in Web.Contents():
let Source = Web.Contents( "http://technitrain.com/blahblah", [ManualStatusHandling={404}]) in Source
The ManualStatusHandling option takes a list of numeric HTTP error codes, and if you run the above example you’ll see that the query no longer returns an error.
The next problem is, then, how do you know whether the request worked or not? It turns out that you can find out by looking at the metadata associated with the Source variable (for some background on getting metadata values see this post). So, for example, using Value.Metadata() on the Source variable like so:
let Source = Web.Contents( "http://technitrain.com/blahblah", [ManualStatusHandling={404}]), GetMetadata = Value.Metadata(Source) in GetMetadata
Returns a record which, among other things, contains the HTTP response code:
Therefore you can use something like the following pattern to trap 404 errors:
let Source = Web.Contents( "http://technitrain.com/blahblah", [ManualStatusHandling={404}]), GetMetadata = Value.Metadata(Source), GetResponseStatus = GetMetadata[Response.Status], Output = if GetResponseStatus=404 then "Error!" else Source in Output