In my minimal API I try to save entity to database. The table contains UNIQUE constraint on license_plate column, so DbUpdateException would be thrown if same license plate would be passed in. I used try..catch in order to handle this situation:
app.MapPost("/vehicles", (VehiclesContext db, Vehicle vehicle) =>
{
var entity = db.Add(vehicle);
try
{
db.SaveChanges();
return Results.CreatedAtRoute("GetVehicle", new { inventoryNumber = entity.Entity.InventoryNumber }, entity.Entity);
}
catch (DbUpdateException)
{
var error = new JsonObject
{
["error"] = $"Creating vehicle failed because such license plate already exists: {vehicle.LicensePlate}"
};
return Results.BadRequest(error);
}
}).AddFilter<ValidationFilter<Vehicle>>();
However, when I pass duplicate license plate, I see this exception in console:
So, why does this exception show up in console? I tried to play with LogLevel for Microsoft.AspNetCore in appsettings.json (and appsettings.Development.json also) by changing Warning to Information, but still exceptions shows up.
The exception is logged prior to throwing, so you cannot stop the logging mechanism from being invoked.
However, you should be able to control output using LogLevel.
Note that the log comes from "Microsoft.EntityFrameworkCore" not "Microsoft.AspNetCore".
I just don't want to see errors which I handle in try...catch block!
Do you mean you don't want to see the fail ? Use Try-catch in minimal API?
Below is a demo, you can refer to it.
without try-catch
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>{
string s = null;
if (s == null)
{
throw new ArgumentNullException(paramName: nameof(s), message: "parameter can't be null.");
}}
);
app.Run();
result:
use try-catch:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>{
try
{
string s = null;
if (s == null)
{
throw new ArgumentNullException(paramName: nameof(s), message: "parameter can't be null.");
}
}
catch (Exception e)
{
Console.WriteLine("{0} Exception caught.", e);
}
}
);
app.Run();
result:
Related
I have been pulling my hair out with this one.
I have a very simple test class that throws this error:
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles.
It doesn't seem to break much, as the put request is successful and the serialize is also successful.
EDIT
I have chased the serialize exception out if it was ever really there. I am starting to think it is a problem with typed HttpClient. It throws the exception that comes out on the console and in the response on Postman. However, it doesn't allow me to catch the exception in the code and the PUT call works. So the exception is happening after the PUT request and is handled before it returns control to my app.
I am going to try to use a standard HttpClientFactor instead of a typed client and see if that works. I know that the JSON exception is a red herring, but it is ugly and breaking the response.
Any suggestions would be welcome.
public virtual async Task<CouchResponse> Create(string id, string db, TObj info)
{
CouchResponse ret = new() { Reason = "Unknown and unExpected error", Ok = false };
HttpResponseMessage rc = null;
if (id is null)
{
return new CouchResponse() { Id = "missing", Ok = false, Rev = "missing" };
}
string url = $"{db}/1";
try
{
// login to Couchdb servwer
await CouchLogin();
try
{
//var jsonInfo = JsonUtils.Serialize<TestJson>(jTest);
var jsonInfo = JsonSerializer.Serialize<TObj>(info, options);
HttpContent content = new StringContent(jsonInfo, Encoding.UTF8,
"application/json");
rc = await client.PutAsync(url, content);
}
catch (Exception eNewton)
{
Console.WriteLine($"Json Exception: {eNewton.Message}");
}
if (rc is not null)
{
var str = await rc.Content.ReadAsStringAsync();
var ret = JsonSerializer.Deserialize<CouchResponse>(str,options);
rc.EnsureSuccessStatusCode();
}
return ret;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
//return ret;
}
return ret;
}
Suggestions?
What a crazy bug. The diagnostic was very missing leading. Everything I was doing in the create method was correct.
What is missed was an await when I called the create method. This made it appear that the sendAsync was having the issue when it was really the controller trying to format the task return as a response. This caused the stack trace in the response message. Thanks for all the help.
Change this
var jsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore
};
To this
var jsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
MaxDepth = null,
};
I am using app.UseExceptionHandler(GlobalErrorHandler) in AspNetCore and after that a custom middleware. When using this separately they work fine but when using them simultaneous the exception is thrown inside my custom middleware and crashes the call. This happens on await _next.Invoke(context). I also tried to use an ExceptionFilter but the results where the same. My global exception handling looks like this. Is there a way to stop the exception from bubbling up?
app.UseCustomMiddleware();
app.UseExceptionHandler(GlobalErrorHandler);
app.UseMvc();
private void GlobalErrorHandler(IApplicationBuilder applicationBuilder)
{
applicationBuilder.Run(
async context =>
{
context.Response.ContentType = "text/html";
var ex = context.Features.Get<IExceptionHandlerFeature>();
if (ex != null)
{
string errorMessage;
var webFault = ex.Error as WebFaultException<string>;
if (webFault != null)
{
context.Response.StatusCode = (int)webFault.StatusCode;
errorMessage = webFault.Detail;
}
else
{
if (ex.Error is UnauthorizedAccessException)
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
errorMessage = string.Empty;
}
else
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
errorMessage = ex.Error.Message + new StackTrace(ex.Error, true).GetFrame(0).ToString();
}
_logger.Error(errorMessage, ex.Error);
}
await context.Response.WriteAsync(errorMessage).ConfigureAwait(false);
}
});
}
The problem is with the order in which you add middlewares to the application. Since you add exception handler after the custom middleware, any exception thrown by this middleware will not be covered by the exception filter.
The fix is very simple. Just change the order of middleware registration, so that exception filter is registered first:
app.UseExceptionHandler(GlobalErrorHandler);
app.UseCustomMiddleware();
app.UseMvc();
Now the exception thrown from the custom middleware will be successfully processed by the exception handler.
I tried debugging through the code and it seems to repro mainly when multiple clients are trying to modify the same key in a transaction. Retrying the transaction usually gets rid of the error, but is there any reason why the exception is thrown in the first place?
The code I'm trying to execute is pretty straightforward:
var existingValue = db.HashGetAsync(hashKey, field);
var t = db.CreateTransaction();
t.AddCondition(Condition.HashEqual(hashKey, field, existingValue));
t.HashSetAsync(hashKey, field, newValue, flags: CommandFlags.FireAndForget);
bool succeeded = await t.ExecuteAsync(); // StackExchange.Redis.RedisConnectionException thrown intermittently
This exception occurs when you are trying to update the same key from 2 different threads simultaneously. If you use one ConnectionMultiplexer per application (as recomended) it will occur only when key is accessed from different applications or hosts.
When you transactionally update value you should retry if update fails (transaction.ExecuteAsync() returns false or "Unexpected response to EXEC: MultiBulk: 0 items" exception is thrown).
Here is a method that transactionally updates string value:
public async Task<string> UpdateValueAsync(string key, Func<string, string> updateAction)
{
for (int i = 0; i < UpdateRetryCount; i++)
{
var oldValue = await database.StringGetAsync(key);
if (oldValue.IsNull)
throw new InvalidOperationException(string.Format("Key \"{0}\" not found.", key));
var newValue = updateAction(oldValue);
var transaction = database.CreateTransaction();
transaction.AddCondition(Condition.StringEqual(key, oldValue));
transaction.StringSetAsync(key, newValue);
try
{
if (await transaction.ExecuteAsync())
{
return newValue;
}
}
catch (RedisConnectionException exception)
{
if (exception.Message != "Unexpected response to EXEC: MultiBulk: 0 items")
{
throw;
}
}
}
throw new InvalidOperationException(string.Format("Failed to update value in key {0}.", key));
}
I've been writing an application using MVC4 and EF5.x, and using ELMAH for logging exceptions for review. We recently released the application, and as expected the ELMAH log filled up with several dozen exceptions. Great (and not)! The problem is that one of those exceptions is
System.Data.Entity.Validation.DbEntityValidationException
Validation failed for one or more entities.
See 'EntityValidationErrors' property for more details.
Of course, there's no way to see the EntityValidationErrors property for more details and the stack trace wraps up to my SubmitChanges()
I know ELMAH has the capability of allowing us to raise our own exceptions, and in some way customize what gets logged and how. Unfortunately, I'm still very new to ELMAH and MVC and a Google search didn't turn up anything relevant. I did find a blog article on logging EntityValidationErrors, and the author specifically mentioned that he would post how to do so in ELMAH but that was posted in September of 2012 and I didn't see anything since then.
Any help would be greatly appreciated!
Probably the best thing to do in this case would be to wrap your context.SaveChanges(); call in a try...catch block and then log the individual items from the ValidationExceptions. Something like the following should get you started:
try
{
context.SaveChanges();
}
catch (DbEntityValidationException ve)
{
var error = ve.EntityValidationErrors.First().ValidationErrors.First();
var msg = String.Format("Validation Error :: {0} - {1}",
error.PropertyName, error.ErrorMessage);
var elmahException = new Exception(msg);
Elmah.ErrorSignal.FromCurrentContext().Raise(elmahException);
}
How about this extension method based on the above..
public static void SaveChangesWithBetterValidityException(this DbContext context)
{
try
{
context.SaveChanges();
}
catch (DbEntityValidationException ve)
{
var errors = new List<string>();
foreach (var e in ve.EntityValidationErrors)
{
errors.AddRange(e.ValidationErrors.Select(e2 => string.Join("Validation Error :: ", e2.PropertyName, " : ", e2.ErrorMessage)));
}
var error = string.Join("\r\n", errors);
var betterException = new Exception(error, ve);
throw betterException;
}
}
Elmah will then have a much better exception in it's log
I added the following to my Global.asax.cs in order to forward all DbEntityValidationException exceptions to Elmah across my MVC application:
private void ElmahEntityValidationException()
{
var dbEntityValidationException = Server.GetLastError() as DbEntityValidationException;
if (dbEntityValidationException != null)
{
var errors = new List<string>();
foreach (var entityError in dbEntityValidationException.EntityValidationErrors)
{
errors.AddRange(entityError.ValidationErrors.Select(e2 => string.Join("Validation Error :: ", e2.PropertyName, " : ", e2.ErrorMessage)));
}
var error = string.Join("\r\n", errors);
var betterException = new Exception(error, dbEntityValidationException);
Elmah.ErrorSignal.FromCurrentContext().Raise(betterException);
}
}
protected void Application_Error(object sender, EventArgs e)
{
ElmahEntityValidationException();
}
Some of this code was reused from #Paige Cook's and #Original10's posts.
Re-throwing as per the code below is not perfect (although I don't mind resetting the call stack here, as Elmah's logged details of the address posted to will show me what lead to the exception) and you will have to work out your own security implications, but this is fairly concise & meets my needs:
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException e)
{
var de = new DetailedEntityValidationException(e);
throw de;
}
public class DetailedEntityValidationException : Exception
{
public DetailedEntityValidationException(DbEntityValidationException ve)
: base(ve.Message + ":\r\n\t-" + string.Join(new string('-',20) + "\r\n\t-", ve.EntityValidationErrors.Select(ev=>string.Join("\r\n\t-",ev.ValidationErrors.Select(e=>e.ErrorMessage)))))
{}
}
Here is my implementation for Global Web API solution for Elmah and EF Validation errors:
public class ElmahHandleWebApiErrorAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
var e = context.Exception;
// Try parse as entity error (i'm not sure of performance implications here)
var efValidationError = e as DbEntityValidationException;
if (efValidationError == null)
{
RaiseErrorSignal(e);
}
else
{
RaiseEntityFrameWorkValidationErrorSignal(efValidationError);
}
}
private static bool RaiseErrorSignal(Exception e)
{
var context = HttpContext.Current;
if (context == null)
return false;
var signal = ErrorSignal.FromContext(context);
if (signal == null)
return false;
signal.Raise(e, context);
return true;
}
private static bool RaiseEntityFrameWorkValidationErrorSignal(DbEntityValidationException e)
{
var context = HttpContext.Current;
if (context == null)
return false;
var signal = ErrorSignal.FromContext(context);
if (signal == null)
return false;
//Taken from post above
var errors = new List<string>();
foreach (var entityError in e.EntityValidationErrors)
{
errors.AddRange(entityError.ValidationErrors.Select(e2 => string.Join("Validation Error :: ", e2.PropertyName, " : ", e2.ErrorMessage)));
}
var error = string.Join("\r\n", errors);
var betterException = new Exception(error, e);
signal.Raise(betterException, context);
return true;
}
}
and then I register the attribute in the WebApiConfig.cs file under App_Start
config.Filters.Add(new ElmahHandleWebApiErrorAttribute());
I am creating a Silverlight 4 web resource in CRM 2011 using the Otganizational Data service. I am able to create Campaigns, Marketing Lists, and Print Activities as well as linking the lists to the campaigns. However when I go to link the marketing lists to the print activity using the CampaignActivityItem I get a 403 Forbidden error. Below is the code I am using.
Models.CampaignActivityItem activityItem = new CampaignActivityItem()
{
CampaignActivityItemId = Guid.NewGuid(),
ItemId = EmailMarketingList.ListId, //Id of my marketing list I have
//already created
CampaignActivityId = new Models.EntityReference()
{
Id = MyCampaignPrintActivity.ActivityId, //Id of the print activity
//I have already created
LogicalName = "CampaignActivity",
Name = "CampaignActivity"
}
};
context.AddObject("CampaignActivityItemSet", activityItem);
context.BeginSaveChanges(System.Data.Services.Client.SaveChangesOptions.ContinueOnError, OnChangesSaved, context);
private void OnChangesSaved(IAsyncResult result)
{
// Use the Dispatcher to ensure that the
// asynchronous call returns in the correct thread.
OnUiThread(() =>
{
try
{
DataServiceResponse response = context.EndSaveChanges(result);
}
catch (DataServiceRequestException ex) // Errors with code=403
// message=Forbidden
{
WriteOperationResponse(ex.Response, "ListLink");
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
}
}
);
}
Any direction on what I am doing wrong would be greatly appreciated. Using the context I am able to perform actions on other objects but not the CampaignActivityItemSet.