How to display a user friendly message for the user

How to display a user friendly message for the user

Old forum URL: forums.lhotka.net/forums/t/5855.aspx


alef posted on Saturday, November 22, 2008

Does somebody know how to display a user friendely message if SQL server encounters a duplicate key exception like the following :

Type : Csla.DataPortalException, Csla, Version=3.5.2.0, Culture=neutral, PublicKeyToken=93be5fdc093e4c30
Message : DataPortal.Update mislukt (System.Data.SqlClient.SqlException: Cannot insert duplicate key row in object 'dbo.Look' with unique index 'UNIQUE_LookValue_Per_LookTypeFID'.
The statement has been terminated.
   bij System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
   bij System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
   bij System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
   bij System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   bij System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   bij System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
   bij System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
   bij System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
   bij System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   bij Shoesoft.Library.Hoofdafdeling.ExecuteInsert(SqlConnection cn) in D:\Projects\DOTNET\Shoesoft\Shoesoft.Library\Admin\Hoofdafdeling.Generated.cs:regel 203
   bij Shoesoft.Library.Hoofdafdeling.DataPortal_Insert() in D:\Projects\DOTNET\Shoesoft\Shoesoft.Library\Admin\Hoofdafdeling.Generated.cs:regel 186
   bij dm(Object , Object[] )
   bij Csla.Reflection.MethodCaller.CallMethod(Object obj, DynamicMethodHandle methodHandle, Object[] parameters) in D:\Projects\DOTNET\csla\cslacs\Csla\Reflection\MethodCaller.cs:regel 221)

sergeyb replied on Saturday, November 22, 2008

You have to wrap your Save inside try/catch in UI and examine the error text of the message, looking for specifics.  If you find those, display a friendly message, otherwise just re-throw the exception.

 

Sergey Barskiy

Principal Consultant

office: 678.405.0687 | mobile: 404.388.1899

cid:_2_0648EA840648E85C001BBCB886257279
Microsoft Worldwide Partner of the Year | Custom Development Solutions, Technical Innovation

 

From: alef [mailto:cslanet@lhotka.net]
Sent: Saturday, November 22, 2008 10:37 AM
To: Sergey Barskiy
Subject: [CSLA .NET] How to display a user friendly message for the user

 

Does somebody know how to display a user friendely message if SQL server encounters a duplicate key exception like the following :

Type : Csla.DataPortalException, Csla, Version=3.5.2.0, Culture=neutral, PublicKeyToken=93be5fdc093e4c30
Message : DataPortal.Update mislukt (System.Data.SqlClient.SqlException: Cannot insert duplicate key row in object 'dbo.Look' with unique index 'UNIQUE_LookValue_Per_LookTypeFID'.
The statement has been terminated.
   bij System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
   bij System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
   bij System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
   bij System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   bij System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   bij System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
   bij System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
   bij System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
   bij System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   bij Shoesoft.Library.Hoofdafdeling.ExecuteInsert(SqlConnection cn) in D:\Projects\DOTNET\Shoesoft\Shoesoft.Library\Admin\Hoofdafdeling.Generated.cs:regel 203
   bij Shoesoft.Library.Hoofdafdeling.DataPortal_Insert() in D:\Projects\DOTNET\Shoesoft\Shoesoft.Library\Admin\Hoofdafdeling.Generated.cs:regel 186
   bij dm(Object , Object[] )
   bij Csla.Reflection.MethodCaller.CallMethod(Object obj, DynamicMethodHandle methodHandle, Object[] parameters) in D:\Projects\DOTNET\csla\cslacs\Csla\Reflection\MethodCaller.cs:regel 221)



DocJames replied on Sunday, November 23, 2008

I have a static method to handle exceptions. Looks something like this (I removed application specific code and calls to methods which log exceptions in the database for further investigation):

