ASP.NET (RSS)

ASP.NET

Atlas Controls Toolkit Next Drop on Its Way

Next version of Atlas Controls Toolkit will be out (probably) during next week. It will include Safari support, existing controls improvement and new extenders. Thanks Shawn for update (teaser :)).

Cross-Posted from here

Client Side ''onsubmit'' Action

There are two ways to add some client-side processing before ASP.NET 2.0 page submitted.
1. Add onsubmit attribute to Form tag in aspx file
2. Use this.Page.ClientScript.RegisterOnSubmitStatement method
Is there any difference?
The answer is processing order.
When aspx page being processed internal collection (_registeredOnSubmitStatements) created which holds all submit statements in order of RegisterOnSubmitStatement calls. Value of onsubmit attribute from aspx added at the end. Client side script created from this collection as statements of WebForm_OnSubmit function.
Now, when the order is particulary importemnt? It will be if we will use validator controls. Validator controls that allows client side script also register submit statement (through BaseValidator class) at PreRender stage. If client side validation fails, any submit statement that follows will not be executed. This behavior is very useful in some scenarios but unwanted in others.
The suggestion is:
- If you need to execute client script on page submit regardless of validation result (submit attempt), use RegisterOnSubmitStatement method before PreRender (for example in Page_Load).
- If you want to execute script only after successful validation, use onsubmit attribute.

HowTo Skip Server Side Validation

ASP.NET button have nice property CausesValidation which allows to prevent client side validation when button clicked. Common, recomended pattern states that any validation performed on client should be done again at server. Suppose we use CustomValidator control with both client and server validation functions. We can bypass client validation using CausesValidation property, but we will be stoped by server validation. Would it be nice (logical) to bypass all validation on postback event raised by button with CausesValidation property set to false? Let's see how can we make this happen. The direct approach is to override Page.Validate method. in this method we'll check if event raised by button with CausesValidation=false and will call real Validate only if not. I probably could reproduce ASP.NET code used to retrieve from Request event source control, but here I am taking shortcut. We'll use private property of Page _registeredControlThatRequireRaiseEvent which is instantiated by ASP.NET. A bit of reflection at it's done:

public override void Validate()
{
   Button btn = typeof(Page).InvokeMember("_registeredControlThatRequireRaiseEvent",
                                        BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance, null, this, null, null) as Button;
   if (btn != null && !btn.CausesValidation)
      return;
   base.Validate();
}

HowTo Change Master Page Dynamically at Runtime

Changing MasterPage dynamically is a simple task, but there is a catch. MasterPage can be changed in PreInit page or earlier. At this stage control tree is not constructed yet. The question is: "How can we use postback event to change MasterPage?" Usual solution for this problem is to save selected MasterPage file name into session, reload page and in PreInit set MasterPage according to persisted value. This works but requires additional roundtrip. Another solution is to intercept postback events in PreInit and process it, so here it is:


ASPX:
@ Page MasterPageFile="~/MasterPage.master" Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>
<asp:Content ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div>
<asp:DropDownList ID="MasterSwitch" runat="server" AutoPostBack="true">
<asp:ListItem Text="Simple Layout" Value="MasterPage.master">asp:ListItem>
<asp:ListItem Text="Complex Style" Value="MasterPageComplex.master">asp:ListItem>
asp:DropDownList>
div>
asp:Content>


Codebeside:

