WCF REST method and non-REST method - c#

I have a WCF Service that I need one of my methods accept HTTP POST request and the other one must be non-REST. Consider the code below:
[OperationContract]
long[] Send(string body, List<string> phoneNumbers);
[OperationContract]
[WebInvoke(Method="POST", BodyStyle = WebMessageBodyStyle.WrappedRequest)]
long[] SendByPost(string body, string phoneNumbers);
But I don't know why the error below is raised:
Operation 'Send' of contract 'IService' specifies multiple request
body parameters to be serialized without any wrapper elements. At most
one body parameter can be serialized without wrapper elements. Either
remove the extra body parameters or set the BodyStyle property on the
WebGetAttribute/WebInvokeAttribute to Wrapped.
How can I solve this issue?

First of all, to send multiple parameters in a url, you can use GET method instead of POST.
[OperationContract]
[WebInvoke(Method="POST", BodyStyle = WebMessageBodyStyle.WrappedRequest)]
long[] SendByGet(string body, string phoneNumbers);
Either change it into this:
[OperationContract]
[WebGet(UriTemplate = "/body={body}?phoneNumbers={phoneNumbers}"]
long[] SendByPost(string body, string phoneNumbers);
Or if you want to use POST.
[OperationContract]
[WebInvoke(Method="POST", BodyStyle = WebMessageBodyStyle.WrappedRequest,
UriTemplate = "MyUrl", RequestFormat = WebMessageFormat.Json
, ResponseFormat = WebMessageFormat.Json)]
long[] SendByPost(Info info);
And here is your Info class:
[DataContract]
Class Info
{
[DataMember]
public string Body {get; set;}
[DataMember]
public string PhoneNumbers{get; set;}
}

You have two methods and you want to expose one of them to REST and the other to another binding, right?
For this, you need to create more than one interface for your wcf service ([ServiceContract]).
Take, for example, this Web.config:
<system.serviceModel>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment minFreeMemoryPercentageToActivateService="1" aspNetCompatibilityEnabled="false" multipleSiteBindingsEnabled="true" />
<bindings>
<webHttpBinding>
<binding name="RestBinding" maxBufferSize="1048576" maxReceivedMessageSize="1048576" maxBufferPoolSize="1048576">
<readerQuotas maxDepth="2147483647" maxStringContentLength="1048576" maxArrayLength="1048576" maxBytesPerRead="1048576" maxNameTableCharCount="1048576"/>
</binding>
</webHttpBinding>
<basicHttpBinding>
<binding name="SoapBinding" maxReceivedMessageSize="20000000" messageEncoding="Text" maxBufferSize="20000000" maxBufferPoolSize="20000000" closeTimeout="00:10:00" openTimeout="00:10:00" sendTimeout="00:10:00"/>
</basicHttpBinding>
<wsHttpBinding>
<binding name="SecureHttpBinding" messageEncoding="Mtom" closeTimeout="00:10:00" openTimeout="00:10:00" sendTimeout="00:10:00" maxBufferPoolSize="20000000" maxReceivedMessageSize="20000000">
<readerQuotas maxDepth="32" maxStringContentLength="20000000" maxArrayLength="20000000"/>
<security mode="None">
<transport clientCredentialType="Windows"/>
</security>
</binding>
</wsHttpBinding>
<customBinding>
<binding name="RawReceiveCapable">
<webMessageEncoding webContentTypeMapperType="WCFService.RawContentTypeMapper, WCFService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<httpTransport manualAddressing="true" maxReceivedMessageSize="524288000" transferMode="Buffered" /><!--transferMode="Streamed"-->
</binding>
</customBinding>
</bindings>
<services>
<service name="WCFService.Service" behaviorConfiguration="WCFService.ServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:5000"/>
</baseAddresses>
</host>
<!-- Service Endpoints -->
<!-- Unless fully qualified, address is relative to base address supplied above -->
<endpoint address="SOAP" binding="basicHttpBinding" bindingConfiguration="SoapBinding" contract="WCFService.IService1" name="SOAP_XML"/>
<endpoint address="" binding="webHttpBinding" bindingConfiguration="RestBinding" contract="WCFService.IService2" behaviorConfiguration="EndpointJSONBehavior"/>
<endpoint address="MTOM" binding="wsHttpBinding" bindingConfiguration="SecureHttpBinding" contract="WCFService.IService1" name="SOAP_MTOM"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WCFService.ServiceBehavior">
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="EndpointXMLBehavior">
<webHttp defaultBodyStyle="Wrapped" defaultOutgoingResponseFormat="Xml"/>
</behavior>
<behavior name="EndpointJSONBehavior">
<webHttp defaultBodyStyle="Wrapped" defaultOutgoingResponseFormat="Json"/>
</behavior>
<behavior name="EndpointRawBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
You can see here two ServiceContracts: IService1 and IService2.
To run methods defined in IService1 you need to use soap encapsulation.
Methods exposed by IService2 can be called RESTfully and easily without soap
encapsulation.
For your SendByPost() method to be RESTful you need to put it in IService2 and put the other method in a different ServiceContract (like IService1).

Related

Error 400 on WCF Rest Service Get Request

I recently developed a WCF rest service where its sole purpose is to handle Get requests and return data.
I have (mostly) successfully done this however I run into an issue when I want to send more than about 200-300 characters in the request. Whenever I try this I end up getting an error 400 on the webpage. Initially I thought I was running into some sort of character limitation but after reading many webpages and other questions I'm stumped as to what is truly failing.
I have tried to change bindings like maxReceivedMessageSize, maxStringContentLength, maxBufferSize, and maxBufferPoolSize hoping one of those was the culprit but alas I was wrong. I also tried to add tracing to see if I could read any errors in the log but when I make a long request nothing gets added to the log. I'm unable to use a different browser than edge currently so I made an app that uses HttpClient to communicate with my server and I am still getting an error 400 when I make a "long" request.
An example of a request that works perfectly fine is http://IP:PORT/POINTINFO/DBNAME/{"User":"testuser","Pc":"testpc","Data":["Point01","Point02"]}
An example of a request that will get an error 400 is http://IP:PORT/POINTINFO/DBNAME/{"User":"testuser","Pc":"testpc","Data":["U1R0011AS","U1R0012AS","U1R0012BS","U1R0041AS","U1R0041BS","U1R0041CS","U1R0044AS","U1R0044BS","U1R0046AS","U1R0046BS","U1R0046CS","U1R0046DS","U1R11A-AV","U1R12A-AV","U1R12B-AV","U1R41A-AV","U1R41B-AV","U1R41C-AV","U1R44A-AV","U1R44B-AV","U1R46A-AV","U1R46B-AV","U1R46C-AV","U1R46D-AV","U1R46E-AV","U1RCSCOLDEST","U1RCSPRESS","U1RXPCNTBE"]}
As you can see, the request is long (at least to type) but it shouldn't be hitting any limitations. This is also perfectly valid JSON so that can't be why it's dying. I've also tried to put a breakpoint on my GetPointList function to see if there's something wrong with my code, but that never even gets called. This is my only exposure to WCF rest services so I'm sure there's a beginners mistake I'm making but I can't see what I'm doing wrong. Any help will be appreciated.
Here is my main:
static void Main(string[] args)
{
WebServiceHost hostWeb = new WebServiceHost(typeof(ConsoleApp1.Service));
ServiceEndpoint ep = hostWeb.AddServiceEndpoint(typeof(ConsoleApp1.IService), new WebHttpBinding(), "");
ServiceDebugBehavior stp = hostWeb.Description.Behaviors.Find<ServiceDebugBehavior>();
stp.HttpHelpPageEnabled = false;
hostWeb.Open();
Console.WriteLine("Service Host started # " + DateTime.Now.ToString());
Console.Read();
}
This is the interface I have:
[ServiceContract]
public interface IService
{
[OperationContract]
[WebInvoke(Method = "GET",
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Wrapped,
UriTemplate = "POINTINFO/{database}/{pointlist}")]
[return: MessageParameter(Name = "Data")]
List<PointData> GetPointList(string pointlist, string database);
}
Here is my config that is scarred with prior attempts at fixing my issue:
<!--tracing for error detection-->
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging" switchValue="Information, ActivityTracing">
<listeners>
<add name="log"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="C:\Users\Administrator\Documents\messages.svclog" />
</listeners>
</source>
</sources>
</system.diagnostics>
<system.serviceModel>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>
<bindings>
<!--<wsHttpBinding>-->
<basicHttpBinding>
<binding name="basicHttp" openTimeout="00:10:00" closeTimeout="00:10:00" sendTimeout="00:10:00" receiveTimeout="01:00:00" maxReceivedMessageSize="2147483647" maxBufferSize="2147483647" maxBufferPoolSize="2147483647">
<security mode="None">
<!--<transport clientCredentialType="None" />-->
</security>
<readerQuotas maxStringContentLength="2147483647"/>
<!--<reliableSession enabled="true" />-->
</binding>
</basicHttpBinding>
<!--</wsHttpBinding>-->
<webHttpBinding>
<binding name="webHttp" openTimeout="00:10:00" closeTimeout="00:10:00" sendTimeout="00:10:00" receiveTimeout="01:00:00" maxReceivedMessageSize="2147483647" maxBufferSize="2147483647" maxBufferPoolSize="2147483647">
<security mode="None">
<!--<transport clientCredentialType="None" />-->
</security>
<readerQuotas maxStringContentLength="2147483647"/>
</binding>
</webHttpBinding>
</bindings>
<services>
<service name="ConsoleApp1.Service">
<endpoint address="rest" binding="webHttpBinding" bindingConfiguration="webHttp" contract="ConsoleApp1.IService" behaviorConfiguration="web"></endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost:12345" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="mexBehaviour">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<!-- rest api-->
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="web">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<diagnostics>
<messageLogging
logEntireMessage="true"
logMalformedMessages="true"
logMessagesAtServiceLevel="true"
logMessagesAtTransportLevel="true"
maxMessagesToLog="300"
maxSizeOfMessageToLog="20000"/>
</diagnostics>
</system.serviceModel>
I figured it out, thanks to this website: https://www.codeproject.com/Questions/847265/WCF-How-to-overcome-urllength-restriction
It turns out that the default character limit of each parameter in a REST url is 260. So you have to go in the registry and change the value of
UrlSegmentMaxLength in HEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\HTTP\Parameters to a REG_DWORD of whatever value you need.

wcf hanging on live server

I am working on a test project to implement wcf callbacks, my code works when I am debugging locally, the callbacks then actually works, but when ever I connect to the live server it would simply freeze at any methods.
I tried all the Conncurency modes, I tried setting the timeout but it all failed
public interface IWorkplaySupportServiceCallBack
{
[OperationContract(IsOneWay = true)]
void HandleData(byte[] buffer);
[OperationContract(IsOneWay = true)]
void SessionCreated();
}
[ServiceContract(CallbackContract = typeof(IWorkplaySupportServiceCallBack))]
public interface IWorkplaySupportService
{
[OperationContract(IsOneWay = true)]
void CreateSession(string sessionId);
}
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
public class WorkplaySupportService : IWorkplaySupportService
{
public void CreateSession(string sessionId)
{
var callback = OperationContext.Current.GetCallbackChannel<IWorkplaySupportServiceCallBack>();
callback.SessionCreated();
}
}
And here is my configuration
<!-- WCF START-->
<bindings>
<wsDualHttpBinding>
<binding name="basicHttpFor2MBMessage" maxBufferPoolSize="2097152"
maxReceivedMessageSize="2097152" messageEncoding="Mtom">
<readerQuotas maxArrayLength="2097000" />
<security mode="None">
<message clientCredentialType="None" />
</security>
</binding>
</wsDualHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="wsDualHttpBinding.SampleServiceBehavior"
name="Workflowms.Web.webservices.support.WorkplaySupportService">
<endpoint address="" binding="wsDualHttpBinding" bindingConfiguration="basicHttpFor2MBMessage"
contract="Workflowms.Web.webservices.support.IWorkplaySupportService">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="wsDualHttpBinding.SampleServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
By use of the Trace events I figured out why it is not responding, but I do not know how to fix this, the Client connecting is my Laptop and the Server is not on the same network (connect Via Internet), I added the wcf reference over the Internet but when I run it I don't get a connection, on the server Trace events I get the following exception
There was no endpoint listening at http://donald-pc/Temporary_Listen_Addresses/f9dbbcde-f968-48f1-bc48-e1e12dd13e32/6ca1305c-95e4-4248-a5f3-cbb594ea8a26 that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details.
I managed to get it to work without any problem by changing
<endpoint address="" binding="wsDualHttpBinding" bindingConfiguration="basicHttpFor2MBMessage" contract="Workflowms.Web.webservices.support.IWorkplaySupportService"> </endpoint>
To
<endpoint address="" binding="netHttpBinding" bindingConfiguration="netHttpBinding" contract="Workflowms.Web.webservices.support.IWorkplaySupportService">
And Adding
<netHttpBinding>
<binding name="netHttpBinding" maxBufferPoolSize="2097152"
maxReceivedMessageSize="2097152" messageEncoding="Mtom">
<readerQuotas maxArrayLength="2097000" />
<security mode="None">
<transport clientCredentialType="None"></transport>
</security>
</binding>
</netHttpBinding>
Now my callbacks work in Intranet and over Internet

IIS: WCF service with https and GET -> HTTP 400 Error

I am trying to create a WCF Service for https binding. The service was working with http before. I changed the binding (with certificate) and now I configure the web.config - but I always get error code "400 - Bad Request".
The web service is called with:
https://servername:444/FolderService.svc/FolderExists/1234
https://servername:444/FolderService.svc/Test
This is my service interface:
[ServiceContract]
public interface IFolderService
{
[OperationContract]
[WebGet(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "/FolderExists/{accountnumber}")]
bool FolderExists(string accountnumber);
[OperationContract]
[WebGet(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "/Test")]
string Test();
}
And this is my web.config:
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<services>
<service name="myService.FolderService">
<endpoint address=""
binding="basicHttpBinding"
bindingConfiguration="secureHttpBinding"
contract="myService.IFolderService"/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="secureHttpBinding">
<security mode="Transport">
<transport clientCredentialType="None"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpsGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
I tried so many different configuration without success. Does anyone have an idea (or a working example)?
Thanks in advance!
You are trying to access your methods in a RESTful manner via a URL. However, what you have wrong in your web.config is that you are using BasicHttpBinding which is for SOAP web services and not RESTful web services.
WebGet and WebInvoke are the necessary attributes to add to your operations as you have already done.
However, the correct binding for the endpoint is WebHttpBinding and the behavior you need to apply to the endpoint is the WebHttpBehavior.
Sample Abbreviated Configuration:
<service>
<endpoint behaviorConfiguration="webBehavior"
binding="webHttpBinding"
contract="myService.IFolderService"
bindingConfiguration="secureHttpBinding" />
</service>
<endpointBehaviors>
<behavior name="webBehavior">
<webHttp />
</behavior>
</endpointBehaviors>

Calling a service method with serialised objects

I have a WCF service with a post method. This takes a single entity.
[OperationContract, FaultContract(typeof(ServiceError))]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped]
Entity SaveEntity(Entity entity);
I have a file with an xml serialised representation of these objects. I need to be able to post this xml to the service directly without deserialising it at the client side (architecture issues, we don't have the references).
Is it possible to do this by constructing a request with something like the HttpClient?
var client = new HttpClient(HttpClient:);
client.PostAsync("http://localhost:55217/MyService.svc/SaveEntity", new HttpContent
{
Headers = new System.Net.Http.Headers.HttpContentHeaders
{
// can I put my serialised xml here?
}
}
Here's the config:
<system.serviceModel>
<services>
<service behaviorConfiguration="Default" name="MyService">
<endpoint address="" binding="webHttpBinding" contract="IMyService" behaviorConfiguration="webBehavior" bindingConfiguration="fullMessageSize" />
<endpoint address="ws" binding="wsHttpBinding" contract="IMyService" bindingConfiguration="fullMessageSize" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="webBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="Default">
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="fullMessageSize" maxReceivedMessageSize="104857600" />
</webHttpBinding>
<wsHttpBinding>
<binding name="fullMessageSize" maxReceivedMessageSize="104857600" />
</wsHttpBinding>
</bindings>
</system.serviceModel>
Why not create an intermediate service?
[OperationContract, FaultContract(typeof(ServiceError))]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped]
Entity SaveEntity(XElement entitySerialized)
{
var entity = Deserialize(entitySerialized);
var realService = new MyServiceClient();
return realService.SaveEntity(entity);
}
The intermediate service can have the references required for serialization.
You could consider creating a service-to-service endpoint for use between the intermediate service and the real service. That endpoint could use one of the faster bindings like netTcpBinding, that you might not want to use with your clients.

WCF Rest service not displaying methods on test client

I am writing a WCF Rest service to return a JSON message. I've been trying to use an example I found on the internet as a guide. Any time I fire up the test client, none of my methods are displayed. Navigating to the Uri while the service is running yields me a "page cannot be displayed" page. Not exactly too sure where to go from here. Any help would be appreciated.
Web Config:
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="webHttpBindingWithJasonP"
crossDomainScriptAccessEnabled="true" />
</webHttpBinding>
</bindings>
<services>
<service name="WcfRestLicense.LicenseService"
behaviorConfiguration="WebServiceBehavior">
<endpoint behaviorConfiguration="jsonBehavior"
binding="webHttpBinding"
bindingConfiguration="webHttpBindingWithJasonP"
contract="WcfRestLicense.ILicenseService" />
<endpoint contract="IMetadataExchange"
binding="mexHttpBinding"
address="mex" />
</service>
</services>
<!--<client />-->
<behaviors>
<endpointBehaviors>
<behavior name="jsonBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="WebServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add
scheme="http"
binding="webHttpBinding"
bindingConfiguration="webHttpBindingWithJasonP" />
</protocolMapping>
<serviceHostingEnvironment
aspNetCompatibilityEnabled="false"
multipleSiteBindingsEnabled="true" />
Service method:
public IQueryable<customer> GetCustomerById(string customerId)
{
int custId = Convert.ToInt32(customerId);
return _context.customers.Where(c => c.cust_id == custId);
}
Interface:
[ServiceContract]
public interface ILicenseService
{
[OperationContract]
[WebGet(UriTemplate = "customer/{customerId}/",
RequestFormat= WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare)]
IQueryable<customer> GetCustomerById(string customerId);
}
If by test client you're talking about the WcfTestClient, then it won't work with RESTful services; it's designed to work with SOAP based web services. You can test RESTful services in a browser by passing in the appropriate URI, something like this:
http://<location of your service>/service/1
Where the number 1 would be a customer ID. This is a rough example as a) I don't do a lot with RESTful services and b) I'm not sure what your actual address is.
As far as getting a 404 when you go to the Uri, it sounds like you're looking for the help page. You can enable that in your config file:
<bindings>
<webHttpBinding>
<binding name="webHttpBindingWithJasonP"
crossDomainScriptAccessEnabled="true"
enableHelp="true" />
</webHttpBinding>
</bindings>

Categories