Yesterday I was trying to make one of my controls keep its scroll position. Since you can't set a scrollTop attribute (which I think it is dumb, but that's another issue) I used a HiddenField and some Javascript. Mainly one at submit that loaded the scrollTop value in the hidden field and one on load to re-set it.

Since the logic of my control was mainly in the Render method of the control, I wrote the RegisterStartupScriptBlock and RegisterOnSubmitStatement lines there. It worked like a charm, but then I decided to see if it works WITHOUT Ajax.Net by removing the update panel I have been using. It failed!

I wasted like two hours before I understood that the RegisterOnSubmitStatement did not add anything to the page. I moved it in the OnPreRender method of the control and it worked!

So, long story short: DO NOT use RegisterOnSubmitStatement in the Render method of controls, as I believe the onsubmit (and possibly ClientScriptBlocks) are rendered by the page before any of the controls on it.

Someone else talks about it here.

and has 0 comments
Many a solution on the net propose clearing a html page of images, dropdowns, buttons, checkboxes, radiosbuttons, etc and replacing them with text, then outputing the result as an Excel file. Excel is smart enough to understand the simplest HTML and opens the html as an Excel. The user only has to save the file as an XLS and everything is set.

But what about styles? Apparently, Excel does not work with numerically defined colors, but instead it uses a predefined set of 40 colors and displays any numerically defined color as the closest of these 40. It is a drag to see a beautiful page rendered in Excel in only the coarsest of colors.

But, if you are trying to build an "Excel compatible" page, or if you want to use a separate style for the excel export (btw, Excel doesn't understand stylesheets, so you have to save all style in a <STYLE> tag and NOT use multiple class names in the same class property like "class1 class2". Older Excels might not even do that) then you have to use the colors that Excel uses. Here is a list, as taken from my Office 2003:
        
Black
Negru
Maroon
Maro
Dark Olive
Oliv inchis
Dark Green
Verde inchis
Dark Blue
Albastru inchis
Navy Blue
Bleumarin
Indigo80% Gray
Gri-80%
#000000#993300#333300#003300#000040#000080#333399#333333
        
Gold Red
Bordo
Orange
Portocaliu
Olive Green
Verde Oliv
Green
Verde
Teal Blue
Albastru Verzui
Blue
Albastru
Gray Blue
Gri albastrui
50% Gray
Gri-50%
#800000#FF6600#808000#008000#008080#0000FF#666699#808080
        
Red
Rosu
Yellow Orange
Galben Portocaliu
Yellow Green
Verde galbui
Sea Green
Verde marin
CyanLight Blue
Albastru deschis
Violet
Violet
40% Gray
Gri-40%
#FF0000#FF9900#99CC00#339966#33CCCC#3366FF#800080#969696
        
Fuchsia
Ciclam
Gold
Auriu
Yellow
Galben
Lime
Verde aprins
Aqua
Turcoaz
Sky Blue
Azuriu
Violet Red
Mov
25% Gray
Gri-25%
#FF00FF#FFCC00#FFFF00#00FF00#00FFFF#00CCFF#993366#C0C0C0
        
Pink
Roz
Ocre
Ocru
Light Yellow
Galben pal
Light Green
Verde deschis
Light Aqua
Turcoaz deschis
Light Blue
Bleu pastel
Orchid
Lila
White
Alb
#FF99CC#FFCC99#FFFF99#CCFFCC#CCFFFF#99CCFF#CC99FF#FFFFFF

It's very complicated to change the default culture for SQL Server: SET LANGUAGE 'English'. Duh!

I've accidentally stumbled upon a thing called OutputCache. Weird little thing, I thought, how come I didn't hear about it? It seemed such a wonderful concept: just cache the output of ASP.Net pages and controls for a specific amount of time, specify for what parameters or controls or properties the cache is valid for and if it is shared between different users. Then just output the cached version and don't execute the entire thing all the time.

I tested it and it works. Most useful I find it for user controls. One can put a list of unchanging stuff like a list of cities or provinces or even customers in a user control, cache its output and forget about it.

How to use it:
A) declaratively. Put in the page or control something like this:
<%@ OutputCache Duration=100000 Shared="true" VaryByParam="none" %>
Just read the link above for the details.

