Serilog / JSNLog .NET Core logging empty JSON - json

I have a .NET Core project using Serilog and JSNLog for client side logging. If I pass a JSON object from the client to the server and log it using Serilog, the logged JSON object is empty.
The very weird thing is that, if I have the debugger attached, the JSON is logged fine.
For example:
While debugging I get:
[11:00:01 FTL] this works
[11:00:02 INF] Request finished in 342.1967ms 200 text/plain
[11:00:02 FTL] "testMessage": "this is an error"
[11:00:02 INF] Request finished in 374.7837ms 200 text/plain
When Crtl+F5 I get:
[10:59:14 FTL] this works
[10:59:14 INF] Request finished in 253.3403ms 200 text/plain
[10:59:15 FTL] [[[]]]
[10:59:15 INF] Request finished in 267.2553ms 200 text/plain
I'm not sure if the problem is with Serilog or JSNLog, but any help would be appreciated.
I've made a very simple sample app to replicate this. Using the default .NET Core Webapp
Dependencies are as shown:
in Startup.cs:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
Log.Logger = new LoggerConfiguration()
.WriteTo.Console().CreateLogger();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddSerilog();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseJSNLog(new LoggingAdapter(loggerFactory));
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
And in my front end:
<script src="~/lib/jsnlog.js/jsnlog.min.js"></script>
<script>
JL().fatal({ testMessage: "this is an error" });
JL().fatal("this works");
</script>

