Sunday 23 December 2007

IINet vs Exetel - is it worth going naked? Maybe not...

I currently use the ISP IInet using the 10GB Off Peak/10GB On Peak (ADSL 2+ light) package. However, I have heard a lot of buzz about the so called 'naked' plans available through IInet and other ISPs like Exetel. Up till now, I grudgingly needed a phone line just for ADSL. To me that was $30 bucks down the drain.... until now. The naked plans don't require line rental at all!

On the face of it, I prefer the IInet deal over Exetel for my situation because:
  1. IInet have been a great provider so far and there are some rumours about Exetel having a less than stellar approach to customer service.
  2. I already have a Belkin router (F1PI241ENau - http://catalog.belkin.com/IWCatProductPage.process?Product_Id=247190) and have heard that they don't play well with the Exetel service. I don't want to fork out $200 for a new modem that works with the Exetel naked service.
  3. You get an incoming number as part of the IItalk service. Apparently you have to pay $10 extra to add this functionality with Exetel.
  4. Even though uploads are counted as part of your bandwidth allowance (Exetel doesn't charge for this), I don't upload that much ( about 1GB per month ).
  5. I don't really care about having a static IP (this is what Exetel provides).
  6. P2P are shaped (according to official announcements) on the Exetel network (see http://whirlpool.net.au/article.cfm?id=1734&show=replies )
  7. No excess usage charges (just shaped) - wheras Exetel charges $3 per GB.

However, after considering some of the drawbacks (e.g. measured uploads including voice), I am staying put for now to see where the new naked DSL market heads.

Thursday 20 December 2007

Golden Oldie - ASP.NET multiline doesn't enforce MaxLength property

This one is a Golden Oldie and is a problem with the Textarea HTML control (not specifically a problem with ASP.NET). Technically, the ASP.NET controls should not even provide the MaxLength property when TextMode is set to MultiLine....

But there are ways around this issue using javascript and/or inherited controls:


using System;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace LendLease.MRP.Web.UI.WebControls
{
//Custom TextBox - used to fix issue with the Maxlength not working in controls
//modified version of http://www.codeproject.com/KB/aspnet/Extended_ASPNET_TextBox.aspx
public class TextBox : System.Web.UI.WebControls.TextBox
{

protected override void OnPreRender(EventArgs e)
{
if (MaxLength > 0 && this.TextMode == TextBoxMode.MultiLine)
{
if (!Page.ClientScript.IsClientScriptIncludeRegistered("TextArea"))
{
Page.ClientScript.RegisterClientScriptInclude("TextArea", ResolveUrl("~/Include/TextBoxMaximumLength.js"));
}
this.Attributes.Add("onkeyup", "LimitInput(this)");
this.Attributes.Add("onbeforepaste", "doBeforePaste(this)");
this.Attributes.Add("onpaste", "doPaste(this)");
this.Attributes.Add("onmousemove", "LimitInput(this)");
this.Attributes.Add("maxLength", this.MaxLength.ToString());
}
base.OnPreRender(e);
}
}
}


JAVASCRIPT:


function doBeforePaste(control){
maxLength = control.attributes["maxLength"].value;
if(maxLength)
{
event.returnValue = false;
}
}
function doPaste(control){
maxLength = control.attributes["maxLength"].value;
value = control.value;
if(maxLength){
event.returnValue = false;
maxLength = parseInt(maxLength);
var oTR = control.document.selection.createRange();
var iInsertLength = maxLength - value.length + oTR.text.length;
var sData = window.clipboardData.getData("Text").substr(0,iInsertLength);
oTR.text = sData;
}
}
function LimitInput(control)
{
if(control.value.length > control.attributes["maxLength"].value)
{
alert("You cannot enter more than " + control.attributes["maxLength"].value + " characters into this field");
control.value = control.value.substring(0,control.attributes["maxLength"].value);
}
};

Issue with VS 2008 and the Web Client Software Factory - FIX

When attempting to add classes (e.g. via Add->New Class) in my VS 2008 projects, I have been getting this error constantly. This lead me to do a workaround by just copying classes from other projects! (NOT an ideal situation!)

==========================================================================
Could not load file or assembly 'Microsoft.VisualStudio.TemplateWizardInterface,
Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its
dependencies. The located assembly's manifest definition does not match the
assembly reference. (Exception from HRESEULT: 0x80131040)
==========================================================================

Apparently, there is a workaround to this issue. You just have to find your devenv.exe.config file (e.g. mine was in "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe.config"), and change the version number from 9.0.0.0 to 8.0.0.0

<dependentAssembly>
<assemblyIdentity name="Microsoft.VisualStudio.TemplateWizardInterface" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.9.9.9" newVersion="8.0.0.0" />
</dependentAssembly>

Wednesday 19 December 2007

Telerik RadGrid V5.0 Sorting Bug with Nullable DateTime Types - "Property [ColumnName] does not support IComparable"

It turns out the Telerik RadGrid does not support sorting for nullable types (e.g. DateTime?) out of the box. For example, if you use a LINQ datasource for your grid based on a column that is nullable in SQL Server, you will get an error when trying to sort the column "Property [BoundColumnName] does not implement IComparable".

The simplest fix for this is to just make your column non-nullable if this is possible in your business situation.

Tuesday 18 December 2007

LINQ - using LINQ as the Combined Business Logic and DAL

We have taken a few different approaches to LINQ on different projects at Lend Lease. Currently on the Bovis Lend Lease Monthly Reporting Team, we are not using the Web Client Software Factory (WCSF) and the Model View Presenter Pattern (MVP). Rather, we are using partial classes of LINQ objects (separate .cs files for each class for readability & maintainablility). This is a more rapid approach to development which I enjoy coding much more. I also just found out that one of the FrameworkTeam has just extended the WCSF ObjectContainerDatasource so that it includes ALL fields (both bound and unbound) used in a control - which avoids the problem whereby non-bound fields are nulled out from the bound objects. e.g. If I use the ObjectContainerDataSource and have a field StatusId, but don't bind it - then the object in the "Updated" Event of by datasource has a statusId of null. It would be wiped out if I updated the database with the same object - which I see as a major bug in the WCSF! (Will post about this problem and solution shortly).

Anyway back to the point - here is some very basic sample Code using LINQ as the Business Logic layer with partial classes to extend the generated SQLMetal classes (aka the classes from the generated dbml file):



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LendLease.ManagementReporting.Service.DataContract.DTO;

namespace LendLease.MRP.Service.Persistence.Entity
{

public partial class Ctl_Person_Resp
{
public string UserName
{
get { return this.Dim_User.First_Name + " " + this.Dim_User.Last_Name; }
}

/// <summary>
/// Gets list of People who have responsibility in the current level of the hierarchy.
/// </summary>
/// <param name="businessHierarchyId"></param>
/// <returns></returns>
public static List<PersonResponsibleListDTO> GetPersonResponsibleList(int businessHierarchyId)
{
MRPDataContext context = new MRPDataContext();

//Join to the the member code so we can filter the person responsible list
var query = from person in context.Ctl_Person_Resps
join hierarchy in context.Dim_Business_Hierarchy_PCs on person.Business_Hierarchy_PC_Member_Code equals hierarchy.Member_Code
where hierarchy.Business_Hierarchy_PC_SKEY == businessHierarchyId
select new PersonResponsibleListDTO { User_SKEY = person.User_SKEY, UserName = person.UserName};
return query.Distinct().ToList<PersonResponsibleListDTO>();
}

}
}

Friday 14 December 2007

Telerik RadGrid - Creating A Grouped Grid that allows sorting and editing

Today, I wanted to get the Telerik RadGrid (http://www.telerik.com/products/aspnet/controls/grid/overview.aspx) 5.0 edit functionality working in conjunction with its grouping functionality.

Background: I have been temporarily seconded from the Lend Lease Property Pipeline project onto a high-profile BI project that has several dashboards for KPIs used by management (hosted within MOSS 2007 - parameters are passed into my page via Request.Params). I have a few management-style pages that need to be created - but they have to be editable so this excludes Reporting Services from the equation.

One of the requirements of this particular page was to have automatic grouping of risks and opportunities into different sections of the grid. However, when I set up the grid my code triggered the edit event, I get the following error:

[InvalidConstraintException: Cannot have 0 columns.]

Google only returned 2 results on this error - and they didn't give me a direct solution. After looking through the Telerik site, I found a few clues which pointed to the fact I had to have both the "Select Fields" and "GroupByFields" Set in the grid. Here is my current (unfinished) version of the page) which show that magic combination of code hacks and declarative settings to get the grid to work in all its Databound AJAX-enabled editable groupable glory!


Code:





<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="RiskOpportunity.aspx.cs"
Inherits="ddkonline.MRP.Client.RiskOpportunity" %>


<%@ Register Assembly="Microsoft.Practices.Web.UI.WebControls" Namespace="Microsoft.Practices.Web.UI.WebControls"
TagPrefix="pp" %>

<%@ Register Assembly="RadGrid.Net2" Namespace="Telerik.WebControls" TagPrefix="radG" %>
<%@ Register Assembly="RadCalendar.Net2" Namespace="Telerik.WebControls" TagPrefix="radCln" %>
<!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 id="Head1" runat="server">
<title>Risks and Opportunities</title>
</head>
<body>
<form id="form1" runat="server">
<pp:ObjectContainerDataSource ID="OdsRiskOpportunityDataSource" runat="server" DataObjectTypeName="ddkonline.MRP.Service.Persistence.Entity.ODS_Risk_Opportunity"
OnInserted="OdsRiskOpportunityDataSource_Inserted" OnUpdated="OdsRiskOpportunityDataSource_Updated"
OnSelecting="OdsRiskOpportunityDataSource_Selecting" />
<asp:LinqDataSource ID="BusinessUnitDataSource" runat="server" ContextTypeName="ddkonline.MRP.Service.Persistence.Entity.MRPDataContext"
OrderBy="Sort_Order" TableName="Dim_Business_Hierarchy_PCs" Where="ETL_Region == @ETL_Region &amp;&amp; Member_Level == @Member_Level"
OnSelecting="BusinessUnitDataSource_Selecting">
<WhereParameters>
<asp:Parameter Name="ETL_Region" Type="String" />
<asp:Parameter DefaultValue="&quot;3&quot;" Name="Member_Level" Type="Int32" />
</WhereParameters>
</asp:LinqDataSource>
<asp:LinqDataSource ID="ReportingCurrencyDataSource" runat="server" ContextTypeName="ddkonline.MRP.Service.Persistence.Entity.MRPDataContext"
OrderBy="Currency_Name" Select="new (Currency_Name, Currency_Code, Reporting_Currency_SKEY)"
TableName="Dim_Reporting_Currencies">
</asp:LinqDataSource>
<asp:LinqDataSource ID="RiskConsequenceDataSource" runat="server" ContextTypeName="ddkonline.MRP.Service.Persistence.Entity.MRPDataContext"
OrderBy="Sort_Order" Select="new (Lookup_SKEY, Lookup_Value)" TableName="Dim_Lookups"
Where="Type == @Type">
<WhereParameters>
<asp:Parameter DefaultValue="RiskConsequence" Name="Type" Type="String" />
</WhereParameters>
</asp:LinqDataSource>
<asp:LinqDataSource ID="StatusDataSource" runat="server" ContextTypeName="ddkonline.MRP.Service.Persistence.Entity.MRPDataContext"
OrderBy="Sort_Order" Select="new (Lookup_SKEY, Lookup_Value)" TableName="Dim_Lookups"
Where="Type == @Type">
<WhereParameters>
<asp:Parameter DefaultValue="Status" Name="Type" Type="String" />
</WhereParameters>
</asp:LinqDataSource>
<%--<asp:LinqDataSource ID="peopleDataSource" runat="server"
ContextTypeName="ddkonline.MRP.Service.Persistence.Entity.MRPDataContext"
OrderBy="ETL_Extract_User" Select="new (People_SKEY, ETL_Extract_User)"
TableName="Dim_People">
</asp:LinqDataSource>--%>
<div>
<radG:RadGrid ID="BusinessRiskGrid" ShowGroupPanel="False" runat="server" AutoGenerateColumns="False"
DataSourceID="OdsRiskOpportunityDataSource" GridLines="None" OnItemCommand="BusinessRiskGrid_ItemCommand"
Skin="Windows" AllowSorting="True" OnItemDataBound="BusinessRiskGrid_ItemDataBound"
GroupingEnabled="true" EnableAJAX="true" EnableAJAXLoadingTemplate="true" LoadingTemplateTransparency="25">
<ExportSettings>
<Pdf PageWidth="8.5in" PageHeight="11in" PageTopMargin="" PageBottomMargin="" PageLeftMargin=""
PageRightMargin="" PageHeaderMargin="" PageFooterMargin=""></Pdf>
</ExportSettings>
<MasterTableView AllowAutomaticInserts="True" AllowAutomaticUpdates="True" CommandItemDisplay="Bottom"
DataSourceID="OdsRiskOpportunityDataSource" EditMode="InPlace" DataKeyNames="Id">
<GroupByExpressions>
<radG:GridGroupByExpression>
<SelectFields>
<radG:GridGroupByField FieldAlias="IsRisk" FieldName="IsRisk"></radG:GridGroupByField>
</SelectFields>
<GroupByFields>
<radG:GridGroupByField FieldName="IsRisk"></radG:GridGroupByField>
</GroupByFields>
</radG:GridGroupByExpression>
</GroupByExpressions>
<CommandItemTemplate>
<table width="100%">
<tr>
<td width="50%">
<asp:LinkButton ID="AddNewLink" runat="server" CommandName="InitInsert">
<img style="border:0px" alt="" src="Image/Insert.gif" /> Add new Record</asp:LinkButton>
</td>
<td width="50%" align="right">
<asp:HyperLink ID="PrintLink" runat="server" NavigateUrl="http://172.24.27.157/ReportserverDEV?%2fBLL+MRP%2fBusiness+Plan+Status&amp;period=20071101&amp;hierarchy=838&amp;businessArea=6">
<img style="border:0px" alt="" src="Image/Insert.gif"/> Print
</asp:HyperLink>
</td>
</tr>
</table>
</CommandItemTemplate>
<Columns>
<%--Issue Detail --%>
<radG:GridTemplateColumn DataField="IssueDetail" HeaderText="Issue Details" SortExpression="IssueDetail"
UniqueName="IssueDetail" ItemStyle-Width="5pc">
<EditItemTemplate>
<asp:TextBox ID="IssueDetailTextBox" runat="server" Text='<%# Bind("IssueDetail")%>' />
<span style="color: Red"></span>
<asp:RequiredFieldValidator ID="IssueDetailRequiredFieldValidator" ControlToValidate="IssueDetailTextBox"
Display="Dynamic" ErrorMessage="* This field is required" runat="server" />
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="IssueDetailLabel" runat="server"><%# Eval("IssueDetail")%></asp:Label>
</ItemTemplate>
</radG:GridTemplateColumn>
<%--Business Unit--%>
<%--
<radG:GridDropDownColumn DataField="BusinessUnitId" DataSourceID="BusinessUnitDataSource"
HeaderText="Business Unit" ListTextField="Lookup_Value" ListValueField="BusinessUnitId"
UniqueName="BusinessUnitId">
</radG:GridDropDownColumn>
--%>
<%--Reporting Currency --%>
<radG:GridDropDownColumn DataField="Reporting_Currency_SKEY" DataSourceID="ReportingCurrencyDataSource"
ItemStyle-Width="5pc" HeaderText="Reporting Currency" ListTextField="Currency_Name"
ListValueField="Reporting_Currency_SKEY" UniqueName="Reporting_Currency_SKEY"
Groupable="True" GroupByExpression="Reporting_Currency_SKEY Group By Reporting_Currency_SKEY">
</radG:GridDropDownColumn>
<%--PBT--%>
<radG:GridTemplateColumn DataField="PBT" HeaderText="PBT" UniqueName="PBT" SortExpression="PBT"
ItemStyle-Width="1pc">
<EditItemTemplate>
<asp:TextBox ID="PBTTextBox" runat="server" Text='<%# Bind("PBT", "{0:N1}")%>' />
<span style="color: Red"></span>
<asp:RequiredFieldValidator ID="PBTRequiredFieldValidator" ControlToValidate="PBTTextBox"
ErrorMessage="* This field is required" runat="server" Display="Dynamic" />
<asp:RegularExpressionValidator ID="PBTRegularExpressionValidator" ControlToValidate="PBTTextBox"
ValidationExpression="-{0,1}\d*\.{0,1}\d{0,1}" ErrorMessage="* You must enter a valid value (1 decimal place)"
runat="server" Display="Dynamic" />
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="PBTLabel" runat="server"><%# Eval("PBT", "{0:N1}")%></asp:Label>
</ItemTemplate>
</radG:GridTemplateColumn>
<%--PAT--%>
<radG:GridTemplateColumn DataField="PAT" HeaderText="PAT" UniqueName="PAT" SortExpression="PAT"
ItemStyle-Width="1pc">
<EditItemTemplate>
<asp:TextBox ID="PATTextBox" runat="server" Text='<%# Bind("PAT", "{0:N1}")%>' />
<span style="color: Red"></span>
<asp:RequiredFieldValidator ID="PATRequiredFieldValidator" ControlToValidate="PATTextBox"
ErrorMessage="* This field is required" runat="server" Display="Dynamic" />
<asp:RegularExpressionValidator ID="PATRegularExpressionValidator" ControlToValidate="PATTextBox"
ValidationExpression="-{0,1}\d*\.{0,1}\d{0,1}" ErrorMessage="* You must enter a valid value (1 decimal place)"
runat="server" Display="Dynamic" />
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="PATLabel" runat="server"><%# Eval("PAT","{0:N1}")%></asp:Label>
</ItemTemplate>
</radG:GridTemplateColumn>
<%--Probability--%>
<radG:GridTemplateColumn DataField="Probability" HeaderText="% Probability" UniqueName="Probability"
SortExpression="Probability" ItemStyle-Width="5pc">
<EditItemTemplate>
<asp:TextBox ID="ProbabilityTextBox" runat="server" Text='<%# Bind("Probability")%>' />
<span style="color: Red"></span>
<asp:RequiredFieldValidator ID="ProbabilityRequiredFieldValidator" ControlToValidate="ProbabilityTextBox"
ErrorMessage="* This field is required" runat="server" Display="Dynamic" />
<asp:RegularExpressionValidator ID="ProbabilityRegularExpressionValidator" ControlToValidate="ProbabilityTextBox"
ValidationExpression="-{0,1}\d*\.{0,1}\d{0,1}" ErrorMessage="* You must enter a numeric value" runat="server"
Display="Dynamic" />
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="ProbabilityLabel" runat="server"><%# Eval("Probability", "{0}%")%></asp:Label>
</ItemTemplate>
</radG:GridTemplateColumn>
<%--Commentary--%>
<radG:GridTemplateColumn DataField="Commentary" HeaderText="Commentary" SortExpression="Commentary"
UniqueName="Commentary" ItemStyle-Width="5pc">
<EditItemTemplate>
<asp:TextBox ID="CommentaryTextBox" runat="server" Text='<%# Bind("Commentary")%>' />
<span style="color: Red"></span>
<asp:RequiredFieldValidator ID="CommentaryRequiredFieldValidator" ControlToValidate="CommentaryTextBox"
Display="Dynamic" ErrorMessage="* This field is required" runat="server" />
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="CommentaryLabel" runat="server"><%# Eval("Commentary")%></asp:Label>
</ItemTemplate>
</radG:GridTemplateColumn>
<%--Person Responsible --%>
<radG:GridTemplateColumn DataField="Person_Responsible_Id" UniqueName="PersonResponsible"
HeaderText="Person Responsible" SortExpression="Person_Responsible_Id">
<EditItemTemplate>
<asp:DropDownList ID="PersonResponsible" runat="server" SelectedValue='<%# Bind("Person_Responsible_Id") %>'>
<asp:ListItem Value="0" Text="" />
<asp:ListItem Value="1" Text="David Klein" />
<asp:ListItem Value="2" Text="Andrew Muller" />
<asp:ListItem Value="3" Text="Damien Herslet" />
</asp:DropDownList>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="dtLabel1" runat="server" Text='<%# Eval("PersonName") %>' />
</ItemTemplate>
</radG:GridTemplateColumn>
<radG:GridTemplateColumn DataField="ResolutionDate" DataType="System.DateTime" HeaderText="Resolution Date"
SortExpression="ResolutionDate" UniqueName="ResolutionDate" ForceExtractValue="Always">
<EditItemTemplate>
<radCln:RadDatePicker ID="ResolutionDateDatePicker" runat="server" DbSelectedDate='<%# Bind("ResolutionDate") %>'>
</radCln:RadDatePicker>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="ResolutionDateLabel" runat="server"><%# Eval("ResolutionDate", "{0:d}")%></asp:Label>
</ItemTemplate>
</radG:GridTemplateColumn>
<radG:GridTemplateColumn DataField="Status_Lookup_SKEY" UniqueName="Status" HeaderText="Status"
SortExpression="Status_Lookup_SKEY">
<EditItemTemplate>
<asp:DropDownList ID="Status" runat="server" DataSourceID="StatusDataSource" DataTextField="Lookup_Value"
DataValueField="Lookup_SKEY" SelectedValue='<%# Bind("Status_Lookup_SKEY") %>'>
</asp:DropDownList>
</EditItemTemplate>
<ItemTemplate>
<asp:Image ID="Image1" runat="server" ImageAlign="Middle" ImageUrl='<%# Eval("StatusImageURL") %>' />
</ItemTemplate>
</radG:GridTemplateColumn>
<radG:GridTemplateColumn DataField="Period_Date_SKEY" HeaderText="Period_Date_SKEY"
UniqueName="Period_Date_SKEY" Visible="false">
<EditItemTemplate>
<asp:Label ID="PeriodDateLabel" runat="server" Text='<%# Bind("Period_Date_SKEY")%>' />
</EditItemTemplate>
</radG:GridTemplateColumn>
<radG:GridTemplateColumn DataField="IsRisk" HeaderText="IsRisk" UniqueName="IsRisk"
Groupable="True" GroupByExpression="IsRisk Group By IsRisk" Visible="False">
<ItemTemplate>
<asp:Label ID="IsRiskLabel" runat="server" Text='<%# Eval("IsRisk")%>' />
</ItemTemplate>
<EditItemTemplate>
<asp:Label ID="IsRiskLabel" runat="server" Text='<%# Eval("IsRisk")%>' />
</EditItemTemplate>
</radG:GridTemplateColumn>
<radG:GridEditCommandColumn UniqueName="Edit" ButtonType="ImageButton">
</radG:GridEditCommandColumn>
</Columns>
</MasterTableView>
<ClientSettings AllowDragToGroup="True">
<Resizing AllowColumnResize="True" EnableRealTimeResize="True" />
</ClientSettings>
</radG:RadGrid>
</div>
</form>
</body>
</html>




using System;
using System.Collections.Specialized;
using ddkonline.MRP.Service;
using ddkonline.MRP.Service.Persistence.Entity;
using Microsoft.Practices.Web.UI.WebControls;
using Telerik.WebControls;
using ddkonline.MRP.Global;

namespace ddkonline.MRP.Client
{
/// <summary>
/// Risk Management page
/// </summary>
public partial class RiskOpportunity : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
}

/// <summary>
/// Defaults the newly inserted items
/// </summary>
/// <param name="sender">Default event sender</param>
/// <param name="e">Grid command event arguments</param>
protected void BusinessRiskGrid_ItemCommand(object sender, GridCommandEventArgs e)
{
//// Default values for the items to insert in grid
//// http://www.telerik.com/community/forums/thread/b311D-emgdc.aspx
if (e.CommandName == RadGrid.InitInsertCommandName)
{
e.Canceled = true;
ListDictionary newValues = new ListDictionary();
newValues["IssueDetail"] = string.Empty;
newValues["Commentary"] = string.Empty;
newValues["BusinessUnitId"] = 871;
newValues["Reporting_Currency_SKEY"] = 0;
newValues["PBT"] = 0;
newValues["PAT"] = 0;
newValues["Probability"] = 0;
newValues["CreatedOn"] = DateTime.Now;
newValues["ModifiedOn"] = DateTime.Now;
newValues["ResolutionDate"] = null;
newValues["Person_Responsible_Id"] = 0;
newValues["Period_Date_SKEY"] = Controller.Current_Period;
newValues["Status_Lookup_SKEY"] = Constant.ON_TRACK;
e.Item.OwnerTableView.InsertItem(newValues);
}
}
/// <summary>
/// Handles the insert of an Item and Saves via partial method in the LINQ Entity (via a Service wrapper class)
/// </summary>
/// <param name="sender">Default event sender</param>
/// <param name="e">Object container event arguments</param>
protected void OdsRiskOpportunityDataSource_Inserted(
object sender, ObjectContainerDataSourceStatusEventArgs e)
{
ODS_Risk_Opportunity RiskOpportunity = (ODS_Risk_Opportunity)e.Instance;

// Set audit attributes
RiskOpportunity.CreatedOn = DateTime.Now;
RiskOpportunity.CreatedBy = Context.User.Identity.Name;
RiskOpportunity.ModifiedOn = DateTime.Now;
RiskOpportunity.ModifiedBy = Context.User.Identity.Name;
RiskOpportunity.Original_Risk_Opportunity_Id = 0;
//RiskOpportunity.Business_Area_Lookup_SKEY = GetBusinessAreaId(Controller.Current_BusinessArea);
RiskOpportunity.Business_Hierarchy_PC_SKEY = Controller.Current_BusinessHierarchy;
RiskOpportunityService.Save(RiskOpportunity);
}

/// <summary>
/// Handles the update of an Item
/// </summary>
/// <param name="sender">Default event sender</param>
/// <param name="e">Object container event arguments</param>
protected void OdsRiskOpportunityDataSource_Updated(
object sender, ObjectContainerDataSourceStatusEventArgs e)
{
ODS_Risk_Opportunity RiskOpportunity = (ODS_Risk_Opportunity)e.Instance;
// Set audit attributes
RiskOpportunity.ModifiedOn = DateTime.Now;
RiskOpportunity.ModifiedBy = Context.User.Identity.Name;
//RiskOpportunity.Business_Area_Lookup_SKEY = GetBusinessAreaId(Controller.Current_BusinessArea);
RiskOpportunity.Business_Hierarchy_PC_SKEY = Controller.Current_BusinessHierarchy;
RiskOpportunityService.Save(RiskOpportunity); ;
}

protected void OdsRiskOpportunityDataSource_Selecting(object sender, ObjectContainerDataSourceSelectingEventArgs e)
{
OdsRiskOpportunityDataSource.DataSource = RiskOpportunityService.GetAllFiltered(Controller.Current_BusinessHierarchy, Controller.Current_BusinessArea, Controller.Current_Period);
}

protected void BusinessUnitDataSource_Selecting(object sender, System.Web.UI.WebControls.LinqDataSourceSelectEventArgs e)
{
e.WhereParameters["ETL_Region"] = Controller.Current_BusinessHierarchy;
//e.SelectParameters. ["ETL_Region"]
//BusinessUnitDataSource.SelectParameters. = Controller.Current_BusinessHierarchy;
}

protected void BusinessRiskGrid_ItemDataBound(object sender, GridItemEventArgs e)
{
//Render Correct Header (RISK/OPPORTUNITY) on ItemDataBound of Telerik Grid.
if (e.Item is GridGroupHeaderItem)
{
GridGroupHeaderItem item = (GridGroupHeaderItem)e.Item;
System.Data.DataRowView groupDataRow = (System.Data.DataRowView)e.Item.DataItem;
item.DataCell.Text = Boolean.Parse(groupDataRow["IsRisk"].ToString()) ? "RISKS (enter -ve)" : "OPPORTUNITIES (enter +ve)";

//Calculations
//item.DataCell.Text += ((System.Decimal)groupDataRow["total"] / (int.Parse(groupDataRow["count"].ToString()))).ToString();
}
}
}
}

Thursday 13 December 2007

Problems Using Telerik Controls for inserts

I had an issue today using the Telerik RadGrid control (Version 5.0) to do inserts (edits work fine). If you don't initialize the fields in your control properly, you will get the following error:

DataBinding: 'Telerik.WebControls.GridInsertionObject' does not contain a property with the name 'Person_Responsible_Id'.

I don't think this is an ideal situation - the control should be smart enough to initialize its own fields - but the way around this is described here on the Telerik site:

http://www.telerik.com/help/aspnet/grid/?grdInsertingValuesUserControlFormTemplate.html



   1:  protected void RadGrid1_InsertCommand(object source, GridCommandEventArgs e)

   2:  {

   3:             GridEditableItem editedItem = e.Item as GridEditableItem;

   4:             UserControl userControl = (UserControl)e.Item.FindControl(GridEditFormItem.EditFormUserControlID);

   5:   

   6:             //Create new row in the DataSource

   7:             DataRow newRow = this.Employees.NewRow();

   8:   

   9:             //Insert new values

  10:             Hashtable newValues = new Hashtable();

  11:   

  12:             newValues["Country"] = (userControl.FindControl("TextBox7") as TextBox).Text;

  13:             newValues["City"] = (userControl.FindControl("TextBox8") as TextBox).Text;

  14:             newValues["Region"] = (userControl.FindControl("TextBox9") as TextBox).Text;

  15:             newValues["HomePhone"] = (userControl.FindControl("TextBox10") as TextBox).Text;

  16:             newValues["BirthDate"] = (userControl.FindControl("TextBox11") as TextBox).Text;

  17:             newValues["TitleOfCourtesy"] = (userControl.FindControl("ddlTOC") as DropDownList).SelectedItem.Value;

  18:   

  19:             newValues["Notes"] = (userControl.FindControl("TextBox1") as TextBox).Text;

  20:             newValues["Address"] = (userControl.FindControl("TextBox6") as TextBox).Text;

  21:             newValues["FirstName"] = (userControl.FindControl("TextBox2") as TextBox).Text;

  22:             newValues["LastName"] = (userControl.FindControl("TextBox3") as TextBox).Text;

  23:             newValues["HireDate"] = (userControl.FindControl("Textbox5") as TextBox).Text;

  24:             newValues["Title"] = (userControl.FindControl("TextBox4") as TextBox).Text;

  25:   

  26:             //make sure that unique primary key value is generated for the inserted row

  27:             newValues["EmployeeID"] = (int)this.Employees.Rows[this.Employees.Rows.Count - 1]["EmployeeID"] + 1;

  28:             try

  29:             {

  30:                 foreach (DictionaryEntry entry in newValues)

  31:                 {

  32:                     newRow[(string)entry.Key] = entry.Value;

  33:                 }

  34:                 this.Employees.Rows.Add(newRow);

  35:                 this.Employees.AcceptChanges();

  36:             }

  37:             catch (Exception ex)

  38:             {

  39:                 RadGrid1.Controls.Add(new LiteralControl("Unable to update/insert Employees. Reason: " + ex.Message));

  40:                 e.Canceled = true;

  41:             }

  42:  }

Tuesday 11 December 2007

Tip - Making your LINQ datasource do all the work for you

One of the important things to remember when trying to get your LINQ datasources to do 2-way binding is to set the "EnableInsert" and "EnableUpdate" attributes to true. Otherwise, nothing will be inserted/updated when you click on the Edit/Insert link in your bound control (e.g. Telerik RadGrid)



   1:      <asp:LinqDataSource ID="OdsRiskManagementDataSource" runat="server" 
   2:          ContextTypeName="LendLease.MRP.Service.Persistence.Entity.MRPDataContext" EnableUpdate="true" EnableInsert="true"    
   3:          TableName="ODS_Risk_Managements">
   4:      </asp:LinqDataSource>    

Monday 10 December 2007

Dynamic Queries in LINQ

Dynamic queries are usually used in search screens where the search criteria is optional. This illustrates how to create dynamic queries.

Dynamic Queries

The following code creates a dynamic query:
1.MyDataContext context = new MyDataContext();
2.var query = from customer in context.Customers select customer;
3.var filteredQuery = (from customer in query where customer.ID == newCustomerID select customer).ToList();

This is how it works:
1. Create a new context to query
2. Creates the first part of the query
3. Adds further criteria to the original query, paying attention to the bold part in line 3, where we extend the query variable.

Saturday 1 December 2007

Web Client Software Factory and Dependency Injection

The Web Client Software Factory is a combination of frameworks to give any .NET web app a head start using the Model View Presenter pattern and the Microsoft Enterprise Library for things such as Error handling policies and Logging. In this way you can be assured that you are starting with a bedrock that is fully testable and refactored. It also provides guidance with real-life samples to get you up and running. We have a team that have built a common framework for the Lend Lease .NET development team based on the WCSF, using LINQ as well in VS 2008.

I looked at using this for my last project at Pfizer - but the project didn't give us the luxury of learning and adapting this framework on the job. We now have the support of Lend Lease to develop this framework which is fantastic.

One issue is that the "recipes" that generate the stubs for different modules don't work with 2008 (even though the code compiles). There are hacks to get the WCSF working in 2008 - but we are just having the team leads of the different projects generating these stubs in 2005 for the other developers.

One of the techniques that the Web Client Software Factory relies upon is
dependency injection. A good description of it can be found here:
http://webclientsoftwarefactory.blogspot.com/2007/08/what-is-dependency-injection.html

Monday 19 November 2007

I've moved from SSW to Oakton!

Just had my last day at SSW after 8.5 years! We had a great time at Strike Bowling @ Fox Studios in Centennial Park - and an even better dinner at Cine Italian restaurant down the street. After the obligatory speeches and hugs, that was it!!!

I started at Oakton this morning with 5 others (the equal largest weekly intake) with a lovely woman called Fiona Begg. I was blown away by her enthusiasm for the company and it has definitely rubbed off on me. I am very much looking forward to starting on a new Lend Lease project through Oakton (Stock Code 'OKN') which will deal with Property Funds Management.

After the induction, I was given a runthrough of the work to be done at Lend Lease. I also had some spare time which I used to look into the Property funds management business in general. I found some very illuminating articles on the software currently available at http://www.avo.gov.au/PropertyFundManagementDiscountedCashFlow.htm. Looking through some of the articles linked to on this site gave me a good overview of some of the tools used. It appears that there is an industry leading application called "DYNA" which is currently used by Lend Lease - along with Excel.

It seems that what ever the company, there is always a worrying number of spreadsheets lurking around where business-critical applications should be. I don't want to go into the many problems there are with spreadsheets - but I would hope that you don't want millions of dollars going awry when you incorrectly cut and paste a formula! After all the spreadsheets I've seen at Queensland Water Infrastructure and Pfizer, my thoughts were .... "Oh no! Not again!"

I also noticed that (like most companies), that Timesheets are not kept at the task level, but rather X number of hours is just allocated to a client. This is a lot simpler than the level of detail we had to provide at SSW. Perhaps our clients were just more demanding.

One of the things that Mark Liu mentioned when I gave him a lift to the bowling was that his brother John Liu (with whom I worked with for a year at SmartSalary) is also at Oakton. This means that I will potentially be working with 4 people I have worked with previously :

  1. Ryan Macnamee will be my supervisor (with whom I worked on the Qantas meal forecasting application at his old company Datacom)
  2. Andrew Weaver (who used to work at SSW) who now works at Lend Lease directly
  3. Tristan Kurniawan (who is contracting to Lend Lease via SSW)
  4. John Liu (if he moves to the Lend Lease project).

So it is really more like changing clients at SSW than moving company... Only bad thing is that I have to start 7:45am tomorrow!

Friday 16 November 2007

The old schema refresh problem with SQL Server 7.0 still exists in SQL 2005

The metadata in SQL Server 2000 and 2005 databases do not automatically update for views when you add to or remove a column to the underlying table. They will only show the original columns from when the view is updated. For example, if you run the following script, you will only be shown the first 2 columns even though there are now 3 columns in the table:



if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Client]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Client]
GO


