1. Trang chủ
  2. » Công Nghệ Thông Tin

Professional C# Third Edition phần 8 potx

140 866 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 140
Dung lượng 3,62 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

We just need to make the following changes to thecode in WebForm1.aspx, which simply references the newly created control library and embeds thedefault control in this library into the p

Trang 1

We use the TagPrefixoption in the same way as before, but we don’t use the TagNameor Srcattributes.This is because the custom control assembly we use may contain several custom controls, and each ofthese will be named by its class, so TagNameis redundant In addition, since we can use the dynamic dis-covery capabilities of.NET Framework to find our assembly we simply have to name it and the names-pace in it that contains our controls.

In the previous line of code, we are instruct the program to use an assembly called PCSCustomWebControls.dllwith controls in the PCSCustomWebControlsnamespace, and use the tag prefix PCS If

we have a control called Control1in this namespace we could use it with the ASP.NET code:

<PCS:Control1 Runat=”server” ID=”MyControl1”/>

With custom controls it is also possible to reproduce some of the control nesting behavior that exists inlist controls:

<asp:DropDownList ID=”roomList” Runat=”server” Width=”160px”>

<asp:ListItem Value=”1”>The Happy Room</asp:ListItem>

<asp:ListItem Value=”2”>The Angry Room</asp:ListItem>

<asp:ListItem Value=”3”>The Depressing Room</asp:ListItem>

<asp:ListItem Value=”4”>The Funked Out Room</asp:ListItem>

</asp:DropDownList>

We can create controls that should be interpreted as being children of other controls in a very similarway to this We’ll discuss how to do this later in this chapter

Custom Control Project Configuration

Let’s start putting some of this theory into practice We’ll use a single assembly to hold all of the examplecustom controls in this chapter for simplicity, which we can create in Visual Studio NET by choosing anew project of type Web Control Library We’ll call our library PCSCustomWebControls, as shown inFigure 27-5

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 2

Here I have created the project in C:\ProCSharp\CustomControls There is no need for the project to becreated on the Web server as with Web applications, since it doesn’t need to be externally accessible inthe same way Of course, we can create Web control libraries anywhere, as long as we remember to copythe generated assembly somewhere where the Web application that uses it can find it!

One technique we can use to facilitate testing is to add a Web application project to the same solution.We’ll call this application PCSCustomWebControlsTestApp For now, this is the only application thatwill use our custom control library, so to speed things up a little we can make the output assembly forour library be created in the correct bin directory (this means that we don’t have to copy the file acrossevery time we recompile) We can do this through the property pages for the PCSCustomWebControlsproject (see Figure 27-6)

Figure 27-6

Note that we have changed the Configuration dropdown to All Configurations, debug and release buildoutputs will be placed in the same place The Output Path has been changed to C:\Inetpub\wwwroot\PCSCustomWebControlsTestApp\bin To make debugging easier we can also change the Debug Mode option on the Debugging property page to URL and the Start URL to, http://localhost/PCSCustomWebControlsTestApp/WebForm1.aspxso we can just execute our project in debug mode

to see our results

We can make sure that this is all working by testing the control that is supplied by default in the cs file

of our custom control library, WebCustomControl1.cs We just need to make the following changes to thecode in WebForm1.aspx, which simply references the newly created control library and embeds thedefault control in this library into the page body:

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 3

<%@ Page language=”c#” Codebehind=”WebForm1.aspx.cs” AutoEventWireup=”false”

<form id=”Form1” method=”post” runat=”server”>

<PCS:WebCustomControl1 ID=”testControl” Runat=”server”

to the PCSCustomWebControls.dll assembly, load it, and then choose controls from the list, as shown inFigure 27-7

Figure 27-7

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 4

Select WebCustomControl1 as shown in Figure 27-7 to display the new control in the Toolbox, ready foradding to our form (see Figure 27-8).

Figure 27-8

The nice thing about this is that if we add the control from the Toolbox both a project reference toPCSCustomWebControlsand the <%@ Register %>directive are added automatically to the control.The tag prefix for the control library is also assigned automatically (in our example: cc1) This is fine,although doing this ourselves gives us greater flexibility that could improve code readability In the rest

of the code in this chapter we’ll assume that the prefix PCS is used for controls fromPCSCustomWebControls

Note that the prefix used for Toolbox items can be controlled in the code for the control, by adding the following attribute to the code for the control:

[assembly: TagPrefix(“WebCustomControl1”, “PCS”)]

One thing that isn’t added for us is a usingstatement to our PCSCustomWebControlsTestApppace in WebForm1.aspx.cs Rather than use fully qualified names for our controls we can add the usingstatement ourselves:

Figure 27-9

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 5

Basic Custom Controls

As can be inferred from the results in the previous section, the sample control generated by default issimply a version of the standard <asp:Label>control The code generated in the cs file for the project,WebCustomControl1.cs, is as follows (omitting the standard and XML documentation comments): using System;

{private string text;

[Bindable(true), Category(“Appearance”), DefaultValue(“”)]

public string Text{

get{return text;

}set{text = value;

}}protected override void Render(HtmlTextWriter output){

output.Write(Text);

}}}

The single class defined here is the WebCustomControl1class (note how the class name mappeddirectly to an ASP.NET element in the simple example we saw before), which is derived from theWebControlclass as discussed earlier Two attributes are provided for this class: DefaultPropertyandToolboxData The DefaultPropertyattribute specifies what the default property for the control will

