Tuesday, December 9, 2008

My View is dumber than yours, but it sure smells better!


Download complete solution
Run Webform implementation
Run Ajax implementation



So, I'm a big fan of the MVP passive view pattern. If you don't know the benefits of this pattern, I'm not gunna take the time in this post to explain them, you can use google.

However, I think that the most common implementation of MVP falls just a bit short. Typically, with the passive view pattern, the presenter exposes public methods that the view calls. While the methods are fully testable, I think that having the view have intimate knowledge of its presenter fails the smell test. In my mind, the dumber untestable code is, the better. A view should only know about itself and the existance of a presenter, not what the presenter does.

I think the solution to this problem is using the MVP with the Observer pattern. Marrying the two is simple enough, and there’s no excuse (IMO) not to.  So I decided to make a quick example….

For this example I made a very simple domain model, consisting of one object.

The Code?

namespace MyDumbView.DomainModel

{

    public class Person

    {

        public virtual int PersonId { get; set; }

 

        public virtual string FirstName{get;set;}

 

        public virtual string LastName{get;set;}

       

    }

}

I then created a simple, generic Repository interface for my Presenters consumption.  

namespace MyDumbView.Repositories

{

    public interface IRepository<T>

    {

        ///

        /// Returns the with the Id

        ///

        ///

The id of the

        ///

        T GetById(int id);

        ///

        /// Persists to sent.

        ///

        ///

The entity to persist

        /// The primary Key of the object persisted

        int Save(T entity);

        ///

        /// Returns all instances of in the system

        ///

        ///

        T[] GetAll();

        ///

        /// Deletes the entity

        ///

        ///

        void Delete(T entity);

       

    }

}

 

A discussion about why I went the generics route is kinda outside the scope of this post, but yeah, it saves a lot of time, because I can now make an interface for getting Person just by doing this.

using MyDumbView.DomainModel;

 

namespace MyDumbView.Repositories

{

    public interface IPersonRepository:IRepository<Person>

    {

       

    }

}

Yeah, I know that my model only had one object, so this didn’t really save anything, but I couldn’t help myself! Anyway, we’ve digressed, this post is supposed to be about MVP / Observer pattern, so let’s get back on track.

Since I’ve got my domain model and repository interface done (if you wanna see the implementation of the repo, look at the attached solution, a discussion of it is outside the scope of this post), it’s now time for the view.

I want this view to support saving and editing of a Person object.

using System;

using MyDumbView.DomainModel;

 

namespace MyDumbView.Presentation.Views

{

    ///

    /// A basic view that allows for the creation and editing of a

    ///

    public interface IPersonEditorView

    {

        ///

        /// The Id of the Person

        ///

        int? PersonId { get; set; }

        ///

        /// The FirstName

        ///

        string FirstName { get; set; }

        ///

        /// The LastName

        ///

        string LastName { get; set; }

        ///

        /// The event to fire when the should be persisted

        ///

        event EventHandler PersonSaved;

        ///

        /// The event to fire when a Person should be loaded from the persistance mechanism.

        ///

        /// PersonId must be set

        event EventHandler PersonLoaded;

 

 

    }

}

Note the use of the two events, PersonSaved and PersonLoaded. Instead of the implementer of this interface calling a PersonSaved method on the presenter, the Presenter will simply subscribe to the event and execute the appropriate method. This means the view only understands what’s going on with itself, and does not have intimate knowledge of the Presenter.

Since I’m a firm believer in TDD, I simply stubbed out the presenter, but DID NOT write any code beyond that. I even commented the names of the unit tests that would be used to test the functionality of the methods. This only takes a few moments and makes unit testing much easier.

Here’s the stubbed code

using System;

using MyDumbView.DomainModel;

using MyDumbView.Presentation.Views;

using MyDumbView.Repositories;

 

namespace MyDumbView.Presentation.Presenters

{

 

    ///

    /// Presenter for an .

    ///

    public class PersonEditorPresenter

    {

        private readonly IPersonEditorView _view;

        private readonly IPersonRepository _repository;

         ///

        /// Creates an instance of

        ///

        ///

the for this presenter

        public PersonEditorPresenter(IPersonEditorView view)

            : this(view, new HttpCachePersonRepository())

        {

        }

