In versions before .net6, I used to add an ErrorHandlerMiddleware to the pipeline, so I can centralize the need of returning error types in the application with different status codes.
an example is something exactly like this:
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)
{
string responseMessage = error.Message;
string exceptionMessage;
object ResponseObject;
var response = context.Response;
response.ContentType = "application/json";
switch (error)
{
case ApplicationBadRequestException e:
if (e.metaData is { })
ResponseObject = new { responseMessage, e.metaData };
else ResponseObject = new { responseMessage };
response.StatusCode = StatusCodes.Status400BadRequest;
break;
case ApplicationNotFoundException e:
if (e.metaData is { })
ResponseObject = new { responseMessage, e.metaData };
else ResponseObject = new { responseMessage };
response.StatusCode = StatusCodes.Status404NotFound;
break;
case ApplicationUnAuthorizedException e:
responseMessage = "You are not authorized to perform this operation.";
ResponseObject = new { responseMessage };
response.StatusCode = StatusCodes.Status401Unauthorized;
break;
default:
responseMessage = "An error has occurred...Please try again later.";
exceptionMessage = getExMessage(error);
ResponseObject = new { responseMessage, exceptionMessage };
response.StatusCode = (int)HttpStatusCode.InternalServerError;
break;
}
var result = JsonSerializer.Serialize(ResponseObject);
await response.WriteAsync(result);
}
}
}
Then at any controller in my application, when I need to return a certain error code in the response - for example 400 BadRequest - I do it like so
throw new ApplicationBadRequestException("this is the error message");
This causes the middleware pipeline to be reversed and this exception is fetched inside the ErrorhandlerMiddleware.
Now I am trying to use GraphQL in.net6, when I make this I can see that the Middleware pipeline is NOT reversed and the ErrorHandlerMiddleware class is not invoked. why does this happen?
EDIT: I have tried the same scenario with rest apis and the code works perfectly, seems that the problem happens due to the existence of GraphQL configuration.
this is my Program.cs file
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var connectionString = builder.Configuration["ConnectionStrings:DBConnectionString"];
builder.Services.AddDbContextFactory<DBEntities>(options => options.UseSqlServer(connectionString, x => x.UseNetTopologySuite()));
builder.Services.AddScoped<DBEntities>(options => options.GetRequiredService<IDbContextFactory<DBEntities>>().CreateDbContext());
builder.Services.AddGraphQLServer().AddQueryType<Query>().AddMutationType<Mutation>()
.AddProjections().AddFiltering().AddSorting();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseMiddleware<ErrorHandlerMiddleware>();
app.MapGraphQL("/graphql");
app.Run();
Related
I have a WebApi project that generates OpenApi Swagger doc. The project already uses OIDC authentication. I need to add Basic authentication to the swagger doc, so I followed this link.
I have the following swagger config in Startup.cs:
app.UseSwaggerAuthorized();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
if (env.IsDevelopment())
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API");
}
else
{
c.SwaggerEndpoint("/myapp/swagger/v1/swagger.json", "My API");
}
c.ConfigObject.AdditionalItems["syntaxHighlight"] = new Dictionary<string, object> {
["activated"] = false
};
c.RoutePrefix = string.Empty;
});
And I added the following class:
public class SwaggerBasicAuthMiddleware
{
private readonly RequestDelegate next;
public SwaggerBasicAuthMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(HttpContext context, IOptions<List<SwaggerCredential>> credentials)
{
if (context.Request.Path.StartsWithSegments("/swagger") ||
context.Request.Path.Value.ToLower().Contains("index.html"))
{
var authHeader = (string)context.Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic "))
{
var header = AuthenticationHeaderValue.Parse(authHeader);
var bytes = Convert.FromBase64String(header.Parameter);
var credential = Encoding.UTF8.GetString(bytes).Split(':');
var username = credential[0];
var password = credential[1];
var current = credentials.Value.SingleOrDefault(x => x.Username.Equals(username));
if (current != null && current.Password.Equals(password))
{
await next(context);
return;
}
}
context.Response.Headers["WWW-Authenticate"] = "Basic";
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
else
{
await next(context);
}
}
}
In development, I access my site at: https://localhost:44333 and the swagger at: https://localhost:44333/index.html.
In production, the site is accessed at: 'https://example.com/myapp' and the swagger at: https://example.com/myapp/index.html
The dialog to enter credentials is displayed and I enter them. However, the dialog keeps reappearing.
What am I missing?
Im trying to set up IAsyncActionFilter to log request body for some API requests.
But when I try to read body stream I get empty string every time.
Here is my code:
StringContent is always an empty string, even tho there is an json body on post requests.
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
ActionExecutedContext rContext = null;
string stringContent = string.Empty;
try
{
context.HttpContext.Request.EnableBuffering();
context.HttpContext.Request.Body.Position = 0;
using (var reader = new StreamReader(context.HttpContext.Request.Body))
{
stringContent = await reader.ReadToEndAsync();
context.HttpContext.Request.Body.Position = 0;
}
rContext = await next();
}
catch (Exception)
{
throw;
}
}
I dont want to use middleware, becouse I need to log only some of the controllers.
The request body has already been read by the MVC model binder by the time your IAsyncActionFilter executes https://github.com/aspnet/Mvc/issues/5260.
As a quick workaround, you could add
app.Use(next => context =>
{
context.Request.EnableBuffering();
return next(context);
});
in your startup.cs Configure() BEFORE your UseEndpoints call
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.Use(next => context =>
{
context.Request.EnableBuffering();
return next(context);
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Of course, this would enable request body buffering for all of your requests, which may not be desirable. If that's the case, you would have to add some conditional logic to the EnableBuffering delegate.
I'm trying to make my web api core return application/json, but it strangely always returns this html page breaking the error convention established by the team.
Here's the code i'm trying to execute but with no success at all so far:
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.Converters.Add(new IsoDateTimeConverter { DateTimeFormat = "dd/MM/yyyy" });
});
services.AddMvcCore().AddRazorViewEngine().AddRazorRuntimeCompilation().ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = actionContext =>
{
var errorList = (from item in actionContext.ModelState
where item.Value.Errors.Any()
select item.Value.Errors[0].ErrorMessage).ToList();
return new BadRequestObjectResult(new
{
ErrorType = "bad_request",
HasError = true,
StatusCode = (int)HttpStatusCode.BadRequest,
Message = "Formato do request inválido",
Result = new
{
errors = errorList
}
});
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseCors(
options => options.AllowAnyOrigin().SetIsOriginAllowed(x => _ = true).AllowAnyMethod().AllowAnyHeader()
);
app.UseHttpsRedirection();
app.UseRouting();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
AuthController.cs
[HttpPost("refreshtoken")]
public IActionResult PostRefreshToken(Guid token)
{
if (!_authTokenService.IsValid(token))
{
return NotFound(new JsonResponse
{
HasError = true,
ErrorType = "not_found",
StatusCode = (int)HttpStatusCode.NotFound,
Title = "Token não encontrado",
Message = "refresh is not valid because it was not found or does not comply",
});
}
var savedToken = _authTokenService.Get(token);
...
return Ok(new JsonResponse
{
StatusCode = (int)HttpStatusCode.OK,
Title = "Token atualizado",
Message = "jwt access token refreshed with success, please update your keys for subsequent requests",
Result = new
{
Expiration = accessToken.Expiration.ToString("dd/MM/yyyy HH:mm:ss"),
AccessToken = accessToken.Token,
RefreshToken = refreshToken.Token,
}
});
}
when this code is executed i was expecting a json result when NotFound() block is reached, but instead it returns this text/html page
ErrorHandlingMiddleware.cs
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var code = HttpStatusCode.InternalServerError;
var result = JsonConvert.SerializeObject(new
{
HasError = true,
StatusCode = (int)code,
Message = ex.Message
}, new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy()
}
});
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
In my case it ended up being the visual studio extension Conveyor by Keyoti being the culprit of the errors aforementioned.
When i disabled the extension, the code was revealed to be ok and returning the right code, a json object body sent by the server.
I am doing a application level exception handling in .NetCore 2.2. But facing some issue
So in my startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
//app.UseDeveloperExceptionPage();
app.UseExceptionHandler("/GlobalException");
app.UseStatusCodePagesWithReExecute("/Error/{0}");
}
else
{
app.UseExceptionHandler("/GlobalException");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
//app.UseAuthentication();
app.UseSession();
//app.UseCookiePolicy();
app.UseMiddleware<AuthenticationMiddleware>();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Account}/{action=Index}/{id?}");
});
}
And ExceptionController.cs is
public class ExceptionController : Controller
{
[Route("Error/{statusCode}")]
public IActionResult HttpStatusCodeHandler(int statusCode)
{
var statusCodeResult = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
switch (statusCode)
{
case 404:
ViewBag.ErrorMessage = "Sorry, the resource you requested could not be found";
break;
default:
ViewBag.ErrorMessage = "Sorry, the resource you requested could not be found";
break;
}
return View();
}
[AllowAnonymous]
[Route("GlobalException")]
public IActionResult GlobalException()
{
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
var exceptionPath = exceptionHandlerPathFeature.Path;
var exceptionMessage = exceptionHandlerPathFeature.Error.Message;
var exceptionStackTrace = exceptionHandlerPathFeature.Error.StackTrace;
ViewBag.ErrorMessage = "Sorry, the resource you requested could not be found";
//return await HttpContext.Response.Body.WriteAsync(JsonConvert.SerializeObject("he"));
//return View("GlobalException");//"~/Views/Shared/HttpStatusCodeHandler.cshtml"
return View("~/Views/Exception/GlobalException.cshtml");
}
}
AuthenticationMiddleware.cs
public async Task Invoke(HttpContext context)
{
bool isAjaxCall = context.Request.Headers["x-requested-with"] == "XMLHttpRequest";
if(isAjaxCall)
{
// someAjax related functionality.
}
else if (!context.Request.Path.Value.Contains("/account/Index"))
{
if (context.Session.Keys.Contains("UserName"))
{
await _next.Invoke(context);
}
else
{
context.Response.Redirect("/account/Index");
}
}
else if (context.Request.Path.Value.Contains("/GlobalException"))
{
await _next.Invoke(context);
}
await _next.Invoke(context);
}
I am able to navigate to all the exception page when I receive 404,and other errors.
But when any run time exception is arised in the application. GlobalException() is being executed but in UI, It not navigating that View.
Trying to raise an exception for testing purpose
public ActionResult RaiseException()
{
throw new Exception();
}
Could anyone please help me with this as I am struggling since two days.
Please provide me a better approach if possible. Thank you.
I have a middleware to log web api requests. Below is the Configuration method inside Startup.cs. If app.UseMiddleware comes before app.UseMvc none of the web api calls get invoked However, if app.UseMiddlewarecomes after app.UseMvc, the middleware does not do anything (i.e., recording requests).
I provided the code below. Any ideas why app.UseMiddleware interfers with asp.UseMvc?
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider services)
{
// global cors policy
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseMiddleware<ApiLoggingMiddleware>();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
}
Below is the middleware:
public async Task Invoke(HttpContext httpContext, IApiLogService apiLogService)
{
try
{
_apiLogService = apiLogService;
var request = httpContext.Request;
if (request.Path.StartsWithSegments(new PathString("/api")))
{
var stopWatch = Stopwatch.StartNew();
var requestTime = DateTime.UtcNow;
var requestBodyContent = await ReadRequestBody(request);
var originalBodyStream = httpContext.Response.Body;
await SafeLog(requestTime,
stopWatch.ElapsedMilliseconds,
200,//response.StatusCode,
request.Method,
request.Path,
request.QueryString.ToString(),
requestBodyContent
);
}
else
{
await _next(httpContext);
}
}
catch (Exception ex)
{
await _next(httpContext);
}
}
You have to always call await _next(httpContext); in middleware otherwise request does not go down the pipeline:
public async Task Invoke(HttpContext httpContext, IApiLogService apiLogService)
{
try
{
_apiLogService = apiLogService;
var request = httpContext.Request;
if (request.Path.StartsWithSegments(new PathString("/api")))
{
var stopWatch = Stopwatch.StartNew();
var requestTime = DateTime.UtcNow;
var requestBodyContent = await ReadRequestBody(request);
var originalBodyStream = httpContext.Response.Body;
await SafeLog(requestTime,
stopWatch.ElapsedMilliseconds,
200,//response.StatusCode,
request.Method,
request.Path,
request.QueryString.ToString(),
requestBodyContent
);
};
}
catch (Exception ex)
{
}
await _next(httpContext);
}
Edit (simple explanation of middleware):
The whole middleware thing works in the following way - When request comes to your application, it goes through middleware pipeline, where each middleware has to invoke next middleware in order to finally get the request to your controller. When you invoke await _next(httpContext); you are basically calling Invoke method of the next middleware in the pipeline. If you do not call await _next(httpContext); you are stopping the request and it wont come to your controller. One thing to notice is that when await _next(httpContext); returns, the request has already been served by your controller.