Saturday, April 19, 2008

Use MVP on Windows Forms

In my previous post, I suggested a MVP pattern for ASP.Net controls. This post intend to implement MVP for .Net Windows Forms. Note that since Windows Forms data are in memory objects, it is much easier to do two-way databinding.

(1) Model ---Reference Data
Data Entry windows almost always need reference data to guide user input. A dropdown list filled with 50 US States is a perfect example. Naturally, we would want a simple type structure class and a complex type storage class to hold 50 states for databinding purposes:

public struct State
{
public State(int? id, string name)
{
_StateID = id;
_StateName = name;
}

private int? _StateID;
public int? StateID
{
get { return _StateID;}
set { _StateID = value; }
}

private string _StateName;
public string StateName
{
get { return _StateName; }
set { _StateName = value; }
}
}

public class States
{
private static List _StateList = new List();
static States()
{
_StateList.Add(new State(0, "MA"));
....

_StateList.Add(new State(null, "(empty)")); // add (empty) for user to de-select to
}
public static List StateList
{
get { return _StateList; }
}
}

(2) Model --- Entity Data
Suppose UI is designed to present User an address for View/Edit/Create, we then need an entity to represent Address:

public class Address
{
private int? _StateID;
public int? StateID
{
get { return _StateID; }
set { _StateID = value; }
}
}

Note Database normalization requires only saving StateID to database and avoid duplicating StateName.


(3) Presenter --- one way databinding to View
Upon View calling its single entry point, a Presenter will initialize View, Load Data from Model and Execute Business logic (e.g. show, update, add):

public interface IAddressView
{
void ShowReferenceData();
void ShowCurrentAddress();
}

class AddressPresenter
{
private IAddressView _view;
public AddressPresenter(IAddressView view)
{
_view = view;
}
public void Render(int? UserID)
{
LoadEntityData(UserID);
_view.ShowReferenceData();
_view.ShowCurrentAddress();
}

private ASC.ReferenceData.State _State;
public ASC.ReferenceData.State State
{
get { return _State;}
set { _State=value;}
}

private ASC.Entity.Address _Address;
private void LoadEntityData(int? UserID)
{
// simulate retrival of Address from Database
_Address = new ASC.Entity.Address();
if (UserID >0) _Address.StateID = 3;
// All UserID <=0 will have empty selection of state in its Address Entity;

foreach (ASC.ReferenceData.State s in ASC.ReferenceData.States.StateList)
{
if (s.StateID == _Address.StateID)
{
_State = new ASC.ReferenceData.State(_Address.StateID, s.StateName);
break;
}
}

}
public void Update()
{
// Save Entity to Database
}
public void Add()
{
// Insert Entity to Database
}
}
Note that two properties in Address Entity Data are represented as a single property of type ASC.ReferenceData.State. This will enable loading into a ComboBox for ease of databinding.