be if used in languages that support this functionality The ToolboxDataattribute specifies exactly whatHTML will be added to an aspx page if this control is added using the Toolbox (as we discussed earlier,once the project is compiled we can add the control to the toolbox by configuring the toolbox to use theassembly created) Note that a {0}placeholder is used to specify where the tag prefix will be placed.The class contains one property: Text This is a very simple text property much like those we’ve seenbefore The only point to note here is the three attributes:

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 6

❑ Bindable, which specifies whether the property can be bound to data.

❑ Category, which specifies where the property will be displayed in the property pages

❑ DefaultValue, which specifies the default value for the property

Exposing properties in this way works in exactly the same way as it did for custom controls, and is nitely preferable to exposing public fields

defi-The remainder of the class consists of the Render()method This is the single most important method

to implement when designing custom controls, as it is where we have access to the output stream to play our control content There are only two cases where we don’t need to implement this method:

dis-❑ When we are designing a control that has no visual representation (usually known as

❑ Create a derived control

❑ Create a composite control

❑ Create a more advanced control

The final example is a straw poll control, capable of allowing the user to vote for one of several dates, and displaying voting progress graphically Options are defined using nested child controls, in themanner described earlier

candi-We’ll start with a simple derived control

The RainbowLabel derived control

For this first example we’ll derive a control from a Labelcontrol and override its Render()method tooutput multicolored text To keep the code for the sample controls in this chapter separate, we’ll createnew source files as necessary, so for this control add a new cs code file called RainbowLabel.cs to thePCSCustomWebControls project and add the following code:

public class RainbowLabel : System.Web.UI.WebControls.Label{

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 7

private Color[] colors = new Color[] {Color.Red, Color.Orange,

Color.Yellow,Color.GreenYellow,Color.Blue, Color.Indigo,Color.Violet};

protected override void Render(HtmlTextWriter output){

string text = Text;

for (int pos=0; pos < text.Length; pos++){

int rgb = colors[pos % colors.Length].ToArgb() & 0xFFFFFF;

output.Write(“<font color=’#” + rgb.ToString(“X6”) + “‘>”

+ text[pos] + “</font>”);

}}}}

This class derives from the existing Labelcontrol (System.Web.UI.WebControls.Label) and doesn’trequire any additional properties, because the inherited Textone will do fine We have added a new pri-vate field, colors[], which contains an array of colors that we’ll cycle through when we output text.The main functionality of the control is in Render(), which we have overridden, because we want tochange the HTML output Here we get the string to display from the Textproperty and display eachcharacter in a color from the colors[]array

To test this control we add it to the form in PCSCustomWebControlsTestApp:

<form method=”post” runat=”server” ID=”Form1”>

<PCS:RainbowLabel Runat=”server” Text=”Multicolored label!”

ID=”rainbowLabel1”/>

</form>

Figure 27-10

Maintaining state in custom controls

Each time a control is created on the server in response to a server request it is created from scratch Thismeans that any simple field of the control will be reinitialized In order for controls to maintain statebetween requests they must use the ViewStatemaintained on the client, which means we need to write

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 8

To illustrate this, we’ll add an additional capability to the RainbowLabelcontrol We’ll add a methodcalled Cycle()that cycles through the colors available, which will make use of a stored offset field todetermine which color should be used for the first letter in the string displayed This field must use theViewStateof the control in order to be persisted between requests

We’ll show the code for both with and without ViewStatestorage cases to demonstrate how easy it is tomake an error that results in a non-persistent control First we’ll look at code that fails to make use ofthe ViewState:

public class RainbowLabel : System.Web.UI.WebControls.Label{

private Color[] colors = new Color[] {Color.Red, Color.Orange,

Color.Yellow,Color.GreenYellow,Color.Blue, Color.Indigo,Color.Violet};

private int offset = 0;

protected override void Render(HtmlTextWriter output){

string text = Text;

for (int pos=0; pos < text.Length; pos++){

int rgb = colors[(pos + offset) % colors.Length].ToArgb()

& 0xFFFFFF;

output.Write(“<font color=’#” + rgb.ToString(“X6”) + “‘>”

+ text[pos] + “</font>”);

}}public void Cycle(){

offset = ++offset;

}}Here we initialize the offset field to zero, then allow the Cycle()method to increment it, using the %operator to ensure that it wraps round to 0 if it reaches 7 (the number of colors in the colors array)

To test this we need a way of calling Cycle(), and the simplest way to do that is to add a button to our form:

<form method=”post” runat=”server” ID=”Form1”>

<PCS:RainbowLabel Runat=”server” Text=”Multicolored label!”

Trang 9

