I'm using ServiceModelEx WCF library from Juval Lowy's "Programming WCF Services". I'm trying to implement a Publish-Subscribe Service with publisher and subscriber. What I have done so far is the publisher and the discover-publish service.
Service Contract:
[ServiceContract]
interface IMyEvents
{
[OperationContract(IsOneWay=true)]
void OnEvent1(int number);
}
Discover - publish Service:
class MyPublishService : DiscoveryPublishService<IMyEvents>, IMyEvents
{
public void OnEvent1(int number)
{
FireEvent(number);
}
}
Discover - publish service host:
ServiceHost host = DiscoveryPublishService<IMyEvents>.
CreateHost<MyPublishService>();
host.Open();
// later..
host.Close();
Publisher:
IMyEvents proxy = DiscoveryPublishService<IMyEvents>.CreateChannel();
proxy.OnEvent1();
(proxy as ICommunicationObject).Close();
My question is how can I implement the subscriber? The book says to implement the service contract. That's simple.
class EventServiceSubscriber : IMyEvents
{
public void OnEvent1(int number)
{
// do something
}
}
but how can i host the subscriber? How subscriber can connect to the Publish-Subscribe service?
To get this to work I created a SubcriptionService as follows:
using ServiceLibrary.Contracts;
using ServiceModelEx;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;
namespace Subscriber
{
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any, IncludeExceptionDetailInFaults = DebugHelper.IncludeExceptionDetailInFaults, InstanceContextMode = InstanceContextMode.Single, UseSynchronizationContext = false)]
class SubscriptionService : DiscoveryPublishService<IMyEvents>, IMyEvents
{
public void OnEvent1()
{
Debug.WriteLine("SubscriptionService OnEvent1");
}
public void OnEvent2(int number)
{
Debug.WriteLine("SubscriptionService OnEvent2");
}
public void OnEvent3(int number, string text)
{
Debug.WriteLine("SubscriptionService OnEvent3");
}
}
}
Then I set up a host for this service as follows:
ServiceHost<SubscriptionService> _SubscriptionHost = DiscoveryPublishService<IMyEvents>.CreateHost<SubscriptionService>();
_SubscriptionHost.Open();
A basic working sample can be found in my Github account at the following url.
https://github.com/systemsymbiosis/PublishSubscribeWithDiscovery
There are a bunch of articles around that cover this subject. For starters, this one. You can host your subscriber in different ways like a console application or a ASP.NET application. Every application type has some kind of startup method so that'd be a good place to implement your subscription/publishing logic.
Related
I'm trying to implement a self-host WCF service in a console app. The first step is that I tried to implement a helloworld app first. However, It seems I couldn't reference the key word or class in System.ServiceModel. Can anyone tell me what I did wrong? Below is my code. Keyword "ServiceHost" cannot be found now.
using System;
using System.ServiceModel;
namespace selfHost
{
[ServiceContract]
public interface IHelloWorldService
{
[OperationContract]
string SayHello(string name);
}
public class HelloWorldService : IHelloWorldService
{
public string SayHello(string name)
{
return string.Format("Hello, {0}", name);
}
}
class Program
{
static void Main(string[] args)
{
Uri baseAddress = new Uri("http://localhost:8080/hello");
// Create the ServiceHost.
using (ServiceHost host = new ServiceHost(typeof(HelloWorldService), baseAddress))
{
// Enable metadata publishing.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(smb);
// Open the ServiceHost to start listening for messages. Since
// no endpoints are explicitly configured, the runtime will create
// one endpoint per base address for each service contract implemented
// by the service.
host.Open();
Console.WriteLine("The service is ready at {0}", baseAddress);
Console.WriteLine("Press <Enter> to stop the service.");
Console.ReadLine();
// Close the ServiceHost.
host.Close();
}
}
}
}
This is no longer supported in .Net Core. Looking at: https://github.com/dotnet/wcf/issues/2559
You can downgrade to .Net framework if you can or use another 3rd party library for servicehost.
Based on your code, I did a demo using the .NET framework, I didn't find any error, but if you're using the .NET Core, it doesn't work, have you tried re-referencing the System.ServiceModel ?
There is a link about System.ServiceModel References ,hope it use for you.
System.ServiceModel References
I am learning WCF and as part of the learning, i found out that the namespace for the contracts should match. I wrote a contract class (both client and host have their own copy) and made their namespace to not match but my code still works. I have provided code for my contract and host class and how client is calling the contract. could someone please advise where i am wrong?
Client Contract Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;
namespace GeoLib.Client.Contracts
{
[ServiceContract]
public interface IMessageContract
{
[OperationContract (Name = "ShowMessage")]
void ShowMsg(string message);
}
}
Host Contract Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;
namespace GeoLib.WindowsHost.Contracts
{
[ServiceContract]
public interface IMessageContract
{
[OperationContract]
void ShowMessage(string message);
}
}
Calling Code in Client:
private void btnMakeCall_Click(object sender, RoutedEventArgs e)
{
ChannelFactory<IMessageContract> factory = new ChannelFactory<IMessageContract>("");
IMessageContract proxy = factory.CreateChannel();
proxy.ShowMsg(txtMessage.Text);
factory.Close();
}
Namespace in ServiceContracts or DataContracts are usually used for versioning as they allow for two objects with the same name to exist in different namespaces.
It seems, however, that you haven't defined a Namespace for your Service.
Defining a namespace would be like:
[ServiceContract (Namespace="http://yourcompany.com/MyService/V1")]
public interface IMessageContract
{
...
}
If you later introduce a new version of your Service with new implementation and put it in a separate namespace such as:
[ServiceContract (Namespace="http://yourcompany.com/MyService/V2")]
public interface IMessageContract
{
...
}
then you can keep the two services separated and have old clients calling version1 and new clients calling version2
When I run my WPF that uses my WCF Service Library through visual studio I get a WCF Service Host startup at the same time with my service starting my WCF Service Library, however when I click on the exe for my WPF in the debug folder it doesn't startup is there anyway to make it start in code as the following code I have believed would work doesn't.
try
{
host = new ServiceHost(typeof(Service1), new Uri("http://" + System.Net.Dns.GetHostName() + ":8733/DatabaseTransferWcfServiceLibaryMethod/Service1/"));
host.Open();
}catch(AddressAlreadyInUseException)
{
}
I'm trying not to use service references.
I'm no expert at this, but perhaps you're missing the binding. Here is the simplest example I can create of hosting and consuming a WCF service in code (you'll need to add references to System, System.Runtime.Serializaton, and System.ServiceModel, but otherwise, this code is complete).
using System;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Create the host on a single class
using
( ServiceHost host
= new ServiceHost
( typeof(MyService)
, new Uri("http://localhost:1234/MyService/MyService")
)
){
// That single class could include multiple interfaces to
// different services, each must be added here
host.AddServiceEndpoint
( typeof(IMyService)
, new WSHttpBinding(SecurityMode.None)
// Each service can have it's own URL, but if blank use the
// default above
, ""
);
// Open the host so it can be consumed
host.Open();
// Consume the service (this cuold be in another executable)
using
( ChannelFactory<IMyService> channel
= new ChannelFactory<IMyService>
( new WSHttpBinding(SecurityMode.None)
, "http://localhost:1234/MyService/MyService"
)
){ IMyService myService = channel.CreateChannel();
Console.WriteLine(myService.GetValue());
}
// Clean up
host.Close();
}
}
}
[ServiceContract]
public interface IMyService
{ [OperationContract] int GetValue();
}
public class MyService : IMyService
{ public int GetValue()
{ return 5;
}
}
}
Okay the error was way away from where I thought it was the error was it was running on my user interface thread so needed to add.
[ServiceBehavior( UseSynchronizationContext = false)]
If anyone else has this problem i hope this helps.
I have a few questions regarding WCF:
- Can a program act as both client and server ?
- What's wrong with my code :
The service:
[ServiceContract]
public interface IShout
{
[OperationContract]
String Broadcast(String message);
}
The implementation:
public class eveShout : IShout
{
public String Broadcast(String message)
{
return message + " reply";
}
}
I start the service in the form contructor:
ServiceHost s = new ServiceHost(typeof(IShout));
s.AddServiceEndpoint(typeof(IShout), new BasicHttpBinding(), "http://localhost:9189");
s.Open();
The, When I click a button on another form, I want to send a message and get a reply back.
I use the following code:
ChannelFactory<IShout> channel = new ChannelFactory<IShout>(new BasicHttpBinding(), "http://localhost:9189");
IShout shout = channel.CreateChannel();
String reply = shout.Broadcast("Test");
Note: all the code is in the same namespace.
Note: I first start the "server" (open) then the app continues.
when i run the code, the server is created. I use netstat -a to see if the port is open. when I run the command, i get 9189 is in listening state. but the code stops at the command reply = shout("test"). and I get anexception that says
The request channel timeout while waiting for a reply after 00:00:59...
Yes, you can have an app act as both client and server.
I see a couple of things that may need correcting. First, try adding OperationContract.
[ServiceContract]
public interface IShout
{
[OperationContract]
String Broadcast(String message);
}
Then, take the type of the class, not the interface.
ServiceHost s = new ServiceHost(typeof(eveShout));
s.AddServiceEndpoint(typeof(IShout), new BasicHttpBinding(), "http://localhost:9189");
s.Open();
Make sure you you have permission to access the namespace (s.Open() should throw an exception if it does not).
net http add urlacl url=http://+:9189/ user=...
See if these suggestions help.
(oh yeh, and make Broadcast public in your class)
A quick example WindowsFormsApplication looks like this...
// form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
ChannelFactory<IShout> channel = new ChannelFactory<IShout>(new BasicHttpBinding(), "http://localhost:9189");
IShout shout = channel.CreateChannel();
String reply = shout.Broadcast("Test");
}
}
}
// and Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.ServiceModel;
namespace WindowsFormsApplication1
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
ServiceHost s = new ServiceHost(typeof(eveShout));
s.AddServiceEndpoint(typeof(IShout), new BasicHttpBinding(), "http://localhost:9189");
s.Open();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
public class eveShout : IShout
{
public String Broadcast(String message)
{
return message + " reply";
}
}
[ServiceContract]
public interface IShout
{
[OperationContract]
String Broadcast(String message);
}
}
See if you can get something as simple as this working. That will at least prove to you that it can be done and that the problem is somewhere else.
Enable WCF debugging.
The easiest way to do this is with WCF Service Configuration Editor. Open the utility and then browse to open your application's configuration file. From the diagnostics section simply click 'enable tracing'. The default tracing will be fine.
Once you run your application, the framework will dump a log file to a location specified in your configuration file. Double click to open it and read through the red events (these are the ones that have exceptions or unexpected outcome). It's very helpful and should help you identify where the problem is occurring.
I’m pretty new to programming, so bear with me if my question isn’t specific enough. Right now I’m trying to make a simple Client Logon to my server. So the server App knows which users are connected. When a client connects I want an event to fire on the server that update the userlist. But it doesn’t and I can’t figure out why. Hope you can help.
In the codes I have removed how the users should be displayed in the serverApp. Right now I just need the event to work.
In my Service Library:
INetworkService contract:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace NetworkLib
{
[ServiceContract]
public interface INetworkService
{
[OperationContract]
void Logon(UserInfo userInfo);
[OperationContract]
void Logout();
}
}
NetworkService Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace NetworkLib
{
public class NetworkService : INetworkService
{
public event EventHandler UserListChanged;
public void Logon(UserInfo userInfo)
{
OnUserListChanged();
}
public void Logout()
{
OnUserListChanged();
}
private void OnUserListChanged()
{
var handler = UserListChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
}
UserInfo Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace NetworkLib
{
[DataContract]
public class UserInfo
{
[DataMember]
public string Name;
}
}
In my ServerApp (WPF):
using System.ServiceModel;
using NetworkLib;
namespace ServerApp
{
public partial class MainWindow : Window
{
NetworkService networkService;
public MainWindow()
{
InitializeComponent();
ServiceHost host = new ServiceHost(typeof(NetworkService));
host.Open();
networkService = new NetworkService();
networkService.UserListChanged += networkService_UserListChanged;
}
private void networkService_UserListChanged(object sender, EventArgs e)
{
MessageBox.Show("It Works!");
}
}
}
In my ClientApp (WPF): (Have made a Service Reference to the Server)
namespace ClientApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
ServiceReference.NetworkServiceClient proxy = new ServiceReference.NetworkServiceClient();
ServiceReference.UserInfo userInfo = new ServiceReference.UserInfo();
userInfo.Name = "Test";
proxy.Logon(userInfo);
}
}
}
You subscribe to event of other NetworkService instance than ServiceHost instantiates. In your case every time you make request to server, new NetworkService instance is created. Place the following attribute above NetworkService class:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
Then subscribe to event:
var serviceInstance = (NetworkService)host.SingletonInstance;
serviceInstance.UserListChanged += networkService_UserListChanged;
When creating your ServiceHost, you should provide NetworkService instance instead of typeof(NetworkService)
ServiceHost host = new ServiceHost(networkService);
You need to initialize it first, of course.
I didnt look in great detail but overall your code looks ok. It is sometimes useful in this situation to run 2 instances of VisualStudio - one for server in debug and one for client in debug. If you put a break point in client button1_Click code in VS thats debugging client, and a break point in NetworkServices.Logout in VS thats debugging server you will be able to step from client to server code and see easily whats going wrong where.
Why do you need an event model here? Why not just handle your "event" directly in NetworkService.Logout(). Does pushing this off to an event and then having to wire that event (which as ilya.dofofeev correctly points out is not on the same object) provide any real benefit?