views:

751

answers:

2

I am going nuts here. I've looked at the following entries and none of them are correcting the aberrant behavior I am seeing:

I've also looked at, and confirmed my setup: http://www.asp.net/AJAX/documentation/live/ConfiguringASPNETAJAX.aspx

Here is my code (ASMX code behind):

namespace RivWorks.Web.Services
{
    /// <summary>
    /// Summary description for Negotiate
    /// </summary>
    [WebService(Namespace = "http://rivworks.com/webservices/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
    [ScriptService]
    public class Negotiate : System.Web.Services.WebService
    {
        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public RivWorks.Data.Objects.rivProduct GetSetup(string jsonInput)
        {
            // Deserialize the input and get all the data we need...
            // TODO:  This is a quick hack just to work with this for now...
            char[] tokens = { '(', '{', '}', ')', ',', '"' };
            string[] inputs = jsonInput.Split(tokens);
            string inputRef = "";
            string inputDate = "";
            string inputProductID = "";
            for (int i = 0; i < inputs.Length; i++)
            {
                if (inputs[i].Equals("ref", StringComparison.CurrentCultureIgnoreCase))
                    inputRef = inputs[i+2];
                if (inputs[i].Equals("dt", StringComparison.CurrentCultureIgnoreCase))
                    inputDate = inputs[i+2];
                if (inputs[i].Equals("productid", StringComparison.CurrentCultureIgnoreCase))
                    inputProductID = inputs[i+2];
            }

            Guid pid = new Guid(inputProductID);
            RivWorks.Data.Objects.rivProduct product = RivWorks.Data.rivProducts.GetProductById(pid);

            return product;
        }
    }

When I run this from my localhost instance I am getting this result set:

  <ResultSet>
    <uiType>modal</uiType>
    <width>775</width>
    <height>600</height>
    <swfSource>
    http://localhost.rivworks.com/flash/negotiationPlayer.swf
    </swfSource>
    <buttonConfig>
    http://cdn1.rivworks.com/Element/Misc/734972de-40ae-45f3-9610-5331ddd6e8f8/apple-logo-2.jpg
    </buttonConfig>
  </ResultSet>

What am I missing???


NOTE: I am using the 3.5 framework (or at least I thought I was as everything in my web.config is marked for 3.5.0.0)


UPDATE: I am browsing to the service and using the input box on that page. You can try here: http://dev.rivworks.com/services/Negotiate.asmx?op=GetSetup. We are also trying to access it from a JS based web app running on another site (the main purpose of this particular service). I do not have the code for that here. (Sorry, the test form is only available from localhost.)


UPDATE: I added the following test page (JsonTest.htm) to try to see what was going back and forth. All I get is a 500 error! I even tried attaching to the process and break into my service. THe 500 error is thrown before the ASP pipeline ever gets into my code.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<head>
    <title>Untitled Page</title>
    <script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.3.2.js" type="text/javascript"></script>

    <script language="javascript" type="text/javascript">
        function sendReq() {
            alert("Before AJAX call");
            $.ajax(
            {
                type: "POST"
                , url: "http://kab.rivworks.com/Services/Negotiate.asmx/GetSetup"
                , data: "{ \"ref\":\"http://www.rivworks.com/page.htm\", \"dt\":\"Mon Dec 14 2009 10:45:25 GMT-0700 (MST)\", \"productId\":\"5fea7947-251d-4779-85b7-36796edfe7a3\" }"
                , contentType: "application/json; charset=utf-8"
                , dataType: "json"
                , success: GetMessagesBack
                , error: Failure
            }
            );
            alert("After AJAX call");
        }
        function GetMessagesBack(data, textStatus) {
            alert(textStatus + "\n" + data);
        }
        function Failure(XMLHttpRequest, textStatus, errorThrown) {
            alert(textStatus + "\n" + errorThrown + "\n" + XMLHttpRequest);
        }
    </script>
</head>
<body>
    <div id="test">Bleh</div>
    <a href="javascript:sendReq()">Test it</a>
</body>
</html>

Why is this so painfully hard?!?! :)


UPDATE: Working through a WCF service. Here is my setup: Interface:

namespace RivWorks.Web.Services
{
    [ServiceContract(Name = "Negotiater", Namespace = "http://www.rivworks.com/services")]
    public interface INegotiaterJSON
    {
        //[WebMethod]
        [OperationContract]
        [WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
        [ScriptMethod(UseHttpGet = false, ResponseFormat = ResponseFormat.Json)]
        ResultSet GetSetup(string jsonInput);
    }
}

Class:

namespace RivWorks.Web.Services
{
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class Negotiater : INegotiaterJSON
    {
        public ResultSet GetSetup(string jsonInput)
        {
            //code removed for brevity - see ASMX code above if you are really interested.
            return resultSet;
        }
    }


    [DataContract()]
    public class ResultSet
    {
        [DataMember]
        public string uiType = "modal";
        [DataMember]
        public int width = 775;
        [DataMember]
        public int height = 600;
        [DataMember]
        public string swfSource = "";
        [DataMember]
        public string buttonConfig = "";
    }
}

web.config

  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name ="soapBinding">
          <security mode="None" />
        </binding>
      </basicHttpBinding>
      <webHttpBinding>
        <binding name="webBinding">
          <security mode="None" />
        </binding>
      </webHttpBinding>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="poxBehavior">
          <webHttp/>
        </behavior>
        <behavior name="jsonBehavior">
          <enableWebScript  />
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="defaultBehavior">
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="RivWorks.Web.Services.Negotiater" behaviorConfiguration="defaultBehavior">
        <endpoint address="json"
                  binding="webHttpBinding"
                  bindingConfiguration="webBinding"
                  behaviorConfiguration="jsonBehavior"
                  contract="RivWorks.Web.Services.INegotiaterJSON" />
      </service>
    </services>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true">
      <baseAddressPrefixFilters>
        <add prefix="http://dev.rivworks.com" />
      </baseAddressPrefixFilters>
    </serviceHostingEnvironment>
  </system.serviceModel>

simple test page

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<head>
    <title>Untitled Page</title>
    <script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.3.2.js" type="text/javascript"></script>

    <script language="javascript" type="text/javascript">
        function sendReq() {
            alert("Before AJAX call");
            $.ajax(
            {
                type: "POST"
                , url: "http://dev.rivworks.com/Services/Negotiater.svc/GetSetup"
                , data: "{ \"ref\":\"http://www.rivworks.com/page.htm\", \"dt\":\"Mon Dec 14 2009 10:45:25 GMT-0700 (MST)\", \"productId\":\"5fea7947-251d-4779-85b7-36796edfe7a3\" }"
                , contentType: "application/json; charset=utf-8"
                , dataType: "json"
                , success: GetMessagesBack
                , error: Failure
            }
            );
            alert("After AJAX call");
        }
        function GetMessagesBack(data, textStatus) {
            alert(textStatus + "\n" + data);
        }
        function Failure(XMLHttpRequest, textStatus, errorThrown) {
            alert(textStatus + "\n" + errorThrown + "\n" + XMLHttpRequest);
        }
    </script>

</head>
<body>
    <div id="test">Bleh</div>
    <!--<button onclick="javascript:sendReq()">TEST IT</button>-->
    <a href="javascript:sendReq()">Test it</a>
</body>
</html>

And now I am getting this error: IIS specified authentication schemes 'IntegratedWindowsAuthentication, Anonymous', but the binding only supports specification of exactly one authentication scheme. Valid authentication schemes are Digest, Negotiate, NTLM, Basic, or Anonymous. Change the IIS settings so that only a single authentication scheme is used.

How do I handle this? <state emotion='wrung out' physical='beat up' />

+8  A: 

Why don't you migrate your ASMX webservice to WCF?

The WCF API in .NET Framework 3.5 supports JSON web services natively.

In addition Microsoft declared ASMX as "legacy technology", and suggests "Web services and XML Web service clients should now be created using Windows Communication Foundation (WCF)". (Source).

You may want to check out these links to get started:

In addition, you may also want to read through the following example which I "extracted" from a self-hosted WCF project of mine. Self-hosted WCF services do not require IIS, but can be served from any managed .NET application. This example is being hosted in a very simple C# Console Application:

IContract.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace MyFirstWCF
{
    [ServiceContract] 
    public interface IContract
    {
        [OperationContract]
        [WebGet(ResponseFormat = WebMessageFormat.Json, UriTemplate = "/CustomerName/{CustomerID}")]
        string GET_CustomerName(string CustomerID);
    }
}

Service.cs

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Syndication;
using System.ServiceModel.Web;

namespace MyFirstWCF
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.NotAllowed)]
    public class Service : IContract
    {
        public string GET_CustomerName(string CustomerID)
        {
            return "Customer Name: " + CustomerID;
        }
    }
}

