Is it possible to configure when Refit should attempt deserialization? - c#

Is it possible to tell Refit not to try and serialise the message body for certain HttpStatus Codes?
I'm integrating with an API that (when authentication fails) returns a HTML body instead of JSON alongside a 203 status code instead of a 401/403 status code. This means Refit will attempt to serialise the body and throw a SerializationException instead of an ApiException.
Is it possible to handle this instance? I.e. tell Refit to only treat 200 as successful or inject a step in prior to deserialization that has access to the status code?

You can use a DelegatingHandler.
public class JamiesHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
// Do stuff with the response here...
return response;
}
}
// Then
var httpClient = new HttpClient(new JamiesHandler()){ BaseAddress = ""};
var contract = Refit.RestService.For<SomeContract>(httpClient);
Here is an example of this being done with Refit for logging.

Related

Azure Function Return Deprecated API Header

I have an Azure Function that sits behind a proxy. If an update occurs to the objects that get returned we want to deprecate the Function after a period of time. I'm trying to create a response with the expected content from an HTTP Header by using what was provided in this solution.
Warning: 299 - "Deprecated API"
I try to append the Azure Function like so:
[FunctionName("MyAPI")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function,
"post", Route = null)]
HttpRequestMessage req,
TraceWriter log)
{
object response = await someService.Get();
if (settingsService.IsDeprecated)
{
var httpresponse = req.CreateResponse(HttpStatusCode.OK, response, "application/json");
httpresponse.Content.Headers.Add("Warning", "299 - Deprecated API");
return httpresponse;
}
return req.CreateResponse(HttpStatusCode.OK, response, "application/json");
}
I get the Exception
Exception while executing function: MyAPI -> Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.
How do I append the "Deprecated" status warning in my API Http Response?
Change your line to
httpresponse.Headers.Add("Warning", "299 - \"Deprecated API\"");
The quotes seem to be important there to adhere to the format requirement.

Web API 2.2, Is there an easy way to update the HttpRequestMessage's QueryString in a DelegatingHandler?

I've created a custom MessageHandler as such:
public class WebAPICustomMessageHandler : DelegatingHandler {
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
//Parse QueryString
NameValueCollection queryString = HttpUtility.ParseQueryString(request.RequestUri.Query);
if (queryString != null) {
//Find my token
String token = queryString.Get("qsVariable");
if (!String.IsNullOrWhiteSpace(token)) {
//Remove token so it doesn't impact future handlers / controllers / etc.
queryString.Remove("qsVariable");
request.RequestUri.Query = queryString.ToString(); //How can we modify the querystring? apparently it's readonly?
//Append token as custom header to the request
request.Headers.Add("token", new String[] { token });
}
}
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
return response;
}
}
It appears that I can't directly change the QueryString, which is a bit odd as I thought that the entire point of custom message handlers in the Web API pipeline was to allow this exact sort of thing. The ability issue pre and post operations on request/response, including things like data scrubbing / injection etc.
I already know I can do what I want very easily utilizing OWIN (as I've already done it), but now I'm trying to do this without OWIN. Am I going to have to create an entirely new HttpRequestMessage in order to just change the QueryString? From the looks of it, I'll have to build a new Uri, then construct the HttpRequestMessage, then copy over each and every other piece from the original.
Is that the only way to do this? or is there a better way that I'm just not aware of?
Please note, that other routines later in the pipeline are setup to use a token found in the header of the request, but setting the header is not possible if the request came from a submission to an iframe, which is where the process above comes into place. The token is added to the querystring, then converted to a header to prevent changes to the rest of the pipeline.
You're correct, the query string is read-only. Just use a standard redirect with the substituted query string values.
public class WebAPICustomMessageHandler : DelegatingHandler {
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
var token = query["qsVariable"];
// just 'short-circuit' if our token was not found...
if (token == null)
return await base.SendAsync(request, cancellationToken);
token = "newValue"; // modify your value here...
query["token"] = token;
// redirect with new query string...
var response = request.CreateResponse(HttpStatusCode.Redirect);
var uri = request.RequestUri;
var ub = new UriBuilder(uri.Scheme,
uri.Host,
uri.Port,
uri.AbsolutePath);
ub.Query = query.ToString();
response.Headers.Location = ub.Uri;
return response;
}
}
For interest sake, it is possible to modify the query string even though it is read-only but I would avoid this practice.

How do I get Web API 2 to return JSON and no other content type?

In the latest Web API 2, how do I configure it so that it will only return a reply if the Accept header is application/json? This API will only support json, if any other accept header is sent then an error must be thrown. There will be no xml and even no html interface.
If the client asks for xml or html or anything, we need to throw an error to let them know they used the wrong accept type. We must not mask this problem by replying with the correct json when they have requested a type that is not actually supported.
var request = (HttpWebRequest)WebRequest.Create(url);
request.Accept = "application/json";
var response = request.GetResponse();
And the json result is returned successfully. But if there is any other Accept then an error is returned
var request = (HttpWebRequest)WebRequest.Create(url);
request.Accept = "application/xml"; // or text/html or text/plain or anything
var response = request.GetResponse();
Returns HTTP 501 Not Implemented or similar http error code.
This question is not a duplicate of How do I get ASP.NET Web API to return JSON instead of XML using Chrome? - that question asks how to also return json. My question is how to only return json, and only if the client asks for json. If the client asks for any other type like xml or html, then an error is returned.
This page shows how to access content negotiation directly. You could conceivably instead pass some filtered subset of this.Configuration.Formatters containing only the desired formatters to IContentNegotiator.negotiate, like so:
ContentNegotiationResult result = negotiator.Negotiate(
typeof(Product), this.Request, this.Configuration.Formatters.Where(/* some expression that excludes all but the json formatter */);
This looks quite clumsy and would be a lot of dull boilerplate, so Javad_Amiry's answer is probably better, but this is another option that might be useful in specific cases.
You can clear all formatters except JSON:
configuration.Formatters.Clear();
configuration.Formatters.Add(new JsonMediaTypeFormatter());
Or you can change the default Web API’s content negotiation mechanism:
public class JsonContentNegotiator : IContentNegotiator
{
private readonly JsonMediaTypeFormatter _jsonFormatter;
public JsonContentNegotiator(JsonMediaTypeFormatter formatter)
{
_jsonFormatter = formatter;
}
public ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
var result = new ContentNegotiationResult(_jsonFormatter, new MediaTypeHeaderValue("application/json"));
return result;
}
}
// in app_start:
var jsonFormatter = new JsonMediaTypeFormatter();
config.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator(jsonFormatter));
See the article.
UPDATE:
Well, if you want to return a HTTP error on non-json request, you can do it by implementing a custom IHttpModule for checking header. But, for self-host apps it won't work. So, it's better to use extend a custom DelegatingHandler. For example, you can use this one:
public class FilterJsonHeaderHandler : DelegatingHandler {
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken){
if (request.Headers.Accept.All(a => a.MediaType == "application/json")){
// if we have only application/json, so the pipeline continues
return base.SendAsync(request, cancellationToken);
}
// otherwise, do whatever you want:
var response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
var completionSource = new TaskCompletionSource<HttpResponseMessage>();
completionSource.SetResult(response);
return completionSource.Task;
}
}
and register it in app_start:
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.MessageHandlers.Add(new FilterJsonHeaderHandler());
// your other settings...
}
}
NOTE: the code is not tested. Please let me know if there is any error.

Get raw post request in an ApiController

I'm trying to implement a Paypal Instant Payment Notification (IPN)
The protocol is
PayPal HTTP POSTs your listener an IPN message that notifies you of an event.
Your listener returns an empty HTTP 200 response to PayPal.
Your listener HTTP POSTs the complete, unaltered message back to
PayPal; the message must contain the same fields (in the same order)
as the original message and be encoded in the same way as the
original message.
PayPal sends a single word back - either VERIFIED (if the message
matches the original) or INVALID (if the message does not match the
original).
So far I have
[Route("IPN")]
[HttpPost]
public void IPN(PaypalIPNBindingModel model)
{
if (!ModelState.IsValid)
{
// if you want to use the PayPal sandbox change this from false to true
string response = GetPayPalResponse(model, true);
if (response == "VERIFIED")
{
}
}
}
string GetPayPalResponse(PaypalIPNBindingModel model, bool useSandbox)
{
string responseState = "INVALID";
// Parse the variables
// Choose whether to use sandbox or live environment
string paypalUrl = useSandbox ? "https://www.sandbox.paypal.com/"
: "https://www.paypal.com/cgi-bin/webscr";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(paypalUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
//STEP 2 in the paypal protocol
//Send HTTP CODE 200
HttpResponseMessage response = client.PostAsync("cgi-bin/webscr", "").Result;
if (response.IsSuccessStatusCode)
{
//STEP 3
//Send the paypal request back with _notify-validate
model.cmd = "_notify-validate";
response = client.PostAsync("cgi-bin/webscr", THE RAW PAYPAL REQUEST in THE SAME ORDER ).Result;
if(response.IsSuccessStatusCode)
{
responseState = response.Content.ReadAsStringAsync().Result;
}
}
}
return responseState;
}
My problem is I can't figure out how to send the original request to Paypal with the parameters in the same order.
I could build a HttpContent with my PaypalIPNBindingModel but I can't guarantee the order.
Is there any way I could achieve this?
Thank you
I believe you should not use parameter binding and just read the raw request yourself. Subsequently, you can deserialize into the model yourself. Alternatively, if you want to leverage Web API's model binding and at the same time, access the raw request body, here is one way I could think of.
When Web API binds the request body into the parameter, the request body stream is emptied. Subsequently, you cannot read it again.
[HttpPost]
public async Task IPN(PaypalIPNBindingModel model)
{
var body = await Request.Content.ReadAsStringAsync(); // body will be "".
}
So, you have to read the body before model binding runs in Web API pipeline. If you create a message handler, you can ready the body there and store it in the properties dictionary of the request object.
public class MyHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Content != null)
{
string body = await request.Content.ReadAsStringAsync();
request.Properties["body"] = body;
}
return await base.SendAsync(request, cancellationToken);
}
}
Then, from controller you can retrieve the body string, like this. At this point, you have the raw request body as well as the parameter-bound model.
[HttpPost]
public void IPN(PaypalIPNBindingModel model)
{
var body = (string)(Request.Properties["body"]);
}

Changing the response object from OWIN Middleware

My OWIN middleware is like this. (Framework is ASP.NET Web API).
public class MyMiddleware : OwinMiddleware
{
public MyMiddleware(OwinMiddleware next) : base(next) { }
public override async Task Invoke(OwinRequest request, OwinResponse response)
{
var header = request.GetHeader("X-Whatever-Header");
await Next.Invoke(request, response);
response.SetHeader("X-MyResponse-Header", "Some Value");
response.StatusCode = 403;
}
}
Questions:
Is it the recommended practice to derive from OwinMiddleware? I see that in Katana source, some of the middleware classes derive from OwinMiddleware and some do not.
I can see the request headers okay. Setting response header or status code after Next.Invoke in my middleware has no effect on the response returned to the client. But if I set the response header or status before the Next.Invoke call, the response with headers and the status that I set is returned to the client. What is the right way of setting these?
Yes, deriving from OwinMiddleware is recommended. The reason some middleware classes don't derive from OwinMiddleware is that either they haven't switched over yet because the class was introduced recently. Or to avoid having the assembly take a dependency on the Microsoft.Owin assembly for some reason.
The probable reason setting stuff on the response after calling Invoke on Next doesn't work is that the response HTTP header gets sent as soon as anyone starts writing to the response body stream. So any changes to status code or HTTP headers after a middleware component starts writing to the response body won't have any effect.
What you can try doing is to use the OnSendingHeaders callback that OWIN provides. Here's how you can use it:
public override async Task Invoke(IOwinContext context)
{
var response = context.Response;
var request = context.Request;
response.OnSendingHeaders(state =>
{
var resp = (OwinResponse)state;
resp.Headers.Add("X-MyResponse-Header", "Some Value");
resp.StatusCode = 403;
resp.ReasonPhrase = "Forbidden";
}, response);
var header = request.Headers["X-Whatever-Header"];
await Next.Invoke(context);
}
Credit to biscuit314 for updating my answer.
I tried to edit Youssef's excellent answer to correct a minor bug and update the example with how the OwinMiddleware now works.
The edit was rejected (well, approved by one, rejected by one for being too minor, and rejected by two for being too major).
Here is that version of Youssef's code:
public override async Task Invoke(IOwinContext context)
{
var response = context.Response;
var request = context.Request;
response.OnSendingHeaders(state =>
{
var resp = (OwinResponse)state;
resp.Headers.Add("X-MyResponse-Header", "Some Value");
resp.StatusCode = 403;
resp.ReasonPhrase = "Forbidden"; // if you're going to change the status code
// you probably should also change the reason phrase
}, response);
var header = request.Headers["X-Whatever-Header"];
await Next.Invoke(context);
}
I used this code to get the time taken by every request.
appBuilder.Use(async (context, next) =>
{
var watch = new Stopwatch();
watch.Start();
await next();
watch.Stop();
context.Response.Headers.Set("ResponseTime", watch.Elapsed.Seconds.ToString());
});

Categories