Note: The author of this blog does not believe that you should use WebSevice/WCF in standalone application when none required. But we live in a world where we don't make rules. If you database is protected by another firewall, you may find this architecture handy. It is more important to have logically separation in architecture that will scale well if application is deployed in a distributed manner.
Here is SOA friendly Architecture
Here is deployment view
Major Components
1. UI Components/Views:
Provide User Interface
Controllers
Get the Business Objects from Gateway and provides to UI
Gateway/Factory
It is factory layer that provides the business object. It knows how to create or get the object across the network boundaries. First looks in repository if none, gets the object across the wire.Our example is going to exploit the capabilities of Web Service. It will convert the proxy object into real domain object using Mapper utility.
References:
http://www.martinfowler.com/eaaCatalog/gateway.html
http://www.martinfowler.com/eaaCatalog/repository.html
http://www.martinfowler.com/eaaCatalog/dataTransferObject.html
Business Object Manager
It populates the business object using data object. This is where one can aggregate the business objects before transferred across the network.
Business Object/Entity Object
Contains the Attributes and operations related to domain entity
Process Object/Workflow Object
Business objects collaborate together to perform some useful operations to users.
Data Object
Data Objects retrieve the data from Database. Will contain all the CRUD operations.
Here is Code.
Gateway
namespace Gateway
{
public class Contact
{
static Contact()
{
//
}
private static List
private static Object syncRoot = new Object();
public static void ClearCache()
{
lock (syncRoot)
{
contactListCache = null;
}
}
public List
{
return ContactList;
}
public List
{
get
{
if (contactListCache == null)
{
lock (syncRoot)
{
contactListCache = new List
ContractServiceWS.ContractServiceClient contractService = new ContractServiceWS.ContractServiceClient("WSHttpBinding_IContractService");
ContractServiceWS.Contact[] proxyContactList = contractService.GetContract();
for (int i = 0; i <= proxyContactList.Length - 1; i++) { BusinessObject.Contact contact = new BusinessObject.Contact(proxyContactList[i].UserName, proxyContactList[i].Name, proxyContactList[i].PhoneNumber, proxyContactList[i].Age); contactListCache.Add(contact); } contactListCache = (List
}
}
return (contactListCache);
}
}
}
}
namespace Gateway
{
public abstract class Mapper
{
public static object MapProperties_Fields(object SourceObj, Type DestinationType)
{
if (SourceObj == null)
{
return null;
}
Type sourceType = SourceObj.GetType();
object destinationObj = Activator.CreateInstance(DestinationType);
foreach (PropertyInfo sourceProperty in sourceType.GetProperties())
{
if (sourceProperty.Name.Equals("ExtensionData") == false)
{
MemberInfo destinationMember = DestinationType.GetMember(sourceProperty.Name)[0];
if (destinationMember.MemberType == MemberTypes.Property)
{
PropertyInfo destinationProperty = ((PropertyInfo)(destinationMember));
if ((destinationProperty.CanWrite == true))
{
if (destinationProperty.PropertyType.Equals(sourceProperty.PropertyType))
{
destinationProperty.SetValue(destinationObj, sourceProperty.GetValue(SourceObj, null), null);
}
}
}
}
}
return destinationObj;
}
public static IList MapProperties_Fields(IList SourceList, Type DestinationListType, Type DestinationElementType)
{
if (SourceList == null)
{
return null;
}
IList destinationList = ((IList)(Activator.CreateInstance(DestinationListType)));
foreach (object sourceObj in SourceList)
{
destinationList.Add(MapProperties_Fields(sourceObj, DestinationElementType));
}
return destinationList;
}
}
}
WCF Service
[ServiceContract()]
public interface IContractService
{
[OperationContract]
IList
}
public class MyService : IContractService
{
public IList
{
BusinessObjectManager.Contact contactManager = new BusinessObjectManager.Contact();
return contactManager.GetContacts();
}
}
Business Object Manager
namespace BusinessObjectManager
{
public class Contact
{
public List
{
DataTable dataTable;
List
dataTable = DataObject.Contact.GetContact();
try
{
foreach (DataRow dataRow in dataTable.Rows)
{
BusinessObject.Contact contact = new BusinessObject.Contact(dataRow[0].ToString(), dataRow[1].ToString(), dataRow[2].ToString(), Int32.Parse(dataRow[3].ToString()));
list.Add(contact);
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
dataTable = null;
}
return(list);
}
}
}
Business Object
namespace BusinessObject
{
[DataContract]
public class Contact
{
private String _userName;
private String _name;
private String _phoneNumber;
private int _age;
public Contact()
{
}
public Contact(string userName, string name,String phoneNumber, int age)
{
_userName = userName;
_name = name;
_phoneNumber = phoneNumber;
_age = age;
}
[DataMember]
public String UserName
{
get
{
return _userName;
}
set
{
_userName = value;
}
}
[DataMember]
public String Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
[DataMember]
public String PhoneNumber
{
get
{
return _phoneNumber;
}
set
{
_phoneNumber = value;
}
}
[DataMember]
public int Age
{
get
{
return _age;
}
set
{
_age = value;
}
}
}
}
Data Object
namespace DataObject
{
public class Contact
{
private const String ContactTableName = "Contact";
private class Fields
{
public const string UserName = "ContactUser_Name";
public const string Name = "Contact_Name";
public const string PhoneNumber = "Contact_Phone_Number";
public const string Age = "Contact_Age";
}
public static DataTable GetContact()
{
System.Text.StringBuilder sql = new System.Text.StringBuilder();
System.Data.DataTable dataTable;
//Build the SQL
AppendBaseSelectColumns(sql);
sql.Append(" FROM");
sql.Append( " " + ContactTableName);
try
{
dataTable = GetDataTable(sql.ToString());
}
catch (Exception ex)
{
//Log the error
throw ex;
}
return(dataTable);
}
private static String AppendBaseSelectColumns(System.Text.StringBuilder sql)
{
sql.Append("SELECT");
sql.Append(" " + Fields.UserName + ",");
sql.Append(" " + Fields.Name + ",");
sql.Append(" " + Fields.PhoneNumber + ",");
sql.Append(" " + Fields.Age );
return sql.ToString();
}
private static DataTable GetDataTable(string sql)
{
//Real life one will connect to Database and return the
DataTable dataTable = new DataTable();
dataTable.Columns.Add(new DataColumn("Contact_User_Name", typeof(String)));
dataTable.Columns.Add(new DataColumn("Contact_Name", typeof(String)));
dataTable.Columns.Add(new DataColumn("Contact_Phone_Number", typeof(String)));
dataTable.Columns.Add(new DataColumn("Contact_Age", typeof(System.Int32)));
dataTable.Rows.Add(new Object[]{ "VK", "Vikas","222-222-2222",int.MaxValue});
dataTable.Rows.Add(new Object[] { "SV", "Survic", "222-222-9999", int.MaxValue });
return dataTable;
}
}
}
Presenter/Controller
namespace Presenter
{
public class ContactPresenter
{
public ContactPresenter(View.IContactView view)
{
_view = view;
_view.LookUp += new EventHandler(_view_LookUp);
_view.Persist += new EventHandler(_view_Persist);
}
private View.IContactView _view;
private BusinessObject.Contact _currentContact;
public View.IContactView View
{
get { return _view; }
}
public BusinessObject.Contact CurrentContact
{
get { return _currentContact; }
}
public void SaveContact()
{
if (_currentContact != null)
{
_currentContact.Name = _view.ContactName;
_currentContact.PhoneNumber = _view.ContactPhoneNumber;
_currentContact.Age = _view.ContactAge;
}
}
public void LookUpContactByUserName(string userName)
{
Gateway.Contact contactGateway = new Gateway.Contact();
List
foreach (BusinessObject.Contact contact in contactList)
{
if (contact.UserName.ToUpper().Trim().Equals(userName.ToUpper().Trim()))
{
_currentContact = contact;
break;
}
}
_view.ContactUserName = _currentContact.UserName;
_view.ContactName = _currentContact.Name;
_view.ContactPhoneNumber = _currentContact.PhoneNumber;
_view.ContactAge = _currentContact.Age;
}
private void _view_LookUp(object sender, EventArgs e)
{
this.LookUpContactByUserName(_view.UserNameToLookUp);
}
private void _view_Persist(object sender, EventArgs e)
{
this.SaveContact();
}
}
}