locked
jQuery AJAX Upload to ASMX WebMethod with return values RRS feed

  • Question

  • User-1826049516 posted

    Hi,

    I'm struggling to get this working. If I include dataType: "json" I always get thrown to request.fail but no errors actually show in the browser console. If I remove dataType: "json" I cannot get the return response with error in the console: Uncaught TypeError: Cannot read property 'Guid' of undefined.

    Everything else works perfectly - file gets upload and written to database just fine.

    JQuery:

    	$( document ).on( "change", "input[name='files']", function( e ) {
    	
    		var files = $( this ).prop( "files");
    		var data = new FormData();
    		
    		var request;
    		var modal = $( this ).closest( ".modal" );
    		var itemid = modal.data( "itemid" );
    		
    		for( var i = 0; i < files.length; i++ ) {
    		
    			data.append( "id", itemid );
    			data.append( "file", files[i] );
    			
    			request = $.ajax( {
    			
    				type: "POST",
    				url: "/webservices/webservices.asmx/UploadFile",
    				data: data,
    				// dataType: "json",
    				contentType: false,
    				processData: false
    				
    			} );
    			
    			request.done( function( response ) {
    			
    				result = response.d;
    				console.log( result.Guid );
    				
    			} );
    			
    			request.fail( function( response ) {
    			
    				console.log( response.responseText );
    				
    			} );
    			
    			request.always( function() {
    			
    				data.delete( itemid );
    				data.delete( files[i] );
    				
    			} );
    			
    		}
    		
    	} );
    	
    

    WebMethod:

    	public class Response
    {

    public Guid Guid;
    public bool Toggle;
    public string Date;
    public string Text;
    public string Image;

    }

    [WebMethod] public Response UploadFile() { Response Response = new Response(); Guid ItemId = Guid.Parse(HttpContext.Current.Request.Form["id"]); string SavePath = HttpContext.Current.Server.MapPath("~/docs/"); HttpFileCollection Files = HttpContext.Current.Request.Files; for (int i = 0; i < Files.Count; i++) { HttpPostedFile File = Files[i]; Guid FileId = BasePage.NewGuid; string FileName = File.FileName; string FileExt = Path.GetExtension(File.FileName); DateTime FileMod = DateTime.Now; string FileType = File.ContentType; long FileSize = File.ContentLength; string FilePath = "/docs/"; File.SaveAs(Path.Combine(SavePath, String.Concat(FileId, FileExt))); using (SqlConnection Connection = new SqlConnection(BasePage.SqlString)) using (SqlCommand Command = new SqlCommand("AddEventFile", Connection)) { Command.CommandType = CommandType.StoredProcedure; Command.Parameters.AddWithValue("@FileId", FileId); Command.Parameters.AddWithValue("@EventId", ItemId); Command.Parameters.AddWithValue("@UserId", BasePage.UserId); Command.Parameters.AddWithValue("@Name", FileName); Command.Parameters.AddWithValue("@Extension", FileExt); Command.Parameters.AddWithValue("@Modified", FileMod); Command.Parameters.AddWithValue("@Type", FileType); Command.Parameters.AddWithValue("@Size", FileSize); Command.Parameters.AddWithValue("@Path", FilePath); Connection.Open(); using (SqlDataReader Reader = Command.ExecuteReader()) { if (Reader.Read()) { Response.Guid = (Guid)Reader[0]; Response.Text = (string)Reader[1]; Response.Image = (string)Reader[2]; } } } } return Response; }

    Monday, December 3, 2018 6:01 PM

