Monday, August 31, 2009

Saving files from one website to another website.

This blog is based on my recent requirement where I had to save some image files from one website to another website. Lets see the requirement and the solution for the requirement.

Requirement

The requirement is as follows. The user will select a file which needs to be uploaded using a website (UploadFilesWebsite) and these selected files will be saved in another website (SaveFilesWebsite). Cross website posting of data. We all know it’ very easy to save files within the same site by making use of a file upload control and calling its SaveAs method. E.g. is pasted below.

fileUploadCntl.SaveAs(System.IO.Path.Combine(Server.MapPath("."), fileUploadCntl.FileName))

But in my case it was different, the file needs to be saved in a different website from the one from where file upload is done.

Solution

The problem was solved by making use of HttpWebRequest and HttpWebResponse object. Below is steps which are needed to solve this problem.

  • First create an ASPX file which will be capable of receiving any type of file stream and saving this stream to the required folder. This file should be created/placed in the website where you want to save the files, in my case it is “SaveFiles” website.
  • Create a HttpWebRequest object and pump the file content to the RequestStream of the HttpWebRequest object.
  • Get the response using the GetResponse method of the HttpWebRequest object. GetResponse will return a HttpWebResponse object.
  • Get the response stream from the HttpWebResponse object by executing the GetResponseStream method.

These are the steps which you need to follow to post file contents from one website to another. Lets see each one of these one by one.

Create an aspx file capable of saving request stream.

The first step is to create an aspx file which will be capable of saving a request stream. This file should be created and saved in the web site where you plan to save your file. This file will receive requests from other website for saving files in the website. So we will create this file in “SaveFiles” website and we will name it as SaveFile.asxp. After creating and saving the file go to the HTML source view of the file. It will look something like this.

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="SaveFile.aspx.cs" Inherits="SaveFile" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    </div>
    </form>
</body>
</html>

Remove the HTML source from the file leaving only the Page attribute. The page should look something like this.

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="SaveFile.aspx.cs" Inherits="SaveFile" %>

Now lets go to the codebehind and do some coding to save the request stream. The codebehind (SaveFile.aspx.cs) file of SaveFile looks something like this.

using System;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;

public partial class SaveFile : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            /*Retrieving the file name from the headers in the request. */
            string fileName = System.IO.Path.Combine(Server.MapPath("."), Request.Headers["FileName"].ToString());
            using (System.IO.FileStream fileStream = System.IO.File.Create(fileName))
            {
                /*Getting stream from the Request object.*/
                using (System.IO.Stream stream = Request.InputStream)
                {
                    int byteStreamLength = (int)stream.Length;
                    byte[] byteStream = new byte[byteStreamLength];
                    /*Reading the stream to a byte array.*/
                    stream.Read(byteStream, 0, byteStreamLength);
                    /*Writing the byte array to the harddisk.*/
                    fileStream.Write(byteStream, 0, byteStreamLength);
                }
            }
            Response.Clear();
            /*Writing the status to the response.*/
            Response.Write("File successfully saved");
        }
        catch (Exception ex)
        {
            /*Writing the error message to the response stream.*/
            Response.Clear();
            Response.Write("Error: " + ex.Message);
        }
    }
}

From the above code one can see we are first retrieving the filename from Request object’ Headers property. We will shortly see how to send info in headers of a request. After getting the filename we are opening a file stream to flush the content to the hard disk. Once the file stream is successfully created, next, we are creating a stream by getting the InputStream of the Request object. The InputStream property of the Request object will hold the content which needs to be saved. In the next line we are reading the stream content to a byte array and finally passing the byte array to the FileStream object and writing/saving to a file. Once the file is successfully saved we are writing the status to the Response after clearing the Response. Also if there is any exception thrown while the file is saved the same is written to the Response.

So that’ about the page which will save the content in the desired filename. So now we will move to our next steps like creating the HttpWebRequest object and passing the file content to the SaveFile.aspx file to save it in the website.

Create an aspx page to upload the images.

The file which we will be creating will have the capability to upload files from the user’ hard disk and will send these file contents to the SaveFile.aspx page. The UploadFile.aspx will be created in a separate website/virtual directory called “UploadFiles” website. This aspx page will have a FileUpload control and a save button. So here is the HTML source of UploadFile.aspx.

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="FileUpload.aspx.cs" Inherits="FileUpload" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    </div>
       <asp:Label ID="lblMessage" runat="server"></asp:Label><br />
        <asp:FileUpload ID="fileUpload" runat="server" /><br />
        <asp:Button ID="btnSave" runat="server" Text="Save Files"
        onclick="btnSave_Click" />
    </form>