CREATE TABLE [dbo].[Client] (
[ClientID] [int] IDENTITY (1, 1) NOT NULL ,
[Name] [varchar] (50) NULL ,
) ON [PRIMARY]
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[vwClient]') and OBJECTPROPERTY(id, N'IsView') = 1)
drop view [dbo].[vwClient]
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO

CREATE VIEW dbo.vwClient
AS
SELECT dbo.Client.*
FROM dbo.Client


GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
ALTER TABLE dbo.Client ADD
DateUpdated datetime NULL
GO
COMMIT


SELECT * FROM vwClient


Even worse, if you remove a column, you will get runtime errors guaranteed if you don't recreate all dependant views. To fix this, you should perform an sp_refreshview 'viewname' on the views whose underlying tables have been updated - or you can manually trigger this by going to design view of the view, adding a space to the query definition and clicking OK.

The simplest way to do this is for all views is to use the following stored procedure to detect any changes or problems:



if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[procRefreshAllViews]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[procRefreshAllViews]
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS OFF
GO



CREATE PROC dbo.procRefreshAllViews
AS

SET quoted_identifier off

DECLARE @ObjectName varchar (255)
DECLARE @ObjectName_header varchar (255)

DECLARE tnames_cursor CURSOR FOR SELECT name FROM sysobjects
WHERE type = 'V' AND uid = 1 Order By Name
OPEN tnames_cursor
FETCH NEXT FROM tnames_cursor INTO @Objectname
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN
SELECT @ObjectName_header = 'Refreshing ' + @ObjectName
PRINT @ObjectName_header
EXEC('sp_refreshview ' + @ObjectName)
END
FETCH NEXT FROM tnames_cursor INTO @ObjectName
END
PRINT ' '
SELECT @ObjectName_header = 'ALL VIEWS HAVE BEEN REFRESHED'