(4) View --- Implement IView and Reverse DataBind
As before, View need to implement IView with consideration of reference data
public partial class AddressChangeForm : Form, IAddressView
{
public void ShowReferenceData()
{
this.cbState.DataSource = ASC.ReferenceData.States.StateList;
this.cbState.DisplayMember = "StateName";
}

public void ShowCurrentAddress()
{
cbState.DataBindings.Add("SelectedItem", _Presenter, "State",false,DataSourceUpdateMode.OnPropertyChanged, new ASC.ReferenceData.State(null,"(empty)"));
}
Note that Reference data are push to View using one-way databinding, while Presenter data are push to View with change probagated backwards through OnPropertyChanged.
Presenter entry point will be called upon user action:
private void btnGetCurrent_Click(object sender, EventArgs e)
{
cbState.DataBindings.Clear();
_Presenter = new AddressPresenter(this);
_Presenter.Render(1078);
}

And a new presenter need to be created upon User Add New action:
private void btnAdd_Click(object sender, EventArgs e)
{
.....
cbState.DataBindings.Clear();
_Presenter = new AddressPresenter(this);
_Presenter.Render(-1);
......
_Presenter.Add();
cbState.DataBindings.Clear();
.....
}

Friday, April 11, 2008

How to serialize XSD class

Unlike DataSet, there are no build in method to serialize a XSD class. Here is the helper function to do that:

Public Class XsdClassHelper

Public Shared Function Serialize(ByVal t As Type, ByVal obj As Object) As String
Dim ser As New XmlSerializer(t)
Dim sw As New StringWriter
ser.Serialize(sw, obj)
Return HttpUtility.HtmlEncode(sw.ToString())
End Function

Public Shared Function Deserialize(ByVal t As Type, ByVal data As String) As Object
Dim ser As New XmlSerializer(t)
Dim sr As New StringReader(HttpUtility.HtmlDecode(data))
Dim xmlr As New System.Xml.XmlTextReader(sr)
Return ser.Deserialize(xmlr)
End Function
End Class

Thursday, April 10, 2008

Unit Testing ASP.Net MVP Design Pattern using MS test Framework

For GMWBPresenter supporting GMWB User Control, MS Test framework can be used to enable access and test view components such as label, textbox, etc.

(1) We will need a Base Accessor from MS Test Framework in order to access private
and protected member on user controls, namely PrivateObject:

Friend Class BaseAccessor

Protected m_privateObject As PrivateObject

Protected Sub New(ByVal target As Object, ByVal type As PrivateType)
MyBase.New()
m_privateObject = New PrivateObject(target, type)
End Sub

Protected Sub New(ByVal type As PrivateType)
Me.New(Nothing, type)
End Sub

Friend Overridable ReadOnly Property Target() As Object
Get
Return m_privateObject.Target
End Get
End Property

Public Overrides Function ToString() As String
Return Me.Target.ToString
End Function

Public Overloads Overrides Function Equals(ByVal obj As Object) As Boolean
If GetType(BaseAccessor).IsInstanceOfType(obj) Then
obj = CType(obj, BaseAccessor).Target
End If
Return Me.Target.Equals(obj)
End Function

Public Overrides Function GetHashCode() As Integer
Return Me.Target.GetHashCode
End Function

Public ReadOnly Property PrivateObject() As PrivateObject
Get
Return Me.m_privateObject
End Get
End Property
End Class

(2) For specific control access we need extend base:

Friend Class GMWBRiderControlAccessor
Inherits BaseAccessor
Protected Shared m_privateType As PrivateType = New PrivateType(GetType(GmwbRiderControl))

Friend Sub New(ByVal target As GmwbRiderControl)
MyBase.New(target, m_privateType)
End Sub

End Class

(3) To utilize MS Test framework without loading ASP.Net Host, all view elements
have to be initialized using its contructor:

_
Public Class GMWBPresenterTester
Inherits PresenterTesterBase

Private _gmwbUCA As New GMWBRiderControlAccessor(New GmwbRiderControl())

_
Public Sub OnlyIndicatorBSeeGMWBMessage()
Dim manulifePrincipal As JHAPrincipal
manulifePrincipal = AuthenticationFacade.Instance.LogonUser _
( _
"....", _
"..." _
)
Thread.CurrentPrincipal = manulifePrincipal
Dim cif As New ContractInquiryFacade
Dim rider As New ContractRiderList

rider = cif.GetContractRiders(New ContractSearch("NAS", "789065"))
_gmwbUCA.PrivateObject.SetFieldOrProperty("dsRiders", rider)
_gmwbUCA.PrivateObject.SetFieldOrProperty("lnkHelp", New Global.System.Web.UI.WebControls.HyperLink())
_gmwbUCA.PrivateObject.SetFieldOrProperty("pnlGMWBMessage", New Global.System.Web.UI.WebControls.Panel())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblGMWBMessage", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("dvGMWB", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("spnGMWB", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblGMWBValue", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("dvWithdrwalAmount", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("spnWithdrwalAmount", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblWithdrawalAmountValue", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("dvRemainingAmount", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("spnRemainingAmount", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblRemainingAmountValue", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("pnlPayments", New Global.System.Web.UI.WebControls.Panel())
_gmwbUCA.PrivateObject.SetFieldOrProperty("spnPayments", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblPaymentsValue", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("pnlYearsValue", New Global.System.Web.UI.WebControls.Panel())
_gmwbUCA.PrivateObject.SetFieldOrProperty("spnYears", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblYearsValue", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("spnLIA", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblLIA", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("spnRLIA", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblRLIA", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("spnDateLastStepUp", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblDateLastStepUp", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("spnNextStepUpDate", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblNextStepUpDate", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("spnStepUpOverride", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblStepUpOverride", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("spnPreviousGMWBRider", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblPreviousGMWBRider", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("spnRiderTradeInDate", New Global.System.Web.UI.HtmlControls.HtmlGenericControl())
_gmwbUCA.PrivateObject.SetFieldOrProperty("lblRiderTradeInDate", New Global.System.Web.UI.WebControls.Label())
_gmwbUCA.PrivateObject.SetFieldOrProperty("litDisclaimer", New Global.System.Web.UI.WebControls.Literal())
_gmwbUCA.PrivateObject.SetFieldOrProperty("litDisclaimer", New Global.System.Web.UI.WebControls.Literal())
Dim r As ContractRiderList.gmwbRow = DirectCast(rider.gmwb.Select()(0), ContractRiderList.gmwbRow)
r.vantage_failure_indicator = False
_gmwbUCA.PrivateObject.Invoke("BindControls", rider)

Dim pnlGMWBMessage As Panel = DirectCast(_gmwbUCA.PrivateObject.GetFieldOrProperty("pnlGMWBMessage"), Panel)
MSU.Assert.IsTrue(pnlGMWBMessage.Visible, "Indicator=B should be able to see GMWB Message if any")

'7889078 has GMWB ind=F
rider = cif.GetContractRiders(New ContractSearch("NAS", "676767"))
r = DirectCast(rider.gmwb.Select()(0), ContractRiderList.gmwbRow)
r.vantage_failure_indicator = False
_gmwbUCA.PrivateObject.SetFieldOrProperty("dsRiders", rider)
_gmwbUCA.PrivateObject.Invoke("BindControls", rider)

pnlGMWBMessage = DirectCast(_gmwbUCA.PrivateObject.GetFieldOrProperty("pnlGMWBMessage"), System.Web.UI.WebControls.Panel)
MSU.Assert.IsTrue(Not pnlGMWBMessage.Visible, "Indicator=F should not see GMWB Message")

End Sub

End Class

Note that App.Config under MS Test project must include all the Authorization, Capability and connection information. Also all context access such as HttpContext,
Session Context are not allowed or need to turn off for this kinds of testing strategy.

Tuesday, April 1, 2008

Testing .NET Application Performance

The content summarize this link http://msdn2.microsoft.com/en-us/library/ms998581.aspx

Max Stress Level:
RPS < 100
TTFB < 8sec
cpu% < 60%


Observing Metrics:
CPU%, Queue Length, Context Switching,
RAM Available, Page/Sec
Process Private Bytes, Working Set

ASP.Net
RPS, TTFB, Request in Queue, Request Execution Time

My Comments:
Too much context Switching will kill scalability on SMP since server will be busy not doing real work; Process with large private bytes need to find way to share data such as caching;

Friday, March 7, 2008

ASP.Net Unit Testing in VS.Net 2005

Let us focus on test Presenter in MVP, including Code-Behind and explicitly declared Presenter class..


Presneter Code:


public interface IGMWBView
{
void UpdateGMWBFields();
}
public class GMWBPresenter
{
private bool _ShowNextStepUpDate;
public bool ShowNextStepUpDate
{
get { return _ShowNextStepUpDate;}
set { _ShowNextStepUpDate = value;}
}
public void UpdateGMWBView(IGMWBView view)
{
BindToDomainModel();
view.UpdateGMWBFields();
}
public void BindToDomainModel()
{
ShowNextStepUpDate = true;
}
}

Code-behind:


public partial class _Default : System.Web.UI.Page, IGMWBView
{
protected void Page_Load(object sender, EventArgs e)
{
_Presenter = new GMWBPresenter();
}
protected void Button1_Click(object sender, EventArgs e)
{
this.lbNextStepUpDate.Text = "Changed";
_Presenter.UpdateGMWBView(this);
}
public GMWBPresenter _Presenter ;
#region IGMWBView Members
public void UpdateGMWBFields()
{
}
#endregion


Unit Test Code:


[TestMethod]
[HostType("ASP.NET")]
[UrlToTest("http://localhost/TDDASPNet1.Web/Default.aspx")]
public void CSRCanSeeNextStepUpDate()
{
Page p = ctx.RequestedPage;
Label lb = (Label) p.FindControl("lbNextStepUpDate");
Assert.IsTrue(lb.Visible, "Lable hidden for CSR");
PrivateObject pObj= new PrivateObject(p);
pObj.Invoke("Page_Load", null, EventArgs.Empty);
pObj.Invoke("Button1_Click",null,EventArgs.Empty);
Button btn = (Button)p.FindControl("Button1");
Assert.IsTrue((lb.Text == "Changed"), "Button click failed " + lb.Text);
GMWBPresenter gmwb= (GMWBPresenter) pObj.GetFieldOrProperty("_Presenter");
Assert.IsTrue( gmwb.ShowNextStepUpDate ,"OOps");
}
General Pattern:

/// (1) Set Url
/// (2) Get the page Object
/// (3) using PrivateObject to Invoke: Page_Load, Click
/// this should call Presenter entry pointer.
/// (4) Get Presenter as property and test its properties.

This will allows us to do Unit Tests by calling server code

Monday, February 18, 2008

Use RelayBinding to Expose Your Service to the world through MS Service Bus

To use Microsoft implementation of ESB at http://www.biztalk.net/, We need only use RelayBinding in WCF + Biztalk Service Extension.
In fact Service Config could be as simple as the following:

<configuration>
<system.serviceModel>
<services>
<service name="JQD.HomeLabService">
<endpoint contract="JQD.IHomeLabService"
name="JQD.HomeLabService"
binding="relayBinding" />
...
and the corresponding code are also simple:

static void Main(string[] args)
{
CardSpaceTokenProvider tok = new CardSpaceTokenProvider();
string rHostName = RelayBinding.DefaultRelayHostName;
Uri rHostUri = new Uri(String.Format("sb://{0}/services/{1}/HomeLabService",rHostName,tok.GetUserName()));
ServiceHost rHost = new ServiceHost(typeof(HomeLabService), rHostUri);
rHost.Description.Endpoints[0].Behaviors.Add(tok);
rHost.Open();
Console.ReadLine();
rHost.Close();
}

Clearly, the endpoint "sb://conect.biztalk.net/services/jqd2001/HomeLabService" will be correctly relayed to your server every running code has a correct cardSpace token.

For a piece of code to reach your web services through relay, it only need to know the contract

[ServiceContract()]
public interface IHomeLabService
{
[OperationContract()]
string HeartBeat();
}

and it then need to connect to MS ESB:


static void Main(string[] args)
{
ChannelFactory f = new ChannelFactory(new RelayBinding(),
"sb://connect.biztalk.net/services/jqd2001/HomeLabService");
f.Open();
IHomeLabService ch =f.CreateChannel();
Console.WriteLine( ch.HeartBeat());
f.Close();

Noticeably, RelayBinding is the key to traverse NAT or firewall and without expose your server to the internet directly.

Friday, February 15, 2008

How to correctly use MVP Design pattern

JHA has a presenter implementation in GMWBRiderControl.ascx, which uses GMWBPresenter. This presenter implementation has two problems:
(1) View are updated by function rather than properties, which makes Presenter stateless and unable to utilize overriding, retraction technique commonly seen in OOP/OOD.
(2) Missing commonly seem MVP elements such as IView and Processing() single entry point. This deviation from standard MVP result in a lot of business logic in ASP.Net Code-behind ( such as BindControls function in GMWBRiderControl.ascx.vb) --- completely defeated the purpuse of MVP to separately logic into Presenter.
To correctly this problem, the following changes are needed:

(1) Convert all function into property. e.g


Public Function ShowLifeTimeIncomeAmount() As Boolean
'
If IsGMWBIndicatorA _
OrElse IsGMWBIndicatorH _
Then
Return False
End If
'
Return True
End Function

would be changed to

Private _ShowLifeTimeIncomeAmount As Boolean
Public Property ShowLifeTimeIncomeAmount() As Boolean
Get
Return _ShowLifeTimeIncomeAmount
End Get
Set(ByVal value As Boolean)
_ShowLifeTimeIncomeAmount = value
End Set
End Property

all logic behind those function will be move into the function gettin gdata from Domain Model

Private Sub BindDataFromDomainModelToPresenter()
If IsGMWBIndicatorA _
OrElse IsGMWBIndicatorH _
Then
ShowLifeTimeIncomeAmount = False
End If
'
ShowLifeTimeIncomeAmount = True
' TODO: Change all the other function to property and call here to set it
End Sub


(2) Define "IView" and implement on User Control

Public Interface IGMWBView
Sub UpdateGMWBFields(ByVal IsVantageDown As Boolean)
Sub UpdateWithDrawAllowanceNewLabel(ByVal IsVantageDown As Boolean)
Sub UpdateWithdrawAmountInfo(ByVal IsVantageDown As Boolean)
Sub UpdateBenefitBase(ByVal IsVantageDown As Boolean)
Sub UpdatePaymentStepInfo(ByVal IsVantageDown As Boolean)
Sub MakeAllLIAFieldVisibleForBrokerClient(ByVal IsVantageDown As Boolean)
End Interface


Partial Class GmwbRiderControl
..
Implements IGMWBView


Public Sub UpdateGMWBFields(ByVal _IsVantageDown As Boolean) Implements IGMWBView.UpdateGMWBFields

' Move some code seqment from BindControls to here
End Sub

(3) Implementing business Execution login in Presenter


Public Overridable Sub UpdateGMWBView(ByVal view As IGMWBView)
' Pump Data from DO to P
BindDataFromDomainModelToPresenter()
' Invoke processing function implemented on ASP.Net code-Behind (VIEW)
view.UpdateGMWBFields(_IsVantageDown)
// other logic to change view
End Sub

(4) Call the single entry point on presenter "UpdateGMWBView" in user control in the function "BindControls"

The above steps will result in standard implementation of MVP.