protected void cycleButton_Click(object sender, System.EventArgs e){

In any case, the solution is quite simple We have to use the ViewStateproperty bag of our control tostore and retrieve data We don’t have to worry about how this is serialized, recreated, or anything else

We just put things in and take things out, safe in the knowledge that state will be maintained betweenrequests in the standard ASP.NET way

To place the offset field into ViewStatewe simply use:

If we do this when nothing is stored in the ViewStateunder that name we will get a null value Casting

a nullvalue in this code will throw an exception, so we can either test for this or check whether theobject type retrieved from ViewStateis nullbefore we cast it, which is what we’ll do in our code

In fact, we can update our code in a very simple way by replacing the existing offset member with a vate offset property that makes use of ViewState, with code as follows:

pri-public class RainbowLabel : System.Web.UI.WebControls.Label{

private int offset{

get{object rawOffset = ViewState[“_offset”];

if (rawOffset != null){

return (int)rawOffset;

}else{

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 10

ViewState[“_offset”] = 0;

return 0;

}}set{ViewState[“_offset”] = value;

}}

}This time, the control allows the Cycle()method to work each time

In general, we might see ViewStatebeing used for simple public properties, for example:

public string Name{

get{return (string)ViewState[“_name”];

}set{ViewState[“_name”] = value;

}}One further point about using ViewStateconcerns child controls If our control has children and is usedmore than once on a page, then we have the problem that the children will share their ViewStatebydefault In almost every case this isn’t the behavior we’d like to see, and luckily we have a simple solu-tion By implementing INamingContaineron the parent control we force child controls to use qualifiedstorage in ViewState, such that child controls will not share their ViewStatewith similar child controlswith a different parent

Using this interface doesn’t require any property or method implementation, we just have to use it, as if

it were simply a marker for interpretation by the ASP.NET server, as discussed in the following sections

Creating a Composite Custom Control

As a simple example of a composite custom control, we can combine the control from the previous tion with the cycle button we have in the test form

sec-We’ll call this composite control RainbowLabel2, and place it in a new file, RainbowLabel2.cs This trol needs to:

con-❑ Inherit from WebControl(not Label)

❑ Support INamingContainer

❑ Possess two fields to hold its child controls

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 11

To fulfill these three requirements, we must modify the code obtained by generating a new class file:using System;

private RainbowLabel rainbowLabel = new RainbowLabel();

private Button cycleButton = new Button();

In order to configure a composite control we have to ensure that any child controls are added to theControlscollection and properly initialized We do this by overriding the CreateChildControls()method and placing the required code there (here we should call the base CreateChildControls()implementation, which won’t affect our class but may prevent unexpected surprises):

protected override void CreateChildControls(){

cycleButton.Text = “Cycle colors.”;

cycleButton.Click += new System.EventHandler(cycleButton_Click);

Controls.Add(cycleButton);

Controls.Add(rainbowLabel);

base.CreateChildControls();

}Here we just use the Add()method of Controlsto get things set up correctly We’ve also added anevent handler for the button so that we can make it cycle colors The handler is the now familiar:

protected void cycleButton_Click(object sender, System.EventArgs e){

rainbowLabel.Cycle();

}This call makes the label colors cycle

To give users of our composite control access to the text in the rainbowLabelchild we can add a erty that maps to the Textproperty of the child:

prop-public string Text{

get{return rainbowLabel.Text;

}set{rainbowLabel.Text = value;

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 12

The last thing to do is to implement Render() The base implementation of this method takes each trol in the Controlscollection of the class and tells it to render itself Since Render()is a protectedmethod, it doesn’t call Render()for each of these controls; instead it calls the public methodRenderControl() This has the same effect, because RenderControl()calls Render(), so we don’thave to change any more code in the RainbowLabelclass To get more control over this rendering (forexample in composite controls that output HTML around that generated by child controls) we can callthis method ourselves:

con-protected override void Render(HtmlTextWriter output){

We can use this control in much the same way as RainbowLabel:

<form method=”post” runat=”server” ID=”Form1”>

<PCS:RainbowLabel2 Runat=”server”

Text=”Multicolored label composite”

ID=”rainbowLabel2”/>

</form>

This time a button to cycle the colors is included

A Straw Poll Control

Next we’ll use and build on the techniques we’ve covered so far to make a more involved custom trol The end result of this will enable the following ASP.NET code to give us the result shown in Figure27-11:

con-<form method=”post” runat=”server” ID=”Form1”>

<PCS:StrawPoll Runat=”server” ID=”strawPoll1”

PollStyle=”voteonly”

Title=”Who is your favorite James Bond?”>

<PCS:Candidate Name=”Sean Connery” Votes=”101”/>

<PCS:Candidate Name=”Roger Moore” Votes=”83”/>

<PCS:Candidate Name=”George Lazenby” Votes=”32”/>

<PCS:Candidate Name=”Timothy Dalton” Votes=”28”/>

<PCS:Candidate Name=”Pierce Brosnan” Votes=”95”/>

</PCS:StrawPoll>

</form>

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 13

The ASP.NET code sets the Nameand Votesproperty for each Candidate This is fine for this example,

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 14

When the ASP.NET code is parsed, structures such as this one are interpreted in a consistent way: eachchild element is interpreted in the way that we specify in a control builder class associated with the par-ent control This control builder, for which we’ll see the code shortly, handles anything nested inside thecontrol it is associated with, including literal text.

The two controls we need to create are Candidateto hold individual candidates, and StrawPoll,which will contain and render the straw poll control Both of these are placed in new source files:Candidate.cs and StrawPoll.cs

The Candidate Controls

To start with, we’ll create our Candidatecontrols, each of which will store a name and the number ofvotes cast for that candidate In addition, these controls will maintain a voting button, and handle anyclicks of this button

For this project, we need:

❑ Code for the Nameand Votesproperties (stored in the ViewState)

❑ Initialization code in CreateChildControls()

❑ Code for our button click handler

We’ll also include a utility method, Increment(), which will add a vote to the current vote count for theCandidate instance This utility method will be called by the button click handler

We’ll also need to support INamingContainer, because we’ll have multiple instances of these controlswith their own children

The code for the Candidateclass goes in Candidate.cs, which we should add to our project along withthe standard namespaceand usingstatements as per the RainbowLabelcontrols we saw earlier Thecode is as follows:

public class Candidate : System.Web.UI.WebControls.WebControl,

INamingContainer{

public string Name{

get{object rawName = ViewState[“_name”];

if (rawName != null){

return (string)rawName;

}else{ViewState[“_name”] = “Candidate”;

return “Candidate”;

}}set{

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 15

ViewState[“_name”] = value;

}}public long Votes{

get{object rawVotes = ViewState[“_votes”];

if (rawVotes != null){

return (long)rawVotes;

}else{ViewState[“_votes”] = (long)0;

return 0;

} }set{ViewState[“_votes”] = value;

}}public void Increment(){

Votes += 1;

}public void Reset(){

Votes = 0;

}protected override void CreateChildControls(){

Button btnVote = new Button();

Increment();

}}Note that Render()hasn’t been overridden here This is because this control has a single child, the vot-ing button, and no other information to display So, we can just go with the default, which will simply be

a rendering of the button

The StrawPoll Control Builder

Next we’ll discuss how we can translate the ASP.NET code for each option into a control that is a child of

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 16

StrawPoll.cs), using the ControlBuilderAttributeattribute We also need to specify that child trols should not be parsed as properties of the StrawPollclass by setting the ParseChildrenattribute

if (tagName.ToLower().EndsWith(“candidate”))return typeof(Candidate);

return null;

}public override void AppendLiteralString(string s){

// Do nothing, to avoid embedded text being added to control}

}

In this example we override the GetChildControlType()method of the base ControlBuilderclass

to return the type of our Candidateclass in response to a tag named <Candidate> In fact, to makesure things work smoothly in as many situations as possible, we just look for any tag name that endswith the string “candidate”, with letters in upper- or lowercase

We also override the AppendLiteralString()method so that any intervening text, including pace, is ignored and won’t cause us any problems

whites-After this is set up, and assuming we don’t place any other controls in StrawPoll, we will have allCandidatecontrols contained in the Controlscollection of StrawPoll This collection won’t containany other controls

Note that the control builder makes use of a collection of attributes In order to support this we need toadd the following using statement to our namespace:

using System.Collections;

Straw Poll Style

Before we look at the StrawPollclass itself, there is one more design consideration The straw pollshould be able to display itself in one of three forms:

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 17

❑ Voting buttons only

❑ Results only

❑ Voting buttons and results

We can define an enumeration for this that we can use as a property of our StrawPollcontrol (puttingthis in StrawPoll.cs is fine):

public enum pollStyle{

voteonly,valuesonly,voteandvalues}

As we discussed earlier, properties that are enumerations are easy to use—we can simply use the textnames as attribute values in ASP.NET

The Straw Poll Control

Now we can start putting things together First we define two properties, Titlefor the title to displayfor the control, and PollStyleto hold the enumerated display type Both of these properties use theViewStatefor persistence:

[ControlBuilderAttribute(typeof(StrawPollControlBuilder))]

[ParseChildren(false)]

public class StrawPoll : System.Web.UI.WebControls.WebControl,

INamingContainer{

public string Title{

get{object rawTitle = ViewState[“_title”];

if (rawTitle != null){

return (string)rawTitle;

}else{ViewState[“_title”] = “Straw Poll”;

return “Straw Poll”;

}}set{ViewState[“_title”] = value;

}}public pollStyle PollStyle

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 18

{object rawPollStyle = ViewState[“_pollStyle”];

if (rawPollStyle != null){

return (pollStyle)rawPollStyle;

}else{ViewState[“_pollStyle”] = pollStyle.voteandvalues;

return pollStyle.voteandvalues;

}}set{ViewState[“_pollStyle”] = value;

}}}The remainder of this class is taken up with the Render()method This displays the entire straw pollcontrol along with any options, taking into account the poll style to use We’ll display voting buttons bycalling the RenderControl()method of child Candidatecontrols, and display the votes cast graphi-cally and numerically using the Votesproperties of child Candidatecontrols to generate simpleHTML

The code is as follows (commented for clarity):

protected override void Render(HtmlTextWriter output){

iColumns = 3;

}output.Write(“<TABLE border=’1’ bordercolor=’black’”

// Default text when no options containedoutput.Write(“<TR><TD bgcolor=’#FFFFDD’>No options to”

+ “ display.</TR></TD>”);

}else{

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 19

// Get total votesfor (int iLoop = 0; iLoop < Controls.Count; iLoop++){

// Get optioncurrentCandidate = (Candidate)Controls[iLoop];

// Sum votes castiTotalVotes += currentCandidate.Votes;

}// Render each optionfor (int iLoop = 0; iLoop < Controls.Count; iLoop++){

