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().
Related
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 an Update method:
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)
{
if ((int)response.StatusCode == StatusCodes.Status409Conflict)
{
throw;
}
}
return await response.Content.ReadFromJsonAsync<User>();
}
The repo is injected into a service, and the service is injected into a controller which is where I'd like to handle the error.
The Update method is incomplete because I am trying to figure out how handle a 409 error which I return from API if the rowversion value was outdated. When response.EnsureSuccessStatusCode(); is called, an exception is thrown if it wasn't a success code. I imagined I could just have it bubble-up to the front-end and handle it in the controller action, but the exception object doesn't contain anything specific enough to identify that it's a 409 error:
So if this bubbles up to the controller action, best I could to is try to parse out the status code from the message, which seems like a bad idea.
I can find examples of people returning 409 codes from their Web APIs, but not how they would be handled in an MVC app when logic is separated into different classes instead of being all in one action.
How could I handle this? Do I create a custom exception and throw that? Maybe add additional data to the exception with ex.Data.Add() and read it in the action? Would that be a bad idea?
Thanks to suggestions from #Peter Csala, #Craig H, and #Filipe, this is the solution I settled on.
Method that calls the API:
public async Task<User> Update(User user)
{
HttpClient client = _clientFactory.CreateClient("namedClient");
HttpResponseMessage response = await client.PutAsync($"api/Users/{user.Id}", ContentEncoder.Encode(user));
await HttpResponseValidator.ValidateStatusCode(response);
return await response.Content.ReadFromJsonAsync<User>();
}
A static method I can reuse that will produce an exception during a concurrency error:
public static class HttpResponseValidator
{
public static async Task ValidateStatusCode(HttpResponseMessage response)
{
if ((int)response.StatusCode < 200 || (int)response.StatusCode > 299)
{
string errorResponse = await response.Content.ReadAsStringAsync();
if (response.StatusCode == HttpStatusCode.Conflict)
{
DBConcurrencyException ex = new DBConcurrencyException(errorResponse);
throw ex;
}
}
}
}
The exception bubbles up all the way back to the action that called the method calling the API. I catch it in the action and handle it after logging the error.
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;
}
I've consumed a WSDL as and have succesfully called web-service methods.The request has an Authorization header that can only be added at the point the request is made:
public static NumberCaptureClient Connect()
{
var remoteAddress = new EndpointAddress("https://website.com:8443/webservice/WebServiceNumberCapture");
using (var NumberCaptureClient = new NumberCaptureClient(new BasicHttpBinding(BasicHttpSecurityMode.Transport), remoteAddress))
{
NumberCapture.ClientCredentials.UserName.UserName = "test";
NumberCapture.ClientCredentials.UserName.Password = "test";
try
{
using (OperationContextScope scope = new OperationContextScope(NumberCaptureClient.InnerChannel))
{
var httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers[HttpRequestHeader.Authorization] = "Basic " +
Convert.ToBase64String(Encoding.ASCII.GetBytes(NumberCaptureClient.ClientCredentials.UserName.UserName + ":" + NumberCaptureClient.ClientCredentials.UserName.Password));
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
}
}
catch (Exception error)
{
MessageBox.Show("Error");
return null;
}
return NumberCaptureClient;
}
}
As you can see I'm in need of returning an instance of the proxy client (the client has hundereds of methods that all need the header) but need it so the headers are always sent, with the 'using' clause this isn't possible as the scope is lost outside of it.
Is there a way to permanantly add the headers so they are sent with every request to the webservice?
This is a WCF proxy, right? Generally speaking, you should remove the using from your Connect method. If the method is used to get a prepared service proxy, then it makes no sense to dispose it as part of the method that creates it.
Instead, the method/code that uses the Connect method should be responsible of using it:
using(var proxy = theClass.Connect())
{
// call service using proxy here
// process response here, if you may need to call the service again
// as part of processing
}
// process response here if you don't need to call the service again
There is a catch however, since for WCF proxies, the Dispose method internally calls the Close method, which in turn can throw exceptions. For this reason, Microsoft has a recommendation for how to handle cleaning up of WCF proxies. See here.
I currently have an ServiceStack Service that does nothing but relay requests to an internal ServiceStack service.
The relay service is setup something like this (code made brief as an example):
public class RelayService : Service
{
public SomeDTO Get(FetchSomething request)
{
try
{
return new JsonServiceClient(settings.OtherServiceURL).Get(request);
}
catch (Exception)
{
throw;
}
}
public void Put(PersistSomething request)
{
try
{
new JsonServiceClient(settings.OtherServiceURL).Put(request);
}
catch (Exception)
{
throw;
}
}
}
My questions are:
Is it best practice to new up a JsonServiceClient for each request? Or should I inject an instance?
Since the relay service can contain variations on Put/Get that return DTO's or void, is there a cleaner way to relay all calls to the backing ServiceStack service instead of having to duplicate each method in the relay service? Is it possible to do this all in one or a few methods using Any()?
Thanks for any input.
This previous answer for an example of a generic reverse proxy in ServiceStack.
The simplest and most generic approach in ServiceStack would be to register a RawHttpHandler that just forwards the Request to the downstream server and writes the Response to the Output Stream, e.g:
RawHttpHandlers.Add(_ => new CustomActionHandler((req, res) =>
{
var bytes = req.InputStream.ReadFully();
var proxyUrl = settings.OtherServiceURL.CombineWith(req.RawUrl);
var responseBytes = proxyUrl.SendBytesToUrl(method: req.Verb,
requestBody: bytes,
accept:MimeTypes.Json,
contentType: req.ContentType,
responseFilter: webRes =>
{
res.StatusCode = (int)webRes.StatusCode;
res.StatusDescription = webRes.StatusDescription;
res.ContentType = webRes.ContentType;
});
res.OutputStream.Write(responseBytes, 0, responseBytes.Length);
}));
In order to access the RequestStream you'll also want to tell ServiceStack to not inspect the FormData when creating the Request (as this forces reading the request body), which you can skip with:
SetConfig(new HostConfig {
SkipFormDataInCreatingRequest = true
});
Another approach would be to configure something like IIS Application Request Routing and URL Rewriting to use as a reverse proxy.
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.