March 2006 - Posts

Some GC Mode Issues

.NET Garbage Collector design to support two distinct modes: server and workstation.
In server mode GC creates one GC heap and one relative GC thread for each processor. Each one of these threads performs collection in parallel. Server mode available on multi-processor machines only and shows good throughput and scalability.
Workstation mode is default and only one available on single processor machine. In FW 1.0 and 1.1 server mode can be used only in unmanaged hosting environment. ASP.NET for example loads web applications in server mode. In FW1.1 and 2.0 GC mode could be controlled by configuration file entry.
Following node in configuration mode switches GC into server mode:
<CONFIGURATION>
   <RUNTIME>
      <GCSERVER enabled="true" />
   </RUNTIME>
</CONFIGURATION>
The first challenge is to recognize in what mode our server application GC is actually running. In FW1.x it is easy. Workstation mode GC loaded from mscorwks.dll, server mode GC will use mscorsrv.dll. You can use Task Manager or Process Explorer from www.sysinternals.com to check this. In FW 2.0 it is trickier. Both GC modes combined in mscorwks.dll.
Now we have new property GCSettings.IsServerGC which is return true in case that GC running in server mode.
The last tip is: be careful with your configuration file if you want to activate GC in server mode. If you'll try to use any non ASCII characters GC will be loaded gracefully in workstation mode regardless of gcServer tag. See this issue http://lab.msdn.microsoft.com/productfeedback/viewfeedback.aspx?feedbackid=55947061-2b06-4325-b1ef-bdfa9f0af9ec

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;
   }
}