Thursday, May 10, 2007

Find ASP.Net page name – a trick by finding relative url

May be not a usual need, but just thought about it… find the page name from the URL. The url might have query string or other things, but just want the page name like AboutUs.aspx from http://mysiteURL/AboutUs.aspx.

First, I created a RelativeURL method that takes the tilde (~) version and returns the absolute url from it. 2.0 framework introduced this cool tilde feature, but some times we need to find in script; so this method:

public static string RelativeURL(string relativeURL)

{

Image imgTemp = new Image();

imgTemp.ImageUrl = relativeURL;

return imgTemp.ResolveClientUrl(imgTemp.ImageUrl);

}

Well, a simple trick did that. Now using this method, we can grab just page name from any url, like:

public static string PageName

{

get

{

return RelativeURL("~" +

HttpContext.Current.Request.ServerVariables["URL"].ToString());

}

}

Love coding!

Wednesday, May 2, 2007

Redirect to SSL

Often times we come across need to redirect (enforcing) a request to SSL. To show how to do that server side, I wrote this blog a while ago and being a popular post, I'm writing a clean code that might help lot of my readers.

First, let's write a utility class that returns whether the request is SSL or not:


/// <summary>
/// Gets if the url is secure or not
/// </summary>
public static bool isSecureURL
{
get
{
// Check the request
if (HttpContext.Current.Request.IsSecureConnection ||
(HttpContext.Current.Request.ServerVariables["HTTPS"] != null &&
HttpContext.Current.Request.ServerVariables["HTTPS"].ToLower() == "on"))
return true;
if ((!string.IsNullOrEmpty(HttpContext.Current.Request.ServerVariables["HTTP_HOST"].ToString())) &&
(HttpContext.Current.Request.ServerVariables["HTTP_HOST"].ToString().EndsWith("443")))
return true;
// url is not secure:
return false;
}
}


Notice that I use 2 condition checks. The first might work for some users but for others who go through proxy with no forwarding information, I'm trying to see if proxy is forwarding HTTP_HOST ending with SSL port (I use 443).

And now, to implement use something like this:


if (//My Condition to see whether I should implement SSL or not)
{
if (!Utility.isSecureURL)
Response.Redirect(Request.Url.ToString().Replace("http://", "https://"));
}



Love coding!

Auto jump JavaScript – like phone number text boxes

If we want to build a set of text boxes that set focus to next control when set length of text if filled, one way we can do so is by writing JavaScript for key strokes and counting characters filled. Let’s jump into the code on how I do this:

StringBuilder sbJS = new StringBuilder();

sbJS.Append("<script language=javascript> ");

sbJS.Append("var isNN = (navigator.appName.indexOf(\"Netscape\")!=-1); ");

sbJS.Append("function autoTab(input,len, e) {");

sbJS.Append(" var keyCode = (isNN) ? e.which : e.keyCode;");

sbJS.Append(" var filter = (isNN) ? [0,8,9] : [0,8,9,16,17,18,37,38,39,40,46];");

sbJS.Append(" if(input.value.length >= len && !containsElement(filter,keyCode)) {");

sbJS.Append(" input.value = input.value.slice(0, len);");

sbJS.Append(" input.form[(getIndex(input)+1) % input.form.length].focus();");

sbJS.Append(" }");

sbJS.Append(" function containsElement(arr, ele) {");

sbJS.Append(" var found = false, index = 0;");

sbJS.Append(" while(!found && index < arr.length)");

sbJS.Append(" if(arr[index] == ele)");

sbJS.Append(" found = true;");

sbJS.Append(" else");

sbJS.Append(" index++;");

sbJS.Append(" return found;");

sbJS.Append(" }");

sbJS.Append(" function getIndex(input) {");

sbJS.Append(" var index = -1, i = 0, found = false;");

sbJS.Append(" while (i < input.form.length && index == -1)");

sbJS.Append(" if (input.form[i] == input)index = i;");

sbJS.Append(" else i++;");

sbJS.Append(" return index;");

sbJS.Append(" }");

sbJS.Append(" return true;");

sbJS.Append("}");

sbJS.Append("</script>");

//Register client script

if (!Page.ClientScript.IsClientScriptBlockRegistered("jsAutoFocus"))

Page.ClientScript.RegisterClientScriptBlock(GetType(),

"jsAutoFocus",

sbJS.ToString());

//Add JS to your textboxes...

tbxArea.Attributes.Add("onKeyUp", "return autoTab(this, 3, event);");

tbxLocal3.Attributes.Add("onKeyUp", "return autoTab(this, 3, event);");

tbxLocal4.Attributes.Add("onKeyUp", "return autoTab(this, 4, event);");

Love coding!

Tuesday, May 1, 2007

Maintaining session state in load balanced environment

Most of us are familiar with using session; but it’s always a point to think and evaluate a best suited model when building session state in a load balanced environment. To preserve session, we could:

Ø design applications to use client (cookies?)

Ø design out-of-process session management; like a State Server or SQL Server

Ø or use Client Affinity (sticky or pinned sessions)

We need to evaluate our environment, hardware requirements, application requirements etc. before we decide on which model to be used. There is no “best method” of these. We could come up with our versions of pros and cons for each. Some of them I think are:

Cookies

Actually this one is mostly ruled out in many situations I know of; some of the reasoning’s are cookies might be disabled on client and cookies are not secure… so on.

Out-of-process

State Server – requires additional or a designated server to handle the session. Since the session is held in server’s process it is a little faster compared to SQL Server but at the cost of consuming (potentially) server resources. If not designed/used carefully, could bring down the server to halting slow.

SQL Server – requires SQL Server (SQL Express might do for free) and additional maintenance on it. It adds additional db I/O and potentially slower than other options. However, provides guaranteed session data consistency (ACID).

One key disadvantage with out-of-process models, I think, is with SSL. SSL adds a lot of overhead during establishing session connection to the server. If load balancer sends to different server each time, there could be a potential loss in efficiency in trying to get SSL established for every request. This could be avoided using the third option – client affinity.

Client Affinity

When client affinity is enabled, Network Load Balancing directs all TCP connections to the same cluster host. This allows session state to be maintained in host memory. However, this could potentially add load imbalances due to pinned sessions. Chris Keyser’s blog has some good points that brings up advantages & disadvantages of using this model.

As we see, there is not best approach to this issue other than evaluating what best suits our current architecture and available resources. In any case, this article in MSDN has some good points for best practices using session state.

Love coding!

Thursday, April 19, 2007

C# String ToTitleCase – intial letter capitalization of the string

To convert string to title case (capitalize initial letters of the string), there is not string method to call. However, we can use the CultureInfo from Globalization and use the TextInfo’s ToTitleCase method. Here is how it can be done in C#:

public static string ToTitleCase(string inputString)

{

System.Globalization.CultureInfo cultureInfo =

System.Threading.Thread.CurrentThread.CurrentCulture;

System.Globalization.TextInfo textInfo = cultureInfo.TextInfo;

return textInfo.ToTitleCase(inputString.ToLower());

}

Simple enough and self explanatory…

Love coding!

Friday, April 13, 2007

Master Pages and Dynamic Page Titles – how to set from web controls inside the page?

Well, we all know how to set dynamic page titles. When using master pages, we can still control the page titles from the actual page itself. How about a need where master always sets the static part of the page title and page prefixes some specific information? And then how about when the page has complex nest of controls, like a wizard and in the wizard a web control, say product control, and based on the product selected to set the prefix as the product name?

To do that, we need to understand the page life cycle of ASP.Net (refer to http://msdn2.microsoft.com/en-us/library/ms178472.aspx for detailed explanation). Getting to the point in need, let’s focus on these three event sequences:

Ø Load (for both Master and the current page)

Ø Control Events (in our case the product web control)

Ø LoadComplete (doesn’t occur in Master, only to the page)

As we see in the above sequence, we can set the Master’s static part in it’s Load event. The control events load up our product information, including the product name. And, finally, the LoadComplete event of the page can read the product name in the control (with a property) and prefix it to the Master’s title.

A simple example:

Master Page:

Create the head section with runat server:

<head id="myHead" runat="server">

In the Page_Load event, we could add the static heading part as:

myHead.Title = "MyBlog.com – C# & Asp.Net";

And then create a public property for the header:

public string myHTMLTitle

{

get { return myHead.Title; }

set { myHead.Title = value; }

}

Product Web Control:

Load the product information and do any other related stuff. Add the ProductName public property:

public string ProductName

{

get { return myProduct.Name; }

}

Product Page:

Finally, in the product page, create Page_LoadComplete event and set the title:

protected void Page_LoadComplete(object sender, EventArgs e)

{

Master.myHTMLTitle = prod.ProductName + " - " + Master.myHTMLTitle;

}

But wait, did you get the 'System.Web.UI.MasterPage' does not contain a definition for 'myHTMLTitle' error? Then you need to set the virtual path to the master page - <%@ MasterType VirtualPath="~/myDir/myMaster.master" %>

Love coding!

Friday, March 16, 2007

Building Features Matrix Grid - Code

Recently I had to build a feature matrix Grid (as shown below), which we do at least once on every project. Well, this time I tried to build the whole matrix in the database (Oracle) and return the core result set. Here is an example on how I wanted my grid to look like:

Standard

Professional

Enterprise

Monthly fee

$9.99

$29.99

$99.99

Setup fee

$0

$0

$0

Support

N/A

3 issues per month

Unlimited

Online Help

Included

Included

Included

Customer Service Chat

Included

Included

Included

On-site service

N/A

N/A

Included

Well, no big deal right? How about trying to build this grid dynamically by passing the products and building with all the distinct features of all the products (may be N/A for the ones that don’t have the feature)? A simplified data model might look like this:

Adding to this, let’s throw in few static benefits to the mix that are not saved in the database. Like for example Online Help, Customer Service Chat and On-site service as shown in the grid above. There could be many reasons we don’t want this in the database; may be we want to attach online help to all products or something like that but those 3 are not available in the features table. Say those static benefits are saved in an xml file like this:

<Benefits>

<Feature ProductId="ALL" ID="ONLINE_HELP" Description="Online Help" Value="Included"></Feature>

<Feature ProductId="ALL" ID="CS_CHAT" Description="Customer Service Chat" Value="Included"></Feature>

<Feature ProductId="1234" ID="SITE_SERVICE" Description="On-site service" Value="Included"></Feature>

</Benefits>

Here is how I implemented this; first created a package in Oracle that does build the ref-cursor with the product benefits matrix:

CREATE OR REPLACE PACKAGE myBenefitsMix AS

TYPE ARRAY_PRD IS TABLE OF Products.ProductID%TYPE INDEX BY BINARY_INTEGER;

TYPE BNFTS_CUR IS REF CURSOR;

--Get the Product benefits matrix

PROCEDURE GETMyMix(p_ARRAY_PRD IN ARRAY_PRD,

p_out_BNFTS_CUR OUT myBenefitsMix.BNFTS_CUR);

END myBenefitsMix;

CREATE OR REPLACE PACKAGE BODY myBenefitsMix AS

PROCEDURE GETMyMix(p_ARRAY_PRD IN ARRAY_PRD,

p_out_BNFTS_CUR OUT myBenefitsMix.BNFTS_CUR)

IS

l_SQL LONG;

l_PRDLIST VARCHAR2(1000);

BEGIN

--Build the SQL for the Prod-Benefits

l_SQL := 'SELECT B.FeatureID, B.Description';

--for each product, as a column

FOR pnum IN 1..p_ARRAY_PRD.LAST

LOOP

l_SQL := l_SQL || ',' ||

'(SELECT PB.FeatureValue' ||

' FROM ProductFeatures_vw PB WHERE PB.ProductID = ''' || p_ARRAY_PRD(pnum) ||

''' AND PB.FeatureID = B.FeatureID AND ROWNUM <> || p_ARRAY_PRD(pnum) || '"';

l_PRDLIST := l_PRDLIST || ',''' || p_ARRAY_PRD(pnum) || '''';

END LOOP;

l_PRDLIST := SUBSTR(l_PRDLIST, 2);

l_SQL := l_SQL ||

' FROM (SELECT DISTINCT B1.FeatureID FeatureID,B1.Description Description ' ||

' FROM ProductFeatures_vw B1' ||

' WHERE B1.ProductID IN (' || l_PRDLIST || ')) B' ||

' Order By B.FeatureID';

--

OPEN p_out_BNFTS_CUR FOR l_SQL;

END GETMyMix;

END myBenefitsMix;

To consume this, here is my Benefits class would do:

public static DataSet FindForProducts(ArrayList Products)

{

MyLibrary.OracleHelper oh = new MyLibrary.OracleHelper();

try

{

OracleParameter[] op = new OracleParameter[2];

op[0] = new OracleParameter();

op[0].ParameterName = "@p_ARRAY_PRD";

op[0].CollectionType = OracleCollectionType.PLSQLAssociativeArray;

op[0].Direction = ParameterDirection.Input;

op[0].OracleDbType = OracleDbType.Varchar2;

op[0].Value = (string[])Products.ToArray(typeof(String));

op[0].Size = Products.Count;

op[1] = new OracleParameter("@p_out_BNFTS_CUR", OracleDbType.RefCursor, ParameterDirection.Output);

// Execute & Return DataSet:

DataSet dsBenefits = oh.ExecuteDataset("myBenefitsMix.GETMyMix", op, CommandType.StoredProcedure);

#region "Add Static Benefits"

DataSet dsStatic = new DataSet();

//DataTable dtStatic = new DataTable();

dsStatic.ReadXml(HttpContext.Current.Server.MapPath("~/references/StaticBenefits.xml"));

string filterCondition = "ProductId = 'ALL'";

foreach (string prodID in Products)

filterCondition += " OR ProductId = '" + prodID + "'";

dsStatic.Tables[0].DefaultView.RowFilter = filterCondition;

foreach (DataRowView drv in dsStatic.Tables[0].DefaultView)

{

DataRow dr = dsBenefits.Tables[0].NewRow();

foreach (DataColumn dc in dsBenefits.Tables[0].Columns)

{

switch (dc.Ordinal)

{

case 0:

dr[dc] = drv["ID"].ToString();

break;

case 1:

dr[dc] = drv["Description"].ToString();

break;

default:

if (dc.ColumnName == drv["ProductId"].ToString() || drv["ProductId"].ToString() == "ALL")

dr[dc] = drv["Value"].ToString();

break;

}

}

dsBenefits.Tables[0].Rows.Add(dr);

}

#endregion "Add Static Benefits"

return dsBenefits;

}

finally { oh.Dispose(); oh = null; }

}

To summarize, this may not be the best of best but a way of doing it. I build dynamic SQL in my oracle package that adds columns for each product passed. That build the matrix with the benefits data from the database and I populate my dataset from that. To add some static benefits from an xml file, I then add rows to the dataset.

Finally, all I’m trying to show here is couple of points: building dynamic SQL and appending rows to the dataset from an xml file.

Love coding!

Wednesday, February 21, 2007

ASP.Net Development server and Oracle db-link connection problem

In one of my app designs, I have an oracle package’s procedure in my web database (oracle) that accesses few other oracle databases using db-link and fetches me some product data. My code always opens the connection to my web database only. However, as I was running my code from my development server (built in to VS 2005) the remote databases got locked up because of too many db-link user connections being opened to them from my web database. This doesn’t happen if I use IIS but only through ASP.Net development server.

My code was clean enough to properly handle the connections, including closing and disposing. Upon few minutes of digging I realized that though I close my connections it’s the pooling that’s keeping my primary connection alive from the local server. That in turn keeps the db-link connection open and every time my code accesses the db-link 2 more new connections are opened and there you go… connections pile up to point to lock up the database.

Well, there might be better solutions to this problem but a quick one is to set pooling to false on my local machine. That forces the local server (local development server) to close my primary connection and thus clearing the db-link connections as well.

Will update with a better solutions or reason if I ever worry about this again and research further.

Love coding!

ASP.Net – Code to make a page read-only

Some times we may come across need to make all the ASP.Net controls on a page disabled, like for example after an order is processed we may want to make the order entry page read-only for future access. A quick way to do that is to loop through all the page controls, identify the type and disable them. A sample code on how I go about doing this…

In my App Helper class,

#region "Page Read-Only"

public static void DisablePageControls(Page curPage)

{

foreach (Control cntrl in curPage.Controls)

{

DisablePageControls(cntrl);

}

}

private static void DisablePageControls(Control myControl)

{

foreach (Control cntrl in myControl.Controls)

{

switch (cntrl.GetType().ToString())

{

case "System.Web.UI.WebControls.TextBox":

((TextBox)cntrl).Enabled = false;

break;

case "System.Web.UI.WebControls.Button":

((Button)cntrl).Enabled = false;

break;

case "System.Web.UI.WebControls.ImageButton":

((ImageButton)cntrl).Enabled = false;

break;

case "System.Web.UI.WebControls.DropDownList":

((DropDownList)cntrl).Enabled = false;

break;

case "System.Web.UI.WebControls.CheckBox":

((CheckBox)cntrl).Enabled = false;

break;

default:

break;

}

//Call for additional controls:

if (cntrl.HasControls())

DisablePageControls(cntrl);

}

}

#endregion "Page Read-Only"

Love coding!

Tuesday, February 6, 2007

A simple case for List<T> in System.Collections.Generic

List<T> in System.Collections.Generic namespace can be sort of equated to ArrayList in System.Collections namespace. However, Generics are the most powerful feature of C# 2.0. Generics allow us to define type-safe data structures, without committing to actual data types. A simple example to demonstrate the ease of this could be done thinking about an OrderItem class; let’s say we define the OrderItem class as:

public class OrderItem

{

public string OrderID = string.Empty,

ProductID = string.Empty,

Quantity = string.Empty;

public OrderItem (string orderID, string productID, string qty)

{

OrderID = orderID;

ProductID = productID;

Quantity = qty;

}

}

To define a list of OrderItem in your shopping cart or other locations, then, all we need to do is:

List<OrderItem> orderItems = new List<OrderItem>();

To add items to the List of OrderItems, all we need to do is:

orderItems.Add(new OrderItem("Order123", "Product234", "3"));

orderItems.Add(new OrderItem("Order123", "Product001", "1"));

Notice that we didn’t declare the size of the OrderItem and we can go on adding items by using the Add method. Do we want to loop through all items, use foreach; do we want to find an item, use find. There are many more properties, methods and advanced advantages for using Generics, but this is a simple case to give the gist of such usage.

Love coding!

Wednesday, January 31, 2007

Bulk inserts using ADO.Net & Oracle

It's a constant challenge to get best out of the database and usage techniques; and there are many ways we achieve it. One such, I would like to show now, is how we can take advantage of bulk inserts (or likes) with Oracle database using the power of command parameter ArrayBindCount of ADO.Net.

Let's take an example of a table called OrderItems and a need to insert multiple order items from our web order entry form. We can create a package (or procedure, but I rather prefer packages always) called OrdersPackage and have a procedure in it called InsertOrderItem. A simple signature of this might look like...

OrdersPackage.InsertOrderItem(pOrderID IN NUMBER, pItemID IN NUMBER, pQuantity IN NUMBER)

We can now call this procedure one time and insert all the order items from our GridView; one way we can do is to loop through all GridView items and have them in arrays and call the procedure once. A simple example might be... [pretty self explanatory; not tested this code as is though ;-)]

int[] orderID, itemID, itemQuantity;

orderID = new int[gvOrderItems.Rows.Count];

itemID = new int[gvOrderItems.Rows.Count];

itemQuantity = new int[gvOrderItems.Rows.Count];

foreach (GridViewRow orderRow in gvOrderItems.Rows)

{

int.TryParse(((TextBox)orderRow.FindControl("OrderID")).Text, out orderID[orderRow.RowIndex]);

int.TryParse(((TextBox)orderRow.FindControl("ItemID")).Text, out itemID[orderRow.RowIndex]);

int.TryParse(((TextBox)orderRow.FindControl("ItemQuantity")).Text, out itemQuantity[orderRow.RowIndex]);

}

OracleParameter[] op = new OracleParameter[3];

op[0] = new OracleParameter("@pOrderID", OracleDbType.Int32, ParameterDirection.Input);

op[0].Value = orderID;

op[1] = new OracleParameter("@pItemID", OracleDbType.Int32, ParameterDirection.Input);

op[1].Value = itemID;

op[2] = new OracleParameter("@pQuantity", OracleDbType.Int32, ParameterDirection.Input);

op[2].Value = itemQuantity;

OracleConnection myCon = new OracleConnection();

OracleCommand myCmd = new OracleCommand();

OracleTransaction myTransaction;

try

{

myCon.ConnectionString = "Read from my Configuration file";

myCon.Open();

myCmd.Connection = myCon;

myCmd.CommandType = CommandType.StoredProcedure;

myCmd.CommandText = "OrdersPackage.InsertOrderItem";

myCmd.ArrayBindCount = gvOrderItems.Rows.Count;

foreach (OracleParameter cmdOP in op)

myCmd.Parameters.Add(cmdOP);

myTransaction = myCon.BeginTransaction();

if (myCmd.ExecuteNonQuery() >= 0)

myTransaction.Commit();

else

myTransaction.Rollback();

myCmd.Parameters.Clear();

}

catch (Exception ex)

{

// Do some other exception handling here...

}

finally

{

myCmd.Dispose();

if (myCon.State == ConnectionState.Open)

myCon.Close();

myCon.Dispose();

}

Love coding!