I tried to implement a global error handler on my Asp.net core mvc web page. For that I created an error handler middleware like described on this blog post.
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception error)
{
var response = context.Response;
response.ContentType = "application/json";
switch (error)
{
case KeyNotFoundException e:
// not found error
response.StatusCode = (int)HttpStatusCode.NotFound;
break;
default:
// unhandled error
response.StatusCode = (int)HttpStatusCode.InternalServerError;
break;
}
var result = JsonSerializer.Serialize(new { message = error?.Message });
await response.WriteAsync(result);
context.Request.Path = $"/error/{response.StatusCode}"; // <----does not work!
}
}
}
The middleware works as expected and catches the errors. As a result i get a white page with the error message. But i am not able to display a custom error page. I tried it with the following line of code. But this does not work.
context.Request.Path = $"/error/{response.StatusCode}";
Any ideas how I can achive my goal?
Thanks in advance
It seems that you wish to redirect the browser to an error page.
To do this you'll need to replace:
context.Request.Path = $"/error/{response.StatusCode}";
With
context.Reponse.Redirect($"/error/{response.StatusCode}");
Also, since you're sending a redirect, the response content needs to be empty, so remove the response.WriteAsync bit too.
var result = JsonSerializer.Serialize(new { message = error?.Message });
await response.WriteAsync(result);
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 working on jwt login session management with .net core 2.1 backend and angular 6 front-end , when i send the jwt my browser throws this error et::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK). But by checking on postman it works properly. This started to happen since i added a middleware in my backend code which handles the tokens
Error Screenshot
Middleware Code:
public class JwtTokenMiddleware : IMiddleware
{
private readonly ISessionManager _sessionManager;
public JwtTokenMiddleware(ISessionManager sessionManager)
{
_sessionManager = sessionManager;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (context.Request.Path.Value.Contains("/api/values/loginuser"))
{
await next(context);
}
else if (_sessionManager.IsCurrentActiveToken())
{
_sessionManager.UpdateTokenExpiryTime();
await next(context);
}
context.Response.StatusCode = (int)HttpStatusCode.OK;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(new ErrorDetails()
{
StatusCode = (int)HttpStatusCode.Unauthorized,
Type = 3,
Message = "Expired Token"
}.ToString());
}
Update: My code works perfectly fine if i remove the middleware , The issue is not because of the transfer-encoding = chunked. But the middleware is very important to my code
Problem Resolved: The issue was that my backend was taking a lot of time to give the response So, i had to optimize the code so that it gives the output under 300 ms of time
That could be anything, even antivirus/firewall issue. Try to provide more details please, like, does this happens in Firefox? Can you debug the server? Replace the token?
So I am looking for a pattern on how to handle exceptions. Specifically I want to be able to pass the exception message on to the client from a Web API controller.
The client is using a third party library which deals with a call to the API
as
this.msgs = [];
let xhr = new XMLHttpRequest(),
formData = new FormData();
for(let i = 0; i < this.files.length; i++) {
formData.append(this.name, this.files[i], this.files[i].name);
}
xhr.upload.addEventListener('progress', (e: ProgressEvent) => {
if(e.lengthComputable) {
this.progress = Math.round((e.loaded * 100) / e.total);
}
}, false);
xhr.onreadystatechange = () => {
if(xhr.readyState == 4) {
this.progress = 0;
if(xhr.status == 200)
this.onUpload.emit({xhr: xhr, files: this.files});
else
this.onError.emit({xhr: xhr, files: this.files});
this.clear();
}
};
xhr.open('POST', this.url, true);
xhr.send(formData);
My current call back function is such
errorComplete(event: any) {
console.log("upload error");
}
notice that on error the library just wraps up the XMLHttpRequest and passes it on to my call back function.
so in the controller I have created a test line as follows
throw new Exception("This is a test message");
This is to simulate an unexpected exception
currently the return code in the XMLHttpRequest is a 500 and the text is the html that .Net generates when an exception occurs.
yes the method in my controller will need to wrapper in a try-catch but I am not sure of what code to put in the catch so I can send the error message down to the client and it can handle it and not take down the application.
the current use case I am looking at is the user uploads a file to the system but there is already a file with the specified name in the system. And renaming the file is not an option! I need to notify the user that there is already a file with that name in the system.
google has not revealed a way to pass the message back so I can process it.
Thank you Nkosi- your comment got me on the right track.
I implemented some middleware.
public class UIExceptionHandler
{
RequestDelegate _next;
public UIExceptionHandler(RequestDelegate next)
{
this._next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await this._next(context);
}
catch (Exception x)
{
if (!context.Response.HasStarted)
{
context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
context.Response.Headers["Message"] = x.Message;
}
}
}
}
public static class UIExcetionHandlerExtensions
{
public static IApplicationBuilder UseUIExceptionHandler(this IApplicationBuilder builder)
{
return builder.UseMiddleware<UIExceptionHandler>();
}
}
and in the configure method of the startup
app.UseUIExceptionHandler();
then on the client I can do
errorComplete(event: any) {
var errorMessage = event.xhr.getResponseHeader('Message');
console.log(errorMessage);
}
If anyone sees an issue with this solution please let me know
I have a bit of middleware written that on a callback checks if the response is a 500. If it is a 500 I want to return the exception that was thrown. How do I get the exception that was thrown in the application?
Startup.cs
...
app.UseMiddleware<APIExceptionMiddleware>();
// Add MVC to the request pipeline.
app.UseMvc();
...
APIExceptionMiddleware.cs:
public class APIExceptionMiddleware
{
private readonly RequestDelegate _next;
public APIExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
context.Response.OnStarting(
callback: (state) =>
{
HttpResponse response = (HttpResponse)state;
if (response.StatusCode == 500)
{
// want to grab exception here turn it into JSON response place it in the response.Body but not sure how I access the exception.
return response.WriteAsync("An Error Occured");
}
return Task.FromResult<object>(null);
}, state: context.Response);
await _next.Invoke(context);
}
}
So as the request goes into UseMvc() I have an exception that is thrown. I can use app.UseDeveloperException(); and get a nice friendly HTML page with the stacktrace and exception.
I want to almost repeat that but make it a nice friendly JSON api response for my application. SO if a 500 is thrown I am using middleware to where I am going to turn that into a pretty json response and send it out as my response through the api request. My problem is how do I fetch this exception in the middleware?
If UseDeveloperException() is doing it shouldn't I be able to as well?
Take a look at the code for DeveloperExceptionPageMiddleware... in particular look at Invoke(HttpContext context) (shown below). Instead of using this default middleware that you are currently adding, use your own which you've started. It will be very much like DeveloperExceptionPageMiddleware: catch any exception, but instead of returning an error page just format your JSON response as desired.
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(0, ex, "An unhandled exception has occurred while executing the request");
if (context.Response.HasStarted)
{
_logger.LogWarning("The response has already started, the error page middleware will not be executed.");
throw;
}
try
{
context.Response.Clear();
context.Response.StatusCode = 500;
await DisplayException(context, ex);
if (_diagnosticSource.IsEnabled("Microsoft.AspNetCore.Diagnostics.UnhandledException"))
{
_diagnosticSource.Write("Microsoft.AspNetCore.Diagnostics.UnhandledException", new { httpContext = context, exception = ex });
}
return;
}
catch (Exception ex2)
{
// If there's a Exception while generating the error page, re-throw the original exception.
_logger.LogError(0, ex2, "An exception was thrown attempting to display the error page.");
}
throw;
}
}
I have a ASP.NET project which involves sending HTTP requests via the Web-API Framework. The following exception is only raised when debugging:
The server committed a protocol violation. Section=ResponseStatusLine
The project runs perfectly if I "Start Without Debugging".
How should I resolve this exception?
Any help is appreciated!
Update
The problem seems related to the ASP.NET MVC Identity Framework.
To access other Web-API methods, the client application has to first POST a login request (The login request does not need to be secure yet, and so I am sending the username and password strings directly to the Web-API POST method). If I comment out the login request, no more exception is raised.
Below are the relevant code snippets:
The Post method:
UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
AccountAccess ac = new AccountAccess();
public async Task<HttpResponseMessage> Post()
{
string result = await Request.Content.ReadAsStringAsync();
LoginMessage msg = JsonConvert.DeserializeObject<LoginMessage>(result);
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
var user = UserManager.Find(msg.username, msg.password);
if (user == null)
return response;
if (user.Roles == null)
return response;
var role = from r in user.Roles where (r.RoleId == "1" || r.RoleId == "2") select r;
if (role.Count() == 0)
{
return response;
}
bool task = await ac.LoginAsync(msg.username, msg.password);
response.Content = new StringContent(task.ToString());
return response;
}
The Account Access class (simulating the default AccountController in MVC template):
public class AccountAccess
{
public static bool success = false;
public AccountAccess()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
}
public AccountAccess(UserManager<ApplicationUser> userManager)
{
UserManager = userManager;
}
public UserManager<ApplicationUser> UserManager { get; private set; }
public async Task<bool> LoginAsync(string username, string password)
{
var user = await UserManager.FindAsync(username, password);
if (user != null)
{
await SignInAsync(user, isPersistent: false);
return true;
}
else
{
return false;
}
}
~AccountAccess()
{
if (UserManager != null)
{
UserManager.Dispose();
UserManager = null;
}
}
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.Current.GetOwinContext().Authentication;
}
}
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
}
Below are the relevant code snippets:
In client application:
public static async Task<List<T>> getItemAsync<T>(string urlAction)
{
message = new HttpRequestMessage();
message.Method = HttpMethod.Get;
message.RequestUri = new Uri(urlBase + urlAction);
HttpResponseMessage response = await client.SendAsync(message);
string result = await response.Content.ReadAsStringAsync();
List<T> msgs = JsonConvert.DeserializeObject<List<T>>(result);
return msgs;
}
In Web-API controller:
public HttpResponseMessage Get(string id)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
if (id == "ItemA")
{
List<ItemAMessage> msgs = new List<ItemAMessage>();
// some code...
response.Content = new StringContent(JsonConvert.SerializeObject(msgs));
}
else if (id == "ItemB")
{
List<ItemBMessage> msgs = new List<ItemBMessage>();
// some code...
response.Content = new StringContent(JsonConvert.SerializeObject(msgs));
}
return response;
}
Some observations I have:
I thought that I may need to send the request asynchronously (with the async-await syntax), but the exception still persists that way.
If I step through the code, the request does enter the HTTP method, but the code breaks at random line (Why?!) before returning the response, so I assume no response is being sent back.
I have tried the following solutions, as suggested in answers to similar questions, none of which works for me:
Setting useUnsafeHeaderParsing to true
Adding the header Keep-Alive: false
Changing the port setting of Skype (I don't have Skype, and port 80 and 443 are not occupied)
Additional information, in case they matter:
Mac OS running Windows 8.1 with VMware Fusion
Visual Studio 2013
.NET Framework 4.5
IIS Express Server
Update 2
The exception is resolved, but I am unsure of which modification did the trick. AFAIK, either one or both of the following fixed it:
I have a checkConnection() method, which basically sends a GET request and return true on success. I added await to the HttpClient.SendAsync() method and enforced async all the way up.
I retracted all code in the MainWindow constructor, except for the InitializeComponent() method, into the Window Initialized event handler.
Any idea?
Below are relevant code to the modifications illustrated above:
the checkConnectionAsync method:
public static async Task<bool> checkConnectionAsync()
{
message = new HttpRequestMessage();
message.Method = HttpMethod.Get;
message.RequestUri = new Uri(urlBase);
try
{
HttpResponseMessage response = await client.SendAsync(message);
return (response.IsSuccessStatusCode);
}
catch (AggregateException)
{
return false;
}
}
Window Initialized event handler (retracted from the MainWindow constructor):
private async void Window_Initialized(object sender, EventArgs e)
{
if (await checkConnectionAsync())
{
await loggingIn();
getItemA();
getItemB();
}
else
{
logMsg.Content = "Connection Lost. Restart GUI and try again.";
}
}
Update 3
Although this may be a little off-topic, I'd like to add a side note in case anyone else falls into this – I have been using the wrong authentication approach for Web-API to start with. The Web-API project template already has a built-in Identity framework, and I somehow "replaced" it with a rather simple yet broken approach...
This video is a nice tutorial to start with.
This article provides a more comprehensive explanation.
In the Client Application you are not awaiting task. Accessing Result without awaiting may cause unpredictable errors. If it only fails during Debug mode, I can't say for sure, but it certainly isn't the same program (extra checks added, optimizations generally not enabled). Regardless of when Debugging is active, if you have a code error, you should fix that and it should work in either modes.
So either make that function async and call the task with the await modifier, or call task.WaitAndUnwrapException() on the task so it will block synchronously until the result is returned from the server.
Make sure URL has ID query string with value either as Item A or Item B. Otherwise, you will be returning no content with Http status code 200 which could lead to protocol violation.
When you use SendAsync, you are required to provide all relevant message headers yourself, including message.Headers.Authorization = new AuthenticationHeaderValue("Basic", token); for example.
You might want to use GetAsync instead (and call a specific get method on the server).
Also, are you sure the exception is resolved? If you have some high level async method that returns a Task and not void, that exception might be silently ignored.