Extending the Notification Pattern Example

Recently I've been looking at implementing the Notification pattern as described by Martin Fowler here. The UI calls methods on the domain to validate the data which is held in a Data Transfer Object (DTO). The DTO contains both the data needed by the class (for reconstituting domain objects) and a class for holding errors returned by the validation method. It's a nice way to remove managing errors in your domain layer but still have them available to your presentation layer.

One thing I've always found with Fowlers posts is that the code is very thin (and it should be since it's just an example) and sometimes not complete. So for you guys I decided to put together a working example based on his sample for checking a policy claim number. A couple of things I've done to go beyond the example on Martin's page are:

  • Working solution file that compiles and runs (C# 2.0 although with slight modifications [removal of generics] it could work under 1.1)
  • Implementation of the Model View Presenter pattern. Martin uses the Autonomous View approach in his sample because he's really focused on Notification, but I thought it would be nice to show it implemented with MVP. Autonomous View is a pattern for putting all presentation state and behavior for a window in a single class, but that really doesn't support a base approach that I prefer namely MVP and separation of concerns so the MVP pattern is here for completeness.
  • Added Rhino mock tests to show how to test the presenter with a mocked out view. I thought this was important as the example is all about UI validation and this would be a good example to mock out a view using Rhino.

The Tests

It starts with the tests (it always starts with the tests). As I was re-implementing an example my tests were slighted a little bit towards how the sample worked. However I was focused on 3 main validations for the UI:

  • Policy Number is present
  • Claim Type is present
  • Incident Date is present and valid (cannot be set in the future)

With those tests in mind, here's the check for the missing policy number (with mock setup and teardown):

   20 [SetUp]

   21 public void SetUp()

   22 {

   23     _mockery = new MockRepository();

   24     _view = _mockery.CreateMock<IRegisterClaimView>();

   25 }

   26 

   27 [TearDown]

   28 public void TearDown()

   29 {

   30     _mockery.VerifyAll();

   31 }

   32 

   33 [Test]

   34 public void MissingPolicyNumber()

   35 {

   36     Expect.Call(_view.PolicyNumber).Return(INVALID_POLICY_NUMBER);

   37     Expect.Call(_view.IncidentDate).Return(VALID_INCIDENT_DATE);

   38     Expect.Call(_view.ClaimType).Return(VALID_CLAIM_TYPE);

   39     _view.ResponseMessage = "Not registered, see errors";

   40     _view.SetError("txtPolicyNumber", "Policy number is missing");

   41     _mockery.ReplayAll();

   42 

   43     RegisterClaimPresenter presenter = new RegisterClaimPresenter(_view);

   44     presenter.RegisterClaim();

   45 }

The constants are defined so it's easier to read and are defined like this:

   13 private const string INVALID_POLICY_NUMBER = "";

   14 private const string VALID_POLICY_NUMBER = "1";

   15 private const string INVALID_CLAIM_TYPE = "";

   16 private const string VALID_CLAIM_TYPE = "1";

   17 private static readonly DateTime INVALID_INCIDENT_DATE = DateTime.MinValue;

   18 private static readonly DateTime VALID_INCIDENT_DATE = DateTime.Now.AddDays(1);

The view is mocked out (which is what we're testing) so we expect calling the 3 properties of the view (that match up to the UI). There's also a ResponseMessage property which displayed if there are errors or not. The SetError method needs a bit of explaining.

In Martins example, he uses Autonomous View which is great and the way he resolves what control is causing what error it's easy to wire up. All the controls are there for the picking. When I implemented the MVP pattern I had a bit of a problem. I wasn't about to pollute my presenter with controls from the UI (otherwise it would be easy) so how could I get the view to wire up the right error message to the right control. The only way I could do it (in the implemenation of the view) was to pass in the control name as a string. Then in my view implementation I did this:

  115 public void SetError(string controlName, string errorMessage)

  116 {

  117     Control control = Controls[controlName];

  118     showError(control, errorMessage);

  119 }

Then showError just handles setting the error via the built-in .NET error provider:

   37 void showError(Control arg, string message)

   38 {

   39     _errorProvider.SetError(arg, message);

   40 }

Once I had the missing policy test working it was time to move onto the other requirements. MissingIncidentType and MissingIncidentDate are both the same (except there's no such thing as a null DateTime so I cheated a bit and returned DateTime.MinValue). The other check against the Incident Date is to ensure it's not set before the policy date. Since we don't have a policy screen I just stubbed it out in a stub Policy class and set it to the current date. So an invalid date would be something set in the past:

   76 [Test]

   77 public void CheckDateBeforePolicyStart()

   78 {

   79     Expect.Call(_view.PolicyNumber).Return(VALID_POLICY_NUMBER);

   80     Expect.Call(_view.ClaimType).Return(VALID_CLAIM_TYPE);

   81     Expect.Call(_view.IncidentDate).Return(VALID_INCIDENT_DATE.AddDays(-1));

   82     _view.ResponseMessage = "Not registered, see errors";

   83     _view.SetError("pkIncidentDate", "Incident Date is before we started doing this business");

   84     _mockery.ReplayAll();

   85 

   86     RegisterClaimPresenter presenter = new RegisterClaimPresenter(_view);

   87     presenter.RegisterClaim();

   88 }

The presenter is pretty basic. In addition to just registering the view and talking to it, it has one main method called by the view (when the user clicks the submit button) called RegisterClaim. Here it is:

   25 public void RegisterClaim()

   26         {

   27             saveToClaim();

   28             _service.RegisterClaim(_claim);

   29             if(_claim.Notification.HasErrors)

   30             {

   31                 _view.ResponseMessage = "Not registered, see errors";

   32                 indicateErrors();

   33             }

   34             else

   35             {

   36                 _view.ResponseMessage = "Registration Succeeded";               

   37             }

   38         }

Basically it calls saveToClaim (below) then calls a service layer method to register the claim. Information is stored in a Data Transfer Object which contains both the data from the view and any errors. The claim DTO has a Notification object which has errors (or not) and the presenter will tell the view if there are any problems, letting the view set the display accordingly.

First here's the saveToClaim method in the presenter that will create a RegisterClaimDTO and populate it with information from the view:

   67 private void saveToClaim()

   68 {

   69     _claim = new RegisterClaimDTO();

   70     _claim.PolicyId = _view.PolicyNumber;

   71     _claim.IncidentDate = _view.IncidentDate;

   72     _claim.Type = _view.ClaimType;

   73 }

The RegisterClaim method on the ClaimService object will just run it's own command (which does the actual registration of the claim and checks for any errors). The core part of the validation is in the Validate method on the RegisterClaim object:

   39 private void Validate()

   40 {

   41     failIfNullOrBlank(((RegisterClaimDTO)Data).PolicyId, RegisterClaimDTO.MISSING_POLICY_NUMBER);

   42     failIfNullOrBlank(((RegisterClaimDTO)Data).Type, RegisterClaimDTO.MISSING_INCIDENT_TYPE);

   43     fail(((RegisterClaimDTO)Data).IncidentDate == RegisterClaimDTO.BLANK_DATE, RegisterClaimDTO.MISSING_INCIDENT_DATE);

   44     if (isNullOrBlank(((RegisterClaimDTO)Data).PolicyId))

   45         return;

   46     Policy policy = FindPolicy(((RegisterClaimDTO)Data).PolicyId);

   47     if (policy == null)

   48     {

   49         Notification.Errors.Add(RegisterClaimDTO.UNKNOWN_POLICY_NUMBER);

   50     }

   51     else

   52     {

   53         fail((((RegisterClaimDTO)Data).IncidentDate.CompareTo(policy.InceptionDate) < 0),

   54             RegisterClaimDTO.DATE_BEFORE_POLICY_START);

   55     }

   56 }

Here it checks the various business rules and then uses the Notification object to keep track of errors. The Notification object is an object embedded in the Data Transfer Object, which is passed into the service when it's created so our service layer has access to the DTO to register errors as it does it's validation.

Finally coming back from the service layer, the presenter checks to see if the DTO's Notification object HasErrors. If it does, it sets the response message (mapped to a textbox in the UI) and calls a method called indicateErrors. This just runs through each error object in the DTO through a method to check the error:

   44 private void indicateErrors()

   45 {

   46     checkError(RegisterClaimDTO.MISSING_POLICY_NUMBER);

   47     checkError(RegisterClaimDTO.MISSING_INCIDENT_TYPE);

   48     checkError(RegisterClaimDTO.DATE_BEFORE_POLICY_START);

   49     checkError(RegisterClaimDTO.MISSING_INCIDENT_DATE);

   50 }

checkError is a method that takes in the Error object which contains both the error message and the control it belongs to. If the Notification list contains the error it's checking, it then calls that ugly SetError method on the view. This will update the UI with the appropiate error message attached to the correct control:

   56 private void checkError(Error error)

   57 {

   58     if (_claim.Notification.IncludesError(error))

   59     {

   60         _view.SetError(error.ControlName, error.ToString());

   61     }

   62 }

And that's about it. It's fairly simple but the sample has been broken down a bit further hopefully to help you understand the pattern better. 

Here's the class diagram for all the classes in the system:

 

And here's how the classes break down in the solution. Each folder would be a layer in your application (or split across multiple assemblies):

Application Layer
RegisterClaim.cs
ServerCommand.cs

Domain Layer
Policy.cs

Presentation Layer
FrmRegisterClaim.cs
IRegisterClaimView.cs
Program.cs
RegisterClaimPresenter.cs

Service Layer
ClaimService.cs
DataTransferObject.cs
Error.cs
Notification.cs
RegisterClaimDTO.cs

So other than the ugly SetError method which takes in a string for a control name, I think this isn't bad. Maybe someone has a suggestion out there how to get rid of the control name string but like I said, I didn't want to introduce UI controls into my presenter so I'm not sure (other than maybe an enumeration) how to hook up the right error message with the right control. Feel free to offer a suggestion for improvement here. 

You can download the entire solution with source code here. Enjoy!

6 Comments

  • Thanks for the sample. I'm doing something in the near future that will require something exactly like this.

    On thought that jumps to mind about getting rid of the control name string is to maybe use something like a decorator?

    i.e.

    interface IValidatableData
    {
    T Value { get; }
    void SetError(string message);
    }

    Then have your View look like:
    public interface IRegisterClaimView
    {
    IValidateableData PolicyNumber { get; }
    IValidateableData IncidentDate { get; }
    IValidateableData ClaimType { get; }
    string ResponseMessage { set; }
    }

    Then maybe your presenter can call something like _view.PolicyNumber.SetError("Some Error Message");

    The concrete IValidatableData implementations would then contain the logic for getting the error message to the correct ErrorProvider control.

    Does this make sense? Or perhaps this is an over-engineered solution to the problem?

    When I get to working with this, I'll try to post an update to your solution that hopefully explains what I mean a bit better.


  • @Tom: Hmmm, that looks like it might work. Nice idea. I'll give it a whirl and maybe post an update if it works well.

  • Here's a different variation.

    What about instead having the view register with the presenter.

    1. First you will have a simple wrapper interface IUIMember.

    2. Next instead of exposing properties on the view, the presenter will have a SetUIMembers(IUIMember PolicyNumber, IUIMember IncidentDate, IUIMember ClaimType) method.

    3. When the View instantiates, it calls SetUIMembers on the presenter passing in the controls either implementing IUIMember or encapsulated in an IUIMember container. In the imp of SetUIMembers the 3 private members _PolicyNumber, _IncidentDate, and _ClaimType are set.

    4. Then your view simply exposes one method SetError(string message, IUIMember member)

    5. Finally when you need to set an error, you just call SetError passing in the message and the appropriate member.

    6. Code to SetError is simply pulling out the control and setting the error value.

    This model makes the interface less chatty with only one additional method on the presenter.



  • @Glenn and @Tom: Thanks for the info. I'll probably post a follow-up with both ideas as options. I like Glenn's idea as it keeps things clean. There's an approach to MVP using this that I recently read about (having the view populate values for the presenter vs. the presenter pulling them from the view).

  • I am not able to download the sample file.

  • Thank you very much. I know this is an old post, but I could not find a solution anywhere else to using the ErrorProvider with MVP. Your sample pointed me in the right direction. I know is ugly, but the SetError does work nicely for me. All I had to do was add a dictionary with the property name as key and the control name as value and this fitted perfectly with my validation scheme.

    Again, Thank you.

Comments have been disabled for this content.