B) programmatically. Put in the page or control code something like this:
CachePolicy.Cached = true;
CachePolicy.Duration = new TimeSpan(1, 0, 0, 0, 0);
CachePolicy.VaryByParams.IgnoreParams = true;

Update: it has come to my attention that this returns an error with the message "The CachePolicy cannot be used, since the control is not being cached." which is returned when the CachePolicy was not instantiated with a BasePartialCachingControl as a parameter. The solution, it seems, it is to also decorate the user control with the PartialCaching attribute.

ex: [PartialCaching(20)] - set the caching to 20 seconds.

C) One can even validate the cache programmatically after whatever of A or B methods were used like this:
Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(MyCacheValidatorMethod),null );

That's it!

This book is written by a guy that made a small company dedicated to programming with free tools. Thus, it tries to show how to debug ASP.Net assuming that you come from an ASP background and that you don't have Visual Studio .Net. So I don't think I have to tell you how much useless information there is in there. It even covers programmatic code tracing.

That doesn't mean it is a bad book, just useless for me. If you expect some wonderful debugging frameworks or code structure that would allow you to easily debug your applications, then this book is not for you. Not to mention that at the time it was written, Visual Studio 2005 was not out yet.

Bottom line: just a hands on approach and some Google when you meet problems will solve any problems that are solved by this book.

You are trying to use a WebMethod or a web service ScriptMethod in Javascript and you get an InvalidOperationException saying something about a circular reference. It happened to me when trying to read a DataTable in Javascript.

Why. The Javascript serialization of DataSets, DataTables and DataRows was available once in the ASP.Net Ajax web extensions. That's why you probably found a lot of Google results with people that could either only serialize DataSets, but not DataTables, or people that made it work by magic by adding some lines in the converters section of web.config, things that can't possibly work with your setup. Then, the option was removed in the final version of ASP.Net Ajax, only to be readded in the ASP.Net Futures, which is a test playground for future features of the platform.

What. There are several options, one being to reference an older version of ASP.Net Ajax and uses the converters there. But why bother? It's unlikely you use the DataTable or some other object in Javascript with all the options of the C# object. You probably just want to itterate through rows and read properties. So build your own converter.

How. Create a new library project. Add a class named DataTableConverter that inherits from JavaScriptConverter, and implement: IEnumerable<Type> SupportedTypes, IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) and object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer).

You probably won't need to Deserialize anything, you can leave that unimplemented. The list of convertible types is easy enough, all you are left with is the Serialize code, which is actually very easy, too. Then all you need to do is add this in the web.config file:
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization >
<converters>
<add name="DataTableAjaxFix" type="AjaxTypeConverters.DataTableConverter"/>
</converters>
</jsonSerialization>


And here is the complete C# code of my DataTableConverter, but you can easily adapt it to anything:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Web.Script.Serialization;

namespace AjaxTypeConverters
{
public class DataTableConverter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
get { return new Type[] {typeof (DataTable)}; }
}

public override object Deserialize(IDictionary<string, object> dictionary, Type type,JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}

public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
DataTable listType = obj as DataTable;

if (listType != null)
{
// Create the representation.
Dictionary<string, object> result = new Dictionary<string, object>();
ArrayList itemsList = new ArrayList();
foreach (DataRow row in listType.Rows)
{
//Add each entry to the dictionary.
Dictionary<string, object> listDict = new Dictionary<string, object>();
foreach (DataColumn dc in listType.Columns)
{
listDict.Add(dc.ColumnName, row[dc]);
}
itemsList.Add(listDict);
}
result["Rows"] = itemsList;

return result;
}
return new Dictionary<string, object>();
}
}
}

Yesterday I was trying desperately to understand why my web site was crashing without any error, the only information I could get being that the connection to the server has been reset. I've spent hours trying to determine what was wrong. Apparently I needed a break, because today it took me a few minutes to realize what it was.

First of all, duh! If there are issues with the connection server, look into the Windows Application Event Log. But we'll get there.

