Now that we have our interfaces we need to make some changes to our existing objects.  Back in part 1 we defined an object tree for contact information.  For this example, a 3rd parting marketing system is loading new contact information into our data store and management does not want anyone to delete data, but in a truely pointy hair boss fashion they want people to be able to update the data... 

First we will create the ContactInformationProviderRepository.  We make a static ProvidersRepositoryGeneric to hold the Providers we load based on the web.config.  By looking at GetById and Update you can see they use the same logic to locate the correct provider.  If the overload that takes the provider as a parameter has a null for the provider it simply throws an exception.

The LoadedProviders accessor is where the real trick happens.  First we look to see if the static  ProvidersRepositoryGeneric is populated.  If it is null we create a lock to stop anyone from accessing it and we check it again to make sure someone else did not set it between our first check and the lock.  This is important to do because we are running in a multi thread environment (multiple web requests at once) we need to be sure we are not stepping on our own feet.  If it is still null we create it.  The constructor goes out and gets the provider information from the web.config and populates the collection. 

 

    public class ContactInformationProviderRepository : 
        IReadProviderRepository<ContactInformationProviderCollection, 
            ContactInformationProvider, ContactInformation, Int32>,
        IUpdateProviderRepository<ContactInformationProviderCollection, 
            ContactInformationProvider, ContactInformation>
    {

        #region IReadProviderRepository<ContactInformationProviderCollection,ContactInformationProvider,ContactInformation,int> Members
        private static ProvidersRepositoryGeneric<ContactInformationProviderCollection, ContactInformationProvider> _loadedProviders = null;
        public ProvidersRepositoryGeneric<ContactInformationProviderCollection, ContactInformationProvider> LoadedProviders
        {
            get
            {
                if (_loadedProviders == null)
                {
                    Object processLock = new Object();
                    lock (processLock)
                    {
                        if (_loadedProviders == null)
                        {
                            _loadedProviders = new ProvidersRepositoryGeneric<ContactInformationProviderCollection, ContactInformationProvider>();
                        }
                    }

                }
                return _loadedProviders;
            }
        }

        public ContactInformation GetById(int id, System.Security.Principal.IPrincipal user)
        {
            return GetById(id, LoadedProviders.Provider, user);
        }

        public ContactInformation GetById(int id, string providerName, System.Security.Principal.IPrincipal user)
        {
            return GetById(id, LoadedProviders.Providers[providerName], user);
        }

        public ContactInformation GetById(int id, int providerIndex, System.Security.Principal.IPrincipal user)
        {
            return GetById(id, LoadedProviders.Providers[providerIndex], user);
        }

        public ContactInformation GetById(int id, ContactInformationProvider provider, System.Security.Principal.IPrincipal user)
        {
            if (provider == null) throw new ArgumentNullException("provider");
            return provider.GetById(id, user);

        }

        #endregion

        #region IUpdateProviderRepository<ContactInformationProviderCollection,ContactInformationProvider,ContactInformation> Members

        public void Update(ContactInformation item, System.Security.Principal.IPrincipal user)
        {
            Update(item, LoadedProviders.Provider, user);
        }

        public void Update(ContactInformation item, string providerName, System.Security.Principal.IPrincipal user)
        {
            Update(item, LoadedProviders.Providers[providerName], user);
        }

        public void Update(ContactInformation item, int providerIndex, System.Security.Principal.IPrincipal user)
        {
            Update(item, LoadedProviders.Providers[providerIndex], user);
        }

        public void Update(ContactInformation item, ContactInformationProvider provider, System.Security.Principal.IPrincipal user)
        {
            if (provider == null) throw new ArgumentNullException("provider"); 
item.Save(provider, user); } #endregion }

Even though ProvidersRepositoryGeneric<ContactInformationProviderCollection, ContactInformationProvider> LoadedProviders  is defined in both the IReadProviderRepository and IUpdateProviderRepository interfaces it only appears once in the class.  That is because the property signature matches between the two and they use the same one.

Now let us get the provider collection created.

 public class ContactInformationProviderCollection : ProviderCollection, 
        IReadProviderCollection<ContactInformationProvider,ContactInformation, Int32>,
        IUpdateProviderCollection<ContactInformationProvider,ContactInformation>
    {
        #region IReadProviderCollection<ContactInformation,int> Members

        public new ContactInformationProvider this[string name]
        {
            get
            {
                if (String.IsNullOrEmpty(name)) throw new ArgumentNullException();
                ContactInformationProvider result = (ContactInformationProvider)base[name];
                if (result == null) throw new ArgumentOutOfRangeException();

                return result;
            }
        }

        public ContactInformationProvider this[int index]
        {
            get
            {
                if ((index < 0) || (index >= this.Count)) throw new ArgumentOutOfRangeException();
                int currentIndex = 0;
                foreach (ContactInformationProvider item in this)
                {
                    if (currentIndex == index) return item;
                    currentIndex++;
                }
                //if we reach this point something really odd happened
                //in theory since we already verdified the index is 0 or greater
                //and is less than the total provider count this should never be hit.
                //it would be possible if a provider was removed during run time...
                //so it should blow up.
                throw new ArgumentOutOfRangeException();
            }
        }

        public new void Add(ProviderBase provider)
        {
            //cant add a null so die if we try
            if (provider == null) throw new ArgumentNullException();

            //check the type of the provider
            if (provider is ContactInformationProvider)
            {
                //all good so add the provider to the collection
                base.Add(provider);
            }
            else
            {
                //Invalid type so blow up.
                throw new ArgumentException();
            }
        }

        #endregion
    }
}

