Friday, August 31, 2012

dhtmlx TreeGrid

mygrid.setCellExcellType(100000, 2, "ro");
mygrid.setItemImage(100000, "blank.gif");

rowId = 100000
column index = 2
ro = read-only

Friday, August 24, 2012

TeamCity Edit Checkout Rules


+:.
-:/db
-:/docs

CI Build File and Web Application Deployment

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="4.0"
         DefaultTargets="Compile">
  <UsingTask AssemblyFile="C:\svn\project\src\packages\ThirdParty\MSBuildCommunityTasks\AsyncExec.dll" TaskName="AsyncExec.AsyncExec"/>
  <UsingTask AssemblyFile="C:\svn\project\src\packages\ThirdParty\MSBuildCommunityTasks\MSBuild.Community.Tasks.dll" TaskName="MSBuild.Community.Tasks.XmlRead" />
  <ItemGroup>
    <SolutionRoot Include="."/>
    <BuildArtifacts Include=".\buildartifacts\"/>
    <SolutionFile Include="..\src\Project.sln"/>
    <MsDeploy Include="..\src\packages\MSdeploy2\msdeploy.exe"/>
    <PackageFile Include=".\buildartifacts\package\Project.zip"/>
    <Website Include=".\buildartifacts\_PublishedWebsites\Project.Website"/>
  </ItemGroup>
  <Target Name="Clean">
    <RemoveDir Directories="@(BuildArtifacts)"/>
  </Target>
  <Target Name="Init" DependsOnTargets="Clean">
    <MakeDir Directories="@(BuildArtifacts)"/>
  </Target>
  <Target Name="Compile" DependsOnTargets="Init">
    <PropertyGroup>
       <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    </PropertyGroup>
    <MSBuild Projects="@(SolutionFile)" Properties="OutDir=%(BuildArtifacts.FullPath);
    Configuration=$(Configuration)"/>
  </Target>
  <Target Name="Package" DependsOnTargets="Compile">
    <PropertyGroup>
      <PackageDir>%(PackageFile.RootDir)%(PackageFile.Directory)</PackageDir>
      <Source>%(Website.FullPath)</Source>
      <Destination>%(PackageFile.FullPath)</Destination>
    </PropertyGroup>
    <MakeDir Directories="$(PackageDir)"/>
    <Exec Command='"@(MsDeploy)" -verb:sync -source:iisApp="$(Source)" -dest:package="$(Destination)"'/>
  </Target>
  <Target Name="DeployToDev" DependsOnTargets="Package">
    <PropertyGroup>
      <WebServerName>DEVSERVER</WebServerName>
      <Source>%(PackageFile.FullPath)</Source>
    </PropertyGroup>
    <Exec Command='"@(MsDeploy)" -verb:sync -source:package="$(Source)" -dest:iisApp="Default Web Site", computerName=$(WebServerName),username=,password='/>
  </Target>
</Project>

SQL Server Service Logon


Thursday, August 9, 2012

Refresh SVN Status Often

It will bring the 'Update Solution to Latest Version' option back.




Tuesday, July 10, 2012

ASP.NET MVC Remote Validation

public class TimeCard : IValidatableObject
{
...
[Remote("CheckUserName", "Home", ErrorMessage="Username is invalid")]
public string UserName{get; set;}
...
}

