2

I've been ripping my hair out for a while now. Can someone provide a really simple example (or a link to a working example) of how to upload a file to a WCF service hosted in IIS.

I've started with something simple. I want to call a URL from a client via POST, pass the name of the file and send the file as well. So I added the following to the contract:

[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "/UploadFile?fileName={fileName}")]
void Upload(string fileName, Stream stream);

The implemented it in the .svc file:

public void Upload(string fileName, Stream stream)
{
    Debug.WriteLine((fileName));
}

Immediately, I get an error upon running the project:

For request in operation Upload to be a stream the operation must have a single parameter whose type is Stream.

Not sure where to go from here. Would love to see an actual working example.

P.S. I did this in .NET 4 with WCF 4 and it seemed a lot simpler, but I've had to downgrade. In .NET 3.5, I am missing something.

AngryHacker
  • 59,598
  • 102
  • 325
  • 594

1 Answers1

4

In order for it to work you need to define an endpoint with a binding compatible with WebHttpBinding and which has the WebHttpBehavior added to it. The message may be a red herring, it's an old bug which if you browse to the service base address, and if you have metadata enabled, it will show up. Another issue is that if you want to be able to upload any file type (including JSON and XML), you'll need to define a WebContentTypeMapper to tell WCF not to try to understand your file (more info at http://blogs.msdn.com/b/carlosfigueira/archive/2008/04/17/wcf-raw-programming-model-receiving-arbitrary-data.aspx).

This is a complete example which does that. The biggest problem with 3.5 is that the ContentTypeMapper property doesn't exist on the WebHttpBinding, so you need to use a custom binding for that. This code defines the endpoint using a custom ServiceHostFactory, but it could be defined using config as well.

Service.svc

<%@ ServiceHost Language="C#" Debug="true" Service="MyNamespace.MyService" Factory="MyNamespace.MyFactory" %>

Service.cs

using System;
using System.Diagnostics;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Web;

public class MyNamespace
{
    [ServiceContract]
    public interface IUploader
    {
        [OperationContract]
        [WebInvoke(Method = "POST", UriTemplate = "/UploadFile?fileName={fileName}")]
        void Upload(string fileName, Stream stream);
    }

    public class Service : IUploader
    {
        public void Upload(string fileName, Stream stream)
        {
            Debug.WriteLine(fileName);
        }
    }

    public class MyFactory : ServiceHostFactory
    {
        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            return new MyServiceHost(serviceType, baseAddresses);
        }

        class MyRawMapper : WebContentTypeMapper
        {
            public override WebContentFormat GetMessageFormatForContentType(string contentType)
            {
                return WebContentFormat.Raw;
            }
        }

        public class MyServiceHost : ServiceHost
        {
            public MyServiceHost(Type serviceType, Uri[] baseAddresses)
                : base(serviceType, baseAddresses) { }

            protected override void OnOpening()
            {
                base.OnOpening();

                CustomBinding binding = new CustomBinding(new WebHttpBinding());
                binding.Elements.Find<WebMessageEncodingBindingElement>().ContentTypeMapper = new MyRawMapper();
                ServiceEndpoint endpoint = this.AddServiceEndpoint(typeof(IUploader), binding, "");
                endpoint.Behaviors.Add(new WebHttpBehavior());
            }
        }
    }
}
carlosfigueira
  • 85,035
  • 14
  • 131
  • 171
  • I copied your solution, but I am getting this error: `A binding instance has already been associated to listen URI 'http://localhost:51147/FileUploader.svc'. If two endpoints want to share the same ListenUri, they must also share the same binding object instance. The two conflicting endpoints were either specified in AddServiceEndpoint() calls, in a config file, or a combination of AddServiceEndpoint() and config.` – AngryHacker Aug 05 '11 at 00:22
  • Do you have anything on web.config which is adding an endpoint to that address? Can you post your web.config? With this solution you actually don't need a web.config *at all* for your service. – carlosfigueira Aug 05 '11 at 01:44
  • You are right. I deleted web.config, let the IDE generate a new one for me and the page came up and I can upload files. I've had to specify `MaxReceivedMessageSize` on the `WebHttpBinding` object to be able to get files larger than the default. – AngryHacker Aug 05 '11 at 17:13
  • My remaining question is how do I determine where I am at in the file system? Since `HttpContext.Current` is null, I can't use `HttpContext.Current.Server.MapPath` to determine the folder, so that I can save the uploaded file. – AngryHacker Aug 05 '11 at 17:15
  • You can use `System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath` to find out the path in the server. – carlosfigueira Aug 05 '11 at 17:59
  • Thanks. That did the trick - everything works great now. I can't believe how complicated it was to create a simple upload service. – AngryHacker Aug 05 '11 at 21:06