Code sharing is in the mind of most programmers. The benefit of
code sharing is obvious: not only is duplicated code avoided, but more
importantly, maintainability is increased. Code sharing can be implemented in
various ways, subject to the constraints of programming language and
framework. Under object-oriented programming (OOP) model, one of the most
common ways to achieve code sharing is inheritance (a.k.a. subclassing).
Unfortunately, inheritance is not available to ASP web developers in the past
because of the limitation of ASP parser. ASP programmers may
circumvent the deficiency and achieve the goal of code sharing, to some
extent, via Server-Side Include (SSI):
| <!-- #Include File="Header.inc" --> |
Apparently SSI is more analogous to containment (a.k.a. layering, composition,
or embedding) than inheritance. Other advantages of inheritance such as
member overriding and access control cannot be easily achieved by SSI.
ASP.NET, the successor of ASP, has enabled programmers to take advantage
of the OOP model. The model is supported in ASP.NET by two new OOP languages:
C# and VB, along with page level declarations, in particular the Inherits
and Codebehind attributes. In ASP.NET, every
web form (with .aspx as extension) is derived directly or indirectly from .Net
Framework class System.Web.UI.Page. The class
hierarchy is:
System.Object
System.Web.UI.Control
System.Web.UI.TemplateControl
System.Web.UI.Page
If you use Visual Studio.NET to create a new WebForm, say WebForm1.aspx, VS.net
automatically creates a code behind class named WebForm1.aspx.[cs|vb] inherited
by the .aspx web page. The code behind class, in turn, inherits directly from
System.Web.UI.Page . If you create another
web form, say WebFrom2.aspx, then WebForm2.aspx will be inherited from the code
behind class WebForm2.aspx.[cs|vb], and so on. What if WebForm1 and WebForm2
use or have something in common? For example, both pages may need the same
SqlConnection, need to access resources from
the same ResourceManager, or call the same user
defined method.
One solution is inheritance. In the following example, I will demonstrate the
use of base class to promote code sharing. The code shown in this
article is in C#, but attached samples contain both VB and C#.
InheritanceSample is created as an ASP.NET web application using VS.NET.
Two web forms are added: WebForm1 and WebForm2.
In addition, I added a C# class MyBasePage to
serve as base class for both WebForm1.aspx.cs and WebForm2.aspx.cs. MyBasePage
is derived from System.Web.UI.Page. It contains
one member variable, SharedString, and one member
method, SharedMethod. SharedString
is initialized to the FilePath of the requested web form, and SharedMethod simply
returns the string "Hello World". When WebForm1.aspx is called, the result is:
Hello World from /InheritanceSample/WebForm1.aspx
Similarly, when WebForm2.aspx is called, the result is:
Hello World from /InheritanceSample/WebForm2.aspx
|
InheritanceSample
[MyBasePage.cs]
using System;
namespace InheritanceSample
{
public class MyBasePage: System.Web.UI.Page
{
protected String SharedString;
public MyBasePage()
{
// cannot define SharedString here as Request is not initialized yet.
// SharedString = Request.FilePath;
}
override protected void OnInit(EventArgs e)
{
SharedString = Request.FilePath;
base.OnInit(e);
}
protected String SharedMethod()
{
return "Hello World ";
}
}
}
[WebForm1.aspx.cs (WebForm2.aspx.cs is the same except replacing all occurrences of WebForm1 with WebForm2)]
using System;
namespace InheritanceSample
{
public class WebForm1 : MyBasePage
{
}
}
[WebForm1.aspx (WebForm2.aspx is the same except replacing all occurrences of WebForm1 with WebForm2)]
<%@ Page Language="cs" AutoEventWireup= "false"< BR>
Codebehind= "WebForm1.aspx.cs" Inherits="InheritanceSample.WebForm1"%>
<html>
<head>
<title>WebForm1</title>
</head>
<body>
<%=SharedMethod()%>
from
<%=SharedString%>
</body>
</html>
|
Notes:
-
The base class
MyBasePage
is derived from
System.Web.UI.Page. No .aspx page needs to be
associated with
MyBasePage.
-
The codebehind classes have to be derived from
MyBasePage instead of the default
System.Web.UI.Page.
-
A variable whose initialization relies on object Request, Response,
Application or Server cannot be initialized in the constructor of class
MyBasePage. It can be initialized in the
overridden method
OnInit, however.
-
In order for the subclass to use the shared member, the member needs to be
declared as
public,
protected
or
protected internal.
The solution applies to any number of web forms. In APS.NET, there is a new
web object type besides web form: web user control. Web user control is a user
defined servlet that derives from .NET Framework class System.Web.UI.UserControl.
The class hierarchy is:
System.Object
System.Web.UI.Control
System.Web.UI.TemplateControl
System.Web.UI.UserControl
The attempt to share code between a web form class and a web user control
class is a natural extension to the issue we addressed in InheritanceSample.
But it turns out to be more challenging. The challenge arises from the fact
that you cannot define a common base class for a web form and a web user
control. This is because unlike C++, a C# class can be derived
directly from at most one superclass. It is not possible to define a
class like
MyBasePage
that inherits from both System.Web.UI.Page
and
System.Web.UI.UserControl, which are
both siblings of
System.Web.UI.TemplateControl. This is not a
problem in C++, however, because C++ allows multiple inheritance. You may
tempt to use interface to get around with C# language barrier, as C# allows a
class to implement multiple interfaces. But our goal is to share
implementation code, and defining implementation in an interface in not
allowed.
Fortunately, containment is at our disposal. One may argue that inheritance and
containment serve different purposes (is-a vs.
has-a), such distinction often gets blurred. From the point of code
sharing, either way may work. When we cannot resort to multiple
inheritance, we don't have much choice but to ignore the subtle
differences and take whichever way that works.
In the following ContainmentSample, instead of creating
WebForm2
, I created a
WebUserControl1
and have it included in
WebForm1
. The codebehind class of
WebUserControl1
is derived from
MyBaseWebUserControl. Inheritance structure for
WebForm1
is the similar to the case in InheritanceSample. The
difference is that I moved implementation of
SharedString
and method
SharedMethod to class
CommonClass. An object of type
CommonClass is contained by both
MyBaseWebUserControl and
MyBasePage.
SharedString
and
SharedMethod in both of the
MyBaseXXX
classes simply delegate to
CommonClass. When
WebForm1 is requested, the output is:
Hello World in WebUserControl1 from /ContainmentSample/WebForm1.aspx
Hello World in WebForm1 from /ContainmentSample/WebForm1.aspx
|
ContainmentSample
[CommonClass.cs]
using System;
using System.Web;
namespace ContainmentSample
{
public class CommonClass
{
public String SharedString;
public CommonClass(HttpRequest Request)
{
SharedString = Request.FilePath;
}
public String SharedMethod()
{
return "Hello World ";
}
}
}
[MyBasePage.cs]
using System;
namespace ContainmentSample
{
public class MyBasePage: System.Web.UI.Page
{
protected internal String SharedString;
CommonClass cc; // private
override protected void OnInit(EventArgs e)
{
cc = new CommonClass(Request);
SharedString = cc.SharedString;
base.OnInit(e);
}
protected String SharedMethod()
{
return cc.SharedMethod();
}
}
}
[MyBaseWebUserControl.cs]
using System;
namespace ContainmentSample
{
public abstract class MyBaseWebUserControl: System.Web.UI.UserControl
{
protected internal String SharedString;
CommonClass cc;
override protected void OnInit(EventArgs e)
{
cc = new CommonClass(Request);
SharedString = cc.SharedString;
base.OnInit(e);
}
protected String SharedMethod()
{
return cc.SharedMethod();
}
}
}
[WebUserControl1.ascx.cs (WebForm1.aspx.cs is the
same as in previous InheritanceSample , except for
namespace]
namespace ContainmentSample
{
public abstract class WebUserControl1 : MyBaseWebUserControl
{
}
}
[WebUserControl1.ascx]
<%@ Control Language="c#"
AutoEventWireup="false"
Codebehind="WebUserControl1.ascx.cs"
Inherits="ContainmentSample.WebUserControl1"%>
<%=SharedMethod
()%>
in
WebUserControl1 from
<%=SharedString
%>
[WebForm1.aspx]
<%@ Page language="c#"
Codebehind="WebForm1.aspx.cs"
AutoEventWireup="false"
Inherits="ContainmentSample.WebForm1" %>
<%@ Register
TagPrefix="uc1"
TagName="UC"
Src="WebUserControl1.ascx" %>
<HTML>
<HEAD>
<title>WebForm1</title>
</HEAD>
<body>
<p>
<uc1:UC
id="UC"
runat="server"></uc1:UC>
</p>
<p>
<%=SharedMethod
()%>
in
WebForm1 from
<%=SharedString
%>
</p>
</body>
</HTML>
|
To summarize, ASP.NET allows us to adopt an object-oriented approach to manage
the complexity of web application. We examined two solutions to achieve the
goal of code sharing under the constraint of C#: inheritance and
containment. Because ASP.NET is backward compatible to ASP,
programmers may continue using Server-Side Inclusion to promote code
sharing.