try
{
  // Do some BO work here
}
catch (Exception ex)
{
  string Message = BusinessObjects.Common.ExceptionHandling.HandleException("Some custom message here regarding the context of where the error is happening", ex);
  MessageBox.Show(Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}

public static string HandleException(string userInformation, Exception ex)
{
 // Check to see if the exception is from the DataPortal (to search for any SQL exceptions)
 if (ex.GetType() == typeof(DataPortalException))
 {
  DataPortalException LocalEx = (DataPortalException) ex;
  Exception LoopException = LocalEx;
  Exception SqlException = null;

  // Loop all inner exceptions to see if we got a SqlClient exception
  while (LoopException != null)
  {
   if (LoopException.Source == ".Net SqlClient Data Provider")
   {
    SqlException = LoopException;
    break;
   }

   // Get next inner exception
   LoopException = LoopException.InnerException;
  }

  // If Sql server error message then only show the message from sql server
  if (SqlException != null)
   userInformation = string.Format("{0}", SqlException.Message);
  else
  {
   userInformation += Environment.NewLine + Environment.NewLine + ex.Message;
  }
 }
 else if (ex.GetType() == typeof(ValidationException)) {
  userInformation = "Validation error. Please check values.";
 }
 else
 {
  userInformation += Environment.NewLine + Environment.NewLine + ex.Message;
 }

 return userInformation;
}

alef replied on Monday, November 24, 2008

I created the following unfinished code, especially the code between the comments
(// TODO : begin rework and
// TODO : begin rework )
will need some better implementation.
But the code from DocJames gives me some idea about looping through all the inner exceptions to find a SqlClient exception.
But in the meantime I want to share already my code also.


     static void Main(string[] args)
      {
        try
        {
          // Add the event handler for handling UI thread exceptions to the event.
          Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);

          Application.EnableVisualStyles();
          Application.SetCompatibleTextRenderingDefault(false);
          ......
        }
        catch (Exception exc)
        {
          ProcessUnhandledException(exc);
        }
      }



      static void Application_ThreadException(object sender,System.Threading.ThreadExceptionEventArgs e)
      {
        ProcessUnhandledException(e.Exception);
      }

      /// <summary>
      /// Process any unhandled exceptions that occur in the application.
      /// This code is called by all UI entry points in the application (e.g. button click events)
      /// when an unhandled exception occurs.
      /// You could also achieve this by handling the Application.ThreadException event, however
      /// the VS2005 debugger will break before this event is called.
      /// </summary>
      /// <param name="ex">The unhandled exception</param>
      static void ProcessUnhandledException(Exception exc)
      {
        // An unhandled exception occured somewhere in our application. Let
        // the 'UserInterfaceLayer' handler have a try at handling it.
        try
        {
          Csla.DataPortalException dpex = exc as Csla.DataPortalException;
          if (dpex != null)
          {
            if (dpex.BusinessObject != null)
            {
              EnterpriseHelper.Log(((Csla.Core.ILogging)dpex.BusinessObject).ToXML(), "Exceptions");
            }
          }

          // TODO : begin rework
          if (exc.InnerException.InnerException is SqlException)
          {
            SqlException ex = (SqlException)exc.InnerException.InnerException;
            if (ex.Number == 547)
              MessageBox.Show(ex.ToString(),
                "Record cannot be deleted or changed as it is being used somewhere else.",
                MessageBoxButtons.OK,
                MessageBoxIcon.Exclamation);
            else if (ex.Number == 2627)
              MessageBox.Show(ex.ToString(),
                "Record cannot be saved, as another record with this key already exists",
                MessageBoxButtons.OK,
                MessageBoxIcon.Exclamation);
            else if (ex.Number == 2601)
              MessageBox.Show(ex.ToString(),
                "Record cannot be saved, as another record with this key already exists",
                MessageBoxButtons.OK,
                MessageBoxIcon.Exclamation);            
          }
          // TODO : end rework

          bool rethrow = EnterpriseHelper.HandleException(exc);
          if (rethrow)
          {
            // Something has gone very wrong - exit the application.
            Application.Exit();
          }
        }
        catch (Exception ex)
        {
          // Something has gone wrong during HandleException (e.g. incorrect configuration of the block).
          // Exit the application
          string errorMsg = "An unexpected exception occured while calling HandleException with policy 'UserInterfaceLayer'. ";
          errorMsg +=  Environment.NewLine + Environment.NewLine;
          errorMsg += ex.Message;

          MessageBox.Show(errorMsg, "Application Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);

          Application.Exit();
        }
      }

dagware replied on Sunday, November 23, 2008

The following isn't always possible, and I suppose there are even times when it's not worth the effort, but just in case you haven't thought about it (and please forgive me if I'm being patronizing):

Consider coding your business objects to detect this type of error before you do the save, typically when a new object is added to the list. This is often-times done with an "ExistsCommand" type of command object (ProjectTracker has these, but I don't know if it actually uses them or not).

Of course, even with due diligence you could still get the error, for instance if someone else added the item to the database between when you checked for its existence and when you actually saved the object.

I've often wished SQL Server returned errors in a documented way that would make it easy to parse the error into end-user-readable text. I guess I just like to dream.

Dan

sergeyb replied on Sunday, November 23, 2008

You could actually do this in SQL server, although I am not sure if this is worth the effort either.  You could wrap your SP in try/catch blocks, trap excepti9ons that can occur, and Raise a custom error.  This is a lot of work though.

 

Sergey Barskiy

Principal Consultant

office: 678.405.0687 | mobile: 404.388.1899

cid:_2_0648EA840648E85C001BBCB886257279
Microsoft Worldwide Partner of the Year | Custom Development Solutions, Technical Innovation

 

From: dagware [mailto:cslanet@lhotka.net]
Sent: Sunday, November 23, 2008 12:56 PM
To: Sergey Barskiy
Subject: Re: [CSLA .NET] How to display a user friendly message for the user

 

The following isn't always possible, and I suppose there are even times when it's not worth the effort, but just in case you haven't thought about it (and please forgive me if I'm being patronizing):

Consider coding your business objects to detect this type of error before you do the save, typically when a new object is added to the list. This is often-times done with an "ExistsCommand" type of command object (ProjectTracker has these, but I don't know if it actually uses them or not).

Of course, even with due diligence you could still get the error, for instance if someone else added the item to the database between when you checked for its existence and when you actually saved the object.

I've often wished SQL Server returned errors in a documented way that would make it easy to parse the error into end-user-readable text. I guess I just like to dream.

Dan


Copyright (c) Marimer LLC