public class HomeController : Controller
{
...
public JsonResult CheckUsername(string username)
{
var result = false;
if(username == "tkhuc")
{
result = true;
}
return Json(result, JsonRequestBehavior.AllowGet);
}

ASP.NET MVC Custom Client Validation

Implement IClientValidatable
Implement a jQuery validation method
Implement an unobtrusive adapter

public class GreaterThanDateAttribute : ValidationAttribute, IClientValidatable
{
...
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata, ControllerContext context)
{
var rule  = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "greater";
rule.ValidationParameters.Add("other", otherPropertyName);
yield return rule;
}

customvalidation.js
/// <reference path="jquery-1.4.4-vsdoc.js" />
/// <reference path="jquery.validate-vsdoc.js" />
/// <reference path="jquery.validate.unobtrusive.js" />

jQuery.validator.addMethod("greater", function(value, element, param)
{
return Date.parse(value) > Date.parse($(param).val());
});

jQuery.validator.unobtrusive.adapters.add("greater", ["other"], function(options){
options.rules["greater"] = "#" + options.params.other;
options.messages["greater"] = options.message;                    // ~ rule.ErrorMessage
});

Generated html output
<input class="text-box-single-line" id="EndDate" name="EndDate" type="text" value="1/1/2010 12:00:00 AM"
data-val="true"
data-val-greater="EndDate must be greater than StartDate"
data-val-greater-other="StartDate"
/>

ASP.NET MVC Client Validation

<script src="jquery-1.4.4.min.js" type="text/javascript"></script>
<script src="jquery.validate.min.js" type="text/javascript"></script>
<script src="jquery.validate.unobtrusive.min.js" type="text/javascript"></script>

Global Settings
<appSettings>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavascriptEnabled" value="true"/>
</appSettings>


Page-level Settings
@Html.EnableClientValidation(false/true)
@Html.EnableUnobtrusiveJavascript(false/true)


Unobtrusive Javascript Validation
<input id="ConfirmHours" name="ConfirmHours" type="text value="0" class="text-box-single-line"
data-val="true"
data-val-equalto="ConfirmHours and Hours do not match."
data-val-equalto-other="*.Hours"
data-val-number="The field ConfirmHours must be a number."
data-val-range="The field ConfirmHours must be between 1 and 120."
data-val-range-max="120"
data-val-range-min="1"
data-val-required="The ConfirmHours field is required."
/>
<span class="field-validation-valid" data-valmsg-for="ConfirmHours" data-valmsg-replace="true"/>

Data Validation using Data Annotations

Data Annotations

[Required()]
[StringLength(25)]

[Range(1,120)]
public int Hours{get; set;}

[Compare("Hours")]
public int ConfirmHours{get; set;}

<div class="editor-label">
@Html.LabelFor(model=>model.ConfirmHours, "Please confirm the hours worked")
</div>
<div class="editor-field">
@Html.EditorFor(model=>model.ConfirmHours)
@Html.ValidationMessageFor(model=>model)
</div>

Custom Validation Attributes

public class GreaterThanDateAttribute : ValidationAttribute
:base("{0} must be greater than {1}")
{
public string OtherPropertyName{get; set;}

public GreaterThanDateAttribute(string otherPropertyName)
{
OtherPropertyName = otherPropertyName;
}
public override string FormatErrorMessage(string name)
{
return String.Format(ErrorMessageString, name, otherPropertyName);
}

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var otherPropertyInfo = validationContext.ObjectType.GetProperty(otherPropertyName);
var otherDate = (DateTime)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
var thisDate = (DateTime)value;
if(thisDate <= otherDate)               //failed
{
var message = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(message);
}

return null;                                      //succeeded
}
}

public DateTime StartDate{get; set;}

[GreaterThanDate("StartDate")]
public DateTime EndDate{get; set;}


Sunday, July 8, 2012

MVC 3 Child Output Caching

Controllers\HomeController.cs
public class HomeController : Controller
{
[OutputCache(Duration = 60)]
public ActionResult Index()
{
var model = DateTime.Now;
return View(model);
}

[ChildActionOnly]
[OutputCache(Duration = 10)]
public PartialViewResult CurrentTime(){
var model = DateTime.Now;
return PartialView(model);
}
}

Views\Home\CurrentTime.cshtml
@model DateTime
<p>This is the child action result, current time is: @Model.ToLongTimeString()</p>

Views\Home\Index.cshtml
@model DateTime
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
<div>This is the index view for Home, rendering at time: @Model.ToLongTimeString()</div>
<div>@Html.Action("CurrentTime")</div>

MVC Request Validation

Be careful when sending HTML to server.

[ValidateInput(false)] - turn off all verification