PRINT @ObjectName_header
DEALLOCATE tnames_cursor
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO


This problem can be detected easily with SSW SQL Auditor which you can get from http://www.ssw.com.au/ssw/sqlauditor/

The simple way to parse strings to GUIDs in C# or VB.NET

While the religious debate rages between whether we should be using GUIDs as primary keys rages, you can use the Parse method of the System.SqlTypes.SQLGuid class to parse query strings to GUIDs. Here's an example:

return System.Data.SqlTypes.SqlGuid.Parse(Request.QueryString["ParticipantGuid"]).Value;

Thursday 15 November 2007

Unit Test I created to force the load of Referenced DLLs

One of the issues with .NET applications is that they will load references Just In Time (JIT). I did this code about 2.5 years ago - but it is still useful to force the load of referenced DLLs in case your setup package doesn't deploy DLLs correctly. This code allows you to do a "sanity check" on your application if a user reports bugs when the install your Service/Windows Forms application.



   1:  using System;

   2:  using System.Collections;

   3:  using System.Reflection;

   4:  using NUnit.Framework;

   5:   

   6:  namespace UnitTest

   7:  {

   8:         /// <summary>

   9:         /// Forces load of all assemblies to stop any problems when the Setup Package doesn't have all the correct DLLs 

  10:         ///(we had this specific problem when DLLs were added to SSW.Framework.WindowsUI.WizardForms  FuelAdvance.Components.Windows.Wizards.dll)

  11:         /// (davidklein@ssw.com.au 17/06/2005)

  12:         /// </summary>

  13:         /// 

  14:         [TestFixture()]

  15:         public class DLLDependencyTest

  16:         {

  17:                private static ArrayList m_LoadedDLLs;

  18:   

  19:                public DLLDependencyTest()

  20:                {

  21:                       m_LoadedDLLs = new ArrayList();

  22:                       TestLoadReferencedDLLs();

  23:                }

  24:   

  25:                [Test()]

  26:                public void TestLoadReferencedDLLs()

  27:                {

  28:                       Assembly applicationToTest = AppDomain.CurrentDomain.Load("SSWeXtremeEmails");

  29:                       LoadDLL(applicationToTest);

  30:                }

  31:   

  32:                private static void LoadDLL(Assembly assembly)

  33:                {

  34:                       AssemblyName[] referencedAssemblies = assembly.GetReferencedAssemblies();

  35:                       foreach (AssemblyName referencedAssemblyName in referencedAssemblies)

  36:                       {

  37:                             Assembly currentReferencedAssembly = Assembly.Load(referencedAssemblyName);

  38:                             if (!m_LoadedDLLs.Contains(currentReferencedAssembly.FullName))

  39:                             {

  40:                                    m_LoadedDLLs.Add(currentReferencedAssembly.FullName);

  41:                                    LoadDLL(currentReferencedAssembly);

  42:                             }

  43:                       }

  44:                }

  45:         }

  46:  }

Wednesday 14 November 2007

What is Project Management Anyway? Different clients have different expectations

The below is a snippet of an email I sent to resolve an issue we had with "Project Management" one of the problems was that our client thought it was one thing (based on their experiences), and we had a different notion of what "Project Management" was.

==========
<Email Snippet>
==========

Marlon,

I think some of our clients such as Westlink and Pfizer have a set idea of what Project Management is – they believe it implies “Dedicated Project Management and Oversight”. We have typically not been providing this as part of our other “Project Management” tasks in many projects, and this could lead to disappointment. In the extremely successful Maximus project, Peter McKeown from Symmetric was doing the project management by:
a. constantly planning
b. suggesting alternative options
c. talking to the clients
d. dictating our work for us
e. giving the project a high level of visibility by organising Friday demos and making sure all stakeholders were present and gave input.

This worked very well. He added real value as a Project Manager.

Project Management

I see project management as separate from creating release plans and the day-to-day meetings that developers have with clients. I don’t believe we are doing project management just by having developers send release plans and sending clarification emails. Project management is not what developers do. Consequently, I have separated this dedicated function out as an optional extra in the SSW Proposal Template. It is like an insurance policy and really needs to be done by a dedicated person that will perform the following function (as per Wikipedia):

"The planning, monitoring and control of all aspects of the project and the motivation of all those involved in it to achieve the project objectives on time and to the specified cost, quality and performance."

