Emil Stoichev reminded today that it is very important to understand what is ViewState, how it works and how we should use it. The other problem mentioned there is unavailability of controls in PreInit phase if MasterPage is used.
The problem is very simple. If you have the following content page (in master page),
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" Title="Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<p>Content</p>
<p><asp:Label runat="server" ID="ContentPageLabel" /></p>
</asp:Content>
<script runat="server">
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
// next line crashes
ContentPageLabel.Text = "Hello, World!";
}
</script>
it crashes on the label text assignment with:
Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Source Error:
Line 11: base.OnPreInit(e); Line 12: // next line crashes Line 13: ContentPageLabel.Text = "Hello, World!"; Line 14: } Line 15: </script> |
I had to solve this problem some time ago,and I found that once you access the Master property of you content page for the first time all controls become instantiated.
So, adding just a single line of code before label text assignment fixes the problem.
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<p>Content</p>
<p><asp:Label runat="server" ID="ContentPageLabel" /></p>
</asp:Content>
<script runat="server">
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
// the following line is important
MasterPage master = this.Master;
// unfortunately, compiler warns us that master is not used
ContentPageLabel.Text = "Hello, World!";
}
</script>
Note that everything works like without MasterPage with this small change.
C# 3.0 and LINQ is not only about accessing data in relational databases and LINQ queries can be used against in-memory data structures as well. Yesterday I had to discover all methods in an assembly satisfying some criteria. As I as playing with Visual Studio 2008 Beta 2 I tried to use new language features everywhere (well, even where it was unnecessary).
I used Linq to filter my classes and methods and wrote this code in a functional style as I am not familiar with LINQ query syntax in C# 3.0.
public IEnumerable<Action> LocateActions()
{
return GetType().Assembly.GetTypes()
.Where(v => v.GetCustomAttributes(
typeof(ActionContainerAttribute), true).Length > 0)
.SelectMany(v => v.GetMethods(BindingFlags.Public |
BindingFlags.Instance))
.Where(v => v.GetCustomAttributes(
typeof(ActionAttribute), true).Length > 0)
.Select(v => new Action(v));
}
My first version works fine for me; I created the second and third versions just to see what is the most readable. So, the second version of the same method is C# 2.0:
public IEnumerable<Action> LocateActions2()
{
foreach (Type t in GetType().Assembly.GetTypes())
{
if (t.GetCustomAttributes(
typeof(ActionContainerAttribute), true).Length > 0)
{
foreach (MethodInfo mi in t.GetMethods(BindingFlags.Public |
BindingFlags.Instance))
{
if (mi.GetCustomAttributes(typeof(ActionAttribute),
true).Length > 0)
{
yield return new Action(mi);
}
}
}
}
}
and I got the last one from C# 2.0 code.
public IEnumerable<Action> LocateActions3()
{
return
from t in GetType().Assembly.GetTypes()
where
t.GetCustomAttributes(typeof(ActionContainerAttribute),true).Length> 0
from mi in t.GetMethods(BindingFlags.Public | BindingFlags.Instance)
where
mi.GetCustomAttributes(typeof(ActionAttribute), true).Length > 0
select
new Action(mi);
}
}
The first one remains for me the most clear and easy to read, but I believe that not everyone will agree with me.
Which one is the easiest to read for you?
Well, I was clicking next and next attempting to find relevant results. I have a reason to try another search engine!
Since the release of .NET 2.0 and ASP.NET 2.0 I was very disappointed that new language power of C# 2.0 is not available for ASP.NET developers. It was stressed at the time of release by multiple people (see here, here and here). While generic classes are not supported in ASP.NET markup in general, it seemed to me that it is still possible to benefit from generic in ASP.NET in many cases.
ASP.NET supports "control builders" abstraction controlling different aspects of code generation. The most important is that control builders define the actual type of page field for the control and actual class of the control to be instantiated.
Moreover, if control builder returns generic type, ASP.NET generates code which successfully instantiate it and page field is also correct.
As a small sample I created a generic enumeration drop-down list control. This control has a TypedValue property returning the currently selected enumeration member. Type of this property is controlled from the EnumType attribute in the ASPX markup. The following markup
<t:EnumDropDown runat="server" EnumType="MyEnum" ID="TheDropDown" />
allows to use TheDropDown in code behind in a statically typed manner:
MyEnum? atr = TheDropDown.TypedValue;
If you find the temporary C# file for the web page, you can see that TheDropDown field is declared as:
protected global::CouldBeDone.GenericControls.EnumDropDown<MyEnum>
TheDropDown;
The trick in getting this behavior is in custom control builder class which tells ASP.NET to use generic version of the class instead of non-generic. So, at first I created a non-generic version of EnumDropDown and decorated it with ControlBuilder attribute.
[ControlBuilder(typeof(EnumDropDownControlBuilder))]
public class EnumDropDown : DropDownList {
private string m_EnumType;
public string EnumType {
get {
return m_EnumType;
}
set { m_EnumType = value; }
}
}
The generic version of EnumDropDown which is used in web pages provides all useful features (like type Value property).
public class EnumDropDown<T> : EnumDropDown
where T : struct {
public T? TypedValue {
//// implementation removed
}
}
The EnumDropDownControlBuilder does only one thing. It replaces the type of control to be instantiated.
public override void Init(TemplateParser parser, ControlBuilder
parentBuilder, Type type,
string tagName, string id, IDictionary attribs) {
string enumTypeName = (string)attribs["EnumType"];
Type enumType = Type.GetType(enumTypeName);
Type dropDownType =
typeof(EnumDropDown<>).MakeGenericType(enumType);
base.Init(parser, parentBuilder, dropDownType,
tagName, id, attribs);
}
This was a very simple case. The most important place where I like to see static typing in ASP.NET is data binding. I tried to apply this technique to get typed Repeater and new ListView controls and stop using Eval and type casting in data binding expressions. I managed to get a result, but unfortunately it requires wrapping templates into special placeholder controls. I will probably make one more post about generic classes in ASP.NET, if I can get typed data binding more simple.
If you like to see live EnumDropDown, here is the sample Visual Studio project:
GenericEnumDropDown.zip
The web site was create in beta 2 of Visual Studio 2008, but as it targets .net 2.0, I hope it should run fine in VS 2005 as well.