WCF FaultException error
I'm new to WCF and I have issues throwing exceptions from my WCF Service to the client. I'm using code examples which I copied from the web. (I'm using VS2010 .NET Framework 4.0)
I created an ErrorHandler where the ProvideFault-method looks like this:
public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message msg)
{
FaultException<Exception> faultException = new FaultException<Exception>(error, error.Message, new FaultCode("Testing."));
MessageFault messageFault = faultException.CreateMessageFault();
msg = Message.CreateMessage(version, messageFault, Constants.FaultAction);
}
The fault contract looks like this:
[FaultContract(typeof(Exception), Action=Constants.FaultAction)]
The client side test code looks like this:
private void button1_Click(object sender, EventArgs e)
{
HistorianAccessServiceClient cli = new HistorianAccessServiceClient();
Tables.Batch bt = new Tables.Batch();
try
{
bt = cli.GetBatch(3241);
}
catch (FaultException<Exception> ex)
{
MessageBox.Show(ex.Message);
}
}
I noticed that if the error parameter to the ProvideFault method contains an inner exception then a System.ServiceModel.CommunicationException is thrown on the client side (!?), the inner exception is System.Net.WebException, the inner exception to that exception is System.IO.IOException and the inner exceptin to that exception is System.Net.Sockets.SocketException (Error Code 10054)?!?!
(Unfortunately I have a swedish operating system installed which means that the messages from the debugger is in swedish.)
The exception message (google translate) looks like this:
An error occurred while receiving the HTTP response to http://localhost:7070/Historian.WebAccess/HistorianAccessService. It may be that the service endpoint binding not using the http protocol. It may also be due to a context for the http request has been interrupted by the server (probably because the service is terminated). You can find more information in server logs.
If I throw an exception without an inner exception, the exception is handled by the client perfectly ok!?!?!
My configuration files looks like this (Service):
<system.serviceModel>
<bindings />
<client />
<services>
<service name="Historian.WebAccess.HistorianAccessService">
<host>
<baseAddresses>
<!--<add baseAddress="http://localhost:8732/Design_Time_Addresses/Historian.WebAccess/HistorianAccessService/"/>-->
<add baseAddress="http://localhost:7070/Historian.WebAccess/"/>
</baseAddresses>
</host>
<!-- Service Endpoints -->
<!-- Unless fully qualified, address is relative to base address supplied above -->
<!--<endpoint address="HistorianAccessService" binding="wsHttpBinding" contract="Historian.WebAccess.IHistorianAccessService">-->
<endpoint address="HistorianAccessService" binding="wsHttpBinding" contract="Historian.WebAccess.IHistorianAccessService">
<!--
Upon deployment, the following identity element should be removed or replaced to reflect the
identity under which the deployed service runs. If removed, WCF will infer an appropriate identity
automatically.
-->
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<!-- Metadata Endpoints -->
<!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
<!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="false"/>
<serviceThrottling maxConcurrentCalls="16" maxConcurrentInstances="2147483646" maxConcurrentSessions="10"/>
<dataContractSerializer maxItemsInObjectGraph="2147483646"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
Configuration file (Client):
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IHistorianAccessService" closeTimeout="00:10:00"
openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00"
bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="104857600" maxReceivedMessageSize="104857600"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="104857600" maxStringContentLength="104857600" maxArrayLength="104857600"
maxBytesPerRead="104857600" maxNameTableCharCount="104857600" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None"
realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true"
algorithmSuite="Default" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:7070/Historian.WebAccess/HistorianAccessService"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IHistorianAccessService"
contract="HistorianAccessHost.IHistorianAccessService"
name="WSHttpBinding_IHistorianAccessService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
<behaviors>
<endpointBehaviors>
<behavior>
<dataContractSerializer 开发者_开发技巧maxItemsInObjectGraph="2147483646"/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
Does anyone out there recognize this phenomenon and the solution to it?!
I'd be greatful for all the help I can get!
The solution is to not attempt to pass .NET Exception objects back to the client. This limits you to clients running .NET.
In fact, it limits you to running clients which know about all of the exceptions that you might throw. What if you add a new MyNewException
on the server, and throw it back to the client? The client will need to have the assembly containing that exception in order for it to be deserialized at all.
I think you're being too fancy for what you're trying to do. If you're just trying to throw FaultException, just do new FaultException(error). You have to do a bit more work if you're throwing a custom fault type, but none of that message stuff is necessary. Here's a VB example I found:
Public Function DoSomething() As Data()
Try
DoSomething()
Catch ex As Exception
Throw New FaultException(ex.Message)
End Try
End Function
If you're throwing a custom type of fault (like say PermissionDenied or such), you need to create an object for that, which is a bit more work.
You also want to be careful what you're returning here. Sending back a lot of details like stack traces to the client can help an attacker trying to break into the system, and isn't a lot of use to your standard end user. You should log that on the server instead.
精彩评论