locked
How to get a file from a web service in vb.net codebehind RRS feed

  • Question

  • User1738843376 posted

    Hi,

    I have an ASMX file that produces a service that upon providing a key and string returns a specific file based on the key validation and on the string to choose the file.

    This is working perfectly, but I can't seem to find a way to consume this service on a webform when the user presses a button. I basically what the user to press a button and a file be returned to the browser, allowing him to either save or view the file (the file type in question is a PDF).

    Here is the code that I'm using for the button:

    Dim result As String = ""
    
    Dim myRequest As HttpWebRequest = HttpWebRequest.Create("http://localhost/SecureWebHandlerTest/GetSecurePDF.asmx?op=DownloadPdf")
    
    myRequest.Method = "POST"
    
    Using requestWriter As StreamWriter = New StreamWriter(myRequest.GetRequestStream())
    
    	requestWriter.Write("&{0}={1}", HttpUtility.UrlEncode("providedKey"), HttpUtility.UrlEncode(ConfigurationManager.AppSettings("kitSecurityKey")))
    	requestWriter.Write("&{0}={1}", HttpUtility.UrlEncode("requestedFileId"), HttpUtility.UrlEncode("01"))
    
    End Using
    
    Using responseReader As StreamReader = New StreamReader(myRequest.GetResponse().GetResponseStream())
    	result = responseReader.ReadToEnd()
    End Using
    
    Response.AppendHeader("content-disposition", "attachment; filename=01.pdf")
    Response.ContentType = "application/pdf"
    Response.TransmitFile(result)

    And just for reference, here is the webmethod inside the ASMX file:

    <WebMethod()>
    Public Function DownloadPdf(ByVal providedKey As String, ByVal requestedFileId As String) As Byte()
    
    	Dim fileBytes As Byte()
    
    	Try
    
    		If providedKey = ConfigurationManager.AppSettings("kitSecurityKey") Then
    
    			Dim strFilename As String = Context.Server.MapPath("~/ProtectedDocs/test_" & requestedFileId & ".pdf")
    
    			fileBytes = File.ReadAllBytes(strFilename)
    
    			Context.Response.Clear()
    			Context.Response.ClearHeaders()
    			Context.Response.ContentType = "application/pdf"
    			Context.Response.AddHeader("content-disposition", "attachment; filename=test_" & requestedFileId & ".pdf")
    			Context.Response.AddHeader("Content-Length", fileBytes.Length)
    			Context.Response.OutputStream.Write(fileBytes, 0, fileBytes.Length)
    			Context.Response.Flush()
    			Context.Response.End()
    
    		Else
    
    			fileBytes = System.Text.Encoding.Unicode.GetBytes("You are not allowed to access this resource")
    
    			Context.Response.Clear()
    			Context.Response.ClearHeaders()
    			Context.Response.ContentType = "text/html"
    			Context.Response.OutputStream.Write(fileBytes, 0, fileBytes.Length)
    			Context.Response.Flush()
    			Context.Response.End()
    
    		End If
    
    	Catch ex As Exception
    
    	End Try
    
    	Return fileBytes
    
    End Function

    And this is the stack that i'm getting:

    System.Xml.XmlException: Data at the root level is invalid. Line 1, position 1.
      at System.Xml.XmlTextReaderImpl.Throw
      at System.Xml.XmlTextReaderImpl.Throw
      at System.Xml.XmlTextReaderImpl.ParseDocumentContent
      at System.Xml.XmlTextReaderImpl.Read
      at SoapEnvelopeReader.Read
      at System.Xml.XmlReader.MoveToContent
      at SoapEnvelopeReader.MoveToContent
      at System.Web.Services.Protocols.SoapServerProtocolHelper.GetRequestElement
      at System.Web.Services.Protocols.Soap12ServerProtocolHelper.RouteRequest
      at System.Web.Services.Protocols.SoapServerProtocol.RouteRequest
      at System.Web.Services.Protocols.SoapServerProtocol.Initialize
      at System.Web.Services.Protocols.SoapServerProtocol.Initialize
      at System.Web.Services.Protocols.ServerProtocolFactory.Create

    Any idea of what is wrong here? how can I correct it and allow the user to download the file?

    Also, i'm posting the key when i call the web service, so that the call can be authenticated, but i'm not sure if there is a better way of securing this service, or if there is a way to provide authentication credentials programmatically that is more secure.

    I need the referred authentication so that the service can be consumed by a partner website, but prevent its access from any other call.

    Any insight is welcome.

    Thanks

    Friday, April 9, 2021 6:43 PM