// Get optioncurrentCandidate = (Candidate)Controls[iLoop];

// Place option name in first columnoutput.Write(“<TR><TD bgcolor=’#FFFFDD’ width=’15%’> “

+ currentCandidate.Name + “ </TD>”);

// Add voting option to second column if required

if (PollStyle != pollStyle.valuesonly){

output.Write(“<TD width=’1%’ bgcolor=’#FFFFDD’>”

+ “<FONT color=’#FFFFDD’>.</FONT>”);

currentCandidate.RenderControl(output);

output.Write(“<FONT color=’#FFFFDD’>.</FONT></TD>”);

}// Place graph, value, and percentage in third// column if required

if (PollStyle != pollStyle.voteonly){

if (iTotalVotes > 0){

iPercentage = (currentCandidate.Votes * 100) /

iTotalVotes;

}else{iPercentage = 0;

}output.Write(“<TD bgcolor=’#FFFFDD’>”

output.Write(“<TD bgcolor=’white’ width=’”

+ (100-iPercentage)+ “%’><FONT color=’white’>.”

+ “</FONT></TD></TR></TABLE></TD>”);

output.Write(“<TD width=’75’>”

+ currentCandidate.Votes + “ (“

+ iPercentage+ “%)</TD></TR></TABLE></TD>”);

}

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 20

}// Show total votes cast if values displayed

if (PollStyle != pollStyle.voteonly){

output.Write(“<TR><TD bgcolor=’#FFFFDD’ colspan=’”

+ iColumns+ “‘>Total votes cast: “ + iTotalVotes+ “</TD></TR>”);

}}// Finish tableoutput.Write(“</TABLE>”);

}There is one more thing we have to do If the straw poll is displayed in voteonlymode then votingshould trigger a change of display to valuesonlymode To do this we must modify in the voting but-ton handler in our Candidateclass:

protected void btnVote_Click(object sender, System.EventArgs e){

Increment();

StrawPoll parent = (StrawPoll)Parent;

if (parent.PollStyle == pollStyle.voteonly){

parent.PollStyle = pollStyle.valuesonly;

}}Now you are free to use the ASP.NET code shown at the start of this section to vote for your favoriteJames Bond to your heart’s content!

Adding an event handler

It is often the case with custom controls that you want to raise custom events, and allow users of the trol to act on them This can be used to excellent effect, as is immediately apparent if you look at theexisting server controls that ASP.NET supplies For example, the Calendarcontrol is nothing more than

con-a well-formcon-atted selection of hyperlinks We could build something like thcon-at ourselves using the niques built up in the previous sections However, it has the useful function that when you click a dateother than the selected one it raises a SelectionChangedevent We can act on this event, either ignor-ing it if the selection is OK to change, or performing some processing, which we did in Chapter 26 when

tech-we checked to see if the selected date was already booked In a similar vein, it would be nice if our strawpoll control had a Votedevent, which will notify the form that a vote has been made, and supply it withall the information needed to act on this

To register a custom event we have to add the following code to a control:

public event EventHandler Voted;

protected void OnVoted(EventArgs e){

if (Voted != null)

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 21

{Voted(this, e);

}}Then, whenever we want to raise the event, we simply call OnVoted(), passing the event arguments.Whenever we call OnVoted()an event is raised that the user of the control can act on To do this theuser has to register an event handler for this event:

strawPoll1.Voted += new EventHandler(this.strawPoll1_Voted);

The user also has to provide the handler code, strawPoll1_Voted()

We’ll extend this slightly by having custom arguments for our event, in order to make the Candidatethat triggers the event available We’ll call our custom argument object CandidateEventArgs, defined

in a new class, CandidateEventArgs.cs, as follows:

public class CandidateEventArgs : EventArgs{

public Candidate OriginatingCandidate;

public CandidateEventArgs(Candidate originator){

OriginatingCandidate = originator;

}}We’ve simply added an additional public field to the existing EventArgsclass As we’ve changed thearguments we’re using, we also need a specialized version of the EventHandlerdelegate that can bedeclared in the PCSCustomWebControlsnamespace as follows:

public delegate void CandidateEventHandler(object sender,

CandidateEventArgs e);

We can use these examples in StrawPollas follows:

public class StrawPoll : System.Web.UI.WebControls.WebControl,

INamingContainer{

public event CandidateEventHandler Voted;

protected void OnVoted(CandidateEventArgs e){

if (Voted != null){

Voted(this, e);

}}

We’ll also have a method to raise the event, called from child Candidatecontrols when voting buttonsare clicked:

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 22

internal void ChildVote(CandidateEventArgs e){

parent.PollStyle = pollStyle.valuesonly;

}CandidateEventArgs eCandidate = new CandidateEventArgs(this);

parent.ChildVote(eCandidate);

}Now we’re ready to implement the handler on the page using the control We simply have to specify it

in our ASP.NET page, adding a label to use in the handler:

<form id=Form1 method=post runat=”server”>

<PCS:StrawPoll Runat=”server” ID=strawPoll1 PollStyle=”voteonly”

Title=”Who is your favorite James Bond?”

Voted=”strawPoll1_Voted”>

<PCS:Option Name=”Sean Connery” Votes=”101”/>

<PCS:Option Name=”Roger Moore” Votes=”83”/>

<PCS:Option Name=”George Lazenby” Votes=”32”/>

<PCS:Option Name=”Timothy Dalton” Votes=”27”/>

<PCS:Option Name=”Pierce Brosnan” Votes=”95”/>

Then we add the event handler itself:

protected void strawPoll1_Voted(object sender, CandidateEventArgs e){

resultLabel.Text = “You voted for “

Trang 23

Now when we vote we receive feedback on our vote, as shown in Figure 27-13.

Figure 27-13

Summar y

In this chapter we have looked at the various ways we can create reusable ASP.NET server controlsusing C# We have discussed how to create simple user controls from existing ASP.NET pages, as well ashow to create custom controls from scratch

There is a lot we can do with custom controls; unfortunately, we cannot possibly cover every detail inthis book (For example, it would have been interesting to discuss data-binding, and how to create con-trols with data-binding in mind.) However, with the information in this chapter you should be able tostart building (and experiment with) your own custom controls For more details on this subject, check

out Professional ASP.NET 1.1 (ISBN: 0-7645-5890-0).

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 24

Par t VI: Interop

Chapter 28: COM Interoperability

Chapter 29: Enterprise Services

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 25

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 26

COM Interoperability

If you had written Windows programs before learning NET, usually there is not the time andresources available to rewrite everything with NET Existing functional code will not be rewrittenjust because a new technology is available You might have thousands of lines of existing, runningcode, which would mean too much effort to rewrite this code just to move it into the managedenvironment

The same applies to Microsoft With the namespace System.DirectoryServices, Microsoft hasn’t rewritten the COM objects accessing the hierarchical data store; the classes inside thisnamespace are wrappers accessing the ADSI COM Objects instead The same thing happens withSystem.Data.OleDbwhere the OLE DB providers that are used by classes from this namespace

do have quiet complex COM interfaces

The same issue may apply for your own solutions If you have existing COM objects that should beused from NET applications, or the other way around if you want to write NET components thatshould be used in old COM clients, this chapter will be a starter for using COM interoperability

In this chapter we are going to:

❑ Compare COM and NET technologies

❑ Use COM objects from within NET applications

❑ Use NET components from within COM clients

As is the case with the other chapters, you can download the sample code for this chapter from theWrox Web site at www.wrox.com

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 27

.NET and COM

The major issues using COM interoperability with NET are to know COM Offering NET componentsfor COM clients or using COM objects with NET, that is all problems we had in the last year with COMare [CKS1]coming back So first we discuss how COM compares to NET

If you already have a good grasp of COM technologies, this section may be a refresher to your COMknowledge Otherwise it introduces you to the concepts of COM that—now using NET—we can behappy not to deal with it anymore in our daily business However, all the problems we had with COMstill apply when COM technology is integrated in NET applications

COM and NET do have many similar concepts with very different solutions Here we will discuss:

we had with COM is that the type library is not extensible With C++ IDL (interface definition language)files have been used to describe the interfaces and methods Some of the IDL modifiers cannot be foundinside the type library, because Visual Basic (and the Visual Basic team was responsible for the typelibrary) couldn’t use these IDL modifiers With NET this problem doesn’t exist because the NET meta-data is extensible using custom attributes

As a result of this behavior, some COM components have a type library, and others don’t Where no typelibrary is available, a C++ header file can be used that describes the interfaces and methods With NET it

is easier using COM components that do have a type library, but it is also possible to use COM nents without a type library In that case it is necessary to redefine the COM interface by using C# code

Trang 28

The interface IUnknownwhich is the interface that is required to be implement by every COM object,offers three methods Two of these methods are related to reference counts The method AddRef()must

be called by the client, if another interface pointer is needed; this method increments the reference count.The method Release()decrements the reference count, and if the resulting reference count is 0, theobject destroys itself to free memory

Interfaces

Interfaces are the heart of COM to differ between a contract that is used between the client and theobject, and the implementation The interface (the contract) defines the methods that are offered by thecomponent, and that can be used by the client With NET interfaces play an important part, too

COM distinguishes between three interface types: custom, dispatch, and dual interfaces.

Custom interfaces

Custom interfaces derive from the interface IUnknown A custom interface defines the order of the ods in a virtual table (vtable), so that the client can access the methods of the interface directly This alsomeans that the client needs to know the vtable during development time, as binding to the methodshappen by using memory addresses As a conclusion, custom interfaces cannot be used by scriptingclients Figure 28-1 shows the vtable of the custom interface IMaththat offers the methods Add()andSub()in addition to the methods of the IUnknowninterface

meth-Figure 28-1

Dispatch interfaces

Because a scripting client (and earlier Visual Basic clients) doesn’t support custom interfaces, a differentinterface type is needed With dispatch interfaces, the interface that is available for the client is alwaysthe IDispatchinterface IDispatchderives from IUnknownand offers four methods in addition to theIUnknownmethods The two most important methods are GetIDsOfNames()and Invoke() As shown

in Figure 28-2, with a dispatch interface two tables are needed The first one maps the method or erty name to a dispatch id; the second one maps the dispatch-id to the implementation of the method orproperty

prop-QueryInterfaceAddRefReleaseAddSub

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 29

Figure 28-2

When the client invokes a method in the component, at first it calls the method GetIDsOfNames()ing the name of the method it wants to call GetIDsOfNames()makes a lookup into the name-to-id table

pass-to return the dispatch id This id is used by the client pass-to call the method Invoke()

Usually, the two tables for the IDispatch interface are stored inside the type library, but this is not a requirement, and some components have the tables on other places.

Dual interfaces

As you can imagine, dispatch interfaces are a lot slower compared to custom interfaces On the otherhand, custom interfaces cannot be used by scripting clients A dual interface can solve this dilemma Ascan be seen in Figure 28-3, a dual interface derives from IDispatch, but offers the additional methods ofthe interface directly in the vtable Scripting clients can use the IDispatchinterface to invoke the meth-ods, while clients aware of the vtable can call the methods directly

Figure 28-3

Casting and QueryInterface

If a NET class implements multiple interfaces, casts can be done to get one interface or another WithCOM, the interface IUnknownoffers a similar mechanism with the method QueryInterface() As dis-

QueryInterfaceAddRefReleaseGetTypeInfoCountGetIDsOfNamesInvoke

AddSub

"Add"

"Sub"

4748

47 pAdd

48 pSub

QueryInterfaceAddRefReleaseGetTypeInfoCountGetIDsOfNamesInvoke

"Add"

"Sub"

4748

47 pAdd

48 pSub

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 30

Method Binding

How a client maps to a method is defined with the terms early and late binding Late binding meansthat the method to invoke is looked for during runtime .NET uses the System.Reflectionnamespace

to make this possible (see Chapter 10)

COM uses the IDispatchinterface that has been discussed earlier for late binding Late binding is sible with dispatch and dual interfaces

pos-With COM early binding has two different options One way of early binding that is also known asvtable binding is using the vtable directly—this is possible with custom and dual interfaces The secondoption of early binding is also known as id binding Here the dispatch id is stored inside the client code,

so during runtime only a call to Invoke()is necessary GetIdsOfNames()is done during design time.With such clients it is important to remember that the dispatch id must not be changed

Because such a 128-bit number cannot be easily remembered, many COM objects also do have a prog id.The prog id is an easy-to-remember name such as Excel.Applicationthat just maps to the CLSID.Besides the CLSID, COM objects also do have a unique identifier for each interface (IID), and for thetype library (typelib id)

Later in this chapter we will discuss information in the registry with more detail

Threading

COM uses apartment models to relieve the programmer from threading issues However, this also addssome more complexity Different apartment types have been added with different releases of the operat-ing system We have to discuss the single-threaded apartment and the multi-threaded apartment

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 31

Single-threaded apartment

The single-threaded apartment (STA) was introduced with Windows NT 3.51 With an STA only onethread (the thread that created the instance) is allowed to access the component However, it is legal hav-ing multiple STAs inside one process (see Figure 28-4)

Trang 32

Figure 28-5

A COM object programmed with thread-safety in mind marks the requirement for an MTA in the istry with the key ThreadingModelset to Free The value Bothis used for thread-safe COM objectsthat don’t mind about the apartment type

reg-Visual Basic 6.0 didn’t offer support for multi-threaded apartments.

Error Handling

With NET, errors are generated by throwing exceptions With the older technology COM, errors aredefined by returning HRESULTvalues with the methods AHRESULTvalue of S_OKmeans that themethod was successful

If a more detailed error message is offered by the COM component, the COM component implementsthe interface ISupportErrorInfowhere not only an error message, but also a link to a help file, and thesource of the error, and returns an error information object with the method return Objects that imple-ment ISupportErrorInfoare automatically mapped to more detailed error information with an excep-tion in NET

Trang 33

Event Handling

.NET offers an event-handling mechanism with the C# keywords eventand delegate(see Chapter 6)

In Chapter 16 we discuss that the same mechanism is also available with NET Remoting

Figure 28-6 shows the COM event-handling architecture With COM events, the component has toimplement the interface IConnectionPointContainer, and one more connection point objects (CPO)that implement the interface IConnectionPoint The component also defines an outgoing interface—ICompletedEventsin Figure 28-6—that is invoked by the CPO The client must implement this outgo-ing interface in the sink object, that itself is a COM object During runtime, the client queries the serverfor the interface IConnectionPointContainer With the help of this interface the client asks for a CPOwith the method FindConnectionPoint(), to get a pointer to IConnectionPointreturned This inter-face pointer is used by the client to call the Advise()method where a pointer to the sink object ispassed to the server In turn, the component can invoke methods inside the sink object of the client

Figure 28-6

Later in this chapter we discuss how the NET events and the COM events can be mapped, so that COMevents can be handled by a NET client and vice versa

Marshaling

Data that is passed from NET to the COM component and the other way around must be converted to

the corresponding representation This mechanism is also known as marshaling What happens here depends on the data type of the data that is passed Here we have to differentiate between blittable and

non-blittable data types

Blittable data types have a common representation with both NET and COM, and no conversion isneeded Simple data types such as byte, short, int, long, and classes and arrays that only contain thesesimple data types belong to the blittable data types Arrays must be one-dimensional to be blittable

lConnectionPointContainerlConnectionPoint

lCompletedEvents

Client

Sink

ServerlConnectionPoint

lConnectionPointContainerlConnectionPoint

lCompletedEvents

CPO

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 34

With non-blittable data types a conversion is needed The following table lists some of the non-blittableCOM data types with their NET-related data type Non-blittable types do need more performancebecause of the conversion.

COM Data Type NET Data Type

Using a COM Component from a NET Client

To see how a NET application can use a COM component we first have to create a COM component.Creating COM components is not possible with C# or Visual Basic NET; we need either Visual Basic 6 orC++ (or any other language that supports COM) In this chapter we use the Active Template Library(ATL) and C++

Because this is not a COM book, we will not discuss all aspects of the code We discuss only what we need to build the sample.

Creating a COM Component

To create a COM component with ATL and C++, create a new ATL Project You can find the ATL ProjectWizard within the Visual C++ Projects group when you select File | New | Project Set the name toCOMServer With the Application Settings, select Attributed and Dynamic Link Library, and press Finish

Since Visual Studio NET 2002, the ATL offers attributes that make it easier to build COM server.

These attributes have nothing in common with the NET attributes; instead they are only used with ATL Instead of writing a separate IDL file and a C++ file defining the interface, only a C++ file is needed that also has attributes that are required by COM.

The ATL Project Wizard just created the foundation for the server A COM object is still needed Add a

class in Solution Explorer, and select ATL Simple Object With the dialog that starts up, enter COMDemo

in the field for the Short name The other fields will be filled automatically, but change the interface

name to IWelcome (see Figure 28-7).

The COM component will offer two interfaces, so that you can see how QueryInterface()is mappedfrom NET, and just three simple methods, so that you can see how the interaction takes place In theClass View, select the interface IWelcome, and add the method Greeting()(see Figure 28-7), withthese parameters:

HRESULT Greeting([in] BSTR name, [out, retval] BSTR* message);

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 35

Figure 28-7

Your wizard-generated code from the file COMDemo.hshould look similar to the following code Theunique identifiers (uuids) will differ The interface IWelcomedefines the methods Greeting() Thebrackets before the keyword interfacedefine some attributes for the interface uuiddefines theinterface id, and dualmarks the type of the interface

// COMDemo.h : Declaration of the CCOMDemo

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 36

// CCOMDemo[

coclass,threading(“apartment”),vi_progid(“COMServer.COMDemo”),progid(“COMServer.COMDemo.1”),version(1.0),

uuid(“2388AAA8-AD72-4022-948D-555316F708E8”),helpstring(“COMDemo Class”)

]class ATL_NO_VTABLE CCOMDemo : public IWelcome

