简体   繁体   中英

Can't expose REST XML, REST JSON and SOAP properly via configuration only for WCF

I'm wanting to be able to build a WCF service which achieves the following:

  • RESTfully exposes JSON
  • RESTfully exposes XML
  • Exposes SOAP
  • Other bindings if necessary

The key here, is that I want to do this all via configuration , and only one function written in code for each required method, rather than having to specify separately in code ResponseFormat=ResponseFormat.Json or ResponseFormat=ResponseFormat.Xml above separate functions for the RESTful methods. I've done plenty of research and I can't find anything solid for whether this is possible purely by configuration.

The strange thing is, when I build the project the RESTful methods work when I access them via URL, but the WSDL throws an error - ie if someone wanted to reference/consume the service via SOAP it would fail at the WSDL import step.

The code for the service is below, as well as the configuration, and the service is hosted locally for testing under http://localhost/WCF . The following 2 RESTful calls work succesfully and return successful XML and JSON in the browser:

localhost/WCF/service1.svc/json/students

localhost/WCF/service1.svc/rest/students

But, if I call http://localhost/WCF/service1.svc?wsdl I get the error below. If I remove one of the webhttpBinding endpoint configurations, the WSDL works and displays correctly, and can therefore be referenced.

Is it just not possible to have multiple webHttpBindings in the config (ie has to be done via separate methods and attributes such as ResponseFormat=ResponseFormat.Json ) for WSDL generation to be valid? Or have I configured something incorrectly here?

Any help appreciated, would be so much more straightforward to do it via config that extra functions with serialization specified. Thanks.

WSDL generation error

