I have a .NET Remoting service which works fine most of the time. If an exception or error happens, it logs the error to a file but still continues to run.
However, about once every two weeks the service stops responding to clients, which causes the client appication to crash with a SocketException with the following message:
A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond
No exception or stack trace is written to our log file, so I can't figure out where the service is crashing at, which leads me to believe that it is somewhere outside of my code which is failing. What additional steps can I take to figure out the root cause of this crash? I would imagine that it writes something to an EventLog somewhere, but I am not super familiar with Windows' Event Logging system so I'm not exactly sure where to look.
Thanks in advance for any assistance with this.
EDIT: Forgot to mention, stopping or restarting the service does nothing, the service never responds. I need to manually kill the process before I can start the service again.
EDIT 2:
public class ClientInfoServerSinkProvider :
IServerChannelSinkProvider
{
private IServerChannelSinkProvider _nextProvider = null;
public ClientInfoServerSinkProvider()
{
}
public ClientInfoServerSinkProvider(
IDictionary properties,
ICollection providerData)
{
}
public IServerChannelSinkProvider Next
{
get { return _nextProvider; }
set { _nextProvider = value; }
}
public IServerChannelSink CreateSink(IChannelReceiver channel)
{
IServerChannelSink nextSink = null;
if (_nextProvider != null)
{
nextSink = _nextProvider.CreateSink(channel);
}
return new ClientIPServerSink(nextSink);
}
public void GetChannelData(IChannelDataStore channelData)
{
}
}
public class ClientIPServerSink :
BaseChannelObjectWithProperties,
IServerChannelSink,
IChannelSinkBase
{
private IServerChannelSink _nextSink;
public ClientIPServerSink(IServerChannelSink next)
{
_nextSink = next;
}
public IServerChannelSink NextChannelSink
{
get { return _nextSink; }
set { _nextSink = value; }
}
public void AsyncProcessResponse(
IServerResponseChannelSinkStack sinkStack,
Object state,
IMessage message,
ITransportHeaders headers,
Stream stream)
{
IPAddress ip = headers[CommonTransportKeys.IPAddress] as IPAddress;
CallContext.SetData("ClientIPAddress", ip);
sinkStack.AsyncProcessResponse(message, headers, stream);
}
public Stream GetResponseStream(
IServerResponseChannelSinkStack sinkStack,
Object state,
IMessage message,
ITransportHeaders headers)
{
return null;
}
public ServerProcessing ProcessMessage(
IServerChannelSinkStack sinkStack,
IMessage requestMsg,
ITransportHeaders requestHeaders,
Stream requestStream,
out IMessage responseMsg,
out ITransportHeaders responseHeaders,
out Stream responseStream)
{
if (_nextSink != null)
{
IPAddress ip =
requestHeaders[CommonTransportKeys.IPAddress] as IPAddress;
CallContext.SetData("ClientIPAddress", ip);
ServerProcessing spres = _nextSink.ProcessMessage(
sinkStack,
requestMsg,
requestHeaders,
requestStream,
out responseMsg,
out responseHeaders,
out responseStream);
return spres;
}
else
{
responseMsg = null;
responseHeaders = null;
responseStream = null;
return new ServerProcessing();
}
}
This is like trying to find out why nobody picks up the phone when you call a friend. And the problem is that his house burned down to the ground. An imperfect view of what is going on is the core issue, especially bad with a service because there is so little to look at.
This can't get better until you use that telephone to talk to the service programmer and get him involved with the problem. Somebody is going to have to debug this. And yes, it will be difficult, failing once every two weeks might not be considered critical enough. Or too long to sit around waiting for it to happen. Only practical thing you can do to help is create a minidump of the process and pass that to the service programmer so he's got something to poke at. If the service runs on another machine then get the LAN admin involved as well.
The issue was due to a deadlock caused in my code, if memory serves I had two locking objects and I locked one from inside the other, essentially making them wait for each other. I was able to determine this by hooking up a debugger to the remote service.
Related
I am trying to find out when the TCP connection has been established while using HttpWebRequest, how these connections have been pooled and reused using ServicePoint.
I have looked at the system.dll, and tried to browse through the code using ILSpy and Reflector, somehow didn't see any references to sockets, establishing tcp connection etc.
Below I have pasted the decompiled code - can any please give me tips or redirect me so that I can understand:
When the TCP connection has been created?
How these connections are kept alive, pooled and reused using ServicePoint?
Code snippet from HttpWebRequest of System.dll:
public override Stream GetRequestStream()
{
TransportContext context;
return this.GetRequestStream(out context);
}
public Stream GetRequestStream(out TransportContext context)
{
if (Logging.On)
{
Logging.Enter(Logging.Web, this, "GetRequestStream", "");
}
context = null;
this.CheckProtocol(true);
if ((this._WriteAResult == null) || !this._WriteAResult.InternalPeekCompleted)
{
lock (this)
{
if (this._WriteAResult != null)
{
throw new InvalidOperationException(SR.GetString("net_repcall"));
}
if (this.SetRequestSubmitted())
{
throw new InvalidOperationException(SR.GetString("net_reqsubmitted"));
}
if (this._ReadAResult != null)
{
throw ((Exception) this._ReadAResult.Result);
}
this._WriteAResult = new LazyAsyncResult(this, null, null);
this.Async = false;
}
this.CurrentMethod = this._OriginVerb;
while (this.m_Retry && !this._WriteAResult.InternalPeekCompleted)
{
this._OldSubmitWriteStream = null;
this._SubmitWriteStream = null;
this.BeginSubmitRequest();
}
while (this.Aborted && !this._WriteAResult.InternalPeekCompleted)
{
if (!(this._CoreResponse is Exception))
{
Thread.SpinWait(1);
}
else
{
this.CheckWriteSideResponseProcessing();
}
}
}
ConnectStream connectStream = this._WriteAResult.InternalWaitForCompletion() as ConnectStream;
this._WriteAResult.EndCalled = true;
if (connectStream == null)
{
if (Logging.On)
{
Logging.Exception(Logging.Web, this, "EndGetRequestStream", this._WriteAResult.Result as Exception);
}
throw ((Exception) this._WriteAResult.Result);
}
context = new ConnectStreamContext(connectStream);
if (Logging.On)
{
Logging.Exit(Logging.Web, this, "GetRequestStream", connectStream);
}
return connectStream;
}
K, after browsing through code some time I think I kind of understood the abstractions. Basically servicepoint, servicepoint manager, how the tcp connection has been created, connections have been pooled, queued etc. always confused me. Below information kind of helped me - hopefully this is useful for others who are curious or tried to understand these details:
ServicePoint: High level abstraction of 'connection' to a particular host (destination Host Ip:port)
(that's why for ex, function static ServicePoint FindServicePoint(string host, int port) is defined in servicePointManger.
ServicePointManager: as the name indicates its the global (static) class which manages service points.
Connection (internal class): Basically this is the one I think that represents TCP connection. it basically derives from System.Net.PoolStream (internal class - it has the definitions of the sockets its using) which derives from stream.
ConnectionGroup (internal class): Each HttpWebRequest is associated with a connection group.
(basically based on connectionLimit it creates at most connectionLimit (can be configured globally through ServicePointManager, and also per httpwebrequest using its servicePoint property) number of connection objects per httpwebrequest)
If the connection limit is reached, its simply queued and passed to the wire (most likely - but still didn't get the code which does this).
And if you are connecting to service on the local machine, the servicepoint.connectionlimit no longer equal to servicepointmanager.defaultconnectionlimit. it defaults to; int.Maxvalue (2147483647 or 7FFFFFFF) ( you may refer to: http://blogs.microsoft.co.il/idof/2011/06/20/servicepointmanagerdefaultconnectionlimit-2-depends/ )
Update:
Looks like following two links are useful too:
System.Net.ServicePointManager.DefaultConnectionLimit and .MaxServicePointIdleTime
http://blogs.msdn.com/b/jpsanders/archive/2009/05/20/understanding-maxservicepointidletime-and-defaultconnectionlimit.aspx
Best Regards!
I've been trying to send a XML file from my WCF to my project without much luck. I have a Exception thrown from my program once the response is completed by WCF and sent to the Phone. I was hoping someone could please help me, as I have been looking around for an answer and found nothing. (The program uses XNA for a Windows Phone Applications)
[System.Net.WebException] {System.Net.WebException: The remote server returned an error: NotFound. ---> System.Net.WebException: The remote server returned an error: NotFound.
at System.Net.Browser.ClientHttpWebRequest.InternalEndGetResponse(IAsyncResult asyncResult)
at System.Net.Browser.ClientHttpWebRequest.<>c__DisplayClasse.<EndGetResponse>b__d(Object sendState)
at System.Net.Browser.AsyncHelper.<>c__DisplayClass1.<BeginOnUI>b__0(Object sendState)
--- End of inner exception stack trace ---
at System.Net.Browser.AsyncHelper.BeginOnUI(SendOrPostCallback beginMethod, Object state)
at System.Net.Browser.ClientHttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.CompleteGetResponse(IAsyncResult result)} System.Net.WebException
public string EndHighScoreList(System.IAsyncResult result) {
object[] _args = new object[0];
string _result = ((string)(base.EndInvoke("HighScoreList", _args, result)));
return _result;
}
IService.cs
[ServiceContract]
[XmlSerializerFormat]
public interface IService
{
[OperationContract]
void ParseScore(HighScore score);
[OperationContract]
string HighScoreList();
}
public class HighScore
{
[XmlElement]
public UInt32 m_rank;
[XmlAttribute]
public string m_name;
[XmlAttribute]
public UInt32 m_score;
}
Service.svc
public string HighScoreList()
{
XmlSerializer ser = new XmlSerializer(typeof(HighScore));
using (FileStream fs = new FileStream(HttpContext.Current.Server.MapPath("App_Data/Highscores.xml"), FileMode.OpenOrCreate))
{
return ser.Deserialize(fs).ToString();
}
}
Here's the requested code
void globalRecieve(object obj, DodgeService.HighScoreListCompletedEventArgs e)
{
try
{
string result = e.Result;
using (TextReader reader = new StringReader(result)){
XmlSerializer xml = new XmlSerializer(typeof(List<DodgeService.HighScore>));
foreach (DodgeService.HighScore sco in xml.Deserialize(reader) as List<DodgeService.HighScore>)
highScores.Add(sco);
}
}catch(Exception exception){
string error = exception.Message;
}
}
protected override void Initialize()
{
service = new DodgeService.ServiceClient("BasicHttpBinding_IService");
service.HighScoreListAsync(null);
service.HighScoreListCompleted += new EventHandler<DodgeService.HighScoreListCompletedEventArgs>(globalRecieve);
base.Initialize();
}
Personally I believe WCF sucks. The Configuration alone is a nightmare and if you change anything, you have to re-build your objects and any changes you've made you have to re-make.
You should migrate to ServiceStack. It handles everything for you. You only write the sending and receiving of business DTO objects. Sending and receiving files is basic stuff for it,
See this google search for several people asking similar questions but based on ServiceStack. Mythz is a project lead for ServiceStack and he answers their questions. It should get you started and you should see how EASY it is.
Just for future reference in case that google search doesn't give the same as I got, here is the search and first three responses;
"servicestack file upload"
Using ServiceStack to upload image files
How to use Servicestack PostFileWithRequest
ServiceStack client add attachment
The error says: "NotFound". It looks like the operation HighScoreList is not exposed / available at all. Try opening the path in your browser.
I kept having a Not Found error because, then the Windows Phone ran it was trying to connect to the service via LocalHost which wouldn't work as I needed it to connect to the development PC. The solution was to host the WCF service on a server and connect to the server or connect to the IP of the development PC.
Do you have any pointers how to determine when a subscription problem has occurred so I can reconnect?
My service uses RabbitMQ.Client.MessagePatterns.Subscription for it's subscription. After some time, my client silently stops receiving messages. I suspect network issues as I our VPN connection is not the most reliable.
I've read through the docs for awhile looking for a key to find out when this subscription might be broken due to a network issue without much luck. I've tried checking that the connection and channel are still open, but it always seems to report that it is still open.
The messages it does process work quite well and are acknowledged back to the queue so I don't think it's an issue with the "ack".
I'm sure I must be just missing something simple, but I haven't yet found it.
public void Run(string brokerUri, Action<byte[]> handler)
{
log.Debug("Connecting to broker: {0}".Fill(brokerUri));
ConnectionFactory factory = new ConnectionFactory { Uri = brokerUri };
using (IConnection connection = factory.CreateConnection())
{
using (IModel channel = connection.CreateModel())
{
channel.QueueDeclare(queueName, true, false, false, null);
using (Subscription subscription = new Subscription(channel, queueName, false))
{
while (!Cancelled)
{
BasicDeliverEventArgs args;
if (!channel.IsOpen)
{
log.Error("The channel is no longer open, but we are still trying to process messages.");
throw new InvalidOperationException("Channel is closed.");
}
else if (!connection.IsOpen)
{
log.Error("The connection is no longer open, but we are still trying to process message.");
throw new InvalidOperationException("Connection is closed.");
}
bool gotMessage = subscription.Next(250, out args);
if (gotMessage)
{
log.Debug("Received message");
try
{
handler(args.Body);
}
catch (Exception e)
{
log.Debug("Exception caught while processing message. Will be bubbled up.", e);
throw;
}
log.Debug("Acknowledging message completion");
subscription.Ack(args);
}
}
}
}
}
}
UPDATE:
I simulated a network failure by running the server in a virtual machine and I do get an exception (RabbitMQ.Client.Exceptions.OperationInterruptedException: The AMQP operation was interrupted) when I break the connection for long enough so perhaps it isn't a network issue. Now I don't know what it would be but it fails after just a couple hours of running.
EDIT: Since I'm sill getting upvotes on this, I should point out that the .NET RabbitMQ client now has this functionality built in: https://www.rabbitmq.com/dotnet-api-guide.html#connection-recovery
Ideally, you should be able to use this and avoid manually implementing reconnection logic.
I recently had to implement nearly the same thing. From what I can tell, most of the available information on RabbitMQ assumes that either your network is very reliable or that you run a RabbitMQ broker on the same machine as any client sending or receiving messages, allowing Rabbit to deal with any connection issues.
It's really not that hard to set up the Rabbit client to be robust against dropped connections, but there are a few idiosyncrasies that you need to deal with.
The first thing you need to do turn on the heartbeat:
ConnectionFactory factory = new ConnectionFactory()
{
Uri = brokerUri,
RequestedHeartbeat = 30,
};
Setting the "RequestedHeartbeat" to 30 will make the client check every 30 seconds if the connection is still alive. Without this turned on, the message subscriber will sit there happily waiting for another message to come in without a clue that its connection has gone bad.
Turning the heartbeat on also makes the server check to see if the connection is still up, which can be very important. If a connection goes bad after a message has been picked up by the subscriber but before it's been acknowledged, the server just assumes that the client is taking a long time, and the message gets "stuck" on the dead connection until it gets closed. With the heartbeat turned on, the server will recognize when the connection goes bad and close it, putting the message back in the queue so another subscriber can handle it. Without the heartbeat, I've had to go in manually and close the connection in the Rabbit management UI so that the stuck message can get passed to a subscriber.
Second, you will need to handle OperationInterruptedException. As you noticed, this is usually the exception the Rabbit client will throw when it notices the connection has been interrupted. If IModel.QueueDeclare() is called when the connection has been interrupted, this is the exception you will get. Handle this exception by disposing of your subscription, channel, and connection and creating new ones.
Finally, you will have to handle what your consumer does when trying to consume messages from a closed connection. Unfortunately, each different way of consuming messages from a queue in the Rabbit client seems to react differently. QueueingBasicConsumer throws EndOfStreamException if you call QueueingBasicConsumer.Queue.Dequeue on a closed connection. EventingBasicConsumer does nothing, since it's just waiting for a message. From what I can tell from trying it, the Subscription class you're using seems to return true from a call to Subscription.Next, but the value of args is null. Once again, handle this by disposing of your connection, channel, and subscription and recreating them.
The value of connection.IsOpen will be updated to False when the connection fails with the heartbeat on, so you can check that if you would like. However, since the heartbeat runs on a separate thread, you will still need to handle the case where the connection is open when you check it, but closes before subscription.Next() is called.
One final thing to watch out for is IConnection.Dispose(). This call will throw a EndOfStreamException if you call dispose after the connection has been closed. This seems like a bug to me, and I don't like not calling dispose on an IDisposable object, so I call it and swallow the exception.
Putting that all together in a quick and dirty example:
public bool Cancelled { get; set; }
IConnection _connection = null;
IModel _channel = null;
Subscription _subscription = null;
public void Run(string brokerUri, string queueName, Action<byte[]> handler)
{
ConnectionFactory factory = new ConnectionFactory()
{
Uri = brokerUri,
RequestedHeartbeat = 30,
};
while (!Cancelled)
{
try
{
if(_subscription == null)
{
try
{
_connection = factory.CreateConnection();
}
catch(BrokerUnreachableException)
{
//You probably want to log the error and cancel after N tries,
//otherwise start the loop over to try to connect again after a second or so.
continue;
}
_channel = _connection.CreateModel();
_channel.QueueDeclare(queueName, true, false, false, null);
_subscription = new Subscription(_channel, queueName, false);
}
BasicDeliverEventArgs args;
bool gotMessage = _subscription.Next(250, out args);
if (gotMessage)
{
if(args == null)
{
//This means the connection is closed.
DisposeAllConnectionObjects();
continue;
}
handler(args.Body);
_subscription.Ack(args);
}
}
catch(OperationInterruptedException ex)
{
DisposeAllConnectionObjects();
}
}
DisposeAllConnectionObjects();
}
private void DisposeAllConnectionObjects()
{
if(_subscription != null)
{
//IDisposable is implemented explicitly for some reason.
((IDisposable)_subscription).Dispose();
_subscription = null;
}
if(_channel != null)
{
_channel.Dispose();
_channel = null;
}
if(_connection != null)
{
try
{
_connection.Dispose();
}
catch(EndOfStreamException)
{
}
_connection = null;
}
}
I have a streaming server that with a contract looking something like this:
[ServiceContract]
public interface IStreamingService
{
[OperationContract(Action = "StreamingMessageRequest", ReplyAction = "StreamingMessageReply")]
Message GetStreamingData(Message query);
}
Here's a rudimentary implementation with stuff (like error handling) removed to simplify things:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
[ErrorBehavior(typeof(StreamingServiceErrorHandler))]
public class StreamingService : IStreamingService
{
public StreamingService()
{
}
public Message GetStreamingData(Message query)
{
var dataQuery = query.GetBody<DataQuery>();
// Hook up events to let us know if the client disconnects so that we can stop the query...
EventHandler closeAction = (sender, ea) =>
{
dataQuery.Stop();
};
OperationContext.Current.Channel.Faulted += closeAction;
OperationContext.Current.Channel.Closed += closeAction;
Message streamingMessage = Message.CreateMessage(
MessageVersion.Soap12WSAddressing10,
"QueryMessageReply",
new StreamingBodyWriter(QueryMethod(dataQuery));
return streamingMessage;
}
public IEnumerable<object> QueryMethod (DataQuery query)
{
// Returns a potentially infinite stream of objects in response to the query
}
}
This implementation uses a custom BodyWriter to stream results from the QueryMethod:
public class StreamingBodyWriter : BodyWriter
{
public StreamingBodyWriter(IEnumerable items)
: base(false) // False should be passed here to avoid buffering the message
{
Items = items;
}
internal IEnumerable Items { get; private set; }
private void SerializeObject(XmlDictionaryWriter writer, object item)
{
// Serialize the object to the stream
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
foreach (object item in Items)
{
SerializeObject(writer, item);
}
}
}
The client connects and starts reading the data stream. Something like this:
public IEnumerable<T> GetStreamingData<T>(Message queryMessage)
{
Message reply = _Server.GetStreamingData(queryMessage);
// Get a chunckable reader for the streaming reply
XmlReader reader = reply.GetReaderAtBodyContents();
// Read the stream of objects, deserializing each one as appropriate
while (!reader.EOF)
{
object item = DeserializeObject(reader);
if (item == null)
continue;
// Yield each item as it's deserialized, resulting in a [potentially] never-ending stream of objects
yield return (T)item;
}
}
This works very well and I get a stream of objects back to the client. Very nice. The problem is when the client disconnects mid-stream (either gracefully or ungracefully). In either case, the server doesn't get notified of this disconnect other than as a fault picked up by the service's error handler.
As you can see, I've tried hooking the Faulted and Closed channel events in the service method but they are not firing when the client disconnects.
I suspect that these events don't fire because with a streaming contract the service has already returned from the operation method (GetStreamingData). This method has returned a Message with a custom body-writer and it's this body-write that (lower in the service channel stack) is grabbing results from the calculation thread (via an IEnumerable) and streaming them in to the reply message. Since the operation method has returned then I'm guessing these channel events don't get fire.
The binding is a customized version of net.tcp binding (non-duplex) with only two binding elements: BinaryMessageEncodingBindingElement and TcpTransportBindingElement. No security or anything. Basically just raw net.tcp with binary messages.
The problem is that when a client disconnects, I want the server to stop the background calculation thread that is generating results and feeding them into the IEnumerable being read by the BodyWriter. The body writer has stopped (obviously), but the thread continues to live.
So where can I hook to discover if a client has disconnected mid-stream?
Update:
There are two main disconnection cases: Explicit disconnection, due either to the disposal of the client proxy or termination of the process at the client; Passive disconnection, usually due to a network failure (example: ISP drops the connection, or the network cable gets yanked).
In the first case, there is an explicit connection exception that get's received by the server when it tries to send new data down the stream. No problem.
The second scenario of a broken network pipe is more troublesome. Usually this condition is detected based on the Send timeout, but since this is a streaming interface the send timeout is cranked way up (I have it cranked to several days since it is conceivable that streaming data transfers could last that long). So when a client is disconnected due to a flaky network pipe, the service continues to send data down a connection that does really exist any more.
At this point I don't know of a good way to resolve the second option. Suggestions?
The Faulted event is really the best thing for handling these types of scenarios, but it will only fire if reliableSession is enabled. It is enabled by default in the standard netTcpBinding, but won't be enabled in this case because you are using a custom stripped down version of the binding. Try adding this to your custom binding :)
I have a trivial .Net 2.0 SOAP web service. I want to access it from Silverlight application that is hosted on the same server, but different port. I know that for this to work I need to provide a clientaccesspolicy.xml or crossdomain.xml policy file (the service is available at http://example:8085/RemoteObject.rem?wsdl , so the policy file has to be present at http://example:8085/crossdomain.xml). What should I add to the following simple web service to self-serve the policy file like the WCF example does?
The web service is being run on Mono, although that shouldn't change anything - there's just no IIS involved.
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
namespace ConsoleApplication1
{
class RemoteObject : MarshalByRefObject
{
static void Main()
{
var channel = new HttpChannel(8085);
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RemoteObject), "RemoteObject.rem",
WellKnownObjectMode.Singleton);
Console.WriteLine("Press ENTER to exit the server.");
Console.ReadLine();
}
public DateTime Now()
{
return DateTime.Now;
}
}
}
EDIT: because of all the unusable answers, let me repeat myself: I need to do this with .Net 2.0, not 3.0 or 3.5. WCF is not available.
I dont know much about the deploymnets in MONO. I would suggest a different approach if you didnt find any better approaches for your question.
Instead of directly calling the webservice from silverlight app.. you can invoke a javascript method from your managed silverlight code using
string sJson = HtmlPage.Window.Invoke("JSMethod", new string[] { strInParam }) as string;
and fire a AJAX request (from JS method) to your server and will internally make a call to the webservice deployed in MONO (from server) and return a JSON formatted result.
I have implemented this approach in my projects and its working fine..
just an alternative..
Ok, I can say that answer is not an easy step. Please look at Cassini open source web server, and you will have to implement a small web server in your application and run your custom service in your custom web server.
And the best way to open this silverlight would be, create an IFrame and let it load the html/aspx from your custom web server from your custom port itself. So you would not need any cross domain policy problems.
EDIT2: my coworker has found a usable solution: Web Service Enhancements from Microsoft. It does need IIS and it has been deprecated with the introduction of WCF, but works well with plain .Net Framework 2.0 and should be deployable with Mono XSP.
EDIT: solution below is pointless, because .Net 2.0 exposes web services using SOAP 1.1 rpc/encoded model, and Silverlight requires SOAP 1.2 document/literal. So while the workaround works for the problem indicated in the question, the web service still cannot be consumed.
I managed to make this work without resorting to extreme hacks. The key to my solution was to insert an additional IServerChannelSink into the request processing queue. So, I changed
var channel = new HttpChannel(8085);
to register my custom IServerChannelSink before the normal pipeline:
var provider = ChainProviders(
new PolicyServerSinkProvider(),
new SdlChannelSinkProvider(),
new SoapServerFormatterSinkProvider(),
new BinaryServerFormatterSinkProvider());
var channel = new HttpChannel(new Hashtable(1) {{"port", 8085}}, null, provider);
I use a helper method to chain the sink providers together:
private static IServerChannelSinkProvider ChainProviders(
params IServerChannelSinkProvider[] providers)
{
for (int i = 1; i < providers.Length; i++)
providers[i-1].Next = providers[i];
return providers[0];
}
PolicyServerSinkProvider simply creates a PolicyServerSink:
internal class PolicyServerSinkProvider : IServerChannelSinkProvider
{
public void GetChannelData(IChannelDataStore channelData){}
public IServerChannelSink CreateSink(IChannelReceiver channel)
{
IServerChannelSink nextSink = null;
if (Next != null)
nextSink = Next.CreateSink(channel);
return new PolicyServerSink(channel, nextSink);
}
public IServerChannelSinkProvider Next { get; set; }
}
PolicyServerSink delegates all messages down the chain, except when it gets a request for crossdomain.xml - then it writes the needed xml into the response stream.
internal class PolicyServerSink : IServerChannelSink
{
public PolicyServerSink(
IChannelReceiver receiver, IServerChannelSink nextSink)
{
NextChannelSink = nextSink;
}
public IDictionary Properties { get; private set; }
public ServerProcessing ProcessMessage(
IServerChannelSinkStack sinkStack, IMessage requestMsg,
ITransportHeaders requestHeaders, Stream requestStream,
out IMessage responseMsg, out ITransportHeaders responseHeaders,
out Stream responseStream)
{
if (requestMsg != null || ! ShouldIntercept(requestHeaders))
return NextChannelSink.ProcessMessage(
sinkStack, requestMsg, requestHeaders, requestStream,
out responseMsg, out responseHeaders, out responseStream);
responseHeaders = new TransportHeaders();
responseHeaders["Content-Type"] = "text/xml";
responseStream = new MemoryStream(Encoding.UTF8.GetBytes(
#"<?xml version=""1.0""?><!DOCTYPE cross-domain-policy SYSTEM "
+ #"""http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"">"
+ #"<cross-domain-policy><allow-access-from domain=""*"" />"
+ #"</cross-domain-policy>")) {Position = 0};
responseMsg = null;
return ServerProcessing.Complete;
}
private static bool ShouldIntercept(ITransportHeaders headers)
{
return ((string) headers["__RequestUri"]).Equals(
"/crossdomain.xml", StringComparison.InvariantCultureIgnoreCase);
}
public void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack,
object state, IMessage msg, ITransportHeaders headers, Stream stream)
{
}
public Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack,
object state, IMessage msg, ITransportHeaders headers)
{
throw new NotSupportedException();
}
public IServerChannelSink NextChannelSink { get; private set; }
}
This can also be used to serve other files together with the web service. I am currently using this method to host my Silverlight application (the consumer of the web service) without a separate http server.
Take a look at this http://blogs.msdn.com/carlosfigueira/archive/2008/03/07/enabling-cross-domain-calls-for-silverlight-apps-on-self-hosted-web-services.aspx