Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I log EntityValidation errors using ELMAH MVC?

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!

like image 316
Elsimer Avatar asked Apr 11 '13 04:04

Elsimer


4 Answers

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);
}
like image 169
Paige Cook Avatar answered Sep 22 '22 22:09

Paige Cook


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.

like image 43
Mau5 Avatar answered Sep 21 '22 22:09

Mau5


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

like image 43
Original10 Avatar answered Sep 22 '22 22:09

Original10


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)))))
    {}
}
like image 40
Brent Avatar answered Sep 19 '22 22:09

Brent