Sunday, June 29, 2008

Demo

For Demo please visit Demo
You can download source code as well....

Tuesday, June 17, 2008

Perfect ASP.NET application, Part 7

Url rewriting.

Nowdays, every web developer bows to Google and Yahoo. And that is right. Who wants/needs to spend it's time and get the perfect application out there and no visitors. And the only "free" way to get visitors is to get them from search engines. But for that you must make your website easy to spider. You can say anything you want about Google and Yahoo is being able to spider urls with query parameters but show me the popular search phrase where URLs with ? symbols shows up on a first page. Sorry, I did not see that.

So now we want to make our pages to look pretty. So instead of http://www.mspiercing.com/Product.aspx?id=1526 we want something like http://www.mspiercing.com/Product/belly-button-ring-web-ball-1526.aspx

Note: With the current version of IIS 6.0 in order to have a pure .NET UrlRewriting solution you need to have .aspx extension. That is how IIS knows that this request needs to be routed through ASP.NET engine. IIS 7.0 will change that but as of now it's in beta. So it will be hard to rewrite url like that http://www.mspiercing.com/belly-rings simply because there is no .aspx extension. You might use ISAPI dll to do that. But there are no pure "good" .NET solution. Some people use custom 404 page that has .aspx extension to trick IIS. So when request is made for http://www.mspiercing.com/belly-rings IIS trying to serve custom 404.aspx page and that is when .NET kicks in. Not sure that it's good solution because that 404.aspx will be called for everything, even images.

HttpContext.RewritePath makes it really easy in .NET to rewrite the path, but unfortunately it's not that simple. Problem is in the <form runat=server> tag which is a must in .NET

Let say you have rewritten url so when browser hits page http://www.mspiercing.com/Product/belly-button-ring-web-ball-1526.aspx it actually goes to page http://www.mspiercing.com/product.aspx?id=1526. Unfortunately the <form> tag in the HTML will look like following <form method="POST" action="http://www.mspiercing.com/product.aspx?id=1526" ...> and if you have single button on the page when user clicks it the browser going to hit not rewritten url with method POST. And users obviously will see it in their address bar of the browsers. Not good.

I've seem solution that uses custom written object derived from HtmlForm which suppresses rendering of the "action" property. Unfortunately it does not work well with validators and who knows what else. I found a nice easy solution (on internet) . All you need to do is rewrite URL back before Render method called.

So here is a skeleton of application that supports URL rewriting.

Global.asax

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

void Application_BeginRequest(Object sender, EventArgs e)
{
   
HttpContext ctx = HttpContext.Current;
   
string sPath = ctx.Request.Path.ToLower().Substring(clsGlobal._sRoot.Length);
   
int iIndex = sPath.LastIndexOf(".aspx");
   
if (iIndex == -1)
   
     return;
   
ctx.Items["OriginalPath"] = sPath;
   
......do rewriting here....
}

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

clsStandardPage.cs (remember from previous post that every page is derived form this object)

public class clsStandardPage : System.Web.UI.Page
{
   
protected override void OnPreInit(EventArgs e)
   
{
        //rewrite URL back
        string sPath = (string)Context.Items["OriginalPath"];
        Context.RewritePath(sPath,
null, null);
        ....
     }
}

Note: We need to check that we have request for .aspx file first. We do not want to rewrite request for famous WebResource.axd. Cause it will be routed through ASP.NET since .axd extension assigned to ASP.NET and it will trigger the BeginRequest event.

That solves all problems....

Note: Just discovered that if you use button with PostBackUrl set then it breaks UrlRewriting all together. Do not know exactly why. It has something to do with actual url not matching requested url. But i do not use PostBackUrl in my projects. So far did not have a need. So i am good :)

 

Tuesday, June 3, 2008

Perfect ASP.NET application, Part 6

Objects, objects everywhere

ASP.NET is a great framework. It turns HTML into objects and it's so much easier to work with objects. I love working with objects. But sometimes it kills me that to do small thing you would need to create an object. Let say you need to show current date/time on a bottom of each page. You have 2 options. 

  1. Create asp:Label control, drop it on master page (I hope you use one in each of your project). In your OnLoad even you write following line lblDate.Text = clsGlobal.GetDateTime(DateTime.Now);
  2. Another option is simply write in a bottom of your master page <%=clsGlobal.GetDateTime(DateTime.Now)%>

Which option would you choose?

First of all why would you want to show current Date/Time on a page. It's simple. We live in a world where Google and Yahoo are kings. Every site owner is worried about how often spiders spider their precious websites. Look at this page. It's a google's cache of the www.mspiercing.com. Scroll to the bottom and you will see a date there. Now you know when Google's robot visited this page last time. 

Now I hope you made up your mind. I would chose #2. Simply because I hate to create small objects without any intelligence in it. It's simply a waste of memory and CPU resource.


Another problem, a lot of people want to do website branding or use themes. Meaning that they want to show for one person everything blue, for another person everything white, or change the logo of the site depending on who is logged in. How can we do it easily?

I offer relatively simple solution. What if we could write our HTML using following construction. <img src="@ImageFolder@/logo.gif> and had a component that would analyze our output stream and substitute @ImageFolder@ appropriately. I offer you a "Replacer". 

Response.Filter allows us to write such thing easily. First let's make a CConfig class which is a global Hashtable for our website and then CReplacer which is a class that being created for each request.

So idea would be that when our application starts we populate CConfig class and put it in clsGlobal

public class clsGlobal
{
    ......
    public CConfig replacer = new CConfig();
    public static void Init()
    {
        .......
        cfg.AddToken("SITENAME", "My Site");
    }
}

Change our clsStandardPage a little:

public class clsStandardPage : System.Web.UI.Page
{
    ......
    Replacer.CReplacer rpl = clsGlobal.replacer.GetReplacer(Response.Filter);
    Response.Filter = rpl;
    rpl.AddToken("DATE", DateTime.Now.ToString());
    rpl.AddToken("ImageFolder", "/brand1/images/");
}

And we done. Now look at this page

<HTML>
<BODY>
Welcome to @SITENAME@
Current date is @DATE@ and our logo is <img src="@ImageFolder@/logo.gif">
</BODY>
</HTML>

No objects created. The code does not break our HTML. The person who has no idea about programming languages can now look at it and understand. 

There are many uses for "Replacer". The most common  one is in CMS (Content Management System). Look at this page http://www.mspiercing.com/Product/razor-navel-ring-1370.aspx. Notice page Title matches the item's name, Item has a description, picture...... All that, I did with a "Replacer". The code  determines Title, Description, Image, BigImage, price.... and adds those as tokens to "Replacer". The SEO (search engine optimizator) can now move staff around at will. If he dumps on a page <img src="@Bigimage@"> user will see big image instead of regular image. He wants to make price red.... I do not care. He edits HTML so it becomes <font color=red>@PRICE@</font>

I hope you got an idea.

Note: You will be able to download Replacer from my last post. It will be included in a sample application.
Note: To improve runtime I made "Replacer" to run in unsafe mode. Meaning that you need to compile your .NET application with /unsafe flag. Easy to do. Dump following line in web.confiig

<system.codedom>
    <compilers>
        <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" compilerOptions="/unsafe" warningLevel="1"/>
    </compilers>
</system.codedom>

If you are on shared hosting environment most likely you will not be able to use /unsafe switch. Then you need to get your hands dirty and make small modifications. 

Note: To avoid problems with emails "Replacer" only counts 20 characters after it encounters first @ symbol. And if no second @ symbol it leaves the HTML intact. So HTML like this "my email is email@email.com send me all spam in a world" will not be modified.

