'How can I catch an exception and send it as json message?

I wrote a code but for some reason it doesn't work...can you tell me what's wrong? I want the app not to stop when I get an exception, only to send that exception back as a json message.

Startup.cs Configure method:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v2/swagger.json", "My API");
            });
        }
        //this is the question...?
        app.UseExceptionHandler(c => c.Run(async context =>
        {
                var exception = context.Features.Get<IExceptionHandlerPathFeature>().Error;
                var response = new { Msg = exception.Message };
                await context.Response.WriteAsJsonAsync(response);
        }));

        app.UseHttpsRedirection();

        app.UseStaticFiles();

        app.UseRouting();

        app.UseCors(x => x
           .AllowAnyMethod()
           .AllowAnyHeader()
           .SetIsOriginAllowed(origin => true)
           .AllowCredentials());

        app.UseAuthentication();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapHub<EventHub>("/events");
        });
    }

Logic the method where I throw an exception:

public IEnumerable<object> Search(string text)
    {
        if (text.Length >= 3)
        {
            var result = new List<object>
            {
                clubRepository.GetAll().Where(club => club.ClubName.Contains(text)),
                playerRepository.GetAll().Where(player => player.PlayerName.Contains(text)),
                managerRepository.GetAll().Where(manager => manager.ManagerName.Contains(text)),
                stadiumRepository.GetAll().Where(stadium => stadium.StadiumName.Contains(text))
            };

            return result;
        }
        else
        {
            throw new ArgumentException("The text is not long enough!");
        }
    }

So I would like to get this exception message as json!

Now it is happening --> Image1

I want that to happen --> Image2



Solution 1:[1]

You can extract exception elements as a key value in a dictionary.

And serialize that into JSON.

Inspired by this answer, here is my method to extract key/value from exception:

public static Dictionary<string, string> GetExceptionDetails(Exception exception)
{
    var properties = exception.GetType()
        .GetProperties();
    var fields = properties
        .Select(property => new
        {
            Name = property.Name,
            Value = property.GetValue(exception, null)
        })
        .Select(x => $"{x.Name} = {(x.Value != null ? x.Value.ToString() : string.Empty)}")
        .ToDictionary(k => k, v => v);
    return fields;
}

For my test I have done this:

private static void CallSome()
{
    throw new Exception("xx");
}

in your try/catch you do the following:

try
{
    CallSome();
}
catch (Exception e)
{
    string str = JsonConvert.SerializeObject(GetExceptionDetails(e));
    Console.WriteLine(str);
}

This will return you a JSON payload.

I use dotnet 6 console app. I have also installed the Newtonsoft.Json package. You can also you dotnet JsonSerializer:

var str = JsonSerializer.Serialize(GetExceptionDetails(e));

Note: it also worth reading this also this.

Solution 2:[2]

You can use Middleware to handle any exception.

First: Create a ErrorHandlerMiddleware.cs like below.

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;
            //Set response ContentType
            response.ContentType = "application/json";

            //Set custome error message for response model
            var responseContent = new ResponseContent()
            {
                error = error.Message
            };
            //handler many Exception types
            switch (error)
            {
                case ArgumentException _ae:
                    response.StatusCode = StatusCodes.Status400BadRequest;
                    break;
                default:
                    response.StatusCode = StatusCodes.Status500InternalServerError;
                    break;
            }
            //Using Newtonsoft.Json to convert object to json string
            var jsonResult = JsonConvert.SerializeObject(responseContent);
            await response.WriteAsync(jsonResult);
        }
    }
    //Response Model
    public class ResponseContent
    {
        public string error { get; set; }
    }
}

Next: In Startup.cs, use the middleware

app.UseMiddleware<ErrorHandlerMiddleware>();

Here is project structure of my simple example :

enter image description here

Goodluck!

Solution 3:[3]

I don't know if you misunderstood but your app is only stopping because you are running it inside VS Code (Debug Mode). If you run your app externally (in command, run "dotnet run") you'll see that the app will not stop.

Now, it's just an advice. Your app is already sending back the json but with status code 500 (internal server error). A better practice for validation errors, is returning as bad request (status code 400). You can add one line like below.

app.UseExceptionHandler(c => c.Run(async context =>
{
    var exception = context.Features.Get<IExceptionHandlerPathFeature>().Error;
    var response = new { Msg = exception.Message };

    context.Response.StatusCode = (int)HttpStatusCode.BadRequest;

    await context.Response.WriteAsJsonAsync(response);

}));

Then, if you want to improve a little more. You can replace exceptions with notification pattern. Here are some links if you are interested.

https://martinfowler.com/articles/replaceThrowWithNotification.html https://timdeschryver.dev/blog/creating-a-new-csharp-api-validate-incoming-requests

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3