您的位置:首页 > 其它

Embedded UserControls: Revisited

2007-07-09 13:32 204 查看
Download demo project and source files - 39.1 Kb

http://www.codeproject.com/aspnet/EmbeddedUserControl.asp

Introduction

Visual Studio 2005 Service Pack 1 restores support for ASP.NET Web Projects, making it once again possible to compile ASPX and ASCX pages into a single assembly. Combined with design-time WYSIWYG support and clever use of resource embedding, it is possible to leverage the ASP.NET
UserControl
technology to create web control libraries that are as flexible and robust as first-class custom controls.

Background

The initial release of Visual Studio 2005 dropped support for the "web project", much to the dismay of web developers. In response, a Web Application Project option became available to restore this functionality, but you had to go getit and install it. With the Service Pack 1 release, this functionality was restored and now we're going to take it for a spin!

Prerequisites

Visual Studio 2005 Service Pack 1

Project Setup

To demonstrate the power of embedded controls, we need to create two projects:

A control library
A test web site

You can use the project stub attached to this project, or if you prefer, you can setup your own project from scratch. When you finish, your solution layout should have one web project for testing and one web project that will contain your embedded controls.



To start from scratch, create a new ASP.NET Web Application project. Notice the difference here - we choose New > Project.





Since we're creating an assembly, remove Default.aspx and web.config. This will leave you with an empty project.



Create VirtualPathProvider.cs or copy it from the sample project.


Collapse
using System.Web.Hosting;
using System.Web.Caching;
using System.Collections;
using System;
using System.IO;
using System.Web;
using System.Reflection;
namespace ControlLibrary
{
public class AssemblyResourceProvider : VirtualPathProvider
{
string mResourcePrefix;
public AssemblyResourceProvider()
:this("EmbeddedWebResource")
{
}
public AssemblyResourceProvider(string prefix)
{
mResourcePrefix = prefix;
}
private bool IsAppResourcePath(string virtualPath)
{
String checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
return checkPath.StartsWith("~/"+mResourcePrefix+"/",
StringComparison.InvariantCultureIgnoreCase);
}
public override bool FileExists(string virtualPath)
{
return (IsAppResourcePath(virtualPath) ||
base.FileExists(virtualPath));
}
public override VirtualFile GetFile(string virtualPath)
{
if (IsAppResourcePath(virtualPath))
return new AssemblyResourceVirtualFile(virtualPath);
else
return base.GetFile(virtualPath);
}
public override CacheDependency
GetCacheDependency(string virtualPath,
IEnumerable virtualPathDependencies,
DateTime utcStart)
{
if (IsAppResourcePath(virtualPath))
return null;
else
return base.GetCacheDependency(virtualPath,
virtualPathDependencies, utcStart);
}
}
class AssemblyResourceVirtualFile : VirtualFile
{
string path;
public AssemblyResourceVirtualFile(string virtualPath)
: base(virtualPath)
{
path = VirtualPathUtility.ToAppRelative(virtualPath);
}
public override Stream Open()
{
string[] parts = path.Split('/');
string assemblyName = parts[2];
string resourceName = parts[3];
assemblyName = Path.Combine(HttpRuntime.BinDirectory,
assemblyName);
Assembly assembly = Assembly.LoadFile(assemblyName);
if (assembly == null) throw new Exception("Failed to load " +
assemblyName);
Stream s = assembly.GetManifestResourceStream(resourceName);
if (s == null) throw new Exception("Failed to load " +
resourceName);
return s;
}
}
}

Points of interest

Note the constructor takes a configurable prefix. This differs from the original code.
Note the exceptions thrown if the assembly or resource name is not found. This differs from the original code.

Create or copy the EmbeddedUserControlLoader.cs class. This is the one that gives us VS2005 toolbox support.


