Tuesday, August 31, 2010

Http Modules

What is an Http Module?
An Http Module is an assembly that implements IHttpModule interface and serves as a component that can be plugged into ASP.NET request processing pipeline. This is performed by registering for a set of events.

To implement IHttpModule, you need to implement its two methods; Init and Dispose. Here is a couple of events the module can register for. You hook up to those events in Init method.
  • AcquireRequestState
  • AuthenticateRequest
  • AuthorizeRequest
  • BeginRequest
  • Disposed
  • EndRequest
  • Error

Before I dive into the details, I should mention something I experienced in IIS 7. When implementing HttpModule, the website must use an application pool with managed pipeline mode of Classic. If integrated mode is chosen, the code in the module will not be hit.
To implement an HttpModule, use IHttpModule interface and implement Dispose and Init methods in your module class (in the example, Module1). In Init method which takes in an HttpApplication object, you can register handlers for several events.

public class Module1 : IHttpModule
{
      public void Init(HttpApplication context)
      {
            context.BeginRequest += new EventHandler(context_BeginRequest);
            context.EndRequest += new EventHandler(context_EndRequest);
      }
      void context_BeginRequest(object sender, EventArgs e)
      {
            HttpApplication app = (HttpApplication)sender;
            app.Context.Response.Output.Write("This is the beginning.<br/>");
      }
}

Then in the web.config file, you need to add the code below to register the module:

<httpModules>
      <add name="Module1" type="Module1"/>
</httpModules>

A good example of an HttpModule could be Url Rewriting:

void context_BeginRequest(object sender, EventArgs e)
{
      HttpApplication app = (HttpApplication)sender;
      string url = app.Request.Url.ToString();
      if (url.Contains("product1.aspx"))
      {
            app.Context.RewritePath("det.aspx?name=product1");
      }
      else if(url.Contains("product2.aspx"))
      {
            app.Context.RewritePath("det.aspx?name=product2");
      }
}

Here arises a small problem. In this example, if you have a button in det.aspx page, by clicking it, a postback occurs which changes the Url to the rewritten path. To avoid this, you will need to create a class library and implement a class that derives from System.Web.UI.HtmlControls.HtmlForm:
 
namespace ActionlessForm

{
      public class Form :System.Web.UI.HtmlControls.HtmlForm
      {
            protected override void RenderAttributes(System.Web.UI.HtmlTextWriter writer)
            {
                  writer.WriteAttribute("name", this.Name);
                  base.Attributes.Remove("name");
                  writer.WriteAttribute("method", this.Name);
                  base.Attributes.Remove("method");
                  this.Attributes.Render(writer);
                  base.Attributes.Remove("action");
                  if (base.ID != null)
                  {
                        writer.WriteAttribute("id", base.ClientID);
                  }
            }
      }
}

You need to add a reference to the new class library in the website and add a Register directive to det.aspx page:

<%@ Register TagPrefix="mnf" Namespace="ActionlessForm" Assembly="ActionlessForm" %>

Then you will need to set a prefix for the page’s form control as follows:

<mnf:form id="form1" runat="server">

</mnf:form>

Monday, August 30, 2010

Manage the content of navigation controls based on the roles defined in ASP.NET

This one is really cool! I suppose you have already set up role and membership in your ASP.NET application. If you have a TreeView or a Menu control that uses SiteMapDataSource and in the Web.sitemap file, you have elements pointing to files in the folders the access rule of which you have set, it makes sense if you want to show and hide the menu or treeview items (nodes) according to the user’s roles. This can be accomplished by a setting added to the web.config file:

<siteMap defaultProvider="default">
      <providers>
            <clear/>
            <add name="default" type="System.Web.XmlSiteMapProvider"
securityTrimmingEnabled="true" siteMapFile="Web.sitemap" />
      </providers>
</siteMap>

As you can see, the magic is done by an attribute called securityTrimmingEnabled.

Reset the state of a page containing web parts

In a page containing web parts, you can tweak the location and appearance of the web parts. Then you may want to clean the mess and reset the state of the web parts to the original one. You can do so by running this code:

PersonalizationAdministration.ResetAllState(PersonalizationScope.User);

Cache API

Imagine a situation where you want to load data from an XML file and you want to avoid redundant IO operation by caching the data and invalidate cache if the file changes. To do the job, you can use a CacheDependency object and pass in the path to the file to the constructor. Here is the complete code:

DataSet ds = null;
ds = (DataSet)Cache["_records"];
if (ds == null)
{
      string path = Server.MapPath("records.xml");
      ds = new DataSet();
      ds.ReadXml(path);
      CacheDependency cd = new CacheDependency(path);
      Cache.Insert("_records", ds, cd);
}
GridView1.DataSource = ds;
GridView1.DataBind();

The codes checks if the cache already contains the data. If not, it refers to the file and loads the data and inserts it into the cache. Then it is displayed in a GridView control.

In order to remove the cache programmatically, you can use this code:

Cache.Remove("_records");

Execute a code regardless of caching using Substitution control

When you want an arbitrary code to be executed irrespective of caching settings in a page, you can use a Substitution control. Its main property is MethodName which points to the static method that is to be executed. Its signature needs to look like this:
static string Method1(HttpContext context)
{

}

Caching in ASP.NET pages

The simplest situation in caching is when you need to cache a page for a specific number of seconds regardless of the page parameters. The code below does the magic for 5 seconds:

<%@ OutputCache Duration="5" VaryByParam="*" %>

If you show data from a database, for example SQL Server, this method will not display the last version of the data. If a new row is added to the database within the amount of time specified as Duration, it will not be shown until the specified time elapses. To make the page display the latest version of data, you need a SqlCacheDependency object. There are two methods to perform this job. The first one is polling and is supported by both SQL Server 2000 and SQL Server 2005. The second one is using broker service which is supported in SQL Server 2005.

To enable the first method on a SQL Server database, you need to run aspnet_regsql command with a few switches indicating the target database and tables for caching.

C:\>aspnet_regsql –S myServer –U myUsername –P myPassword –d myDatabase –ed –et –t myTable

This command adds a table named ‘AspNet_SqlCacheTablesForChangeNotification’ and a trigger named ‘myTable_AspNet_SqlCacheNotification_Trigger’ to the database. The table is going to be polled by ASP.NET to see if there is any change.
 Also, you need to add the code below in the web.config file:

<caching>
      <sqlCacheDependency enabled="true" pollTime="2000">
            <databases>
                  <add connectionStringName="myDBCS" name="myDBCS"/>
            </databases>
      </sqlCacheDependency>
</caching>

Here I assume that we have a connection string defined as myDBCS. Also the OutputCache directive will need an additional attribute:

<%@ OutputCache Duration="5" VaryByParam="*" SqlDependency="myDBCS:myTable"%>

To enable the second method, you should change the code above to this:

<%@ OutputCache Duration="5" VaryByParam="*" SqlDependency="CommandNotification"%>

Also, you need to add a Global.asax file to the application and add the code snippet below to start SQL dependency:

System.Data.SqlClient.SqlDependency.Start(ConfigurationManager.ConnectionStrings["myDBCS"].ConnectionString);

Note: It is very important to know that you must include the database schema in the select query supplied in the SqlDataSource object you use to let the broker service work properly.

<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$ ConnectionStrings: myDBCS %>"
SelectCommand="SELECT column1, column2 from dbo.table1">
</asp:SqlDataSource>


URL mapping to map obscure URLs to more human-readable URLs

There might be situations where you have long ugly file names on your website and you have links to them. You would not want to have a link to something like 7bd137a5-8a61-2a21-eb82-3352af7ec87d.html in your web application. There is an easy way to map such ugly Urls to more readable Urls. You need to enable Url mapping in web.config and then add elements to urlMappings tag. The code below is self descriptive enough.

<urlMappings enabled="true">
      <add url="~/page.html" mappedUrl="~/7bd137a5-8a61-2a21-eb82-3352af7ec87d.html"/>
</urlMappings>

Saturday, August 28, 2010

Protect web.config sections

There are always sections in web.config files that contain confidential information, like the database credentials in connection string. So you may want to encrypt it so that the bad guys who gain access to the web.config cannot steal your secret information. This is not the only possible scenario. Suppose that your shared hosting system offers you a service to edit your files online. If someone sniffs the network, they can easily check your sent and received data and if the connection is not secure and your data is transmitted in plain text, your confidential data will be disclosed.

Here is a common way to encrypt a section in web.config:

void EncryptConfig(bool encrypt)
{
      string path = Request.ApplicationPath;
      Configuration config = WebConfigurationManager.OpenWebConfiguration(path);
      ConfigurationSection sec = config.GetSection("connectionStrings");
      if (encrypt)
      {
            sec.SectionInformation.ProtectSection("RSAProtectedConfigurationProvider");
      }
      else
      {
            sec.SectionInformation.UnprotectSection();
      }
      config.Save();
}

There is an alternative to the parameter that the protectSection takes in. Instead of RSAProtectedConfigurationProvider, you can use DataProtectionConfigurationProvider.

The second way to achieve the same goal is to use aspnet_regiis with a couple of switches to encrypt a section in the web.config file. Run Visual Studio command prompt and execute aspnet_regiis command followed by /pef switch, then the section you want to protect, and lastly the application path.

C:\>aspnet_regiis /pef "connectionStrings" "C:\...\MyWebApp"
Encrypting Configuration section...
Succeeded!

In the sample above, the connectionStrings section of the web.config file in MyWebApp application is encrypted and protected (You need to specify the full path to the application). To unprotect a section, replace the switch /pef with /pdf.

C# history

Since the C# language was introduced, it has been evolving. The other day, I was thinking how different it has become, so to make it more accurate, I downloaded the specification files from microsoft website and other resources. Here is a short list of the changes after C# 2.0 was introduced:


New features added to C# 2.0

  • Generics
  • Anonymous methods
  • Iterators
  • Partial types
  • Nullable types

New features added to C# 3.0

  • Implicitly typed local variables
  • Lambda expressions
  • Object and collection initializers
  • Anonymous types
  • Implicitly typed arrays
  • Query expressions
  • Expression trees

New features added to C# 4.0

  • Dynamic binding
  • Named and optional arguments
  • COM interoperability
  • Covariance and Contravariance

I don't think the list is complete. So if you think there is something wrong or you could add an item to it, let me know.

Plan

I have recently been watching some videos I downloaded from http://www.asp.net/ a while ago. I've found some of them really helpful. I thought I could put the codes here so that I can refer to them later and who knows, maybe it will resolve the problems of someone in trouble!