WCFHost.cs (Console Application)

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Description;
using System.Threading;
using System.Text;

namespace MyFirstWCF
{
    class Program
    {
        private static WebServiceHost M_HostWeb = null;

        static void Main(string[] args)
        {
            M_HostWeb = new WebServiceHost(typeof(MyFirstWCF.Service));

            M_HostWeb.Open();

            Console.WriteLine("HOST OPEN");
            Console.ReadKey();

            M_HostWeb.Close();
        }
    }
}

app.config

<?xml version="1.0" encoding="utf-8" ?>

<configuration>
  <system.serviceModel>
    <services>
      <service name="MyFirstWCF.Service">

        <endpoint address="http://127.0.0.1:8000/api"
                  binding="webHttpBinding"
                  contract="MyFirstWCF.IContract" />

      </service>
    </services>

  </system.serviceModel>
</configuration>

The above example is very basic. If you build a request with Fiddler to http://127.0.0.1:8000/api/CustomerName/1000 it will simply return "Customer Name: 1000".

Make sure to set the content-type: application/json in the request header. To return more complex data structures, you will have to use Data Contracts. These are constructed as follows:

[DataContract]
public class POSITION
{
    [DataMember]
    public int      AssetID { get; set; }

    [DataMember]
    public decimal  Latitude { get; set; }