[AllowHtml] - granular verification for view model

MVC 3 Action Results

HttpNotFoundResult
public ActionResult Results()
{
return HttpNotFound();
}

HttpRedirectResult
public ActionResult Results()
{
return RedirectPermanent("http://google.com");
}

HttpStatusCodeResult
public AtionResult Results()
{
return HttpStatusCodeResult(415, "Media type not recognized");
}

Global Filters

Controllers\HomeController.cs
public class HomeController : Controller
{
public ActionResult Index()
{
throw new InvalidOperationException();
return View();
}

Views\Shared\Error.cshtml
<h2>Sorry, an error occurred while processing your request.</h2>

Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}

protected void Application_Start()
{
RegisterGlobalFilters(GlobalFilters.Filters);
}

Web.config
<system.web>
<customErrors mode="On"/>
....

Action filters: [Authorize], [HandleError]

Custom Action Filters
public class LogAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext){}
public override void OnActionExecuted(ActionExecutedContext filterContext){}
public override void OnResultExecuting(ResultExecutingContext filterContext){}
public override void OnResultExecuted(ResultExecutedContext filterContext){}
}

Sunday, June 3, 2012

Testing the Sad Path

When_passing_null_measurements.cs

using NUnit.Framework;

namespace Domain.Tests.AveragingCalculator_Tests.SadPath
{
[Category("AveragingCalculator")]
public class When_passing_null_measurements
{

private AveragingCalculator _averageCalculator;

[SetUp]
public void Setup()
{
_averageCalculator = new AveragingCalculator();
}

[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void When_measurement_is_null()
{
var measurements = Mother.Get4Measurements();
measurements.Add(null);

_averageCalculator.Aggregate(measurements);
}
}

AveragingCalculator.cs

namespace Domain
{
public class AveragingCalculator : IAggregateCalculator
{
if(measurements.Contains(null))
{
throw new ArgumentNullException();
}

return new Measurement()
{
HighValue=measurements.Average(m=>m.HighValue),
LowValue=measurements.Average(m=>m.LowValue)
};

}

Test Project Structure

Solution