        ///

        /// Dependency incjector constructor, allows unit tests to set the

        /// object to use.

        ///

        ///

the for this presenter

        ///

an Instance of to handle persistance

        internal PersonEditorPresenter(IPersonEditorView view, IPersonRepository repository)

        {

             //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterRegistersForEvents()

           

        }

        ///

        /// Executed when the is raised.

        ///

        ///

        ///

        void LoadPerson(object sender, EventArgs e)

        {

            //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonLoadedWithoutIdTest()

            //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonLoadedTest()

             

        }

        ///

        /// Executed when the event is raised.

        ///

        ///

        ///

        void SavePerson(object sender, EventArgs e)

        {

            //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonGetsAddedTest()

            //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonGetsEditedTest()

        }

       

    }

 

 

  

}

 

 

Note the 2 constructors on this object. For unit testing I’ll use the internal constructor by setting the friend attribute on MyDumbView assembly in AssemblyInfo.cs.

[assembly: InternalsVisibleTo("MyDumbView.UnitTests")]

After completing the stubbed presenter, I compiled the project. After a good compile, it was time to start writing the unit tests.

I’m a Rhino Mocks fan, and use it extensively… I’ve found that the Object Mother pattern saves a tremendous amount of time, and makes my unit tests much less brittle.

The way I typically do an Object Mother is create readonly fields that expose the values the mocked objects will return as well as fields that return the actual Mocked object, so you can add more specific expectation or results based on the needs of the specific unit test. This makes assertions nice and simple and makes unit tests much easier to construct.  See below…


public readonly IPersonRepository Repository;

public readonly int SavedPersonId = 2;

 

public readonly int LoadedPersonId = 1;

public readonly Person LoadedPerson;

public readonly string LoadedPersonFirstName = "First Name";

public readonly string LoadedPersonLastName = "Last Name";

private int? _saveResults;

 

Should these be properties? Maybe, but I’m not that concerned since this object will only be consumed by unit tests.

In the constructor, I create the instances of the mocked objects. It accepts and instance of MockRepository to use for creating mocked objects.

///

        /// Default Constructor

        ///

        ///

The instance of to set up mock objects.

        public PersonRepositoryMother(MockRepository mocker)

        {

            Repository = mocker.DynamicMock<IPersonRepository>();

            LoadedPerson = mocker.Stub<Person>();

        }

 

I then create a method that sets up the results for the mocked objects. I like calling it Record(), since it’s always called in a MockRepository’s Record block.

///

        /// Method that sets up expectations for all objects.

        /// It should be called during a block.

        ///

        public void Record()

        {

            //Person

            LoadedPerson.FirstName = LoadedPersonFirstName;

            LoadedPerson.LastName = LoadedPersonLastName;

            LoadedPerson.PersonId = LoadedPersonId;

 

            //Repository

            SetupResult.For(Repository.GetById(LoadedPersonId)).Return(LoadedPerson);

            SetupResult.For(Repository.Save(null)).IgnoreArguments().Return(SaveResults);

        }

 

What is SaveResults? I decided that I’m probably going to need the repository to return a different PersonId based on whether we’re saving or editing a Person object, so I created a property that consumers can set. Nothing special here.

///

        /// The results to return for a call to Repository.Save()

        ///

        public int SaveResults

        {

            private get

            {

                return _saveResults.HasValue ? _saveResults.Value : SavedPersonId;

            }

            set

            {

                _saveResults = value;

            }

        }

With my Object Mother complete, I now have a mocked up version of my repository, my own testable version of a database (or whatever the persistence mechanism is).

Now, on to unit tests: I’ve already mentioned the unit tests needed in my stubbed presenter. I make my unit tests clear and concise as possible. I look at them as the developers spec. To me, unit tests are a heck of a lot easier to read than the specs that BA’s give me, and they enforce requirements a LOT better….

First, the Unit test for the constructor. All it has to do is ensure that the presenter subscribes to the events that the view exposes.