The "error" appeared at any postback after I loaded a certain page, but only if that page displayed a minimum of data. Above that threshold I would get the server reset thing that you can see both in IE7 and FireFox2 in the animated GIF. Basically the error messages were:
FireFox
The connection was reset
The connection to the server was reset while the page was loading.

Internet Explorer
Internet Explorer cannot display the webpage
Internet connectivity has been lost.
The website is temporarily unavailable.
The Domain Name Server (DNS) is not reachable.

Ajax UpdatePanel
Server returned error 12031

So, today I realised I should look in the Application Event Log and this Web Event Warning was displayed (shortened it a bit):
Event code: 3004
Event message: Post size exceeded allowed limits.

Process information:
Process name: aspnet_wp.exe

Exception information:
Exception type: HttpException
Exception message: Maximum request length exceeded.

Stack trace: at System.Web.HttpRequest.GetEntireRawContent()
at System.Web.HttpRequest.FillInFormCollection()
at System.Web.HttpRequest.get_Form()
at System.Web.HttpRequest.get_HasForm()
at System.Web.UI.Page.GetCollectionBasedOnMethod(Boolean dontReturnNull)
at System.Web.UI.Page.DeterminePostBackMode()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)


It turns out I was putting a lot of data into the ViewState, which, as you know, is saved as a HiddenField (a.k.a. hidden html input) and the size of it exceeded the set up maximum POST size.

Solutions:
A. Add this code to your page: (NET 2.0)
 protected override PageStatePersister PageStatePersister
{
get
{
//return base.PageStatePersister;
return new SessionPageStatePersister(this);
}
}


This should put your ViewState into the Session, rather than in the page. This solves some other issues as well, obviously.

B. Increase the maximum Request limit (default is 4Mb)
- In the Machine.config file, change the maxRequestLength attribute of the <httpRuntime> configuration section to a larger value. This change affects the whole computer.
- In the Web.config file, override the value of maxRequestLength for the application. For example, the following entry in Web.config allows files that are less than or equal to 8 megabytes (MB) to be uploaded:
<httpRuntime maxRequestLength="8192" />

This is an exact quote from the Microsoft support page.

That's it, folks!

Update:

The maxRequestLength maximum value is 2097151, that is less than 2.1Gb. No file that exceeds this size can be uploaded through the default upload mechanism.

You may have noticed that in debug mode, in Visual Studio, you have a little magnifier glass next to some of the variables in Autos, Local or Watch debug windows. Once you click on it, you get to visualize your data in a more comprehensive way. A good example are the DataSet Visualizer or the DataTable Visualizer which show you in a normal DataGridView a DataSet or DataTable.

The good news is that you can build your own visualizers and that in a very simple way. Here are the quick steps to achieving this, followed by some links to other people detailing:

  1. Create a new Visual Studio class library project
  2. Add a reference to the Microsoft.VisualStudio.DebuggerVisualizers library you can find directly in the .NET tab
  3. Go to Add New Item and choose Debugger Visualizer. That will create a small class for you with ToDos and stuff like that. What is important is that you don't really need to declare the Type of your data object in the class, as suggested by the template.
  4. Remove everything from the class except the override of the Show method
  5. Change the Show method in order to use your own data type.
  6. Add a reference to System.Windows.Forms
  7. Add a Windows Form to your library and make it display your data the way you like it
  8. Add the following lines to decorate the namespace of your visualizer class:

[assembly : DebuggerVisualizer(typeof (--YourVisualizer--),
Target = typeof (--Your Type--),
Description = "--Your Type-- Visualizer")]
namespace ...


Warning: use a different description from whatever default or own visualizers that are already there. Use stuff like "Siderite's DataTable Visualizer" not "DataTable Visualizer", since there is already an out-of-the-box visualizer with the same name and you won't get to see yours.

Now compile. The resulting DLL can be either copied in My Documents\Visual Studio 2005\Visualizers in this case any contained visualizers will be available only to that user or in C:\Program Files\Microsoft Visual Studio 8\Common7\Packages\Debugger\Visualizers to make the available to all users.

