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.
Related
I have a WCF Windows Service that checks for MSMQ messages.
It picks the messages up ok but the ProcessMSMQMessage event does not seem to get called.
Any ideas why this is? Have I set ProcessMSMQMessage event correctly? Or am I missing something?
My code is below. Thanks.
WCF Service Class...
public partial class MyService : ServiceBase
{
private ServiceHost host;
public MyService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
string queueName = ConfigurationManager.AppSettings["ProcessMsgQueueName"];
if (!MessageQueue.Exists(queueName))
{
MessageQueue thisQueue = MessageQueue.Create(queueName, true);
thisQueue.SetPermissions("Everyone", MessageQueueAccessRights.ReceiveMessage);
}
try
{
Uri serviceUri = new Uri("msmq.formatname:DIRECT=OS:" + queueName);
// communicate to MSMQ how to transfer and deliver the messages
MsmqIntegrationBinding serviceBinding = new MsmqIntegrationBinding();
serviceBinding.Security.Transport.MsmqAuthenticationMode = MsmqAuthenticationMode.None;
serviceBinding.Security.Transport.MsmqProtectionLevel = System.Net.Security.ProtectionLevel.None;
serviceBinding.SerializationFormat = MsmqMessageSerializationFormat.Binary;
host = new ServiceHost(typeof(MyService.Service1)); // add watcher class name
host.AddServiceEndpoint(typeof(MyService.IService1), serviceBinding, serviceUri);
host.Open();
}
catch (Exception ex)
{
EventLog.WriteEntry("SERVICE" + ex.Message, EventLogEntryType.Error);
}
}
protected override void OnStop()
{
if (host != null)
host.Close();
}
}
IService1 Contract...
[ServiceContract(Namespace = "MyService")]
[ServiceKnownType(typeof(Events.Dashboard_Message))]
public interface IService1
{
[OperationContract(IsOneWay = true)]
void ProcessMSMQMessage(MsmqMessage<Events.Dashboard_Message> msg);
}
Service1 Class...
public class Service1 : IService1
{
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void ProcessMSMQMessage(MsmqMessage<Events.Dashboard_Message> msg)
{
string msgName = msg.GetType().Name;
// send to eventlog
EventLog.WriteEntry("MyService", msgName);
}
}
Got it working finally.
The issue was in IService1 contract. Needed to add Action = "*".
[OperationContract(IsOneWay = true, Action = "*")]
Just trying to get a small demo working voor wcf rest. I have a domain classlibrary, a console application and a wcf service project.
The interface [ServiceContract] is located in the Domain classlib:
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebGet]
CompositeType GetDataUsingDataContract(string id);
}
The implementation is located in the wcf service application (visual studio template):
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class Service1 : IService1
{
public CompositeType GetDataUsingDataContract(string id)
{
if (id != null)
{
return new CompositeType() { StringValue = id, BoolValue=true };
}
return null;
}
}
When I bind this in IIS properly it works just fine:
http://test123/Service1.svc/GetDataUsingDataContract?id=42
outputs:
{"d":{"__type":"CompositeType:#Domain","BoolValue":true,"StringValue":"42"}}
I then wrote the consumer:
static void Main(string[] args)
{
var factory = new ChannelFactory<IService1>(new WebHttpBinding(), new EndpointAddress("http://test123/Service1.svc"));
factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
var service = factory.CreateChannel();
CompositeType result = service.GetDataUsingDataContract("42");
}
This compiles and runs, but the properties of CompositeType result are null and false. Serialization didn't go properly. I used fiddler to verify that the http call is correct and it is exactly as I do manually with the browser.
When I change the parameter "42" to null, it also gives me a CompositeType object instead of null. Fiddler shows {"d":null} as expected.
I'm clueless!
Update
The code for CompositeType
[DataContract]
public class CompositeType
{
[DataMember]
public bool BoolValue { get; set; }
[DataMember]
public string StringValue { get; set; }
}
I have the following types:
public enum MyEnum
{
Value1,
Value2
}
[DataContract]
public class Configuration
{
[DataMember]
public MyEnum MyValue { get; set; }
[DataMember]
public Credentials CredentialValues { get; set; }
}
[DataContract, KnownType(typeof(CustomCredentials))]
public class Credentials
{
}
[DataContract]
public class CustomCredentials : Credentials
{
[DataMember]
public string Property1 { get; set; }
[DataMember]
public string Property2 { get; set; }
}
And on my service interface, I have a function that returns an instance of Configuration with its CredentialValues property set to a fully populated instance of CustomCredentials. I receive no errors from the client or the server, but while the data is being property serialized on the server and received by the client, the properties on CustomCredentials never have a value. What do I need to change here in order to have these properties properly deserialized on the client?
For reference, the connection between client and server is made with a DuplexChannelFactory over a NetTcpBinding using a data/service contract project that is shared by the client and service applications (the service is self-hosted), so there are no service proxy types that could need to be regenerated.
Added this code to the Contracts project along with your DataContracts.
[ServiceContract(Namespace = "http://schemas.platinumray.com/duplex", SessionMode = SessionMode.Required, CallbackContract = typeof(IService1Callback))]
public interface IService1
{
[OperationContract(IsOneWay = true)]
void GetData();
}
public interface IService1Callback
{
[OperationContract(IsOneWay = true)]
void SetData(Configuration config);
}
Created the service.
public class Service1 : IService1
{
public void GetData()
{
var x = new Configuration()
{
MyValue = MyEnum.Value1,
CredentialValues = new CustomCredentials { Property1 = "Something", Property2 = "Something else" }
};
OperationContext.Current.GetCallbackChannel<IService1Callback>().SetData(x);
}
}
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost( typeof(Service1), new Uri[] { new Uri("net.tcp://localhost:6789") }))
{
host.AddServiceEndpoint(typeof(IService1), new NetTcpBinding(), "Service1");
host.Open();
Console.ReadLine();
host.Close();
}
}
}
Created the client.
public class CallbackHandler : IService1Callback
{
public void SetData(Configuration config)
{
Console.WriteLine(config.CredentialValues.GetType().Name);
Console.WriteLine(((CustomCredentials)config.CredentialValues).Property1);
Console.WriteLine(((CustomCredentials)config.CredentialValues).Property2);
}
}
class Program
{
static void Main(string[] args)
{
// Setup the client
var callbacks = new CallbackHandler();
var endpoint = new EndpointAddress(new Uri("net.tcp://localhost:6789/Service1"));
using (var factory = new DuplexChannelFactory<IService1>(callbacks, new NetTcpBinding(), endpoint))
{
var client = factory.CreateChannel();
client.GetData();
Console.ReadLine();
factory.Close();
}
}
}
Outputs the following as expected:
CustomCredentials
Something
Something else
So this actually worked without modifying any of your data contracts... The same results if I revert to a twoway operation and just return Configuration directly without using the callback.
Also tried making Credentials abstract but could not replicate your problem.
Have I missed something?
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();
}
}
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.