Collapse
using System.Web.UI.WebControls;
using System.Web.UI;
using System;
using System.ComponentModel;
using System.Web;
namespace ControlLibrary
{
[ToolboxData("<{0}:EmbeddedUserControlLoader runat=server>
</{0}:EmbeddedUserControlLoader>")]
public class EmbeddedUserControlLoader : WebControl
{
# region VirtualPathProvider setup code
staticEmbeddedUserControlLoader()
{
if (!IsDesignMode)
{
System.Web.Hosting.HostingEnvironment.
RegisterVirtualPathProvider(
new AssemblyResourceProvider(ResourcePrefix));
}
}
staticbool IsDesignMode
{
get
{
return HttpContext.Current == null;
}
}
staticstring mResourcePrefix = "EmbeddedWebResource";
public staticstring ResourcePrefix
{
get
{
return mResourcePrefix;
}
set
{
mResourcePrefix = value;
}
}
#endregion
#region Toolbox properties
private string mAssemblyName = "";
[Bindable(true)]
[Category("Behavior")]
[Localizable(true)]
public string AssemblyName
{
get{ return mAssemblyName; }
set{ mAssemblyName = value; }
}
private string mControlNamespace = "";
[Bindable(true)]
[Category("Behavior")]
[Localizable(true)]
public string ControlNamespace
{
get{ return mControlNamespace; }
set{ mControlNamespace = value; }
}
private string mControlClassName = "";
[Bindable(true)]
[Category("Behavior")]
[Localizable(true)]
public string ControlClassName
{
get{ return mControlClassName; }
set{ mControlClassName = value; }
}
#endregion
#region Private members
string Path
{
get
{
return String.Format("/{0}/{1}.dll/{2}.{3}.ascx",
ResourcePrefix, AssemblyName, ControlNamespace,
ControlClassName);
}
}
Control c;
protected override void OnInit(EventArgs e)
{
c = Page.LoadControl(Path);
Controls.Add(c);
base.OnInit(e);
}
protected override void RenderContents(HtmlTextWriter output)
{
if (IsDesignMode)
{
output.Write(Path);
return;
}
base.RenderContents(output);
}
#endregion
#region Helper members to access UserControl properties
public void SetControlProperty(string propName, object value)
{
c.GetType().GetProperty(propName).SetValue(c, value, null);
}
public object GetControlProperty(string propName)
{
return c.GetType().GetProperty(propName).GetValue(c, null);
}
#endregion
}
}

Points of interest

Static method

Registering a virtual path has a special sequence tied to it. Microsoft recommends registering all virtual paths "before any page parsing or compilation is performed by the Web application" and that registering them later will not affect pages that are compiled and cached.
In web portal settings, you do not always have access to
Application_Start()
. Therefore, I prefer an alternative that initializes the provider within the user code. In my testing I didn't have any problem registering in the
static
constructor of
EmbeddedUserControlLoader
because that executes before any instance of the class executes, therefore the virtual path is configured in time for
OnInit
to utilize it. For more information, read about VirtualPathProvider on MSDN.

 

Static prefix property
Your control library must implement a unique prefix for the virtual paths to work. This property allows you to configure the prefix at runtime.

Get
/
Set
methods
This class allows you to
get
and
set
properties on the underlying control. More on that later.

Toolbox design-time properties
You can specify the assembly name, namespace, and control name to load at design-time. More on that later.

As a final step, create your test web site. This can be either a new ASP.NET Web Project or a traditional VS2005 Website project. Keep the default.aspx and web.config, and be sure to add a reference to your
ControlLibrary
assembly.

Creating and Embedding Your First UserControl

With our solution stub in hand, we are ready to begin! Let's create a Hello World
UserControl
to demonstrate.

Create a new
UserControl
(named
HelloControl
) in your
ControlLibrary
project. Put some text in the
UserControl
.

Then - and this is the key step - setthe
Build Action
on the ASCX file to
Embedded Resource
. That's it! You are done. Compile. Your ASCX file is now embedded in ControlLibrary.dll.



Now turn to your test site where you have included a reference to
ControlLibrary
. Open Default.aspx and drag an
EmbeddedUserControlLoader
control from the VS2005 toolbox to your page. You can check HelloControl.ascx.cs in
ControlLibrary
for the appropriate values for
AssemblyName
,
ControlClassName
, and
ControlNamespace
. As you change them, the control's design-time representation will display the path to the embedded resource that it intends to load.



Run Default.aspx and be amazed. Your user control loads flawlessly, directly from the
ControlLibrary
assembly!



Now that you've done something basic, let's take a look at a few more complicated scenarios.

Advanced Technique: Nested Controls

Your embedded
UserControls
can be nested. This works so well that I'll leave it up to you. Just drop
EmbeddedUserControlLoader
objects in the
UserControls
you create in the
ControlLibrary
project. You can nest your controls all day long.

Advanced Technique: Property Accessors

You can use
EmbeddedUserControlLoader
to access properties of your
UserControls
. This is worth understanding in detail because it is the only thing that separates your controls from feeling like genuine custom controls.

Let's make a feedback control. We want to collect some basic information and expose it through properties.



FeedbackControl.ascx

<%@ Control Language="C#" AutoEventWireup="true"
Codebehind="FeedbackControl.ascx.cs"
Inherits="ControlLibrary.FeedbackControl" %>
<h1>
Your Feedback is <span style="font-size: 5pt">not</span> Valuable</h1>
Name:
<asp:TextBox ID="txtName" runat="server"></asp:TextBox><br />
Subject:
<asp:TextBox ID="txtSubject" runat="server"></asp:TextBox><br />
Message:<br />
<asp:TextBox ID="txtMessage" runat="server" Height="256px"
TextMode="MultiLine" Width="353px"></asp:TextBox>

FeedbackControl.ascx.cs

 


Collapse
using System;
using System.Data;
using System.Configuration;
using System.Collections;
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;
namespace ControlLibrary
{
public partial class FeedbackControl : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
public string Name
{
get
{
return this.txtName.Text;
}
}
public string Subject
{
get
{
return this.txtSubject.Text;
}
}
public string Message
{
get
{
return this.txtMessage.Text;
}
}
}
}

Now, let's make a feedback form that collects the information. Rather than emailing it, we will just display it to the user at postback.



Feedback.aspx


Collapse
<%@ Page Language="C#" AutoEventWireup="true"
Codebehind="Feedback.aspx.cs" Inherits="DemoSite.Feedback" %>
<%@ Register Assembly="ControlLibrary" Namespace="ControlLibrary"
TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<cc1:EmbeddedUserControlLoader ID="EmbeddedUserControlLoader1"
runat="server" AssemblyName="ControlLibrary"
ControlClassName="FeedbackControl"
ControlNamespace="ControlLibrary">
</cc1:EmbeddedUserControlLoader>
<br />
<br />
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click"
Text="Submit Feedback" /><br />
<br />
<asp:Panel ID="Panel1" runat="server" Height="244px"
Visible="False" Width="383px">
Name:
<asp:Label ID="lblName" runat="server" Text="Label">
</asp:Label><br />
Subject:
<asp:Label ID="lblSubject" runat="server" Text="Label">
</asp:Label><br />
Message:<br />
<asp:Label ID="lblMessage" runat="server" Text="Label">
</asp:Label></asp:Panel>
 </div>
</form>
</body>
</html>

Feedback.aspx.cs


Collapse
using System;
using System.Data;
using System.Configuration;
using System.Collections;
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;
namespace DemoSite
{
public partial class Feedback : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
this.Panel1.Visible = true;
this.lblName.Text =
this.EmbeddedUserControlLoader1.GetControlProperty("Name")
as string;
this.lblSubject.Text=
this.EmbeddedUserControlLoader1.GetControlProperty("Subject")
as string;
this.lblMessage.Text =
this.EmbeddedUserControlLoader1.GetControlProperty("Message")
as string;
}
}
}

