SpecExpressImplementation

First of all this implementation is not using the EntityFramework that we will probably go to in future releases. Rather it is an elegant addition to our current hack (e.g. the ongoing effort to organize the growing mire into something other than the big ball of mud).

Procedure for implementation on each Data Object

Step 1: Write the Specification

A specification looks like this:

   1     public class PartIdentificationDataSpecification : Validates<PartIdentificationData>
   2     {
   3         public PartIdentificationDataSpecification()
   4         {
   5             IsDefaultForType();
   6             Check(c => c.locusIdentity).Required();
   7             Check(c => c.locusNumber).Required().GreaterThan(0);
   8             Check(c => c.site).Required();
   9             Check(c => c.season).Required();
  10             Check(c => c.field).Required();
  11             Check(c => c.square).Required();
  12             Check(c => c.DateStart).Required();
  13             Check(c => c.DateEnd).Optional();
  14             Check(c => c.supervisor).Required();
  15             Check(c => c.balk).Optional();
  16             Check(c => c.designation).Optional();
  17         }
  18     }

For Association Objects which are bound to DataGridView objects, you must go the extra mile to make sure there are no duplicates

The following is from MaterialsUsedAscSpecification

   1 public class MaterialsUsedAscSpecification : Validates<MaterialsUsedAsc>
   2     {
   3         public MaterialsUsedAscSpecification()
   4         {
   5             IsDefaultForType();
   6             Check(c => c.MaterialID).Required().Expect((m, b) => 
   7                   MaterialsUsedAscSpecification.isUnique((MaterialsUsedAsc)m), 
   8                   "This is not unique and will not be saved");
   9             Check(c => c.Qualifiers).Optional();
  10             Check(c => c.Percentage).Required().Between(0, 100);
  11         }
  12 
  13         public static bool isUnique(MaterialsUsedAsc m)
  14         {
  15             if (m.createdFromDB() && !m.changed())
  16                 return true;
  17             try
  18             {
  19                 LocusDataArchitectural locus = (LocusDataArchitectural)StaticData.getLocus(m.locusIdentity);
  20                 if (locus == null) return false;
  21                 int count = locus.secDescription.MaterialsUsed.Count(r =>
  22                     r.locusIdentity == m.locusIdentity &&
  23                     r.supplementID == m.supplementID &&
  24                     r.MaterialID == m.MaterialID);
  25                 return count <= 1;
  26             }
  27             catch (Exception e)
  28             {
  29                 System.Windows.Forms.MessageBox.Show(
  30                   "Error in validating Materials Used - see MaterialsUsedAscSpecification.cs.\r\n\r\n" +
  31                   e.Message);
  32             }
  33             return false;
  34         }
  35     }

In addition to this we need to have the object initialized, so we have the parent object containing the List Listen to the AddingNew event of the BindingList. The listening method (in PartArchitecturalDescription) looks like the following:

   1         public void addNewMaterialsUsedList(object sender, AddingNewEventArgs e)
   2         {
   3             MaterialsUsedAsc mua = new MaterialsUsedAsc();
   4             mua.locusIdentity = this.parentLocus.locusIdentity;
   5             mua.supplementID = StaticData.ZERO;
   6             e.NewObject = mua;
   7         }

Step 2: Modify the Controls for each object

For each object there are "Controls" that were POCO that must be changed into inheriting from UserControl. This is necessary to validate properly (don't ask its a long story).

  1. Inherit form UserControl (01)

  2. Add a private System.ComponentModel.IContainer components (02)

  3. Create a constructor that calls InitializeComponent() - this is a change from what we have done. We always did want to make these identical in that sense. So do it now! (03)

  4. Now, change the name of Initialize... to InitializeComponent and make the changes shown in (04)

  5. It appears that you must change the taborder in a different place. SO, you will have to move these into a private function and call it after you call InitializeComponents().

  6. YOU ARE NOT DONE UNTIL YOU CAN VIEW THE CONTROL IN DESIGN VIEW. AND WHATEVER YOU DO, DO NOT CHANGE THE PANEL ON THE CONTROL! You may change the location of the fields to allow the ErrorProvider to put in the little red warning labels though.

Now you have a real component that can be used as such in the future.

   1      ...
   2 (01) public class IdentificationControls : UserControl
   3 
   4      ...
   5 
   6 (02) private System.ComponentModel.IContainer components;
   7 
   8      ...
   9 
  10 (03) public IdentificationControls()
  11      {
  12          InitializeComponent();
  13      }
  14 
  15      ...
  16 
  17 (04) public void InitializeComponent()
  18      {
  19          components = new Container();
  20 
  21          ...
  22 
  23          //Add the controls to the user control
  24          Controls.AddRange(new System.Windows.Forms.Control[]
  25          {
  26              this.panelIdentification
  27          });
  28      }

Step 3: Implement Verification in the BusinessObject (those under Data)

  1. Add the IDataErrorInfo interface to the Class (01)
  2. Add a new property: string Error (02)
  3. Add two private variables: BindingSource myBindingSource (03) and ErrorProvider myErrorProvider (04)

  4. Add instantiation of the two new private variables and set their values as shown in in (05) - (09). This effectively binds the graphic elements of the component to this object and allows the IDataErrorInfo to worry about the validation.
  5. Add the required interface method in the Interface Methods region (10) - (11). This is what actually calls the Spec Express Framework.
  6. The last step here is less concrete, but should be simple enough: Before an object is saved, it must be validated!
    1. Make sure any public call to save, only saves a valid object! (see 12)
    2. Make sure every object returns a bool for save==true to indicate success!
    3. Success should only be reported if child objects also save==true successfully.
    4. to do this, add a public isValid() (13) command to each dataobject.

   1 (01) public class PartIdentificationData : IDataErrorInfo
   2 
   3    ...
   4 
   5 (02) //for validation purposes
   6      public string Error
   7      {
   8         get
   9         {
  10             return _lastError;
  11         }
  12      }
  13 
  14    ...
  15 
  16 (03) private BindingSource myBindingSource;
  17 (04) private ErrorProvider myErrorProvider;
  18 
  19    ...
  20 
  21    private void buildControl()
  22    {
  23        //The next two lines create a BindingSource for validation purposes
  24 (05)   myBindingSource = new BindingSource();
  25 (06)   myBindingSource.DataSource = this;
  26        
  27 
  28        //Get the "View"
  29        IdentificationControls idcs = new IdentificationControls();
  30        idcs.InitializeIdentificationControls();
  31        idCtrl = idcs.panelIdentification;
  32 
  33        //Create an Error Provider to show errors to the user.
  34 (07)   myErrorProvider = new ErrorProvider();
  35 (08)   myErrorProvider.DataSource = myBindingSource;
  36 (09)   myErrorProvider.ContainerControl = idcs;
  37 
  38    ...
  39 
  40 (10)
  41    #region Interface Methods
  42 
  43    public string this[string fieldName]
  44    {
  45        get
  46        {
  47           try
  48           {
  49                switch (fieldName)
  50                {
  51                    case "Error": return _lastError;
  52                    default:
  53                        _lastError = ValidationExpressions.getErrorMessage(this, fieldName);
  54                        break;
  55                }
  56            }
  57            catch (Exception ae)
  58            {
  59                System.Windows.Forms.MessageBox.Show("PartIdentificationData through an exception while trying to validate Field\"" + fieldName + "\"\n\n" + ae.Message);
  60            }
  61            return _lastError;
  62        }
  63    }
  64 
  65 (11)
  66    #endregion
  67 
  68 
  69 (12)    //Check all public save* methods return bool: true if valid e.g. if saved. For example
  70 
  71         public bool saveRow()
  72         {
  73             bool rval;
  74             if ( (rval=isValid()) )
  75             {
  76                 row.LocusIdentity = locusIdentity;
  77                 row.Reason = reason;
  78                 row.TopSepClarityID = topSepClarityID; // int.Parse(topSepClarityID);
  79                 row.BotSepClarityID = botSepClarityID; // int.Parse(botSepClarityID);
  80                 row.EndEdit();
  81                 if (!LoadedFromTable)
  82                 {
  83                     rationaleTable.AddARCH_RationaleRow(row);
  84                     LoadedFromTable = true;
  85                 }
  86                 tam.ARCH_RationaleTableAdapter.Update(row);
  87             }
  88             return rval;
  89         }
  90 
  91 (13)   //Add the following 
  92 
  93         public bool isValid()
  94         {
  95             var notification = ValidationCatalog.Validate(this);
  96             return notification.IsValid;
  97         }

Step 4: Correct the Errors

In changing the name of one of the control methods, you should see an error in your data object. The line can be safely deleted.

Step 5: Troubleshoot

So your form still doesn't have the beautiful little red circles? Perhaps you have not added an ErrorProvider to the form?

  1. Add an ErrorProvider to the form

  2. Set the DataSource property of the ErrorProvider to the same BindingSource used by your controls.

  3. Make sure that the ErrorProvider is set to use your form as the ContainerControl

ArchaeologyProject/SpecExpressImplementation (last edited 2011-05-22 22:40:37 by 71-87-243-84)