{public:

CCOMDemo(){

}

DECLARE_PROTECT_FINAL_CONSTRUCT()HRESULT FinalConstruct()

{return S_OK;

}void FinalRelease() {

}public:

STDMETHOD(Greeting)(BSTR name, BSTR* message);

};

Add the custom attribute with the same identifier and the name Wrox.ProCSharp.COMInterop.Server.IWelcometo the header section of the IWelcomeinterface Add the same attribute with a corresponding name to the class CCOMDemo

// IWelcome[

object,uuid(“015ED275-3DE6-4716-A6FA-4EBC71E4A8EA”),dual, helpstring(“ICOMDemo Interface”),pointer_default(unique),

With custom attributes it is possible to change the name of the class and interfaces that are generated by a NET wrapper class You just have to add the attribute

custom with the identifier 0F21F359-AB84-41e8-9A78-36D110E6D2F9 , and the name how it should appear within NET.

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 37

custom(0F21F359-AB84-41e8-9A78-36D110E6D2F9,

“Wrox.ProCSharp.COMInterop.Server.IWelcome”)]

interface IWelcome : IDispatch

interface IMath : IDispatch

{

[id(1)] HRESULT Add([in] LONG val1, [in] LONG val2, [out, retval] LONG* result);[id(2)] HRESULT Sub([in] LONG val1, [in] LONG val2, [out, retval] LONG* result);};

