Nicholas Allen's Indigo Blog

Windows Communication Foundation From the Inside

October, 2006

  • Nicholas Allen's Indigo Blog

    Advanced URL ACLing with Windows Vista

    • 3 Comments

    This article covers some of the advanced topics that I left out of the earlier piece on configuring HTTP for Windows Vista. In addition to having gone over the basics article, it would also be helpful to have at least a little background knowledge about Windows security descriptors.

    The standard command for giving a user permission to make reservations in an HTTP namespace looks like

    netsh http add urlacl url=http://+:8000/ user=MYMACHINE\UserName

    All of these commands are going to assume that you're running as an account that has sufficient privilege to give away a piece of the HTTP reservation namespace (by default, such as the elevated Administrator account). Here are three additional options that I didn't talk about before.

    1. There are actually two separate permissions for using a registration (listen=yes/no) and for further delegating pieces of that namespace (delegate=yes/no). The ability to use a registration is GENERIC EXECUTE while the ability to delegate the namespace is GENERIC WRITE. The combination of these permission is represented by GENERIC ALL.
    2. A namespace ACL can represent either a positive permission (Allow ACE) or a negative permission (Deny ACE). Setting a negative permission explicitly denies someone the ability to make use of a reservation or perform delegation. You can create an ACL that both denies the ability to use a registration (listen=no) and denies the ability to delegate within that namespace (delegate=no). Most other uses of this command will result in a positive permission to do something.
    3. There's a new concept for a stable per-service SID in Windows based on the name of the service. This SID is the same across machines so you can actually use it in setup scripts. Using this feature allows you to ACL a namespace to a specific service even if that service has an account that is shared with lots of other services (such as NETWORK SERVICE). These service SIDs look like S-1-5-80 followed by a GUID and show up with a friendly name as NT SERVICE\MyServiceName.

    Additionally, netsh allows you to just use SDDL to describe your ACLs, although I'm not sure if you'll ever need to do that for WCF.

    Next time: Design Pattern for Building Channel Factories and Listeners

  • Nicholas Allen's Indigo Blog

    The GetProperty Picture

    • 1 Comments

    I drew this picture for myself while working on the guidelines for implementing GetProperty. I figured that other people might find it useful as well. One of the guidelines is that queryable properties on design time objects should flow to the corresponding run time objects. The black boxes show the classes that support GetProperty and the black arcs show the anticipated flow of properties. The red boxes show the classes that don't support GetProperty today but might make sense to have support in the future.

  • Nicholas Allen's Indigo Blog

    Datagram Transports

    • 1 Comments

    Datagrams are a self-contained unit of data. Each datagram carries with it the routing and context information needed to receive the data without knowing about any earlier exchanges of information. This means that you can continue sending datagrams from a source to a destination even if past datagrams along this path have been lost, duplicated, or misdirected. The protocol commonly associated with datagrams is the User Datagram Protocol (UDP) although it's possible to think of other transport protocols as having the same semantics as datagrams. For instance, when used in certain scenarios, queued transports and HTTP connections exhibit the same characteristics of datagram messaging as UDP.

    There is a particular pattern that you can use when implementing datagram channels in WCF. The singleton channel has a one-to-one mapping between channel listeners and accepted channels. When receiving datagrams, it doesn't make sense to have multiple outstanding channels listening at the same address. Any channel would be equally as good as any other for delivery precisely because datagrams are self-contained without reference to any earlier exchanges. This has led to the singleton model, where the channel listener only ever proceeds to accept one active channel. That channel has to hold its own state because even though the channel and channel listener are one-to-one, the channel listener may be destroyed before the channel. Of course, if that singleton channel is ever closed, the channel listener can accept a new channel in its place. However, this new channel is indistinguishably identical to the old instance, and there's always only ever at most one channel in existence.

    Next time: Advanced URL ACLing with Windows Vista

  • Nicholas Allen's Indigo Blog

    Implementation Guidelines for GetProperty

    • 6 Comments

    This post is just some quick thinking about guidelines for implementing the GetProperty method. These guidelines are still in development so think of this as a draft rather than real guidance at this time.

    Background: We provide an extensibility point called GetProperty on many of the components you can build using the channel model. The exact list of components that support GetProperty are bindings, binding elements, channel factories, channel listeners, channels, and message encoders. GetProperty allows someone to execute a strongly-typed query against your component to inspect its configuration settings. GetProperty takes a type parameter T and either returns some instance of T or returns default(T) to indicate that the type is unknown.

    DO expose configurable settings for your component using GetProperty. GetProperty should be used in preference to ad-hoc methods for searching through a stack of components.

    DO NOT require the use of GetProperty to access mandatory or intrinsic configuration settings. The types supported by GetProperty are not discoverable and any setting that your component considers mandatory should be accessible through the object model. GetProperty can be used as an alternative way of accessing the same value.

    DO support in your runtime objects every property that was available on your design time objects. If a channel factory exposes a setting, then every channel produced by that factory should expose a corresponsing setting that tells you what the value was when the channel was created.

    DO create specialized interfaces that capture each class of information that you want people to query on. Specialized interfaces promote the reuse of a particular type for semantically identical settings.

    DO reuse existing interfaces if your information has exactly the same semantics as the previous use. Conversely, if your settings don't have the same semantics, don't reuse a type just because it's more convenient to do so.

    DO NOT key properties off of commonly used types with a generic meaning. Types that appear frequently, such as IDisposable, will not have consistent semantics for all of their potential uses.

    DO NOT key properties off of value or enumerated types. We enforce this by placing a restriction on the generic type parameter.

    DO delegate down to inner channels, channel factories, and channel listeners when they exist and your component does not know about the requested type. We have a pipeline model that supports the composition of many components. If your component does not understand a type, then it should give others the chance to respond.

    Next time: Datagram Transports

  • Nicholas Allen's Indigo Blog

    Asymmetry Between Listeners and Factories

    • 6 Comments

    Throughout much of WCF development, we tried to preserve some fundamental symmetries between the client and server sides of messaging. A few months ago in the earlier release candidates, we ended up having to break one symmetry between channel factories and channel listeners. This broken symmetry involves the management of the lifetimes of the channels created by the factory or listener.

    Our original model was that channels had a lifetime that was strictly contained within the lifetime of a channel manager. The channel manager was simply the channel factory or listener that created the channel. The channel manager maintained strong references to all of its channels and was considered the channels' owner. When the channel manager was closed, it would in turn try to close all of the channels that it had created. This allowed you to clean up any outstanding channels even if you weren't keeping careful track of the channels after creation. Architecturally, this also allowed you to design your channels with shared state stored on the channel manager. As long as your channel existed, the channel manager must also exist so you know the shared state is valid.

    The change we made was to break this common channel manager model and have different behavior for channel factories and listeners. Channel factories continue to use the strong ownership model of channels. This makes it less likely that you'll accidently leak resources on the client even if you are sloppy about cleaning up your channels. Channel listeners no longer have their lifetime coupled to their channels. The reason that we did this is to allow for a smooth migration of work from one listener to another. Stopping new work requests from coming into a channel listener during migration requires shutting down that listener. In the old model, shutting down the listener causes all of the existing work requests to terminate. In the new model, you can shut down the current listener and let those existing work requests continue to completion. At the same time, you can start a new channel listener to process new work requests that come in. The channel implementation needs to be more complicated though because the channel cannot rely on the continued existence of its listener after creation.

    Next time: Implementation Guidelines for GetProperty

  • Nicholas Allen's Indigo Blog

    Two Hundred and First Post

    • 0 Comments

    Yesterday's post was the two hundredth post, an event which I almost decided not to mention. After all, at the current pace, a hundredth post is going to come around almost three times a year. Most of them are not going to be as exciting as the first time there was a hundredth post. It was interesting though that the total readership up to the time of the first hundredth post is about the same as the total readership just from the start of this month to the second hundredth post. That will be a tough pace to keep up because it requires quadrupling the number of readers every four months. Well, I'll just have to find other interesting occasions to make announcements about.

    One amusing consequence of all this posting is that Indigo is one of the 100 most commonly used tags on MSDN even though it was dropped as a product name a long time ago.

  • Nicholas Allen's Indigo Blog

    Comment and Email Policy (Abridged Edition)

    • 0 Comments
    I haven't had a lot of problems with people commenting, although I occasionally get more inquiries than I can individually respond to in a timely manner. That's why I haven't had to make any kind of real comment policy, although I did put up a disclaimer a few weeks ago. I try to respond with an answer or at least tell you that I have no idea what's going on within a week or so. That happens about 90% of the time, frequently sooner. Sometimes I write a question down to answer in a future post or I just have to wait for someone else to tell me what the answer is. If you have critical business questions that you need answered immediately, then blogs are probably not your best choice for support. We'll have the standard product support options available once WCF goes live.
  • Nicholas Allen's Indigo Blog

    Configuring IIS for Transport Security

    • 4 Comments

    I'm hosting a WCF application in IIS that uses transport security. How do I configure the security settings for my Virtual Directory (VDir)?

    The general model that we try to promote in WCF is that if a setting is shared between multiple places, the value of the setting has to match between all of those places. For instance, if you're sharing a TCP socket between multiple users, then the socket options that each user specifies have to match. This is probably the safest model for shared resources. Another model would be that the first one to specify the settings wins but that introduces ambiguity. Being the first one to do something is dependent on both timing and implementation-specific details like how granularly we chose to share resources. These aren't really things you want to rely on if your goal is to build a reliable application. There are cases where we are lax in enforcing that shared settings policy in order to be more forgiving of common programming mistakes. An example is that after we establish a TCP connection to a particular address, it doesn't make sense to send a message over that connection to some other address. There isn't any way to redirect the TCP connection on a per-message basis. However, we don't filter on the addressing headers of messages going through that channel because we're going to strip the addressing information out anyway before delivery. The likely reason that those headers are there is that you accidently copied them from another message.

    That was a long digression. In this case, the security settings in IIS have to match the security settings you specify in WCF. If your binding does not specify the use of HTTP authentication, then your VDir needs to be configured to accept anonymous authentication. If your binding specifies the use of Windows credentials, then your VDir needs to be configured to accept integrated authentication. When the settings don't match, you're generally going to get a poor experience. Make sure that the person deploying your application to the web server knows which settings they'll need to configure.

    Next time: Asymmetry Between Listeners and Factories

  • Nicholas Allen's Indigo Blog

    Keeping Connections Open in IIS

    • 3 Comments

    My web service needs to periodically broadcast messages to clients. The service is an Internet-facing application hosted inside of IIS. What's the best way to do this?

    The big limitation in this scenario is that your clients might be behind a firewall and non-addressable. There are basically two architecture camps for broadcasting messages to clients over the Internet. The push architecture camp has the clients maintain a continuous connection to the server and pushes data out at each update. The pull architecture camp has the clients periodically poll the server to see if there's any new data. Both of these architectures are widely used and they trade latency versus resources off of each other. There are a few other architectures that work locally as well but aren't as useful over the Internet, such as multicasting and callbacks. I'm just going to pick one of these and talk about using a push architecture. The basic way to build a push architecture is to have clients connect to the server and then the server holds the connection open indefinitely to send messages.

    If your service is hosted in IIS version 6 or below, then you don't have a lot of choice about the network protocol. Pushing data from the server to client is difficult with HTTP because the protocol is built on top of the request-reply model. A typical way of using HTTP to push is to make an empty request and send back a response using the chunked transfer encoding. Chunked transfers allow an HTTP server to send the response in pieces without having to specify the total length of the response up front. Normally, the client knows the message is done when the connection is closed or the pre-announced content length is reached. Neither of those options work in this case. Instead, the server needs to define some framing protocol so that the client can tell when the individual messages are done. The easiest way to get chunked HTTP transfers in WCF is to use the HTTP transport with streaming enabled.

    If your service is hosted in IIS 7 (the Vista/Longhorn version of IIS), then you are able to pick other network protocols for your service, such as TCP. TCP is inherently duplex so it works across the Internet for server-initiated transfer of messages without having to connect back through a firewall. Since TCP is duplex, you can write a duplex contract that gives you a very nice programming model for sending the broadcasts. This is a lot less work on your part than the equivalent setup needed with HTTP.

    Next time: Configuring IIS for Transport Security

  • Nicholas Allen's Indigo Blog

    How HostNameComparisonMode Works

    • 5 Comments

    How do I configure my service to listen on all of the host names for my machine?

    How do I configure my service to only listen on one particular host name?

    I sometimes see these funny + and * characters in URLs when using WCF. What do these characters mean?

    All three of these questions are related to the feature we refer to as HostNameComparisonMode. WCF matches addresses to listening services by comparing the address you specified to several tables of address prefix paths. A prefix path claims a particular address and can also optionally claim the entire namespace of addresses that start with that prefix. Of course, someone could then claim a longer (and therefore more specific) address inside of the namespace to provide a more suitable match. The + and * characters are wildcard symbols that match against any host name for the machine. There's no special symbol for exactly matching the name, that's just done by writing out the name itself.

    I was going to follow that by explaining how HostNameComparisonMode and prefix matching work in detail, but I found out that Kenny Wolf has already explained comparison and matching.

    Next time: Keeping Connections Open in IIS

  • Nicholas Allen's Indigo Blog

    Avoiding OneWay Deadlocks

    • 8 Comments

    I have two web services and I'm seeing a deadlock when making calls between them. The operation calls are marked as OneWay. How do I fix this? And, how is it even possible for one-way calls to block?

    Marking an operation with the OneWay attribute doesn't offer any magical protection against deadlocks. The OneWay attribute means that in messaging terms, there is no application data that the service is expected to return as a result of the call. However, one-way calls still need to wait for the service to accept transmission of the message. A one-way call is complete when the service acknowledges that the message has successfully arrived (although the definition of successful can vary a bit). We've seen this before in HTTP, where successful acknowledgement takes the form of a 202 Accepted response.

    There's a number of reasons then why a service might appear to have deadlocked. The service could simply be really slow and has either not accepted the connection or not finished processing to the point where it can send the acknowledgement. Now that you know that the one-way call can block, it takes more sleuthing before you can declare that a deadlock is taking place. The operation call could be held in limbo for a while because of the ConcurrencyMode setting. Setting ConcurrencyMode to Single prevents the service from taking a second call in while an existing call is underway. The operation call could also be waiting for the SynchronizationContext to become free. Basically, the problem with the one-way call could be almost any of the problems that you'd associate with a standard bidirectional call. Applying OneWay attributes to your calls does not get you out of having to debug your service deadlocks.

    Next time: How HostNameComparisonMode Works

  • Nicholas Allen's Indigo Blog

    TransportWithMessageCredential Over TCP

    • 4 Comments

    After switching from message security to transport security, I'm seeing a bunch of weird protocols being used in message exchanges (even when the credentials are still at the message level). What's going on? Also, why do I need to provide a certificate for the server? HTTPS doesn't make me do this.

    WCF has several negotiation protocols that it can use to exchange credentials. When using message security, these protocols are typically implemented at the SOAP level running through the WS-Security family of standards. With transport-level security, the client and service have to securely exchange credentials on their own. The negotiation protocol that transport security uses is going to depend on the type of identifying token being exchanged. For example, the exchange of Windows credentials has a different protocol than the exchange of UserNamePassword credentials. The details of the individual negotiation protocols aren't relevant here because they all ultimately provide transport protection of the exchange (although it is simple to find the details of protocols like SPNego).

    A certificate is needed when the protocol must authenticate the service before having a secure channel to send the token. Unless you have a scheme similar to that of SSL-enabled web sites, you need some out-of-band way of providing that certificate to the client.

    Next time: Avoiding OneWay Deadlocks

  • Nicholas Allen's Indigo Blog

    Configuring HTTP for Windows Vista

    • 21 Comments

    One of the major changes in Windows Vista security is that most people are no longer going to be running with Administrator privileges by default like they were doing on earlier platforms. This impacts your ability to run HTTP web services because listening at a particular HTTP address is a restricted operation. By default, every HTTP path is reserved for use by the system administrator. Your services will fail to start with an AddressAccessDeniedException if you aren't running the service from an elevated account. Windows 2003 includes a tool called httpcfg.exe that lets the owner of an HTTP namespace delegate that ownership to another user. On Windows Vista, httpcfg.exe is no longer included and instead there's a new command set available through netsh.exe.

    I'm going to walk through delegating part of the HTTP namespace to get a web service working that wants to listen at http://localhost:8000/. Since I'm not running as the Administrator when debugging in Visual Studio, the service fails to start when I run it.

    HTTP could not register URL http://+:8000/. Your process does not have access rights to 
    this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details).

    The plus sign in the URL just means that there's a wildcard being applied to the hostname. I'll have another article that talks about wildcards in more detail. To fix this problem, I first need to start a command prompt using "Run as administrator" so that I have elevated privileges. Then, I can use netsh.exe to give some of the Administrator's HTTP namespace to my user account. You can look at the existing HTTP namespace delegations by using "netsh http show urlacl". There should be several namespaces set up by default, including the default one that WCF uses for temporary addresses as an example.

    Reserved URL            : http://+:80/Temporary_Listen_Addresses/
    User: \Everyone
    Listen: Yes
    Delegate: No
    SDDL: D:(A;;GX;;;WD)

    Now, I'm going to use "netsh http add urlacl url=http://+:8000/ user=MYMACHINE\UserName" to assign some of the HTTP namespace to my user account. You can get the syntax for all of these commands by running "netsh http" without any arguments. Note that I've matched the URL in this command to the URL that appeared in the error message. The wildcarding is important for getting the right reservation and you'll continue to be denied access if your reservation covers less than your service's attempted registration. Going back to Visual Studio, my service now starts up and runs as expected.

    For more details on netsh options, see the article on namespace reservation permissions.

    Next time: TransportWithMessageCredential Over TCP

  • Nicholas Allen's Indigo Blog

    Use OneWay for Long-Running Operations

    • 3 Comments

    I have a long-running service operation that needs to receive a response. What options do I have for designing my web services?

    The problem that most people run into with long-running operations is that the operation eventually hits some quota value and times out. For instance, if you want to run the operation over an HTTP connection, you have to configure the HTTP transport to not give up waiting before the operation completes. This configuration consists of setting the send and receive timeouts of the channel to some maximum bound for the operation time. Setting the timeouts correctly requires you to know what a maximum bound is for your operation. If you don't know of a maximum bound or the expected operation time is poorly defined, then it becomes very difficult to set a reasonable quota value. Quota values that are too small cause the operation to be unnecessarily aborted. Quota values that are too large weaken your security and makes you more vulnerable to malicious attackers tying up your resources.

    Large operation times also mean that you're more susceptible to dropped connections or network glitches that cause the operation to be aborted. One alternative to holding a transport channel open during the entire operation is to use an asynchronous delivery mechanism (this shouldn't be confused with asynchronous method calls). Asynchronous delivery, such as a queue, allows you to drop messages off without having a continuously stable network connection to the other party. Reliable messaging is also a way to recover from dropped connections. However, neither of these methods helps you bound your maximum operation time for request-reply style messaging.

    The basic solution to unbounding your operation time is to decouple the request and response of the operation into a pair of one-way operations. A one-way operation on the service is used to receive the request and a one-way operation on the client is used to receive the response. Correlation is still needed to associate the request and response together. This correlation is no longer defined by the use of a single network connection but instead is some property associated with the message. Each one-way call has a well-bounded and predictably short operation time.

    The one-way model is not perfect. Transports that do not support symmetric message transfer, such as HTTP, are harder to use because you may need to address client machines and poke holes through firewalls. However, that is a tradeoff you may need to make against trying to hold an HTTP connection open for hours on your server until the operation finishes.

    If both the client and server are built using WCF and you have a bidirectional transport, then you can use the CallbackContract attribute of the service contract to define a linkage between a service located on the server and a service located back on the client. Using a callback contract makes the programming model easier to use, but you could always build the equivalent functionality yourself if you had to.

    Next time: Configuring HTTP for Windows Vista

  • Nicholas Allen's Indigo Blog

    Impersonating with Windows Security

    • 3 Comments

    I have a service that uses Windows authentication and want to impersonate the caller in one of the service operations. How should I configure the client and service?

    There are a couple of things you need to do here to make this work.

    On the client side, you need to give the client proxy your Windows credentials and permit it to perform the impersonation. The exact level of impersonation you need is something determined by your application. I'm using plain old Impersonate level impersonation in this example.

    client.ClientCredentials.Windows.ClientCredential.Domain = "MYDOMAIN";
    client.ClientCredentials.Windows.ClientCredential.UserName = "User";
    client.ClientCredentials.Windows.ClientCredential.Password = "Password";
    client.ClientCredentials.Windows.AllowedImpersonationLevel =
    System.Security.Principal.TokenImpersonationLevel.Impersonation;

    Your service process needs the SeImpersonatePrivilege.

    Your operation needs to perform the impersonation. If you just want to impersonate for part of the operation, you can scope the impersonation block.

    using (ServiceSecurityContext.Current.WindowsIdentity.Impersonate())
    {
    ...
    }

    You'd probably want to have some error handling for that block. If you want to impersonate for all of the operation, you can add an attribute to modify the operation behavior.

    [OperationBehavior(Impersonation = ImpersonationOption.Required)]

    Finally, you can add a service behavior to instruct the service to impersonate the caller for all operations when allowed.

    host.Description.Behaviors.Find<ServiceAuthorizationBehavior>().ImpersonateCallerForAllOperations = true;

    Next time: Use OneWay for Long-Running Operations

  • Nicholas Allen's Indigo Blog

    Out of Time

    • 0 Comments

    What time zone does this server use?

    I'm not sure if there's a precise name for this server's time zone, but it seems to be a proprietary combination of Eastern, Pacific, GMT, UTC, and Martian Standard Time. Sometimes, more than one of these time zones is in play at once. For instance, I queue posts to go live at 5 AM Pacific, which sometimes appears as 8 AM or 1 PM depending on how many times you refresh the page. As far as I can tell, yesterday's post problems were because 5 AM simply didn't happen at its normal time. 9 AM also didn't work, 9:30 AM had intermittent issues, but full temporal continuity was restored by 10 AM.

    If your news reader has lost the article Which Client Credential Does TransportWithMessageCredential Use? in the murky streams of times, that link should restore you to the proper location.

  • Nicholas Allen's Indigo Blog

    Which Client Credential Does TransportWithMessageCredential Use?

    • 2 Comments

    I'm trying to use a Certificate credential with security mode TransportWithMessageCredential. Certificate credentials were working with transport security but now my clients can't connect. Why isn't this working?

    This one is fairly quick to diagnose if you look at what your service is telling you. I've set up a service with the WSHttpBinding and the security mode set to TransportWithMessageCredential. What's probably gone wrong is that the configuration is setting the transport client credential type to Certificate.

    binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;

    Let's look at what the service metadata says that is doing.

    <wsHttpBinding>
    <binding name="WSHttpBinding_Service" closeTimeout="00:01:00"
    openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
    bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
    maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
    messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
    allowCookies="false">
    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
    maxBytesPerRead="4096" maxNameTableCharCount="16384" />
    <reliableSession ordered="true" inactivityTimeout="00:10:00"
    enabled="false" />
    <security mode="TransportWithMessageCredential">
    <transport clientCredentialType="None" proxyCredentialType="None"
    realm="" />
    <message clientCredentialType="Windows" negotiateServiceCredential="true"
    algorithmSuite="Default" establishSecurityContext="true" />
    </security>
    </binding>
    </wsHttpBinding>

    Hmm, the transport client credential type seems to be ignoring the setting and the message client credential type is Windows.

    What's going on is that TransportWithMessageCredential only uses the message security credentials. The transport (HTTPS in this case) is locked to anonymous authentication and the message credentials are using Windows because that's the default if you don't change any of the settings. The mismatch between Windows and Certificate credentials are why the clients are failing. We can fix this by changing the client credential type line to set the message security credentials instead.

    binding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;

    Now, the security block in the metadata gives us exactly what we want.

    <security mode="TransportWithMessageCredential">
    <transport clientCredentialType="None" proxyCredentialType="None"
    realm="" />
    <message clientCredentialType="Certificate" negotiateServiceCredential="true"
    algorithmSuite="Default" establishSecurityContext="true" />
    </security>

    Next time: Impersonating with Windows Security

  • Nicholas Allen's Indigo Blog

    Correlating Message Identifiers

    • 2 Comments

    I'm trying to send a correlated exchange of messages using the MessageId to perform the correlation. The MessageId appears on the first message but it looks like I have to copy it around manually because it's not on the reply messages. Why isn't this working?

    Let's look at a simple message exchange using MessageId to see what is happening here. I'll use the WSHttpBinding to trigger the addition of a MessageId and a simple service contract with one operation. The operation has a request-reply exchange pattern over HTTP so we expect simple correlation between the request and reply messages.

    [ServiceContract]
    public interface Service
    {
    [OperationContract]
    string Operation(string parameter);
    }

    Inside the implementation for my operation, I'll print out the MessageId that's in OperationContext.Current.IncomingMessageHeaders. On the client side, I'll construct an OperationContextScope so that I get access to that same header on the return message.

    using (new OperationContextScope((IContextChannel)client.InnerChannel)) {
    client.Operation(parameter);
    Console.WriteLine("id: " + OperationContext.Current.IncomingMessageHeaders.MessageId);
    }

    As the questioner described, I get a MessageId header on the server that looks like urn:uuid:67a1b220-aaf2-4cbb-9273-92fb2af1d035 but I don't get any MessageId back in the response.

    The MessageId header only applies to the first message in the sequence. Subsequent messages are sent to the ReplyTo address of the endpoint and with the RelatesTo header set to the original MessageId. Let's put a second debug statement on the client and server.

    Console.WriteLine("re: " + OperationContext.Current.IncomingMessageHeaders.RelatesTo);

    We see that on the first message, the RelatesTo header is not set. On the response message, the RelatesTo header is set to my original MessageId. The solution to the question is that the automatic handling works by linking the MessageId header to RelatesTo in successive messages. You should be looking at the RelatesTo header for your correlation.

    Next time: Which Client Credential Does TransportWithMessageCredential Use?

  • Nicholas Allen's Indigo Blog

    Reader Mail

    • 1 Comments

    This article series summarizes the answers to WCF questions I've seen in the last few months. There are ten questions for ten days on a variety of topics. I've changed the actual questions to remove any identifying information or distractions.

    Some of the topics are the length of a normal post; others are shorter. I'm going to continue doing this regularly once or twice a week in the future. After I see how this mini-marathon works out, I'll figure out whether to run the shorter posts doubled up. I probably have 40 or 50 of these questions that I can do so far. I'm more worried about the amount of time it takes to write the posts than running out of topics.

    All of these posts will be tracked with the new Answers tag. Think of this as a tag for marking answers to task-related questions. I went back and populated the tag with a collection of past posts that were based on bug reports or questions.

    Available now: Correlating Message Identifiers

  • Nicholas Allen's Indigo Blog

    A Problem with Large Faults

    • 1 Comments

    Fault messages have their own quota sizes, allowing you to limit the amount of data you'll process in response to an error. Existing transport protocols don't know about SOAP fault messages, but some of them have their own error reporting mechanisms that we use for similar purposes. HTTP lets you send back error responses, and there is a similar quota setting for HTTP web requests that let you bound the size of the response.

    The point of writing this is that the translation doesn't always go smoothly between these quota systems. Our SOAP quotas are measured in bytes while the HTTP quotas need to be rounded up in size to the next largest value measured in kilobytes. Someone recognized that this means that there's a potential for integer overflow in the conversion and so handled that case specially. We take the maximum buffer size and use that to bound your maximum HTTP error size. For buffered transfers, the maximum buffer size is the same thing as the maximum received message size while for streamed transfers, the total message can span many buffered transfers. Unfortunately, the way that overflow is avoided in the computation causes the HTTP quota to not be set in that case. This means that if your maximum buffer size is within 1 KB of Int.MaxValue, the maximum error size doesn't get increased.

    There's a couple of solutions that you can use here. The default allowed error size is 64 KB so this bug doesn't matter at all if your error messages are smaller than this size. From a practical perspective, there isn't much difference between using Int.MaxValue or a slightly smaller value for the buffer size, so you can avoid the bug by reducing your maximum buffer size by 1025 bytes. It's also possible to directly set the default maximum error size. This is the DefaultMaximumErrorResponseLength setting of HttpWebRequest. Changing this value affects all HTTP traffic in your application, but if you know what your maximum limit is, you can set this and we won't ever use a smaller value.

    Next time: Reader Mail

  • Nicholas Allen's Indigo Blog

    Some Upgrade Growing Pains

    • 0 Comments

    I've managed by now to work out most of the difficulties of moving to the new server software. There have been some changes that primarily affect people reading these posts through their web browser (that's about 33% of you last month). It looks like the calendar of posts is gone for good and the new tagging system is here to stay. I managed to disable the more obnoxious parts of the tag cloud interface so I think that's fine. The navigation by filtering on tags is actually pretty nice. You can drill down categories in any order now, so clicking on Bindings and then adding a filter for HTTP has the same result as clicking on HTTP and then adding a filter for Bindings. I have noticed that the stylesheet is frequently not loading, giving you the base MSDN style for pages. You can usually fix this by refreshing a few times until the server sends it to you. I'm assuming this is a server load problem.

    The changes that affect everyone include having my autoposter once again be slightly incompatible with the form submission process. This time, it broke setting the AM/PM time setting, causing yesterday morning's post to be queued for 5 PM instead of 5 AM. That actually isn't the first time this problem has happened. I fixed this when I noticed it, but unfortunately people are so conditioned to the time that having the post come in late causes some people to miss it. I analyzed my long term history and found that a post even just a few hours late is expected to miss about 3000 readers. Here's Part 5 of the stream upgrade sample if your aggregator missed it.

  • Nicholas Allen's Indigo Blog

    ROT 128 Stream Upgrade Sample, Part 5

    • 2 Comments

    Today is the last part of the stream upgrade sample. We've already looked at all the parts required to make the stream upgrade operate: stream, binding element, provider, initiator, and acceptor. All that's left today is to build a test harness for the code and run it. Here are the previous parts of this series for reference. It's taken a little longer than I expected to get this out due to some server difficulties.

    1. Stream class
    2. Binding element
    3. Provider
    4. Initiator and acceptor

    I've created a simple service with one operation for demonstration purposes.

    using System;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;

    namespace Microsoft.ServiceModel.Samples
    {
    [ServiceContract]
    interface ICalculator
    {
    [OperationContract]
    int Add(int x, int y);
    }

    class CalculatorService : ICalculator
    {
    public int Add(int x, int y)
    {
    return x + y;
    }
    }

    class service
    {
    static void Main(string[] args)
    {
    Binding binding = new CustomBinding(
    new ROT128StreamUpgradeBindingElement(),
    new TextMessageEncodingBindingElement(),
    new TcpTransportBindingElement()
    );
    ServiceHost host = new ServiceHost(typeof(CalculatorService), new Uri("net.tcp://localhost/"));
    host.AddServiceEndpoint(typeof(ICalculator), binding, "");
    ServiceMetadataBehavior mexBehavior = new ServiceMetadataBehavior();
    host.Description.Behaviors.Add(mexBehavior);
    Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
    host.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, "mex");
    host.Open();
    Console.WriteLine("Press <ENTER> to quit.");
    Console.ReadLine();
    host.Close();
    }
    }
    }

    I've enabled metadata so you can generate the client proxy yourself using svcutil. The attachments to this post contain one such proxy. The client just connects to the service, makes one operation call, and then closes the connection.

    using System;
    using System.ServiceModel;
    using System.ServiceModel.Channels;

    namespace Microsoft.ServiceModel.Samples
    {
    class client
    {
    static void Main(string[] args)
    {
    Binding binding = new CustomBinding(
    new ROT128StreamUpgradeBindingElement(),
    new TextMessageEncodingBindingElement(),
    new TcpTransportBindingElement()
    );
    CalculatorClient client = new CalculatorClient(binding, new EndpointAddress("net.tcp://localhost/"));
    client.Open();
    if (client.Add(1, 2) != 3)
    {
    throw new Exception("Operation call failed!");
    }
    client.Close();
    }
    }
    }

    Let's start by looking at what happens on the client side. Remember that the debug output is at the start of the Read or Write method of the stream. That means that when writing, we see the stream before rotation. When reading, we see the stream after one rotation. You can directly match up the reads and writes between the two sides.

    [WRITE] 1 bytes
    0C .
    [READ] 1 bytes
    8B .
    [WRITE] 512 bytes
    06 FD 03 3C 73 3A 45 6E 76 65 6C 6F 70 65 20 ...<s:Envelope
    78 6D 6C 6E 73 3A 73 3D 22 68 74 74 70 3A 2F xmlns:s="http:/
    2F 77 77 77 2E 77 33 2E 6F 72 67 2F 32 30 30 /www.w3.org/200
    33 2F 30 35 2F 73 6F 61 70 2D 65 6E 76 65 6C 3/05/soap-envel
    6F 70 65 22 20 78 6D 6C 6E 73 3A 61 3D 22 68 ope" xmlns:a="h
    74 74 70 3A 2F 2F 77 77 77 2E 77 33 2E 6F 72 ttp://www.w3.or
    67 2F 32 30 30 35 2F 30 38 2F 61 64 64 72 65 g/2005/08/addre
    73 73 69 6E 67 22 3E 3C 73 3A 48 65 61 64 65 ssing"><s:Heade
    72 3E 3C 61 3A 41 63 74 69 6F 6E 20 73 3A 6D r><a:Action s:m
    75 73 74 55 6E 64 65 72 73 74 61 6E 64 3D 22 ustUnderstand="
    31 22 3E 68 74 74 70 3A 2F 2F 74 65 6D 70 75 1">http://tempu
    72 69 2E 6F 72 67 2F 49 43 61 6C 63 75 6C 61 ri.org/ICalcula
    74 6F 72 2F 41 64 64 3C 2F 61 3A 41 63 74 69 tor/Add</a:Acti
    6F 6E 3E 3C 61 3A 4D 65 73 73 61 67 65 49 44 on><a:MessageID
    3E 75 72 6E 3A 75 75 69 64 3A 35 39 66 63 39 >urn:uuid:59fc9
    66 63 39 2D 65 33 65 32 2D 34 62 63 31 2D 62 fc9-e3e2-4bc1-b
    36 33 32 2D 34 61 33 38 39 39 30 32 34 61 62 632-4a3899024ab
    39 3C 2F 61 3A 4D 65 73 73 61 67 65 49 44 3E 9</a:MessageID>
    3C 61 3A 52 65 70 6C 79 54 6F 3E 3C 61 3A 41 <a:ReplyTo><a:A
    64 64 72 65 73 73 3E 68 74 74 70 3A 2F 2F 77 ddress>http://w
    77 77 2E 77 33 2E 6F 72 67 2F 32 30 30 35 2F ww.w3.org/2005/
    30 38 2F 61 64 64 72 65 73 73 69 6E 67 2F 61 08/addressing/a
    6E 6F 6E 79 6D 6F 75 73 3C 2F 61 3A 41 64 64 nonymous</a:Add
    72 65 73 73 3E 3C 2F 61 3A 52 65 70 6C 79 54 ress></a:ReplyT
    6F 3E 3C 61 3A 54 6F 20 73 3A 6D 75 73 74 55 o><a:To s:mustU
    6E 64 65 72 73 74 61 6E 64 3D 22 31 22 3E 6E nderstand="1">n
    65 74 2E 74 63 70 3A 2F 2F 6C 6F 63 61 6C 68 et.tcp://localh
    6F 73 74 2F 3C 2F 61 3A 54 6F 3E 3C 2F 73 3A ost/</a:To></s:
    48 65 61 64 65 72 3E 3C 73 3A 42 6F 64 79 3E Header><s:Body>
    3C 41 64 64 20 78 6D 6C 6E 73 3D 22 68 74 74 <Add xmlns="htt
    70 3A 2F 2F 74 65 6D 70 75 72 69 2E 6F 72 67 p://tempuri.org
    2F 22 3E 3C 78 3E 31 3C 2F 78 3E 3C 79 3E 32 /"><x>1</x><y>2
    3C 2F 79 3E 3C 2F 41 64 64 3E 3C 2F 73 3A 42 </y></Add></s:B
    6F 64 79 3E 3C 2F 73 3A 45 6E 76 65 6C 6F 70 ody></s:Envelop
    65 3E e>
    [READ] 478 bytes
    86 5B 83 BC F3 BA C5 EE F6 E5 EC EF F0 E5 A0 .[.............
    F8 ED EC EE F3 BA F3 BD A2 E8 F4 F4 F0 BA AF ...............
    AF F7 F7 F7 AE F7 B3 AE EF F2 E7 AF B2 B0 B0 ...............
    B3 AF B0 B5 AF F3 EF E1 F0 AD E5 EE F6 E5 EC ...............
    EF F0 E5 A2 A0 F8 ED EC EE F3 BA E1 BD A2 E8 ...............
    F4 F4 F0 BA AF AF F7 F7 F7 AE F7 B3 AE EF F2 ...............
    E7 AF B2 B0 B0 B5 AF B0 B8 AF E1 E4 E4 F2 E5 ...............
    F3 F3 E9 EE E7 A2 BE BC F3 BA C8 E5 E1 E4 E5 ...............
    F2 BE BC E1 BA C1 E3 F4 E9 EF EE A0 F3 BA ED ...............
    F5 F3 F4 D5 EE E4 E5 F2 F3 F4 E1 EE E4 BD A2 ...............
    B1 A2 BE E8 F4 F4 F0 BA AF AF F4 E5 ED F0 F5 ...............
    F2 E9 AE EF F2 E7 AF C9 C3 E1 EC E3 F5 EC E1 ...............
    F4 EF F2 AF C1 E4 E4 D2 E5 F3 F0 EF EE F3 E5 ...............
    BC AF E1 BA C1 E3 F4 E9 EF EE BE BC E1 BA D2 ...............
    E5 EC E1 F4 E5 F3 D4 EF BE F5 F2 EE BA F5 F5 ...............
    E9 E4 BA B5 B9 E6 E3 B9 E6 E3 B9 AD E5 B3 E5 ...............
    B2 AD B4 E2 E3 B1 AD E2 B6 B3 B2 AD B4 E1 B3 ...............
    B8 B9 B9 B0 B2 B4 E1 E2 B9 BC AF E1 BA D2 E5 ...............
    EC E1 F4 E5 F3 D4 EF BE BC E1 BA D4 EF A0 F3 ...............
    BA ED F5 F3 F4 D5 EE E4 E5 F2 F3 F4 E1 EE E4 ...............
    BD A2 B1 A2 BE E8 F4 F4 F0 BA AF AF F7 F7 F7 ...............
    AE F7 B3 AE EF F2 E7 AF B2 B0 B0 B5 AF B0 B8 ...............
    AF E1 E4 E4 F2 E5 F3 F3 E9 EE E7 AF E1 EE EF ...............
    EE F9 ED EF F5 F3 BC AF E1 BA D4 EF BE BC AF ...............
    F3 BA C8 E5 E1 E4 E5 F2 BE BC F3 BA C2 EF E4 ...............
    F9 BE BC C1 E4 E4 D2 E5 F3 F0 EF EE F3 E5 A0 ...............
    F8 ED EC EE F3 BD A2 E8 F4 F4 F0 BA AF AF F4 ...............
    E5 ED F0 F5 F2 E9 AE EF F2 E7 AF A2 BE BC C1 ...............
    E4 E4 D2 E5 F3 F5 EC F4 BE B3 BC AF C1 E4 E4 ...............
    D2 E5 F3 F5 EC F4 BE BC AF C1 E4 E4 D2 E5 F3 ...............
    F0 EF EE F3 E5 BE BC AF F3 BA C2 EF E4 F9 BE ...............
    BC AF F3 BA C5 EE F6 E5 EC EF F0 E5 BE .............
    [WRITE] 1 bytes
    07 .
    [READ] 1 bytes
    87 .

    The start of each block is part of our framing format. You can clearly see the three sets of calls. The first set are single bytes for the end of connection establishment. The second set are the actual transfers for the service call. You can see the request on this side but the reply is obscured by the stream rotation as we have not yet undone the transformation. The third set are single bytes for connection termination.

    Here's the same view showing what the server received and sent. Everything that was obscured on the client side is visible on the server side and vice versa.

    Press <ENTER> to quit.
    [READ] 1 bytes
    8C .
    [WRITE] 1 bytes
    0B .
    [READ] 512 bytes
    86 7D 83 BC F3 BA C5 EE F6 E5 EC EF F0 E5 A0 .}.............
    F8 ED EC EE F3 BA F3 BD A2 E8 F4 F4 F0 BA AF ...............
    AF F7 F7 F7 AE F7 B3 AE EF F2 E7 AF B2 B0 B0 ...............
    B3 AF B0 B5 AF F3 EF E1 F0 AD E5 EE F6 E5 EC ...............
    EF F0 E5 A2 A0 F8 ED EC EE F3 BA E1 BD A2 E8 ...............
    F4 F4 F0 BA AF AF F7 F7 F7 AE F7 B3 AE EF F2 ...............
    E7 AF B2 B0 B0 B5 AF B0 B8 AF E1 E4 E4 F2 E5 ...............
    F3 F3 E9 EE E7 A2 BE BC F3 BA C8 E5 E1 E4 E5 ...............
    F2 BE BC E1 BA C1 E3 F4 E9 EF EE A0 F3 BA ED ...............
    F5 F3 F4 D5 EE E4 E5 F2 F3 F4 E1 EE E4 BD A2 ...............
    B1 A2 BE E8 F4 F4 F0 BA AF AF F4 E5 ED F0 F5 ...............
    F2 E9 AE EF F2 E7 AF C9 C3 E1 EC E3 F5 EC E1 ...............
    F4 EF F2 AF C1 E4 E4 BC AF E1 BA C1 E3 F4 E9 ...............
    EF EE BE BC E1 BA CD E5 F3 F3 E1 E7 E5 C9 C4 ...............
    BE F5 F2 EE BA F5 F5 E9 E4 BA B5 B9 E6 E3 B9 ...............
    E6 E3 B9 AD E5 B3 E5 B2 AD B4 E2 E3 B1 AD E2 ...............
    B6 B3 B2 AD B4 E1 B3 B8 B9 B9 B0 B2 B4 E1 E2 ...............
    B9 BC AF E1 BA CD E5 F3 F3 E1 E7 E5 C9 C4 BE ...............
    BC E1 BA D2 E5 F0 EC F9 D4 EF BE BC E1 BA C1 ...............
    E4 E4 F2 E5 F3 F3 BE E8 F4 F4 F0 BA AF AF F7 ...............
    F7 F7 AE F7 B3 AE EF F2 E7 AF B2 B0 B0 B5 AF ...............
    B0 B8 AF E1 E4 E4 F2 E5 F3 F3 E9 EE E7 AF E1 ...............
    EE EF EE F9 ED EF F5 F3 BC AF E1 BA C1 E4 E4 ...............
    F2 E5 F3 F3 BE BC AF E1 BA D2 E5 F0 EC F9 D4 ...............
    EF BE BC E1 BA D4 EF A0 F3 BA ED F5 F3 F4 D5 ...............
    EE E4 E5 F2 F3 F4 E1 EE E4 BD A2 B1 A2 BE EE ...............
    E5 F4 AE F4 E3 F0 BA AF AF EC EF E3 E1 EC E8 ...............
    EF F3 F4 AF BC AF E1 BA D4 EF BE BC AF F3 BA ...............
    C8 E5 E1 E4 E5 F2 BE BC F3 BA C2 EF E4 F9 BE ...............
    BC C1 E4 E4 A0 F8 ED EC EE F3 BD A2 E8 F4 F4 ...............
    F0 BA AF AF F4 E5 ED F0 F5 F2 E9 AE EF F2 E7 ...............
    AF A2 BE BC F8 BE B1 BC AF F8 BE BC F9 BE B2 ...............
    BC AF F9 BE BC AF C1 E4 E4 BE BC AF F3 BA C2 ...............
    EF E4 F9 BE BC AF F3 BA C5 EE F6 E5 EC EF F0 ...............
    E5 BE ..
    [WRITE] 478 bytes
    06 DB 03 3C 73 3A 45 6E 76 65 6C 6F 70 65 20 ...<s:Envelope
    78 6D 6C 6E 73 3A 73 3D 22 68 74 74 70 3A 2F xmlns:s="http:/
    2F 77 77 77 2E 77 33 2E 6F 72 67 2F 32 30 30 /www.w3.org/200
    33 2F 30 35 2F 73 6F 61 70 2D 65 6E 76 65 6C 3/05/soap-envel
    6F 70 65 22 20 78 6D 6C 6E 73 3A 61 3D 22 68 ope" xmlns:a="h
    74 74 70 3A 2F 2F 77 77 77 2E 77 33 2E 6F 72 ttp://www.w3.or
    67 2F 32 30 30 35 2F 30 38 2F 61 64 64 72 65 g/2005/08/addre
    73 73 69 6E 67 22 3E 3C 73 3A 48 65 61 64 65 ssing"><s:Heade
    72 3E 3C 61 3A 41 63 74 69 6F 6E 20 73 3A 6D r><a:Action s:m
    75 73 74 55 6E 64 65 72 73 74 61 6E 64 3D 22 ustUnderstand="
    31 22 3E 68 74 74 70 3A 2F 2F 74 65 6D 70 75 1">http://tempu
    72 69 2E 6F 72 67 2F 49 43 61 6C 63 75 6C 61 ri.org/ICalcula
    74 6F 72 2F 41 64 64 52 65 73 70 6F 6E 73 65 tor/AddResponse
    3C 2F 61 3A 41 63 74 69 6F 6E 3E 3C 61 3A 52 </a:Action><a:R
    65 6C 61 74 65 73 54 6F 3E 75 72 6E 3A 75 75 elatesTo>urn:uu
    69 64 3A 35 39 66 63 39 66 63 39 2D 65 33 65 id:59fc9fc9-e3e
    32 2D 34 62 63 31 2D 62 36 33 32 2D 34 61 33 2-4bc1-b632-4a3
    38 39 39 30 32 34 61 62 39 3C 2F 61 3A 52 65 899024ab9</a:Re
    6C 61 74 65 73 54 6F 3E 3C 61 3A 54 6F 20 73 latesTo><a:To s
    3A 6D 75 73 74 55 6E 64 65 72 73 74 61 6E 64 :mustUnderstand
    3D 22 31 22 3E 68 74 74 70 3A 2F 2F 77 77 77 ="1">http://www
    2E 77 33 2E 6F 72 67 2F 32 30 30 35 2F 30 38 .w3.org/2005/08
    2F 61 64 64 72 65 73 73 69 6E 67 2F 61 6E 6F /addressing/ano
    6E 79 6D 6F 75 73 3C 2F 61 3A 54 6F 3E 3C 2F nymous</a:To></
    73 3A 48 65 61 64 65 72 3E 3C 73 3A 42 6F 64 s:Header><s:Bod
    79 3E 3C 41 64 64 52 65 73 70 6F 6E 73 65 20 y><AddResponse
    78 6D 6C 6E 73 3D 22 68 74 74 70 3A 2F 2F 74 xmlns="http://t
    65 6D 70 75 72 69 2E 6F 72 67 2F 22 3E 3C 41 empuri.org/"><A
    64 64 52 65 73 75 6C 74 3E 33 3C 2F 41 64 64 ddResult>3</Add
    52 65 73 75 6C 74 3E 3C 2F 41 64 64 52 65 73 Result></AddRes
    70 6F 6E 73 65 3E 3C 2F 73 3A 42 6F 64 79 3E ponse></s:Body>
    3C 2F 73 3A 45 6E 76 65 6C 6F 70 65 3E </s:Envelope>
    [READ] 1 bytes
    87 .
    [WRITE] 1 bytes
    07 .

    Next time: A Problem with Large Faults

  • Nicholas Allen's Indigo Blog

    ROT 128 Stream Upgrade Sample, Part 4

    • 2 Comments

    The final pieces needed for the ROT 128 sample are a stream upgrade initiator and a stream upgrade acceptor. The initiator starts the upgrade process by providing an upgrade type string from GetNextUpgrade. I've coded this so that the initiator and acceptor share a type string that is stored as a static member back on the binding element. You can produce or store your upgrade type string however you want. This is an opaque string to the runtime.

    using System;
    using System.IO;
    using System.ServiceModel;
    using System.ServiceModel.Channels;

    namespace Microsoft.ServiceModel.Samples
    {
    class InitiateAsyncResult : TypedCompletedAsyncResult<Stream>
    {
    internal InitiateAsyncResult(Stream stream, AsyncCallback callback, object state)
    : base(stream, callback, state)
    {
    }
    }

    class ROT128StreamUpgradeInitiator : StreamUpgradeInitiator
    {
    ROT128StreamUpgradeProvider provider;
    string nextUpgrade = ROT128StreamUpgradeBindingElement.ROT128UpgradeType;

    internal ROT128StreamUpgradeInitiator(ROT128StreamUpgradeProvider provider, EndpointAddress remoteAddress, Uri via)
    : base()
    {
    this.provider = provider;
    }

    public override IAsyncResult BeginInitiateUpgrade(Stream stream, AsyncCallback callback, object state)
    {
    return new InitiateAsyncResult(new ROT128Stream(stream), callback, state);
    }

    public override Stream EndInitiateUpgrade(IAsyncResult result)
    {
    return ((InitiateAsyncResult)result).Data;
    }

    public override string GetNextUpgrade()
    {
    string result = nextUpgrade;
    nextUpgrade = null;
    return result;
    }

    public override Stream InitiateUpgrade(Stream stream)
    {
    return new ROT128Stream(stream);
    }
    }
    }

    Each time that GetNextUpgrade is called, you're expected to provide a different upgrade type string if you support multiple upgrades. Once you're out of upgrade types to suggest, GetNextUpgrade should return null forever afterwards. This sample only supports a single upgrade type. At some point in the future, after the upgrade is accepted, you'll get a call on InitiateUpgrade to actually perform the Stream transformation. There's no type string given to InitiateUpgrade so, implicitly, calling InitiateUpgrade means to perform the upgrade for the last upgrade type that you passed out of GetNextUpgrade.

    To get to the point where you're transforming Streams, you first need to make it through the upgrade acceptor. The upgrade acceptor takes an upgrade type string argument to the CanUpgrade method and returns whether it recognizes this upgrade type. If CanUpgrade returns false, then the upgrade fails and the connection cannot continue. If CanUpgrade returns true, then at some point in the future you'll get a call on AcceptUpgrade to transform the Stream on this end of the connection. Again, there's no type string given to AcceptUpgrade, so you need to know the upgrade type that you've just accepted.

    using System;
    using System.IO;
    using System.ServiceModel.Channels;

    namespace Microsoft.ServiceModel.Samples
    {
    class AcceptAsyncResult : TypedCompletedAsyncResult<Stream>
    {
    internal AcceptAsyncResult(Stream stream, AsyncCallback callback, object state)
    : base(stream, callback, state)
    {
    }
    }

    class ROT128StreamUpgradeAcceptor : StreamUpgradeAcceptor
    {
    internal ROT128StreamUpgradeAcceptor(ROT128StreamUpgradeProvider provider)
    : base()
    {
    }

    public override Stream AcceptUpgrade(Stream stream)
    {
    return new ROT128Stream(stream);
    }

    public override IAsyncResult BeginAcceptUpgrade(Stream stream, AsyncCallback callback, object state)
    {
    return new AcceptAsyncResult(new ROT128Stream(stream), callback, state);
    }

    public override bool CanUpgrade(string contentType)
    {
    return contentType == ROT128StreamUpgradeBindingElement.ROT128UpgradeType;
    }

    public override Stream EndAcceptUpgrade(IAsyncResult result)
    {
    return ((AcceptAsyncResult)result).Data;
    }
    }
    }

    That's all there is to performing a stream upgrade. Next time, we'll look at what those debugging statements I embedded in the upgrade Stream class print during a service call.

    Next time: ROT 128 Stream Upgrade Sample, Part 5

  • Nicholas Allen's Indigo Blog

    You Know the Drill (Server Upgrade Attempt #2)

    • 0 Comments

    The second attempt to upgrade to the new version of Community Server will take place early Monday morning. This knocks out the time window for the normal Monday morning post. The stream upgrade sample will continue on Tuesday covering the stream upgrade initiator and acceptor. There should be little server downtime during the upgrade for readers but any posts or comments made during the migration will be lost.

    Update: Upgrade complete. I've tried to unbreak the layout changes that the new tag cloud feature caused. There's still some missing features that I'm waiting back from the administrative folks to find out if they're gone for good.

Page 1 of 1 (24 items)