An ExceptionDetail, likely created by IncludeExceptionDetailInFaults=true, whose value is:
    System.NullReferenceException: Object reference not set to an instance of an object.
       at System.ServiceModel.Description.WsdlExporter.CreateWsdlBindingAndPort(ServiceEndpoint endpoint, XmlQualifiedName wsdlServiceQName, Port& wsdlPort, Boolean& newBinding, Boolean& bindingNameWasUniquified)
       at System.ServiceModel.Description.WsdlExporter.ExportEndpoint(ServiceEndpoint endpoint, XmlQualifiedName wsdlServiceQName)
       at System.ServiceModel.Description.WsdlExporter.ExportEndpoints(IEnumerable`1 endpoints, XmlQualifiedName wsdlServiceQName)
       at System.ServiceModel.Description.ServiceMetadataBehavior.MetadataExtensionInitializer.GenerateMetadata()
       at System.ServiceModel.Description.ServiceMetadataExtension.EnsureInitialized()
       at System.ServiceModel.Description.ServiceMetadataExtension.get_Metadata()
       at System.ServiceModel.Description.ServiceMetadataExtension.HttpGetImpl.InitializationData.InitializeFrom(ServiceMetadataExtension extension)
       at System.ServiceModel.Description.ServiceMetadataExtension.HttpGetImpl.GetInitData()
       at System.ServiceModel.Description.ServiceMetadataExtension.HttpGetImpl.TryHandleMetadataRequest(Message httpGetRequest, String[] queries, Message& replyMessage)
       at System.ServiceModel.Description.ServiceMetadataExtension.HttpGetImpl.ProcessHttpRequest(Message httpGetRequest)
       at SyncInvokeGet(Object , Object[] , Object[] )
       at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
       at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
       at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
       at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
       at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

Web.config

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="httpGetBehavior" name="WCF.Service1">
        <endpoint address="json" binding="webHttpBinding" name="jsonRest" contract="WCF.IService1" behaviorConfiguration="jsonBehavior"></endpoint>
        <endpoint address="rest" binding="webHttpBinding" name="xmlRest" contract="WCF.IService1" behaviorConfiguration="restBehaviour"></endpoint>
        <endpoint address="soap" binding="basicHttpBinding" name="soap" contract="WCF.IService1"></endpoint>
        <endpoint name="mex" address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost/WCF"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="httpGetBehavior">
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="jsonBehavior">
          <webHttp defaultOutgoingResponseFormat="Json"/>
        </behavior>
        <behavior name="restBehaviour">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>  
</configuration>

Code

public class Service1 : IService1
{       
    public IList<Student> GetStudents()
    {
        IList<Student> students = new List<Student>();
        students.Add(new Student() { FirstNme = "Bob", LastName = "Long", StudentId = 1, Subject = "Economics" });
        students.Add(new Student() { FirstNme = "Jack", LastName = "Short", StudentId = 2, Subject = "IT" });
        return students;
    }
}

[ServiceContract]
public interface IService1
{       
    [WebGet(UriTemplate = "/students")]
    [OperationContract]
    IList<Student> GetStudents();

}

[DataContract]
public class Student
{
    private int _studentId;
    private string _firstName;
    private string _lastName;
    private string _subject;

    [DataMember]
    public int StudentId
    {
        get { return _studentId; }
        set { _studentId = value; }
    }

    [DataMember]
    public string FirstNme
    {
        get { return _firstName; }
        set { _firstName = value; }
    }

    [DataMember]
    public string LastName
    {
        get { return _lastName; }
        set { _lastName = value; }
    }

    [DataMember]
    public string Subject
    {
        get { return _subject; }
        set { _subject = value; }
    }
}

This is a known issue where having json, xml and soap endpoints for a single service throws an exception.

I have raised it as an error on MS Connect .

If you switch your Framework to 3.5 then that would work or an workaround is provided as well. You can just get the content-header from the request object and determine that in your code to set the response format and send the response back accordingly.

Also in the Web API, the framework automatically determines this based on the content type if nothing is specified in your WebGet/WebInvoke attributes and returns the response accordingly.

NOTE : At the moment when i try to open the link on MS Connect I get some system error on the MS Connect site. If you would like the workaround let me know i can send it across. Also MS has confirmed that they are not going to Fix it as not many clients would want to expose all the 3 formats on a single service.

Hmmm, I have created a very simple service that works with having SOAP, JSON, and XML endpoints. Does it work only because of its simplicity?

I found out that I had to declare separate binding configurations (albeit empty) for json and xml. Here is the configuration (updated, because only the system.serviceModel section was visible before):

<?xml version="1.0"?>
<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0"/>
  </system.web>

  <system.serviceModel>

    <bindings>
      <webHttpBinding>
        <!-- separate bindings are necessary -->
        <binding name="jsonBinding"/>
        <binding name="xmlBinding"/>
      </webHttpBinding>
    </bindings>

    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>

      <endpointBehaviors>
        <behavior name="xmlEndpoint">
          <webHttp helpEnabled="true" defaultOutgoingResponseFormat="Xml"/>
        </behavior>
        <behavior name="jsonEndpoint">
          <!-- do not specify enableWebScript or UriTemplate will not work -->
          <webHttp helpEnabled="true" defaultOutgoingResponseFormat="Json"/>
        </behavior>
      </endpointBehaviors>
    </behaviors>

    <services>
      <service name="Services.Addressbook.AddressbookService">
        <endpoint address="" binding="basicHttpBinding" name="soap" contract="Services.Addressbook.IAddressbookService" />
        <endpoint address="mex" binding="mexHttpBinding" name="mex" contract="IMetadataExchange" />
        <endpoint address="json" behaviorConfiguration="jsonEndpoint" binding="webHttpBinding" bindingConfiguration="jsonBinding" name="restJson" contract="Services.Addressbook.IAddressbookService" />
        <endpoint address="xml" behaviorConfiguration="xmlEndpoint" binding="webHttpBinding" bindingConfiguration="xmlBinding" name="restXml" contract="Services.Addressbook.IAddressbookService" />
      </service>
    </services>

    <serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>

  </system.serviceModel>

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>

</configuration>

This is the service contract:

[ServiceContract]
public interface IAddressbookService {
  [OperationContract]
  [WebGet(UriTemplate = "Version")]
  string GetVersion();

  [OperationContract]
  [WebGet(UriTemplate = "Entries/Count")]
  int GetEntriesCount();

  [OperationContract]
  [WebGet(UriTemplate = "Entries")]
  Address[] GetEntries();

  [OperationContract]
  [WebGet(UriTemplate = "Entries/{name}")]
  Address[] SearchEntriesByName(string name);

  [OperationContract]
  [WebInvoke(Method = "POST", UriTemplate = "Entries/Add")]
  bool AddEntry(Address entry);

  [OperationContract]
  [WebInvoke(Method = "DELETE", UriTemplate = "Entries/Remove")]
  bool RemoveEntry(Address entry);
}

And here is the data contract:

[DataContract]
public class Address {
  [DataMember]
  public string FirstName { get; set; }
  [DataMember]
  public string LastName { get; set; }
  [DataMember]
  public string Street { get; set; }
  [DataMember]
  public string City { get; set; }
  [DataMember]
  public int ZipCode { get; set; }
  [DataMember]
  public string Country { get; set; }
  [DataMember]
  public string Phone { get; set; }
  [DataMember]
  public string Email { get; set; }
}

Please try this

<bindings>
  <webHttpBinding>
    <binding name="webBindingXML"></binding>
    <binding name="webBindingSOAP"></binding>
  </webHttpBinding>
</bindings>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM