Issuing HEAD request with IRestClient in ServiceStack - c#

The context: I've built a REST service which handles 'Profile' objects. Each profile is required to have a unique name. One of the operations that clients will need to do for validation purposes is check to make sure that a profile with the given name does not already exist.
Rather than build a RPC-style 'ProfileExists' method, I would prefer to stay within REST design principles and issue a HEAD request to the Profile with the given name and then return the appropriate response code depending on whether the profile already exists or not (200, 404, respectively), no response body needed.
Following the conventions with the newer ServiceStack API, I've set up a method to accept Head requests and tested it successfully for both cases using Fiddler:
public object Head(GetProfile request)
{
ValidateRequest(request);
HttpStatusCode responseCode;
using (var scope = new UnitOfWorkScope())
{
responseCode = _profileService.ProfileExists(request.Name) ? HttpStatusCode.OK : HttpStatusCode.NotFound;
scope.Commit();
}
return new HttpResult { StatusCode = responseCode };
}
The trouble is on the client-side. Issuing the HEAD request through ServiceStack's IRestClient interface is proving difficult. While there are methods for Get, Post, Put, and Delete, there is no method for Head. From there I assumed I could use CustomMethod to specify the HEAD verb explicitly as a parameter:
public bool ProfileExists(string profileName)
{
try
{
var response = _restClient.CustomMethod<IHttpResult>(HttpMethods.Head, new GetProfile { Name = profileName });
return response.StatusCode == HttpStatusCode.OK;
}
catch (WebServiceException ex)
{
if (ex.StatusCode == 404)
return false;
}
// Return false for any other reason right now.
return false;
}
However, the underlying implementation (ServiceClientBase) throws an exception when validating the HttpVerb parameter:
if (HttpMethods.AllVerbs.Contains(httpVerb.ToUpper()))
throw new NotSupportedException("Unknown HTTP Method is not supported: " + httpVerb);
The set HttpMethods.AllVerbs contains all of the usual verbs for RFC 2616 and more. Unless this behavior is a bug, throwing an exception for any of the known HTTP verbs suggests that the author's intent for CustomMethod did not include being able to issue requests for a known HTTP verb.
Which leads me to my question: How do I issue a HEAD request on the client side in ServiceStack?

This was a bug:
if (HttpMethods.AllVerbs.Contains(httpVerb.ToUpper()))
throw new NotSupportedException("Unknown HTTP Method is not supported: " + httpVerb);
That I've just fixed in this commit. This fix will be available on the next release of ServiceStack (v3.9.33+) due this weekend.

Related

JsonServiceClient not respecting RedirectHttpHandler response

We have a global handler setup for catching a specific type of exception. It is possibly thrown from multiple service endpoints using a base service implementation. We bind the error handlers and try redirect using a RedirectHttpHandler:
ServiceExceptionHandlers.Add(HandledErrorLogging);
...
private object HandledErrorLogging(IRequest httpreq, object request, Exception ex)
{
if (ex is NoActiveSubscriptionException)
{
return new RedirectHttpHandler
{
RelativeUrl = "/account?error=",
StatusCode = HttpStatusCode.TemporaryRedirect
};
}
}
We are using JsonServiceClient to query these endpoints.
The JsonServiceClient is not respecting the RedirectHttpHandler redirect. When we connect jsonclient.responsefilter(r), r.redirectedicted is false:
let client = new JsonServiceClient(process.env.REACT_APP_API_BASE_URL);
client.setBearerToken(cookie.load("XSRF-TOKEN"));
JsonServiceClient.globalResponseFilter = function(e)
{
console.log("e.redirect:", e.redirected));
};
return client;
}
What is the best way to cause a redirect using the ServiceExceptionHandlers and the JsonServiceClient ?
RedirectHttpHandler is an IHttpAsyncHandler, it can only be used at the start of the request pipeline in RawHttpHandlers which is used to tell ServiceStack which HttpHandler it should use to handle the request.
ServiceExceptionHandlers is used to override handling of an Exception which you can override to return a different error Response DTO.
If nothing has been written to the Response you can return a redirect response with HttpResult.Redirect().

Best practice to handle content in the response after a Web API call results in an exception

I'm working on a Core 3.1 Web API and an MVC application that uses it. In the MVC app I have UserRepo set up containing methods that send requests to the API:
public class UserRepo : IUserRepo
{
private readonly IHttpClientFactory _clientFactory;
public UserRepo(IHttpClientFactory httpClientFactory)
{
_clientFactory = httpClientFactory;
}
public async Task<User> GetById(int Id)
{
// same code structure as Update ...
}
public async Task<User> Update(User user)
{
HttpClient client = _clientFactory.CreateClient("NamedClient");
try
{
HttpResponseMessage response = await client.PutAsync($"api/Users/{user.Id}", ContentEncoder.Encode(user));
return await response.Content.ReadFromJsonAsync<User>();
}
catch (Exception ex)
{
throw;
}
}
public async Task<User> Insert(User user)
{
// same code structure as Update ...
}
}
The Update method never throws errors like 400, 404, etc, that come back from the API, resulting in silent errors. I found that to cause exceptions I need to call response.EnsureSuccessStatusCode();, which worked.
However, the exception doesn't contain what I need to find out what went wrong with the API call. If a 400 error occurs, an exception will be thrown saying that 400 error occurred, but not why it occurred. The why is returned to the response variable and it may look something like this due to validation I have implemented:
{
"errors": {
"FirstName": [
"The FirstName field is required."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|502d647b-4c7425oa321c8c7b."
}
Is there a widely used way to handle the response that comes back after an error is produced in the API? I want to know why a 400 error occurred so I know what to fix. I just don't know what is the "right" way to handle these response messages.
One idea I had was to catch the exception and log it along with the response text every time before throwing it. Then when my app crashes I can go to the logs and read the message returned. The Update method would look like this:
public async Task<User> Update(User user)
{
HttpClient client = _clientFactory.CreateClient("NamedClient");
HttpResponseMessage response = await client.PutAsync($"api/Users/{user.Id}", ContentEncoder.Encode(user));
try
{
response.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
string errorMessage = await response.Content.ReadAsStringAsync()
_logger.LogError(ex, errorMessage);
throw;
}
return await response.Content.ReadFromJsonAsync<User>();
}
Another thought that came would be maybe it's possible to add the message to the exception itself and see it when it's thrown? Would it make sense to add the message as an inner exception?
Is there a widely used way to handle the response that comes back after an error is produced in the API? I want to know why a 400 error occurred so I know what to fix. I just don't know what is the "right" way to handle these response messages.
Generally, exception details are only logged, and not returned. This is because details may include personally identifiable information or technical details that could reveal potential security vulnerabilities. There is an error details RFC that is becoming more common, but even that should not have details like PII or a stack trace.
In the case of one API (the MVC endpoint) calling another API (the actual API), the MVC endpoint should return a code in the 5xx range. Either 500 or 502 would be acceptable here. All such errors should be logged on the server side along with their details.
Note that the default behavior is to return 500 if an exception is propagated, so keeping the throw; is all you really need to do. However, it's normal to do error logging in the "pipeline", e.g., middleware for ASP.NET Core or something like a globally-installed action filter for ASP.NET MVC. This is to ensure all errors are logged while avoiding repetition.
EnsureSuccessStatusCode throws an HttpRequestException if the StatusCode is different than 2xx.
In order to gain the most information from the response, you have to retrieve it manually.
The general flow could be described in the following way:
Issue the request inside a try-catch block.
If there was no exception then examine the response's statusCode.
If it is different than the expected one(s) then try to read the response's body
And log everything.
Step #1
HttpResponseMessage response = null;
try
{
response = await httpClient.PutAsync(...);
}
catch (InvalidOperationException ioEx)
{
//The request message was already sent by the HttpClient instance, but failed due to some protocol violation
HttpClient.CancelPendingRequests();
//TODO: logging
}
catch (TaskCanceledException tcEX)
{
//The request was not completed due to either it's timed out or cancelled
if(!tcEX.CancellationToken.IsCancellationRequested)
HttpClient.CancelPendingRequests();
//TODO: logging
}
catch (HttpRequestException hrEx)
{
//The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation.
//TODO: logging
}
Step #2
HttpStatusCodes[] validResponseCodes = new [] {
HttpStatusCode.OK,
HttpStatusCode.Created,
HttpStatusCode.NoContent,
};
if(!validResponseCodes.Contains(response?.StatusCode))
{
//Step #3
}
Step #3
string errorResponse = await response.Content.ReadAsStringAsync();
//Try to parse it if you know the structure of the returned json/xml/whatever

Flurl Client - Is it possible to access the headers from a failed request?

I am using Flurl Client to call a restful API with a post data. There is a validation performed on the server on the data I submit and it returns back a header containing an error message for the user.
As the request requirement doesn't satisfy server marks the request as 400 BadRequest. In the below code on line cli.Request(uri).PostJsonAsync(data) it throws the FlurlHttpException with appropriate status code.
Now, as there is a problem with the input data by the user I want to report the user back with the error message which I receive from the server in the header. However, I am unable to access the response headers as the request has failed.
Is there any other way to access the response headers from a failed request using Flurl?
try
{
using (var cli = new FlurlClient(baseUrl))
{
var httpResponse = await cli.Request(uri).PostJsonAsync(data);
var errorMessage = httpResponse.GetHeaderValue("errorMessage");
}
}
catch (FlurlHttpException ex)
{
}
Using an event handler works, but I think those are better for cross-cutting concerns like logging that you don't want cluttering the main flow of your app. You basically want to allow and/or handle 400 responses as part of that main flow. You can do that more directly with AllowHtttpStatus, which can be set on the client:
cli.AllowHtttpStatus(HttpStatusCode.BadRequest);
or the request:
var httpResponse = await cli
.Request(uri)
.AllowHttpStatus(HttpStatusCode.BadRequest)
.PostJsonAsync(data);
Either way, the call will not throw on a 400.
Another way to do this, and one I'd recommend if your app logic takes a completely different path on an error condition than on a success condition, is to keep your try/catch in place and use the Response property of the exception to handle the error condition:
try
{
await cli.Request(uri).PostJsonAsync(data);
// handle success condition
}
catch (FlurlHttpException ex) when (ex.Response?.StatusCode == 400)
{
var errorMessage = ex.Response.GetHeaderValue("errorMessage");
// handle error condition
}
As a side note, there are some significant changes coming in 3.0 that you should be aware of as they touch on some of these areas directly:
https://github.com/tmenier/Flurl/issues/354
https://github.com/tmenier/Flurl/issues/488
I am configuring the Error Event Handler to report any error. As a result, the code doesn't jump to the exception block it asynchronously fires the event handler, and the rest of my subsequent code executes OK with an appropriate httpResponseMessage, StatusCode, headers everything.
...
FlurlHttp.Configure(settings => settings.OnErrorAsync = HandleFlurlErrorAsync);
using (var cli = new FlurlClient(baseUrl))
{
var httpResponse = await cli.Request(uri).PostJsonAsync(data);
var errorMessage = httpResponse.GetHeaderValue("errorMessage");
}
...
private async Task HandleFlurlErrorAsync(HttpCall call)
{
//Log your exception here
call.ExceptionHandled = true;
}

.NET Core Web API: return a custom HTTP status code with Content Type application/problem+json

ControllerBase contains methods such as Conflict() that return a ConflictResult object (representing an HTTP 409 response) that is derived from StatusCodeResult. The resulting response body has content type application/problem+json and looks like this:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.8",
"title": "Conflict",
"status": 409,
"traceId": "0HLO99QHFC9QI:00000001"
}
There is no in-built method/class for an HTTP 410 response, so I made one:
[DefaultStatusCode(410)]
public class GoneResult : StatusCodeResult
{
public GoneResult() : base(410)
{}
}
...
public static class ControllerBaseExtensions
{
public static GoneResult Gone(this ControllerBase controllerBase) // this doesn't give all the problem+JSON attributes
{
return new GoneResult();
}
}
However, this gives
{
"type": "about:blank",
"status": 410
}
i.e., the type value is different and the title and traceId fields are missing.
I'd also like to create a custom class for an HTTP 500 response that includes a message field with the error message. I've tried returning StatusCode(StatusCodes.Status500InternalServerError), which gives me the same minimal application/problem+json response as my Gone() method; I've also tried returning StatusCode(StatusCodes.Status500InternalServerError, message), which gives me my error message but formats the response as text/plain.
The code that generates the ProblemDetails response isn't aware of the 410 status-code, so it doesn't have an associated Link and Title property to use when building the response object. To add this awareness, configure ApiBehaviorOptions in ConfigureServices, like this:
services.Configure<ApiBehaviorOptions>(options =>
{
options.ClientErrorMapping[410] = new ClientErrorData
{
Title = "Gone",
Link = "https://tools.ietf.org/html/rfc7231#section-6.5.9"
};
});
ClientErrorMapping is a dictionary of int (status-code) to ClientErrorData. Note that the value I've used for Link above does point to the correct section of the RFC.
Simply, you have to actually return a ProblemDetails response body. I'd have to dig through the code to be sure, but I think ASP.NET Core is doing this via middleware only for particular results. They say anything in the 4xx range, but I think that's actually just confined to the built-in result types that return status codes in that range, not any result with a 4xx status code. Again, this is conjecture, as I haven't looked at exactly what they're doing, although it's not happening as part of the actual result class.
For your purposes, there's a few different ways you can handle this. You can write your own middleware to catch outbound responses and rewrite them. You can use an custom exception handler. You could simply inherit from ObjectResult instead, and then just create a ProblemDetails instance yourself and drop that into the base. You could even just return ProblemDetails directly from your action (though, that's obviously the least optimal way).

