When using convention based routing I am able to use a DelegatingHandler to create a response wrapper by overriding the SendAsync method.
DelegatingHandler[] handler = new DelegatingHandler[] {
new ResponseWrapper()
};
var routeHandler = HttpClientFactory.CreatePipeline(new HttpControllerDispatcher(config), handler);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}",
defaults: null,
constraints: null,
handler: routeHandler
);
However, this approach does not work for methods that rely upon attribute routing. In my case convention based routing will not work for all scenarios and the routeHandler does not apply to the attribute based routes.
How can I apply a response wrapper to all attribute based route responses?
I was able to add a global message handler that applies to all requests.
config.MessageHandlers.Add(new ResponseWrapper());
Since I am using swagger, I also had to ignore the swagger request URI. Here is the code for the ResponseWrapper class in the event it helps someone. I have not had a chance to go back through it so there are certain to be some improvements...
public class ResponseWrapper : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (request.RequestUri.ToString().Contains("swagger"))
{
return response;
}
return BuildApiResponse(request, response);
}
private static HttpResponseMessage BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
{
object content = null;
string errorMessage = null;
response.TryGetContentValue(out content);
if (!response.IsSuccessStatusCode)
{
content = null;
var error = new HttpError(response.Content.ReadAsStringAsync().Result);
var data = (JObject)JsonConvert.DeserializeObject(error.Message);
errorMessage = data["message"].Value<string>();
if (!string.IsNullOrEmpty(error.ExceptionMessage) && string.IsNullOrEmpty(errorMessage))
{
errorMessage = error.ExceptionMessage;
}
}
var newResponse = request.CreateResponse(response.StatusCode, new ApiResponse(response.StatusCode, content, errorMessage));
foreach (var header in response.Headers)
{
newResponse.Headers.Add(header.Key, header.Value);
}
return newResponse;
}
}
Related
In my WebAPI project I'm using Owin.Security.OAuth to add JWT authentication.
Inside GrantResourceOwnerCredentials of my OAuthProvider I'm setting errors using below line:
context.SetError("invalid_grant", "Account locked.");
this is returned to client as:
{
"error": "invalid_grant",
"error_description": "Account locked."
}
after user gets authenticated and he tries to do "normal" request to one of my controllers he gets below response when model is invalid (using FluentValidation):
{
"message": "The request is invalid.",
"modelState": {
"client.Email": [
"Email is not valid."
],
"client.Password": [
"Password is required."
]
}
}
Both requests are returning 400 Bad Request, but sometimes You must look for error_description field and sometimes for message
I was able to create custom response message, but this only applies to results I'm returning.
My question is: is it possible to replace message with error in response that is returned by ModelValidatorProviders and in other places?
I've read about ExceptionFilterAttribute but I don't know if this is a good place to start. FluentValidation shouldn't be a problem, because all it does is adding errors to ModelState.
EDIT:
Next thing I'm trying to fix is inconsistent naming convention in returned data across WebApi - when returning error from OAuthProvider we have error_details, but when returning BadRequest with ModelState (from ApiController) we have modelState. As You can see first uses snake_case and second camelCase.
UPDATED ANSWER (Use Middleware)
Since the Web API original delegating handler idea meant that it would not be early enough in the pipeline as the OAuth middleware then a custom middleware needs to be created...
public static class ErrorMessageFormatter {
public static IAppBuilder UseCommonErrorResponse(this IAppBuilder app) {
app.Use<JsonErrorFormatter>();
return app;
}
public class JsonErrorFormatter : OwinMiddleware {
public JsonErrorFormatter(OwinMiddleware next)
: base(next) {
}
public override async Task Invoke(IOwinContext context) {
var owinRequest = context.Request;
var owinResponse = context.Response;
//buffer the response stream for later
var owinResponseStream = owinResponse.Body;
//buffer the response stream in order to intercept downstream writes
using (var responseBuffer = new MemoryStream()) {
//assign the buffer to the resonse body
owinResponse.Body = responseBuffer;
await Next.Invoke(context);
//reset body
owinResponse.Body = owinResponseStream;
if (responseBuffer.CanSeek && responseBuffer.Length > 0 && responseBuffer.Position > 0) {
//reset buffer to read its content
responseBuffer.Seek(0, SeekOrigin.Begin);
}
if (!IsSuccessStatusCode(owinResponse.StatusCode) && responseBuffer.Length > 0) {
//NOTE: perform your own content negotiation if desired but for this, using JSON
var body = await CreateCommonApiResponse(owinResponse, responseBuffer);
var content = JsonConvert.SerializeObject(body);
var mediaType = MediaTypeHeaderValue.Parse(owinResponse.ContentType);
using (var customResponseBody = new StringContent(content, Encoding.UTF8, mediaType.MediaType)) {
var customResponseStream = await customResponseBody.ReadAsStreamAsync();
await customResponseStream.CopyToAsync(owinResponseStream, (int)customResponseStream.Length, owinRequest.CallCancelled);
owinResponse.ContentLength = customResponseStream.Length;
}
} else {
//copy buffer to response stream this will push it down to client
await responseBuffer.CopyToAsync(owinResponseStream, (int)responseBuffer.Length, owinRequest.CallCancelled);
owinResponse.ContentLength = responseBuffer.Length;
}
}
}
async Task<object> CreateCommonApiResponse(IOwinResponse response, Stream stream) {
var json = await new StreamReader(stream).ReadToEndAsync();
var statusCode = ((HttpStatusCode)response.StatusCode).ToString();
var responseReason = response.ReasonPhrase ?? statusCode;
//Is this a HttpError
var httpError = JsonConvert.DeserializeObject<HttpError>(json);
if (httpError != null) {
return new {
error = httpError.Message ?? responseReason,
error_description = (object)httpError.MessageDetail
?? (object)httpError.ModelState
?? (object)httpError.ExceptionMessage
};
}
//Is this an OAuth Error
var oAuthError = Newtonsoft.Json.Linq.JObject.Parse(json);
if (oAuthError["error"] != null && oAuthError["error_description"] != null) {
dynamic obj = oAuthError;
return new {
error = (string)obj.error,
error_description = (object)obj.error_description
};
}
//Is this some other unknown error (Just wrap in common model)
var error = JsonConvert.DeserializeObject(json);
return new {
error = responseReason,
error_description = error
};
}
bool IsSuccessStatusCode(int statusCode) {
return statusCode >= 200 && statusCode <= 299;
}
}
}
...and registered early in the pipeline before the the authentication middlewares and web api handlers are added.
public class Startup {
public void Configuration(IAppBuilder app) {
app.UseResponseEncrypterMiddleware();
app.UseRequestLogger();
//...(after logging middle ware)
app.UseCommonErrorResponse();
//... (before auth middle ware)
//...code removed for brevity
}
}
This example is just a basic start. It should be simple enough able to extend this starting point.
Though in this example the common model looks like what is returned from OAuthProvider, any common object model can be used.
Tested it with a few In-memory Unit Tests and through TDD was able to get it working.
[TestClass]
public class UnifiedErrorMessageTests {
[TestMethod]
public async Task _OWIN_Response_Should_Pass_When_Ok() {
//Arrange
var message = "\"Hello World\"";
var expectedResponse = "\"I am working\"";
using (var server = TestServer.Create<WebApiTestStartup>()) {
var client = server.HttpClient;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(message, Encoding.UTF8, "application/json");
//Act
var response = await client.PostAsync("/api/Foo", content);
//Assert
Assert.IsTrue(response.IsSuccessStatusCode);
var result = await response.Content.ReadAsStringAsync();
Assert.AreEqual(expectedResponse, result);
}
}
[TestMethod]
public async Task _OWIN_Response_Should_Be_Unified_When_BadRequest() {
//Arrange
var expectedResponse = "invalid_grant";
using (var server = TestServer.Create<WebApiTestStartup>()) {
var client = server.HttpClient;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(expectedResponse, Encoding.UTF8, "application/json");
//Act
var response = await client.PostAsync("/api/Foo", content);
//Assert
Assert.IsFalse(response.IsSuccessStatusCode);
var result = await response.Content.ReadAsAsync<dynamic>();
Assert.AreEqual(expectedResponse, (string)result.error_description);
}
}
[TestMethod]
public async Task _OWIN_Response_Should_Be_Unified_When_MethodNotAllowed() {
//Arrange
var expectedResponse = "Method Not Allowed";
using (var server = TestServer.Create<WebApiTestStartup>()) {
var client = server.HttpClient;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Act
var response = await client.GetAsync("/api/Foo");
//Assert
Assert.IsFalse(response.IsSuccessStatusCode);
var result = await response.Content.ReadAsAsync<dynamic>();
Assert.AreEqual(expectedResponse, (string)result.error);
}
}
[TestMethod]
public async Task _OWIN_Response_Should_Be_Unified_When_NotFound() {
//Arrange
var expectedResponse = "Not Found";
using (var server = TestServer.Create<WebApiTestStartup>()) {
var client = server.HttpClient;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Act
var response = await client.GetAsync("/api/Bar");
//Assert
Assert.IsFalse(response.IsSuccessStatusCode);
var result = await response.Content.ReadAsAsync<dynamic>();
Assert.AreEqual(expectedResponse, (string)result.error);
}
}
public class WebApiTestStartup {
public void Configuration(IAppBuilder app) {
app.UseCommonErrorMessageMiddleware();
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
app.UseWebApi(config);
}
}
public class FooController : ApiController {
public FooController() {
}
[HttpPost]
public IHttpActionResult Bar([FromBody]string input) {
if (input == "Hello World")
return Ok("I am working");
return BadRequest("invalid_grant");
}
}
}
ORIGINAL ANSWER (Use DelegatingHandler)
Consider using a DelegatingHandler
Quoting from an article found online.
Delegating handlers are extremely useful for cross cutting concerns.
They hook into the very early and very late stages of the
request-response pipeline making them ideal for manipulating the
response right before it is sent back to the client.
This example is a simplified attempt at the unified error message for HttpError responses
public class HttpErrorHandler : DelegatingHandler {
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
var response = await base.SendAsync(request, cancellationToken);
return NormalizeResponse(request, response);
}
private HttpResponseMessage NormalizeResponse(HttpRequestMessage request, HttpResponseMessage response) {
object content;
if (!response.IsSuccessStatusCode && response.TryGetContentValue(out content)) {
var error = content as HttpError;
if (error != null) {
var unifiedModel = new {
error = error.Message,
error_description = (object)error.MessageDetail ?? error.ModelState
};
var newResponse = request.CreateResponse(response.StatusCode, unifiedModel);
foreach (var header in response.Headers) {
newResponse.Headers.Add(header.Key, header.Value);
}
return newResponse;
}
}
return response;
}
}
Though this example is very basic, it is trivial to extend it to suit your custom needs.
Now it is just a matter of adding the handler to the pipeline
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.MessageHandlers.Add(new HttpErrorHandler());
// Other code not shown...
}
}
Message handlers are called in the same order that they appear in
MessageHandlers collection. Because they are nested, the response message travels in the other direction. That is, the last handler is
the first to get the response message.
Source: HTTP Message Handlers in ASP.NET Web API
is it possible to replace message with error in response that is
returned by ModelValidatorProviders
We may use overloaded SetError to do it otherwise, replace error with message.
BaseValidatingContext<TOptions>.SetError Method (String)
Marks this context as not validated by the application and assigns various error information properties. HasError becomes true and IsValidated becomes false as a result of calling.
string msg = "{\"message\": \"Account locked.\"}";
context.SetError(msg);
Response.StatusCode = 400;
context.Response.Write(msg);
I'm working on legacy code. No colleagues whom to ask...
From one api I'm getting an byte array as response:
Ç¡–¶Àá3`‰1wñžbF
While my Api response is:
¶–¡ÇáÀ`3‰1wñžbF
It seems to close, yet so far away...
I'm getting an base64 and convert it to byte array.
var key = await _httpclient.GetKey(id);
return Convert.FromBase64(key);
And in my controller
var response = await _aesService.GetLicense(id, cancellationToken);
return Request.CreateResponse(HttpStatusCode.OK, response, "application/octet-stream");
Where as in my webapi I have a BinaryFormatter:
public class BinaryMediaTypeFormatter : MediaTypeFormatter
{
private static readonly Type _supportedType = typeof(byte[]);
public BinaryMediaTypeFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/octet-stream"));
}
public override bool CanReadType(Type type)
{
return false;
}
public override bool CanWriteType(Type type)
{
return type == _supportedType;
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)
{
var buff = (byte[])value;
return writeStream.WriteAsync(buff, 0, buff.Length, cancellationToken);
}
}
And in the Register from WebApiConfig
public static void Register(HttpConfiguration config)
{
// Enable support for CORS headers
config.EnableCors();
// Web API configuration and services
config.Formatters.Clear();
// Configure JSON formatter
var jsonSettings = new JsonSerializerSettings();
jsonSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
var jsonFormatter = new JsonMediaTypeFormatter { SerializerSettings = jsonSettings };
// Add support for JSON POSTs without proper content-type
jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-www-form-urlencoded"));
config.Formatters.Add(jsonFormatter); // Add JSON formatter
config.Formatters.Add(new BinaryMediaTypeFormatter()); // Add custom formatter for Byte[] application/octet-stream
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new { controller = "Index", id = RouteParameter.Optional }
);
}
}
What am I missing, or am I wasting my time here? It feels like I'm almost there, but I am missing something...
In desperate need for your help :)
I have a webapi 2.0 project. I am registering two delegating handler in webapiconfig.cs. Sometimes it throws error "Index was out of bounds of the array" and the stact trace shows - error at System.Collections.GenericList.Add(T item) at handler1 at handler2. I am also using unityconfig that i am registering at Application_Start in Global.asax and I am registering WebApiconfig at Application_Start in Global.asax
I dont know why such strange behavior as it is working fine for most of the time but sometimes it throws this error.
My Code in WebApiconfig.cs is as below -
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.EnableCors();
config.MessageHandlers.Add(new Handler1(););
config.MessageHandlers.Add(new Handler2());
// Web API routes
config.MapHttpAttributeRoutes();
}
Code for two handlers are as below -
public class Handler1 : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string routeTemplate = ((IHttpRouteData[])request.GetConfiguration().Routes.GetRouteData(request).Values["MS_SubRoutes"])
.First().Route.RouteTemplate.ToString();
IPrincipal principal = new GenericPrincipal(new GenericIdentity(new Guid()), new string[] { "myRole" });
HttpContext.Current.User = principal;
return base.SendAsync(request, cancellationToken).ContinueWith(
(task) =>
{
HttpResponseMessage response = task.Result;
return response;
}
);
}
}
public class Handler2 : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string routeTemplate = ((IHttpRouteData[])request.GetConfiguration().Routes.GetRouteData(request).Values["MS_SubRoutes"])
.First().Route.RouteTemplate.ToString();
HttpConfiguration config = request.GetConfiguration();
config.Filters.Add(new Filter1());
return base.SendAsync(request, cancellationToken).ContinueWith(
(task) =>
{
HttpResponseMessage response = task.Result;
return response;
}
);
}
}
Can someone please help with what I should do?
This line looks very suspicious
string routeTemplate = ((IHttpRouteData[])request.GetConfiguration()
.Routes.GetRouteData(request)
.Values["MS_SubRoutes"])
.First()
.Route.RouteTemplate.ToString();
Because "MS_SubRoutes" thing could not exists, so you need to break that codeion different blocks to validate. Also you need to be sure that the collection has at least one member so you need to control this issue too.
var ms_SubRoutes = (IHttpRouteData[])request
.GetConfiguration()
.Routes
.GetRouteData(request)
.Values["MS_SubRoutes"];
string routeTemplate;
//Verify if "MS_SubRoutes" are part of the request
if (ms_SubRoutes != null)
{
try
{
routeTemplate = ms_SubRoutes.First()
.Route
.RouteTemplate.ToString();
}
catch(IndexOutOfRangeException ior)
{
//Do something
throw;
}
catch (Exception ex)
{
//Do something
throw;
}
}
The problem was add filters inside handler.
config.Filters.Add(new Filter1());
Added the filter in GlobalConfig and everything is running smoothly.
I couldn't call web api with params from android. I can do without params so problem probably how I send params or how I get them.
Following code gives this error :
No action was found on the controller 'Foo' that matches the request.
Android
ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("token", session.getAccessToken()));
json = restClientService.getResponseAsJSON("http://192.168.2.242/WebApi/api/fbfeed/foo/", params);
--
private HttpResponse getWebServiceResponse(String URL,
ArrayList<NameValuePair> params) {
HttpResponse httpResponse = null;
try {
HttpParams httpParameters = new BasicHttpParams();
// defaultHttpClient
DefaultHttpClient httpClient = new DefaultHttpClient(httpParameters);
HttpPost httpPost = new HttpPost(URL);
try {
httpPost.setEntity(new UrlEncodedFormEntity(params));
} catch (UnsupportedEncodingException e) {
}
httpResponse = httpClient.execute(httpPost);
Config
config.Routes.MapHttpRoute(name: "UserCreateApi", routeTemplate: "api/{controller}/{action}", defaults: new { action = "Foo" });
Controller
[AcceptVerbs("GET", "POST")]
public IHttpActionResult Foo([FromBody]string token)
{
//some code
}
Is your controller inheriting from ApiController? Can you hit the url from a browser on your local machine?
Hello, I have built an Authorization Handler in order to intercept all requests of my MVC.NET v4 Application (Using .NET 4.5).
The Handler is registered in Global.asax.cs, in WebAPIConfig.cs, for both global and path based route configurations and I have done all of the steps detailed in ASP.NET Web API Security book py Apress.
What is the proper way to register an Auth Handler for an MVC.NET Web Application?
WebAPIConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: null,
handler: new AuthHandler()
);
config.MessageHandlers.Add(new AuthHandler());
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
}
}
AuthHandler.cs
public class AuthHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
var claims = new List<Claim>() {new Claim(ClaimTypes.Name, "ghoil")};
var id = new ClaimsIdentity(claims, "dummy");
var principal = new ClaimsPrincipal(new[] { id });
var config = new IdentityConfiguration();
var newPrincipal = config.ClaimsAuthenticationManager.Authenticate(request.RequestUri.ToString(), principal);
Thread.CurrentPrincipal = newPrincipal;
if (HttpContext.Current != null)
HttpContext.Current.User = newPrincipal;
return await base.SendAsync(request, cancellationToken);
}
}