And there you have it. You have accessed your properties almost as easily as you can do with custom controls.



Advanced Technique: Custom Control Wrappers

Ok, some of you may not be satisfied with
EmbeddedUserControlLoader.GetPropertyValue()
and I can appreciate that. For some situations, such as if you are distributing your control commercially, you want full design-time support available in Visual Studio 2005.

Let's approach that by creating a light-weight, custom control wrapper for the feedback control. Create a custom control FeedbackControlWrapper.cs in the
ControlLibrary
project.


Collapse
using System.Web.UI;
namespace ControlLibrary
{
[ToolboxData("<{0}:FeedbackControlWrapper runat=server>
</{0}:FeedbackControlWrapper>")]
public class FeedbackControlWrapper : EmbeddedUserControlLoader
{
public FeedbackControlWrapper()
{
ControlClassName = "FeedbackControl";
AssemblyName = "ControlLibrary";
ControlNamespace = "ControlLibrary";
}
public string Name
{
get
{
return GetControlProperty("Name") as string;
}
}
public string Subject
{
get
{
return GetControlProperty("Subject") as string;
}
}
public string Message
{
get
{
return GetControlProperty("Message") as string;
}
}
}
}

Points of interest

The wrapper is a genuine custom control that uses the same property accessor technique shown above in Feedback.aspx.cs, but hides it from the end user of your control. Notice that the
ToolboxData
control prefix is
FeedbackControlWrapper
. It should match the class name and should be different than the
UserControl
class name. (An alternate naming convention is to save the
SomeControl
name pattern for the wrapper names and use a different pattern for the underlying
UserControls
that nobody else will ever see).
Creating a palatable design-time rendering of your control is beyond the scope of this article, but
EmbeddedUserControlLoader
demonstrates what you would need to do to create a design-time rendering. In general,
HttpContext
is not available and must be simulated. Any commercial control must deal with the issue of design-time rendering better than I have in this article!
You can add properties to the wrapper that will appear in the VS2005 property designer box. Search around for your favorite article demonstrating how to expose properties of custom controls at design-time.

The new custom control feedback page is nearly identical to the earlier example. However this time, we drop our custom control from the VS2005 toolbox and change our code-behind to something a little more familiar.



FeedbackCustomControl.aspx.cs


Collapse
using System;
using System.Data;
using System.Configuration;
using System.Collections;
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;
namespace DemoSite
{
public partial class FeedbackCustomControl : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
this.Panel1.Visible = true;
this.lblName.Text = this.FeedbackControlWrapper1.Name;
this.lblSubject.Text = this.FeedbackControlWrapper1.Subject;
this.lblMessage.Text = this.FeedbackControlWrapper1.Message;
}
}
}

Success, and a much more professional architecture!

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: