As with the ElementHost class used to host a WPF control inside Windows Forms, now you need a wrapper that is a WPF control to host a Windows Forms control.. This assembly contains a con
Trang 1< StopStoryboard BeginStoryboardName=”beginMoveEye” / >
< /EventTrigger >
< /DockPanel.Triggers >
to allow you to control the animation with a stop request
public partial class Window1 : System.Windows.Window {
public Window1() {
InitializeComponent();
startAnimationButton.Click += OnStartAnimation;
stopAnimationButton.Click += OnStopAnimation;
} void OnStartAnimation(object sender, RoutedEventArgs e) {
moveEye.Begin(eye, true);
} void OnStopAnimation(object sender, RoutedEventArgs e) {
moveEye.Stop(eye);
} }
Now, you can start the application and watch the eye move as soon as one of the Start buttons is clicked
Storyboard
The Storyboard class inherits from the base class Timeline but can contain multiple timelines The
Storyboard class can be used to control timelines The following table describes the methods of the Storyboard class
Storyboard Methods Description
CreateClock() The CreateClock() method returns a Clock object that you can use to
control the animations
Trang 2Storyboard Methods Description
Pause()Resume() With Pause() and Resume() , you can pause and resume animations
Seek() With the Seek() method, you can jump in time and move the animation
to a specified time interval
Stop() The Stop() method halts the clock and stops the animation
The EventTrigger class makes it possible to define actions when events occur The following table
describes the properties of this class
EventTrigger Properties Description
RoutedEvent With the RoutedEvent property, you can define the event when the
trig-ger should start; for example, a Click event of a Button
SourceName The SourceName property defines to what WPF element the event
should connect
Trigger actions that you can put within an EventTrigger are listed in the following table You ’ ve seen
the BeginStoryboard and StopStoryboard actions in the example, but the following table shows
Trang 3Adding 3 - D Features in WPF
This section gives you an introduction to the 3 - D features of WPF Here you’ll find the information to get started
The namespace for 3 - D with WPF is System.Windows.Media.Media3D
To understand 3 - D with WPF it is important to know the difference of the coordination system Figure
35 - 22 shows the coordination system of WPF 3 - D The origin is placed in the center The x - axis has positive values to the right and negative values to the left The y - axis is vertical with positive values up and negative values down The z - axis defines positive values in direction to the viewer
ViewPort3D ViewPort3D defines the rendering surface for 3 - D objects This element
contains all the visual elements for 3 - D drawing
ModelVisual3D ModelVisual3D is contained in a ViewPort3D and contains all the visual
elements You can assign a transformation to a complete model
GeometryModel3D GeometryModel3D is contained within a ModelVisual3D and consists of
a mesh and a material
Geometry3D Geometry3D is an abstract base class to define geometric shapes The
con-crete class that derives from Geometry3D is MeshGeometry3D With MeshGeometry3D you can define positions of triangles to build a
3 - D model
Trang 4Class Description
Material Material is an abstract base class to define the front and back side from
the triangles defined with the MeshGeometry3D Material is contained within a GeometryModel3D NET 3.5 defines several material classes, such as DiffuseMaterial , EmissiveMaterial , and
SpecularMaterial Depending on the material type, the light calculates differently EmissiveMaterial behaves with lighting calculations that the material emits the light equal to the color of the brush
DiffuseMaterial lights with a diffuse light, and SpecularMaterial defines a specularly lit model With the MaterialGroup class you can cre-ate a combination consisting of other materials
Light Light is the abstract base class for lighting Concrete implementations are
AmbientLight , DirectionalLight , PointLight and SpotLight
AmbientLight is an unnatural light that lights the complete scene larly You will not see edges using that light DirectionalLight defines
simi-a directed light Sunlight is simi-a exsimi-ample of directed light The light comes from one side and here you can see edges and shadows PointLight is a light with a specified position and lights in all directions SpotLight lights in a specified direction This light defines a cone so you can get a very intensive illuminated area
Camera Camera is the abstract base class for the camera that is used to map the 3 - D
scene to a 2 - D display Concrete implementations are PerspectiveCamera ,
OrthographicCamera , and MatrixCamera With the
PerspectiveCamera the 3 - D objects are smaller the further away they are
This is different with the OrthographicCamera Here the distance of the camera doesn ’ t influence the size With the MatrixCamera you can define the view and transformation in a matrix
Transform3D Transform3D is the abstract base class for 3 - D transformations Concrete
implementations are RotateTransform3D , ScaleTransform3D ,
TranslateTransform3D , MatrixTransform3D , and
Transform3DGroup TranslateTransform3D allows transforming an object in the x, y, and z direction ScaleTransform3D allows for an object resize With the RotateTransform3D class you can rotate the object defined by an angle in the x, y, and z direction With Transform3DGroup you can combine other transformations
Triangle
This section starts with a simple 3 - D sample A 3 - D model is made up of triangles, so a simple model
is just one triangle The triangle is defined by the Positions property of the MeshGeometry3D
The three points all use the same z coordinate, – 4, and x/y coordinates – 1 – 1, 1 – 1, and 0 1 The property
TriangleIndices indicates the order of the positions in a counterclockwise way With this property
you define which side of the triangle is visible One side of the triangle shows the color defined with
the Material property of the GeometryModel3D class, and the other side shows the BackMaterial
property
Trang 5The camera that is used to show the scenario is positioned at the coordinates 0, 0, 0, and looks into the direction 0, 0, – 8 Changing the camera position to the left side, the rectangle moves to the right and vice versa Changing the y position of the camera, the rectangle appears larger or smaller
The light that is used in this scene is an AmbientLight to light up the complete scene with a white light Figure 35 - 23 shows the result of the triangle
< ModelVisual3D.Content >
< GeometryModel3D >
< GeometryModel3D.Geometry >
< MeshGeometry3D Positions=”-1 -1 -4, 1 -1 -4, 0 1 -4”
Trang 6Changing Lights
Figure 35 - 23 just shows a simple triangle where you can get the same result with less effort using 2 - D
However, from here you can continue getting into 3 - D features For example, by changing the light from
an ambient light to a spotlight with the element SpotLight you can immediately see a different
appearance of the triangle With the spotlight you define a position where the light is placed, and the
position to which the light is directed Specifying - 1 1 2 for the position, the light is placed at the left
corner of the triangle and the y coordinate to the height of the triangle From there the light is directed
down and to the left You can see the new appearance of the triangle in Figure 35 - 24
Trang 7Adding Textures
Instead of using a solid color brush with the materials of the triangle, you can use a different brush such
as the LinearGradientBrush as shown with the following XAML code The LinearGradientBrush element defined with DiffuseMaterial defines gradient stops with the colors yellow, orange, red, blue, and violet To map a 2 - D surface from an object such as the brush to a 3 - D geometry, the
TextCoordinates property must be set TextCoordinates defines a collection of 2 - D points that map
to the 3 - D positions Figure 35 - 25 shows the 2 - D coordinates of the brush from the sample application The first position in the triangle, – 1 – 1, maps to the brush coordinates 0 1; the position 1 – 1, which is the lower corner on the right, maps to 1 1 of the brush, which is violet; and 0 1 maps to 0.5 0 Figure 35 - 26 shows the triangle with the material of the gradient brush, again with the ambient light
TriangleIndices=”0, 1, 2”
TextureCoordinates=”0 1, 1 1, 0.5 0” / >
< /GeometryModel3D.Geometry >
< GeometryModel3D.Material >
< GradientStop Color=”Yellow” Offset=”0” / >
< GradientStop Color=”Orange” Offset=”0.25” / >
< GradientStop Color=”Red” Offset=”0.50” / >
< GradientStop Color=”Blue” Offset=”0.75” / >
y
x
1
10/0
Figure 35-25
(continued)
Trang 8< GradientStop Color=”Violet” Offset=”1” / >
You can add text or other controls in a similar way to the materials To do this you just need to create
a VisualBrush with the elements that should be painted The VisualBrush is discussed in
Chapter 34 , “ Windows Presentation Foundation ”
3 - Dimensional Object
Now let ’ s get into a real three - dimensional object: a box The box is made up of five rectangles: the back,
front, left, right, and bottom sides Each rectangle is made up of two triangles because this is the core of a
mesh With WPF and 3 - D the term mesh is used to describe the triangle primitive for building 3 - D
shapes
Here is the code of the rectangle for the front side of the box that consists of two triangles The positions
of the triangles are set in a counterclockwise order as defined by the TriangleIndices The front
side of the rectangle is done with a red brush; the back side with a gray brush Both of these brushes
are of type SolidColorBrush and defined with the resources of the Window
Trang 9The other rectangles look very similar, just with different positions Here you can see the XAML code
of the left side of the box:
< ! Left side >
< GeometryModel3D >
< GeometryModel3D.Geometry >
< MeshGeometry3D Positions=”-1 -1 1, -1 1 1, -1 -1 -1, -1 -1 -1, -1 1 1, -1 1 -1”
All the rectangles are combined within a Model3DGroup , so one transformation can be done with all the sides of the box:
< /Model3DGroup >
With the Transform property of the Model3DGroup element, all the geometries inside this group can be transformed Here a RotateTransform3D is used that defines an AxisAngleRotation3D To rotate the box during runtime, the Angle property is bound to the value of a Slider control
< ! Transformation of the complete model >
< /RotateTransform3D.Rotation >
< /RotateTransform3D >
< /Model3DGroup.Transform >
Trang 10To see the box, a camera is needed Here the PerspectiveCamera is used so that the box gets smaller
the further the camera is The position and direction of the camera can be set during runtime
The other light source is a SpotLight With this light source it is possible to highlight a specific area
on the box The SpotLight defines the properties InnerConeAngle and OuterConeAngle to define the
area of the full illumination:
Direction=”{Binding Path=Text, ElementName=spotDirection}”
Position=”{Binding Path=Text, ElementName=spotPosition}”
Range=”{Binding Path=Value, ElementName=spotRange}” / >
< /ModelVisual3D.Content >
< /ModelVisual3D >
When running the application you can change the rotation of the box, the camera, and lights as shown in
Figure 35 - 27
Creating a 3 - D model that consists of just rectangles or triangles is easy to do You would not manually
create more complex models; instead you would use one of several tools You can find 3 - D tools for WPF
at www.codeplex/3DTools
Trang 11Windows Forms Integration
Instead of rewriting your user interface completely from scratch for WPF, you can use existing Windows Forms controls within WPF applications, and create new WPF controls to be used within Windows Forms applications The best way of integrating Windows Forms and WPF is by creating controls and
integrating the controls in the application types of the other technology
The integration of Windows Forms and WPF has a big drawback If you integrate Windows Forms with WPF, the Windows Forms controls still look like they looked in the old days Windows Forms controls and applications don ’ t get the new look of WPF From a user interface standpoint, it would be better to rewrite the UI completely
To integrate Windows Forms and WPF, you need classes from the namespace System
Windows.Forms.Integration in the assembly WindowsFormsIntegration
WPF Controls Within Windows Forms
You can use WPF controls within a Windows Forms application A WPF element is a normal NET class However, you cannot use it directly from the Windows Forms code; a WPF control is not a Windows Forms control The integration can be done by the wrapper class ElementHost from the namespace
System.Windows.Forms.Integration ElementHost is a Windows Forms control, because it derives from System.Windows.Forms.Control , and can be used like any other Windows Forms control in a Windows Forms application ElementHost hosts and manages WPF controls
Figure 35-27
Trang 12Let ’ s start with a simple WPF control With Visual Studio 2008, you can create a WPF User Control
Library The sample control is derived from the base class UserControl and contains a grid and a
button with a custom content:
< Canvas Height=”230” Width=”230” >
< Ellipse Canvas.Left=”50” Canvas.Top=”50” Width=”100” Height=”100”
Stroke=”Blue” StrokeThickness=”4” Fill=”Yellow” / >
< Ellipse Canvas.Left=”60” Canvas.Top=”65” Width=”25” Height=”25”
Stroke=”Blue” StrokeThickness=”3” Fill=”White” / >
< Ellipse Canvas.Left=”70” Canvas.Top=”75” Width=”5” Height=”5”
You can create a Windows Forms application by selecting the Windows Forms Application
template Because the WPF user control project is in the same solution as the Windows Forms
application, you can drag and drop the WPF user control from the toolbox to the designer surface of
the Windows Forms application This adds references to the assemblies PresentationCore ,
PresentationFramework , WindowsBase , WindowsFormsIntegration , and of course, the assembly
containing the WPF control
Within the designer - generated code you will find a variable referencing the WPF user control and an
object of type ElementHost that wraps the control:
private System.Windows.Forms.Integration.ElementHost elementHost1;
private WPFControl.UserControl1 userControl11;
In the method InitializeComponent you can see object initializations and the assigning of the WPF
control instance to the Child property of the ElementHost class:
private void InitializeComponent()
Trang 13this.elementHost1.Location = new System.Drawing.Point(39, 44);
Windows Forms Controls Within WPF Applications
You can integrate Windows Forms and WPF in the other direction as well by placing a Windows Forms control within a WPF application As with the ElementHost class used to host a WPF control inside Windows Forms, now you need a wrapper that is a WPF control to host a Windows Forms control This class has the name WindowsFormsHost and is in the same assembly, WindowsFormsIntegration The class WindowsFormsHost is derived from the base classes HwndHost and FrameworkElement , and thus can be used as a WPF element
For this integration, a Windows Control Library is created first Add a TextBox and Button control to the form by using the Designer To change the Text property of the button, the property ButtonText is added to the code behind:
public partial class UserControl1 : UserControl {
public UserControl1() {
InitializeComponent();
}
Figure 35-28
(continued)
Trang 14public string ButtonText
{
get { return button1.Text; }
set { button1.Text = value; }
}
}
In the WPF application, you can add a WindowsFormsHost object from the toolbox to the Designer This
adds a reference to the assemblies WindowsFormsIntegration , System.Windows.Forms , and the
assembly of the Windows Forms control To use the Windows Forms control from XAML, you must add
an XML namespace alias to reference the NET namespace Because the assembly containing the
Windows Forms control is in a different assembly than the WPF application, you also must add the
assembly name to the namespace alias The Windows Forms control can now be contained within the
WindowsFormsHost element as shown You can assign a value for the property ButtonText directly
from XAML similarly to NET Framework elements
< WindowsFormsHost Grid.Row=”0” Height=”180” >
< winforms:UserControl1 x:Name=”myControl” ButtonText=”Click Me!” / >
< /WindowsFormsHost >
< StackPanel Grid.Row=”1” >
< TextBox Margin=”5,5,5,5” Width=”140” Height=”30” > < /TextBox >
< Button Margin=”5,5,5,5” Width=”80” Height=”40” > WPF Button < /Button >
< /StackPanel >
< /Grid >
< /Window >
You can see a view of the WPF application in Figure 35 - 29 Of course, the Windows Forms control still
looks like a Windows Forms control and does not have all the resizing and styling features you get
with WPF
(continued)
Figure 35-29
Trang 15WPF Browser Application
Visual Studio 2008 has another WPF project template: a WPF Browser Application Such an application can run within Internet Explorer, but still the NET Framework version that you use must be installed with the client system Here you get the features of the rich client to the browser However, with WPF Browser Applications, the NET Framework is required to be available on the client system, and only Internet Explorer is supported
Creating such a project type, an XBAP (XAML Browser Application) file is created XBAP is an XML file that defines the application and the assemblies it consists of for ClickOnce deployment
An XBAP application is a partial - trust application You can use only NET code that is available with the Internet permissions
ClickOnce is explained in Chapter 16 , “ Deployment ” WPF Browser Applications are different from Silverlight Silverlight defines a subset of WPF that does not require the NET Framework to be installed with the client system Silverlight requires an add - in with the browser but supports different browsers and different operating systems Silverlight 1.0 cannot
be programmed using NET; you can use only JavaScript for accessing the XAML elements matically Silverlight 1.1 will support the NET Microframework
Summar y
This chapter covered some more features of WPF
WPF data binding gives a leap forward compared to Windows Forms You can bind any property of a NET class to a property of a WPF element The binding mode defines the direction of the binding You can bind NET objects and lists, and define a data template to create a default look for a NET class with a data template
Command binding makes it possible to map handler code to menus and toolbars You ’ ve also seen how easy it is to do copy and paste with WPF because a command handler for this technology is already included in the TextBox control
Animation allows the user to dynamically change every property of a WPF element Animations can be very decent and not annoying and make the UI more responsive and attractive for the user
WPF also allows for an easy 3 - D mapping to the 2 - D surface of a screen You ’ ve seen how to create a 3 - D model and view it with the help of different light sources and cameras
This and the previous chapter gave you an overview of WPF and enough information to get started with this technology For more information on WPF, you should read a book that focuses on WPF; for exam- ple, Professional WPF Programming: NET Development with the Windows Presentation
Foundation by Chris Andrade et al (Wiley Publishing, 2007)
Trang 17Add - Ins
Add - ins allow you to add functionality to an application at a later time You can create a hosting application that gains more and more functionality over time — functionality that might be written by your developer team but also different vendors can extend your application by creating add - ins
Today, add - ins are used with many different applications, such as Internet Explorer and Visual Studio Internet Explorer is a hosting application that offers an add - in framework that is used by many companies to offer extensions when viewing Web pages The Shockwave Flash Object allows you to view Web pages with Flash content The Google toolbar offers specific Google features that can be accessed quickly from Internet Explorer Visual Studio also has an add - in model that allows you to extend Visual Studio with different levels of extensions
For your custom applications it has always been possible to create an add - in model to dynamically load and use functionality from assemblies With an add - in model many issues need to be thought about How can new assemblies be detected? How can versioning issues be resolved? Can the add - in change the stability of the hosting application?
The NET Framework 3.5 offers a framework for hosting and creating add - ins with the assembly System.AddIn This framework is also known by the name Managed AddIn Framework (MAF)
Add - ins are also known by different terms such as “ add - on ” or “ plug - in ”
Topics covered in this chapter are System.AddIn architecture Creating a simple add - in
Trang 18to resolve these issues In this section, you read about the issues of add - ins and how the architecture of
MAF solves them:
Issues with add - ins
Issues with Add - ins
Creating a hosted application that dynamically loads assemblies that are added at a later time has
several issues that must be dealt with, as shown in the table that follows
Add-Ins Issues Description
different options One way is to add information about add-ins to a tion file This has the disadvantage that the installation of new add-ins needs to change an existing configuration file Another option is to just copy the assem-bly containing the add-in to a predefined directory and read information about the assembly with reflection
configura-You can read more about reflection in Chapter 13, “Reflection.”
the new operator to create an instance You can create such assemblies with the
Activator class Also, different activation options might apply if the add-in is loaded within a different application domain or a new process.Assemblies and application domains are described in Chapter 17, “Assemblies.”
with Internet Explorer crashes caused by various add-ins Depending on the type of the hosting application and how the add-ins are integrated, the add-in can be loaded within a different application domain or also within a different process
collector cannot help here because add-ins might be active in a different application domain or a different process Other ways to keep the object in memory are reference count or leasing and sponsoring mechanisms
version of the host still can load old add-ins, and an old host should have the option to load newer add-ins
Trang 19Now let ’ s look at the architecture of MAF and how this framework solves these issues The design of MAF was influenced by these goals:
It should be easy to develop add - ins
Finding add - ins during runtime should be performant
Developing hosts should be an easy process as well, but not as easy as developing add - ins
The add - in and the host application should progress independently
Pipeline Architecture
The MAF architecture is based on a pipeline of seven assemblies This pipeline solves the versioning issues with add - ins Because the assemblies from the pipeline have a very light dependency, it is possible that the contract, the hosting, and the add - in applications progress with new versions completely independent of one another
Figure 36 - 1 shows the pipeline of the MAF architecture In the center is the contract assembly This assembly contains a contract interface that lists methods and properties that must be implemented by the add - in and can be called by the host Left of the contract is the host side, and on the right, the add - in side
In the figure you can see the dependencies between the assemblies The host assembly shown leftmost does not have a real dependency to the contract assembly; the same is true of the add - in assembly Both
do not really implement the interface that is defined by the contract Instead, they just have a reference to
a view assembly The host application references the host view; the add - in references the add - in view
The views contain abstract view classes that define methods and properties as defined by the contract
Add-In Adapter
Add-In
Figure 36-1 Figure 36 - 2 shows the relationship of the classes from the pipeline The host class has an association with the abstract host view class and invokes its methods The abstract host view class is implemented by the host adapter Adapters make the connection between the views and the contract The add - in adapter implements the methods and properties of the contract This adapter contains a reference to the add - in view and forwards calls from the host side to the add - in view The host adapter class defines a concrete class that derives from the abstract base class of the host view to implement the methods and properties This adapter includes a reference to the contract to forward calls from the view to the contract
Figure 36-2
Trang 20With this model it is possible that the add - in side and the host side emerge completely independent Just the
mapping layer needs to adapt For example, if a new version of the host is done that uses completely new
methods and properties, the contract can still stay the same and only the adapter needs to change It is also
possible that a new contract is defined Adapters can change, or several contracts can be used in parallel
Discovery
How can new add - ins be found for the hosting application? The MAF architecture uses a predefined
directory structure to find add - ins and the other assemblies of the pipeline The components of the
pipeline must be stored in these subdirectories:
All these directories with the exception of the AddIns directory directly contain the assembly of the
specific part of the pipeline The AddIns directory contains subdirectories for every add - in assembly
With add - ins, it is also possible to store them in directories that are completely independent of the other
pipeline components
The assemblies of the pipeline are not just loaded dynamically to get all the information about the add - in
using reflection With many add - ins, this would increase the startup time of the hosting application
Instead, MAF uses a cache with information about the pipeline components The cache is created by the
program installing the add - in or by the hosting application if the hosting application has write access to
the directory of the pipeline
The cache information about the pipeline components is created by invoking methods of the
AddInStore class The method Update() finds new add - ins that are not already listed with the store
files The Rebuild() method rebuilds the complete binary store file with information about the add - ins
The following table lists the members of the AddInStore class
pipe-FindAddIn()
FindAddIns()
These methods are used to find add-ins by using the cache The method
FindAddIns() returns a collection of all add-ins that match the host view The FindAddIn() method returns a specific add-in
Trang 21Activation and Isolation
The FindAddIns() method of the AddInStore class returns a collection of AddInToken objects that represent an add - in With the AddInToken class, you can access information about the add - in such as name, description, publisher, and version You can activate the add - in by using the Activate() method The following table lists properties and methods of the AddInToken class
AddInToken Members Description
NamePublisherVersionDescription
The Name, Publisher, Version and Description properties of the
AddInToken class return information about an add-in that was assigned
to the add-in with the attribute AddInAttribute
AssemblyName AssemblyName returns the name of the assembly that contains the add-in
EnableDirectConnect With the property EnableDirectConnect you can set a value that the
host should directly connect to the add-in instead of using the nents of the pipeline This is only possible if the add-in and the host are running in the same application domain, and the types of the add-in view and the host view are the same With this it is still required that all components of the pipeline exist
compo-QualificationData The add-in can mark appdomain and security requirements with the
attribute QualificationDataAttribute The add-in can list ments for security and isolation requirements For example,
require-[QualificationData(“Isolation”, “NewAppDomain“)] means that the add-in requires to be hosted in a new process You can read this information from the AddInToken to activate the add-in with the speci-fied requirements In addition to appdomain and security requirements, you can use this attribute to pass custom information through the pipeline
Activate() The add-in is activated with the Activate() method With parameters
of this method, you can define if the add-in should be loaded inside a new application domain or a new process You can also define what permissions the add-in gets
One add - in can break the complete application You may have seen Internet Explorer crash because of a failing add - in Depending on the application type and the add - in type, you can avoid this by letting the add - in run within a different application domain or within a different process MAF gives you several options here You can activate the add - in in a new application domain or a new process The new application domain might also have restricted permissions
The Activate() method of the AddInToken class has several overloads where you can pass the environment into which the add - in should be loaded The different options are listed in the following table
Trang 22Parameters of
AddInToken.Activate()
Description
AppDomain You can pass a new application domain into which the add-in should
be loaded This way you can make it independent of the host application, and it can also be unloaded with the application domain
AddInSecurityLevel If the add-in should run with different security levels you can pass a
value of the AddInSecurityLevel enumeration Possible values are
Internet, Intranet, FullTrust, and Host
PermissionSet If the predefined security levels are not specific enough, you can also
assign a PermissionSet to the appdomain of the add-in
AddInProcess Add-ins can also run within a different process from the hosting
appli-cation You can pass a new AddInProcess to the Activate() method
The new process can shut down if all add-ins are unloaded, or it can keep running This is an option that can be set with the property
KeepAlive
AddInEnvironment Passing an AddInEnvironment object is another option to define the
application domain where the add-in should be loaded With the constructor of AddInEnvironment, you can pass an AppDomain object
You can also get an existing AddInEnvironment of an add-in with the
AddInEnvironment property of the AddInController class
Application domains are explained in Chapter 17 , “ Assemblies ”
The type of application may restrict the choices you have WPF add - ins currently do not support
crossing processes With Windows Forms, it is not possible to have Windows controls connected across
different application domains
Let ’ s get into the steps of the pipeline when the Activate() method of an AddInToken is invoked:
1 The application domain is created with the permissions specified
2 The assembly of the add - in is loaded into the new application domain with the Assembly
.LoadFrom() method
3 The default constructor of the add - in is invoked by using reflection Because the add - in derives
from the base class that is defined with the add - in view, the assembly of the view is loaded as well
4 Next, an instance of the add - in side adapter is constructed The instance of the add - in is passed
to the constructor of the adapter, so the adapter can connect the contract to the add - in The
add - in adapter derives from the base class MarshalByRefObject , so it can be invoked across
application domains
5 The activation code returns a proxy to the add - in side adapter to the application domain of the
hosting application Because the add - in adapter implements the contract interface, the proxy
contains methods and properties of the contract interface
6 An instance of the host side adapter is constructed in the application domain of the hosting
application The proxy of the add - in side adapter is passed to the constructor The activation
finds the type of the host - side adapter from the add - in token
The host side adapter is returned to the hosting application
Trang 23Contracts
Contracts define the boundary between the host side and the add - in side Contracts are defined with an interface that needs to derive from the base interface IContract The contract should be well - thought in that it supports flexible add - in scenarios as needed
Contracts are not versionable and may not be changed so that previous add - in implementations can still run in newer hosts New versions are created by defining a new contract
There ’ s some restriction on the types you can use with the contract The restriction exists because of versioning issues and also because application domains are crossed from the hosting application to the add - in The types need to be safe and versionable, and able to pass it across the boundaries (application domain or cross - process) to pass it between hosts and add - ins
Possible types that can be passed with a contract are:
Primitive types Other contracts Serializable system types Simple serializable custom types that consists of primitive types, contracts, and do not have an implementation
The members of the IContract interface are explained in the following table
❑
❑
❑
❑
QueryContract() With QueryContract() it is possible to query a contract to verify
if another contract is implemented as well An add-in can support several contracts
RemoteToString() The parameter of QueryContract() requires a string
representa-tion of the contract RemoteToString() returns a string representation of the current contract
AcquireLifetimeToken()RevokeLifetimeToken()
The client invokes AcquireLifetimeToken() to keep a ence to the contract AcquireLifetimeToken() increments a reference count RevokeLifetimeToken() decrements a reference count
refer-RemoteEquals() RemoteEquals() can be used to compare two contract references
Contract interfaces are defined in the namespaces System.AddIn.Contract , System.AddIn.Contract.Collections , and System.AddIn.Contract.Automation The following table lists contract interfaces that you can use with a contract:
Trang 24Contract Description
IListContract<T> The IListContract<T> can be used to return a list of contracts
IEnumeratorContract<T> IEnumeratorContract<T> is used to enumerate the elements of
a IListContract<T>
IServiceProviderContract An add-in can offer services for other add-ins Add-ins that offer
services are known as service provider and implement the interface IServiceProviderContract With the method
QueryService() an add-in implementing this interface can be queried for services offered
IProfferServiceContract IProfferServiceContract is the interface offered by a service
provider in conjunction with IServiceProviderContract
IProfferServiceContract defines the methods
ProfferService() and RevokeService() ProfferService()
adds an IServiceProviderContract to the services offered,
RevokeService() removes it
INativeHandleContract This interface provides access to native Window handles with the
GetHandle() method This contract is used with WPF hosts to use WPF add-ins
Lifetime
How long does an add - in need to be loaded? How long is it used? When is it possible to unload the
application domain? There are several options to resolve this One option is to use reference counts
Every use of the add - in increments the reference count If the reference count decrements to zero, the
add - in can be unloaded Another option is to use the garbage collector If the garbage collector runs, and
there ’ s no more reference to an object, the object is the target of garbage collection .NET Remoting is
using a leasing mechanism and a sponsor to keep objects alive As soon as the leasing time ends,
sponsors are asked if the object should stay alive
With add - ins, there ’ s a specific issue for unloading add - ins because they can run in different application
domains and also in different processes The garbage collector cannot work across different processes
MAF is using a mixed model for lifetime management Within a single application domain, garbage
collection is used Within the pipeline an implicit sponsorship is used, but reference counting is available
from the outside to control the sponsor
Let ’ s consider a scenario where the add - in is loaded into a different application domain Within the host
application, the garbage collector cleans up the host view and the host side adapter when the reference is
not needed anymore For the add - in side, the contract defines the methods AcquireLifetimeToken()
and RevokeLifetimeToken() to increment and decrement the reference count of the sponsor These
methods do not just increment and decrement a value which could lead to release an object too early if
one party would call the revoke method too often Instead, AcquireLifetimeToken() returns an
identifier for the lifetime token, and this identifier must be used to invoke the RevokeLifetimeToken()
method So these methods are always called in pairs
Trang 25Usually you do not have to deal with invoking the AcquireLifetimeToken() and
RevokeLifetimeToken() methods Instead you can use the ContractHandle class that invokes
AcquireLifetimeToken() in the constructor and RevokeLifetimeToken() in the finalizer
The finalizer is explained in Chapter 12 , “ Memory Management and Pointers ”
In scenarios where the add - in is loaded in a new application domain, it is possible to get rid of the loaded code when the add - in is not needed anymore MAF uses a simple model to define one add - in
as the owner of the application domain to unload the application domain if this add - in is not needed anymore An add - in is the owner of the application domain if the application domain is created when the add - in is activated The application domain is not unloaded automatically if it was created previously
The class ContractHandle is used in the host side adapter to add a reference count to the add - in The members of this class are explained in the following table
ContractHandle Members Description
Contract In the construction of the ContractHandle class, an object
imple-menting IContract can be assigned to keep a reference to it The
Contract property returns this object
Dispose() The Dispose() method can be called instead of waiting for the
gar-bage collector to do the finalization to revoke the lifetime token
AppDomainOwner() AppDomainOwner() is a static method of the ContractHandle
class that returns the add-in adapter if it owns the application domain that is passed with the method
ContractOwnsAppDomain() With the static method ContractOwnsAppDomain() you can verify
if the specified contract is an owner of the application domain
Thus, the application domain gets unloaded when the contract is disposed
Versioning
Versioning is a very big issue with add - ins The host application is developed further as are the add - ins One requirement for an add - in is that it should be possible that a new version of the host application can still load old versions of add - ins The other direction should work as well: older hosts should run newer versions of add - ins But what if the contract changes?
System.AddIn is completely independent from the implementation of the host application and add - ins This is done with a pipeline concept that consists of seven parts
Trang 26Copy Local to False, so that the assembly does not get copied One exception is the HostApp console
project that needs a reference to the HostView project This assembly needs to be copied so it can be
found from the host application Also you need to change the output path of the generated assemblies so
that the assemblies are copied to the correct directories of the pipeline
Contracts\
This assembly contains the contract for communi-cation with the add-in
The contract is defined with an interface
AddInViews\
The CalcView assembly contains an abstract class that is referenced by the add-in This is the add-in side of the contract
This assembly contains the implementation of the add-in
CalcAddIn
Adapter
System.AddInSystem.AddIn.ContractCalcView
CalcContract
\Pipeline\
AddInSideAdapters\
CalcAddInAdapter nects the add-in view and the contract assembly and maps the contract to the add-in view
the abstract class of the host view does not need to reference any Add-In assembly and also does not have a reference to another project in the solution
System.AddIn.ContractHostView
Trang 27The Operate() method invokes the operation within the add - in and requires an operation defined by the IOperation interface and the operands with a double array
With this contract it is possible that the add - in supports any operations that require any number of
double operands and returns one double
The attribute AddInContract is used by the AddInStore to build the cache The AddInContract attribute marks the class as an add - in contract interface
using System.AddIn.Contract;
using System.AddIn.Pipeline;
namespace Wrox.ProCSharp.AddIns{
[AddInContract]
public interface ICalculatorContract : IContract {
IListContract < IOperationContract > GetOperations();
double Operate(IOperationContract operation, double[] operands);
} public interface IOperationContract : IContract {
string Name { get; } int NumberOperands { get; } }
}
Calculator Add - In View
The add - in view redefines the contract as it is seen by the add - in The contract defined the interfaces
ICalculatorContract and IOperationContract For this, the add - in view defines the abstract class
Calculator and the concrete class Operation With Operation there ’ s not a specific implementation required by every add - in Instead, the class is already implemented with the add - in view assembly This class describes an operation for mathematical calculations with the Name and NumberOperands properties
The abstract class Calculator defines the methods that need to be implemented by the add - ins While the contract defines parameters and return types that need to be passed across appdomain - and process - boundaries, that ’ s not the case with the add - in view Here you can use types, which make it easy to write add - ins for the add - in developer The GetOperations() method returns IList < Operation > instead of
IListOperation < IOperationContract > , as you ’ ve seen with the contract assembly
The AddInBase attribute identifies the class as an add - in view for the store
using System.AddIn.Pipeline;
using System.Collections.Generic;
(continued)
Trang 28public abstract IList < Operation > GetOperations();
public abstract double Operate(Operation operation, double[] operand);
}
public class Operation
{
public string Name { get; set; }
public int NumberOperands { get; set; }
}
}
Calculator Add - In Adapter
The add - in adapter maps the contract to the add - in view This assembly has references to both the
contract and the add - in view assemblies The implementation of the adapter needs to map the method
IListContract < IOperationContract > GetOperations() from the contract to the view
method IList < Operation > GetOperations()
The assembly includes the classes OperationViewToContractAddInAdapter and
CalculatorViewToContractAddInAdapter These classes implement the interfaces
IOperationContract and ICalculatorContract The methods of the base interface IContract can be
implemented by deriving from the base class ContractBase This class offers a default implementation
OperationViewToContractAddInAdapter implements the other members of the IOperationContract
interface and just forwards the calls to the Operation view that is assigned in the constructor
The class OperationViewToContractAddInAdapter also contains static helper methods
ViewToContractAdapter() and ContractToViewAdapter() that map Operation to
IOperationContract and the other way around
private Operation view;
public OperationViewToContractAddInAdapter(Operation view)
Trang 29{ return new OperationViewToContractAddInAdapter(view);
} public static Operation ContractToViewAdapter(
IOperationContract contract) {
return (contract as OperationViewToContractAddInAdapter).view;
} }}
The class CalculatorViewToContractAddInAdapter is very similar to
OperationViewToContractAddInAdapter : It derives from ContractBase to inherit a default implementation of the IContract interface, and it implements a contract interface This time the
ICalculatorContract interface is implemented with the GetOperations() and Operate() methods The Operate() method of the adapter invokes the Operate() method of the view class
Calculator where IOperationContract needs to be converted to Operation This is done with the static helper method ContractToViewAdapter() that is defined with the
OperationViewToContractAddInAdapter class
The implementation of the GetOperations method needs to convert the collection
IListContract < IOperationContract > to IList < Operation > For such collection conversions, the class CollectionAdapters defines conversion methods ToIList() and ToIListContract() Here, the method ToIListContract() is used for the conversion
The attribute AddInAdapter identifies the class as an add - in side adapter for the add - in store
using System.AddIn.Contract;
using System.AddIn.Pipeline;
namespace Wrox.ProCSharp.AddIns{
[AddInAdapter]
internal class CalculatorViewToContractAddInAdapter : ContractBase, ICalculatorContract
{ private Calculator view;
public CalculatorViewToContractAddInAdapter(Calculator view) {
this.view = view;
} public IListContract < IOperationContract > GetOperations() {
return CollectionAdapters.ToIListContract < Operation, IOperationContract > (view.GetOperations(), OperationViewToContractAddInAdapter.ViewToContractAdapter, OperationViewToContractAddInAdapter.ContractToViewAdapter);
} public double Operate(IOperationContract operation, double[] operands) {
return view.Operate(
OperationViewToContractAddInAdapter.ContractToViewAdapter(
operation), operands);
} }}
Trang 30Calculator Add - In
The add - in now contains the real implementation of the add - in The add - in is implemented by the class
CalculatorV1 The add - in assembly has a dependency on the add - in view assembly as it needs to
implement the abstract Calculator class
The attribute AddIn marks the class as an add - in for the add - in store, and adds publisher, version, and
description information On the host side, this information can be accessed from the AddInToken
CalculatorV1 returns a list of supported operations in the method GetOperations() Operate()
calculates the operands based on the operation
operations = new List < Operation > ();
operations.Add(new Operation() { Name = “+”, NumberOperands = 2 });
operations.Add(new Operation() { Name = “-”, NumberOperands = 2 });
operations.Add(new Operation() { Name = “/”, NumberOperands = 2 });
operations.Add(new Operation() { Name = “*”, NumberOperands = 2 });
return operand[0] * operand[1];
Because the adapter classes are invoked by NET reflection, it is possible that the
internal access modifier is used with these classes As these classes are an
implementation detail, it’s a good idea to use the internal access modifier.
Trang 31default:
throw new InvalidOperationException(
String.Format(“invalid operation {0}”, operation.Name));
} } }}
Calculator Host View
Let ’ s continue with the host view of the host side Similar to the add - in view, the host view defines an abstract class with methods similar to the contract However, the methods defined here are invoked by the host application
Both the class Calculator and Operation are abstract as the members are implemented by the host adapter The classes here just need to define the interface to be used by the host application
using System.Collections.Generic;
namespace Wrox.ProCSharp.AddIns{
public abstract class Calculator {
public abstract IList < Operation > GetOperations();
public abstract double Operate(Operation operation, params double[] operand);
} public abstract class Operation {
public abstract string Name { get; } public abstract int NumberOperands { get; } }
}
Calculator Host Adapter
The host adapter assembly references the host view and the contract to map the view to the contract
The class OperationContractToViewHostAdapter implements the members of the abstract
Operation class The class CalculatorContractToViewHostAdapter implements the members of the abstract Calculator class
With OperationContractToViewHostAdapter , the reference to the contract is assigned in the constructor The adapter class also contains a ContractHandle instance that adds a lifetime reference to the contract , so that add - in stays loaded as long it is needed by the hosting application
using System.AddIn.Pipeline;
namespace Wrox.ProCSharp.AddIns{
internal class OperationContractToViewHostAdapter : Operation {
private ContractHandle handle;
public IOperationContract Contract { get; private set; } public OperationContractToViewHostAdapter(IOperationContract contract)
(continued)
Trang 32The class CalculatorContractToViewHostAdapter implements the methods of the abstract host view
Calculator class and forwards the call to the contract Again, you can see the ContractHandle
holding the reference to the contract, which is similar to the adapter from the add - in side type
conversions This time the type conversions are just in the other direction from the add - in adapters
The attribute HostAdapter marks the class as an adapter that needs to be installed in the
private ICalculatorContract contract;
private ContractHandle handle;
public CalculatorContractToViewHostAdapter(ICalculatorContract contract)
(continued)
Trang 33{ this.contract = contract;
handle = new ContractHandle(contract);
} public override IList < Operation > GetOperations() {
return CollectionAdapters.ToIList < IOperationContract, Operation > ( contract.GetOperations(),
OperationHostAdapters.ContractToViewAdapter, OperationHostAdapters.ViewToContractAdapter);
} public override double Operate(Operation operation, double[] operands) {
return contract.Operate(OperationHostAdapters.ViewToContractAdapter(
operation), operands);
} }}
Calculator Host
The sample host application uses the WPF technology You can see the user interface of this application
in Figure 36 - 3 On top is the list of available add - ins On the left, the operations of the active add - in are shown As you select the operation that should be invoked, operands are shown After entering the values for the operands, the operation of the add - in can be invoked
The buttons on the bottom row are used to rebuild and update the add - in store, and to exit the application
Figure 36-3
Trang 34The XAML code that follows shows the tree of the user interface With the ListBox elements, different
styles with item templates are used to give a specific representation of the list of add - ins, the list of
operations, and the list of operands
You can read information about item templates in Chapter 35 , “ Advanced WPF ”
< DockPanel >
< GroupBox Header=”AddIn Store” DockPanel.Dock=”Bottom” >
< UniformGrid Columns=”4” >
< Button x:Name=”rebuildStore” Click=”RebuildStore”
Margin=”5” > Rebuild < /Button >
< Button x:Name=”updateStore” Click=”UpdateStore”
Margin=”5” > Update < /Button >
< Button x:Name=”refresh” Click=”RefreshAddIns”
Margin=”5” > Refresh < /Button >
< Button x:Name=”exit” Click=”App_Exit” Margin=”5” > Exit < /Button >
< /UniformGrid >
< /GroupBox >
< GroupBox Header=”AddIns” DockPanel.Dock=”Top” >
< ListBox x:Name=”listAddIns” ItemsSource=”{Binding}”
Style=”{StaticResource listAddInsStyle}” / >
< /GroupBox >
< GroupBox DockPanel.Dock=”Left” Header=”Operations” >
< ListBox x:Name=”listOperations” ItemsSource=”{Binding}”
< Button x:Name=”buttonCalculate” Click=”Calculate” IsEnabled=”False”
Margin=”5” > Calculate < /Button >
< GroupBox DockPanel.Dock=”Bottom” Header=”Result” >
< Label x:Name=”labelResult” / >
< /GroupBox >
< /StackPanel >
< /DockPanel >
In the code behind, the FindAddIns() method is invoked in the constructor of the Window
FindAddIns() uses the AddInStore class to get a collection of AddInToken objects and pass them to
the DataContext property of the ListBox listAddIns for display The first parameter of the
AddInStore.FindAddIns() method passes the abstract Calculator class that is defined by the host
view to find all add - ins from the store that apply to the contract The second parameter passes the
directory of the pipeline that is read from the application configuration file When you run the sample
application from the Wrox download site you have to change the directory in the application
configuration file to match your directory structure
Trang 35using System.Windows.Controls;
using Wrox.ProCSharp.AddIns.Properties;
namespace Wrox.ProCSharp.AddIns{
public partial class CalculatorHostWindow : Window {
private Calculator activeAddIn = null;
private Operation currentOperation = null;
public CalculatorHostWindow() {
InitializeComponent();
FindAddIns();
} void FindAddIns() {
try { this.listAddIns.DataContext = AddInStore.FindAddIns(typeof(Calculator), Settings.Default.PipelinePath);
} catch (DirectoryNotFoundException ex) {
MessageBox.Show(“Verify the pipeline directory in the “ + “config file”);
Application.Current.Shutdown();
} } //
To update the cache of the Add - In store, the UpdateStore() and RebuildStore() methods are mapped to the Click events of the Update and Rebuild buttons Within the implementation of these methods, the Rebuild() or Update() methods of the AddInStore class are used These methods return
a string array of warnings if assemblies are stored in the wrong directories Because of the complexity of the pipeline structure, there ’ s a good chance that the first time you may not get the project configuration completely right for copying the assemblies to the correct directories Reading the returned information from these methods, you will get a clear explanation about what ’ s wrong For example, the message ” No usable AddInAdapter parts could be found in assembly Pipeline\AddInSideAdapters\CalcView.dll ” gives a hint that the assembly CalcView is stored inside the wrong directory
private void UpdateStore(object sender, RoutedEventArgs e) {
string[] messages = AddInStore.Update(Settings.Default.PipelinePath);
if (messages.Length != 0) {
MessageBox.Show(string.Join(“\n”, messages), “AddInStore Warnings”, MessageBoxButton.OK, MessageBoxImage.Warning);
} } private void RebuildStore(object sender, RoutedEventArgs e)
(continued)
Trang 36In Figure 36 - 2 you can see an Activate button beside the available add - in Clicking this button invokes
the handler method ActivateAddIn() With this implementation, the add - in is activated by using the
Activate() method of the AddInToken class Here the add - in is loaded inside a new process that is
created with the AddInProcess class This class starts the process AddInProcess32.exe Setting the
KeepAlive property of the process to false , the process is stopped as soon as the last add - in reference
is garbage collected The parameter AddInSecurityLevel.Internet leads to an add - in running with
restricted permissions The last statement of ActivateAddIn() invokes the ListOperations()
method, which in turn invokes the GetOperations() method of the add - in GetOperations() assigns
the returned list to the data context of the ListBox listOperations for displaying all operations
private void ActivateAddIn(object sender, RoutedEventArgs e)
{
FrameworkElement el = sender as FrameworkElement;
Trace.Assert(el != null, “ActivateAddIn invoked from the wrong “ +
“control type”);
AddInToken addIn = el.Tag as AddInToken;
Trace.Assert(el.Tag != null, String.Format(
“An AddInToken must be assigned to the Tag property “ +
“of the control {0}”, el.Name);
AddInProcess process = new AddInProcess();
After the add - in is activated and the list of operations displays in the UI, the user can select an operation
The Click event of the Button shown in the Operations category is assigned to the handler method
OperationSelected() In the implementation, the Operation object that is assigned to the Tag
property of the Button is retrieved to get the number of operands needed with the operation To allow
the user adding values to the operands, an array of OperandUI objects is bound to the ListBox
listOperands
private void OperationSelected(object sender, RoutedEventArgs e)
{
FrameworkElement el = sender as FrameworkElement;
Trace.Assert(el != null, “OperationSelected invoked from “ +
“the wrong control type”);
(continued)
Trang 37Operation op = el.Tag as Operation;
Trace.Assert(el.Tag != null, String.Format(
“An AddInToken must be assigned to the Tag property “ + “of the control {0}”, el.Name);
currentOperation = op;
ListOperands(new double[op.NumberOperands]);
} private class OperandUI {
public int Index { get; set; } public double Value { get; set; } }
void ListOperands(double[] operands) {
this.listOperands.DataContext = operands.Select((operand, index) = >
new OperandUI() { Index = index + 1, Value = operand }).ToArray();
}
The Calculate() method is invoked with the Click event of the Calculate button Here, the operands are retrieved from the UI, the operation and operands are passed to the Operate() method of the add - in, and the result is shown with the content of a label
private void Calculate(object sender, RoutedEventArgs e) {
OperandUI[] operandsUI = (OperandUI[])this.listOperands.DataContext;
double[] operands = operandsUI.Select(opui = > opui.Value).ToArray();
labelResult.Content = activeAddIn.Operate(currentOperation, operands);
}
Additional Add - Ins
The hard work is now done The pipeline components and the host application are created The pipeline
is now working, yet it ’ s an easy task to add other add - ins such as the Advanced Calculator add - in shown
in the following code segment into the host application
[AddIn(“Advanced Calc”, Publisher = “Wrox Press”, Version = “1.1.0.0”, Description = “Another AddIn Sample”)]
public class AdvancedCalculatorV1 : Calculator
The next chapter starts a sequence of three chapters for developing the UI with ASP.NET
Trang 39ASP NET Pages
If you are new to the world of C# and NET, you might wonder why a chapter on ASP.NET has been included in this book It ’ s a whole new language, right? Well, not really In fact, as you will see, you can use C# to create ASP.NET pages
ASP.NET is part of the NET Framework and is a technology that allows for the dynamic creation
of documents on a Web server when they are requested via HTTP This mostly means HTML and XHTML documents, although it is equally possible to create XML documents, CSS files, images, PDF documents, or anything else that supports MIME types
In some ways, ASP.NET is similar to many other technologies — such as PHP, ASP, or ColdFusion
There is, however, one key difference: ASP.NET, as its name suggests, has been designed to be fully integrated with the NET Framework, part of which includes support for C#
Perhaps you are familiar with Active Server Pages (ASP) technology, which enables you to create dynamic content If you are, you will probably know that programming in this technology used scripting languages such as VBScript or JScript The result was not always perfect, at least not for those of us used to “ proper, ” compiled programming languages, and it certainly resulted in a loss
of performance
One major difference related to the use of more advanced programming languages is the provision
of a complete server - side object model for use at runtime ASP.NET provides access to all of the controls on a page as objects, in a rich environment On the server side, you also have access to other NET classes, allowing for the integration of many useful services Controls used on a page expose a lot of functionality; in fact, you can do almost as much as with Windows Forms classes, which provide plenty of flexibility For this reason, ASP.NET pages that generate HTML content
are often called Web Forms
This chapter takes a more detailed look at ASP.NET, including how it works, what you can do with
it, and how C# fits in The following is a brief outline of what is covered:
An introduction to ASP.NET How to create ASP.NET Web Forms with server controls How to bind data to ASP.NET controls with ADO.NET Application configuration
❑
❑
❑
❑
Trang 40ASP NET Introduction
ASP.NET works with Internet Information Server (IIS) to deliver content in response to HTTP requests
ASP.NET pages are found in aspx files Figure 37 - 1 illustrates the technology ’ s basic architecture
HTTP Request for aspx Resource ASP.NET Generated resource in HTTP Response
Server Processing
of aspx Resource Result of aspx Processing
ASP.NET page in aspx Resource
Database
Other Resources
.NET Framework OS
IIS Web Server
Figure 37 - 1
During ASP.NET processing, you have access to all NET classes, custom components created in C# or
other languages, databases, and so on In fact, you have as much power as you would have running a C#
application; using C# in ASP.NET is, in effect, running a C# application
An ASP.NET file can contain any of the following:
Processing instructions for the server
Code in C#, Visual Basic NET, JScript NET, or any other language that the NET Framework
supports
Content in whatever form is appropriate for the generated resource, such as HTML
Client - side script code, such as JavaScript
Embedded ASP.NET server controls
So, in fact, you could have an ASP.NET file as simple as this:
Hello!
This would simply result in an HTML page being returned (as HTML is the default output of ASP.NET
pages) containing just this text
As you will see later in this chapter, it is also possible to split certain portions of the code into other files,
which can provide a more logical structure
State Management in ASP NET
One of the key properties of ASP.NET pages is that they are effectively stateless By default, no
information is stored on the server between user requests (although there are methods for doing this, as
you will see later in this chapter) At first glance, this seems a little strange because state management