    [DataMember]
    public decimal  Longitude { get; set; }
}

You need to add .NET References to System.RuntimeSerialization, System.ServiceModel and System.ServiceModel.Web for this example project to compile.

Daniel Vassallo
Hmmm. Seems something is FUBAR on my machine. I do not have an option to create an AJAX WFC Service. Is there something I should install on top of VS2008?
Keith Barrows
You can just create a normal WCF service; you can decorate your interface with attributes to indicate that the ResponseFormat will be JSON.
Jan Jongboom
I don't have an option to create *any* WCF service. http://screencast.com/t/OGQwMDlk
Keith Barrows
Can you try to create a new solution, to check if the WCF option appears? When you add a new project, you should have the choice of framework version, as shown here: http://www.techbubbles.com/net-framework/wcf-sample-in-visual-studio-2008/. Make sure you have .NET 3.5 selected wherever prompted.
Daniel Vassallo
OK. Going that route I do have the option for a WCF project. Any references on building a .NET WCF JSON enabled WS *AND* calling it from a seperate (non .NET) web site? Maybe a pure HTML site with JS as the calling method?
Keith Barrows
I would suggest checking out the links I have posted in my answer, and google a bit on WCF RESTful web services. You should be using the webHttpBinding. Methods will respond in JSON if their interface is decorated with `[WebGet(ResponseFormat = WebMessageFormat.Json]`.
Daniel Vassallo
Been working through all the links you posted (TYVM!). Still not getting a simple service to run correctly. Getting brain wiped at this point. I created a seperate project for the WS. Now trying to figure out the "correct" way to merge it back into my Web project...
Keith Barrows
Keith, give a look to the example I've included in the answer. This is from a self-hosted WCF project, so it was not being served from IIS.
Daniel Vassallo
The example described above will return pure JSON, as you requested. It should work perfectly with your external JavaScript application. Obviously you will need to construct your Data Contracts according to the JSON format of your spec.
Daniel Vassallo
If the JSON request is cross domain, you'll need to look into JSONP. You can implement it in .NET 2 using an extension - http://msdn.microsoft.com/en-us/library/cc716898.aspx. It's also supporting in .NET 4 - http://bendewey.wordpress.com/2009/11/24/using-jsonp-with-wcf-and-jquery/
Richard Szalay
You can also use a very simple reverse proxy to get around the cross-domain limitation. However this is a problem of the client-side, and it appears to be outside Keith's control/responsibility.
Daniel Vassallo
+2  A: 

What's the "Content-Type" set to on the request to the method?

From what I've done with the ASP.NET, if it's set to text/xml, you'll get back XML; but if it's set to application/json, you'll get JSON back.

Chris Waters
application/json in JS. Note: The JS is not on our pages! THe WS needs to respond to a foreign JS request.
Keith Barrows
I think the point is that it will return the response in the type that is requested, which seems to be 'text/xml' in your example.Even if it's not a request from something you wrote, you could easily write a small JS script of your own to test it with 'application/json'
Chris Waters
The content type is very important. Thanks!
Keith Barrows