Host a SOAP service within a BackgroundService using .Net Core 5 - wcf

I'm new to .NET Core. I currently have a WCF host service that hosts another service that I'm trying to convert to .NET core. Using .NET 5, I created a worker service that handles the host background tasks and setup another service w/ an endpoint to handle incoming responses from another client. I'm having trouble using the EndpointAddress and ChannelFactory approach to create the endpoint and channel so the endpoint can be accessible via the outside world for response messages, but in doing so, I get the following error:
"No connection could be made because the target machine actively refused it. (localhost:8000)"
Maybe I'm going about this in the wrong way to host the service, not sure. Does anyone know?
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
return;
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<JLinkHostService>();
});
}
}
[ServiceContractAttribute]
public interface IResponseService
{
[OperationContractAttribute]
bool ResponseMessage(string sTermID, string sRespMsg);
}
public class ResponseService : IResponseService
{
public bool ResponseMessage(string sTermID, string sRespMsg)
{
string filePath = $"{c:\test"}\\{DateTime.Now.ToString("yyyy -MM-dd_HHmmssfff")}.txt";
System.IO.File.WriteAllText(filePath, $"{sTermID}\n\n{sRespMsg}");
return true;
}
}
public class HostService : BackgroundService
{
public override Task StartAsync(CancellationToken cancellationToken)
{
return base.StartAsync(cancellationToken);
}
public override Task StopAsync(CancellationToken cancellationToken)
{
return base.StopAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
ChannelFactory<IResponseService> factory = null;
try
{
Binding binding = new BasicHttpBinding();
EndpointAddress respAddress = new EndpointAddress("http://localhost:8000/response.svc");
factory = new ChannelFactory<IResponseService>(binding, respAddress);
IResponseService channel = factory.CreateChannel();
// Test service proxy
channel.ResponseMessage("test", "test");
while (!stoppingToken.IsCancellationRequested)
{
// Host background tasks happen here
await Task.Delay(Int32.Parse(GetCfgValue("AppSettings:pollingIntervalMilli")), stoppingToken);
}
}
catch (Exception ex)
{
Log.Fatal(ex.ToString());
}
finally
{
if(factory != null)
factory.Close();
}
}
}
}

Related

.NET core web api with queue processing

How to setup a .NET core web api that
accepts a string value,
puts into a queue
and return flag that message is accepted (regardless it is processed).
Also, a routine which keeps checking the queue, and process the messages one by one.
As per the requirement, the api is going to act as the receiver of messages which may get hits as much as hundreds of times in a minute, while the messages it receives should be processed one by one.
I am bit new to web apis, so wonder if such setup is good to have and if yes how to put together different components.
Thanks in advance..
Honestly, I don't think that it makes sense to receive and process messages in one process, so I would recommend to use external messaging system like RabbitMQ or Kafka or any other existing system of your preference, where you can put your messages and another process would consume it. It's quite big topic, you can start from this tutorial
If you still want to have it in one process it's also possible, you can create a background task queue, put there your messages and create background task which will consume them from that queue.
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(
Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
public async Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
}
Background task:
public class QueuedHostedService : BackgroundService
{
private readonly ILogger _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILoggerFactory loggerFactory)
{
TaskQueue = taskQueue;
_logger = loggerFactory.CreateLogger<QueuedHostedService>();
}
public IBackgroundTaskQueue TaskQueue { get; }
protected async override Task ExecuteAsync(
CancellationToken cancellationToken)
{
_logger.LogInformation("Queued Hosted Service is starting.");
while (!cancellationToken.IsCancellationRequested)
{
var workItem = await TaskQueue.DequeueAsync(cancellationToken);
try
{
await workItem(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
$"Error occurred executing {nameof(workItem)}.");
}
}
_logger.LogInformation("Queued Hosted Service is stopping.");
}
}
Registration:
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
}
Inject to controller:
public class ApiController
{
private IBackgroundTaskQueue queue;
public ApiController(IBackgroundTaskQueue queue)
{
this.queue = queue;
}
public IActionResult StartProcessing()
{
queue.QueueBackgroundWorkItem(async token =>
{
// put processing code here
}
return Ok();
}
}
You can modify BackgroundTaskQueue to fit your requirements, but I hope you understand the idea behind this.
Update for latecomers....
I used in Asp.net core 6, you can download sample here: https://learn.microsoft.com/en-us/dotnet/core/extensions/queue-service
Config Program
// and more...
#region Worker Services
builder.Host.ConfigureServices((context, services) =>
{
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(_ =>
{
if (!int.TryParse(context.Configuration["QueueCapacity"], out var queueCapacity))
{
queueCapacity = 100;
}
return new BackgroundTaskQueue(queueCapacity);
});
});
#endregion
#region App
// App config
var app = builder.Build();
// Monitor worker config
var monitorLoop = app.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
else
{
app.UseHttpsRedirection();
}
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
await app.RunAsync();
#endregion
In Controller
// and more..
private readonly IMailService _mailService;
private readonly IBackgroundTaskQueue _queue;
// and more..
public AuthenticateController(
IMailService mailService,
IBackgroundTaskQueue queue)
{
_mailService = mailService;
_queue = queue;
}
[HttpPost]
[Route("forgot-password")]
public async Task<IActionResult> ForgotPassword([FromBody] ForgotPasswordModel model)
{
// and more...
// Queue processing
await _queue.QueueBackgroundWorkItemAsync(async (token) =>
{
await _mailService.SendAsync(mailData, token);
});
return Ok();
}
Hope this help !

.NET Core Middleware is not invoked

Hello i have the following problem:
I am trying to send an object from a Console Application to a ASP NET Core Server via websockets.
On the Console application i serialize an object with NewtonSoft Jsonand i send it over the Socket to the .NET Core server.
On the Server i have created a tiny middleware.The Invoke method just does not get called.I keep two instances of visual studio open and nothing happens on the server side,until i close the connection on the client side and this error pops in on the server side - terminal :
1 Server
1.1 Program
class Program
{
static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)=>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseKestrel()
.UseSockets()
.UseUrls("http://localhost:8200")
.Build();
}
1.2 Startup class
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<WebSocketService>(x=>new WebSocketService());
}
public void Configure(IApplicationBuilder builder)
{
builder.UseWebSockets();
builder.UseMiddleware<SocketMiddleware>();
}
}
1.3 Middleware
class SocketMiddleware
{
WebSocketService handler;
public SocketMiddleware(WebSocketService _handler,RequestDelegate del)
{
this.handler = _handler;
this.next = del;
}
RequestDelegate next;
public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
await this.next(context);
return;
}
await this.handler.AddClientAsync(context.WebSockets);
}
}
Note: I will not post code of the service that the middleware uses since it is not relevant in this case.The invoke does not get called.
2 Client
The client just serializes a POCO class Player and sends it over the socket.It then waits for the server to respond with a list of Player.
static async Task Main(string[] args)
{
Player updatePlayer = new Player(100);
List<Player> players;
Memory<byte> buffer = new byte[2000];
ReadOnlyMemory<byte> toSend;
ReadOnlyMemory<byte> actualReceived;
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8200);
Random rand = new Random();
await socket.ConnectAsync(iPEndPoint);
using(NetworkStream stream =new NetworkStream(socket))
{
while (true)
{
updatePlayer.Lat = rand.Next(10, 100);
updatePlayer.Lng = rand.Next(10, 100);
string data=JsonConvert.SerializeObject(updatePlayer);
toSend = Encoding.UTF8.GetBytes(data);
await stream.WriteAsync(toSend);
int received=await stream.ReadAsync(buffer);
actualReceived = buffer.Slice(0, received);
string msg = Encoding.UTF8.GetString(actualReceived.ToArray());
players = JsonConvert.DeserializeObject<List<Player>>(msg);
if (Console.ReadKey().Key == ConsoleKey.Escape)
{
break;
}
}
}
}
P.S Could it be a problem that the server uses websockets and on the client i use the socket class?I figure as long as the destination address and port are okay..its just bytes.
In the Server.cs the Invoke method has a parameter of type WebSocketManager.We can accept a WebSocket connection but not a raw Socket.Therefore in the Client.cs i replaced the raw Socket with a WebSocket implementation.

Intercommunication between asp.net core web Api using RabbitMQ