I had a similar issue. I took a look at JSNLog and what seemed to be the issue was the logging of the JSON .NET object that was being created when desearializing an object from the log message.
I did the following workaround:
I installed the Nuget package Destructurama.JsonNet (Install-Package Destructurama.JsonNet)
Then I changed the Logger configuration to include the destructuring:
Log.Logger = new LoggerConfiguration()
.Destructure.JsonNetTypes()
.WriteTo.Console()
.CreateLogger();
I then created a CustomLoggingAdapter class like this:
public class CustomLoggingAdapter: ILoggingAdapter
{
private ILoggerFactory _loggerFactory;
public CustomLoggingAdapter(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
public void Log(FinalLogData finalLogData)
{
ILogger logger = _loggerFactory.CreateLogger(finalLogData.FinalLogger);
Object message = LogMessageHelpers.DeserializeIfPossible(finalLogData.FinalMessage);
switch (finalLogData.FinalLevel)
{
case Level.TRACE: logger.LogTrace("{#logMessage}", message); break;
case Level.DEBUG: logger.LogDebug("{#logMessage}", message); break;
case Level.INFO: logger.LogInformation("{#logMessage}", message); break;
case Level.WARN: logger.LogWarning("{#logMessage}", message); break;
case Level.ERROR: logger.LogError("{#logMessage}", message); break;
case Level.FATAL: logger.LogCritical("{#logMessage}", message); break;
}
}
}
and changed the log to have the following format {#logMessage}
Note: LogMessageHelpers.DeserializeIfPossible can be found in the JSONLog GitHub repo
Then I changed the JSNLog configuration to take in my CustomLoggingAdapter like this:
app.UseJSNLog(new CustomLoggingAdapter(loggerFactory), jsnlogConfiguration);
and the log messages appeared.
Let me know if that helps

Related

.Net 6 - exception middleware not catching authorization error [duplicate]

I'm developing ASP Core Web API using dotnet core v3.1.
I'm using JWT tokens for authentication. And for authorization I use the [Authorize] attribute.
How can I create my own response if the user is not logged in (while trying to access the action marked with the [Authorize] attribute) or the user's token is not authenticated.
I came across a solution using a custom authorization attribute inherited from the default one. And in this example, the HandleUnauthorizedRequest method is overridden. But I don't see such a method inside the AuthorizeAttribute class.
Is there a way to create custom unauthorized responses with http body?
Since you are using JWT bearer authentication, one way to override the default Challenge logic (which executes to handle 401 Unauthorized concerns) is to hook a handler to the JwtBearerEvents.OnChallenge callback in Startup.ConfigureServices:
services.AddAuthentication().AddJwtBearer(options =>
{
// Other configs...
options.Events = new JwtBearerEvents
{
OnChallenge = async context =>
{
// Call this to skip the default logic and avoid using the default response
context.HandleResponse();
// Write to the response in any way you wish
context.Response.StatusCode = 401;
context.Response.Headers.Append("my-custom-header", "custom-value");
await context.Response.WriteAsync("You are not authorized! (or some other custom message)");
}
};
});
This will override the default challenge logic in JwtBearerHandler.HandleChallengeAsync, which you can find here for reference purposes.
The default logic does not write any content to response (it only sets the status code and set some headers). So to keep using the default logic and add content on top of it, you can use something like this:
options.Events = new JwtBearerEvents
{
OnChallenge = context =>
{
context.Response.OnStarting(async () =>
{
// Write to the response in any way you wish
await context.Response.WriteAsync("You are not authorized! (or some other custom message)");
});
return Task.CompletedTask;
}
};
For .net core 5 web api project with jwt authentication use this middleware in Configure method of Startup.cs for show ErrorDto in Swagger:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "LoginService v1"));
}
app.ConfigureExceptionHandler();
app.UseHttpsRedirection();
app.UseRouting();
// Unauthorized (401) MiddleWare
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized) // 401
{
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(new ErrorDto()
{
StatusCode = 401,
Message = "Token is not valid"
}.ToString());
}
});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
ErrorDto :
public class ErrorDto
{
public int StatusCode { get; set; }
public string Message { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
This is what I came up with for responding with the same ProblemDetails you would get from returning Unauthorized() in an ApiController:
.AddJwtBearer(options =>
{
// Other configs...
options.Events = new JwtBearerEvents
{
OnChallenge = async context =>
{
// Call this to skip the default logic and avoid using the default response
context.HandleResponse();
var httpContext = context.HttpContext;
var statusCode = StatusCodes.Status401Unauthorized;
var routeData = httpContext.GetRouteData();
var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());
var factory = httpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
var problemDetails = factory.CreateProblemDetails(httpContext, statusCode);
var result = new ObjectResult(problemDetails) { StatusCode = statusCode };
await result.ExecuteResultAsync(actionContext);
}
};
});

Minimal API - Invalid JSONs in request body

I'm facing a behavior in Minimal API that I can't understand.Consider the following simple Minimal API:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseExceptionHandler((exceptionApp) =>
{
exceptionApp.Run(async context =>
{
context.Response.ContentType = MediaTypeNames.Text.Plain;
var feature = context.Features.Get<IExceptionHandlerPathFeature>();
if (feature?.Error is BadHttpRequestException ex)
{
context.Response.StatusCode = 400;
var message =
(ex.InnerException is JsonException)
? "The request body is an invalid JSON"
: "Bad Request";
await context.Response.WriteAsync(message);
}
else
{
context.Response.StatusCode = 500;
await context.Response.WriteAsync("There is a problem occured");
}
});
});
app.MapPost("/models", (Model model) => Results.Created(string.Empty, model));
app.Run();
public record Model(int Value, string Title);
When I run the application in the Development environment, and pass an invalid JSON like
{
"value": 1,
"Title": Model #1
}
the custom exception handler is called and I have to control the behavior of the API. But whenever
I run the application in the Production environment, the framework automatically returns a
"bad request" response without letting me control the response.
Could anyone explain this behavior to me? I really need my exception handler to handle invalid input
JSON exceptions.
Thanks
After digging into ASP.Net Core source code for a while, I found that the following line resolves the issue.
builder.Services.Configure<RouteHandlerOptions>(o => o.ThrowOnBadRequest = true);

Passing data to SignalR Hub using Post method to Web Api returned: The remote server returned an error: (404) Not Found

Recently, I just started to learn on SignalR and I had been testing on one project that I found on GitHub. However I did stuck when trying to Post data to Web api part.
I just get everything done yet I cannot really make this project to work somehow. This is basically the program for the project. It is a console app and did send the data(Json) to Web Api
// Get the stuff we need to send
GetMetrics(out cpuTime, out memUsage, out totalMemory);
// Send the data
var postData = new
{
MachineName = System.Environment.MachineName,
Processor = cpuTime,
MemUsage = memUsage,
TotalMemory = totalMemory
};
var json = JsonConvert.SerializeObject(postData);
// Post the data to the server http://localhost:80/api/cpuinfo
var serverUrl = new Uri(ConfigurationManager.AppSettings["ServerUrl"]);
var client = new WebClient();
client.Headers.Add("Content-Type", "application/json");
client.UploadString(serverUrl, json);
Moving to web part. I did have the Asp.net MVC and did create the RouteConfig inside the App_Start to route HTTP request to controller.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
And this is the controller class.
public class CpuInfoController : ApiController
{
public void Post(CpuInfoPostData cpuInfo)
{
var context = GlobalHost.ConnectionManager.GetHubContext<CpuInfo>();
context.Clients.All.cpuInfoMessage(cpuInfo.MachineName, cpuInfo.Processor, cpuInfo.MemUsage, cpuInfo.TotalMemory);
}
}
I also had it registered inside Global.asax as below
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
After done all this, I still cant get this done and my console application pop up some errors as in the image here. It seems like the api/cpuinfo was not found.
Please advice me if anything that I had done wrong here.
The full version of this project can be found here.
You have to modify the File App.config in "CpuInfoClient" project. (the value of the Key)
Use "http" instead of "https"
Change the port number to the actual port number (instead of 44300), that uses the web application after starting. The exact port for the substitution you can see , when the web app starts in IE or Firefox. The port is also in "WcfCpuApp -> Properties -> Web -> Project-URL
Be sure that your web application is running, when you start "CpuInfoClient"

Is there any way within middleware running on ASP.NET Core 2.2 to detect if the request is for an ApiController?

I have an application with both MVC and 'new' ApiController endpoints in ASP.NET Core 2.2 co-existing together.
Prior to adding the API endpoints, I have been using a global exception handler registered as middleware using app.UseExceptionHandler((x) => { ... } which would redirect to an error page.
Of course, that does not work for an API response and I would like to return an ObjectResult (negotiated) 500 result with a ProblemDetails formatted result.
The problem is, I'm not sure how to reliably determine in my 'UseExceptionHandler' lambda if I am dealing with an MVC or a API request. I could use some kind of request URL matching (eg. /api/... prefix) but I would like a more robust solution that won't come back to bite me in the future.
Rough psuedo-code version of what I'm trying to implement is:
app.UseExceptionHandler(x =>
{
x.Run(async context =>
{
// extract the exception that was thrown
var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error;
try
{
// generically handle the exception regardless of what our response needs to look like by logging it
// NOTE: ExceptionHandlerMiddleware itself will log the exception
// TODO: need to find a way to see if we have run with negotiation turned on (in which case we are API not MVC!! see below extensions for clues?)
// TODO: ... could just use "/api/" prefix but that seems rubbish
if (true)
{
// return a 500 with object (in RFC 7807 form) negotiated to the right content type (eg. json)
}
else
{
// otherwise, we handle the response as a 500 error page redirect
}
}
catch (Exception exofex)
{
// NOTE: absolutely terrible if we get into here
log.Fatal($"Unhandled exception in global error handler!", exofex);
log.Fatal($"Handling exception: ", ex);
}
});
});
}
Any ideas?
Cheers!
This might be a bit different than what you expect, but you could just check if the request is an AJAX request.
You can use this extension:
public static class HttpRequestExtensions
{
public static bool IsAjaxRequest(this HttpRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
if (request.Headers == null)
return false;
return request.Headers["X-Requested-With"] == "XMLHttpRequest";
}
}
And then middleware with an invoke method that looks like:
public async Task Invoke(HttpContext context)
{
if (context.Request.IsAjaxRequest())
{
try
{
await _next(context);
}
catch (Exception ex)
{
//Handle the exception
await HandleExceptionAsync(context, ex);
}
}
else
{
await _next(context);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
//you can do more complex logic here, but a basic example would be:
var result = JsonConvert.SerializeObject(new { error = "An unexpected error occurred." });
context.Response.ContentType = "application/json";
context.Response.StatusCode = 500;
return context.Response.WriteAsync(result);
}
see this SO answer for a more detailed version.
If you want to check whether the request is routed to ApiController, you could try IExceptionFilter to hanlde the exceptions.
public class CustomExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (IsApi(context))
{
HttpStatusCode status = HttpStatusCode.InternalServerError;
var message = context.Result;
//You can enable logging error
context.ExceptionHandled = true;
HttpResponse response = context.HttpContext.Response;
response.StatusCode = (int)status;
response.ContentType = "application/json";
context.Result = new ObjectResult(new { ErrorMsg = message });
}
else
{
}
}
private bool IsApi(ExceptionContext context)
{
var controllerActionDesc = context.ActionDescriptor as ControllerActionDescriptor;
var attribute = controllerActionDesc
.ControllerTypeInfo
.CustomAttributes
.FirstOrDefault(c => c.AttributeType == typeof(ApiControllerAttribute));
return attribute == null ? false : true;
}
}
Thanks to all of the advice from others, but I have realised after some more thought and ideas from here that my approach wasn't right in the first place - and that I should be handling most exceptions locally in the controller and responding from there.
I have basically kept my error handling middleware the same as if it was handling MVC unhandled exceptions. The client will get a 500 with a HTML response, but at that point there isn't much the client can do anyway so no harm.
Thanks for your help!