We are doing 3 things here, we are way to get the provider by the index.   Next we are hiding the base class implementation of get by name "public new ContactInformationProvider this[string name] ".  Finally we are hiding the the Add method on the base class.  We hide it so we can check to make sure the provider being added is of the correct type.

Next we setup an abstract provider...

public abstract class ContactInformationProvider : ProviderBase,
        IReadProvider<ContactInformationProvider,ContactInformation, Int32>,
        IUpdateProvider<ContactInformationProvider,ContactInformation>
    {
        #region IReadProvider<ContactInformation,int> Members

        public abstract ContactInformation GetById(int id, System.Security.Principal.IPrincipal user);

        #endregion

        #region IUpdateProvider<ContactInformationProvider,ContactInformation> Members

        public virtual void Update(ContactInformation item, System.Security.Principal.IPrincipal user)
        {
            throw new NotSupportedException("The abstract ContactInformation can not be updated.");
        }

        #endregion
        public abstract void Update(EmailAddress item, System.Security.Principal.IPrincipal user);
        public abstract void Update(PhoneNumber item, System.Security.Principal.IPrincipal user);
    }

We will be able to inherit this class to make a working provider for contact information.  We added two overloads to the update method to take the specific types of contact information we have in our model.  Because ContactInforamtion is an abstract class and is the root of the object tree, or the branch we are interested in saving, we do not want to try to update it so we can throw a NotImplementedException or a NotSupportedException; I marked the method as virtual just incase a provider is created that can handle an unboxed ContactInformation; you could do a if else tree checking if (item is EmailAddress){ Update(((EmailAddress)item),user); } else if (item is PhoneNumber).....

Now we need to modify the ContactInofrmation base class.

public abstract class ContactInformation : IReadDataObject<ContactInformation, Int32>, 
        IUpdateDataObject<ContactInformationProvider,ContactInformation>
    {
        public static ContactInformation GetById(Int32 id, IPrincipal user)
        {
           return new ContactInformationProviderRepository().GetById(id, user);
        }        

        private String _name;
        public String Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
            }
        }

        #region IReadableDataObject<ContactInformation,int> Members

        private Int32 _id;
        public int Id
        {
            get { return _id; }
            set { _id = value; }
        }

        #endregion

        #region IUpdateDataObject<ContactInformationProvider,ContactInformation> Members

        public void Save(IPrincipal user)
        {
            new ContactInformationProviderRepository().Update(this, user);
        }

        public virtual void Save(ContactInformationProvider provider, IPrincipal user)
        {
            provider.Update(this, user);
        }

        #endregion
    }

First we added a static method, GetById that will return a contact information by id.  It simply calls a new ContactInformationProviderRepository and calls the GetById method.  Next we added an Id property.  The set accessor was not required by the interface but we still added one.

The save method calls out to the repository to get back the provider.  The repository finds the requested provider and calls the items save method passing the provider as a parameter (The delete method would do the same thing).  The Save overload that accepts the provider is marked virtual so it can be overridden in subclasses. 

