Hello,
I've a problem with the current implementation of the error handling.
Suppose the user is clicking on the OK button.(form ProjectEdit from the example of the book) Suppose the database throws an error because there is some incorrect data. The function RebindUI will catch the error (Csla.DataPortalException) and absorb the error. Then code continues to execute in the procedure OKButton_Click, the statement "this.Close()" will be called which will close the form without the user letting the possibility to correct the data. Is it possible to provide some code where it would be possible for the user to correct some things if the database is throwing an error?
Below follows the code:
Class ProjectEdit.cs
private void OKButton_Click(object sender, EventArgs e)
{
using (StatusBusy busy = new StatusBusy("Saving..."))
{
RebindUI(true, false);
}
this.Close();
}
private void RebindUI(bool saveObject, bool rebind)
{
// disable events
...
try
{
// unbind the UI
...
// save or cancel changes
if (saveObject)
{
_project.ApplyEdit();
try
{
_project = _project.Save();
}
catch (Csla.DataPortalException ex)
{
MessageBox.Show(ex.BusinessException.ToString(),
"Error saving", MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(),
"Error Saving", MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
}
}
else
_project.CancelEdit();
}
finally
{
// rebind UI if requested
...
}
}
}
Absolutely! This is just UI code, and is not part of CSLA at all. You can write your UI code however you'd like!
(keeping in mind that data binding imposes some strict rules you have to live with)
I've replaced the code with the following. So I've put the try catch statement in the OKButton_Click event. Apparently you are first trying to see if it is a Csla.DataPortalException. I've seen that this DataPortalException has an interesting property BusinessObject which gives access to the business object at the time of the exception. This is really interesting for a developer to know what data the user entered on the form in case of an exception.
Do you have some generic piece of code to log all the data of the businessobject?
private void OKButton_Click(object sender, EventArgs e)
{
try
{
using (StatusBusy busy = new StatusBusy("Saving..."))
{
RebindUI(true, false);
}
this.Close();
}
catch (Csla.DataPortalException ex)
{
MessageBox.Show(ex.BusinessException.ToString(),
"Error saving", MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(),
"Error Saving", MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
}
}
private void RebindUI(bool saveObject, bool rebind)
{
// disable events
...
try
{
// unbind the UI
...
// save or cancel changes
if (saveObject)
{
_project.ApplyEdit();
_project = _project.Save();
}
else
_project.CancelEdit();
}
finally
{
// rebind UI if requested
...
}
}
}
On a related note, we had similar issues with "in grid" editing using the DevExpress XtraGrid. Once the user leaves the row, no further corrections are possible.
Most grid, including this one, provide for the capability to stop the row focus from changing, but we had all sorts of weird issues -- like you couldn't even click on some totally unrelated window. Validation exceptions where flying everywhere...
At least for the time being, we have resolved this problem by discarding the users changes if we ge a validation exception when no further correction is readily feasible. Not optimal, but if the user is ignoring errors displayed to them...
Hi Lothka,
I've added the following code and tested it and everything works as expected.
Can you review the code and maybe if you find it usefull at it for your next release?
I've added these new methods to the interface IUndoableObject or would you prefer to put these methods to another interface?
Below you can find the code which is needed:
1) Coded added to the class IUndoableObject
/// <summary> /// Alef:Dump the entire state of an object graph (root object, its child objects, etc) to the output window /// in the VS.NET IDE while debugging /// </summary> /// <remarks> /// http://www.lhotka.net/Article.aspx?id=3967df85-ee82-4266-a131-7a3e8e08f5be /// </remarks> void DumpState(); /// <summary> /// Alef:Dump the entire state of an object graph (root object, its child objects, etc) to human readable XML /// (for logging purposes). /// </summary> /// <remarks> /// If an exception occurs during the normal data portal processing, a Csla.DataPortalException will be thrown. To get /// at the original exception thrown by the business code, use the BusinessException property. Remember /// that you can also use the BusinessObject property to get a reference to the business object as it /// was when the exception was thrown — a fact that can be very useful for debugging. /// We can use e.g. the following code in the UI to log the entire state of an object graph when an /// exception occurs: /// EnterpriseHelper.Log(((Csla.Core.IUndoableObject)ex.BusinessObject).ToXML(), "Exceptions"); /// </remarks> string ToXML(); /// <summary> /// Alef:Dump the entire state of an object graph (root object, its child objects, etc) to human readable XML /// (for logging purposes). /// </summary> /// <param name="objXmlWriter"> /// The XmlWriter object that you want to use as the underlying writer. /// </param> void DumpToXML(ref XmlWriter objXmlWriter);2) Coded added to the class UndoableBase
#region
"Alef: Logging/Debugging functions" void IUndoableObject.DumpState(){
DumpState();
}
string IUndoableObject.ToXML(){
return ToXML();}
void IUndoableObject.DumpToXML(ref XmlWriter objXmlWriter){
DumpToXML(
ref objXmlWriter);}
#region
" To Output Window " /// <summary> /// Alef:Dump the entire state of an object graph (root object, its child objects, etc) to the output window /// in the VS.NET IDE while debugging /// </summary>[
EditorBrowsable(EditorBrowsableState.Never)] protected internal void DumpState(){
Type currentType = this.GetType(); Hashtable state = new Hashtable(); string fieldName; FieldInfo[] fields; Debug.IndentSize = 2; string currentTypeName = currentType.Name; Debug.WriteLine("OBJECT " + currentType.Name); do { // get the list of fields in this typefields = currentType.GetFields(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach (FieldInfo field in fields){
// make sure we process only our variables if (object.ReferenceEquals(field.DeclaringType, currentType)){
fieldName = field.DeclaringType.Name +
"!" + field.Name; // see if this field is marked as not undoable if (!NotUndoableField(field)){
// the field is undoable, so it needs to be processed if (typeof(Csla.Core.IUndoableObject).IsAssignableFrom(field.FieldType)){
// this is a child collection or a child object, cascade the call Debug.Indent(); Debug.WriteLine("CHILD " + fieldName); object value = field.GetValue(this); // make sure the variable has a value if (value != null){
// this is a child object, cascade the call((Core.
IUndoableObject)value).DumpState();}
else{
// variable has no value - store that fact Debug.WriteLine("<Nothing>");}
Debug.Unindent();}
else{
// this is a normal field, simply trap the value if ((field.GetValue(this) != null)){
Debug.WriteLine(fieldName + ": " + field.GetValue(this).ToString());}
else{
Debug.WriteLine(fieldName + ": <Nothing>");}
}
}
else{
// field is not undoable if ((field.GetValue(this) != null)){
Debug.WriteLine("<NotUndoable()> " + fieldName + ": " + field.GetValue(this).ToString());}
else{
Debug.WriteLine("<NotUndoable()> " + fieldName + ": <Nothing>");}
}
}
}
currentType = currentType.BaseType;
}
while (currentType != typeof(UndoableBase)); Debug.WriteLine("END OBJECT " + currentTypeName);}
#endregion
#region
" ToXML " /// <summary> /// Alef:Dump the entire state of an object graph (root object, its child objects, etc) to human readable XML /// (for debugging purposes) /// </summary>[
EditorBrowsable(EditorBrowsableState.Never)] protected internal string ToXML(){
return ToXML(true, "CSLA_Object");}
private string ToXML(bool includeDocumentTag, string rootElementName)
{
MemoryStream objMS = new MemoryStream(); XmlWriterSettings settings = new XmlWriterSettings(); // Write individual elements on new lines and indentsettings.Indent =
true; // Indent with Tabssettings.IndentChars =
"\t"; XmlWriter writer = XmlWriter.Create(objMS, settings); // Write XML data. if (includeDocumentTag){
// Begin with our XML Header // Standalone: Dit attribuut is een aanwijzing voor de verwerker dat dit document wel of niet // het gebruik van een extern DTD-bestand of een extern entiteitenbestand vereist. Als je een // goed opgesteld XML-document maakt, gebruik je geen externe DTD, dus dan wordt de waarde yes.writer.WriteStartDocument(
true);}
writer.WriteStartElement(rootElementName);
this.DumpToXML(ref writer); //RootElementNamewriter.WriteEndElement();
//Commit everything to our underlying MemoryStreamwriter.Flush();
// Set the position to the beginning of the streamobjMS.Position = 0;
//Create a streamreader object based on our Memory Stream StreamReader objsr = new StreamReader(objMS); // Return the entire contents of our MemoryStream return objsr.ReadToEnd();}
/// <summary> /// Alef:Dump the entire state of an object graph (root object, its child objects, etc) to human readable XML /// </summary> /// <param name="objXmlWriter"> /// The XmlWriter object that you want to use as the underlying writer. /// </param>[
EditorBrowsable(EditorBrowsableState.Never)] protected internal void DumpToXML(ref XmlWriter objXmlWriter){
Type currentType = this.GetType(); Hashtable state = new Hashtable(); string fieldName; FieldInfo[] fields;objXmlWriter.WriteStartElement(currentType.Name);
do { // get the list of fields in this typefields = currentType.GetFields(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach (FieldInfo field in fields){
// make sure we process only our variables if ((field.DeclaringType == currentType)){
fieldName = field.Name;
// see if this field is marked as not undoable if (!NotUndoableField(field)){
// the field is undoable, so it needs to be processed if (typeof(Csla.Core.IUndoableObject).IsAssignableFrom(field.FieldType)){
// this is a child collection or a child object, cascade the callobjXmlWriter.WriteStartElement(fieldName);
object value = field.GetValue(this); // make sure the variable has a value if (value != null){
// this is a child object, cascade the call((Core.
IUndoableObject)value).DumpToXML(ref objXmlWriter);}
else{
// variable has no value - store that factobjXmlWriter.WriteElementString(
"Value", "Nothing");}
objXmlWriter.WriteEndElement();
}
else{
// this is a normal field, simply trap the value if (!(field.GetValue(this) == null)){
objXmlWriter.WriteElementString(fieldName, field.GetValue(
this).ToString());}
else{
objXmlWriter.WriteElementString(fieldName,
"Nothing");}
}
}
else { // field is not undoable if (!(field.GetValue(this) == null)){
objXmlWriter.WriteElementString(fieldName, field.GetValue(
this).ToString());}
else{
objXmlWriter.WriteElementString(fieldName,
"Nothing");}
}
}
}
currentType = currentType.BaseType;
}
while (currentType != typeof(UndoableBase));objXmlWriter.WriteEndElement();
}
// ToXML_Header and ToXML_Footer are not redundant because // we had some instances where we were using the methods to log the state of objects // in the middle of a complex business operation, so we would create a single xml document // that had a "before" and "after" version of a business object. // All in one xml doc. So, we needed that ability to write the header and footer seperately. private string ToXML_Footer(string RootElementName){
//</CSLA_Object> return ("\r\n" + "</" + RootElementName + ">" + "\r\n");}
private string ToXML_Header(string RootElementName){
//<?xml version="1.0" encoding="utf-8" standalone="yes"?> //<CSLA_Object> return ("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" + "\r\n" + "<" + RootElementName + ">" + "\r\n");}
#endregion
#endregion
3) Coded added to the class BusinessListBase
#region
"Alef: Logging/Debugging functions" void Core.IUndoableObject.DumpState(){
DumpState();
}
string Core.IUndoableObject.ToXML(){
return ToXML();}
void Core.IUndoableObject.DumpToXML(ref XmlWriter objXMLTW){
DumpToXML(
ref objXMLTW);}
private void DumpState(){
// this is a child collection, cascade the call Debug.Indent(); foreach (C child in this)((Core.
IUndoableObject)child).DumpState(); Debug.Unindent();}
private string ToXML(bool includeDocumentTag, string rootElementName){
MemoryStream objMS = new MemoryStream(); XmlWriterSettings settings = new XmlWriterSettings(); // Write individual elements on new lines and indentsettings.Indent =
true; // Indent with Tabssettings.IndentChars =
"\t"; XmlWriter writer = XmlWriter.Create(objMS, settings); // Write XML data. if (includeDocumentTag){
// Begin with our XML Header // Standalone: Dit attribuut is een aanwijzing voor de verwerker dat dit document wel of niet // het gebruik van een extern DTD-bestand of een extern entiteitenbestand vereist. Als je een // goed opgesteld XML-document maakt, gebruik je geen externe DTD, dus dan wordt de waarde yes.writer.WriteStartDocument(
true);}
writer.WriteStartElement(rootElementName);
DumpToXML(
ref writer); //RootElementNamewriter.WriteEndElement();
//Commit everything to our underlying MemoryStreamwriter.Flush();
// Set the position to the beginning of the streamobjMS.Position = 0;
//Create a streamreader object based on our Memory Stream StreamReader objsr = new StreamReader(objMS); // Return the entire contents of our MemoryStream return objsr.ReadToEnd();}
private string ToXML(){
return ToXML(true, "CSLA_Collection");}
private void DumpToXML(ref XmlWriter objXMLTW){
// this is a child collection, cascade the call foreach (C child in this)((Core.
IUndoableObject)child).DumpToXML(ref objXMLTW);}
#endregion
3) Code in the UI
try{
using (StatusBusy busy = new StatusBusy("Saving...")){
RebindUI(
true, false);}
this.Close();}
catch (Csla.DataPortalException ex){
EnterpriseHelper.Log(((Csla.Core.IUndoableObject)ex.BusinessObject).ToXML(), "Exceptions"); if (EnterpriseHelper.HandleException(ex.BusinessException)) throw; //MessageBox.Show(ex.BusinessException.ToString(), // "Error saving", MessageBoxButtons.OK, // MessageBoxIcon.Exclamation);}
I've moved to version 3.5.2 of the csla framework.
I need again this possibility to dump the object state.
But in version 3.5.2 I now get an error:
type Csla.Core.FieldManager.FieldDataManager can't be casted to Csla.Core.IUndoableObject
Apparently FieldDataManager is something new in this version. Lhotka, can you tell what I have to change so that the code works again?
Below an extract of the code where it fails: (see in previous reply for the whole code)
protected internal void DumpToXML(ref XmlWriter objXmlWriter)
{
....
object value = field.GetValue(this);
// make sure the variable has a value
if (value != null){
// this is a child object, cascade the call((Core.IUndoableObject).DumpToXML(
ref objXmlWriter); >>>>>>> it is failing here}
else{
}
The field manager is new in 3.5, and offers an alternative to
storing your field data in private fields. I added this to simplify the
creation of CSLA .NET for Silverlight, which doesn’t allow reflection
against private fields (and so doesn’t have the BinaryFormatter or
NetDataContractSerializer).
You are basically writing a serializer. And that’s fine in
.NET. But in CSLA 3.5 and higher you’ll need to serialize private fields
like you are doing, AND you’ll need to serialize the field manager in a
different manner. Look at Csla\Serialization\Mobile\MobileFormatter to get some
ideas.
Rocky
The MobileFormatter exists in both Silverlight and Windows. The reason
for MobileFormatter is to allow the data portal to communicate between
Silverlight and Windows, and it is necessary because Silverlight doesn’t
have a powerful enough serializer to meet the data portal’s needs. This
blog post may help
http://www.lhotka.net/weblog/CSLALightSerializationImplementation.aspx
I do not plan to add a DumpState() concept to the framework.
This could (and should) be constructed as an external tool (DLL), and isn’t
something that I think belongs in the framework itself.
Rocky
Can you show me how to externalize this code?
rsbaker0:On a related note, we had similar issues with "in grid" editing using the DevExpress XtraGrid. Once the user leaves the row, no further corrections are possible.
Most grid, including this one, provide for the capability to stop the row focus from changing, but we had all sorts of weird issues -- like you couldn't even click on some totally unrelated window. Validation exceptions where flying everywhere...
At least for the time being, we have resolved this problem by discarding the users changes if we ge a validation exception when no further correction is readily feasible. Not optimal, but if the user is ignoring errors displayed to them...
Sorry that this is a bit off topic and late ;-)
I'm working on a Dev Express based project atm and they seem to think that it is ok to just swallow every error :-( I have found that often you just need to handle BaseEdit.InvalidValue and throw the error yourself (or change the default behaviour to throw instead of swallow).
Copyright (c) Marimer LLC