Configuration IIS 7 for JSON

i have this code for receiving data from server:
try
{
var data = _model.GetAll().ToList();
if (data.Count > 100)
return SendMessage($"Too much results", HttpStatusCode.Forbidden);
var result = JsonConvert.SerializeObject(data,
new JsonSerializerSettings { Converters = new JsonConverter[] { new StringEnumConverter() } });
return SendMessage(new{ results = result });
}catch (Exception ex)
{
return SendMessage("Server error.", HttpStatusCode.InternalServerError);
}
SendMessage method:
protected JsonResult SendMessage(string message, object data, HttpStatusCode code = HttpStatusCode.OK)
{
Response.StatusCode = (int)code;
return Json(new { message, data }, JsonRequestBehavior.AllowGet);
}
When method return 403 http code, i localhost is everything ok. Method send data in content-type application/json. But when I deploy my code to web server (IIS 7) method send content-type text/html and data is empty.
Where is problem? How i must configure IIS server?
Thanks for advices
Here is solution for my problem: https://forums.iis.net/t/1213176.aspx.
Open your IIS > click web site > Error Pages > here under "Alerts" section click on "Edit Features Settings..." it will show following three option :
1] Custom error pages
2] Detail errors
3] Detailed errors for local requests and custom error pages for remote requestes.
select 2nd option Detail errors click Ok.