WCF Dynamic Response Format - c#

How do I create a dynamic response with out using the query string?
I want to dynamically output the response format based on what the user specifics inside of the message body.
For example, if the user inputs "json","xml","soap", it will return the respective format. Thanks, in advance.
public interface IReg
{
[OperationContract]
[WebInvoke]
MemberBasic Login(string uniqueID, string password, string returnFormat);
}
[DataContract(Namespace = "", IsReference=false)]
[Serializable()]
public class MemberBasic
{
#region Properties
[DataMember]
public DateTime LastModified
{
get;
set;
}
}
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public sealed class RWS : IReg
{
public MemberBasic Login(string uniqueID, string password, string returnFormat)
{
MemberBasic result = new MemberBasic();
setReturnFormat(returnFormat);
return result;
}
}
private static void Init(string returnFormat)
{
var response = WebOperationContext.Current.OutgoingResponse;
response.Headers.Add("cache-Control", "no-cache");
response.Headers.Add("Last-Modified", string.Format("{0:r}", DateTime.Today));
switch (returnFormat)
{
case "xml":
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Xml;
WebOperationContext.Current.OutgoingRequest.Headers.Add(System.Net.HttpRequestHeader.ContentType, "application/json");
} break;
case "json":
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Json;
} break;
default:
{
throw new ArgumentException("Return Format unrecognized; cannot complete request.",
"returnFormat");
}
}
}

The simplest way to do what you are after is to create different endpoints with different bindings. You can have one for POX, SOAP and JSON. They can share contracts and implementations but WCF/configuration is responsible for managing the request/response formats.
It doesn't make a lot of sense to have SOAP specified as the response format since, in WCF it would mean the request would also have to be a SOAP request.

You cannot have in a same endpoint a SOAP and a JSON (or POX - "plain old XML") response. SOAP is a protocol which dictates how the request and the response need to be formatted - according to the SOAP envelope version, SOAP addressing headers (or absence of them), etc. If an endpoint "talks" SOAP, it cannot talk "non-SOAP".
For changing between JSON and XML (i.e., POX), you can specify as part of the operation the format which you want to use in the return in a single endpoint. The endpoint needs to be SOAP-less (i.e., its binding must have MessageVersion.None, such as the WebHttpBinding), and have a Web behavior applied to it (usually the WebHttpBehavior, or <webHttp/> if defined in config). Such endpoints are often known as WCF WebHttp endpoints or (rather misnamed) REST endpoints.
Your example is one way to do that for Web endpoints, although you're setting the content-type to application/json if you set the response format to XML, which will likely break your clients.

Related

Understanding WCF Multipart Soap service/endpoint with Attachment (SwA)

I'm trying to understand how to create/backwards engineer a Multipart WCF Soap endpoint that takes attachments as input parameters seperated by "MIME-delimiter".
I got an example of who requests to this kind of endpoint would look like but i don't understand how to create the service to receive the request.
So the example i got was something like this (FYI I've removed information for security reasons):
--MIME11111.11111
<SOAP-ENV:Envelope xmlns:SOAP-ENV="" xmlns:eb="" xmlns:xsi="" xsi:schemaLocation="">
<SOAP-ENV:Header>
<eb:MessageHeader SOAP-ENV:mustUnderstand="1" eb:version="2.0">
<eb:From>
<eb:PartyId eb:type="TYPE1">NUMBER</eb:PartyId>
</eb:From>
<eb:To>
<eb:PartyId eb:type="TYPE2">NUMBER</eb:PartyId>
</eb:To>
<eb:CPAId>ID</eb:CPAId>
<eb:Service eb:type="TYPE3">TEXT</eb:Service>
<eb:Action>TEXT</eb:Action>
<eb:MessageData>
<eb:MessageId>ID</eb:MessageId>
<eb:Timestamp>DATE</eb:Timestamp>
</eb:MessageData>
</eb:MessageHeader>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<eb:Manifest eb:version="2.0">
<eb:Reference xlink:href="cid:payload-1" xlink:role="aop:ROOT"/>
</eb:Manifest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
--MIME11111.11111
Content-ID: payload-1
<?xml version="1.0" encoding="UTF-8"?>
<aop:ROOT xsi:schemaLocation="" xmlns:aop="" xmlns:xsi="">
<aop:ELEMENT>
<aop:SUBELEMENT11>TEXT</aop:SUBELEMENT11>
<aop:SUBELEMENT12>
<aop:SUBELEMENT21>NUMBER</aop:SUBELEMENT21>
<aop:SUBELEMENT22>NUMBER</aop:SUBELEMENT22>
</aop:SUBELEMENT12>
</aop:ELEMENT>
</aop:ROOT>
--MIME11111.11111--
What i have done so far is:
Created a WCF Project in Visual Studio.
Created my Interface like this:
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebInvoke(Method = "Post", BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "/PostBusinessData")]
PostBusinessDataResponseContract PostBusinessData(Manifest data);
}
[MessageContract]
public class Manifest
{
[MessageHeader]
public Headers MessageHeader { get; set; }
[MessageBodyMember]
public Stream Reference { get; set; }
}
[MessageContract]
public class PostBusinessDataResponseContract
{
[MessageBodyMember]
public string PostBusinessDataResponse { get; set; }
}
Created my controller / service like this:
public class Service1 : IService1
{
public PostBusinessDataResponseContract PostBusinessData(Manifest data)
{
return new PostBusinessDataResponseContract() { PostBusinessDataResponse = "It Works"};
}
}
My Header class was created by "special pasting" the XML structure between "header" elements from the example above.
I have the class Model for the soap-envelope header pretty much set up except some of the attributes on the elements instead show up as sub elements in the generated request structure.
But the main thing is that i don't quite understand how to built the code for the in parameter in regards to the attachment. It looks to me that the Attachment in the multipart example comes in as a stream, but as a stream of what? A file, a xml string/text? the only thing the reference in the SOAP envelope contains is a so called "Content-ID". No file name, nothing else.
How do i set up my endpoint in my backed to be able to consume the type of request shown in the example above is really my question.
According to your description, I made a demo. After creating a WCF service, we can generate a proxy class to call the service by adding a service reference.
Right click References and select Add Service Reference.
Enter the address of the service in the address field, and click OK to generate the proxy class and configuration file for you to call the service.
ServiceReference1.Service1Client service1Client = new Service1Client();
string str = "Testing";
byte[] array = Encoding.ASCII.GetBytes(str);
MemoryStream stream = new MemoryStream(array);
Console.WriteLine(service1Client.PostBusinessData(stream));
Console.ReadLine();
The client-side can support the request type of the server through the generated proxy class.
This is the result.
UPDATE
WCF supports MTOM, which is the W3C standard superseded SwA.This is a link to MTOM related information:
https://learn.microsoft.com/en-us/dotnet/framework/wcf/samples/mtom-encoding
For more information about MTOM and SWA, please refer to the following link:
https://learn.microsoft.com/en-us/previous-versions/dotnet/articles/ms996462(v=msdn.10)?redirectedfrom=MSDN
In WCF, if you want to receive the XML message you give, I think you can use message inspectors to intercept the XML message and parse it.
public class ServerMessageLogger : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
string xml = ""+request;
// Parse the received XML here
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
string xml = ""+reply;
//Encapsulate the XML to send
}
}
For more information about message inspectors, please refer to the following link:
https://learn.microsoft.com/en-us/dotnet/framework/wcf/samples/message-inspectors?redirectedfrom=MSDN

Convert Web API backward compatible with WCF

Lets say I have a WCF service Service1.svc that contains GetData(value).
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
}
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
}
I also have a client that already auto-generate the proxy to consume this service, something like this:
using (var client = new ServiceReference1.Service1Client())
{
var result = client.GetData(1);
//Assert.AreEqual("You entered: 1", result);
}
Now, I removed that WCF service and replaced it with a new Web API service, something like this:
[RoutePrefix("Service1.svc")]
public class DataController : ApiController
{
[HttpGet]
[Route("GetData")]
public string GetDataOld(int value)
{
return string.Format("You entered: {0}", value);
}
}
So when I try to use the client Service1Client() it doesn't work anymore. I am pretty sure this is possible but what I have to do in order to accomplish this goal?
Update 05/23/2016
Since this is not possible, I decided to create a proxy so the client(s) can easily implement the new Restful Web API.
WebApi is designed to be REST endpoints, so the wcf way of var client = new ServiceReference1.Service1Client() is counter to the concept.
Rather you should try to access it through the url, for your example it'll be something like ~\Service1.svc\GetData\1. Also check your route configuration to be sure.
Your ServiceReference1.Service1Client() is directly related to WCF. You cannot reuse it for interacting with WebApi endpoints.
If you want a client that interacts with your WebApi endpoints, you will need to write a wrapper client yourself. RestSharp is my library of choice for such a thing. Example:
var client = new RestClient("http://localhost:8000");
int value = 12345;
var request = new RestRequest($"Service1.svc/GetData/{value}", Method.GET);
IRestResponse response = client.Execute(request);
var content = response.Content; // returns "You entered: 12345"
(untested)
This is possible, and we are currently doing something similar - we replaced our WCF/SOAP service endpoints with WebAPI/JSON endpoints, without changing the client's proxies.
To do this, you have to use the WebHttpBinding and add some more attributes to your service interface so that the proxy generates the right calls:
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebGet]
string GetData(int value);
[OperationContract]
[WebInvoke(Method="POST", RequestFormat = WebMessageFormat.Json)]
void PostData(MyDataType data);
}
This will make the proxy use HTTP calls, rather than SOAP calls, to access your service. As you can see, this works both for GET and POST requests, and automatically serializes your data as JSON (or XML, if you prefer).
There are more ways to customize it - like using the UriTemplate property to specify more complex HTTP routes (or have two methods, GetData and PostData, both map to the same route and differ only by verb, the RESTful way). And there are caveats as well - from what I could see, you can only pass strings to GET requests, not ints. But all in all, it works quite well.
Would we have chosen to go with WCF proxies mapped to WebAPI endpoints if we had started the project with WebAPI/REST? Probably not. But this allowed us to switch back-ends with relatively little front-end code changes.

Bad Request on JsvServiceClient.get but not JsvServiceClient.send

Running ServiceStack 4.0.44 I have the following on my test client:
return client.Send(new GetVendor { VenCode = vencode });
vs what I had
// return client.Get(new GetVendor { VenCode = vencode });
and then on the server I have/had
public class VendorsService : Service {
public object Any(GetVendor request) {
var vendor = Db.SingleWhere<Vendors>("VenCode", request.VenCode);
return vendor;
}
//public object Get(GetVendor request) {
// var vendor = Db.SingleWhere<Vendors>("VenCode", request.VenCode);
// return vendor;
//}
}
//[Route("/vendor/{VenCode}", "GET")]
[Route("/vendor/{VenCode}")]
public class GetVendor : IReturn<Vendors> {
public string VenCode { get; set; }
}
public class Vendors {
:
:
}
My question is why when I pass "B&T" for VenCode -- and I understand that IIS is interpreting the & as part of the URL -- why does the Send work and return Vendors -- but the Get blows up with Bad Request unless I put
<httpRuntime requestPathInvalidCharacters="" />
into my web.config
Bottom line what is the difference? How would I implement CRUD routines with all the html special characters without modifying the registry etc? Or do I need to urlEncode them somehow?
Using a Get() API sends the request using the ?QueryString which is what requestPathInvalidCharacters is validating against.
When you use Send() you're sending a JSV serialized Request DTO via a HTTP POST which isn't validated by requestPathInvalidCharacters.
It's unlikely there's any way to disable ASP.NET's default behavior other than using Web.config, note this validation happens in ASP.NET before the request reaches ServiceStack.

