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

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

Related

Is it possible to model bind a soap request with mvc?

A client has a service that sends out xml soap formatted requests that we need to receive via our .Net MVC4 project. A request would be in the format:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<ReceiveStatusUpdate xmlns="http://test.com">
<StatusUpdate>
<Reference>214563</Reference>
<ThirdPartyReference>YOUR-REFERENCE</ThirdPartyReference>
<Status>Pending</Status>
</StatusUpdate>
</ReceiveStatusUpdate>
</soap:Body>
</soap:Envelope>
I'm wondering what would be the best way to receive and parse this request?
Slightly hacky way of doing this but it worked for me and it's a one off request type that I need to handle. I basically pulled the request body out and parsed it with XDocument
public ActionResult Update()
{
var inputStream = Request.InputStream;
inputStream.Seek(0, SeekOrigin.Begin);
var request = new StreamReader(inputStream).ReadToEnd();
var soapRequest = XDocument.Parse(request);
...
}
The easiest way to achieve this is to use an old-fashioned asmx web service.
Exposing via Web API would require considerable work as it does not support SOAP binding out of the box.
You could use a WCF service, but they can be fiddly and time consuming to configure, which is the price to pay for their flexibility.
In short, if you only need to support SOAP bindings, use the tool that was made for that job - asmx web services.
Just add a new item to your MVC project of type Web Service (ASMX), example shown below (you'd obviously have your StatusUpdate class defined in a separate file).
/// <summary>
/// Summary description for StatusWebService
/// </summary>
[WebService(Namespace = "http://test.com")]
[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 StatusWebService : System.Web.Services.WebService
{
[WebMethod]
public void ReceiveStatusUpdate(StatusUpdate StatusUpdate)
{
//Do whatever needs to be done with the status update
}
}
public class StatusUpdate
{
public string Reference { get; set; }
public string ThirdPartyReference { get; set; }
public string Status { get; set; }
}
In my opinion - maybe someone can say more about it, would be to choose WebAPI. It's simple to use, just some code so it's light. You have lot's of tools for processing XML documents in .NET so this wont be any problem for you.
There is one more thing. In your XML there is error, the closing tag "ReceiveStatusUpdate" is misspelled.
This will be helpful in the beginning: http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api
Then you can use Fiddler to post those XML's to your WebAPI.
Might not be the best answer BUT here is what I'm doing now.
[HttpPost]
public IHttpActionResult HotelAvailRQ(HttpRequestMessage request)
{
// Parse the SOAP request to get the data payload
var xmlPayload = MyHelper.GetSoapXmlBody(request);
// Deserialize the data payload
var serializer = new XmlSerializer(typeof(OpenTravel.Data.CustomAttributes.OTA_HotelAvailRQ));
var hotelAvailRQ = (OpenTravel.Data.CustomAttributes.OTA_HotelAvailRQ)serializer.Deserialize(new StringReader(xmlPayload));
return Ok();
}
Helper class
public static class MyHelper
{
public static string GetSoapXmlBody(HttpRequestMessage request)
{
var xmlDocument = new XmlDocument();
xmlDocument.Load(request.Content.ReadAsStreamAsync().Result);
var xmlData = xmlDocument.DocumentElement;
var xmlBodyElement = xmlData.GetElementsByTagName("SOAP-ENV:Body");
var xmlBodyNode = xmlBodyElement.Item(0);
if (xmlBodyNode == null) throw new Exception("Function GetSoapXmlBody: Can't find SOAP-ENV:Body node");
var xmlPayload = xmlBodyNode.FirstChild;
if (xmlPayload == null) throw new Exception("Function GetSoapXmlBody: Can't find XML payload");
return xmlPayload.OuterXml;
}
}

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.

WCF Post Request content type text/xml

i have a problem with a post method..
Here is my interface
public interface Iinterface
{
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "inventory?")]
System.IO.Stream inventory(Stream data);
}
And the function..
public System.IO.Stream inventory(System.IO.Stream data)
{
//Do something
}
Well, if from the client sends with content-type text/plain or application/octet-stream works perfect, but the client cant change the content type, and his is text/xml, and i obtain an error..
The exception message is 'Incoming message for operation 'inventory' (contract
'Iinterface' with namespace 'http://xxxx.com/provider/2012/10') contains an
unrecognized http body format value 'Xml'. The expected body format value is 'Raw'.
This can be because a WebContentTypeMapper has not been configured on the binding.
Someone can help me?
Thanks.
As the error said - you need a WebContentTypeMapper to "tell" WCF to read the incoming XML messages as raw messages. You'd set the mapper in your binding. For example, the code below shows how you could define such a binding.
public class MyMapper : WebContentTypeMapper
{
public override WebContentFormat GetMessageFormatForContentType(string contentType)
{
return WebContentFormat.Raw; // always
}
}
static Binding GetBinding()
{
CustomBinding result = new CustomBinding(new WebHttpBinding());
WebMessageEncodingBindingElement webMEBE = result.Elements.Find<WebMessageEncodingBindingElement>();
webMEBE.ContentTypeMapper = new MyMapper();
return result;
}
The post at http://blogs.msdn.com/b/carlosfigueira/archive/2008/04/17/wcf-raw-programming-model-receiving-arbitrary-data.aspx has more information about using content type mappers.

WCF Dynamic Response Format

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.

Invoking an ASP.NET web service method via an http request

I want to invoke an ASP.NET web service via an http POST request using C# (i.e. I don't want to use the SoapHttpClientProtocol object generated by running wsdl.exe).
As far as I can tell, the process involves:
creating an HttpWebRequest object which points to the url/method of the web service, with the method;
Creating a SOAP xml envelope;
Serialising any parameters I want to pass to the web method using an XmlSerializer;
Making the request, and parsing the response.
I would like to do this without having to copy and use generated code.
(1) seems pretty straightforward;
(2) I don't know if the envelope here is standard, or how it should change depending on the webservice method I am calling. I guess I might need to add custom soap headers if required by the service?
(3) What is the process of doing this? I assume that I need to do something like this:
MyClass myObj;
XmlSerializer ser = new XmlSerializer(myObj.GetType());
TextWriter writer = new StringWriter();
ser.Serialize(writer, myObj);
string soapXml = writer.ToString();
writer.Close();
Also, I guess I should add the soapXml to the soap:Body element
(4) I believe I should extract and deserialize the contents of the soap:Body element as well. Is it OK to use the reverse of the process in (3)?
Thanks,
K.
I don't know why I am doing this but here's an example of invoking a web service manually. Please promise to never use this in a production code.
Suppose you had the following SOAP service:
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service1 : System.Web.Services.WebService
{
[WebMethod]
public string HelloWorld(Foo foo)
{
return "Hello World";
}
}
You can invoke it manually like this:
class Program
{
static void Main(string[] args)
{
using (WebClient client = new WebClient())
{
client.Headers.Add("SOAPAction", "\"http://tempuri.org/HelloWorld\"");
client.Headers.Add("Content-Type", "text/xml; charset=utf-8");
var payload = #"<?xml version=""1.0"" encoding=""utf-8""?><soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema""><soap:Body><HelloWorld xmlns=""http://tempuri.org/""><foo><Id>1</Id><Name>Bar</Name></foo></HelloWorld></soap:Body></soap:Envelope>";
var data = Encoding.UTF8.GetBytes(payload);
var result = client.UploadData("http://localhost:1475/Service1.asmx", data);
Console.WriteLine(Encoding.Default.GetString(result));
}
}
}

Categories

Resources