Windows service using WCF NetNamedPipe to communicate to client - wcf

The reason I'm writing this question is that I seem to be getting the following error when I'm trying to communicate between a windows service and a WPF app via a WCF service with a NetNamedPipe binding:
System.ServiceModel.EndpointNotFoundException: There was no endpoint
listening at net.pipe://localhost/Pipe_SendInfo
Now the gory details.
Ok, I have a windows service that is periodically executing code, I wanted to let a user know what is happening inside the service. So I read that I could accomplish this via NetNamedPipe WCF service. I created two test apps and successfully was able to send a message from one process to another. I then attempted to send messages from the windows service to a client app(on the same machine) and have so far failed miserably :(.
My windows service essentially does this(trying to send info):
ChannelFactory<SkipSyncLib.ISendInfo> pipeFactory =
new ChannelFactory<SkipSyncLib.ISendInfo>(
new NetNamedPipeBinding(),
new EndpointAddress("net.pipe://localhost/Pipe_SendInfo"));
pipeFactory.CreateChannel();
pipeFactory.SendInfo(info);
and the application that is supposed to receive the information does this when it starts up:
public void Start()
{
HostService = new ServiceHost(this, new Uri[] { new Uri("net.pipe://localhost") });
HostService.AddServiceEndpoint(typeof(ISendInfo), new NetNamedPipeBinding(), "Pipe_SendInfo");
try
{
HostService.Open();
}
catch (Exception exc)
{
//Error handling
}
}
The kicker is that while the windows service is failing miserably to find the endpoint I have another console app that is able to send info to the running client app successfully. So logic would tell me that it probably has something to do with users. But I can't figure it out.
Any ideas? Should I just drop named pipes and go with an http binding?
I just found out about named pipes earlier today so please be gentle if the answer is obvious.
Thanks

I figured out a way for it to work. There's probably a more elegant way to do this, but if the windows service and the client app are run as the same user then the communication channel works.

Should you use the same endpoint address?
HostService = new ServiceHost(this, new Uri[] { new Uri("net.pipe://localhost/Pipe_SendInfo") });
add /Pipe_SendInfo in Uri

Related

Unable to authenticate to http API when using public url from same machine (Kestrel/.NET7)

I have a .NET7 service which is running kestrel to host an http API (REST controller). The service uses windows authentication with a local windows account. There is an SSL certificate installed and the service is running on a Windows VM in Azure.
When I access the service using a browser or httpClient from my home PC everything works great. e.g. https://blah.myservice.com:21000/api/foo - this challenges me for credentials, I put them in and it returns data and the browser is happy with the SSL cert.
When I access the service in the same way from another Azure VM on the same network it also works as expected.
The issue is that when I access the public URL from the same machine that the service is running on I get a 401 unauthorized response from the service.
If I do https://localhost:21000/api/foo then it challenges me for credentials and returns data just fine, but the browser is not happy with the SSL cert because the domain in the browser does not match. That's not a mystery, obviously. But the service does work. It also works using the local network IP address of the machine.
I don't understand why I get a 401 when using the public URL - it's hitting the right service because I can see the request come in through the log files, it just won't authenticate after challenging me.
The reason this bothers me is that I have a number of services running on a few VMs - sometimes they take data from each other which means they have to effectively connect to localhost to call the REST controllers on another service. This will fail with a 401 if I use the public URL, but will also fail if I use "localhost" because the certificate does not validate. e.g. this code fails when going from one service to another on the same host (or just running it as a console app on the same server):
var credentials = new NetworkCredential("username", "password");
var httpClientHandler = new SocketsHttpHandler { Credentials = credentials };
HttpClient httpClient = new HttpClient(httpClientHandler, true);
try
{
string result = await httpClient.GetStringAsync("https://blah.myservice.com:21000/api/foo");
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
try
{
string result = await httpClient.GetStringAsync("https://localhost:21000/api/foo");
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Both of the above requests fail - I realise I can set the RemoteCertificateValidationCallback to always return true but that's a hack and I have a valid SSL cert so it makes no sense to do that.
Is anyone able to explain why this doesn't work and how to fix it?

SignalR firing OnConnectedAsync for users in a hub on another machine

Our Azure SignalR application is a bit of a hybrid, outgoing messages go into ActiveMQ then get picked up by a listener in a hosted service that receives them and sends them to the NotificationsHub (in the same API project). e.g. await _notificationsHub.Clients.All.SendAsync("PerformAction", action, payload);
Recently I tried to extend this so that a second application using a different authentication scheme (this one is external facing, the existing one is internal facing) could connect and receive some messages (and send others back). I did a POC and everything seemed fine.
public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub {
public override async Task OnConnectedAsync() {
await Groups.AddToGroupAsync(Context.ConnectionId, "foo");
await base.OnConnectedAsync();
await Clients.Caller.SendAsync("SetConnectionId", Context.ConnectionId);
}
}
Today I was debugging in Visual Studio the new application API and in the Hub's OnConnectedAsync method I kept receiving the "connected" events for users on a completely different server.
So my question is, does SignalR fire the "OnConnected" event for every hub connected to the same endpoint (for every instance of that hub)?
Even if that is the case wouldnt these two Hub classes be considered separate and not share their groups (see multiple hubs)?
Edit
For reference the project is targeting netcoreapp3.1, and using Microsoft.Azure.Signalr 1.8.1 in the original project and 1.16.1 in the new one.
It turns out that SignalR uses the name of the "Hub" class when connecting to the Azure SignalR service, i.e. 'wss://foo.service.signalr.net/server/?hub=notificationshub&cid={guid}'.
All hubs with the same name are treated as the same hub. I couldnt find that in the docs anywhere but it is implied in ApplicationName property of ServiceOptions which says "Gets applicationName, which will be used as a prefix to apply to each hub name"

WinRT - consume WCF service

I have a windows service, which acts as a propagator for data received from external source. This windows service hosts WCF ServiceHost with NetNamedPipeBinding(). ServiceContract also defines CallbackContract
There is also a client DLL component, which consumes the server and bubbles up parsed data as an event. Data is bubbled upon receiving callback from the server.
The code works in desktop app, however when I try to reference client DLL in WinRT app I get following error:
The pipe name could not be obtained for the pipe URI: Access is denied. (5, 0x5)
I presume this is because WinRT (to my knowledge) lacks support for named pipes.
How to go about consuming such service in WinRT? I can alter WCF side to any requirement, but it has to be hosted as windows service (it has non WinRT consumers). Communication will always occur within the same machine, polling is the last resort.
First you need to switch to basicHttpBinding, because net.namedpipe is not supported.
Actually supported are BasicHttpBinding, NetTcpBinding, NetHttpBinding
Secondly in WinRT there's a policy in place, which prevents you from accessing localhost over the network stack.
To overcome this security policy you need to add a LoopbackExempt for your app.
CheckNetIsolation.exe LoopbackExempt -s
See details on MSDN:
http://msdn.microsoft.com/en-us/library/windows/apps/Hh780593.aspx
For the duplex way either POLLING is an aption (only works, when the app is focused).
Or using push notifications: http://blogs.msdn.com/b/jimoneil/archive/2012/10/15/windows-8-notifications-push-notifications.aspx
Use the HttpClient class.. that's the only simple workaround, and works as well.
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/xml"));
httpClient.DefaultRequestHeaders.Add("SOAPAction", "http://ws.cdyne.com/WeatherWS/GetCityWeatherByZIP");
var soapXml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><GetCityWeatherByZIP xmlns=\"http://ws.cdyne.com/WeatherWS/\"><ZIP>23454</ZIP></GetCityWeatherByZIP></soap:Body></soap:Envelope>";
var response = httpClient.PostAsync("http://wsf.cdyne.com/WeatherWS/Weather.asmx", new StringContent(soapXml, Encoding.UTF8, "text/xml")).Result;
var content = response.Content.ReadAsStringAsync().Result;
Try this? I hope this is what you're looking for - https://quirkd.wordpress.com/2015/01/24/shorts-consuming-a-wcf-asmx-web-service-in-winrt/

How to host Web API in Windows Service

I have several resources that I'd like to expose using the WCF Web API. I've investigated the Web API using a Web host but our services all run as Windows Services in production so it's time for me to put the tests aside and verify that everything will work as we need it. I've looked as the sample app here: http://webapicontrib.codeplex.com/SourceControl/changeset/view/2d771a4d6f6f#Samples%2fSelfHosted%2fserver%2fProgram.cs but this does not work with the current version (preview 5) because the HttpConfigurableServiceHost class is not accessible from our code.
One of the most appealing aspects of the Web API is the simple startup using MapServiceRoute and the new WebApiConfiguration. I don't see, however, a way to define the base url and port for the services. Obviously, hosting the service in IIS eliminates this because we configure this information in IIS. How can I accomplish this when hosting in a Windows Service?
It's actually pretty simple. In a nutshell you need to instantiate HttpSelfHostServer and HttpSelfHostConfiguration and then call server.OpenAsync().
public void Start()
{
_server.OpenAsync();
}
public void Stop()
{
_server.CloseAsync().Wait();
_server.Dispose();
}
For an example on how to do this using Windows service project template and/or Topshelf library see my blog post: http://www.piotrwalat.net/hosting-web-api-in-windows-service/
The latest version just uses HttpServiceHost. http://webapicontrib.codeplex.com/SourceControl/changeset/view/ddc499585751#Samples%2fSelfHosted%2fserver%2fProgram.cs
Ping me on twitter if you continue to have problems.
This is the basic code using a console app. A Windows Service uses the same basic approach except you use the start and stop methods to start and stop the service and don't need to block.
static void Main(string[] args)
{
var host = new HttpServiceHost(typeof(PeopleService), "http://localhost:8080/people");
host.Open();
foreach (var ep in host.Description.Endpoints)
{
Console.WriteLine("Using {0} at {1}", ep.Binding.Name, ep.Address);
}
Console.ReadLine();
host.Close();
}
See this blog post.

WCF Authentication and Socket Aborted Exception

here's the setup for the project.
I have a WCF Service that is hosted on a net.tcp binding in buffered mode and ReliableSession enabled.
The binding is configured to use TransportWithMessageCredential security. The certificate is a self signed certificate that I am identifying using the Thumbprint.
The UserNameValidator is a custom validator which for testing, never fails (it wasn't failing before but I removed the uncertainty)
The service and client are on the same machine and they both have access to the certificate.
The problem:
I am receiving a Socket Aborted exception when trying to consume a Method from the service. Here is the code I use to open a connection to the service. MonitorServiceCallbacks only contains empty methods to fulfil the requirements of the Interface.
_instanceContext = new InstanceContext(new MonitorServiceCallbacks());
_serviceInterface = new MonitorCallbackServiceClient(_instanceContext);
_serviceInterface.ClientCredentials.UserName.UserName = Environment.MachineName;
_serviceInterface.ClientCredentials.UserName.Password = "myPassword";
_serviceInterface.Open();
This appears to work fine as the _serviceInterface.State variable is set to State.Opened and the custom validator is called and returns without exception.
However, if I try to call a method using the _serviceInterface proxy, no code that I can break into is run on the service and the tracelog reveals no errors apart from a SocketAborted exception which occurs 1 minute after receiving what appears to be the message for the method.
There is no InnerException.
I have tried to google the issue but the results tend to say "just disable security and it will work fine". Unfortunately, this cannot be done.
I am extremely confused by this issue and any help would be greatly appreciated.
Many thanks,
Ehrys
This was actually a serialisation error.
The object I was trying to send to the service inherited from the data contract. So I was trying to send a cast down to the data contract to the service.
WCF doesn't appear to allow this.
I would like to thank John Saunders for reminding me that not only the service can have tracing enabled. Enabling client side tracing would have saved me a lot of time.
I was attempting to do the following:
_serviceInterface.Register((MyDataContract)MyParentObject, aVariable, anotherOne);
What I needed to do:
MyDataContract tempContract = MyParentObject.CreateMyDataContract();
_serviceInterface.Register(tempContract, aVariable, anotherOne);
/* Note: MyParentObject.CreateMyDataContract() is my own method which creates an instance
of the MyDataContract and copies the relevant information to it */