The class CCOMDemomust also be changed, so that it implements both interfaces IWelcomeand IMath: [

helpstring(“COMDemo Class”)

]

class ATL_NO_VTABLE CCOMDemo :

public IWelcome, public IMath{

Now you can implement the three methods in the file COMDemo.cpp with the following code TheCComBSTRis an ATL class that makes it easier to deal with BSTRs In the Greeting()method just a wel-come message is returned that adds the name passed in the first argument to the message that is

returned The Add()method just does a simple addition of two values, while the Sub()method does asubtraction, and returns the result

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 38

STDMETHODIMP CCOMDemo::Greeting(BSTR name, BSTR* message){

*result = val1 + val2;

return S_OK;

}STDMETHODIMP CCOMDemo::Sub(LONG val1, LONG val2, LONG* result){

*result = val1 val2;

return S_OK;

}Now you can build the component The build process also configures the component in the registry

Creating a Runtime Callable Wrapper

You can now use the COM component from within NET To make this possible, you must create a time callable wrapper (RCW) Using the RCW the NET client sees a NET object instead of the COMcomponent, there is no need to deal with the COM characteristics as this is done by the wrapper A RCWhides IUnknownand IDispatchinterfaces (see Figure 28-8) and deals itself with the reference counts ofthe COM object

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 39

The RCW can be created by using the command-line utility tlbimp, or by using Visual Studio NET.Starting the command

tlbimp COMServer.dll /out: Interop.COMServer.dll

creates the file Interop.COMServer.dll including a NET assembly with the wrapper class In this ated assembly we can find the namespace COMWrapperwith the class CCOMDemoClassand the inter-faces CCOMDemo, IMath, and IWelcome The name of the namespace can be changed by using options ofthe tlbimp utility The option /namespaceallows us to specify a different namespace, with /asmver-sionthe version number of the assembly can be defined

gener-Another important option of this command-line utility is /keyfile for assigning a strong name to the generated assembly Strong names are discussed in Chapter 13

An RCW can also be created by using Visual Studio NET To create a simple sample application, create aC# console project In Solution Explorer, add a reference to the COM server by selecting the COM taband scroll down to the entry COMServer 1.0 Type Library(see Figure 28-9) Here all COM objectsare listed that are configured in the registry Selecting a COM component from the list creates an assem-bly with an RCW class

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 40

using System;

using System.Runtime.InteropServices;

using Wrox.ProCSharp.COMInteorp.Servernamespace Wrox.ProCSharp.COMInterop.Client{

class Class1{

COMDemo obj = new COMDemoClass();

IWelcome welcome = (IWelcome)obj;

Console.WriteLine(obj.Greeting(“Christian”));

If the object—as in our case—offers multiple interfaces, a variable of the other interface can be declared,and by using a simple assignment with the cast operator, the wrapper class does a QueryInterface()with the COM object to return the second interface pointer With the mathvariable the methods of theIMathinterface can be called

Marshal.ReleaseComObject(math);

}}}

As can be seen, with a runtime callable wrapper, a COM component can be used similar to a NETobject

A special case of a runtime callable wrapper is a primary interop assembly

Primary interop assemblies

A primary interop assembly is an assembly that is already prepared by the vendor of the COM component.

This makes it easier to use the COM component A primary interop assembly is a runtime-callable per that might differ from an automatically generated RCW

wrap-Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Ngày đăng: 13/08/2014, 15:21

TỪ KHÓA LIÊN QUAN