using
System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class Default : System.Web.UI.Page
{
   protected void Page_PreInit(object sender, EventArgs e)
   {
      switchMaster();
  
   protected void Page_Load(object sender, EventArgs e)
   {
      if (!IsPostBack)
      {
         Session[
"MasterSwitch"] = this.MasterSwitch.UniqueID;
      }
   }
   private void switchMaster()
   {
      if (IsPostBack)
      {
         // Get control that fire postback event
         string eventTarget = Request.Form["__EVENTTARGET"];
         if (String.IsNullOrEmpty(eventTarget))
            return;
         // At this stage we don't have control tree yet,
         // so we'll use previosly saved control ID
         string switchControlName = (string)Session["MasterSwitch"];
         if (String.IsNullOrEmpty(switchControlName))
            return;
         if (String.Compare(eventTarget, switchControlName, true) != 0)
            return;
         setMaster(Request.Form[eventTarget]);
      }
   }
   private void setMaster(string masterName)
   {
      if (String.IsNullOrEmpty(masterName))
         return;
      // In real implementation some logic to convert
      // selected value into real MasterPage File Name should be here
      if (String.Compare(this.MasterPageFile, masterName, true) != 0)
         this.MasterPageFile = masterName;
   }
}

Prevent Postback Events from Firing on Page Refresh

One annoying behavior of ASP.NET pages is a replay of postback events when user hit refresh (F5) in browser.
This behavior is easily reproducible:
Place ASP button on Web Form
1. Add some code to Button_Click event
2. View page in browser
3. Check that your code being executed when you hit button
4. Refresh page and observe that your code being executed once again

Explanation is also simple. As far as the browser goes, when user hits refresh, exact previous request sent to server. This way server has no clue if it is postback event, first page hit or refresh.

The only workaround I found based on fact that client side onsubmit event is not fired on refresh. This being said, you can place 'stamp' counter into session and hidden field on page, increment it in submit event and compare to session version back at server.

Complete solution skeleton will look as follows:
ASPX:
<form id="Form1" onsubmit="var o=getElementById('__REFRESHSTAMP');var i=Number(o.value);i++;o.value=i;" 
       method="post" runat="server">
 <asp:button id="Button2" Text="Button" runat="server"></asp:button>
 <input id="__REFRESHSTAMP" type="hidden" value="1" runat="server">
 <asp:label id="Label1" runat="server">False</asp:label>
</form>

Codebehind (Designer generated code skipped):
public class WebForm1 : System.Web.UI.Page
{
   protected System.Web.UI.WebControls.Button Button1;
 
   protected System.Web.UI.HtmlControls.HtmlInputHidden __REFRESHSTAMP;
   private bool m_RefreshStamped;
   protected System.Web.UI.WebControls.Label Label1;
   private bool m_IsRefresh;
   private bool IsRefresh
   {
      get
      {
         if (!this.m_RefreshStamped)
            this.m_IsRefresh = checkRefresh();
         return this.m_IsRefresh;
      }
   }
   private int getValue(string val)
   {
      if (val == null || val.Length == 0)
         return 0;
      return Int32.Parse(val, CultureInfo.InvariantCulture);
   }
   private bool checkRefresh()
   {
      this.m_RefreshStamped = true;
      string stampKey = String.Concat(this.ToString(), "__REFRESHSTAMP");
      int prevStamp = getValue(this.Session[stampKey] as string);
      int stamp = getValue(this.__REFRESHSTAMP.Value);
      if (stamp > prevStamp) // Postback
      {
         this.Session[stampKey] = stamp.ToString(CultureInfo.InvariantCulture);
         return false;
      }
      return true;
   }
   private void Button1_Click(object sender, System.EventArgs e)
   {
      this.Label1.Text = this.IsRefresh.ToString();
      if (!this.IsRefresh)
         this.Button1.Text = "Clicked at: " + DateTime.Now.ToLongTimeString() ;
   }
}

Notes:
1. You should implement this for each page you want to process.
2. You should reset session stamp before redirecting from page.
3. I would suggest constructing complete solution as custom control with IsRefresh property and Clear method.

ASP.NET 2.0 Client Callback bug

Client Callback is one of cool and a bit overlooked features of ASP.NET 2.0. A few minutes ago, when trying to prepare demo based on Client Callback I found a nice bug. When Client Callback defined following javascript code rendered to client (skeleton):
function WebForm_CallbackComplete(){
    for (i = 0; i < __pendingCallbacks[i].length; i++) {
    // Not important
    WebForm_ExecuteCallback(callbackObject);
    // Not important
    } 
}
WebForm_ExecuteCallback method invokes callback function which is defined on WebForm. The problem appears when you have global variable named i in your WebForm.  For example another "for loop": for (i = 0; i < 100; i++). In this case loop variable in WebForm_CallbackComplete receive unpredictable value and cause uexpected behavior.
Simple fix immediately solves the problem: "for (var i=0…" instead of "for (i=0..."


Conclusions:
1. Do not use global variable named i in client callback function.
2. More important: never use undeclared (global) variables in javascript, unless you are absolutely sure what are you doing.

HOWTO: Create Templates Programmatically

There are three controls that use templates in ASP.NET (Repeater, DataList and DataGrid). You can create and use templates dynamically (in runtime) for each one of them. 

You can choose one of two ways to do it:
  1. Create template as User Control (ascx) and load it dynamically using Page.LoadTemplate method. See Creating a Templated User Control.
  2. Implement ITemplate interface. This simple interface has single method InstantiateIn, which is responsible for rendering template content. Common pattern suggest passing template type (HeaderTemplate, ItemTemplate etc.) as constructor parameter. Simple template implementation could look like this:

public class SeverityTemplate : ITemplate
{
   ListItemType templateType;
   public SeverityTemplate(ListItemType type)
  
   public SeverityTemplate(ListItemType type)
   {
      templateType = type;
   }
   public void InstantiateIn(System.Web.UI.Control container)
   {
      switch (templateType)
      {
         case ListItemType.Header:
            Literal lc = new Literal();
            lc.Text = "<U>Severity</U>";
            container.Controls.Add(lc);
            break;
         case ListItemType.Item:
            DropDownList ddl = new DropDownList();
            ddl.Items.Add("High");
            ddl.Items.Add("Medium");
            ddl.Items.Add("Low");
            ddl.DataBinding += new EventHandler(this.OnDataBinding);
            container.Controls.Add(ddl);  
            break;
      }
   }
   public void OnDataBinding(object sender, EventArgs e)
   {
      DropDownList ddl = (DropDownList) sender;
      DataGridItem container = (DataGridItem) ddl.NamingContainer;
      ddl.SelectedValue = ((DataRowView) container.DataItem)["Severity"].ToString();
   }
}
Note There are some light differences in creating DataGrid Template columns (different template types). See Creating DataGrid Templates Programmatically.

ASP.NET 1.x to 2.0 Upgrade Center

ASP.NET 1.x to 2.0 Upgrade Center on MSDN finally lunched. This site will serve as the single content point for all upgrade related collateral to help customers who are already using ASP.NET today easily move forward onto ASP.NET 2.0. If you are interesting in other migration related issues you can still visit Migration Center.

Using Non-Default Database to Store ASP.NET 2.0 Application Settings

In beta1 of ASP.NET 2.0 there were two providers available to access application management data database. MS Access provider was default one. Access mdb file was created on demand and stored in Data application folder. In beta2 things were changed. Now we have only one AspNetSqlProvider. Personally I love single radio button configuration touch in ASP.NET website configuration tool :-) :
   