“Project management is quite often the province and responsibility of an individual project manager. This individual seldom participates directly in the activities that produce the end result, but rather strives to maintain the progress and productive mutual interaction of various parties in such a way that overall risk of failure is reduced.”

I believe the project manager should take on the role of and liaising with the client about problems and potential problems with the project. It is the project managers role to do the following:

1. Helping to make sure the customer is informed of budget overruns and technical issues and given the earliest possible opportunity to:
a. stop the project
b. remove tasks
c. reassign tasks to cheaper resources (e.g. China).
d. Reassign the tasks to more effective resources

This is assisted by generating Timing, ETAs and financial reports that developers don’t normally generate:
a. Financial Reporting e.g. using Microsoft Project via TFS WorkItems (based on our proposal spreadsheet) to show baseline estimates against actual figures and predict problems and deadlines.
b. Updating TimePRO projects and the project status and sending out the Project Progress report.

2. Keeping a close eye on developers/team leaders to see if there are any problems that have simpler solutions or would be better done by an expert/outside resource or using a 3rd party tool.

3. Helping to make sure that the ongoing quality of the product is good standards are enforced. E.g.
a. Identify opportunities for automated testing and unit tests.
b. Running code auditor/SQL auditor/FXCop.
c. The most effective tools are used such as using code generators for repetitive work.
d. Suggesting refactoring possibilities if there is repetitive code.