Now let's update the PhoneNumber EmailAddress clasesses

    public abstract class PhoneNumber : ContactInformation
    {
        private string _number;
        public string Number
        {
            get
            {
                return _number;
            }
            set
            {
                _number = value;
            }
        }
        public override void Save(ContactInformationProvider provider, System.Security.Principal.IPrincipal user)
        {
            provider.Update(this, user);
        }
    }

    public class EmailAddress : ContactInformation
    {
        public EmailAddress()
        {
            Name = "Email Address";
        }
        private string _address;
        public string Address
        {
            get
            {
                return _address;
            }
            set
            {
                _address = value;
            }
        }
        public override void Save(ContactInformationProvider provider, System.Security.Principal.IPrincipal user)
        {
            provider.Update(this, user);
        }
    }

Both classes have the same change; they both override the Save method.  The code seems the same but it is important to be there.  There is a type boxing issue, (I mentioned the boxing issue back in part 2 as well), that we want to get around.  Let me explain...

The the method calls are mapped at compile time, not runtime.  For example say you have a variable "contact" defined as ContactInformation contact=new EmailAddress(); and ContactInformationProvider provider=new DummyContactInformationProvider();

Now what happens if you call provider.Update(contact,Thread.CurrentPrincipal);?

  1. the provider update method is passed an object of type ContactInformation
  2. the NotSupportedException is thrown

  The fact that contact is holding an object of type EmailAddress is not checked.  This is because of the compile time mapping of method calls.  Now let's look at what happens if you use the same objects and call Contact.Save(Thread.CurrentPrincipal);

  1. The item is passed to the repository boxed as a ContactInformation object
  2. The repository calls its own overload passing the item, provider, and user
  3. The items save method is called with the provider and user as the parameters.
  4. Because EmailAddress overrides the save method overload with that signature, it is used.
  5. The Save method call provider.Update passing "this" and the user.
  6. Because "this" from inside of the EmailAddress is strongly typed as an EmailAddress the provider's Update method that accepts an EmailAddress object is called and there is no exception.

provider.Update(contact,Thread.CurrentPrincipal); would work if .net used runtime mapping.  However I suspect the overhead in doing that might be a performance killer.  This is the same reason why we can not use a System.Type variable with generics to make runtime generic objects. 

The only thing remaining is to build a provider...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ObjectHelpDesk.ContactInformations;
using ObjectHelpDesk.ContactInformations.PhoneNumbers;

namespace DummyProviders
{
    public class dummyContactInformationProvider : ContactInformationProvider
    {
        private string email = "bob@test.com";
        private string phone = "555.555.1234";
        public override ContactInformation GetById(int id, System.Security.Principal.IPrincipal user)
        {
            ContactInformation result = null; ;
            switch (id)
            {
                case 1:
                case 2:
                    result = new EmailAddress();
                    ((EmailAddress)result).Address = email;
                    break;
                case 3:
                default:
                    result = new HomePhoneNumber();
                    ((PhoneNumber)result).Number = phone;
                    break;
            }
            
            result.Id = id;
            return result;
        }

        public override void Update(EmailAddress item, System.Security.Principal.IPrincipal user)
        {
            email = item.Address;
        }

        public override void Update(PhoneNumber item, System.Security.Principal.IPrincipal user)
        {
            phone = item.Number;
        }
    }
}
 

This provider is really simple, and fairly useless, but it gets the point across of how this works.  GetById returns an EmailAddress object for if the id is 1 or 2 and a phone number for anything else.  The update methods update private strings that are used to generate those objects. 

All of these interfaces make setting up a new provider fairly quick and easy.  More importantly they give you a great deal of flexibility.  There is room for expansion/improvement here; Off the top of my head...

  1. you could add another set of interfaces for a function that would be common, IReadByParentId.
  2. Make the update interfaces use an IsDirty property and the INotifiyPropertyChanged interface.  It is part of the .net framework.
  3. make the abstract providers have a public concrete implementation of the methods and call a protected version of the method internally... like public void Update(IUpdateDataObject item, IPrincipal user) { DoUpdate(item,user);} protected abstract void DoUpdate( IUpdateDataObject item, IPrincipal user);
  4. add pre/post events for each data method (this ties into #3)
  5. make the pre event able to cancel the call.

Happy Coding!