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;