Of course Replacer scans the whole HTML output. So you might think there is a performance hit. CPU is designed to do scans and comparing to creating and initializing several Label controls on your page it would be nothing. Also do you know that your IIS supports so called "HTML includes". It's constructions like this in HTML <!--#include virtual="insertthisfile.html" -->. How do you think it's working. The same scanning looking for <!-- #include.... And I do not remember anyone complaining.... Of course if you work in google you might have to come up with something else.

 

 

Continue >>

Perfect ASP.NET application, Part 5

Benefits of clsStandardPage

Note: You must see previous post to understand what I am talking about here.

Let's talk about our project and what we have already.

First we have clsBrowser:

public class clsBrowser
{
    public string _sFirstName = '';
}

Then we have our clsStandardPage all pages derived from

public class clsStandardPage : System.Web.UI.Page
{
    public clsBrowser _objBs;

    protected override void OnPreInit(EventArgs e)
    {
        //no caching
        Response.CacheControl = "no-cache";
        Response.Expires = -1;
        //Get Browser object
        _objBs = (clsBrowser)Session["browser"];
        if (_objBs == null)
            Session["browser"] = _objBs = new clsBrowser();
        base.OnInit(e);
    }
}

So now it's time to see why we went through all the trouble and did all that. Our customer comes to us and says that he want people to be able to login before seeing content of the web site. So here are our steps. 
Note: I will skip some code that you can see in previous posts.

public class clsBrowser
{
    public int _iUserId = -1;   
//add iUserId for users
    ....
}

public class clsStandardPage : System.Web.UI.Page
{
    public clsBrowser _objBs;
    private bool _bRequiresLogin = false;

    public bool RequiresLogin
    {    
        get {return _bRequiresLogin;}
        set { _bRequiresLogin = value;}
    }
    protected override void OnPreInit(EventArgs e)
    {
            ..............get _objBs.......
   
         //Check if user is not logged in then redirect to login.aspx
            if
( _bRequiresLogin && ( _objBs.iUserId == -1 ) )
                Response.Redirect("~/login.aspx");  
           base.OnInit(e);
    }
}

------Login.aspx--------
<%@ Page RequiresLogin = "false" %>
.....code to do actual login and set our _objBs._iUserId to valid UserId

Note: We added public property RequiresLogin to avoid infinite loop. Our Login.aspx is derived from clsStandardPage and we do not want to redirect from it on itself. 
Note: If we not using separate .cs file we can declaratively set it to RequiresLogin  to false (like i did). Unfortunately if you separate aspx page and .cs file you will not be able to do that. Visual Studio throws a compiler error. (Not sure about VS 2008). In this case you would have to set RequiresLogin to false in constructor. 
Note: You need to set RequiresLogin only on Login.aspx since by default we set it to true.


Another reason to do it that way.... I had a client who came back to me after site was done and said "We want to do bait and switch. Meaning that we let users to see 10 pages of our web site without having him to create an account/login. Then when he sees what we offer he would more easily give us his email address."

Good luck doing that with your standard way.....

I did it with practically 3 lines of code. 

public class clsBrowser
{
    public int _iUserId = -1;   
//add iUserId for users
    public int _iCounter = 0;  
//page view counter
    ....
}

public class clsStandardPage : System.Web.UI.Page
{
    ........
    protected override void OnPreInit(EventArgs e)
    {
            ..............get _objBs.......
   
         //Check if user is not logged in then redirect to login.aspx
            if
( _bRequiresLogin && ( _objBs.iUserId == -1 ) )
            {
                 if( _objBs._iCounter > 10)
                    Response.Redirect("~/login.aspx");  
                else
   
                 _objBs._iCounter ++;
            }
           base.OnInit(e);
    }
}

Note: We only count pages that requires login. And it makes sense. We do not want to make person "pay" (in terms of views) if poor guy refreshed login page 10 times. Or was looking at our "Privacy Policy". Only real content matters.

I hope by now you starting to like the way I do it.....

Continue >>

Perfect ASP.NET application, Part 4

clsStandardPage and clsBrowser

Note: You must see previous post to understand what i am talking about here.