  • Domain
    • AggregationType.cs
    • AveragingCalculator.cs
    • IAggregateCalculator.cs
    • IGrouper.cs
    • Measurement.cs
    • MeasurementAggregator.cs
    • ModalCalculator.cs
    • SizeGrouper.cs
  • Domain.Tests
    • References
      • Domain
    • AveragingCalculator_Tests
      • SadPath
        • When_passing_null_measurements.cs
          • When_measurements_are_null()
          • When_measurent_is_null()
      • When_averaging_4_numbers.cs
      • When_averaging_several_numbers.cs
    • MeasurementAggregator_Tests
      • When_aggregating_four_measurements.cs
    • Mother.cs

Thursday, May 24, 2012

LINQPad

string[] names = {"Michael", "Long"};

IEnumerable<string> query = names
.Where(n=>n.Contains("o"))
.OrderBy(n=>n.Length)
.Select(n=>n.ToUpper());

query.Dump();

// same query as above
IEnumerable<string>filtered = names.Where(n=>n.Contains("o"));
IEnumerable<string>sorted = filtered.OrderBy(n=>n.Length);
IEnumerable<string>finalQuery = sorted.Select(n=>n.ToUpper());

filtered.Dump("Filtered");
sorted.Dump("Sorted");
finalQuery.Dump("FinalQuery");

LINQ Dynamic Query

using System.Linq.Dynamic

var repository = new EmployeeRepository();

var query = repository.GetAll()
.AsQueryable()
.OrderBy("Name")
.Where("DepartmentID = 1");

LINQ Joins


var employeeRepository = new EmployeeRepository();
var departmentRepository = new DepartmentRepository();

Inner Join (Normal)

var employees =
from employee in employeeRepository.GetAll()
join department in departmentRepository.GetAll()
on employee.DepartmentID equals department.ID
select new {employee.Name, Department = department.Name};

Left Join (Grouping on Departments)

var query =
from d in departmentRepository.GetAll()
join e in employeesRepository.GetAll()
on d.ID equals e.DepartmentID
into ed
select new
{
Department = d.Name,
Employees = ed};

foreach(var group in query)
{
Console.WriteLine(group.Department);
foreach(var employee in group.Employees)
{
Console.WriteLine("\t" + employee.Name);
}
}

Cross Join (for completeness)


var employees =
from employee in employeeRepository.GetAll()
join department in departmentRepository.GetAll()
on employee.DepartmentID equals department.ID
select new {employee.Name, Department = department.Name};

LINQ Grouping and Projecting

var repository = new EmployeeRepository();

Comprehensive Query Syntax

var queryByDepartment =
from e in repository.GetAll()
group e by e.DepartmentID
into eGroup
orderby eGroup.Key descending
where eGroup.Key < 3
select new
{
DepartmentID = eGroup.Key,
Count = eGroup.Count(),
Employees = eGroup
};

Extension Methods with Lambda Expressions

var queryByDepartment2 =
repository.GetAll()
.GroupBy(e=>e.DepartmentID)
.OrderByDescending(g=>g.Key)
.Where(g=>g.Key < 3)
.Select(g=>
new
{
DepartmentID = g.Key,
Count = g.Count(),
Employees = g
});

foreach(var group in queryByDepartment2)
{
Console.WriteLine("DID: {0}, Count: {1}",
group.DepartmentID,
group.Count);

foreach(var employee in group.Employees)
{
Console.WriteLine("\t{0}:{1}", employee.DepartmentID, employee.Name);
}
}

Restrictions for Implicit Typing

The following var statements result in compiler errors:

var i;

var j, k = 0;

var n = null;

var number = "2";
int x = number + 1;

Extension Methods with Lambda Expressions vs. Comprehensive Query Syntax

Employee[] employees = new Employee[]
{
new Employee{ID=1, Name="The"},
new Employee{ID=2, Name="Foo"}
};

//Employee the = Array.Find(employees, FindThePredicate);
Employee the = Array.Find(employees, e=>e.Name == "The");

Comprehensive Query Syntax

Collection

IEnumerable<Employee> query1 =
from e in employees
where e.Name == "The"
orderby e.ID ascending
select e;

Single Item

Employee query1 =
(from e in employees
where e.Name == "The"
orderby e.ID ascending
select e).First();

Extension Methods with Lambda Expressions

Collection

IEnumerable<Employee> query2 =
employees.Where(e=>e.Name=="The")
.OrderBy(e=>e.ID)
.Select(e=>e);

Single Item

Employee query2 =
employees.Where(e=>e.Name=="The")
.OrderBy(e=>e.ID)
.Select(e=>e)
.First();

Action and Func Delegates and Lambda Expressions

private static void ActionAndFunc()
{
Action printEmptyLine = () => Console.WriteLine();
Action<int> printNumber = x => Console.WriteLine(x);
Action<int, int> printTwoNumbers = (x,y) =>
{
Console.WriteLine(x);
Console.WriteLine(y);
};

Func<DateTime> getTime = () => DateTime.Now;
Func<int, int> square = x => x*x;
Func<int, int, int> multiply = (x,y) => x*y;

printEmptyLine();
printNumber(6);
printTwoNumbers(2,3);

DateTime now = getTime();
int z = multiply(4,7);
}

Wednesday, May 23, 2012

Named vs. Anonymous Method vs. Lambda Expressions

Named Method Predicate - passing method pointers

static void Main(string[] args)
{
Employee the = Array.Find(employees, FindThePredicate);
}

static bool FindThePredicate(Employee e)
{
return e.Name == "The";
}

Anonymous Method

static void Main(string[] args)
{
Employee the = Array.Find(employees,
delegate(Employee e)
{
return e.Name == "The";
}
);
}

Lambda Expression - anonymous method but with less code

static void Main(string[] args)
{
Employee the = Array.Find(employees, (e) => e.Name =="The");
}