Sunday, September 16, 2007

simplified command pattern (non-emit) AOP: anonymous delegate and generics

simplified command pattern (non-emit) AOP: anonymous delegate and generics

In my previous blogs, I mentioned that with C# 2.0, we finally can do similar things like java's anonymous inner class for object-adapter pattern. This will simplify command pattern AOP.

Basically, it is an easier and better way of strict inheritance of template method pattern.

Generics here is just a helper, it makes the "template" more like, well, a template -- you do not really need it, a lot of times, if your template does not need to be general.

Note that this is still too expensive, so, it can only be used for facade-level methods; so, it cannot replace real (i.e. emit-based) AOP that can be used for entity get-set methods.

I know, I used too much "patterns" here, so, here are code -- copied/pasted.

-----------------------------------------
-----------------------------------------




public static EmployeeDetails FindEmployee(int empId)
{
WindowsIdentity id = HttpContext.Current.User.Identity as WindowsIdentity;
WindowsImpersonationContext wic = id.Impersonate();
try
{
BrowseOrganizationServiceWse bosw = new BrowseOrganizationServiceWse();
bosw.SetPolicy("kerberos");
FindEmployeeResult fer = bosw.FindEmployee(empId);
EmployeeDetails ed = fer.Item as EmployeeDetails;
return ed;
}
finally
{
wic.Undo();
}
}

--------------------------------templeate ThreadStart deledagate is simply return void

private static void RunMethodImpersonating(ThreadStart method)
{
WindowsIdentity id = HttpContext.Current.User.Identity as WindowsIdentity;
WindowsImpersonationContext wic = id.Impersonate();
try
{
method();
}
finally
{
wic.Undo();
}
}


---------------------------

public static EmployeeDetails FindEmployee(int empId)
{
EmployeeDetails ed = null;

RunMethodImpersonating(
delegate
{
BrowseOrganizationServiceWse bosw = new BrowseOrganizationServiceWse();
bosw.SetPolicy("kerberos");
FindEmployeeResult fer = bosw.FindEmployee(empId.ToString());
ed = fer.Item as EmployeeDetails;
}
);

return ed;
}

-----------------------------
-----------------------------
-----------------------------
-----------------------------


try
{
// cmd is from IDbCommand (SqlCommand, OracleCommand, OleDbCommend etc) type.
// This is the command which we want to run against our database.

using (IDbConnection conn = ProviderFactory.CreateConnection())
{
cmd.Connection = conn;

conn.Open();

// use the cmd object.

} //"using" will close the connection even in case of exception.
}
catch (Exception e)
{
// 1. Trace ?
// 2. Rollback transaction ?
// 3. Throw a wrapper exception with some more information ?
}



--------------------------
--------------------------
--------------------------

public delegate T CommandHandler(IDbCommand cmd);

--------------------------
--------------------------template
--------------------------
///
/// Simple command executer "design pattern".
///

/// The type to return
/// The command
/// The handler which will receive the open command and handle it (as required)
/// A generic defined result, according to the handler choice
public static T ExecuteCommand(IDbCommand cmd, CommandHandler handler) //*1
{
try
{
using (IDbConnection conn = ProviderFactory.CreateConnection()) //*2
{
cmd.Connection = conn;

// Trace the query & parameters.
DatabaseTracer.WriteToTrace(TraceLevel.Verbose, cmd, "Data Access Layer - Query profiler"); //*3

conn.Open();

return handler(cmd); //*4
} //"using" will close the connection even in case of exception.
}
catch (Exception e)
{
// Trace the exception into the same log.
Tracer.WriteToTrace(TraceLevel.Error, e, "Data Access Layer - Exception"); //*5

throw WrapException(e); //*6
}
}


-----------------------------------
-----------------------------------
public delegate T ReaderHandler(IDataReader reader);

-----------------------------------
-----------------------------------
///
/// Execute the db command as reader and parse it via the given handler.
///

/// The type to return after parsing the reader.
/// The command to execute
/// The handler which will parse the reader
/// A generic defined result, according to the handler choice
public static T ExecuteReader(IDbCommand cmd, ReaderHandler handler)
{
return ExecuteCommand(cmd,
delegate(IDbCommand liveCommand) //*1
{
// This is the anonymous delegate handler.
// REMINDER: The original template sends the live command as parameter.
IDataReader r = liveCommand.ExecuteReader();
return handler(r);
});
}


-----------------------------------
-----------------------------------
///
/// Retrieve the persons according to the specified command.
///

/// Typed collection of person.
public static List GetPersonsList()
{
IDbCommand cmd = ProviderFactory.CreateCommand();
cmd.CommandText = "SELECT Name,Age,Email FROM Persons";
cmd.CommandType = CommandType.Text;

return DalServices.ExecuteReader>(cmd,
delegate(IDataReader r)
{
List persons = new List();

while (r.Read())
{
// Create a Person object, fill it by the reader and add it to the "persons" list.
Person person = new Person(r["Name"].ToString(), Convert.ToInt32(r["Age"]), r["Email"].ToString());
persons.Add(person);
}

return persons;
});
}


----------------------------------
----------------------------------
----------------------------------

///
/// Retrieve the persons xml according to the specified command.
///

/// Xml representation of the persons.
public static string GetPersonsXml()
{
IDbCommand cmd = ProviderFactory.CreateCommand();
cmd.CommandText = "SELECT Name,Age,Email FROM Persons";
cmd.CommandType = CommandType.Text;

return DalServices.ExecuteReader(cmd,
delegate(IDataReader r)
{
StringBuilder builder = new StringBuilder(500);

builder.Append("");
while (r.Read())
{
// Create a Person object, fill it by the reader and add it to the "persons" list.
Person person = new Person(r["Name"].ToString(), Convert.ToInt32(r["Age"]), r["Email"].ToString());
builder.Append(person.ToXml());
}
builder.Append("
");

return builder.ToString();
});
}



///
/// Execute the db command in "NonQuery mode".
///

/// The command to parse
/// Affected rows number
public static int ExecuteNonQuery(IDbCommand cmd)
{
return ExecuteCommand(cmd,
delegate(IDbCommand liveCommand)
{
return liveCommand.ExecuteNonQuery();
});
}

///
/// Execute the db command in "Scalar mode".
///

/// The type to return after parsing the reader.
/// The command to execute
/// A generic defined result, according to the handler choice
public static T ExecuteScalar(IDbCommand cmd)
{
return ExecuteCommand(cmd,
delegate(IDbCommand liveCommand)
{
return (T)liveCommand.ExecuteScalar();
});
}

Saturday, September 01, 2007

Why and How to use server side ajax – even you do not like it – part 2

Why and How to use server side ajax – even you do not like it – part 2

As my previous blogs show, my basic estimate is that it will take one or two years for Ajax controls to be really mature so that we can safely wrap them on the server side (and still use javascript API). Before that, it is simply not cost-effective; we will spend a lot of time on dealing with bugs, limitations, and covering-ups. You can avoid all those by learning to treat javascript as an engineering language and is required for every developer.

However, it is not at its prime time, yet; its prime time will be after a few months or half a year after Orcas release; as a compromise, at least before Orcas, I have to use C1WebGrid etc., instead of client side ajax controls (the best is ext, and yahoo is also good).

As I pointed out in my previous blogs, the key is to pay attention to the javascript API.

However, the problem is, you cannot find those APIs! Although they are advertised in some fancy wording, the reality is, they are not ready yet! Actually, the reality is even uglier: because the engineering section is not really ready yet, the whole thing is in the hands of marketing people, they do their best to do their spin to make the weakness of the product into features. For example, they turn the fact that there is not client side javascript API into something “you can use ajax automatically” – you would think that since you can do it automatically, so, you certainly can do it manually – no, you cannot! The reality is: the product is not ready for that yet!

A good example is the C1WebGrid and C1WebDialog. C1WebGrid is not really ajax ready yet (ya, it has canned ajax features; that means almost nothing nowadays!); C1WebDialog is a little better. However, both are marketed as ajax ready. If you believe those marketing hypes for C1WebGrid, then, you will really be disappointed, or, misled, by thinking that “ajax can only so little”. As a result, in such a situation, user developers have to lower their standard, and use postback mostly, and use callback whenever they hit one by chance and luck. Note that some marketing materials lead you believe that update panel does not use postback. That is not true. Update panel uses postback. As a result, if it is not client side API, it uses postback, even it is not the whole page postback. As a result, we need to talk about how to do Postback or updatePanel in a way to limit their damages.

Updatepanel postback is still bad, comparing with real client side API call back, for two reasons: (a) Postback always invokes the whole machinery of postback, so, it is bad for performance hit. However, this part of it is not big deal for us, because its damage is only performance, as long as it is fast enough for users, we are fine. (b) More importantly, a lot of times, postback has to use a twisted way to do things. For example, the "multi-value selection shuttling grid pairs": you have two grids, the left or top grid, and the right or bottom grid; the left one is for the available, the right one is for the selected; so, you do the selection by move a row from the left to the right. The selection action is a pure client side thing, until you click “submit selections” button. However, postback makes this a server side operation. Worse, sometimes, to make things perform better, you have to create a workaround on the UI: you put a checkbox on each row, and let users select a few rows, then, click a “select” button ( but this select button is not the “submit selections” – the reason is that there is paging on the available grid – that is actually the reason why we need to shuttle the selected one to a new grid) – the whole thing is getting more and more confusing when you try to put more and more workarounds.

I believe we have a few options:

(i) Find those workarounds;

(ii) Say no to users;

(iii) Buy more new controls, for example, a shuttle control. You will need perhaps a dozen of controls. This has two sides:

(1) new controls has more canned-ajax (i.e., build-in ajax without javascript);

(2) new controls has more javascript APIs.

Obviously, iii-(2) is our real hope! However, iii-(1) is actually also a good one – once you have a dozen pretty good controls, your javascripting will be very “regularized” – if you need to do any javascripting at all! So, the approach is that we will try to push to buy more new controls. Most of the time, it has to be from the same vendor -- for political reasons; however, sometimes, as long as it is “server side controls”, it is also fine.

In short, here is the summary:

(a) For now, for some old so-called ajax-enabled server side controls, most of the time, we have to use postback/updatePanel, insteald of real callbacks;

(b) We buy most recent server side controls, for both purposes: (i) we can begin to use a little bit javascript API, and (ii) we do not need javascripts directly.

--------------------------------------------


Below are the notes that I study the user manual of C1WebGrid.

http://www.componentone.com/Support.aspx?ItemType=2&TabTypeID=2&PanelIndex=2&TabMapID=144&Itemid=0&Tabid=171&SubCategoryTypeID=0

http://helpcentral.componentone.com/

http://helpcentral.componentone.com/Article.aspx?ID=71

Note: You must create a ComponentOne Account and register your product with a valid serial number to obtain

support using some of the above methods.
-----------------------
The ComponentOne Studio for ASP.NET 2.0 installation program will create the directory C:\Program

Files\ComponentOne Studio.NET 2.0. This directory contains the following subdirectories:

bin Contains copies of all ComponentOne binaries (DLLs, EXEs).

Common Contains support and data files that are used by many of the demo

programs.

Help Contains online documentation for all Studio components.

C1WebGrid Contains samples for C1WebGrid.

http://helpcentral.componentone.com/ProductResources.aspx.

-----------------------

Select the ComponentOne C1WebGrid assembly from the list on the .NET tab or browse to find the

C1.Web.C1WebGrid.2.dll file and click OK.

-----------------------

an assembly resource file which contains the actual run-time license (host dll or for asp 2.0 App_Licenses.dll assembly)

a "licenses.licx" file that contains the licensed component strong name and version information (Show All Files on the Project menu) ---- to add the file, add the control to the form (then, can delete the control)

----------------------------------

Right-click C1WebGrid and select Show Smart Tag from the context menu. Select Property builder from

the C1WebGrid Tasks menu.

Or,

1. Select the C1WebGrid component.

2. Select Properties Window from the View menu on the Visual Studio toolbar.

Or,

1. Right-click C1WebGrid and select Auto Format from the context menu. The Auto Format dialog box opens.