WCF Restful returning HttpResponseMessage wants to negotiate when setting content

I have a WCF Restful service and I would like the methods to return HttpResponseMessage because it seems structured rather than just returning the data or the exception or whatever else might make its way there.
I am assuming this is a correct, if not let me know, but my problem is what happens when I try to set HttpResponseMessage.Content. When I do this, the client in which I made the RESTful call request authentication.
Here is my code:
In the interface:
[WebGet(UriTemplate = "/GetDetailsForName?name={name}"
, ResponseFormat = WebMessageFormat.Json)]
HttpResponseMessage GetDetailsForName(string name);
In the class:
public HttpResponseMessage GetDetailsForName(string name)
{
HttpResponseMessage hrm = new HttpResponseMessage(HttpStatusCode.OK)
{
//If I leave this line out, I get the response, albeit empty
Content = new StringContent("Hi")
};
return hrm;
}
I wanted to try to use Request.CreateResponse but I can't seem to get to Request from my WCF Restful method. OperationContext.Current.RequestContext does not have CreateResponse.
Any pointers?
Unfortunately this will not work. The demonstrated code says:
Construct an HttpResponseMessage object, serialize it with a JSON serializer and pass the result over the wire.
The problem is HttpResponseMessage is disposable and not meant to be serialized, while StringContent cannot be serialized at all.
As to why you are redirected to an authentication form -
the service throws an exception when it cannot serialize StringContent
and returns a 400 HTTP status code which gets interpreted as an authentication issue.
I had a similar error, but not quite the same. I was trying to serialize a plain object and was getting an net::ERR_Conection_Reset message. The wcf method executed 7 times and never threw an exception.
I discovered I had to annotate the class I was returning so that my JSON serializer would understand how to serialize the class. Here is my wcf method:
[OperationContract]
[WebGet(
UriTemplate = "timeexpensemap",
ResponseFormat = WebMessageFormat.Json)]
public TimeexpenseMap timeexpensemap() {
string sql = "select * from blah"
DbDataReader reader = this.GetReader(sql);
TimeexpenseMap tem = null;
if (reader.Read()) {
tem = new TimeexpenseMap();
// Set properties on tem object here
}
return tem;
}
My original class which failed to serialize had no annotations:
public class TimeexpenseMap {
public long? clientid { get; set; }
public int? expenses { get; set; }
}
The annotated class serialized without issues:
[DataContract]
public class TimeexpenseMap {
[DataMember]
public long? clientid { get; set; }
[DataMember]
public int? expenses { get; set; }
}
If I am calling, for example a public string getDetails(int ID) and an error is thrown, this works ...
catch(Exception ex)
{
OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
response.StatusCode = System.Net.HttpStatusCode.OK; //this returns whatever Status Code you want to set here
response.StatusDescription = ex.Message.ToString(); //this can be accessed in the client
return "returnValue:-998,message:\"Database error retrieving customer details\""; //this is returned in the body and can be read from the stream
}

Create/Consume REST WebService using WCF and Universal Apps

I want to create an IIS-hosted webservice which I will consume using a universal windows store aoo (windows phone/windows 8.1/windows RT).
As I understand universal applications do not support proxy class generation and SOAP calls using "Add service reference" so I need to create a RESTful webservice and manually consume it in the universal application.
I've tried dozens of tutorials and approaches throughout the net but I never managed to actually POST data to the webservice.
I need to send objects of a custom class which is defined in a shared library to the webservice. I understand that I will need to serialize the Object and include it in the POST request, however no matter what I try I end up with different issues - e.g HTTP 400 Bad Request: The incoming message has an unexpected message format 'Raw'. The expected message formats for the operation are 'Xml'; 'Json'.
I've seen several approaches to manually set the content type header, however the methods I found are not available in a universal application.
Can someone provide information or an example which is fitting my scenario (POST-ing via universal app)?
update 1: For further clarification: I am aware how WCF works and I was already able to complete a basic GET request like described in this post. However I was unable to extend that to also work with POST requests.
Some code I've tried:
public async static void SendStartup(CustomClass customObject)
{
var httpClient = new HttpClient();
var serialized = JsonConvert.SerializeObject(customObject);
var response = await httpClient.PostAsync("http://localhost:49452/Metrics.svc/LogStartup", new StringContent(serialized));
string content = await response.Content.ReadAsStringAsync();
}
Web Service Interface:
[OperationContract]
[WebInvoke(UriTemplate = "LogStartup", Method="POST", BodyStyle=WebMessageBodyStyle.Wrapped)]
string LogStartup(CustomClass obj);
Implementation:
public void LogStartup(CustomClass obj)
{
// nothing
}
This for example failes at runtime with the error mentioned above
There are two problem with your code.
1) You have to send the Content-Type header while your are making a request
var content = new StringContent(serialized,Encoding.UTF8,"application/json");
2) You have to use BodyStyle = WebMessageBodyStyle.Bare
WebMessageBodyStyle.Bare can work with one parameter as in your example, but if you want to post more parameters then you have to use WebMessageBodyStyle.Wrapped but then, your object you post should be modified as
var serialized = JsonConvert.SerializeObject(new { obj = customObject });
Here is a working code you can test with self-hosted WCF service
async void TestRestService()
{
var ready = new TaskCompletionSource<object>();
Task.Factory.StartNew(() =>
{
var uri = new Uri("http://0.0.0.0:49452/Metrics.svc/");
var type = typeof(Metrics);
WebServiceHost host = new WebServiceHost(type, uri);
host.Open();
ready.SetResult(null);
},TaskCreationOptions.LongRunning);
await ready.Task;
var customObject = new CustomClass() { Name = "John", Id = 333 };
var serialized = JsonConvert.SerializeObject(new { obj = customObject });
var httpClient = new HttpClient();
var request = new StringContent(serialized,Encoding.UTF8,"application/json");
var response = await httpClient.PostAsync("http://localhost:49452/Metrics.svc/LogStartup", request);
string content = await response.Content.ReadAsStringAsync();
}
[ServiceContract]
public class Metrics
{
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped)]
public string LogStartup(CustomClass obj)
{
return obj.Name + "=>" + obj.Id;
}
}
public class CustomClass
{
public string Name { set; get; }
public int Id { set; get; }
}
PS: If you want to return a json response then you can use ResponseFormat=WebMessageFormat.Json. You should then change the WebInvoke attribute as
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped,ResponseFormat=WebMessageFormat.Json)]
BTW: You can still dynamically choose the returned content type(xml or json) by setting AutomaticFormatSelectionEnabled.
Have you seen this article?
How to use HttpClient to post JSON data
Basically it seems like you need to add more parameters to your StringContent() constructor like this:
new StringContent(serialized, System.Text.Encoding.UTF8, "application/json");
One thing you need to know about Windows Communication Foundation is the ABC's.
A : Address
B : Binding
C : Contract
So the theory is incredibly simple, though while your coding it, it is quite odd. A simple tutorial can be found here or here. Several other tutorials can be found at Code Project for this exact approach.
Understanding Polymorphism may be helpful for understanding Windows Communication Foundation as it relies heavily on it.
[ServiceContract]
public interface IContent
{
[OperationContract]
void DoSomething(SomeModel model);
}
So what you're doing here is defining your service, defining your method. As I mentioned above we've explicitly declared our contract but we haven't implemented our method. Also we intend to pass SomeModel which would be our Data Contract.
We will build our model:
[DataContract]
public class SomeModel
{
[DataMember]
public string Name { get; set; }
}
The model can be incredibly simple like above, or incredibly complex. It will depend on usage.
Now we would like to implement our method:
public class Content : IContent
{
public void DoSomething(SomeModel model)
{
// Implementation
}
}
Now on the client, you simply consume your service. Once you understand the basics and how it serializes and deserializes you can use it for REST. Which tutorials also exist for that.

Categories

Resources