I have two web api.
One publish a message when specific controller be call (work)
One subscribe to receive message. (not work)
Here is config for both web api :
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddRawRabbit();
var builder = new ContainerBuilder();
builder.RegisterRawRabbit("guest:guest#localhost:15672/");
var container = builder.Build();
}
In web api 1, here is action that publish a message :
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
EventMessage message = new EventMessage() ;
var client = BusClientFactory.CreateDefault();
message = new EventMessage() { id = new Guid(), createDate = DateTime.Now };
client.PublishAsync<EventMessage>(message);
Console.WriteLine($"message create {message.ToString()}");
return new string[] { "value1", "value2",};
}
}
And in web api 2, I dont know how to receive that message,
here is the way that i try but it not work
public class Listener
{
public static void Start()
{
var client = BusClientFactory.CreateDefault());
client.SubscribeAsync<EventMessage>(async (msg, context) => Console.WriteLine($"Recieved:{msg.createDate.ToString()}.");
}
}
public class Program
{
public static void Main(string[] args)
{
//not working
Listener.Start();
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
In this project, im using Rawrabbit, any library with the same scenario will be ok. Thanks
Update: i fix this code, it work

WCF application don't working in my home network

i create a small wcf apps when i am testing this apps in my machine then it is working but when i run the wcf server end on another pc at my home in same network then i am getting error
A remote side security requirement was not fulfilled during authentication.Try increasing the ProtectionLevel and/or ImpersonationLevel.
both the pc at my home in same work group and they can access each other. i try to find out the answer but people say this is firewall issue. so i disable firewall at both the pc but still getting the problem. here is my sample code. please guide me how can i run this wcf apps in two pc at my home network. thanks
Service end
namespace WCFSample
{
[ServiceContract]
public interface IService1
{
[OperationContract]
string MyName(string name);
}
}
namespace WCFSample
{
public class Service1 : IService1
{
public string MyName(string name)
{
return string.Format("My Name... {0}", name);
}
}
}
namespace ConsoleApplication1
{
class Program
{
static ServiceHost customerHost = null;
static void Main(string[] args)
{
try
{
HostCustomerService();
Console.WriteLine();
Console.WriteLine("Press any key to stop the services.");
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
customerHost.Close();
}
}
private static void HostCustomerService()
{
customerHost = new ServiceHost(typeof
(Service1));
ServiceEndpoint tcpEndpoint = customerHost.AddServiceEndpoint(
typeof(IService1), new NetTcpBinding(),
"net.tcp://192.168.1.103:9020/Service1");
customerHost.Open();
Console.WriteLine("{0} {1}", tcpEndpoint.Address, tcpEndpoint.Name);
Console.WriteLine();
}
}
}
client end
namespace Client1
{
class Program
{
static void Main(string[] args)
{
IService1 channel = null;
var endPoint = new EndpointAddress(
"net.tcp://192.168.1.103:9020/Service1");
channel = ChannelFactory<IService1>.CreateChannel(new NetTcpBinding(), endPoint);
Console.WriteLine("Enter Name");
string line = Console.ReadLine();
Console.WriteLine(channel.MyName(line));
Console.ReadKey();
}
}
}
I think by default, NetTcpBinding requires a secure channel.
When you create your binding (on client and server), instead of:
new NetTcpBinding()
Try:
new NetTcpBinding(SecurityMode.None)

WCF duplex TCP communication error

I have a sample service to test WCF net.tcp communication. It is very simple service and all it does is subscribing a client to the service and then calls callbackchannel to notify all connected clients about broadcasted message. The service is hosted inside IIS 7.5.
Here is service code and test client to test it.
[ServiceContract(CallbackContract = typeof(ISampleServiceCallBack), SessionMode = SessionMode.Required)]
public interface ISampleCuratioService
{
[OperationContract(IsOneWay = true)]
void SubcribeToService(string sub);
[OperationContract]
string GetData(int value);
[OperationContract(IsOneWay = true)]
void Broadcast(string message);
}
public interface ISampleServiceCallBack
{
[OperationContract(IsOneWay = true)]
void NotifyClient(string message);
}
Here is the service implementation:
[ServiceBehavior(Name = "CuratioCSMService", InstanceContextMode = InstanceContextMode.PerSession)]
public class Service1 : ISampleCuratioService
{
private static List<ISampleServiceCallBack> JoinedClien = new List<ISampleServiceCallBack>();
public void SubcribeToService(string sub)
{
var subscriber = OperationContext.Current.GetCallbackChannel<ISampleServiceCallBack>();
if (!JoinedClien.Contains(subscriber))
{
JoinedClien.Add(subscriber);
}
}
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
public void Broadcast(string message)
{
JoinedClien.ForEach(c => c.NotifyClient("message was received " + message));
}
}
I can not understand the behavior I get when running it. After the first client runs everything works fine but as I close and open test client app, it throws exception notifying that channel can not be used for communication as it is in fault state.
This is sample test client:
static void Main(string[] args)
{
var callneckclient = new ServiceClientProxy();
var client = new SampleCuratioServiceClient(new InstanceContext(callneckclient));
client.SubcribeToService("me");
Console.ReadLine();
for (int i = 0; i < 15; i++)
{
Console.WriteLine(client.GetData(5));
client.Broadcast("this is from client me");
}
client.Close();
Console.Read();
}
public class ServiceClientProxy : ISampleCuratioServiceCallback, IDisposable
{
public void NotifyClient(string message)
{
Console.WriteLine(message);
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
The situation gets even buggy when I run 5 clients. Non of those send or receive messages.
When a client calls SubcribeToService you add its operation context to a List called JoinedClien.
When you call Broadcast in your server, you call the method NotifyClient on all collected operation contexts for every client that has ever connected.
The problem is, that a disconnected client won't get removed from your JoinedClien list.
When you try to call an operation method on a disconnected operation context, you get the channel is in faulted state error.
To work around, you should subscribe to the Channel_Closed and Channel_Faulted events and also catch the CommunicationException when calling back into your clients and remove the operation context of the faulted clients:
public void Broadcast(string message)
{
// copy list of clients
List<OperationContext> clientsCopy = new List<OperationContext>();
lock(JoinedClien) {
clientsCopy.AddRange(JoinedClien);
}
// send message and collect faulted clients in separate list
List<OperationContext> clientsToRemove = new List<OperationContext>();
foreach (var c in JoinedClien)
{
try {
c.NotifyClient("message was received " + message));
}
catch (CommunicationException ex) {
clientsToRemove.Add(c);
}
}
foreach (var c in clientsToRemove)
{
lock(JoinedClien) {
if(JoinedClien.Contains(c))
JoinedClien.Remove(c);
}
}
}
When adding new clients you have to lock that operation, too:
var subscriber = OperationContext.Current.GetCallbackChannel<ISampleServiceCallBack>();
lock(JoinedClien)
{
if (!JoinedClien.Contains(subscriber))
{
JoinedClien.Add(subscriber);
}
}