[DataContract]
public abstract class BusMessage
{
[DataMember(Name = "encoding")]
public string Encoding { get; set; }
[DataMember(Name = "type")]
public virtual MessageType Type
{
get { return _type; }
private set { _type = value; }
}
}
[DataContract]
public class BusTextMessage : BusMessage
{
[DataMember(Name = "type")]
public override MessageType Type
{
get { return MessageType.Text; }
}
[DataMember(Name = "payload")]
public string Payload { get; set; }
}
[ServiceContract]
[ServiceKnownType("GetKnownTypes", typeof(Helper))]
public interface ICommunicationService
{
[WebInvoke(Method = "POST",
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate = "/SendMessage")]
string SendMessage(BusMessage jsonMessage);
}
}
When I send request with Postman chrome, if I don't add __type as "__type":"BusTextMessage:#TransportModels.Messages" the object won't be serialized properly because it doesn't know how to instantiate BusMessage class. I have already defined Type property which defines the type of message. Is there any possibility to override __type behaviour for example return proper implementation depending on Type property? I don't want anyone to put __type information to json manually so is there an option to edit json before deserialization and add __type property manually to json if it doesn't exist? For example I want to do something like this:
public void BeforeDeserialization(string json)
{
if(json doesnt include __type)
{
if(json property type(my property) is MessageType.Text)
add to json "__type":"BusTextMessage:#TransportModels.Messages"
///etc
}
}
I Found this methods but it doesn't seem to be usable:
[OnDeserializing()]
internal void OnDeserializingMethod(StreamingContext context)
{
}
I think you need to add the KnownType attribute to the BusMessage class.
[DataContract]
[KnownType(typeof(BusTextMessage)]
public class BusMessage
{
.
.
.
}
This is the quickest solution I discovered. I configure MessageInspector and handle AfterReceiveRequest. Then I check message format(XML,JSON). If it is XML(for example sent from any WCF Client written in C#, WCF is configured to send everything with XML's) then I accept that message because field __type will be automatically inserted by WCF mechanism. Otherwise I Check if it is JSON, for example sent from external client. If it doesn't contain property "__type" I check my property Type and generate proper __type value. For example if my Type is equal to Text I add __type property BusTextMessage:#TransportModels.Messages and insert it into JSON and then recreate the message. I couldn't find quicker and easier solution and it seems to be working. Handling AfterReceiveRequest I found at http://code.msdn.microsoft.com/windowsdesktop/WCF-REST-Message-Inspector-c4b6790b.
public class MessageTypeInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
RecreateMessage(ref request);
return null;
}
}
private void RecreateMessage(ref Message message)
{
WebContentFormat messageFormat = this.GetMessageContentFormat(message);
var ms = new MemoryStream();
XmlDictionaryWriter writer = null;
switch (messageFormat)
{
case WebContentFormat.Default:
case WebContentFormat.Xml:
writer = XmlDictionaryWriter.CreateTextWriter(ms);
break;
case WebContentFormat.Json:
writer = JsonReaderWriterFactory.CreateJsonWriter(ms);
break;
case WebContentFormat.Raw:
this.ReadRawBody(ref message);
break;
}
message.WriteMessage(writer);
writer.Flush();
string messageBody = Encoding.UTF8.GetString(ms.ToArray());
if (messageFormat == WebContentFormat.Json && !messageBody.Contains("__type"))
messageBody = AddTypeField(messageBody);
ms.Position = 0;
ms = new MemoryStream(Encoding.UTF8.GetBytes(messageBody));
XmlDictionaryReader reader = messageFormat == WebContentFormat.Json ?
JsonReaderWriterFactory.CreateJsonReader(ms, XmlDictionaryReaderQuotas.Max) :
XmlDictionaryReader.CreateTextReader(ms, XmlDictionaryReaderQuotas.Max);
Message newMessage = Message.CreateMessage(reader, int.MaxValue, message.Version);
newMessage.Properties.CopyProperties(message.Properties);
message = newMessage;
}
private WebContentFormat GetMessageContentFormat(Message message)
{
WebContentFormat format = WebContentFormat.Default;
if (message.Properties.ContainsKey(WebBodyFormatMessageProperty.Name))
{
WebBodyFormatMessageProperty bodyFormat;
bodyFormat = (WebBodyFormatMessageProperty)message.Properties[WebBodyFormatMessageProperty.Name];
format = bodyFormat.Format;
}
return format;
}
private string AddTypeField(string jsonReply)
{
var typeRegex = new Regex("\"type\":(?<number>[0-9]*)");
Match match = typeRegex.Match(jsonReply);
if (match.Success)
{
int number = Int32.Parse(match.Groups["number"].Value);
var type = (MessageType)number;
var nameFormat = string.Format("Bus{0}Message", type);
string format = string.Format("\"__type\":\"{0}:#TransportModels.Messages\"", nameFormat);
jsonReply = "{" + string.Format("{0},{1}", format, jsonReply.Substring(1));
return jsonReply;
}
else
{
throw new Exception("Wrong message type.");
}
}
Related
We have a console application using the Azure WebJob SDK. The WebJob relies on a WCF service using SOAP, which it accesses through a DLL we wrote that wraps the auto-generated WCF types in something a bit more friendly.
For logging purposes, we want to save the request and response XML bodies for requests that we make. These XML bodies would be saved in our database. But, because the WCF code lives in a low-level DLL, it has no concept of our database and can't save to it.
The DLL uses Microsoft's DI extensions to register types, and the WebJob calls into it like this:
class WebJobClass
{
IWCFWrapperClient _wcfWrapperClient;
public WebJobClass(IWCFWrapperClient wcfWrapperClient)
{
_wcfWrapperClient = wcfWrapperClient;
}
public async Task DoThing()
{
var callResult = await _wcfWrapperClient.CallWCFService();
}
}
IWCFWrapperClient looks like this:
class WCFWrapperClient : IWCFWrapperClient
{
IWCF _wcf; // auto-generated by VS, stored in Reference.cs
public async Task<object> CallWCFService()
{
return await _wcf.Call(); // another auto-generated method
}
}
I've implemented an IClientMessageInspector, and it works fine to get me the XML request/response, but I don't have a way to pass it back up to WCFWrapperClient.CallWCFService so that it can be returned to WebJobClass.DoThing(), who could then save it to the database.
The problem is multithreading. WebJobs, IIRC, will run multiple requests in parallel, calling into the DLL from multiple threads. This means we can't, say, share a static property LastRequestXmlBody since multiple threads could overwrite it. We also can't, say, give each call a Guid or something since there's no way to pass anything from IWCFWrapperClient.CallWCFService into the auto-generated IWCF.Call except what was auto-generated.
So, how can I return XML to WebJobClass.DoThing in a thread-safe way?
I was able to find a solution that uses ConcurrentDictionary<TKey, TValue>, but it's a bit ugly.
First, I amended the auto-generated classes in Reference.cs with a new property Guid InternalCorrelationId. Since the auto-generated classes are partial, this can be done in separate files that aren't changed when the client is regenerated.
public partial class AutoGeneratedWCFType
{
private Guid InternalCorrelationIdField;
[System.Runtime.Serialization.DataMember()]
public Guid InternalCorrelationId
{
get { return InternalCorrelationIdField; }
set { InternalCorrelationIdField = value; }
}
}
Next, I made all my request DTO types derive from a type named RequestBase, and all my response DTO types derive from a typed named ResponseBase, so I could handle them generically:
public abstract class RequestBase
{
public Guid InternalCorrelationId { get; set; }
}
public abstract class ResponseBase
{
public string RequestXml { get; set; }
public string ResponseXml { get; set; }
}
I then added a type RequestCorrelator that simply holds on to a ConcurrentDictionary<Guid, XmlRequestResponse>:
public sealed class RequestCorrelator : IRequestCorrelator
{
public ConcurrentDictionary<Guid, XmlRequestResponse> PendingCalls { get; }
public RequestCorrelator() => PendingCalls = new ConcurrentDictionary<Guid, XmlRequestResponse>();
}
public sealed class XmlRequestResponse
{
public string RequestXml { get; set; }
public string ResponseXml { get; set; }
}
RequestCorrelator is its own type for DI purposes - you may just be able to use a ConcurrentDictionary<TKey, TValue> directly.
Finally, we have the code that actually grabs the XML, a type implementing IClientMessageInspector:
public sealed class ClientMessageProvider : IClientMessageInspector
{
private readonly IRequestCorrelator _requestCorrelator;
public ClientMessageProvider(IRequestCorrelator requestCorrelator) =>
_requestCorrelator = requestCorrelator;
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
var requestXml = request.ToString();
var internalCorrelationId = GetInternalCorrelationId(requestXml);
if (internalCorrelationId != null)
{
if (_requestCorrelator.PendingCalls.TryGetValue(internalCorrelationId.Value,
out var requestResponse))
{
requestResponse.RequestXml = requestXml;
}
request = RemoveInternalCorrelationId(request);
}
return internalCorrelationId;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
// WCF can internally correlate a request between BeforeSendRequest and
// AfterReceiveReply. We reuse the same correlation ID we added to the
// XML as our correlation state.
var responseXml = reply.ToString();
var internalCorrelationId = (correlationState is Guid guid)
? guid
: default;
if (_requestCorrelator.PendingCalls.TryGetValue(internalCorrelationId,
out var requestResponse))
{
requestResponse.ResponseXml = responseXml;
}
}
private static Guid? GetInternalCorrelationId(string requestXml)
{
var document = XDocument.Parse(requestXml);
var internalCorrelationIdElement = /* You'll have to write this yourself;
every WCF XML request is different. */
return internalCorrelationIdElement != null
? Guid.Parse(internalCorrelationIdElement.Value)
: null;
}
private static Message RemoveInternalCorrelationId(Message oldMessage)
{
// https://stackoverflow.com/a/35639900/2709212
var buffer = oldMessage.CreateBufferedCopy(2 * 1024 * 1024);
var tempMessage = buffer.CreateMessage();
var dictionaryReader = tempMessage.GetReaderAtBodyContents();
var document = new XmlDocument();
document.Load(dictionaryReader);
dictionaryReader.Close();
var internalCorrelationIdNode = /* You'll also have to write this yourself. */
var parent = internalCorrelationIdNode.ParentNode;
parent.RemoveChild(internalCorrelationIdNode);
var memoryStream = new MemoryStream();
var xmlWriter = XmlWriter.Create(memoryStream);
document.Save(xmlWriter);
xmlWriter.Flush();
xmlWriter.Close();
memoryStream.Position = 0;
var xmlReader = XmlReader.Create(memoryStream);
var newMessage = Message.CreateMessage(oldMessage.Version, null, xmlReader);
newMessage.Headers.CopyHeadersFrom(oldMessage);
newMessage.Properties.CopyProperties(oldMessage.Properties);
return newMessage;
}
}
In short, this type:
Finds the correlation ID in the XML request.
Finds the XmlRequestResponse with the same correlation ID and adds the request to it.
Removes the correlation ID element so that the service doesn't get elements they didn't expect.
After receiving a reply, uses correlationState to find the XmlRequestResponse and write the response XML to it.
Now all we have to do is change IWCFWrapperClient:
private async Task<TDtoResult> ExecuteCallWithLogging<TDtoRequest,
TWcfRequest,
TWcfResponse,
TDtoResult>(TDtoRequest request,
Func<TDtoRequest, TWcfRequest> dtoToWcfConverter,
Func<TWcfRequest, Task<TWcfResponse>> wcfCall,
Func<TWcfResponse, TDtoResult> wcfToDtoConverter)
where TDtoRequest : CorrelationBase
where TDtoResult : WcfBase
{
request.InternalCorrelationId = Guid.NewGuid();
var xmlRequestResponse = new XmlRequestResponse();
_requestCorrelator.PendingCalls.GetOrAdd(request.InternalCorrelationId,
xmlRequestResponse);
var response = await contractingCall(dtoToWcfConverter(request));
_requestCorrelator.PendingCalls.TryRemove(request.InternalCorrelationId, out _);
return wcfToDtoConverter(response).WithRequestResponse(xmlRequestResponse);
}
public async Task<DoThingResponseDto> DoThing(DoThingRequestDto request) =>
await ExecuteCallWithLogging(request,
r => r.ToWcfModel(),
async d => await _wcf.Call(d),
d => d.ToDtoModel());
WithRequestResponse is implemented as follows:
public static T WithRequestResponse<T>(this T item, XmlRequestResponse requestResponse)
where T : ResponseBase
{
item.RequestXml = requestResponse?.RequestXml;
item.ResponseXml = requestResponse?.ResponseXml;
return item;
}
And there we go. WCF calls that return their XML in the response object rather than just something you can print to console or log to a file.
I have trouble with the Microsoft.Azure.ServiceBus.Message class. I want to create a Message object containing a payload object and then read this object back from it. In my current example I am not even sending the Message through a real Azure bus; I'm just creating it in memory and then trying to read it.
I cannot figure out what type I am supposed to read the message body as. I've tried byte[], string and the original object type. In all my cases I get an XmlException: "The input source is not correctly formatted".
Can someone please tell me what I am doing wrong, either when encoding or decoding the Message?
[DataContract]
public class Thingy
{
[DataMember]
public string Doodad { get; set; }
}
private static Message CreateMessage()
{
var entityMessage = new Thingy {Doodad = "foobar"};
var serializedMessageBody = JsonConvert.SerializeObject(entityMessage);
var contentType = typeof(Thingy).AssemblyQualifiedName;
var bytes = Encoding.UTF8.GetBytes(serializedMessageBody);
var message = new Message(bytes) {ContentType = contentType};
return message;
}
[Test]
public void ReadMessageBytes()
{
var message = CreateMessage();
var body = message.GetBody<byte[]>();
Console.WriteLine(body);
}
[Test]
public void ReadMessageString()
{
var message = CreateMessage();
var body = message.GetBody<string>();
Console.WriteLine(body);
}
[Test]
public void ReadMessageThingy()
{
var message = CreateMessage();
var body = message.GetBody<Thingy>();
Console.WriteLine(body);
}
I found out that this works:
[Test]
public void ReadMessageProperly()
{
var message = CreateMessage();
var body = message.Body;
var text = Encoding.UTF8.GetString(body);
var thingy = JsonConvert.DeserializeObject<Thingy>(text);
Assert.IsInstanceOf<Thingy>(thingy);
Assert.AreEqual("foobar", thingy.Doodad);
}
When creating a BrokeredMessage using custom DataContract type and using DataContractSerializer :
Record recordDataContract = new Record { Id = "DataContract Record" };
BrokeredMessage recordDataContractMessage = new BrokeredMessage(recordDataContract, new DataContractSerializer(typeof(Record)));
You can receive this message as:
Record r = receiveMessage.GetBody<Record>(new DataContractSerializer(typeof(Record)));
When creating a **BrokeredMessage** using custom **DataContract** type and using default serializer (DataContract + Binary Xml):
[DataContract(Namespace = "")]
class Record {
[DataMember]
public string Id { get; set; }
}
Record recordDefault = new Record { Id = "default Record" };
BrokeredMessage recordDefaultMessage = new BrokeredMessage(recordDefault);
You can receive this message as:
Record r = receiveMessage.GetBody<Record>();
For additional reference , you can check this blog. It has detailed example for different scenarios.
Hope it helps.
I am creating a webservice using asp.net 4.0.
I have created a asmx file and creating a User.cs Class. It has 8 Properties.
I have return a service with json format.
If the userlogin is true i need to return all the properties of user.cs, if it's fail i need to return only 2 property.
How to achieve it.
User login is true. It will return all
{"message":"valid user","BranchId":1,"BranchName":"My branch Name","Id":1,"Name":"admin","Password":"admin","RoleId":1,"Status":1}
User login is failed i need to retun only message and Status. but it will return all like as foloows
{"message":"username or password is invalid","BranchId":0,"BranchName":null,"Id":0,"Name":null,"Password":null,"RoleId":0,"Status":0}
I have google it and get the following Link. How to use it based on my login status condition.
If i have used [ScriptIgnore] in my property it will ignore property both case. I need to ignore property when login failed.
My properties like this
// Properties
public int BranchId
{
get { return _BranchId; }
set { if (_BranchId != value) { _BranchId = value; } }
}
public string BranchName
{
get { return _BranchName; }
set { _BranchName = value; }
}
private String _message;
public String message
{
get { return _message; }
set { _message = value; }
}
My webservice
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public void appLogin(String userName, String passWord)
{
Admin_AppUserBLL objusr = Admin_AppUserBLL.GetAdmin_AppUserBLL(userName);
string strResponse = "";
if (objusr != null)
{
if (objusr.Password == passWord)
{
objusr.message = "valid username";
strResponse = new JavaScriptSerializer().Serialize(objusr);
}
}
else
{
objusr = new Admin_AppUserBLL();
objusr.message = "username or password is invalid";
strResponse = new JavaScriptSerializer().Serialize(objusr);
}
Context.Response.Clear();
Context.Response.ContentType = "application/json";
Context.Response.AddHeader("content-length", strResponse.Length.ToString());
Context.Response.Flush();
Context.Response.Write(strResponse);
}
Add following attribute on your property and also make it nullable by using "?"
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "BranchId")]
public int? BranchId{ get; set; }
It will ignore if value will be null and also json does not contain these peoperties.
Add reference in Newtonsoft
using Newtonsoft.Json;
while serialize the object
string strResponse = JsonConvert.SerializeObject(objusr, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
it will avoid null value property
I have written an Interface for writing a very very simple Plugin. In fact it is just a class that is loaded at runtime out of a dll file and is stored as Property in another class. That class that stores the interface has to get serialized. As example this is my serialized object:
<?xml version="1.0" encoding="utf-8"?><MD5HashMapper xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.namespace.net" />
But now If i want to load that Object I get an Exception:
As example :
{"<MD5HashMapper xmlns='http://www.vrz.net/Vrz.Map'> was not expected."}
So does anyone has an idea how to solve that problem?
Code:
I have an Interface named IMap that is shared in a dll file to create Addins based on that interface:
public interface IMap
{
object Map(object input);
}
I also have different Mappers (you can pass an input through them and they modify the output). All Mappers are derived from:
[XmlInclude(typeof(ConstMapper))]
[XmlInclude(typeof(FuncMapper))]
[XmlInclude(typeof(IdentMapper))]
[XmlInclude(typeof(NullMapper))]
[XmlInclude(typeof(RefMapper))]
[XmlInclude(typeof(VarMapper))]
[XmlInclude(typeof(TableMapper))]
[XmlInclude(typeof(AddinMapper))]
public class MapperBase:ComponentBase,IMap
{ public virtual object Map(object input) {
throw new NotImplementedException("Diese Methode muss überschrieben werden");
}
public override string ToString() {
return ShortDisplayName;
}
}
Just forget ComponentBase. It is not important for this...
Now i also have a AddinMapper. The main function of that mapper is to cast create MapperBase Object out of the IMap object:
And that is exactly that class I want to seralize including the properties of the Mapper Property (type IMap).
public class AddinMapper : MapperBase
{
private static MapperBase[] _mappers;
const string addinDirectory = #"Addin\Mappers\";
//Mappers from *.dll files are loaded here:
[XmlIgnore]
public static MapperBase[] Mappers
{
get
{
if (_mappers == null)
{
List<MapperBase> maps = new List<MapperBase>();
foreach (string dll in Directory.GetFiles(addinDirectory, "*.dll"))
{
if (Path.GetFileName(dll) != "IMap.dll")
{
var absolutePath = Path.Combine(Environment.CurrentDirectory, dll);
Assembly asm = Assembly.LoadFile(absolutePath);
foreach (Type type in asm.GetTypes().ToList().Where(p => p.GetInterface("IMap") != null))
{
maps.Add(new AddinMapper((IMap)Activator.CreateInstance(type)));
}
}
}
Mappers = maps.ToArray();
}
return _mappers;
}
set
{
_mappers = value;
}
}
IMap _base;
public string MapperString { get; set; }
[XmlIgnore()]
public IMap Mapper
{
get
{
if (_base == null)
{
Type type = null;
foreach (MapperBase mapperBase in Mappers)
{
if (mapperBase is AddinMapper && ((AddinMapper)mapperBase).Mapper.GetType().FullName == _mapperName)
{
type = (mapperBase as AddinMapper).Mapper.GetType();
break;
}
}
if (type != null)
{
XmlSerializer serializer = new XmlSerializer(type);
using (StringReader reader = new StringReader(MapperString))
{
Mapper = (IMap)serializer.Deserialize(reader);
}
}
}
return _base;
}
private set
{
_base = value;
StoreMapperString();
}
}
string _mapperName;
[System.ComponentModel.Browsable(false)]
public string MapperName
{
get
{
return _mapperName;
}
set
{
_mapperName = value;
}
}
public AddinMapper(IMap baseInterface) : this()
{
Mapper = baseInterface;
_mapperName = baseInterface.GetType().FullName;
}
public AddinMapper()
{
}
public override object Map(object input)
{
return Mapper.Map(input);
}
public override string ToString()
{
return Mapper.ToString();
}
private void StoreMapperString()
{
MemoryStream memstream = new MemoryStream();
XmlStore.SaveObject(memstream, Mapper);
using (StreamReader reader = new StreamReader(memstream))
{
memstream.Position = 0;
MapperString = reader.ReadToEnd();
}
}
}
An example for such a addin would be:
public class ReplaceMapper : IMap
{
public string StringToReplace { get; set; }
public string StringToInsert { get; set; }
public object Map(object input)
{
if (input is string)
{
input = (input as string).Replace(StringToReplace, StringToInsert);
}
return input;
}
}
And the Problem is I want to save the Settings like StringToReplace,... as xml
I ve solved my problem:
I really don t know why but take a look at this article: http://www.calvinirwin.net/2011/02/10/xmlserialization-deserialize-causes-xmlns-was-not-expected/
(if link is dead later)
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = elementName;
xRoot.IsNullable = true;
XmlSerializer ser = new XmlSerializer(typeof(MyObject), xRoot);
XmlReader xRdr = XmlReader.Create(new StringReader(xmlData));
MyObject tvd = (MyObject)ser.Deserialize(xRdr);
Now the important thing: It does not matter if you don t get an excption on serialization. You have to add the XmlRootAttribute on both ways: Serialisation and Deserialization.
WCF offers two options for ResponseFormat attribute in WebGet annotation in ServiceContract.
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebGet(UriTemplate = "greet/{value}", BodyStyle = WebMessageBodyStyle.Bare)]
string GetData(string value);
[OperationContract]
[WebGet(UriTemplate = "foo", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json)]
string Foo();
The options for ResponseFormat are WebMessageFormat.Json and WebMessageFormat.Xml. Is it possible to write my own web message format? I would like that when client calls foo() method he gets raw string - without json or xml wrappers.
Try using
BodyStyle = WebMessageBodyStyle.Bare
Then return a System.IO.Stream from your function.
Here's some code I use to return an image out of a database, but accessible via a URL:
[OperationContract()]
[WebGet(UriTemplate = "Person/{personID}/Image", BodyStyle = WebMessageBodyStyle.Bare)]
System.IO.Stream GetImage(string personID);
Implementation:
public System.IO.Stream GetImage(string personID)
{
// parse personID, call DB
OutgoingWebResponseContext context = WebOperationContext.Current.OutgoingResponse;
if (image_not_found_in_DB)
{
context.StatusCode = System.Net.HttpStatusCode.Redirect;
context.Headers.Add(System.Net.HttpResponseHeader.Location, url_of_a_default_image);
return null;
}
// everything is OK, so send image
context.Headers.Add(System.Net.HttpResponseHeader.CacheControl, "public");
context.ContentType = "image/jpeg";
context.LastModified = date_image_was_stored_in_database;
context.StatusCode = System.Net.HttpStatusCode.OK;
return new System.IO.MemoryStream(buffer_containing_jpeg_image_from_database);
}
In your case, to return a raw string, set the ContentType to something like "text/plain" and return your data as a stream. At a guess, something like this:
return new System.IO.MemoryStream(ASCIIEncoding.Default.GetBytes(string_to_send));
WebGetAttribute is shipped by Microsoft, and I don't think you can extend WebMessageFormat. However you could probably extend the WebHttpBinding that uses WebGetAttribute. You could add your own attribute like
[WebGet2(UriTemplate = "foo", ResponseFormat = WebMessageFormat2.PlainText)]
string Foo();
In general, customizing the message layout in WCF is called custom message encoder/encoding. Microsoft provides an example: Custom Message Encoder: Compression Encoder. Also another common extension people do is to extend behavior to add custom error handling, so you could look for some example in that direction.
I implemented this attribute like this, maybe it will help someone in the future:
[AttributeUsage(AttributeTargets.Method)]
public class WebGetText : Attribute, IOperationBehavior
{
public void Validate(OperationDescription operationDescription)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Formatter = new Formatter(dispatchOperation.Formatter);
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
}
public class Formatter : IDispatchMessageFormatter
{
IDispatchMessageFormatter form;
public Formatter (IDispatchMessageFormatter form)
{
this.form = form;
}
public void DeserializeRequest(Message message, object[] parameters)
{
form.DeserializeRequest(message, parameters)
}
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
IEnumerable<object> cl = (IEnumerable<object>)result;
StringBuilder csvdata = new StringBuilder();
foreach (object userVariableClass in cl) {
Type type = userVariableClass.GetType();
PropertyInfo[] fields = type.GetProperties();
// Dim header As String = String.Join(";", fields.Select(Function(f) f.Name + ": " + f.GetValue(userVariableClass, Nothing).ToString()).ToArray())
// csvdata.AppendLine("")
// csvdata.AppendLine(header)
csvdata.AppendLine(ToCsvFields(";", fields, userVariableClass));
csvdata.AppendLine("");
csvdata.AppendLine("=====EOF=====");
csvdata.AppendLine("");
}
Message msg = WebOperationContext.Current.CreateTextResponse(csvdata.ToString());
return msg;
}
public static string ToCsvFields(string separator, PropertyInfo[] fields, object o)
{
StringBuilder linie = new StringBuilder();
foreach (PropertyInfo f in fields) {
if (linie.Length > 0) {
}
object x = f.GetValue(o, null);
if (x != null) {
linie.AppendLine(f.Name + ": " + x.ToString());
} else {
linie.AppendLine(f.Name + ": Nothing");
}
}
return linie.ToString();
}
}
There is one way how to achieve this if you're dealing with HTTP, it's not exactly nice, but I thought I could mention it.
You can set the return type of your method to void and just output your raw string directly into the response.
[OperationContract]
[WebGet(UriTemplate = "foo")]
void Foo()
{
HttpContext.Current.Response.Write("bar");
}