Look at your ASP.NET application. It's full of references to Session object. It's everywhere. In one spot you have Session["FirstName"] = 'George' in another you have Response.Write((string)Session["FirstName"]. What if you make a misspell. And write .....Session["FirstNane"] (notice nane not name). Who is going to know... only when you populate your order database with empty names you will see it. And hopefully QA department will see it.

Plus every time you write Session["..."] it's a look up in hashtable.

So here is an idea. Let's make a class

public class clsBrowser
{
    public string _sFirstName = '';
}

and put it in out clsStandardPage. Then since every our page is derived from clsStandardPage it will have access to _objBs. So if we need to get user's first name we simply write _objBs._sFirstName.

Benefits:

  1. Faster, no hashtable look-ups.
  2. Type safe. We do not need to do conversion to string or Int32 every time we use it.
  3. Misspell proof. Compiler will throw an error if you try to write _sFirsNane.

Continue >>

Perfect ASP.NET application, Part 3

clsStandardPage

Our goal here is to write easy maintainable application. So nowadays in any of my projects I define following class (we will discuss later what is clsBrowser and why i need it.)

public class clsStandardPage : System.Web.UI.Page
{
    public clsBrowser _objBs;

    protected override void OnPreInit(EventArgs e)
    {
        //no caching
        Response.CacheControl = "no-cache";
        Response.Expires = -1;
        //Get Browser object
        _objBs = (clsBrowser)Session["browser"];
        if (_objBs == null)
            Session["browser"] = _objBs = new clsBrowser();
        base.OnPreInit(e);
    }
}

In web.config we do a small change

<system.web>
    <pages pageBaseType="clsStandardPage" />
</system.web>

And now all our pages derived from clsStandardPage. 

Benefits: All our pages now derived from clsStandardPage and we can change the way they behave in one place (You will see how later). 

Notes: You can have more than one clsStandardPage, but then on a page you will need to specify (if you do not separate code and html)

<%@ Page Inherits="clsStandardPage1"%> 

to overwrite the web.config setting. And if you do code separation (VS default) you would need to modify your .cs class. Something like

public partial class Default2 : clsStandardPage

 

Read next post to find out what is clsBrowser and why it's so much better to have clsStandardPage

PS: If you are working on your home page you are welcome to do anything you want. If you working on a real project even small one use MasterPage. First of all small projects tend to become medium projects with time. And trust me it's much easier to modify a navigation, menu or simply change company's logo in one place than 10s aspx pages.

Continue >>

Perfect ASP.NET application, Part 2

clsGlobal and it's benefits.

We now have a static class clsGlobal. Let fill it with our little helpers that simplify our lives.

The first thing that comes in mind methods to work with database

public static DataTable GetData(string sSql)
{
    SqlConnection con = new SqlConnection(sConnection);
    SqlDataAdapter ad = new SqlDataAdapter(sSql, con);
    DataTable dt = new DataTable();
    try
    {
        con.Open();
        ad.Fill(dt);
        return dt;
     }
    finally
    {
        ad.Dispose();
        con.Close();
    }
}
public static void ExecuteSql(string sSql)
{
    SqlConnection con = new SqlConnection(sConnection);
    SqlCommand com = new SqlCommand(sSql, con);
    try
    {
        con.Open();
        com.ExecuteNonQuery();
    }
    finally
    {
        com.Dispose();
        con.Close();
    }
}
public static object ExecuteScalar(string sSql)
{
    SqlConnection con = new SqlConnection(sConnection);
    SqlCommand com = new SqlCommand(sSql, con);
    try
    {
        con.Open();
        object objTmp = com.ExecuteScalar();
        return objTmp;
    }
    finally
    {
        com.Dispose();
        con.Close();
    }
    return null;
}

Now we want to be consistent everywhere in our web site so let's introduce couple of methods
public static string GetDateTime(DateTime dt)
{
    return dt.ToString("MM/dd/yy hh:mm tt");
}

public static string GetMoneyHtml(object objVal)
{
    return ((decimal)objVal).ToString("$0.00");
}

Benefits:

  1. Convenient and save methods to get DataTable or run SQL statement. You will not forget to close .NET connection.
  2. Unified look of our website. Everywhere where we need to show DateTime we use <%=clsGlobal.GetDateTime(DateTime.Now) %> or like this in a table <%# clsGlobal.GetMoneyHtml(Eval("amount"))%>

I hope you got an idea and will put bunch of helpers in this class. Just do not forget to make them static.

Continue >>

Monday, June 2, 2008

Perfect ASP.NET application, Part 1

Database connection string and other application settings...

I hope that everybody knows that it's a bad practice to hardcode everything. We as a programmers must (at least try to) write a maintenance free application. Once we gave our application to the client it must live without our everyday supervision. Let's take a database connection string for example. You do not want to have to recompile your application every time SQL server changes name or you want to change login information. ASP.NET gives you web.config to keep your settings. So we add our setting to web.config

<appsettings>
    <add value="server=(local);Trusted_Connection=false;database=Database;User ID=User;Password=Password" key="ConnectionString">
    <add value="10" key="Timeout">
</appsettings>

And now every time we need to connect to SQL server we get our connection string like this

sConnection = System.Configuration.ConfigurationManager.AppSettings["ConnectionString"];

Not really convenient. Plus we spending some runtime on a Hashtable look-up every time. Minimal but still a waste. So here is solution

Let's make clsGlobal class. Like this

public class clsGlobal 

    public static string sConnection; 
    public static int _iAmountOfThreads; 

    public static void Init() 
    { 
        sConnection = System.Configuration.ConfigurationManager.AppSettings["ConnectionString"]; 
        _iAmountOfThreads = Int32.Parse(System.Configuration.ConfigurationManager.AppSettings["AmountOfThreads"]); 
    } 
}

And in our global.asax write following code to initialize the clsGlobal

void Application_Start(object sender, EventArgs e) 

    clsGlobal.Init(); 
}

Now every time we need to get a connection string (or any other setting) we would use

clsGlobal.sConnection or clsGlobal._iAmountOfThreads

Benefits:

  1. No runtime waste
  2. Type safe. If we have it defined as a string or Int32 we do not need to convert it every time.

Note: The key here is that we defined everything in clsGlobal as static. It allows us to access those members/functions without actually creating instance of clsGlobal.

PS: I use _ to prefix members variables. But sConnection is an exception. It's used so often i do not want to type an extra characters.

PPS: One smart guy gave me a good advice. Make a static constructor in clsGlobal and move all Init code there. Then there is no need to call clsGlobal.init in Application_Start. You might want to do it that way.

Continue >>

Perfect ASP.NET application, Part 0

This is going to be a several posts from my own collections of .NET wisdoms/code. I came up with them on trying to perfect my ASP.NET skills.

I wanted to post them long time ago. Once, i actually even made an attempt to post them on codeproject.com, but their 'spam' team rejected them. Still not sure why, but seeing how codeproject became a collection of useless articles about how to write a program to calculate 2 + 2 ( and usually their programs would get a 5 as a result) i suspect that their 'spam' team simply did not understand what those articles about. 

But I would let you decide yourself if my posts useful or not. Some of my posts only beginners will find interesting and experienced programmers will think of as obvious. But hopefully even experienced programmers will like some of my ideas.

Please let me know your thoughts about them as I am very interested what you have to say.

Note: Most of my member variables in classes are declared as public for simplicity. But you can do it as you wish and create get/set property for each variable.

Note: I am using Hungarian notation. I know that .NET people not using it anymore but i am programming for more than 15 years and kind of get used it and do not want to abandon it. I find that i "read" code more easily if it's using Hungarian notation.

PS: Hungarian notation was actually one of the reason why codeproject's 'SPAM team did not like it. I do not understand why it matters....

 

Continue >>