I'm using WCF (.Net 4) to upload a file to an IIS server from a .Net 4 WinForms client app for an in-house system.
I have a MessageContract
class defined as follows:
/// <summary>
/// Message contract for uploading document data together with a file stream
/// </summary>
[MessageContract]
public class DocumentDataFileUploadInfo : IDisposable
{
/// some fields omitted for brevity
/// <summary>
/// The stream containing the file
/// </summary>
[MessageBodyMember(Order = 1)]
public Stream FileByteStream;
/// <summary>
/// Dispose of the stream if necessary
/// </summary>
public void Dispose()
{
try
{
if (FileByteStream != null)
{
FileByteStream.Close();
FileByteStream.Dispose();
FileByteStream = null;
}
}
catch { }
}
}
The guts of my server-side WCF upload method is as follows:
/// <summary>
/// Upload the given file into the database, as a stream
/// </summary>
/// <param name="fileInfoWithStream">The message containing all the information required for upload</param>
/// <returns></returns>
public DocumentDataFileUploadResponse UploadDocument(DocumentDataFileUploadInfo fileInfoWithStream)
{
byte[] documentBytes = null;
string fileName = fileInfoWithStream.FileName;
// create the message response
DocumentDataFileUploadResponse response = new DocumentDataFileUploadResponse();
// check the file type being uploaded (from the database)
FileType fileType = GetFileType(fileName, context);
if (!fileType.UploadPermitted)
{
// we don't allow this file type
response.MetaData = InsertDocumentDataResult.FileTypeProhibited;
return response;
}
// get the contents of the stream as a byte array (extension method)
documentBytes = fileInfoWithStream.FileByteStream.GetStreamContents(fileInfoWithStream.TransmissionSize, _uploadBufferSize, null, out cancelled);
// save the document to disk/database
// code omitted for brevity
response.MetaData = InsertDocumentDataResult.Success;
return response;
}
In case it's important, I'm using NetTcpBinding:
<netTcpBinding>
<binding name="netTcpStreamBinding"
transferMode="Streamed"
maxReceivedMessageSize="1099511627776" />
</netTcpBinding>
Everything works great, I can upload very large files no problem, and the client can cancel the upload too. The only problem is with this block:
if (!fileType.UploadPermitted)
{
response.MetaData = InsertDocumentDataResult.FileTypeProhibited;
return response;
}
This should (and does) return a failure code because the file type is not permitted. However, as soon as the service method completes, the Dispose
method on the MessageContract
class is called, the stream is disposed of, and the client then completes the entire stream upload BEFORE getting the return code back, even though the server isn't hitting the line which reads the stream. There must be some underlying WCF streaming plumbing that thinks the stream needs to complete before it's disposed of.
Basically, I want the SERVER to be able to cancel the stream upload and return a failure code back. I've tried the following:
- Throwing a
FaultException<>
- this still causes the stream to complete uploading - Calling a
System.ServiceModel.OperationContext.Current.RequestContext.Abort();
- This causes a CommunicationException to be thrown to the client, and aborts the stream upload but won't let me return any sort of failure reason - Temporarily removing the 'Dispose()' on the 'MessageContract' class. No difference.
- Seeking to the end of the stream - Not supported.
- Closing the stream - immediately causes the client to send all data.
My only other option (that I can see) is to abort the request as above, causing a CommunicationException
to be thrown, but add a new service method for the client to get the failure code afterwards. I really don't want to do that, because this is supposed to be stateless as far as possible, and I'm sure there must be a simple way to cancel the stream upload from the server-side.
Any help greatly appreciated!