That is it! Now links to others explaining in more detail:
Writing a Visualizer at MSDN
Post on Debugger Visualizers from 4GuysFromRolla
Julia Lerman on Debug Visualizers

Update:
A problem with this solution is that the debugger visualizer expects your target type to be ISerializable. But what if it is not? The solution is to add another parameter to the DebuggerVisualizerAttribute like this:
[assembly : DebuggerVisualizer(typeof (--YourVisualizer--),
typeof (--YourVisualizerObjectSource--),
Target = typeof (--Your Type--),
Description = "--Your Type-- Visualizer")]
namespace ...


You see, the debugging is done through communication between a debuggee and a debugger. The VisualizerObjectSource is the debugee and the default one tries to serialize the target object and send it to the debugger. What you have to do it create your own class, inheriting from VisualizerObjectSource and overriding public void GetData(object target, System.IO.Stream outgoingData). This method has access to the actual object, so you can transform it into any other object, one that can be serializable.

A simple example is a DataView or a DataRow. You take the DataRow, you add it to a Table and you return the DataTable, which is serializable.

Another issue you might stumble upon is a weird Access Denied error for the DLL containing the visualizers, especially after adding a VisualizerObjectSource to the library. The solution is to add trust level to full to the site you are debugging. I am looking for a more elegant solution, but so far what you have to do is add this to the web.config of the site you are debugging:

<system.web>
<trust level="Full" originUrl="" />
</system.web>

More links, specific to this update:
RemoteObjectSourceException: Graphics is not marked as serializable
Visualizers For Web Debugging

Yes, the situation is fairly simple, you do a parentControl.FindControl("someOtherControl") in ASP.Net and you get controls that are not in parentControl. Why?

Because apparently, FindControl wants a NamingContainer in which to search. If the parentControl is not an INamingContainer, it will look into the parentControl's NamingContainer.

Sollution? Create your own FindControl method, one that recursively goes through the child controls and looks for the specific ID.

Well, it's just as in the AjaxControlToolKit page for the AutoCompleteExtender control. But here is a quick dirty list of the steps:

Option A: (the static page method option)

  1. Add a TextBox to your page
  2. Add the AutoCompleteExtender
  3. Set the ScriptManager property EnablePageMethods to true
  4. Add a static method to the page, one that gets a string and an integer as parameters and returns a string array
  5. Decorate it with [WebMethod(true/false)]. If you set the WebMethod parameter to true, it will have access to the Session
  6. Make sure to return the list of strings depending on the string parameter (which represents what was typed in the TextBox)
  7. Warning! If the method is faulty, you will get no error message, the autocomplete will simply not work.
  8. Warning! Having a different method signature will also cause this to not work.
  9. Set the properties for the AutoCompleteExtender: TargetControlID with the ID of the TextBox, MinimumPrefixLength with the count of typed characters from which to attempt autocomplete, ServiceMethod with the name of the static page method and CompletionInterval with the miliseconds before it attempts autocomplete.

Option B: (the web service option)

  1. Add a TextBox to your page
  2. Add the AutoCompleteExtender
  3. Add a webservice to your web site
  4. The webservice must have [ScriptService] decorating it's class in the codebehind
  5. Add a NOT static method to the webservice, one that gets a string and an integer as parameters and returns a string array
  6. Decorate it with [WebMethod(true/false)] and [ScriptMethod]. If you set the WebMethod parameter to true, it will have access to the Session
  7. Make sure to return the list of strings depending on the string parameter (which represents what was typed in the TextBox)
  8. Warning! If the method is faulty, you will get no error message, the autocomplete will simply not work.
  9. Warning! Having a different method signature will also cause this to not work.
  10. Set the properties for the AutoCompleteExtender: TargetControlID with the ID of the TextBox, MinimumPrefixLength with the count of typed characters from which to attempt autocomplete, ServiceMethod with the name of the WebMethod in the webservice, CompletionInterval with the miliseconds before it attempts autocomplete and ServicePath to the path of the asmx path.


Now it should work.

Code:

//=== AutoComplete.cs - the web service ===
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class AutoComplete : WebService
{

[WebMethod(true)]
[ScriptMethod]
public string[] GetList(string prefixText, int count)
{
string[] arr=new string[] {'list','of','words'};
return arr;
}
}