4. Helping to Removing any client-side or developer-side bottlenecks to development (e.g. following up questions that the client is not answering/developers just waiting on response or are stuck with a product bug or coding issue)

5. Helping developers to determine what should go into the current release and what should go later (scoping – ie what is in scope/out of scope)

6. Updating the scope documents (this is not done in normal projects; we typically create the document and leave it to gather dust)

2 main tasks are performed by developers which were previously classified as “Project Management” by us are:
1. “Project Administration” which includes creating releases and sending out release plans and done emails & timesheets
2. “Requirements Gathering, Client Meetings and Clarifications”

With this in mind.... I believe that dedicated project management (e.g. what I’ve been doing with Westlink) should be an optional extra in the template. I have updated the template accordingly as follows:

Updates to Template:
1. Changed “Project Management” to “Project Administration” (I think it reflects our activities more accurately for that task). I kept Project Administration (primarily Timesheets and Invoicing) at 2 hours per 40 work hours.

2. Added new task type – “Requirements Gathering, Client Meetings and Clarification.” I put this down as 4 hours per 40 work hours.

3. Added an “Optional Extra Items” Group to clearly indicate that a dedicated Project Manager is not normally used on our projects. Our normal task system (eXtreme Emails) provides basic project management capabilities.
4. Includes the “Weeks” calculation

Figure 1 - Updates to the SSW Proposal Template







Figure 2 - Added Dedicated Project Management as an optional Extra for the whole project


==========
</Email Snippet>
==========