Still, default application configuration database behaves pretty much the same way. SQLEXPRESS database file is created by demand in App_Data folder. It is not best solution for all deployment scenarios.
I have been asked couple of times lately how to setup ASP.NET 2.0 application to store settings in non-default database. Let's say we want to use centralized MS-SQL server instead of CQLEXPRESS files in App_Data directory for each application. With beta2 we can easily achieve this. Management database connection string is defined by "LocalSqlServer" entry in connectionStrings section of config file. We can add following section to Web.config to access database of our choice:

<connectionStrings>
   <
remove name="LocalSqlServer"/>
   <
add name="LocalSqlServer" 
             connectionString="Data Source=SqlServerName;Initial Catalog=aspnetdb;Integrated Security=True" 
             providerName="System.Data.SqlClient"/>
</
connectionStrings>

Note: You can use aspnet_regsql GUI driven utility to setup new or configure existing configuration database. This simple utility installed with .NET 2.0 SDK and can be accessed from Visual Studio 2005 Command Prompt.

ASP.NET 2.0 Deployment Without Source Code

I was asked couple of times recently about deployment of ASP.NET 2.0 application without source code.
New compilation model of ASP.NET allow immediate application update, including changes in codebehind files (ASP like “JUST SAVE” behavior). You are no longer required to compile Web Application before accessing it. It is very convenient behavior for development and even testing environment, but what about production? We, usually, do not want to upload source code to production server to be JIT compiled. It is not so good for intellectual property protection and allows dangerous accidentally code altering.


In VS2005 we have a lot of options to deploy Web Site:

  1. xcopy deployment
  2. Copy Web Site Wizard - provide nice GUI for copying/synchronization of files to/from destination server
  3. Web Setup Project
  4. Publish Web Site Wizard
  5. aspnet_compiler SDK utility

You can not use first two options to copy application without sources because normally compiled assembly stored in ASP.NET Temporary directory and not under application root. Only last two options will allow you to deploy precompiled Web without source code.