//=== Web page codebehind
[WebMethod(true)]
[ScriptMethod]
public static string[] GetList(string prefixText, int count)
{
string[] arr=new string[] {'list','of','words'};
return arr;
}


=== Web page ===

<asp:TextBox ID="textboxWithAutoComplete" runat="server">
<cc1:AutoCompleteExtender ID="autoCompleteExtender1" runat="server" TargetControlID="textboxWithAutoComplete"
MinimumPrefixLength="0"
ServiceMethod="GetList"
CompletionInterval="0"
ServicePath="AutoComplete.asmx"
>
</cc1:AutoCompleteExtender>

WARNING! The parameters of the web method must be named prefixText and count or the AutoCompleteExtender will NOT WORK!

A small paragraph that most people miss on the AjaxControlToolKit sample page says: Note that you can replace "GetCompletionList" with a name of your choice, but the return type and parameter name and type must exactly match, including case.

Update (21Th of March 2008): Very important, you need to add the CreateChildControls override in order to work. Otherwise, because of something I can only consider a GridView bug, this will happen: the last page in a mock paging grid will have, let's say, 2 items when the PageSize is 10; on a postback, the number of rows created by the gridview will be 10! even if only 2 have data. Thus, after a postback that doesn't rebind the data in the grid, the GridView.Rows.Count will be PageSize, not the actual bound number.

Update: Recreated the code completely. Now it has both PageIndex and ItemCount.
Also: Actually there is a way to get only the rows that you need in SQL Server 2005. It is a function called Row_Number and that returns the index number of a row based on a certain ordering. Then you can easily filter by it to take items from 20 to 30, for example. In this case, another interesting property of the PagedDataSource is CurrentPageIndex, to set the displayed page number in the pager.

Now, for the actual blog entry.

Why would anyone want to change the PageCount, you ask? Well, assume you have a big big table, like hundreds of thousands of rows, and you want to page it. First you must put it in a DataTable from the SQL server, so that takes time, then the table set a datasource to the GridView, then it implements the paging.

Wouldn't it be nicer to only get the data that you need from the SQL Server, then change the PageCount to show the exact page count that should have been? However, the PageCount property of the GridView is read-only. One quick solution is to get only the data you need, then fill the resulting DataTable with empty rows until you get the real row count. However, adding empty rows to DataTables is excruciatingly slow, so you don't really gain anything, and the Grid works with a big table anyway.

So this is what you do:
First of all determine how much of the data to gather.

Afterwards you need to trick the GridView into creating a Pager that shows the real row count (and possibly page index). Unfortunately you can't do this from outside the GridView. You need to inherit the GridView control and add your stuff inside. After you do this, you need to override the InitializePager method, which is just about the only protected virtual thing related to Paging that you can find in the GridView.

Code:

using System.Web.UI.WebControls;

namespace Siderite.Web.WebControls
{
public class MockPagerGrid : GridView
{
private int? _mockItemCount;
private int? _mockPageIndex;

///<summary>
/// Set it to fool the pager item Count
///</summary>
public int MockItemCount
{
get
{
if (_mockItemCount == null)
{
if (ViewState["MockItemCount"] == null)
MockItemCount = Rows.Count;
else
MockItemCount = (int) ViewState["MockItemCount"];
}
return _mockItemCount.Value;
}
set
{
_mockItemCount = value;
ViewState["MockItemCount"] = value;
}
}

///<summary>
/// Set it to fool the pager page index
///</summary>
public int MockPageIndex
{
get
{
if (_mockPageIndex == null)
{
if (ViewState["MockPageIndex"] == null)
MockPageIndex = PageIndex;
else
MockPageIndex = (int) ViewState["MockPageIndex"];
}
return _mockPageIndex.Value;
}
set
{
_mockPageIndex = value;
ViewState["MockPageIndex"] = value;
}
}

///<summary>
///Initializes the pager row displayed when the paging feature is enabled.
///</summary>
///
///<param name="columnSpan">The number of columns the pager row should span. </param>
///<param name="row">A <see cref="T:System.Web.UI.WebControls.GridViewRow"></see> that represents the pager row to initialize. </param>
///<param name="pagedDataSource">A <see cref="T:System.Web.UI.WebControls.PagedDataSource"></see> that represents the data source. </param>
protected override void InitializePager(GridViewRow row, int columnSpan, PagedDataSource pagedDataSource)
{
if (pagedDataSource.IsPagingEnabled && (MockItemCount != pagedDataSource.VirtualCount))
{
pagedDataSource.AllowCustomPaging = true;
pagedDataSource.VirtualCount = MockItemCount;
pagedDataSource.CurrentPageIndex = MockPageIndex;
}
base.InitializePager(row, columnSpan, pagedDataSource);
}

protected override int CreateChildControls
(System.Collections.IEnumerable dataSource, bool dataBinding)
{
PageIndex = MockPageIndex;
return base.CreateChildControls(dataSource, dataBinding);
}
}
}


