Windows Communication Foundation provides
a flexible model for exposing service endpoints and communicating with
them through a variety of different communication protocols. This month,
I dive into various addressing details that surround endpoint
communication, many of which enable more advanced messaging scenarios.
Endpoints and Addresses
The Windows®
Communication Foundation architecture cleanly separates physical
communication details from the underlying service implementation.
Developers spend most of their time working with familiar Microsoft®
.NET Framework code to implement service contracts and to define
service behavior. After the implementation is complete, they can decide
how to host the service and expose it to consumers.
In
order to host a particular service, you create a ServiceHost instance
and define a collection of service endpoints. A service endpoint
specifies an address, a binding, and a contract to use for
communication. Windows Communication Foundation needs this information
to build the necessary messaging runtime, also known as the channel
stack, and to provide metadata in the form of Web Services Description
Language (WSDL) to external consumers upon request.
You
can supply service endpoints to a ServiceHost instance through code
(see the ServiceEndpoint class) or through entries in the
<system.serviceModel> configuration section (see the
<endpoint> element). Anything you can accomplish in code, you can
also accomplish in configuration and vice-versa. However, supplying
endpoints via configuration usually provides greater flexibility for
making post-development changes.
The
ServiceHost must contain at least one service endpoint before you
attempt to open it or the runtime will throw an exception; a service
without at least one endpoint wouldn’t be very useful since you’d have
no way to interact with it. A service can, however, expose more than one
endpoint in order to accommodate multiple contracts or different types
of consumers that have a wide range of capabilities or constraints.
When
discussing endpoints, most folks tend to focus on the binding and
contract while overlooking the endpoint address and the various issues
surrounding it. This month, my goal is to buck that trend by uncovering
the various addressing techniques available to you.
For
a more thorough discussion of the Windows Communication Foundation
programming model and how you work with endpoints in either code or the
various configuration elements, see my article called "Learn the ABCs of Programming Windows Communication Foundation" in the February 2006 issue of MSDN® Magazine.
Addressing Fundamentals
When
you define service endpoints, you have numerous options for specifying
the address. You can specify an IP address or a host name, and you can
specify a port number or just assume the default port for the transport.
And, of course, you can include a specific path in the address (the
part of the address that comes after the host/port).
There
are two fundamental techniques you can use when specifying the address
in Windows Communication Foundation. You can specify an absolute address
for each endpoint or you can supply the ServiceHost with a base address
and then specify relative paths for each endpoint. Specifying absolute
addresses is a little easier to understand, but the base address
technique typically makes things easier to manage.
You specify an absolute address by providing the fully qualified address in the endpoint definition as illustrated in Figure 1.
In this example, I’ve specified three absolute addresses, but notice
that the two HTTP addresses share the same base address of
http://localhost:8080/calcservice. Given this situation, you have the
option to provide the host with the base address and then use relative
addresses when defining the individual endpoints.
Figure 1 Configuring Endpoints
<configuration> <system.serviceModel> <services> <service name=”CalculatorService”> <endpoint address=”http://localhost:8080/calcservice” binding=”basicHttpBinding” contract=”ISimpleMath”/> <endpoint address=”http://localhost:8080/calcservice/secure” binding=”wsHttpBinding” contract=”ISimpleMath”/> <endpoint address=”net.tcp://localhost:8081/calcservice” binding=”netTcpBinding” contract=”ISimpleMath”/> </service> </services> </system.serviceModel> </configuration>
You can supply one base address per transport when constructing a ServiceHost object, as shown here:
ServiceHost host = new ServiceHost(typeof(CalculatorService), // base HTTP address new Uri(“http://localhost:8080/calcservice”), // base TCP address new Uri(“net.tcp://localhost:8081/calcservice”));
It’s
also possible to specify the base addresses in the configuration file
along with the endpoints themselves. You do this by listing the base
addresses within the <host> element for each <service>, like
so:
<configuration> <system.serviceModel> <services> <service name=”CalculatorService”> <host> <baseAddresses> <add baseAddress=”http://localhost:8080/calcservice”/> <add baseAddress=”net.tcp://localhost:8081/calcservice”/> </baseAddresses> </host> <!-- endpoint definitions follow --> ...
Whether
you do it in code or in configuration, both techniques accomplish the
same thing—they configure the ServiceHost object with a base address for
each transport in use. Once the ServiceHost has been configured this
way, you can take advantage of relative URIs when defining endpoint
addresses, as you see in Figure 2. When you open the
ServiceHost, it determines the absolute address to use for each endpoint
by resolving any relative addresses against the corresponding base
address (the one that matches the binding’s transport).
Figure 2 Using Relative URIs
<configuration> <system.serviceModel> <services> <service name=”CalculatorService”> <endpoint binding=”basicHttpBinding” contract=”ISimpleMath”/> <endpoint address=”secure” binding=”wsHttpBinding” contract=”ISimpleMath”/> <endpoint binding=”netTcpBinding” contract=”ISimpleMath”/> </service> </services> </system.serviceModel> </configuration>
Notice
both the first and the last endpoints omit the "address" attribute,
which is the same as leaving it empty. Hence, each of these endpoints
will simply use the corresponding base address for its endpoint address;
the first endpoint uses the base HTTP address while the last endpoint
uses the base TCP address. The second endpoint uses a relative address
of "secure", which will be resolved against the base HTTP address (it
becomes http://localhost:8080/calcservice/secure).
Even
when using base addresses, you can still specify absolute addresses for
endpoints when desired. And the absolute addresses don’t have to match
up in any way to the corresponding base address if one exists. Each
endpoint can specify a wildly different location if you so choose.
The
base address technique is mostly a convenience to reduce the number of
places you’ll have to make changes when modifying the locations of your
endpoints. Windows Communication Foundation also uses the base HTTP
address by default to expose metadata when GET retrieval has been
enabled (via the <serviceMetadata> behavior). You can, however,
change the retrieval location using the behavior’s httpGetUrl property.
Clients
have no awareness of the service’s base address and have no need to
support something similar on their side of the wire. As a result, you
won’t find anything related to base addresses in the client-side object
model or the <client> configuration section. Clients simply choose
a particular endpoint, which always comes configured with an absolute
address, and that absolute address determines the address it will use
during transmission.
These
addressing fundamentals apply when self-hosting Windows Communication
Foundation services (where you create and manage the ServiceHost
instance yourself), but there are some special considerations to keep in
mind when you host services within IIS.
IIS Addressing Considerations
When
hosting in IIS, you don’t have to worry about creating or managing the
ServiceHost instance. IIS takes care of this for you behind the scenes.
You simply map a .svc endpoint to your service class, configure the
service endpoints and behaviors in web.config, and let Windows
Communication Foundation manage the process of creating and configuring
the ServiceHost instance at runtime.
In
this hosting scenario, the base HTTP address is determined by the IIS
virtual directory housing the service along with the name of the .svc
file. You, as the developer, really have nothing to say about the base
address since it’s fully controlled by the IIS addressing scheme. As a
result, Windows Communication Foundation happily ignores any base
addresses you may specify in web.config in this scenario. If you want to
change the base address for your endpoints, you’ll need to move the
service to a different IIS virtual directory.
Not
only does IIS control the base address, it forces all of your endpoints
to actually use the same base address (unlike self-hosting). This means
that if you do specify an absolute address for a particular endpoint,
it must start with the base address corresponding to the virtual
directory or you’ll get an exception. As a result, it really only makes
sense to use relative addresses when hosting in IIS. You will need to
use relative addresses when exposing more than one endpoint for a
particular .svc service (we’ll discuss why you’d want to do this
shortly).
Let’s
take a look at an example. Suppose you have a file named calc.svc and
you place it in a virtual directory that corresponds to
http://localhost:8080/calcservice. The base address for this service
will be http://localhost:8080/calcservice/calc.svc. Assuming you’ve
enabled HTTP GET metadata retrieval, the help page will be exposed at
that same address, which you can simply browse to in Internet Explorer.
Now, consider the endpoint configuration found in the virtual directory’s web.config file (in Figure 3).
In this case, the address of the first endpoint becomes the same as the
base address (http://localhost:8080/calcservice/calc.svc) since I left
the endpoint address empty. The address of the second endpoint becomes
the combination of the base address appended with "secure", like this:
http://localhost:8080/calcservice/calc.svc/secure. And the address of
the "mex" endpoint is http://localhost:8080/calcservice/calc.svc/mex.
This can seem a bit strange to some people since the relative portion of
the address is added to the right of the file name, but you have to
remember that calc.svc is part of the base address so it has to work
this way.
Figure 3 Sample Config for IIS-Hosted Service
<configuration> <system.serviceModel> <services> <service name=”CalculatorService”> <!-- base address determined by IIS virtual directory --> <endpoint binding=”basicHttpBinding” contract=”ISimpleMath”/> <endpoint address=”secure” binding=”wsHttpBinding” contract=”ISimpleMath”/> <endpoint address=”mex” binding=”mexHttpBinding” contract=”IMetadataExchange”/> </service> ...
Since
I’m on the subject of IIS hosting, I’ll also point out that with IIS
5.0 and IIS 6.0, you can only use the various HTTP bindings in your
endpoints. In other words, you cannot host a TCP endpoint in an IIS 5.0-
or 6.0-hosted service—only HTTP endpoints are supported. This changes
with the Windows Process Activation Services (WAS), which ships with
Windows Server code-named "Longhorn." WAS makes it possible to host
non-HTTP endpoints within IIS using the same .svc techniques discussed
above.
Multiple Endpoints and Unique Addresses
There
are a few reasons why you might wish to expose multiple endpoints on a
particular service. One reason is to expose the same contract using a
few different bindings. For example, you may have some consumers that
can only deal with WS-I Basic Profile 1.1-compliant services (one
binding) and others that can handle the full suite of standards (another
binding). Or you may have some internal enterprise consumers that
demand binary TCP transmissions for performance reasons (yet another
binding). The ability to expose the same contract using different
bindings allows you to accommodate all of these consumers at the same
time.
When
exposing multiple endpoints with different bindings, each endpoint
address must be unique. This is because each endpoint requires a
different transport listener and channel stack. Consider the service
configuration in Figure 4. In this example, all of the
endpoints expose the same contract (ISimpleMath) but each one uses a
different binding, so each address must be unique. If you modify an
endpoint to use the same address as one of the other endpoints, Windows
Communication Foundation will throw an exception while opening the
ServiceHost.
Figure 4 Sample Configuration Using Unique Addresses
<configuration> <system.serviceModel> <services> <service name=”CalculatorService”> <endpoint address=”http://localhost:8080/calcservice” binding=”basicHttpBinding” contract=”ISimpleMath”/> <endpoint address=”http://localhost:8080/calcservice/secure” binding=”wsHttpBinding” contract=”ISimpleMath”/> <endpoint address=”net.tcp://localhost:8081/calcservice” binding=”netTcpBinding” contract=”ISimpleMath”/> </service> ...
If,
however, multiple endpoints share the same binding, you can use the
same address across those endpoints. This approach makes a lot of sense
when you need to expose multiple contracts using the same binding, which
is another major case in which you need multiple endpoints on a single
service. In the following example, the two defined endpoints share the
same address, but they expose different contracts:
<configuration> <system.serviceModel> <services> <service name=”CalculatorService”> <endpoint address=”http://localhost:8080/calcservice” binding=”wsHttpBinding” contract=”ISimpleMath”/> <endpoint address=”http://localhost:8080/calcservice” binding=”wsHttpBinding” contract=”IScientific”/> </service> ...
In
this case, both endpoints end up sharing the same transport listener
and channel stack (they simply dispatch to a different set of methods).
Windows Communication Foundation is smart enough to know that it can
create and use a single binding instance for both endpoints to share
since both of them specified "wsHttpBinding" for the binding.
If
you’re adding endpoints manually in code, and they’re going to share
the same address, you’ll need to ensure that you use the same binding
instance when calling AddServiceEndpoint. The following code example
illustrates how not to do it:
ServiceHost host = new ServiceHost(typeof(CalculatorService)); host.AddServiceEndpoint( typeof(ISimpleMath), new WSHttpBinding(), “http://localhost:8080/calc”); host.AddServiceEndpoint( typeof(IScientific), new WSHttpBinding(), “http://localhost:8080/calc”); host.Open(); ...
Even
though both endpoints use the same type of binding, I’ve supplied two
distinct instances when calling AddServiceEndpoint. If you attempt to
run this code, Windows Communication Foundation throws an exception
during the second call to AddServiceEndpoint indicating that a binding
instance has already been associated with that particular endpoint
address. Instead, you should create a single WSHttpBinding instance and
supply it to both AddServiceEndpoint calls as shown here:
ServiceHost host = new ServiceHost(typeof(CalculatorService)); WSHttpBinding wsbinding = new WSHttpBinding(); host.AddServiceEndpoint( typeof(ISimpleMath), wsbinding, “http://localhost:8080/calc”); host.AddServiceEndpoint( typeof(IScientific), wsbinding, “http://localhost:8080/calc”); host.Open(); ...
Clients
don’t need to be concerned with the fact that both of these endpoints
are sharing the same address. The client will still be presented with
two distinct endpoints and will have to choose one. In this particular
example, svcutil.exe actually generates two proxy classes, one for each
endpoint, because they expose different contracts. It just so happens
that both proxy classes target the same address.
Logical vs. Physical Addresses
In
Windows Communication Foundation, each service endpoint actually has
two addresses associated with it—a logical address and a physical
address. The difference between these addresses is the same as the
difference between "To" and "Via" in WS-Addressing. The logical address
("To") is the address that SOAP messages target. The physical address
("Via"), on the other hand, is the actual transport-specific network
address that Windows Communication Foundation listens to for messages to
arrive. Throughout the object model, Windows Communication Foundation
refers to the logical address as "Address" or "Endpoint Address" and the
physical address as "ListenUri".
The
Windows Communication Foundation channel infrastructure revolves around
the physical address since it’s responsible for receiving incoming
messages using a particular transport protocol at a specific location.
The Windows Communication Foundation dispatcher, on the other hand, is
shielded from such networking details and instead focuses on mapping the
incoming message to an endpoint, and ultimately to a method call.
When
you specify endpoint addresses like I’ve done thus far, you’re actually
supplying the logical address. However, the logical address is also
used as the physical address unless otherwise specified. The following
example illustrates how to output various endpoint details including
both the logical and physical addresses:
... foreach (ServiceEndpoint se in host.Description.Endpoints) { Console.WriteLine(“Endpoint details:”); Console.WriteLine(“Logical address: \t{0}”, se.Address); Console.WriteLine(“Physical address: \t{0}”, se.ListenUri); Console.WriteLine(“Binding: \t{0}”, se.Binding.Name); Console.WriteLine(“Contract: \t{0}”, se.Contract.Name); Console.WriteLine(); } ...
If
you run it against the preceding examples, it will print the same value
for both addresses. In most situations, there’s no reason for the
logical and physical addresses to be different. Nevertheless, there are
situations where you may need to specify a different physical address.
You can specify the ListenUri when defining an endpoint in either code
or configuration. The following example illustrates how to do it in
code:
host.AddServiceEndpoint( typeof(ISimpleMath), wsbinding, “urn:calcservice:simplemath”, // logical new Uri(“http://localhost:8080/calc”)); // physical (listenUri) host.AddServiceEndpoint( typeof(IScientific), wsbinding, “urn:calcservice:scientific”, // logical new Uri(“http://localhost:8080/calc”)); // physical (listenUri)
You can accomplish the same thing in configuration using the listenUri attribute, as Figure 5
illustrates. Now when you run the code to print the endpoint details,
you should see different values for the logical and physical addresses.
In this example, both endpoints share the same physical address (in this
case http://localhost:8080/calcservice), but each one has a unique
logical address in the form of a URN.
Figure 5 Specifying the ListenUri
<configuration> <system.serviceModel> <services> <service name=”CalculatorService”> <endpoint address=”urn:calcservice:simplemath” listenUri=”http://localhost:8080/calcservice” binding=”wsHttpBinding” contract=”ISimpleMath”/> <endpoint address=”urn:calcservice:scientific” listenUri=”http://localhost:8080/calcservice” binding=”wsHttpBinding” contract=”IScientific”/> </service> ...
If
you need to guarantee that a ListenUri is unique on the machine, you
can use the ListenUriMode property and set its value to
ListenUriMode.Unique. When you do this, the channel infrastructure will
either choose a unique port number (in the case of TCP, without port
sharing enabled) or it will append a GUID to the end of the ListenUri
address before using it.
It’s
worth noting that you can also use a relative address when specifying
the ListenUri and Windows Communication Foundation will resolve it
against the corresponding base address just like it does for the logical
endpoint addresses.
At
this point, you may be wondering how clients will interface with a
service configured with a different ListenUri address. Clients have no
notion of a ListenUri. As far as clients know, there’s a single address
for each endpoint. Since the WSDL definition contains the logical
address for each endpoint, svcutil.exe embeds the logical address in the
client configuration and will use it for transmission by default.
When
a service has been configured with a different physical address, you’ll
need an out-of-band mechanism to inform clients of the physical address
to use. Then clients can instruct Windows Communication Foundation to
route outgoing messages via the physical address, as if it were a router
or some other type of intermediary. The ClientViaBehavior makes this
easy to accomplish, as illustrated here:
SimpleMathClient client = new SimpleMathClient( “WSHttpBinding_ISimpleMath”); client.Endpoint.Behaviors.Add(new ClientViaBehavior( new Uri(“http://localhost:8080/calcservice”))); double sum = client.Add(3, 4);
It’s also possible to apply the ClientViaBehavior within the client’s configuration file as shown in Figure 6.
Now, instead of trying to transmit the message to
"urn:calcservice:simplemath", the client transmits the message via the
same physical address used by the service endpoint. Once the message
arrives at the ListenUri, the dispatcher can determine which endpoint to
use by inspecting the logical address (the URN).
Figure 6 Applying the ClientViaBehavior in Configuration
<configuration> <system.serviceModel> <client> <endpoint address=”urn:calcservice:simplemath” binding=”wsHttpBinding” behaviorConfiguration=”Via” contract=”Client.localhost.ISimpleMath” name=”WSHttpBinding_ISimpleMath”/> ... </client> <behaviors> <endpointBehaviors> <behavior name=”Via”> <clientVia viaUri=”http://localhost:8080/calcservice”/> </behavior> </endpointBehaviors> </behaviors> ...
Why
would someone want to use these techniques? The answer is simple:
routing. Any time you need to transmit messages through an intermediary
en route to the ultimate destination, you’ll need to understand the
difference between Address and ListenUri and use them appropriately.
This is common in certain enterprise scenarios where a central "gateway"
is used to handle common tasks such as security, logging, and
statistics for all deployed services.
TcpTrace
is exactly this type of intermediary—the only difference is it’s used
by developers for diagnostic purposes. Let’s say that you want to use
TcpTrace to intercept all messages en route to the service but you don’t
want to change the service code. You can easily do this by configuring
the client with a ClientViaBehavior pointing to the TcpTrace listen
address, and presto, you’re done.
There’s
another way to accomplish the same thing without requiring the client
to use the ClientViaBehavior. When defining the service endpoint, you
can use the same address that TcpTrace is listening on for the logical
address (since this address shows up in WSDL, it causes the client to
send messages to the TcpTrace listen address). Then you specify a
different physical address for the endpoint (the ListenUri) and
configure TcpTrace to forward to that address.
In
this example, TcpTrace is just like any other intermediary performing
simple routing. It’s not hard to extrapolate to more sophisticated
custom routing scenarios.
Addressing Headers
In
order to accommodate even more sophisticated routing and dispatching
logic, you may want to annotate SOAP messages with custom addressing
headers. Custom addressing headers are carried in SOAP messages
alongside the traditional WS-Addressing headers. For example, if an
incoming message contains the <premium> membership header, you
dispatch to a feature-rich endpoint whereas if it contains the
<basic> header, you route to the endpoint with basic
functionality.
You
may be wondering why you wouldn’t just specify this information as part
of the service contract so it shows up in the message body. The issue
is that the consumer may not know or have the required information.
Rather, the information will need to be supplied by an intermediary en
route to the endpoint. For instance, an intermediary looks up the
supplied member id, determines the membership level, and adds the
appropriate header to the outgoing message.
You
can associate custom addressing headers with endpoints to define
additional dispatching criteria. When you do this, only messages
containing the required addressing headers will be dispatched to the
given endpoint.
When
clients need to be aware of any required headers, they should also be
included in the service’s WSDL definition; addressing headers are
typically described within an <EndpointReference> element as
reference parameters (the <EndpointReference> is placed within the
WSDL <port> element).
Although
this may sound a bit complicated, Windows Communication Foundation
makes it easy to work with addressing headers. Consider the service
configuration in Figure 7. In this example, I’ve
defined two endpoints that share the same logical/physical addresses but
each exposes a different contract. Each endpoint also requires a
specific addressing header. The first one requires the <basic>
header while the second requires the <premium> header. Only
incoming messages that contain one of these addressing headers will be
dispatched to one of these endpoints.
Figure 7 Endpoints with Custom Addressing Headers
<configuration> <system.serviceModel> <services> <service name=”CalculatorService” behaviorConfiguration=”metadata”> <host> <baseAddresses> <add baseAddress=”http://localhost:8080/calcservice”/> </baseAddresses> </host> <endpoint binding=”wsHttpBinding” contract=”ISimpleMath”> <headers> <basic xmlns=”http://example.org/level”/> </headers> </endpoint> <endpoint binding=”wsHttpBinding” contract=”IScientific”> <headers> <premium xmlns=”http://example.org/level”/> </headers> </endpoint> </service> ...
It’s
also possible to specify addressing headers in code by manually
creating the AddressHeader object, and supplying it to an
EndpointAddress object. Then you can create a new ServiceEndpoint (based
on the EndpointAddress) and add it to the host’s Endpoints collection,
as shown here:
ServiceHost host = new ServiceHost(typeof(CalculatorService)); AddressHeader header = AddressHeader.CreateAddressHeader(“basic”, “http://example.org/level”, null); EndpointAddress ea = new EndpointAddress( new Uri(“http://localhost:8080/calcservice/foobar”), header); host.Description.Endpoints.Add( new ServiceEndpoint( ContractDescription.GetContract(typeof(ISimpleMath)), new WSHttpBinding(), ea)); ...
If you inspect the WSDL for this particular endpoint configuration, you’ll find the details in the <service> element (see Figure 8).
The <port> element contains a <wsa:EndpointReference>
element, which in turn contains the required headers as reference
parameters. When you run the WSDL through svcutil.exe, you’ll end up
with a similar client-side configuration that includes the same
<headers> element. Then when you use that particular endpoint,
Windows Communication Foundation will automatically include the required
<basic> header in the outgoing message, as you see in Figure 9.
Figure 9 Custom Addressing Headers Sent in SOAP
<s:Envelope xmlns:s=”http://www.w3.org/2003/05/soap-envelope” xmlns:a=”http://www.w3.org/2005/08/addressing”> <s:Header> <a:Action s:mustUnderstand=”1” >http://example.org/calc/Add</a:Action> <a:MessageID >urn:uuid:db1ba7a6-ca2e-494b-b390-9f621e8b90f4</a:MessageID> <a:ReplyTo> <a:Address >http://www.w3.org/2005/08/addressing/anonymous</a:Address> </a:ReplyTo> <a:To s:mustUnderstand=”1”>http://localhost:8080/calcservice</a:To> <basic a:IsReferenceParameter=”true” xmlns=”http://example.org/level”/> </s:Header> <s:Body> ...
Figure 8 Custom Addressing Headers in WSDL
... <wsdl:service name=”CalculatorService”> <wsdl:port name=”WSHttpBinding_ISimpleMath” binding=”tns:WSHttpBinding_ISimpleMath”> <soap12:address location=”http://localhost:8080/calcservice”/> <wsa10:EndpointReference> <wsa10:Address>http://localhost:8080/calcservice</wsa10:Address> <wsa10:ReferenceParameters> <basic xmlns=”http://example.org/level”/> </wsa10:ReferenceParameters> ... </wsa10:EndpointReference> </wsdl:port> ... </wsdl:service> ...
You
can confirm that the dispatcher is looking for the <basic> header
by removing the <headers> element from the generated client
configuration and invoking the service again, at which point dispatching
will fail.
Message Filters
You
can dig down even further to customize the dispatching process by
leveraging what are known as message filters. When Windows Communication
Foundation dispatches incoming messages, it uses message filters to
determine the matching endpoint, if one exists. You can choose which
message filter to use or you can provide your own. This flexibility
allows you to break free from the traditional dispatching model when
using Windows Communication Foundation to implement things other than
traditional SOAP—for instance, the techniques described here enable you
to implement REST/POX-style services on the Windows Communication
Foundation messaging foundation.
Each
endpoint actually has two filters associated with it—an address filter
and a contract filter. The address filter determines if the incoming
message matches the endpoint’s "To" address and any required address
headers, while the contract filter determines whether it matches the
endpoint’s contract. Both filters are used by the dispatcher to
determine the destination endpoint.
A
filter is a simply a class that derives from MessageFilter, which
defines a few abstract Match methods. There are several built-in
implementations of MessageFilter including EndpointAddressMessageFilter
and ActionMessageFilter, which are used as the default address and
contract filters respectively. The EndpointAddressMessageFilter simply
compares the "To" address to the endpoint address and expects an exact
match. It also compares the addressing headers found in the incoming
message with the collection of addressing headers required by the
endpoint. The ActionMessageFilter compares the incoming "Action" value
against the actions on the contract, again looking for an exact match.
There
are other addressing filters that provide different behavior. For
example, the PrefixEndpointAddressMessageFilter causes an incoming "To"
address to match the endpoint address as long as it starts with the same
address prefix (a looser match). There’s also a MatchAllMessageFilter,
which causes all messages to match the given endpoint.
An
easy way to change the message filter used by a service is through the
AddressFilterMode property of [ServiceBehavior]. AddressFilterMode comes
with three values: Any, Exact, and Prefix. Each of these values maps to
the corresponding MessageFilter implementation. The following example
illustrates how to configure my service to use the
PrefixEndpointAddressMessageFilter:
[ServiceBehavior(AddressFilterMode=AddressFilterMode.Prefix)] public class CalculatorService : ISimpleMath, IScientific { ...
It’s
also possible to change each endpoint’s filters by writing some code.
You can even write your own MessageFilter implementation to incorporate
some customized matching logic. Windows Communication Foundation does
come with an XPathMessageFilter, which allows you to evaluate arbitrary
XPath expressions against incoming messages during the matching process,
single-handedly covering content-driven scenarios.
Host Name Comparisons
One
final note about addresses and dispatching. It’s common to hard-code a
specific host name into the physical address when defining an endpoint.
But what if you want to configure the endpoint to listen for all host
names bound to a particular machine (localhost, contoso,
www.contoso.com, and so on)? Or what if you want to restrict an endpoint
to only match a particular host name?
You
can further control this via the HostNameComparisonMode, which is made
available on most bindings as a property. HostNameComparisonMode comes
with three settings: StrongWildcard (the default), Exact, and
WeakWildcard.
StrongWildcard
and WeakWildcard mean any hostname plus port combination will match the
endpoint address (including IP addresses). StrongWildcard matches
simply have higher priority than WeakWildcard matches. Exact means the
hostname plus port must exactly match the endpoint address (a
case-insensitive match at least).
Since
the default mode is StrongWildcard, your endpoints should automatically
handle the differences between localhost and your machine name, so
you’ll only need to use this feature when you wish to further constrain
the matching process.
Note
that many of the addressing techniques discussed in this article will
only work when used in conjunction with bindings configured to use SOAP
1.2 and WS-Addressing. If you’re having issues getting something to
work, make sure you’re using a modern binding like the WSHttpBinding.
For more information on how to enable a binding to use WS-Addressing,
see last month’s column titled "WCF Messaging Fundamentals".
Conclusion
The
Windows Communication Foundation messaging framework provides
developers with numerous addressing features that are designed to
simplify common hosting and communication scenarios and to increase
flexibility when dealing with sophisticated routing and dispatching
logic. Adding these addressing techniques to your toolbelt will increase
your future productivity, not to mention your skill set for bending
Windows Communication Foundation to your will.
Reference:
No comments:
Post a Comment