Notes:
Web Setup Project
• In beta2 this option was moved from Website to Build menu.
• It is not present in Web Express edition.
• Uncheck “Allow this precompiled site to be updateable“ option to remove markup from aspx files
aspnet_compiler
• You can only compile Web Site or compile and deploy it in single step
• Can not deploy to remote computer. You will have to deploy locally and copy output to remote server.

Microsoft Web Services Benchmarks for.NET 2.0

Microsoft has released a new version of the Web Service and XML benchmark tests originally developed by Sun. The new version implemented using .NET 2.0 Beta 2 and show a nice improvement in performance both of Web Service Requests processing and XML manipulation.

Unable to Update Default User Database from ASP.NET 2.0 site, published to IIS

In VS2005 beta2/MS-SQL EXPRESS if you are publishing your site to IIS virtual directory, attempt to access default user database (App_Data\aspnetdb.mdf) will fail. You will likely receive error message similar to this:

Failed to update database "D:\WebSites\ASPNET20CC\App_Data\ASPNETDB.MDF" because the database is read-only.

To reproduce the problem:
  1. Create simple web site with LoginControl and CreateUserWizard
  2. Publish site to IIS
  3. Browse login page from IIS virtual directory instead of Casiny
  4. Try to create new user or login with existing one
To fix the problem:
  1. Detach aspnetdb database from SQLEXPRESS using sseutil.exe utility.
  2. Remove *.mdf and *.ldf files from App_Data application directory
  3. Grant Read/Write access on App_Data to working process identity (ASPNET for IIS5.X or NETWORK SERVICE for IIS6)
  4. Copy *.mdf, *.ldf files back into App_Data folder
  5. Run application, refresh if any connection related error received

This bug is announced and fixed in later build of SSE. You can see full description here.

ASP.NET 2.0 Beta2 Compilation Model

Apparently compilation model of ASP.NET 2.0 was changed in beta2. If you take a look now at “...\Temporary ASP.NET files\Application Name\x\y\”, you can find out that first hit on any page in application trigger compilation of 3 assemblies:

  1. App_global.asax.<xyz>.dll - if Global asax exists in application
  2. App_Code_<xyz>.dll - if App_Code folder exists and contains al teast one source file
  3. App_Web_<xyz>.dll - wich contains all webforms' classes
If you add new page to existing application, first hit on it will create additional App_Web_<hlm>.dll.
App_Web_... assemblies reference global ones.

This model is different from widely discribed assembly-per-page model. IMHO it is simpler. Keap in mind possible implication of this model (type reference, reflection GetType implications etc.)

ASP.NET tab disappears from IIS MMC Application Properties

When Visual Studio 2005 installed on W2K or Win2003 platform, ASP.NET tab magically disappears from IIS MMC. ASP.NET tab used to set ASP.NET version for specified virtual directory. This behavior was introduced with 8.0.50203.0 (CTP) version and still present in beta2 release.

There is an open bug on this (open by Juan T. Llibre). Please review and vote.

For now if you want to run both ASP.NET 1.X and ASP.NET 2.0 on the same machine, you can use Denis Bauer's ASP.NET Version Switcher

Thanks to Memi Lavi for better solution: Clean registry settings. Worked for mine W2K VS2005 installation

ASP.NET 1.X to ASP.NET 2.0 migration talk

I am going to talk about ASP.NET 1.X to ASP.NET 2.0 migration at "Get Ready to VS2005" Microsoft Israel event.

Topics I am planning for now are:
  • Side-by-Side execution and configuration
  • Automatic conversion and Upgrade Wizard
  • XHTML and client resources issues
  • New Compilation model issues
  • Naming collisions

If anybody can think of other issues/pitfalls related to 1.X to 2.0 migration, I'll be glad to here. Any suggestion is welcome!

Execute javascript and perform postback from the same ASP.NET button

Another common question:

How can we execute both client script function and server side event from the same ASP.NET Button server control?

Button server control rendered as <input type=“submit”> html element. It submits the form and executes server side Click event handler on each click, mousedown or “Enter” pressed on form. Sometimes button controls render javascript in onlick event to perform validation tasks (if validator controls were droped on page). Knowing this, we don't want to interfere with validation or forms submit. The simplest way to execute client script before submit, is to attach scriptto mousedown event. You have to initiate button click after script execution to continue with submit:

<script language=“javascript“>
function doSomething()
{
   // Do some thing us