What, what, whaaat? What is a PagedDataSource? Inside the GridView, the paging is done with a PagedDataSource, a wrapper around a normal DataSource, which has some of the GridView paging properties like PageSize, PageCount, etc. Even if the PageCount is also a read-only property, you have the AllowCustomPaging property and then the VirtualCount and CurrentPageIndex properties that you can set.

In other words: the pager is initialized at databinding. Set MockItemCount and MockPageIndex before MockPagerGrid.DataBind();

That's it.

Update: People keep asking me to provide a code sample. Let's try together. First, let's see a classic GridView use example:

gridView.DataSource=getDataSource();
gridView.PageIndex=getPageIndex();
gridView.DataBind();
As you can see, we provide a data source programatically, then set the pageindex (let's assume we took it from the URL string) and then call DataBind(). In this situation, we would load the entire data source (say, 10000 rows) then give it to the grid, which would only render something like 20 rows. Very inefficient.

Now, let's replace the original GridView control with the with MockPagerGrid. The code would look like this:

mockPagerGrid.DataSource=getDataSource(2);
mockPagerGrid.MockPageIndex=getPageIndex();
mockPagesGrid.MockItemCount=10000;
mockPagerGrid.DataBind();
This gets the rows for the second page, sets the mock ItemCount and PageIndex to the total number of rows and the page we want and then calls DataBind(). In this situation getDataSource would load only the 20 rows of page 2, would display it, then the pager would show that it is on page 2 out of 500.

This is a very simple example. It assumes you already know the total number of rows returned. A more complete example would look like this:

// starting with an arbitrary page index
var pageIndex=getPageIndex();
// do operations on the database that would return the rows for the page
// with that index, having the size of the page size of the grid
// and also get the total number of rows in the data source
CustomDataSource dataSource=getDataSource(pageIndex,mockPagerGrid.PageSize);
// set the returned rows as the data source
mockPagerGrid.DataSource=dataSource.Rows;
// set the page index
mockPagerGrid.MockPageIndex=pageIndex;
// set the total row count
mockPagesGrid.MockItemCount=dataSource.TotalRowCount;
// databind
mockPagerGrid.DataBind();

// CustomDataSource would only have two properties: Rows and TotalRowCount
// The sql for getDataSource(index,size) would be something like
// SELECT COUNT(*) FROM MyTable -- get the total count
// SELECT * FROM MyTable WHERE RowIndex>=@index*@size
// AND RowIndex<(@index+1)*@size

// for convenience, I assumed that there is a column called RowIndex in
// the table that is set to the row index


Hopefully, this will help people use this code.

Well, a timestamp is defined as the integer number of seconds from 1st of January 1970, but not 1st January 1970 itself, that would mean 0 seconds and that is reserved as the 'zero time'.

So, converting is easy in T-Sql (Microsoft Sql Server):
@dateTime=DateAdd(second,{d '1970-01-01'},@timeStamp)
@timeStamp=DateDiff(second,{d '1970-01-01'},@dateTime)

The {d 'yyyy-MM-dd'} notation is an ODBC escape sequence.

and has 2 comments
The Multiline Regex option changes the way "^" and "$" work so that they match the beginning and the end of each line in the input string. Good for quick searches of a string in a list of newline separated strings.

The WriteLine and AppendLine and other .Net text related methods that end in Line append at the end of the input string an Environment.NewLine. This is \r\n in Windows and \n in Linux based systems. But, RegexOptions.Multiline only works on... you guessed it... \n aka new line, ignoring the good old carriage return altogether.

The solutions are: either create the string that contains the lines by adding manually \n and not using *Line, or change the regular expression to something like "^something\r?$".

I was trying to make a site using a TabContainer and an UpdatePanel and I kept receiving a PageRequestManagerParserErrorException, but only sometimes. A page refresh would fix it and the message looked like "Error parsing near '<html>

<head>

'". The funny thing is that in the Ajax response output I had no <html> or <head>.
Trying desperately a fix detailed in this very nice post: Sys.WebForms.PageRequestManagerParserErrorException - what it is and how to avoid it , mainly forcing the start of a Session when the Page is first loaded, seemed to fix it. (put a if (!IsPostBack) Session["Siderite"]="rules"; in Page_Load)

So beware: the dreaded PageRequestManagerParserErrorException doesn't appear only when the response output is malformed, but apparently also when in some cases someone tries to start a Session from within an async postback.

If you have some other problem linked to this exception, read this post: ASP.Net Ajax and Response.Write or response filters The message received from the server could not be parsed.

Update: The 30 September 2009 release of the AjaxControlToolkit doesn't have the error that I fix here. My patch was applied in July and from September on the bug is gone in the official release as well. Good riddance! :)

==== Obsolete post follows

Update: On June 20th 2009, Codeplex notified me that the patch I did for the ACT has been applied. I haven't tested it yet, though. Get the latest source (not latest stable version) and you should be fine.

This post was updated on the 1st of July 2008 with some clearer explanations and some error corrections thanks to Santoé who pointed out some mistakes.

My ASP.Net app uses a TabContainer, with a TabPanel in the *x code and with additional TabPanels added dynamically in codebehind.

Well, I got a lot of errors so I've decided to debug and change the control in order to fix it.

Step 1: download the AjaxControlToolKit with source included and open the project locally.

First error : Specified argument was out of the range of valid values. Parameter name: index, somewhere in the TabPanelCollection indexer. The problem actually occurs in TabContainer in LoadClientState(string clientState) where there is a for (int i = 0; i < tabState.Length ; i++). It doesn't take into account the possibility that the number of Tabs and the number of values taken from the tabState can be different. So the code must look like this: for (int i = 0; i < tabState.Length && i < Tabs.Count; i++).

Step 2: In the AjaxControlToolkit\AjaxControlToolkit\Tabs\ folder there is a TabContainer.cs file. Change for (int i = 0; i < tabState.Length ; i++) to for (int i = 0; i < tabState.Length && i < Tabs.Count; i++).

The second error is actually a thrown error in the ActiveTabIndex property setter: if (value >= Tabs.Count) { throw new ArgumentOutOfRangeException("value"); }, but it all comes from this: if (Tabs.Count==0 && !_initialized), because it doesn't take into account the possibility that the Tabs.Count is smaller than the ActiveTabIndex, but not zero. So that should look like this: if (value >= Tabs.Count && !_initialized).

I've downloaded the latest AjaxControlToolKit (version Version 1.0.20229 - Feb 29 2008) and the scratched fix above doesn't seem to work anymore. Instead, try patching the ActiveTabIndex property like this:

[DefaultValue(-1)]
[Category("Behavior")]
[ExtenderControlProperty]
[ClientPropertyName("activeTabIndex")]
public virtual int ActiveTabIndex
{
get
{
if (_cachedActiveTabIndex > -1)
{
return _cachedActiveTabIndex;
}
if (Tabs.Count == 0)
{
return -1;
}
return _activeTabIndex;
}
set
{
if (value < -1)
throw new ArgumentOutOfRangeException("value");
if (Tabs.Count == 0 && !_initialized)
{
_cachedActiveTabIndex = value;
}
else
{
if (ActiveTabIndex != value)
{
if (ActiveTabIndex != -1
&& ActiveTabIndex < Tabs.Count)
{
Tabs[ActiveTabIndex].Active = false;
}
if (value >= Tabs.Count)
{
_activeTabIndex = Tabs.Count-1;
_cachedActiveTabIndex = value;
}
else
{
_activeTabIndex = value;
_cachedActiveTabIndex = -1;
}
if (ActiveTabIndex != -1
&& ActiveTabIndex < Tabs.Count)
{
Tabs[ActiveTabIndex].Active = true;
}
}
}
}
}


In other words, remove the ArgumentException code block and move the condition inside the next block, where you set the real _activeTabIndex to the highest legal value, yet you put the real value in _cachedActiveTabIndex.

Step 3: in the AjaxControlToolkit\AjaxControlToolkit\Tabs\ folder there is a TabContainer.cs file. Change the ActiveTabIndex property with the code above.

The same thing must be done in Javascript, in the Tabs.js file, if you intend to use a TabContainer with no static tabs defined. In case you do that, you will get a javascript error "Microsoft JScript runtime error: Sys.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: value
". The fix is to change the set_activeTabIndex function of the TabContainer in the file tabs.js to this:
set_activeTabIndex : function(value) {
if (!this.get_isInitialized()) {
this._cachedActiveTabIndex = value;
} else {
if (this._activeTabIndex != -1) {
this.get_tabs()[this._activeTabIndex]._set_active(false);
}
if (value < -1 || value >= this.get_tabs().length) {
this._activeTabIndex = this.get_tabs().length-1;
this._cachedActiveTabIndex=value;
} else {
this._activeTabIndex = value;
this._cachedActiveTabIndex=-1;
}
if (this._activeTabIndex != -1) {
this.get_tabs()[this._activeTabIndex]._set_active(true);
}
if (this._loaded) {
this.raiseActiveTabChanged();
}
this.raisePropertyChanged("activeTabIndex");
}
},


Step 4: in the AjaxControlToolkit\AjaxControlToolkit\Tabs\ folder there is a tabs.js file. Change the set_activeTabIndex function with the code above.

This fixed it for me for now.

Step 5: Compile the now patched AjaxControlToolKit and use the resulting dll in your project instead of the default one.

As a reference, my test app does the following things:
  • Starts with a TabContainer with single TabPanel defined in the aspx
  • Has a button that adds new tabs to the TabContainer dynamically on the Click event
  • The panels have buttons in them that can be clicked
  • The active tab must be preserved during postbacks
  • The page must work both on synchronous and asynchronous postbacks


Here is the code for the page

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using AjaxControlToolkit;

public partial class _Default : Page
{
private int? _tabCount;

/// <summary>
/// Keep in ViewState the number of dynamically added tabs
/// </summary>
public int TabCount
{
get
{
if (_tabCount == null)
{
if (ViewState["TabCount"] == null)
TabCount = 0;
else
TabCount = (int) ViewState["TabCount"];
}
return _tabCount.Value;
}
set
{
_tabCount = value;
ViewState["TabCount"] = value;
}
}

protected void Page_Load(object sender, EventArgs e)
{
InitTabs();
}

/// <summary>
/// Add the dynamical tabs after each postback
/// </summary>
private void InitTabs()
{
for (int c = 0; c < TabCount; c++)
AddPanel();
}

/// <summary>
/// Dynamically add a panel to the TabContainer
/// </summary>
private void AddPanel()
{
TabPanel tp = new TabPanel();
tp.HeaderText = "Test Dinamic";
TextBox tb = new TextBox();
Button btn = new Button();
btn.Text = "Click me!";
tp.Controls.Add(btn);
tp.Controls.Add(tb);
TabContainer1.Tabs.Add(tp);
}

/// <summary>
/// Click event to add a new panel
/// and update the TabCount property
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void btnAddPanel_Click(object sender, EventArgs e)
{
AddPanel();
// do this if you didn't have any staticly defined
// tabs or else the dynamic tabs will be invisible
If (TabCount==0) TabContainer1.ActiveTabIndex=0;
TabCount++;
}
}