</body>
</html>

From the HTML you can see that we have an FileUpload control and a save button with an OnClick event handler. So that’ our simple UploadFile page. Now lets see the real action i.e. the place where the code to upload files and sending the content of these files to the SaveFile.aspx in “SaveFiles” website happen. So here is the click event of the save button where all the action takes place.

protected void btnSave_Click(object sender, EventArgs e)
{
        /*Creating the WebRequest object using the URL of SaveFile.aspx.*/
        System.Net.HttpWebRequest webRequest =
(System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create
("Http://Localhost/SaveFiles/SaveFile.aspx");
        webRequest.Method = "POST";
        webRequest.KeepAlive = false;
        /*Assigning the content type from the FileUpload control.*/
        webRequest.ContentType = fileUpload.PostedFile.ContentType;
        /*Creating the Header object.*/
        System.Net.WebHeaderCollection headers = new System.Net.WebHeaderCollection();
        headers.Add("FileName", fileUpload.FileName);
        /*Adding the header to the WebRequest object.*/
        webRequest.Headers = headers;
        /*Getting the request stream by making use of the GetRequestStream method of WebRequest object.*/
        using (System.IO.Stream stream = webRequest.GetRequestStream())//Filling request stream with image stream.
        {
            /*Writing the FileUpload content to the request stream.*/
            stream.Write(fileUpload.FileBytes, 0, fileUpload.FileBytes.Length);
        }
        /*Creating a WebResponse object by calling the GetResponse method of WebRequest object.*/
        using (System.Net.HttpWebResponse webResponse = (System.Net.HttpWebResponse)webRequest.GetResponse())
        {
            /*Retrieving the response stream from the WebResponse object by calling the GetResponseStream method.*/
            using (System.IO.StreamReader sr = new System.IO.StreamReader(webResponse.GetResponseStream()))
            {
                lblMessage.Text = sr.ReadToEnd();
            }
        }
}

In the above code first I am creating a WebRequest object by passing the URL of the page which will handle the saving mechanism. Once the WebRequest object is created next I am assigning the Method as POST. This is a very important step. If you don’t assign the method property it will default to “GET”. If you use “GET” as the method to send data then you will receive System.Net.ProtocolViolationException with the following message.

"Cannot send a content-body with this verb-type."

Once the method is set to “POST” we are setting the KeepAlive property to false. KeepAlive property will be responsible for maintaining a live connection. The next line we are setting the ContentType of the request. The content type can be easily retrieved by calling the ContentType property from PostedFile property of the FileUpload Control. Next line we are creating a WebHeaderCollection object to pass additional information in the headers of the request. You can see we are passing the file name as one of the header value. These header details are read in SaveFile.aspx. So if you want send some extra information you can do that using WebHeaderCollection. In the next few lines we are retrieving the request stream using the GetRequestStream method and then writing the contents of the selected file to request stream using the Write method of the Stream object. To pump the content of the chosen file we are making use of the FileBytes property of the FileUpload control. One can note that we are making use of the “using” statement quite a lot. To know more about the usefulness of the “using” statement you can refer my blog on using statement here.

Once the FileUpload contents are pumped to the request stream of the HttpRequest object in the next line I am creating the HttpWebResponse using the GetResponse method of the HttpWebRequest object. So what GetResponse does is that it sends a request to the Url (savefile.aspx) specified while creating the HttpWebRequest object. When the HttpWebRequest object sends request to the url its similar to the way we access url through the browser. So the whole life cycle of the page executes and response is saved in the form of HttpWebResponse object. After getting the HttpWebResponse object we are reading what output was returned by the page using the StreamReader object’ ReadToEnd method. You can retrieve the StreamReader object by calling the GetResponseStream method of the WebRequest object. The response which I am getting is directly assigned to the Text property of a label control. What we get in the ResponseStream object is what we have written to the response using the Write method of the Response property in the SaveImage.aspx page. So if the image is successfully saved we get “File Successfully saved” as the response else we get error as the response.

That’ it, we are done. With this technique we can save files or contents from one website to another site i.e. cross website posting of data. This technique is very simple but can be very dangerous. You should make sure that you implement some sort of authentication so that hackers can be evaded from saving some malicious files.

Try to know more.

Sandeep

16 comments:

  1. Hi, EXCELLENT!!! One thing. The first file posts great, but the next gives the following error: System.Web.HttpException: The state information is invalid for this page and might be corrupted.

    I just added a Response.Redirect back to UploadFile.aspx after posting the file and it worked ok.

    Just curious if I'm missing something or you have another solution to this.

    Thanks much!

    Jeremy

    ReplyDelete
  2. Hi Jeremy,
    Could you please paste the code you have used. Seeing the code I may be able to help you. I have used somewhat the same implementation and saved files n number of times without any error.

    ReplyDelete
  3. Hi
    Sandeep i Ajay here in my website i want to upload document in one website and save the document in other website folder (meance other server) can you please tell me how can it possible i am very new in asp.net

    ReplyDelete
  4. Hi Ajay,
    This blog talks about the same requirement what you are asking for. So follow the blog instruction and you will be good to go.

    Sandeep

    ReplyDelete
  5. hi Sandeep,
    This is kesava.
    i am also having same kind of requirement but in case of web application i need to use windows application to send image to the web application as stream . i am using memory stream to convert the image to stream but when i'm sending the stream it will rises an error "Internal server error(500) like that ".
    If u know the solution please help me
    my mail id :ramki.pindi@gmail.com

    ReplyDelete
  6. hi,
    i have another requirement . i want to capture the image using web application for that i used widows user control to capture image .i place the user control with the help of "object" tag before that i register the assembly using regasm and add it to gac folder it works fine on my system .If i want to access this application from another system in the network it will display only the border of the control ,it will not display any thing .
    if u know please send to my mail id or post on ur blog
    Thanks&Regards
    Kesava

    ReplyDelete
  7. Hi Kesava,

    The same implementation you can use in windows application as well. I have done a similar one like this. Just make sure that the page is not under any mode of authentication like forms or windows. If the website is under forms/windos authentication use location tag in web.config to exclude the image saving aspx file from the authentication.

    Normally 500 is an internal server error. The cause of it can be know by inspecting the server log. So just what is the cause of the internal error.

    ReplyDelete
  8. Hi Kesava,
    May be the browser settings don't allow to run the windows user control. Just compare the settings of the browsers in both the system.

    ReplyDelete
  9. I really need this codes for my project, we need to make a website creator that can transfer the existing file of a website to a new one. :)

    ReplyDelete
  10. Hi Kenny,
    I have talked about the same scenario in the blog with code samples. If you are facing any particular problem or you have any doubt please let me know.

    ReplyDelete
  11. Hi,

    this article is 2 much useful for me. But i have one problem i am to transfer the images from our website to another server.

    According to your code i am to make the virtual directory on another server also for creating the SaveFile.aspx page.

    Please guide is am right??

    ReplyDelete
  12. Hi Lata,
    Yes that is correct. If you have write permission to a folder in the third party website you will be able to save it. But again that is not advisable, as anyone can save anything. Instead you can use FTP or the solution provide above where you have to have the saving page in the third party website. My option is possible only when you have control in both the websites. If not you can use FTP. Also please do take care to proper security checking so that someone else doesn't host some malicious program.

    ReplyDelete
  13. Hi Sandeep,

    I have a project that is similar to your solution (saving files from one website to another). The HttpWebRequest and HttpWebResponse works as expected in HTTP post. However, our web servers used SSL for client authentication. I have a hard time implementing and validating certificates in HTTPS call. Can you lead me to a solution that will work to post data(image) to an HTTPS service based on your approach (cross website posting of data)?

    I'm hoping you could help me on my problem. Thanks

    Junius D.

    ReplyDelete
  14. this type of error.....

    The remote server returned an error: (405) Method Not Allowed.

    ReplyDelete
  15. Hi
    I am using MVC so i cant use fileupload server conntrol of asp.net . But in case of mvc when i am going to convert file into byte array it gives an error like Could not find file 'C:\Program Files\IIS Express\slide2.jpg'.

    ReplyDelete
  16. I did the same code File is being saved on other website but its blank. can anyone please help

    ReplyDelete

Please provide your valuable comments.