All replies

  • User475983607 posted

    The error is a standard XML parsing error that happens when the client expects XML but receives an HTML error response instead.  Like a 500 or 4XX status.  Have you tried running the code through the debugger and looking a the response in the debugger?

    Is the intent to create a REST service?  

    Friday, April 9, 2021 7:39 PM
  • User1738843376 posted

    Hi Mgebhard,

    Thanks for your reply.

    I cant find the response on the debugger, since its exactly when the server attempts to read the response that it all halts.

    My intent is to provide a way to allow users from a partner website to download files that must remain unavailable to any other attempt to use the web service. These users need to be validated on the partner site, to evaluate if they are allowed to access the file, and then, the partner website must connect to our website via a webservice, providing some sort of credentials or key and the reference to the file to access.

    The webservice then checks for the credentials, and if they match, then the service streams the file back to the partner website, where they in return serve it to the user.

    I hope my explanation was not to dense...

    The web service code that i posted works perfectly when accessed directly via the browser, but we need to provide an example of how to access it programmatically, and i can't seem to make it work from that point of view.

    Any idea how to achieve this?

    Thanks

    Friday, April 9, 2021 8:09 PM
  • User475983607 posted

    It's hard to understand the intent given the code.  ASMX is a SOAP service.   The code looks like a REST service that returns either a byte array or HTML.  The design returns a 200 Ok when there is an error.  The client will not expect a 200 response with an HTML error message.  Clients will expect 404 or 401 status.  A SOAP client will expect a SOAP Exception.

    Can you explain the technical design?   Are you building a REST service using ASMX?  Or are you building a SOAP service?

    Friday, April 9, 2021 9:23 PM
  • User1738843376 posted

    Hi again mgebhard,

    I guess it needs to be REST, since what i need is to validate a request from an external client, making sure it is the allowed client, and in return stream back a specific PDF file according to the data received from the client.

    It is my understanding that SOAP only deals with XML data, and never with filetypes.

    To make it more clear:

    • My website has a number of files that are not open access;
    • A friend website needs to be allowed to access some of these files, provided the user of that same website meets the requirements imposed by them;
    • Nobody accessing from anywhere can gain access to the file via a URL, so that we can control who can access the file;
    • The webservice consumer needs to be validated on our website before being served the file;
    • The file must be served via stream, so that it only exists in memory while the user of the friendly website is seeing it (sure, they can save it, but to share it they will need to provide the file themselves to someone, and not simply the URL directly calling the file on our website);
    • We need to obfuscate/hide as much as possible the data provided on the request, hence the option for POST and not GET, so that that data is not exposed in an URL somewhere on the friend website source code;

    These are the basic needs i have, and what i intend to achieve with the code posted earlier.

    Hope you can provide me some insight on this

    Friday, April 9, 2021 10:37 PM
  • User475983607 posted

    I created a HTTP example but it's not the approach I recommend.  It a bit hacky.   SOAP gives you more security options.   Frankly, Web API is a far more robust framework and much easier to implement this requirement.  

    Web Service

    Imports System.ComponentModel
    Imports System.IO
    Imports System.Web.Services
    Imports System.Web.Services.Protocols
    
    ' To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
    ' <System.Web.Script.Services.ScriptService()> _
    <System.Web.Services.WebService(Namespace:="http://tempuri.org/")>
    <System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)>
    <ToolboxItem(False)>
    Public Class GetSecurePDF
        Inherits System.Web.Services.WebService
    
        <WebMethod()>
        Public Function HelloWorld() As String
            Return "Hello World"
        End Function
    
        <WebMethod()>
        Public Function DownloadPdf(ByVal providedKey As String, ByVal requestedFileId As String) As Byte()
    
            Dim fileBytes As Byte() = Nothing
    
            If providedKey = ConfigurationManager.AppSettings("kitSecurityKey") Then
    
                Dim strFilename As String = Context.Server.MapPath("~/ProtectedDocs/test_" & requestedFileId & ".pdf")
    
                If (File.Exists(strFilename)) Then
                    fileBytes = File.ReadAllBytes(strFilename)
    
                    Context.Response.Clear()
                    Context.Response.ClearHeaders()
                    Context.Response.ContentType = "application/pdf"
                    Context.Response.AddHeader("content-disposition", "attachment; filename=test_" & requestedFileId & ".pdf")
                    Context.Response.AddHeader("Content-Length", fileBytes.Length)
                    Context.Response.OutputStream.Write(fileBytes, 0, fileBytes.Length)
                    Context.Response.Flush()
                    Context.Response.End()
                End If
            End If
    
            fileBytes = System.Text.Encoding.Unicode.GetBytes("You are not allowed to access this resource")
            Context.Response.Clear()
            Context.Response.ClearHeaders()
            Context.Response.StatusCode = 401
            Context.Response.ContentType = "text/html"
            Context.Response.OutputStream.Write(fileBytes, 0, fileBytes.Length)
            Context.Response.Flush()
            Context.Response.End()
    
            Return fileBytes
        End Function
    
    End Class

    The client is pretty simple.  I used a standard web form with a button.  The client could be a self-submitting form or a basic HTTP request using code.  Code client expect a 200 status.  You need to take this into consideration when building your test client.  If you don't get a 200 then something went wrong.  

    <%@ Page Language="vb" AutoEventWireup="false" CodeBehind="default.aspx.vb" Inherits="WebFormsVBDemo._default7" %>
    
    <!DOCTYPE html>
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <asp:HiddenField ID="providedKey" runat="server" Value="123" />
                <asp:HiddenField ID="requestedFileId" runat="server" Value="2" />
                <asp:Button ID="Button1" runat="server" Text="Button" 
                    PostBackUrl="https://localhost:44323/GetSecurePDF.asmx/DownloadPdf " />
            </div>
        </form>
    </body>
    </html>
    

    Saturday, April 10, 2021 12:49 AM
  • User1738843376 posted

    Hi mgebhard,

    Again, thx for the example.  I had a similar example working with a post and using hidden fields, but that lacks the security for the authentication, since the key is exposed on the hidden field. This is why i was looking for a way to perform the connection via the codebehind, so that the info not easily accessible.

    At this time, i am able to connect and receive the file with the exemple below:

    Dim webRequest = System.Net.HttpWebRequest.Create("http://localhost/SecureWebHandlerTest/GetSecureDoc.ashx")
    
    webRequest.Method = "POST"
    
    Dim postString As String = "security_key=mysimplekey&file_id=03"
    webRequest.ContentLength = postString.Length
    webRequest.ContentType = "application/x-www-form-urlencoded"
    Dim requestWriter As StreamWriter = New StreamWriter(webRequest.GetRequestStream())
    requestWriter.Write(postString)
    requestWriter.Close()
    
    Dim responseReader As StreamReader = New StreamReader(webRequest.GetResponse().GetResponseStream())
    Dim responseData As String = responseReader.ReadToEnd()
    
    responseReader.Close()
    webRequest.GetResponse().Close()
    
    Response.Clear()
    Response.ClearHeaders()
    
    Response.AddHeader("content-disposition", "attachment; filename=test_03.pdf")
    Response.AddHeader("Content-Length", responseData.Length)
    Response.ContentType = "application/pdf"
    
    Response.OutputStream.Write(Encoding.UTF8.GetBytes(responseData), 0, Encoding.UTF8.GetBytes(responseData).Length)
    Response.Flush()
    Response.End()

    As you can see, i'm now using an handler (ashx), and the file arrives at the client, but the encoding seems to always be wrong, no matter what option i set on the code. 

    I can open the file received with the notepad, and see that the contents are there, but the encoding is always messed, hence preventing the acrobat reader or the browser from opening it.

    This is the ashx code:

    Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
    
    	Dim providedKey As String = context.Request.Form("security_key")
    	Dim requestedFileId As String = context.Request.Form("file_id")
    	Dim strFilename As String = context.Server.MapPath("~/ProtectedDocs/test_" & requestedFileId & ".pdf")
    
    	Try
    
    		If providedKey = ConfigurationManager.AppSettings("kitSecurityKey") Then
    
    			'Give the document a name
    			context.Response.Clear()
    			context.Response.ClearHeaders()
    			context.Response.AddHeader("content-disposition", "attachment; filename=test_" & requestedFileId & ".pdf")
    			context.Response.ContentType = "application/pdf"
    			context.Response.WriteFile(strFilename)
    			context.Response.End()
    
    		End If
    
    	Catch ex As Exception
    
    	End Try
    	
    End Sub

    Saturday, April 10, 2021 2:33 PM
  • User1535942433 posted

    Hi 0belix,

    I think you could let the client know we processed their request successfully.

    context.Resonse.StatusCode = HttpStatusCode.OK;

    Best regards,

    Yijing Sun

    Tuesday, April 13, 2021 9:44 AM
  • User1738843376 posted

    Hi guys,

    Thanks all for your contributions.

    I ended up creating a WebAPI, but ended up with the same encoding issue. 

    Later on, i realized what could have saved me a lot of trouble... i was using response.Write instead of response.BinaryWrite.

    As soon as i changed it, the files started to arrive correctly. I guess the byte array needed to be handed directly, and not reencoded back to a string, to then return it with response.write.

    Best

    Thursday, April 15, 2021 6:25 PM
  • User1535942433 posted

    Hi 0belix,

    Thanks all for your contributions.

    I ended up creating a WebAPI, but ended up with the same encoding issue. 

    Later on, i realized what could have saved me a lot of trouble... i was using response.Write instead of response.BinaryWrite.

    As soon as i changed it, the files started to arrive correctly. I guess the byte array needed to be handed directly, and not reencoded back to a string, to then return it with response.write.

    Best

    Do you have solved your problems now? If  you still have problems,you could post to us.If  you have solved,you could mark these answers which help you.It will help more member which have same issue to look for you.

    Best regards,

    Yijing Sun

    Friday, April 16, 2021 2:33 AM
  • User1531687445 posted

    Thanks

    Friday, April 16, 2021 4:12 PM