How can I use existingResponse="Auto" successfully?

So I am returning detailed 400 error responses from my MVC web app. Setting existingResponse="PassThrough" works, but that's not what I want. I don't want to expose all failures, I only want to expose them when I have custom responses.
Auto, is set by default, but I deliberately set it. However, the documentation says "SetStatus" flag must be set, but I have no idea how to do such a thing. I wrote the following four controller methods in order to test it, and only BadRequestD works. The others set the status code and the status just fine, but the body content is "Bad Request".
public ActionResult BadRequestA()
{
Response.StatusCode = 400;
return Content("weeeeee");
}
public ActionResult BadRequestB()
{
Response.Status = "400 U DUN MESSED UP";
return Content("weeeeee");
}
public ActionResult BadRequestC()
{
Response.Status = "400 U DUN MESSED UP";
Response.StatusCode = 400;
return Content("weeeeee");
}
public ActionResult BadRequestD()
{
Response.StatusCode = 400;
Response.TrySkipIisCustomErrors = true;
return Content("weeeeee");
}
However, the documentation says "SetStatus" flag must be set, but I have no idea how to do such a thing
It's actually talking about the fTrySkipCustomErrors flag/argument to the IHttpResponse::SetStatus method in the IIS C++ SDK (see note I added to bottom of documentation here). But in ASP.NET the flag is exposed as Response.TrySkipIisCustomErrors. So according to:
http://www.iis.net/configreference/system.webserver/httperrors
Auto = Leaves the response untouched only if the SetStatus flag is set
I would expect to see IIS replace the response with its own html error page content (you can configure what that content is) by default unless you set:
Response.TrySkipIisCustomErrors = true;
Which is what you're seeing.
Additional related info, in MVC5 it seems to act as if that flag is true even if it's false for uncaught exceptions which I don't see in WebForms. As a workaround in Global.asax I'm:
protected void Application_Error()
{
var error = Server.GetLastError();
Server.ClearError();
//code to log error here
var httpException = error as HttpException;
Response.StatusCode = httpException != null ? httpException.GetHttpCode() : (int)HttpStatusCode.InternalServerError;
}
If you need to have custom responses with 4xx http statuses and still want to use Custom Error Pages here's what you should do:
set existingResponse="Auto" in web.config;
set TrySkipIisCustomErrors = true in your action (one that returns 4xx status and a content);
clear server error in global.asax (in Application_Error() - Server.ClearError()) and re-set the status code (Reponse.StatusCode = ((HttpException)Server.GetLastError()).GetHttpCode())
It's weird that IIS team didn't implement existingResponse attribute for specific status codes, so it's impossible to use existingResponse="PassThrough" just for one (or few) codes.

Categories

Resources