Passing a class as parameter in RESTful WCF Service - wcf

In my RESTful WCF Serice I need to pass a class as a parameter for URITemplate.
I was able to pass a string or multiple strings as parameters.
But I have a lot of fields are there to pass to WCF Service.
So I have created a class and added all the fields as properties and then
I want to pass this class as one paramenter to the URITemplate.
When I am trying to pass class to the URITemplate I am getting error
"Path segment must have type string". Its not accepting class as a parameter.
Any idea how to pass class as a parameter.
Here is my code (inputData is class)
[OperationContract]
[WebGet(UriTemplate = "/InsertData/{param1}")]
string saveData(inputData param1);

You actually can pass a complex type (class) in a GET request, but you need to "teach" WCF how to use it, via a QueryStringConverter. However, you usually shouldn't do that, especially in a method which will change something in the service (GET should be for read-only operations).
The code below shows both passing a complex type in a GET (with a custom QueryStringConverter) and POST (the way it's supposed to be done).
public class StackOverflow_6783264
{
public class InputData
{
public string FirstName;
public string LastName;
}
[ServiceContract]
public interface ITest
{
[OperationContract]
[WebGet(UriTemplate = "/InsertData?param1={param1}")]
string saveDataGet(InputData param1);
[OperationContract]
[WebInvoke(UriTemplate = "/InsertData")]
string saveDataPost(InputData param1);
}
public class Service : ITest
{
public string saveDataGet(InputData param1)
{
return "Via GET: " + param1.FirstName + " " + param1.LastName;
}
public string saveDataPost(InputData param1)
{
return "Via POST: " + param1.FirstName + " " + param1.LastName;
}
}
public class MyQueryStringConverter : QueryStringConverter
{
public override bool CanConvert(Type type)
{
return (type == typeof(InputData)) || base.CanConvert(type);
}
public override object ConvertStringToValue(string parameter, Type parameterType)
{
if (parameterType == typeof(InputData))
{
string[] parts = parameter.Split(',');
return new InputData { FirstName = parts[0], LastName = parts[1] };
}
else
{
return base.ConvertStringToValue(parameter, parameterType);
}
}
}
public class MyWebHttpBehavior : WebHttpBehavior
{
protected override QueryStringConverter GetQueryStringConverter(OperationDescription operationDescription)
{
return new MyQueryStringConverter();
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITest), new WebHttpBinding(), "").Behaviors.Add(new MyWebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
WebClient client = new WebClient();
Console.WriteLine(client.DownloadString(baseAddress + "/InsertData?param1=John,Doe"));
client = new WebClient();
client.Headers[HttpRequestHeader.ContentType] = "application/json";
Console.WriteLine(client.UploadString(baseAddress + "/InsertData", "{\"FirstName\":\"John\",\"LastName\":\"Doe\"}"));
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}

Passing a class (data contract) is only possible with POST or PUT request (WebInvoke). GET request allows only simple types where each must be part of UriTemplate to be mapped to parameter in the method.

Related

Ninject Dependency Binding to WCF Services Without XML Configuration

I am trying to use the Ninject Dependency Injection to bind a callback method to WCF REST services runs in a kind of plugin module of a software system, which is NOT possible to use the SVC file or webconfig or app.config for any configuration.
The interface and implementation of the WCF services are defined as below:
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
string DoWork();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class Service1 : IService1
{
private IEventCallback EventCallback { get; set; }
public Service1(IEventCallback eventCallback)
{
EventCallback = eventCallback;
}
public string DoWork()
{
if (EventCallback != null)
{
EventCallback.Send("Testing Event ID");
}
return "Success";
}
}
where the IEventCallback and the corresponding implementation are defined as below:
public interface IEventCallback
{
void Send(string eventId);
}
public class EventCallback : IEventCallback
{
private Action<string> OnSendCustomEventCallBack { get; set; }
public EventCallback(Action<string> onSendCustomEventCallBack)
{
OnSendCustomEventCallBack = onSendCustomEventCallBack;
}
public void Send(string eventId)
{
if (OnSendCustomEventCallBack != null)
{
OnSendCustomEventCallBack(eventId);
}
}
}
The codes to create the REST Service are as below:
public AuthenticatedWebServiceHost(Type type, Uri url, string authenUsername, string authenPassword)
{
AuthenUsername = authenUsername;
AuthenPassword = authenPassword;
IDictionary<string, ContractDescription> desc;
InitializeDescription(type, new UriSchemeKeyedCollection());
base.CreateDescription(out desc);
var val = desc.Values.First();
var binding = new WebHttpBinding();
binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
Credentials.UserNameAuthentication.CustomUserNamePasswordValidator =
new CustomUserNamePasswordValidator(AuthenUsername, AuthenPassword);
AddServiceEndpoint(val.ContractType, binding, url);
}
And, the AuthenticatedWebServiceHost is called as below:
var eventCallback = new EventCallback(OnSendCustomEventCallBack); // where OnSendCustomEventCallBack is a defined method
// How to write codes to use Ninject to inject the callback into the Service?
// kernel.Bind<IEventCallback>().To<??>()
_webServiceHost = new AuthenticatedWebServiceHost(typeof(Service1), new Uri("http://localhost:9000/Events"),
"admin", "password");
_webServiceHost.Open();
Since, no XML configuration is allowed in my case, how to write codes to use Ninject to bind the callback to the WCF Services?
I eventually figured out the solution by referencing Is it possible to instantiate a WebServiceHost via an instance of the service type, without a parameterless constructor? without using NInject.

How to read WCF message headers in duplex callback?

In a normal WCF request/reply contract, you can read the message headers using something like:
OperationContract.Current.IncomingMessageHeaders
What I can't figure out is how to do this on the callback side of a duplex contract. Inside the callback implementation OperationContext.Current is null.
Edit 4/5/2013:
I'm using a custom binding based on net.tcp, but with a lot of customizations. For example, using protocol buffers message encoding rather than Xml. Also there is some custom security.
What binding are you using? In the SSCCE below the context is not null on the callback implementation.
public class StackOverflow_15769719
{
[ServiceContract(CallbackContract = typeof(ICallback))]
public interface ITest
{
[OperationContract]
string Hello(string text);
}
[ServiceContract]
public interface ICallback
{
[OperationContract(IsOneWay = true)]
void OnHello(string text);
}
public class Service : ITest
{
public string Hello(string text)
{
ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>();
ThreadPool.QueueUserWorkItem(delegate
{
callback.OnHello(text);
});
return text;
}
}
class MyCallback : ICallback
{
AutoResetEvent evt;
public MyCallback(AutoResetEvent evt)
{
this.evt = evt;
}
public void OnHello(string text)
{
Console.WriteLine("[callback] Headers: ");
foreach (var header in OperationContext.Current.IncomingMessageHeaders)
{
Console.WriteLine("[callback] {0}", header);
}
Console.WriteLine("[callback] OnHello({0})", text);
evt.Set();
}
}
public static void Test()
{
bool useTcp = false;
string baseAddress = useTcp ?
"net.tcp://" + Environment.MachineName + ":8000/Service" :
"http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
Binding binding = useTcp ?
(Binding)new NetTcpBinding(SecurityMode.None) :
new WSDualHttpBinding(WSDualHttpSecurityMode.None)
{
ClientBaseAddress = new Uri("http://" + Environment.MachineName + ":8888/Client")
};
host.AddServiceEndpoint(typeof(ITest), binding, "");
host.Open();
Console.WriteLine("Host opened");
AutoResetEvent evt = new AutoResetEvent(false);
MyCallback callback = new MyCallback(evt);
DuplexChannelFactory<ITest> factory = new DuplexChannelFactory<ITest>(
new InstanceContext(callback),
binding,
new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.Hello("foo bar"));
evt.WaitOne();
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}

When i want to retrieving the image from database ,It showing me a ApplicationName and WCFService Name and Class name why?

In WCF Service I am making
[ServiceContract]
public interface IService1
{
[OperationContract]
CompositeType QuestionRetrieve(String email);
}
[DataContract]
public class CompositeType
{
private String imageName;
public string ImageName
{
get { return imageName; }
set { imageName= value; }
}
In Service1.cs
public CompositeType QuestionRetrieve(String email)
{
context = new myEntities();
Profile aProfile= new Profile();
aProfile= (from c in context.Questions
where c.QuestionId == email
select c).First();
CompositeType aCompositeType = new CompositeType();
aCompositeType.imageName= aProfile.ImageName;
return aCompositeType;
}
In Other WindowForm I tried to retrieving the value but it show me WindowApplicationName, ServiceName and Class name "CompositeType"
In WindowForm we are doing on PageLoad:
Service aService = new Service();
Label1.Text = aService.QuestionRetrieve("gh#gmail.com").ToString();
It showing me
WindowFom.Service.CompositeType
Can you tell where is my error I am working with Entity.
You're executing the ToString() of the CompositeType object. Do this in stead:
Label1.Text = aService.QuestionRetrieve("gh#gmail.com").ImageName;

What's the best way to serialize an array based on an interface in WCF?

First the code:
[ServiceContract]
public interface IWorker
{
[OperationContract]
void Process(XmlElement data);
[OperationContract]
void Update(Rule rule);
}
[DataContract]
public class Rule
{
[OperationContract]
public string Expression { get; set; }
[OperationContract]
public List<IAction> Actions { get; set; }
}
public interface IAction
{
void Execute(XmlElement data);
}
A dispatcher encodes data as xml and sends it to an IWorker instance where each expression is evaluated. When an IWorker instance evaluates an expression as true, IAction.Execute is called and the xml/data is passed.
What's the best way to serialize Rule.Actions? I've started writing a custom serializer but I'd prefer to see if there is an easier way.
Thanks.
I dont think you can use interfaces in DataContracts (someone correct me if im wrong, but i assume thats like trying to use a generic too). What I do, is have a parent class, then add the KnownType attribute. For instance
[DataContract]
public class Action
{
//members and properties
}
[DataContract]
public class SomeOtherAction:Action
{
//more implimentation
}
[DataContract]
[KnownType(typeof(SomeOtherAction))]
public class Rule
{
[DataMember]
List<Action> Actions{get;set;}
}
Now you can stuff any object that inherits from the parent Action object in to the Actions list, and it will properly serialize all their respective class properties (as long as the object is listed as a knowntype).
*I used "Action" name as an example to relate to yours, obviously Action is a keyword in .NET
Serialization is the process of converting between an object data and bytes which can be transferred over the wire. Interfaces define behavior, so by default WCF can't serialize such data. If you have the exact same assemblies on the client and the server, however, you can use the NetDataContractSerializer, which will essentially serialize (and be able to serialize) all the type information for the objects being serialized, so it can be recreated at the other side.
The code below shows how to use the NetDataContractSerializer in a service for that (based on the main example for this, the post from Aaron Skonnard at http://www.pluralsight-training.net/community/blogs/aaron/archive/2006/04/21/22284.aspx)
public class StackOverflow_6932356
{
[ServiceContract]
public interface IWorker
{
[OperationContract]
void Process(XmlElement data);
[OperationContract]
void Update(Rule rule);
}
[DataContract]
public class Rule
{
[DataMember]
public string Expression { get; set; }
[DataMember]
public List<IAction> Actions { get; set; }
}
public interface IAction
{
void Execute(XmlElement data);
}
public class Service : IWorker
{
static List<IAction> AllActions = new List<IAction>();
public void Process(XmlElement data)
{
foreach (var action in AllActions)
{
action.Execute(data);
}
}
public void Update(Rule rule)
{
AllActions = rule.Actions;
}
}
public class Action1 : IAction
{
public void Execute(XmlElement data)
{
Console.WriteLine("Executing {0} for data: {1}", this.GetType().Name, data.OuterXml);
}
}
public class Action2 : IAction
{
public void Execute(XmlElement data)
{
Console.WriteLine("Executing {0} for data: {1}", this.GetType().Name, data.OuterXml);
}
}
class NetDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
{
public NetDataContractSerializerOperationBehavior(OperationDescription operationDescription)
: base(operationDescription) { }
public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
{
return new NetDataContractSerializer(name, ns);
}
public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
{
return new NetDataContractSerializer(name, ns);
}
}
static void ReplaceDCSOB(ServiceEndpoint endpoint)
{
foreach (var operation in endpoint.Contract.Operations)
{
for (int i = 0; i < operation.Behaviors.Count; i++)
{
if (operation.Behaviors[i] is DataContractSerializerOperationBehavior)
{
operation.Behaviors[i] = new NetDataContractSerializerOperationBehavior(operation);
break;
}
}
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(IWorker), new BasicHttpBinding(), "");
ReplaceDCSOB(endpoint);
host.Open();
Console.WriteLine("Host opened");
var factory = new ChannelFactory<IWorker>(new BasicHttpBinding(), new EndpointAddress(baseAddress));
ReplaceDCSOB(factory.Endpoint);
var proxy = factory.CreateChannel();
proxy.Update(new Rule
{
Expression = "Expr",
Actions = new List<IAction> { new Action1(), new Action2() }
});
XmlDocument doc = new XmlDocument();
doc.LoadXml("<root><foo>bar</foo></root>");
proxy.Process(doc.DocumentElement);
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}

WCF: How to define a DataContract class for an extreamly simple response? Need to deserialize Text of the root element

I would like to call Sql Azure's REST API to create a SQL Azure server. The method is documented here: http://msdn.microsoft.com/en-us/library/gg715274.aspx
I ran into problem. The response from this method is very simple:
<?xml version="1.0" encoding="utf-8"?><ServerName xmlns="http://schemas.microsoft.com/sqlazure/2010/12/">zpc0fbxur0</ServerName>
How do I define a DataContract class for this response?
If response were something like this:
<?xml version="1.0" encoding="utf-8"?>
<ServerName xmlns="http://schemas.microsoft.com/sqlazure/2010/12/"><Name>zpc0fbxur0</Name></ServerName>
the following class would work:
`
[DataContract(Namespace=SqlAzureConstants.ManagementNS, Name="ServerName")]
public class ServerName : IExtensibleDataObject
{
[DataMember()]
public string Name
{
get;
set;
}
public ExtensionDataObject ExtensionData
{
get;
set;
}
}
`
But I need to specify that property should be mapped to the text of the root element. Any ideas how to do this?
The DataContractSerializer as it's created by default cannot deserialize that XML - but if you use a constructor which sets the rootName and rootNamespace parameters, it can be done.
Another alternative is to use the XmlSerializer, where you can use it directly.
The code below shows both options, and also a WebChannelFactory implementation which uses the XmlSerializer type.
public class StackOverflow_6399085
{
[XmlRoot(ElementName = "ServerName", Namespace = "http://schemas.microsoft.com/sqlazure/2010/12/")]
public class ServerName
{
[XmlText]
public string Name { get; set; }
public override string ToString()
{
return string.Format("ServerName[Name={0}]", this.Name);
}
}
const string XML = "<?xml version=\"1.0\" encoding=\"utf-8\"?><ServerName xmlns=\"http://schemas.microsoft.com/sqlazure/2010/12/\">zpc0fbxur0</ServerName>";
static void RunWithXmlSerializer()
{
XmlSerializer xs = new XmlSerializer(typeof(ServerName));
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
ServerName obj = (ServerName)xs.Deserialize(ms);
Console.WriteLine("Using XML serializer: {0}", obj);
}
static void RunWithDataContractSerializer()
{
DataContractSerializer dcs = new DataContractSerializer(typeof(string), "ServerName", "http://schemas.microsoft.com/sqlazure/2010/12/");
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
string name = (string)dcs.ReadObject(ms);
Console.WriteLine("Using DataContractSerializer (different name): {0}", name);
}
[ServiceContract(Namespace = "http://schemas.microsoft.com/sqlazure/2010/12/")]
public class MockSqlAzureRestService
{
[WebGet]
public Stream GetServerName()
{
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
WebOperationContext.Current.OutgoingResponse.ContentType = "text/xml";
return ms;
}
}
[ServiceContract(Namespace = "http://schemas.microsoft.com/sqlazure/2010/12/")]
public interface IServerNameClient
{
[WebGet(BodyStyle = WebMessageBodyStyle.Bare)]
[XmlSerializerFormat]
ServerName GetServerName();
}
static void RunWithWCFRestClient()
{
// Setting up the mock service
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
WebServiceHost host = new WebServiceHost(typeof(MockSqlAzureRestService), new Uri(baseAddress));
host.Open();
WebChannelFactory<IServerNameClient> factory = new WebChannelFactory<IServerNameClient>(new Uri(baseAddress));
IServerNameClient proxy = factory.CreateChannel();
var name = proxy.GetServerName();
Console.WriteLine("Using WCF REST client: {0}", name);
}
public static void Test()
{
RunWithXmlSerializer();
RunWithDataContractSerializer();
RunWithWCFRestClient();
}
}