The following code is part of an error handling middleware, whose goal is to provide consistent formatting for the client even if an error is thrown.
I am trying to serialize a response as XML when the Accept header is set to application/xml, otherwise return JSON. This article helped get me started: https://www.devtrends.co.uk/blog/handling-errors-in-asp.net-core-web-api
if (context.Request.Headers["Accept"] == "application/xml")
{
context.Response.ContentType = "application/xml";
using (var stringwriter = new StringWriter())
{
var serializer = new XmlSerializer(response.GetType());
serializer.Serialize(stringwriter, response);
await context.Response.WriteAsync(stringwriter.ToString());
}
}
else {
context.Response.ContentType = "application/json";
var json = JsonConvert.SerializeObject(response);
await context.Response.WriteAsync(json);
}
The else block works as expected. If I set a breakpoint on the line where the XmlSerializer is declared, execution halts. If I set a breakpoint on the following line, the breakpoint is never hit; a response has already been sent to the client.
My middleware is configured like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStatusCodePagesWithReExecute("/error/{0}");
app.UseExceptionHandler("/error/500");
app.UseHsts();
app.UseMiddleware<ErrorWrappingMiddleware>();
app.UseMvc();
}
Why is a response returned to the client before context.Response.WriteAsync(stringwriter.ToString()); is called in the if block?
I figured out the problem, it was a silly mistake.
To test 500 errors, I was intentionally adding a property to a model that didn't have a corresponding column in the database. This throws a SqlException on an invalid column name. There was however a second exception I missed that told me exactly what the problem was:
ApiResponse cannot be serialized because it does not have a parameterless constructor.
This occurred in the call to serializer.Serialize(stringwriter, response). The example I referenced creates an ApiResponse class (the response I attempted to serialize). However this class does not have a parameterless constructor in the example I was learning from, and I wasn't aware this was necessary until I found this answer. This second exception halted execution, but since I forgot to turn on development mode and I was trying to throw an exception, I didn't notice the second one.
Related
I'm having a strange problem with ASP.Net Core custom middleware.
I've got an exception handler that I'm trying to log exceptions and then send a generic message to the caller without the exception details that seem to be sent by default.
The problem is that the response is being sent without a body.
My code:
public async Task Invoke(HttpContext context)
{
try
{
await next.Invoke(context);
}
catch (Exception e)
{
logger.LogError(e, "Request threw an exception");
context.Response.StatusCode = 500;
using (var writer = new StreamWriter(context.Response.Body))
{
await writer.WriteAsync($"Error on server processing request");
await writer.FlushAsync();
}
}
}
I'm using .Net 5. This project is a Web API project, in case that makes any difference.
I'm invoking my API via swagger UI and I'm also checking the responses using the dev tools in Edge (Chromium version) to make sure that swagger isn't hiding the body.
It appears that there is no body to the response, it's just an empty response with a 500 code.
Running in the debugger shows that it is executing the code and if I change the response code to 566 that response code is received by SwaggerUI so it's doing something.
Note: before I added the FlushAsync() call, I was getting an exception sent to Swagger saying that the dispose was using a synchronous write when flushing so that seems to be necessary.
Update:
Pipeline configuration, as requested:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment() || env.IsTesting())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Clients v1"));
}
app.UseRequestLoggingMiddleware(); // My custom middleware
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Behaviour is the same regardless of the environment (development, testing, staging or production)
I'm uncertain why using context.Response.Body is not working, but what I've done instead is use the methods directly on context.Response:
context.Response.StatusCode = 500;
await context.Response.WriteAsync($"Error on server processing request");
Update: Given your middleware pipeline starts with app.UseDeveloperExceptionPage() I suspect that's what's overwriting the response - at least if you're running with dev or test as environment. This is the middleware that actually exposes the full exception details you are saying you're trying to avoid. In the ASP.NET Core project boilerplate/template this is intentionally only added when not running on a production environment.
So perhaps your problem will be solved by changing the ASPNETCORE_ENVIRONMENT environment variable to something other than Development or Test. Or if you still want your own middleware, you should probably remove app.UseDeveloperExceptionPage() and perhaps even move your own app.UseRequestLoggingMiddleware() up as the first line in Configure (although I don't think the Swagger stuff should interfere - but I make no promises :) )
I found the problem. The code I needed was
context.Response.StatusCode = 500;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync($"Error on server processing request");
I noticed that if I requested the API by typing the url into the web browser, I was getting the text back but when I requested via Swagger UI, it was not sending the text.
SwaggerUI was setting an accept: text/plain header with the request so ASP.Net was ignoring any content that wasn't set as this type (the ContentType is null by default).
The browser had */* in its accept header so the content was being sent regardless of type.
I have an ASP.NET Core application and I'm attempting to handle HTTP responses with status codes between 400 and 599 by using UseStatusCodePagesWithRedirects.
In Startup.cs I've added the following:
app.UseStatusCodePagesWithRedirects("/Error");
My Error controller is empty except for the following action (which was taken from the default scaffolded Home controller):
[Route("Error")]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
This works perfectly when I call return new BadRequestResult(); or return NotFound(); from one of my controllers, but when I try to return an error with more detail (such as including an error object) the controller action is never called and the body of the response is displayed on screen as plaintext instead. As an example, the following statement does not trigger the Error controller:
context.Result = new BadRequestObjectResult({ Errors = errors });
If I use the following statement instead, the middleware is correctly called:
context.Result = new BadRequestResult();
This appears to be working as designed, as the documentation states that UseStatusCodePagesWithRedirects "checks for responses with status codes between 400 and 599 that do not have a body" (emphasis mine) and the source code backs this up.
I want to include more information on my error page (such as user friendly error messages where appropriate) but I can't figure out how I can pass the data across effectively using the middleware since I'm trying to avoid my controllers knowing too much about how errors are handled so that the logic can be contained in one place and potentially changed later.
Is there a way to return a HTTP error that has additional information but will still get picked up by UseStatusCodePagesWithRedirects?
This is not how the exception handling middleware works. I'm not sure what you're doing exactly, but it looks like you're attempting to return BadRequest from middleware or an action filter. If you want to intercept some error there, you should simply allow the exception to bubble up (or throw one), not return a response, as that way, you'll keep the context of what happened.
Inside your error action, you can use HTTP feature interfaces to get the data you're looking for then. For example, there's:
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
If there was an exception, you can access it then via exceptionHandlerPathFeature.Error. There's also IStatusCodeReExecuteFeature, which you can use to get the original URL of the request for things like 404s:
var statusCodeReExecuteFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
if (statusCodeReExecuteFeature != null)
{
OriginalURL =
statusCodeReExecuteFeature.OriginalPathBase
+ statusCodeReExecuteFeature.OriginalPath
+ statusCodeReExecuteFeature.OriginalQueryString;
}
Source
Depending on exactly what you're doing, there might be other ways as well.
The below is not exactly what you need (passing an error details/an error object) but it seems like you can pass an error code, at least in ASP.NET Core.
If you look at the documentation for UseStatusCodePagesWithRedirects, it says that you can pass a status code, since the url template may contain such parameter:
app.UseStatusCodePagesWithRedirects("/MyStatusCode?code={0}");
Then in your MyStatusCode.cshtml you can intercept it like:
#{
var codeStr = Context.Request.Query.ContainsKey("code") ? Context.Request.Query["code"].ToString() : "-1";
}
I have a WebAPI (not sure what version but it was created in VS2017) that receives a Json string and binds it to a model in the controller. Most of the time this works just fine. Occasionally it will throw an exception the first time I try to access the class instance. It is clear the binding routines are failing but the exception message is no help: Object reference not set to an instance of an object. I am guessing that the binder is failing and not even creating an empty instance of my object.
I log the Json string before I call the API so I can review the string for issues. I have identified that certain unicode symbols (such as the Trademark TM) will cause a failure, so I am managing those.
But I have a couple of recent Json strings that are throwing the exception and I cannot figure out why. There are no unicode symbols that I can locate. Now my users are asking why this certain style of job is failing.
It seems that once the InputStream is read by the inner MVC binding routines, it can never be read again. The trick of:
HttpContext.Current.Request.InputStream.Position = 0;
string streamresult = new System.IO.StreamReader(HttpContext.Current.Request.GetBufferedInputStream()).ReadToEnd();
throws it own exception, saying call was made before "the internal storage was filled by the caller of HttpRequest.GetBufferedInputStream".
Is there a way to peek inside the binder and see what it is choking
on?
or Is there a way to get a better exception message?
or to capture and log the incoming data stream?
If you want to read the Request Body, you will need to enable rewind on its stream, otherwise the request body can be read only once since by default it's a forward-only stream.
If you are using ASP.NET Core MVC, you could enable the rewind on your startup code with:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Use(async (context, next) => { // <----
context.Request.EnableRewind();
await next();
});
app.UseMvc();
}
For your last point I can recommend the Audit.NET library with its Audit.WebApi extension.
I am using a body in a http DELETE request. I know having a body in a delete is non-standard at the moment (but is permissible).
The problem is occurring when using the HttpClient which does not allow a body for delete requests. I know I can just use SendAsync, but I would rather make my API more flexible.
I want this body to be optional. In the sense that if asp.net core cannot ascertain the content type then ignore it. At the moment, asp.net core is returning a 415, even though no body is being sent (via the HttpClient - so content length should be 0).
Can FromBody be extended in this way? Or would I need some custom logic in the pipeline?
You can create ResourceFilter which is executed before Model Binding where content type is checked:
public class AddMissingContentType : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.HttpContext.Request.Headers["Content-Type"] = "application/json";
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
And add it to your method:
[AddMissingContentType]
[HttpDelete]
public async Task<IActionResult> Delete([FromBody]RequestData request)
{
}
One solution is to manually fetch the body in the controller method. You could check for the content type if it exist or just use a default reader, such as json, and convert it to your model that way. Then you don't need to modify any request headers
[HttpDelete]
public async Task Delete()
{
using (StreamReader reader = new StreamReader(request.Body, encoding))
{
var bodyContent = await reader.ReadToEndAsync();
if(!String.IsNullOrEmpty(bodyContent))
{
//mapp the bodyContent to your model
}
}
//perform the logic which should allways be done
}
NOTE: Important to leave out the FromBody parameter in the method signature, else the binding will fail when the content type or body is missing
Maybe convert it to a POST if you will use your ~app as an API behind a proxy, load balancer or other stuff you don't have control on...
Example: some load balancers can consider a DELETE method with a body as malformed.
But if you are sure about your future environment, why not...
EDIT: The answer is: even if it's possible, maybe it's better to follow rules that are sometimes applied and sometimes not to don't have any further surprises. (--cannot comment I m a new born user).
I'm writing an MSMVC API to handle simple user registration. In the API I want to throw exceptions when the data passed to the controller is invalid.
public XmlResult RegisterByEmailAddress(UserModel model)
{
var errorResponse = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent("Problem"),
ReasonPhrase = "Problem"
};
throw new HttpResponseException(errorResponse);
}
However regardless of what I set in the exception I only ever see a 500 error in the client response. I'm using Visual Studio 2012 and running the API in debug mode on the local IIS server.
The actual content of the response as seen in the Chrome Dev is :
Method : Get, Status : 500 Internal Server Error, Type text/html
Does anyone have any idea what the problem might be?
I think, based on your own answer, and some tests I just did, that your controller is inheriting from the wrong base class. If you posted the rest of the controller it might be easier to see.
All API controllers should inherit from ApiController
MyController : ApiController
OK, well after much trial and error the following works :
throw new HttpException((int)HttpStatusCode.BadRequest, "Invalid password provided to the Api");
This seems to go against the documentation which (I think) recommends throwing an HttpResponseException when developing an Api.
I am doing something similar but in the extended ExceptionFilterAttribute. Not sure if you can do that but thought of sharing the code I currently use. This is just a sample. as I have lot more to add in this method.
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName;
var controllerName =
actionExecutedContext.ActionContext.ActionDescriptor.ControllerDescriptor.ControllerName;
Logger.LogException(controllerName + ":" + actionName, actionExecutedContext.Exception);
actionExecutedContext.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
ReasonPhrase = YourPhrase
};
}
If you are inheriting ApiController and throwing HttpException in the constructor - you will get Internal Server error (500), regardless what HttpStatusCode you passed
If you throw the exception from the constructor or a function/method called from the constructor, it will also give your status 500.