I have seen many articles for downloading and uploading files and in nearly everyone, the old way of copying the stream data was used. In the .Net Framework 4.0 there is a new method, Stream.CopyTo that makes it really easy for copying the stream data. Also, I have not seen any article that uploads/downloads a file using a Windows authenticated WCF REST service hosted on IIS. So, I thought of writing one with all that.
In this article share how to easily download and upload a file using a Windows Authenticated WCF service hosted in IIS. The client will be a C# windows client that will send the request for downloading and uploading the file.
The source code is attached with the article.
WCF REST Service
The first step is to create a WCF REST service that will be used for downloading and uploading the file. I prefer to keep all my services in a separate WCF project. This project is then hosted in the IIS.
- Add a new Blank solution by the name of “FileHandling”.
- Add a new WCF Service Application and give it the name FileHandling.WCFHost.
- Right-click the FileHandling.WCFHost project in Visual Studio and go to the properties. Under the Web tab click on “Use local IIS WebServer” and under the Project URL, write “http://localhost/FileHandling.WCFHost” and click on the button “Create Virtual Directory”. This will create a Virtual Directory for FileHandling.WCFHost in IIS.
- Go to IIS and check if the Virtual Directory is created.
- Ensure that the App Pool associated to the account is of .Net Framework 4.0. Also, the App Pool should be running under the identity of the user that has Read/write rights on any folder of the server. WCF uses this identity for doing all its network operations. In this case I run the App Pool under my account, but it is not mandatory, you can choose a different account having permissions as well.
- Ensure that for the FileHandling.WCFHost, only Windows Authentication is enabled.
With the preceding steps the IIS configuration is complete. Now it's time to add the service.
- Add a new WCFService in the FileHost.WCFHost project in VisualStudio and give it the name FileManagerService.
- In the IFileManagerService add the following methods.
- using System.IO;
- using System.ServiceModel;
- using System.ServiceModel.Web;
-
- namespace FileHandling.WCFHost
- {
- [ServiceContract]
- public interface IFileManagerService
- {
-
- [OperationContract]
- [WebGet(UriTemplate = "RetrieveFile?Path={path}")]
- Stream RetrieveFile(string path);
-
- [OperationContract]
- [WebInvoke(UriTemplate = "UploadFile?Path={path}")]
- void UploadFile(string path, Stream stream);
-
- }
- }
- In the FileManagerService.svc.cs add the following code:
- using System;
- using System.IO;
- using System.ServiceModel.Web;
-
- namespace FileHandling.WCFHost
- {
- public class FileManagerService : IFileManagerService
- {
- public Stream RetrieveFile(string path)
- {
- if(WebOperationContext.Current == null) throw new Exception("WebOperationContext not set");
-
-
-
- var fileName = Path.GetFileName(path);
- WebOperationContext.Current.OutgoingResponse.ContentType= "application/octet-stream";
- WebOperationContext.Current.OutgoingResponse.Headers.Add("content-disposition", "inline; filename=" + fileName);
-
- return File.OpenRead(path);
- }
-
- public void UploadFile(string path, Stream stream)
- {
- CreateDirectoryIfNotExists(path);
- using (var file = File.Create(path))
- {
- stream.CopyTo(file);
- }
- }
-
- private void CreateDirectoryIfNotExists(string filePath)
- {
- var directory = new FileInfo(filePath).Directory;
- if (directory == null) throw new Exception("Directory could not be determined for the filePath");
-
- Directory.CreateDirectory(directory.FullName);
- }
- }
- }
The RetrieveFile method is used for downloading the file. The method takes the parameter “path”, that is the location of the file for downloading from the WCF server. The method itself is pretty simple, it sets the ContentType and filename in the Response Header and then sends the FileStream back. Do not worry about how this FileStream will be disposed. WCF automatically takes care of it and at the end of the method disposes of it.
The UploadFile is also pretty simple. It takes the following two parameters:
- path: Path where the uploaded file should be saved
- stream: Stream represents the Uploaded file.
The method creates the Directory if it does not already exist and then creates the file in the location specified by “path”. For copying the uploaded file in the “path” location, Stream.CopyTo has been used.
- It's time to add the configuration settings for the service. In the Web.Config add the following configuration.
- <system.serviceModel>
- <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
- <bindings>
- <webHttpBinding>
- <binding name="ServiceWebBindingName" transferMode="Streamed" maxReceivedMessageSize="2147483647" >
- <readerQuotas maxArrayLength="2147483647" maxStringContentLength="2147483647" />
- <security mode="TransportCredentialOnly">
- <transport clientCredentialType="Windows"></transport>
- </security>
- </binding>
- </webHttpBinding>
- </bindings>
- <behaviors>
- <endpointBehaviors>
- <behavior name="DefaultRestServiceBehavior">
- <webHttp defaultOutgoingResponseFormat="Json" defaultBodyStyle="Wrapped" automaticFormatSelectionEnabled="false"/>
- </behavior>
- </endpointBehaviors>
- <serviceBehaviors>
- <behavior name="">
- <serviceMetadata httpGetEnabled="true" />
- <serviceDebug includeExceptionDetailInFaults="true" />
- </behavior>
- </serviceBehaviors>
- </behaviors>
- <services>
- <service name="FileHandling.WCFHost.FileManagerService">
- <endpoint address=""
- binding="webHttpBinding"
- bindingConfiguration="ServiceWebBindingName"
- behaviorConfiguration="DefaultRestServiceBehavior"
- name="FileManagerServiceEndpoint"
- contract="FileHandling.WCFHost.IFileManagerService"/>
- </service>
- </services>
- </system.serviceModel>
- </configuration>
Please note that the Transfermode has been set to “Streamed”. This will ensure that the file is streamed to the client. And also, notice that I have given maximum values to maxReceivedMessageSize, maxArrayLength, maxStringContentLength. This will ensure that large files can be transferred as well.
- <binding name="ServiceWebBindingName" transferMode="Streamed" maxReceivedMessageSize="2147483647" >
- <readerQuotas maxArrayLength="2147483647" maxStringContentLength="2147483647" />
- <security mode="TransportCredentialOnly">
- <transport clientCredentialType="Windows"></transport>
- </security>
- </binding>
- </webHttpBinding>
- </bindings>
Also note that for Security mode has been set to TransportCredentialsOnly and the ClientCredentialType has been set to Windows.
Client
Now it's time to build the Client.
I decided to use a simple Windows client for consuming the WCF service for downloading and uploading the file. Methods for uploading and downloading are specified below.
File Upload using WCF
The UploadFileToRemoteLocation method takes the following 2 parameters:
- filePath: specifies the local path of where the file is located.
- destinationFilePath: specifies the path on the server (on which WCF is hosted) where file must be uploaded.
The method UploadFileToRemoteLocation copies the file specified in the filePath to the request stream. It then calls the WCF service for uploading this file and passes destinationFilePath as the path where the file should be stored on the server.
The service on being called receives the file and then saves it at the destinationFilePath on the server.
In this example, both WCFHost and the client are located on the same server but the WCFHost can be located on any other server as well.
-
-
-
- private void UploadFileToRemoteLocation(string filePath, string destinationFilePath)
- {
- var serviceUrl = string.Format("{0}/UploadFile?Path={1}", FileManagerServiceUrl, destinationFilePath);
- var request = (HttpWebRequest)WebRequest.Create(serviceUrl);
- request.Method = "POST";
- request.UseDefaultCredentials = true;
- request.PreAuthenticate = true;
- request.Credentials = CredentialCache.DefaultCredentials;
-
- using (var requestStream = request.GetRequestStream())
- {
- using (var file = File.OpenRead(filePath))
- {
- file.CopyTo(requestStream);
- }
- }
- using (var response = request.GetResponse() as HttpWebResponse)
- {
-
- }
- }
File Download using WCF
The method DownloadFileFromRemoteLocation takes the following 2 parameters:
- downloadFileLocation: This is the path on the server where the file is located.
- downloadedFileSaveLocation: This is the local path where the file is saved after downloading.
The DownloadFileFromRemoteLocation method first downloads file from the service and then stores the file in the path specified in downloadedFileSaveLocation.
- private void DownLoadFileFromRemoteLocation(string downloadFileLocation, string downloadedFileSaveLocation) {
- string serviceUrl = string.Format("{0}/RetrieveFile?Path={1}", FileManagerServiceUrl, downloadFileLocation);
- var request = WebRequest.Create(serviceUrl);
- request.UseDefaultCredentials = true;
- request.PreAuthenticate = true;
- request.Credentials = CredentialCache.DefaultCredentials;
-
- try {
- using(var response = request.GetResponse()) {
- using(var fileStream = response.GetResponseStream()) {
- if (fileStream == null) {
- MessageBox.Show("File not recieved");
- return;
- }
-
- CreateDirectoryForSaveLocation(downloadedFileSaveLocation);
- SaveFile(downloadedFileSaveLocation, fileStream);
- }
- }
-
- MessageBox.Show("File downloaded and copied");
-
- } catch (Exception ex) {
- MessageBox.Show("File could not be downloaded or saved. Message :" + ex.Message);
- }
-
- }
-
- private static void SaveFile(string downloadedFileSaveLocation, Stream fileStream) {
- using(var file = File.Create(downloadedFileSaveLocation)) {
- fileStream.CopyTo(file);
- }
- }
-
- private void CreateDirectoryForSaveLocation(string downloadedFileSaveLocation) {
- var fileInfo = new FileInfo(downloadedFileSaveLocation);
- if (fileInfo.DirectoryName == null) throw new Exception("Save location directory could not be determined");
- Directory.CreateDirectory(fileInfo.DirectoryName);
- }
Please refer to the attached code if you would like to have a look at the complete working solution.
The following are the prerequisites for the attached code to work:
- IIS should be installed.
- .Net Framework 4.0 should be installed
- Visual Studio 2010 or above.