///

        /// This test ensures that the registers for

        /// all events in the .

        ///

        [Test]

        public void PersonEditorPresenterRegistersForEvents()

        {

            var mocker = new MockRepository();

            var mother = new PersonRepositoryMother(mocker);

            var view = mocker.DynamicMock<IPersonEditorView>();

            using(mocker.Record())

            {

                //Expectations

                view.PersonLoaded += null;

                LastCall.IgnoreArguments();

                view.PersonSaved += null;

                LastCall.IgnoreArguments();

            }

           using(mocker.Playback())

           {

              new PersonEditorPresenter(view, mother.Repository);

           }

           

           

        }

 

Simple enough… Of course, this unit test fails, since the constructor of of PersonEditorPresenter does nothing at all yet. Let’s go and fix that. Looking at the unit test, we know exactly what we need to do to make the test turn green.

///

                      

    /// Presenter for an .

    ///

    public class PersonEditorPresenter

    {

        private readonly IPersonEditorView _view;

        private readonly IPersonRepository _repository;

        ///

        /// Creates an instance of

        ///

        ///

the for this presenter

        public PersonEditorPresenter(IPersonEditorView view)

            : this(view, new HttpCachePersonRepository())

        {

            // DO NOT PUT LOGIC HERE, use the

            // PersonEditorPresenter(IPersonEditorView view,IPersonRepository repository)

            // method.

        }

 

        ///

        /// Dependency incjector constructor, allows unit tests to set the

        /// object to use.

        ///

        ///

the for this presenter

        ///

an Instance of to handle persistance

        internal PersonEditorPresenter(IPersonEditorView view,IPersonRepository repository)

        {

            //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterRegistersForEvents()

            _view = view;

            _repository = repository;

            view.PersonLoaded += LoadPerson;

            view.PersonSaved += SavePerson;

        }

 

Ignore the new HttpCachePersonRepository(), just note that it’s the default implementation of IPersonRepository. It’s just a simple HttpCache implementation that persists the Person objects. It could just as well been one that persists to a db, and probably would be in a real world scenario.

Run the PersonEditorPresenterRegistersForEvents test, and VOLA! Green! One test down.

On to the next unit test. Looking back at the PersonEditorPresenter, I see the next UnitTest.

//UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonLoadedWithoutIdTest()

       

Pretty clear what’s needed, right? Someone fired the PersonLoaded event, but never set the view’s PersonId.

[Test] [ExpectedException(typeof(Exception))]

        public void PersonEditorPresenterPersonLoadedWithoutIdTest()

        {

            var mocker = new MockRepository();

            var mother = new PersonRepositoryMother(mocker);

            var view = mocker.DynamicMock<IPersonEditorView>();

            IEventRaiser personLoadedEvent;

            using (mocker.Record())

            {

                mother.Record();

                SetupResult.For(view.PersonId).Return(null);

                view.PersonLoaded += null;

                personLoadedEvent = LastCall.IgnoreArguments().GetEventRaiser();

            }

            using (mocker.Playback())

            {

                var presenter = new PersonEditorPresenter(view, mother.Repository);

                personLoadedEvent.Raise(null, null);

 

            }

 

 

        }

Note that I had the view return null for PersonId, and made sure the the presenter threw an Exception using

 

[ExpectedException(typeof(Exception))].

Yeah, I shouldn’t just be throwing an Exception of type Exception here, but YAGNI anything more for now… Anything more is outside the scope of this post.

Once again, the unit test failed since the view did absolutely nothing yet. But once again, I’ve got pretty clear requirements via the unit test.  Let’s make this test pass now.

///

        /// Executed when the is raised.

        ///

        ///

        ///

        void LoadPerson(object sender, EventArgs e)

        {

           

            if(!_view.PersonId.HasValue)

            {

                //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonLoadedWithoutIdTest()

                throw new Exception("Can not load person without id");

            }

            //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonLoadedTest()

        }

 

Simple enough, and yep, the Unit test passes.

It’s also simple to see the next unit test we need – The one that loads the person object. Let’s go write it…

  ///

        /// This test ensures that the hydrates

        /// the properly during the standard use case.

        ///

        [Test]

        public void PersonEditorPresenterPersonLoadedTest()

        {

            var mocker = new MockRepository();

            var mother = new PersonRepositoryMother(mocker);

            var view = mocker.DynamicMock<IPersonEditorView>();

            IEventRaiser personLoadedEvent;

            using (mocker.Record())

            {

                mother.Record();

                SetupResult.For(view.PersonId).Return(mother.LoadedPersonId);

                //Expectations

                view.FirstName = mother.LoadedPersonFirstName;

                view.LastName = mother.LoadedPersonLastName;

                view.PersonLoaded += null;

                personLoadedEvent = LastCall.IgnoreArguments().GetEventRaiser();

            }

            using (mocker.Playback())

            {

                var presenter = new PersonEditorPresenter(view, mother.Repository);

               

                personLoadedEvent.Raise(null,null);

            }

 

 

        }

 

A discussion of Rhino mocks expectations is outside the scope of this document, but note that I DID NOT use assertions here, but RinoMocks expectations. The reason I typically do that is that in many views we’ll only expose setters, which is great design on read only views. RhinoMocks lets you set up the expectation that the setter is called, instead of checking the getter. I think this is much better practice since we don’t want to test the getter, we want to test the setter.

Now, since we know this unit test will fail, since all that the presenter currently does is throw an exception if PersonId is null, I’ll skip running it. Instead, I’ll get to work making it pass.

Easy enough, since the unit test makes it really clear what the presenter should do, right?

Here’s the code…

///

        /// Executed when the is raised.

        ///

        ///

        ///

        void LoadPerson(object sender, EventArgs e)

        {

           

            if(!_view.PersonId.HasValue)

            {

                //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonLoadedWithoutIdTest()

                throw new Exception("Can not load person without id");

            }

            //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonLoadedTest()

            var person = _repository.GetById(_view.PersonId.Value);

            _view.FirstName = person.FirstName;

            _view.LastName = person.LastName;

        }

 

Compile, run unit test. Unit test passes. No surprises.

Next, we’re on to the PersonSaved event (SavePerson method) in the presenter. The stubbed code tells me what unit tests I’m going to need.

//UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonGetsAddedTest()

            //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonGetsEditedTest()

 

Should this method do 2 things? Probably not, I’m quite possibly violating the Single Responsibility Rule here, oh well. My wife tells me that I’m not perfect; I just had to prove her right!

Let’s write the PersonGetsAddedTest now.

///

        /// This test ensures that when the View returns null for

        /// ,

        /// the presenter passes the null up to the

        /// so that it can handle the add.

        ///

        [Test]

        public void PersonEditorPresenterPersonGetsAddedTest()

        {

           

            var mocker = new MockRepository();

            var mother = new PersonRepositoryMother(mocker);

           

            //Tell repo to return the correct id

            mother.SaveResults = mother.SavedPersonId;

           

            var view = mocker.DynamicMock<IPersonEditorView>();

            var firstName = "firstName";

            var lastName = "lastName";

            var personId = mother.SavedPersonId;

            IEventRaiser personSavedEvent;

            using (mocker.Record())

            {

                mother.Record();

                //Expectations

                Expect.Call(view.FirstName).Return(firstName);

                Expect.Call(view.LastName).Return(lastName);

                view.PersonSaved += null;

                personSavedEvent = LastCall.IgnoreArguments().GetEventRaiser();

                view.PersonId = personId;

            }

            using (mocker.Playback())

            {

                var presenter = new PersonEditorPresenter(view, mother.Repository);

 

                personSavedEvent.Raise(null, null);

            }

 

So, the only expectations we have here is that the event gets fired, and that the view’s PersonId property does get set, and the Views FirstName and LastName properties getters are called. Simple enough, so let’s go make the unit test pass.

///

        /// Executed when the event is raised.

        ///

        ///

        ///

        void SavePerson(object sender, EventArgs e)

        {

            //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonGetsAddedTest()

            //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonGetsEditedTest()

            var person = new Person();

            person.FirstName = _view.FirstName;

            person.LastName = _view.LastName;

            _view.PersonId = _repository.Save(person);

        }

 

Yep, the unit test passes. One unit test to go…

Now we need to make sure that if the view already has PersonId set, we edit the Person, not insert a new one. Actually, we really don’t care if one gets inserted or edited, that’s the responsibility of the repository (and arguably a result of a violation of SOC). Anyway, we’re just going to make sure that the view returns the PersonId.

///

        /// This test ensures that when the View returns a valid id for

        /// ,

        /// the presenter passes the value to the

        /// so that it edits the .

        ///

        [Test]

        public void PersonEditorPresenterPersonGetsEditedTest()

        {

            var mocker = new MockRepository();

            var factory = new PersonRepositoryMother(mocker);

            var view = mocker.DynamicMock<IPersonEditorView>();

            var firstName = "firstName";

            var lastName = "lastName";

            var personId = (int?) factory.LoadedPersonId;

            factory.SaveResults = personId.Value;

            IEventRaiser personSavedEvent;

            using (mocker.Record())

            {

                factory.Record();

    //Expectations               

    Expect.Call(view.FirstName).Return(firstName);

                Expect.Call(view.LastName).Return(lastName);

                SetupResult.For(view.PersonId).Return(personId);

               

                view.PersonSaved += null;

                personSavedEvent = LastCall.IgnoreArguments().GetEventRaiser();

                view.PersonId = personId;

            }

            using (mocker.Playback())

            {

                var presenter = new PersonEditorPresenter(view, factory.Repository);

 

                personSavedEvent.Raise(null, null);

            }

 

 

        }

 

Unit test does fail because we PersonId gets set to LoadedPersonId instead of SavedPersonId. Simple enough to make the unit test pass; just check if the view has PersonId set, if it does, use it.

 

   ///

        /// Executed when the event is raised.

        ///

        ///

        ///

        void SavePerson(object sender, EventArgs e)

        {

            //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonGetsAddedTest()

            //UnitTest:MyDumbView.UnitTests.PersonEditorPresenterTestFixture.PersonEditorPresenterPersonGetsEditedTest()

            var person = new Person();

            if (_view.PersonId.HasValue)

            {

                person.PersonId = _view.PersonId.Value;

            }

            person.FirstName = _view.FirstName;

            person.LastName = _view.LastName;

            _view.PersonId = _repository.Save(person);

        }

 

There we go, fully unit tested, fully functional Presentation Layer!


Nunit Test run

Now for the easy part, make the UI. Since we’ve already tested everything… This will be silly easy.

The markup…

 

DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>My View Is Dumber Than Your Viewtitle>

   <link rel="stylesheet" href="css/c.css" />

head>

<body>

    <h1>MVP /  Observer pattern, Standard ASPX exampleh1>

    <form id="form1" runat="server">

    <fieldset>

        <legend>Edit a Personlegend>

    <div>

        <asp:Label runat="server" ID="PersonIdLabel" AssociatedControlID="PersonIdControl">IDasp:Label>

        <asp:TextBox runat="server" ID="PersonIdControl" style="width:2em;" />

        <asp:Button runat="server" ID="LoadButton" Text="Load" />

    div>

    <div>

        <asp:Label runat="server" ID="PersonFirstNameLabel" AssociatedControlID="PersonFirstNameControl">First Nameasp:Label>

        <asp:TextBox runat="server" ID="PersonFirstNameControl" />

    div>

    <div>

        <asp:Label runat="server" ID="PersonLastNameLabel" AssociatedControlID="PersonLastNameControl">Last Nameasp:Label>

        <asp:TextBox runat="server" ID="PersonLastNameControl" />

    div>

    <div class="commands">

        <asp:Button runat="server" ID="SaveButton" Text="Save" />

    div>

    fieldset>

    form>

body>

html>

 

And the codebehind, Just mark the Page with the interface and let the IDE do most of the work for us. We just simple fill in the stubbed out implementation. Don’t forget to new up a presenter!

using System;

using System.Web.UI;

using MyDumbView.DomainModel;

using MyDumbView.Presentation.Presenters;

using MyDumbView.Presentation.Views;

 

 

public partial class _Default :Page,IPersonEditorView 

{

    private PersonEditorPresenter presenter;

    ///

    /// creates and instance of

    ///

    ///

    protected override void OnLoad(EventArgs e)

    {

        base.OnLoad(e);

        presenter = new PersonEditorPresenter(this);

 

    }

    ///

    /// Wires up events

    ///

    ///

    protected override void OnInit(EventArgs e)

    {

        base.OnInit(e);

        LoadButton.Click += delegate { PersonLoaded(this, e); };

        SaveButton.Click += delegate { PersonSaved(this, e); };

    }

    #region IPersonEditorView Methods

   

    ///

    /// The , null if we're adding a new one.

    ///

    public int? PersonId

    {

        get

        {

            int x;

            if (int.TryParse(PersonIdControl.Text,out x))

            {

                return x;   

            }

            return null;

        }

        set { PersonIdControl.Text = value.ToString(); }

    }

    ///

    /// The to display

    ///

    public string FirstName

    {

        get { return PersonFirstNameControl.Text; }

        set { PersonFirstNameControl.Text = value; }

    }

    ///

    /// The to display

    ///

    public string LastName

    {

        get { return PersonLastNameControl.Text; }

        set { PersonLastNameControl.Text = value; }

    }

    ///

    /// Event raised when Saved is Clicked

    ///

    public event EventHandler PersonSaved = delegate { };

    ///

    /// Event raised when Load is clicked

    ///

    public event EventHandler PersonLoaded = delegate { };

   

    #endregion

}

As a side note, I’ve found that it saves a lotta coding to declare the events with no-ops

    public event EventHandler PersonSaved = delegate { };

Since it saves the null check, and makes code much more readable.

       

SaveButton.Click += delegate { PersonSaved(this, e); };

Just personal preference, some people find it easier to actually make methods to fire events, I just think it’s more code, and to me, less code is usually better.

No surprises, it just works! When we leave PersonID textbox blank, we add a new person. After adding one, if we enter the ID and click load, it loads one, and if we edit the person, and click save, the Person is saved.

I didn’t like the usability much, so added a button to reset everything right next to the save button.

<div class="commands">

        <asp:Button runat="server" ID="SaveButton" Text="Save" />

        <input type="button" id="AddPerson" value="Add New Person" />

    div>

 

Then, I wired up some simple JS for it.

<asp:ScriptManager runat="server" ID="SM" />

    <script type="text/javascript">

    var personIdControlId = '';

    var personFirstNameControlId = '';

    var personLastNameControlId = '';

    $addHandler(window,"load",load);

    function load()

    {

         $addHandler($get("AddPerson"),"click",reset);

    }

    function reset()

    {

        $get(personIdControlId).value =

        $get(personFirstNameControlId).value =

        $get(personLastNameControlId).value = "";

    }

    script>

 

Wow, that was easy! Now I’m bored, so I’m gunna make a quick AJAX implementation of the same view.

using System;

using MyDumbView.Presentation.Views;

 

public class PersonView : IPersonEditorView

{

 

    public void FirePersonLoaded()

    {

        if(PersonLoaded != null)

        {

            PersonLoaded(this, EventArgs.Empty);

        }

    }

    public void FirePersonSaved()

    {

        if(PersonSaved != null)

        {

            PersonSaved(this, EventArgs.Empty);

        }

    }

 

    #region IPersonEditorView Members

 

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public int? PersonId { get; set; }

    public event EventHandler PersonLoaded;

    public event EventHandler PersonSaved;

 

    #endregion

}

 

Nothing special here except 2 methods to fire the events.

Now let’s make a quick webservice that consumes the view and presenter.

using System.Web.Services;

using MyDumbView.Presentation.Presenters;

 

///

/// A simple webservice to Add and Edit a

///

[WebService(Namespace = "http://www.elliottohara.com/MyDumbView/")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

[System.Web.Script.Services.ScriptService]

public class PersonService :WebService

{

 

 

    [WebMethod]

    public PersonView LoadPerson(PersonView view)

    {

        var presenter = new PersonEditorPresenter(view);

        view.FirePersonLoaded();

        return view;

    }

    [WebMethod]

    public PersonView SavePerson(PersonView view)

    {

        var presenter = new PersonEditorPresenter(view);

        view.FirePersonSaved();

        return view;

    }

 

}

 

Now, a simple page that uses the webservice, it doesn’t even need a codebehind.

DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>Ajax implementation of a dumb viewtitle>

    <link rel="stylesheet" href="css/c.css" />

head>

<body>

    <h1>MVP /  Observer pattern, Ajax exampleh1>

    <form id="form1" runat="server">

    <div id="Status" class="status">div>

    <fieldset><legend>Add a Personlegend>

    <div>

        <label for="FirstName">First Namelabel>

        <input id="FirstName" />

    div>

    <div>

        <label for="LastName">Last Namelabel>

        <input id="LastName" />

    div>

    <div class="command">

        <input type="button" id="AddNewButton" value="Add" />

    div>

    fieldset>

    <fieldset>

        <legend>Load Edit a Personlegend>

        <div>

            <label for="PersonId">IDlabel>

            <input id="PersonId" style="width:2em;"/><input type="button" value="Load" id="LoadButton" />

        div>

        <div>

        <label for="EditFirstName">First Namelabel>

        <input id="EditFirstName" />

    div>

    <div>

        <label for="EditLastName">Last Namelabel>

        <input id="EditLastName" />

    div>

    <div class="commands">

        <input type="button" id="SaveButton" value="Save" />

    div>

    fieldset>

    <asp:ScriptManager runat="server" ID="ScriptManager">

        <Services>

            <asp:ServiceReference InlineScript="true" Path="~/PersonService.asmx" />

        Services>

    asp:ScriptManager>

    <script type="text/javascript">

        var mapper =

        {

            AddFirstNameId:"FirstName",

            AddLastNameId:"LastName",

            EditFirstNameId:"EditFirstName",

            EditLastNameId:"EditLastName",

            EditPersonId:"PersonId",

            SaveButtonId:"SaveButton",

            LoadButtonId:"LoadButton",

            AddNewButtonId:"AddNewButton",

            StatusId:"Status"

           

        };

    script>

 

    <script src="scripts/AjaxExample.js" type="text/javascript">script>

 

    form>

body>

html>

And AjaxExample.js

// This file requires a ScriptManager and a object named mapper with the appropriate values set

/*

var mapper =

        {

            AddFirstNameId:"FirstName",

            AddLastNameId:"LastName",

            EditFirstNameId:"EditFirstName",

            EditLastNameId:"EditLastName",

            EditPersonId:"PersonID",

            SaveButtonId:"SaveButton",

            LoadButtonId:"LoadButton",

            AddNewButtonId:"AddNewButton",

            StatusId:"Status"

           

        };

*/

$addHandler(window,"load",ajaxOnload);

function ajaxOnload()

{

    $addHandler($get(mapper.SaveButtonId),"click",editPerson);

    $addHandler($get(mapper.LoadButtonId),"click",loadPerson);

    $addHandler($get(mapper.AddNewButtonId),"click",addPerson);

}

function addPerson()

{

   savePerson($v(mapper.AddFirstNameId),$v(mapper.AddLastNameId),null);

}

function editPerson()

{

    savePerson($v(mapper.EditFirstNameId),$v(mapper.EditLastNameId),$v(mapper.EditPersonId));

}

function savePerson(firstName,lastName,personId)

{

    status("");

    var view = new PersonView();

    view.FirstName = firstName;

    view.LastName = lastName;

    view.PersonId = personId;

    PersonService.SavePerson(view,onSavedSuccess,onFailed);

}

function onSavedSuccess(view)

{

    status("Person " + view.PersonId + " Saved.");

    $s(mapper.AddFirstNameId,"");

    $s(mapper.AddLastNameId,"");

}

function loadPerson()

{

    status("");

    $s(mapper.EditFirstNameId,"");

    $s(mapper.EditLastNameId,"");

    var view = new PersonView();

    view.PersonId = $v(mapper.EditPersonId);

    PersonService.LoadPerson(view,onPersonLoadSuccess,onFailed);

}

function onPersonLoadSuccess(view)

{

   

    $s(mapper.EditFirstNameId,view.FirstName);

    $s(mapper.EditLastNameId,view.LastName);

}

function onFailed(err)

{

   status(err.get_message());

}

//Silly utility functions

function $v(id)

{

    return $get(id).value;

}

function $s(id,val)

{

    $get(id).value = val;

}

function status(message)

{

    $get(mapper.StatusId).innerHTML = message;

}

MS Ajax.Net makes wiring up the View easy enough, and once again, everything just works since we’ve tested it all through unit tests…

I hope you liked the example. I’ve really found that TDD makes my job as a developer much more fun, and by making builds run unit tests, we essentially make run time errors compile time errors.  It also ends up saving time, since we’re debugging in NUnit, and not using a UI.

Download complete solution
Run Webform implementation
Run Ajax implementation

No comments:

Post a Comment