Answers

  • User475983607 posted

    The issue has to do with content negotiation.  Your sending application/x-www-form-urlencoded and the XML service which causes the service to return XML; the default for XML Web Services.  If you send content-type: application/json the service will return json.

    There are a few solutions.  IMHO, the best is using Web API.  If you can't use Web API craft a custom JSON formatter so the XML Service always returns JSON rather than the default XML or do it manually by returning a JSON string from the service end point. 

        [WebService(Namespace = "http://tempuri.org/")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        [System.ComponentModel.ToolboxItem(false)]
        // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
        [System.Web.Script.Services.ScriptService]
        public class WebService1 : System.Web.Services.WebService
        {
    
            [WebMethod]
            [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
            public void HelloWorld()
            {
                Message m = new Message() { Content = "Hello World" };
                Context.Response.Clear();
                Context.Response.ContentType = "application/json";
                Context.Response.Write(new JavaScriptSerializer().Serialize(m));
            }
    
            public class Message
            {
                public string Content { get; set; }
            }
        }

    Or simply accept the fact that the result is XML and just parse the XML in the response.

        [WebService(Namespace = "http://tempuri.org/")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        [System.ComponentModel.ToolboxItem(false)]
        // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
        [System.Web.Script.Services.ScriptService]
        public class WebService1 : System.Web.Services.WebService
        {
    
            [WebMethod]
            [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
            public Message HelloWorld()
            {
                Message m = new Message() { Content = "Hello World" };
                return m;
            }
    
            public class Message
            {
                public string Content { get; set; }
            }
        }
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title></title>
        <script src="https://code.jquery.com/jquery-3.3.1.js"
                integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60="
                crossorigin="anonymous"></script>
    </head>
    <body>
        <input id="Button1" type="button" value="button" />
        <script>
            $('#Button1').click(function () {
                $.ajax({
                    type: "POST",
                    url: "/WebService1.asmx/HelloWorld",
                    data: {}
                }).done(function (data) {
                    console.log(data);
                    console.log($(data).find('Message Content').text());
                });
            });
    
        </script>
    </body>
    </html>



     

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, December 4, 2018 3:36 PM

All replies

  • User475983607 posted

    Simple return a type rather than Response.

    Monday, December 3, 2018 6:29 PM
  • User-474980206 posted

    the json support is rather limited for web methods. only primitive type and arrays of primitive types. try:

    return new string[] {Response.Guid.ToString(),Response.Text,Response.Image)};
    	

      use WCF is you want better json support, or MVC/webapi.

    Monday, December 3, 2018 10:18 PM
  • User-1826049516 posted

    Can I return multiple values without a response?

    Monday, December 3, 2018 11:20 PM
  • User1724605321 posted

    Hi idoodle ,

    As @Bruce said , you can return an array of strings:

    return new string[] { data, data1 };

    Or  create a custom data type that has the two return values you want .

    Best Regards,

    Nan Yu

    Tuesday, December 4, 2018 3:23 AM
  • User-1826049516 posted

    I think I'm missing something here! I tried with a simple return string value and that doesn't seem to make a difference. With my code posted above, this is what console.log( response.responseText ) returns from request.fail:

    <?xml version="1.0" encoding="utf-8"?>
    <Response xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://domain.com/webservices">
      <Guid>6165a989-429e-4441-ba7a-7097d0bf6fd0</Guid>
      <Toggle>false</Toggle>
      <Text>RegEx.txt</Text>
      <Image>/images/app_icons/gen-text.png</Image>
    </Response>

    So it appears it's returning XML. If I change my WebMethod to return a simple string of "Hello" (and comment out all other code), this is what I get, again from request.fail:

    <?xml version="1.0" encoding="utf-8"?>
    <string xmlns="https://domain.com/webservices">Hello</string>

    rather than just the string "hello". I have other WebMethod and ajax requests that do not use FormData to post and they all work fine, because they do just return the string value I want to return (using Response and not string).

    So with FormData as the value for the ajax data part and with dataType: json it ALWAYS fails whether I return a simple string or response object. With FormData and no dataType it works ok but is not returning a json object for me to get the values out.

    Tuesday, December 4, 2018 10:30 AM
  • User-1826049516 posted

    If I check Network > Headers in Chrome, the response definitely is coming back as text/xml.

    This gets around it:

    	request.done( function( response ) {
    	
    		console.log( "Success" );
    		console.log( $( response ).find( "Guid" ).text() );
    		console.log( $( response ).find( "Text" ).text() );
    		console.log( $( response ).find( "Image" ).text() );
    		
    	} );
    

    Would rather work out (or at least understand) why the response always comes back as XML when FormData is used with processData: false.

    Tuesday, December 4, 2018 1:59 PM
  • User475983607 posted

    So it appears it's returning XML. If I change my WebMethod to return a simple string of "Hello" (and comment out all other code), this is what I get, again from request.fail:

    <?xml version="1.0" encoding="utf-8"?>
    <string xmlns="https://domain.com/webservices">Hello</string>

    Correct and the expected result for an XML (SOAP) web service.

    rather than just the string "hello". I have other WebMethod and ajax requests that do not use FormData to post and they all work fine, because they do just return the string value I want to return (using Response and not string).

    Unclear...  Please post your code.

    So with FormData as the value for the ajax data part and with dataType: json it ALWAYS fails whether I return a simple string or response object. With FormData and no dataType it works ok but is not returning a json object for me to get the values out.

    Correct, FormData is an HTML form and your AJAX function submits an  application/x-www-form-urlencoded; content-type.  dataType:'json' is the expected return type from the service.  Your configuration is telling the AJAX function to expect JSON but XML service is returning XML which causes the error.  Please read the jQuery AJAX documentation.

    If you want the XML Service to return JSON then apply an a response format to the service endpoint and return a type.

    [WebMethod]
    [ScriptMethod(ResponseFormat = ResponseFormat.Json)]

    Tuesday, December 4, 2018 2:15 PM
  • User-1826049516 posted

    Thanks for you post.

    None of my other WebMethod's return as XML - they all use Response as the return value. The only difference in this one is I'm passing FormData instead of JSON.stringify and processData is false.

    I'm not forcing "XML Services" unless that is the default for a C# WebMethod. In which case why are my other methods not returning XML? Is it because in this one I'm omitting dataType: "json"?

    I've tried forcing the ResponseFormat to Json in my WebMethod. Doesn't make any difference. I'll try again just to rule it out.

    Tuesday, December 4, 2018 2:30 PM
  • User-1826049516 posted

    With RequestFormat set to Json in WebMethod and dataType omitted in ajax:

    Network > Headers:

    Content-Type: text/xml; charset=utf-8

    Network > Response:

    <?xml version="1.0" encoding="utf-8"?>
    <Response xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://domain.com/webservices">
    <Guid>469165f4-6e10-4c2c-b9a2-f397b07f9cbd</Guid>
    <Toggle>false</Toggle>
    <Text>File1.txt</Text>
    <Image>/images/app_icons/gen-text.png</Image>
    </Response>

    With RequestFormat in WebMethod and dataType: json in ajax:

    hits request.fail, not request.done

    Tuesday, December 4, 2018 2:36 PM
  • User475983607 posted

    The issue has to do with content negotiation.  Your sending application/x-www-form-urlencoded and the XML service which causes the service to return XML; the default for XML Web Services.  If you send content-type: application/json the service will return json.

    There are a few solutions.  IMHO, the best is using Web API.  If you can't use Web API craft a custom JSON formatter so the XML Service always returns JSON rather than the default XML or do it manually by returning a JSON string from the service end point. 

        [WebService(Namespace = "http://tempuri.org/")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        [System.ComponentModel.ToolboxItem(false)]
        // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
        [System.Web.Script.Services.ScriptService]
        public class WebService1 : System.Web.Services.WebService
        {
    
            [WebMethod]
            [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
            public void HelloWorld()
            {
                Message m = new Message() { Content = "Hello World" };
                Context.Response.Clear();
                Context.Response.ContentType = "application/json";
                Context.Response.Write(new JavaScriptSerializer().Serialize(m));
            }
    
            public class Message
            {
                public string Content { get; set; }
            }
        }

    Or simply accept the fact that the result is XML and just parse the XML in the response.

        [WebService(Namespace = "http://tempuri.org/")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        [System.ComponentModel.ToolboxItem(false)]
        // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
        [System.Web.Script.Services.ScriptService]
        public class WebService1 : System.Web.Services.WebService
        {
    
            [WebMethod]
            [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
            public Message HelloWorld()
            {
                Message m = new Message() { Content = "Hello World" };
                return m;
            }
    
            public class Message
            {
                public string Content { get; set; }
            }
        }
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title></title>
        <script src="https://code.jquery.com/jquery-3.3.1.js"
                integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60="
                crossorigin="anonymous"></script>
    </head>
    <body>
        <input id="Button1" type="button" value="button" />
        <script>
            $('#Button1').click(function () {
                $.ajax({
                    type: "POST",
                    url: "/WebService1.asmx/HelloWorld",
                    data: {}
                }).done(function (data) {
                    console.log(data);
                    console.log($(data).find('Message Content').text());
                });
            });
    
        </script>
    </body>
    </html>



     

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, December 4, 2018 3:36 PM
  • User-1826049516 posted

    Thanks for the replies everyone. I've had a rethink on this one. I am going to change it so files are appended to formdata in a loop, then the ajax request is outside of that loop so it uploads all files in one go (keeping the loop in the WebMethod).

    This has brought on another issue which I'll post about!

    Wednesday, December 5, 2018 1:32 PM