I have a asp.net core web api. As of now I'm using ILogger to log the messages. But ILogger doesn't have Fatal loglevel in it. There is Critical level, but our team requires Fatal word instead of Critical word.Is there any way I can tweak the work which gets printed to logs?
If not, I want to replace ILogger with log4Net which has Fatal level in it.So this is what I have done , but somehow it is not working.
I have multi layer architecture : WebApplication1, WebApplication1.Helper . All these are different projects with in a solution.
In WebApplication1:
I have added Microsoft.Extensions.Logging.Log4Net.AspNetCore reference.
In startup.cs
public void ConfigureServices(IServiceCollection apiServices)
{
var provider = apiServices.BuildServiceProvider();
var factory = new LoggerFactory()
.AddConsole().AddLog4Net().AddApplicationInsights(provider, LogLevel.Information);
apiServices.AddSingleton(factory);
apiServices.AddLogging();
apiServices.AddMvc();
apiServices.AddOptions();
}
HomeController.cs
[Route("api/[controller]")]
[ApiController]
public class HomeController : Controller
{
private readonly ILog4NetHelper _logHelper = new Log4NetHelper();
[HttpGet]
public virtual IActionResult GetData()
{
try
{
_logHelper.Log4NetMessage("Info", "Start GetData");
return new OkObjectResult("Your in Home Controller");
}
catch (Exception ex)
{
_logHelper.Log4NetMessage("Error", "Exception in GetData" + ex.Message);
throw;
}
}
}
WebApplication1.Helper project
And in WebApplication1.Helper project , I have added a interface ILog4NetHelper and class which implements this interface Log4NetHelper. Also I have added log4Net config file.
public class Log4NetHelper : ILog4NetHelper
{
readonly ILog _log =log4net.LogManager.GetLogger(typeof(Log4NetHelper));
public void Log4NetMessage(string type,string message)
{
string logMessage = message;
switch (type)
{
case "Info":
_log.Info(logMessage);
break;
case "Error":
_log.Error(logMessage);
break;
case "Fatal":
_log.Fatal(logMessage);
break;
default:
_log.Info(logMessage);
break;
}
}
}
When I host this application and run this, it is giving me a 500 internal server error. The error message is this :
InvalidOperationException: Unable to resolve service for type
'WebApplication1.Helper.Log4NetHelper' while attempting to activate
'WebApplication1.Helper.Log4NetHelper'.
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type
serviceType, Type implementationType, CallSiteChain callSiteChain,
ParameterInfo[] parameters, bool throwIfCallSiteNotFound)
How can I resolve this?
ASP.Net Core built-in logging was Microsoft's stab at doing logging the Microsoft, dependency-injected way. It follows the basic principles and tenets of the Log4Net approach (which has been standardized across .Net, Java, and Javascript, among others). So, the two approaches are not entirely at odds with one another.
However, in this particular case, the implementation appears to actually conflict with the intent of both approaches to logging.
Log4Net separates out the two acts of recording and writing log output. The first is done via the ILog interface. The second is done via one of the Appenders.
Similarly, the ASP.net Core API uses an ILogger and one or more Providers to emit log messages.
As I am more comfortable with log4net, and also don't see much of a point in having loggers added via dependency injection in EVERY CLASS, I used log4net's approach of LogManager.GetLogger(typeof(MyClass)) rather than doing it via Microsoft DI. My appenders also run through log4net. Thus, my implementation focused on translating the Microsoft logging outputs into the log4net format, which appears to be the what your team would like but the opposite of what you are doing here. My approach was based on this article. The code I used is below.
Implementation Notes:
I set up a custom appender via log4net which writes my logs out to a logging database (commonly-used databases for this are loki and/or elasticsearch).
In the Configure() method on startup.cs, you'll need to have the following line (note that I instantiate the customAppender in the ConfigureServices and then add it to the DI, but you wouldn't have to do it this way):
loggerFactory.AddLog4Net(_serverConfig.LoggingSettings, customAppender);
It is also necessary to have the following in ConfigureServices() (not sure why, but it seems to ensure that the regular .net core logging kicks in).
services.AddLogging(config => {
config.AddDebug();
config.AddConsole();
});
Log4NetLogger.cs
/// <summary>
/// Writes ASP.net core logs out to the log4net system.
/// </summary>
public class Log4NetLogger : ILogger
{
private readonly ILog _logger;
public Log4NetLogger(string name)
{
_logger = LogManager.GetLogger(typeof(Log4NetProvider).Assembly, name);
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
switch (logLevel) {
case LogLevel.Critical:
return _logger.IsFatalEnabled;
case LogLevel.Debug:
case LogLevel.Trace:
return _logger.IsDebugEnabled;
case LogLevel.Error:
return _logger.IsErrorEnabled;
case LogLevel.Information:
return _logger.IsInfoEnabled;
case LogLevel.Warning:
return _logger.IsWarnEnabled;
default:
throw new ArgumentOutOfRangeException(nameof(logLevel));
}
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
if (!this.IsEnabled(logLevel)) {
return;
}
if (formatter == null) {
throw new ArgumentNullException(nameof(formatter));
}
string message = null;
if (null != formatter) {
message = formatter(state, exception);
}
if (!string.IsNullOrEmpty(message) || exception != null) {
switch (logLevel) {
case LogLevel.Critical:
_logger.Fatal(message);
break;
case LogLevel.Debug:
case LogLevel.Trace:
_logger.Debug(message);
break;
case LogLevel.Error:
_logger.Error(message);
break;
case LogLevel.Information:
_logger.Info(message);
break;
case LogLevel.Warning:
_logger.Warn(message);
break;
default:
_logger.Warn($"Encountered unknown log level {logLevel}, writing out as Info.");
_logger.Info(message, exception);
break;
}
}
}
Log4NetProvider.cs
/// <summary>
/// Returns new log4net loggers when called by the ASP.net core logging framework
/// </summary>
public class Log4NetProvider : ILoggerProvider
{
private readonly LoggingConfig _config;
private readonly ConcurrentDictionary<string, Log4NetLogger> _loggers =
new ConcurrentDictionary<string, Log4NetLogger>();
private readonly ILoggerRepository _repository =
log4net.LogManager.CreateRepository(typeof(Log4NetProvider).Assembly, typeof(log4net.Repository.Hierarchy.Hierarchy));
public Log4NetProvider(LoggingConfig config, MyCustomAppender otherAppender)
{
_config = config;
BasicConfigurator.Configure(_repository, new ConsoleAppender(), otherAppender);
LogManager.GetLogger(this.GetType()).Info("Logging initialized.");
}
public ILogger CreateLogger(string categoryName)
{
return _loggers.GetOrAdd(categoryName, this.CreateLoggerImplementation(categoryName));
}
public void Dispose()
{
_loggers.Clear();
}
private Log4NetLogger CreateLoggerImplementation(string name)
{
return new Log4NetLogger(name);
}
}
Log4NetExtensions.cs
/// <summary>
/// A helper class for initializing Log4Net in the .NET core project.
/// </summary>
public static class Log4netExtensions
{
public static ILoggerFactory AddLog4Net(this ILoggerFactory factory, LoggingConfig config, MyCustomAppender appender)
{
factory.AddProvider(new Log4NetProvider(config, appender));
return factory;
}
}
I ran into multiple issues with Log4Net few years ago - i don't remember exactly what, since then i switched to my own implementation for log, it is not complicated to write your own. my implementation is copied below.
When i had to change the log class, it was nightmare since i had to replace all its usage with the new implementation.
There are 2 benefits of custom class.
A. It will match your application's requirement.
B. It acts as a wrapper for any underlying logging framework that you may use. So now even if i have to change logging in future, i just have to change the internal implementation of this custom class.
Since writing this wrapper, i have changed from log4net to Microsofts log extensions and now 100 % my own implementation.
using System;
using System.Text;
using System.IO;
namespace BF
{
//This is a custom publisher class use to publish the exception details
//into log file
public class LogPublisher
{
private static object _lock;
static LogPublisher()
{
_lock = new object();
}
//Constructor
public LogPublisher()
{
}
//Method to publish the exception details into log file
public static void Debug(string message)
{
if (ClientConfigHandler.Config.IsDebugMode())
{
Exception eMsg = new Exception(message);
Publish(eMsg, "#DEBUG");
}
}
public static void DebugBackgroundAction(string message)
{
if (ClientConfigHandler.Config.IsDebugMode())
{
Exception eMsg = new Exception(message);
Publish(eMsg, "#DEBUG #BG");
}
}
public static void BackgroundAction(string message)
{
Exception eMsg = new Exception(message);
Publish(eMsg, "#BG");
}
public static void Publish(string message)
{
Exception eMsg = new Exception(message);
Publish(eMsg, "");
}
public static void Publish(Exception fException)
{
Publish(fException, "");
}
public static void Publish(Exception fException, string prefix)
{
if (fException == null) return;
// Load Config values if they are provided.
string m_LogName = ResourceConfig.LogFileName;
// Create StringBuilder to maintain publishing information.
StringBuilder strInfo = new StringBuilder();
// Record required content of the AdditionalInfo collection.
strInfo.AppendFormat("{0}**T {1} {2} ", Environment.NewLine, CommonConversions.CurrentTime.ToString(CommonConversions.DATE_TIME_FORMAT_LOG), prefix);
// Append the exception message and stack trace
strInfo.Append(BuildExceptionLog(fException, false));
try
{
lock (_lock)
{
FileStream fs = File.Open(m_LogName, FileMode.Append, FileAccess.Write);
StreamWriter sw = new StreamWriter(fs);
sw.Write(strInfo.ToString());
sw.Close();
fs.Close();
}
}
catch
{
//ignore log error
}
}
private static string BuildExceptionLog(Exception fException, bool isInnerExp)
{
StringBuilder strInfo = new StringBuilder();
if (fException != null)
{
string msgType;
if (isInnerExp)
{
msgType = "#IN-ERR";
}
else if (fException.StackTrace == null)
{
msgType = "#INF";
}
else
{
msgType = "#ERR";
}
strInfo.AppendFormat("{0}: {1}", msgType, fException.Message.ToString());
if (fException.StackTrace != null)
{
strInfo.AppendFormat("{0}#TRACE: {1}", Environment.NewLine, fException.StackTrace);
}
if (fException.InnerException != null)
{
strInfo.AppendFormat("{0}{1}", Environment.NewLine, BuildExceptionLog(fException.InnerException, true));
}
}
return strInfo.ToString();
}
}
}
The Microsoft.Extensions.Logging system doesn't contain Fatal level as an accepted LogLevel.
But, if you are interested in treat the Critical messages as log4net's Fatal level, starting at v.2.2.5, there is a property on Microsoft.Extensions.Logging.Log4Net.AspNetCore nuget package that allow you to decide if the Critical Level is managed as Fatal or not.
Please, review the original issue to check why the implementation was done as it was.
I see one issue in your code. Your class Log4NetHelper requires in constructor an instance of Log4NetHelper. So it can't create Log4NetHelper. Why you passing Log4NetHelper in constructor, it smells. You should provide default constructor, or constructor with parameters which are registered in DI service.
Please try add parameterless constructor and check if it works, if not check exception and/or error message.
I have two actors, lets call them ActorA and ActorB. Both actors reside in their own separate process as a Topshelf based Windows Service.
Basically they look like this.
public class ActorA : ReceiveActor
{
public ActorA()
{
this.Receive<ActorIdentity>(this.IdentifyMessageReceived);
}
private bool IdentifyMessageReceived(ActorIdentity obj)
{
return true;
}
}
public class ActorB : ReceiveActor
{
private readonly Cluster Cluster = Akka.Cluster.Cluster.Get(Context.System);
public ActorB()
{
this.Receive<ActorIdentity>(this.IdentifyMessageReceived);
this.ReceiveAsync<ClusterEvent.MemberUp>(this.MemberUpReceived);
}
protected override void PreStart()
{
this.Cluster.Subscribe(this.Self, ClusterEvent.InitialStateAsEvents, new[]
{
typeof(ClusterEvent.IMemberEvent),
typeof(ClusterEvent.UnreachableMember)
});
}
protected override void PostStop()
{
this.Cluster.Unsubscribe(this.Self);
}
private async Task<bool> MemberUpReceived(ClusterEvent.MemberUp obj)
{
if (obj.Member.HasRole("actora"))
{
IActorRef actorSelection = await Context.ActorSelection("akka.tcp://mycluster#localhost:666/user/actora").ResolveOne(TimeSpan.FromSeconds(1));
actorSelection.Tell(new Identify(1));
}
return true;
}
private bool IdentifyMessageReceived(ActorIdentity obj)
{
return true;
}
}
My config files are super simple
ActorA:
akka {
log-config-on-start = on
stdout-loglevel = DEBUG
loglevel = DEBUG
actor.provider = cluster
remote {
dot-netty.tcp {
port = 666
hostname = localhost
}
}
cluster {
seed-nodes = ["akka.tcp://mycluster#localhost:666"]
roles = [actora]
}
}
ActorB:
akka {
log-config-on-start = on
stdout-loglevel = DEBUG
loglevel = DEBUG
actor.provider = cluster
remote {
dot-netty.tcp {
port = 0
hostname = localhost
}
}
cluster {
seed-nodes = ["akka.tcp://mycluster#localhost:666"]
roles = [actorb]
}
}
I now want to identify all the given actors attached to my cluster. I do this by waiting for the cluster node MEMBER UP event and trying to send an Identify() message to the given actor to receive a reference to it.
The problem is that I cannot seem to be able to successfully send the message back to ActorA. Infact when executing the above code (despite the fact that I have the correct reference in the ActorSelection method) the ActorIdentity message is invoked in ActorB rather than ActorA.
I have tried handling all received message in ActorA and it appears I never receive the Identity message. However I can successfully send any other type of message ActorA using the same ActorSelection reference.
So can anyone provide any insight? Why is my identity message never reaching my target actor?
ActorIdentity message is invoked in ActorB rather than ActorA.
This works as intended, as you're sending Identify request from actor B → A, for which ActorIdentity is a response message (send automatically from A → B).
You can already observe this behavior in action, since:
Context.ActorSelection(path).ResolveOne(timeout)
is more or less an equivalent of
Context.ActorSelection(path).Ask<ActorIdentity>(new Identify(null), timeout: timeout)
Identify is a system message, which is handled always before any programmer-defined message handlers are invoked - for this reason you probably won't catch it in your own handlers.
I'm trying to implement a chat in my app, with azure asp.net web api on back-end and xamarin ios on front-end.
So on back-end I configure my hub with this lines:
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
app.MapSignalR("/signalr", hubConfiguration);
and here is my hub source:
[HubName("Chat")]
public class Chat : Hub
{
public Task JoinRoom(string roomName)
{
return Groups.Add(Context.ConnectionId, roomName);
}
public Task LeaveRoom(string roomName)
{
return Groups.Remove(Context.ConnectionId, roomName);
}
public Task Send(string message, string room)
{
return Clients.OthersInGroup(room).addMessage(message);
}
}
on xamarin ios client everything is pretty simple too:
[Preserve(AllMembers=true)]
public class Msg
{
public string txt { get; set; }
}
public class Client
{
private readonly string _userName;
private readonly HubConnection _connection;
private readonly IHubProxy _proxy;
public event EventHandler<string> OnMessageReceived;
public Client(string userName)
{
_userName = userName;
_connection = new HubConnection("http://mywebsite.azurewebsites.net/");
_connection.ConnectionToken = NetManager.Instance.token.access_token;
_proxy = _connection.CreateHubProxy("Chat");
}
public async Task Connect()
{
await _connection.Start();
_proxy.On("messageReceived", (Msg platform, Msg message) =>
{
if (OnMessageReceived != null)
OnMessageReceived(this, string.Format("{0}: {1}", platform, message));
});
Send("Connected");
}
public Task Send(string message)
{
return _proxy.Invoke("Send", _userName, message);
}
}
So if I connect to server from ios simulator -- it works fine, but when I try to do this from my ipad device -- it crashes with internal server error on line ('await _connection.Start();')
I have already checked server with debugger, but no exceptions raised there, and logs are clear.
Any ideas how to fix this will be very helpful!
[Upd]
Some new server logs:
FROM SIMULATOR:
2015-09-27 03:29:39 IBYB GET /signalr/negotiate clientProtocol=1.4&connectionData=[%7B%22Name%22:%22chat%22%7D]&connectionToken=1qZRVTwNMqgGiI8iPpJ9oaPPCeLhHti3UXZR4HYsw2_7SGzOj44WRt8qzBFPRELZu6zk33-8uS7MNaq5K7N5qA2BR1IgzUf8CP9ihoGbjcwtXpFkdyh5gNqFBTHIRSgc2yto5_AOGUok_opd4B9FjAmOhgQlHF_myf28oBBYJxaXZ5iJOXFpI33k6pmQASRvveW-kBRX_89BF2mxAqFkZmVh3_MCo2gWP-NRZZFtMd8ZoxYHnGhyGNVsiiN1KaTHB1xAakP7HZjLpWg7SigfMvtKW0g3eXBsAr1wCJsAKIRjCaMAQFGV0BkKfYztRXvz4QbSXmIBXpKtviYamOqih4-LQJyywwVNh_Djt9H0wYIZmVO565G4ZNKzQfSkK6jMFQz6GfFf_OSlUJIz-0IXsQ7t2kP5VfqVrRu5KK7pyqtZJE5Y4HikRkh6DP8GIYBiXZclmBrpwWhUYVq5P3J2zhDYDNW2GiB95xnRjzXSjPQ&noCache=ccc35de3-5b7a-49ac-bf89-f15145d2634f&X-ARR-LOG-ID=bce175df-8246-4e75-8887-707a7386e1ee 80 - 89.179.240.94 - - - ibyb.azurewebsites.net 200 0 0 942 1718 1093
FROM DEVICE:
2015-09-27 03:35:02 IBYB GET /signalr/negotiate clientProtocol=1.4&connectionData=[%7B%7D]&connectionToken=YIZWqEe7AHvZHwb_aG7jOA9y_NFwUTBuLWSP46q8yh2rQMcjASbsp7VWlZ0Jzo_Z-n230IlhnOHZKm8kJr72ejLF-4LMopwyfZaWmsKNAy6cTd5uyU-76WoXsd2gpmpEJp8A0vMXe2HeLMIvH2Ckw6NIamEbu_uQvHRplkGeUhqGbTQU04dsU47ksebG_zh9XTtLGY9767CiwCYBg_Zk3aFgfrSvzPBiijfmIP9mUhz2ViAigyPeDeOE6WYRgtkkOIGMXGOoS5vQODHMUtiMaoV-w-jcCWtjHzzaObKNeX6zAsB0aJDc9_7fJAoBER7Jd6g0FOuEDvo8D95f1vA8j2SxbBgR4SFIzBDo_JfzO_TbPA6a2FR-ruw3yZHMidmcz3XQWb3vL5a0BPntzL9MPiVgvuhvkXfiRoDrRbzn2YXSqWrN-eEdjsF_WX-LMUc1JyKkjcHP00EAw3kocDWbnXaPqirsSvC5SZ7KY1u63BU&noCache=daae80e6-d209-42c9-8780-35d00fd8208c&X-ARR-LOG-ID=b1aad3d6-7df8-4828-a89c-665d8b550c0c 80 - 89.179.240.94 - - - ibyb.azurewebsites.net 500 0 0 11369 1676 281
So somehow, don't know why from device sends empty group name
(1) :[%7B%22Name%22:%22chat%22%7D]
(2) :[%7B%7D]
Seems like json serializer problem, but how can I fix that?
finally got it, it was because of 'link all' option in device build options, so json didn't work well
I am developing 2 applications, the first being a C# console application and the other an Asp.net web application. I am using SignalR to connect the two.
This is my C# console application (Client)
public class RoboHub
{
public static IHubProxy _hub;
public RoboHub()
{
StartHubConnection();
_hub.On("GetGoals", () => GetGoals());
_hub.On("PrintMessageRobot", x => PrintMessageRobot(x));
Thread thread = new Thread(MonitorHubStatus);
thread.Start();
}
public void GetGoals()
{
//TODO: Does stuff
}
public void PrintMessageRobot(string msg)
{
Console.WriteLine(msg);
}
public void StartHubConnection()
{
Console.WriteLine("Robo Hub Starting");
string url = #"http://localhost:46124/";
var connection = new HubConnection(url);
_hub = connection.CreateHubProxy("WebHub");
connection.Start().Wait();
Console.WriteLine("Robo Hub Running");
}
public void MonitorHubStatus()
{
while (true)
{
Thread.Sleep(1000);
_hub.Invoke("Ping", "ping").Wait();
Console.WriteLine("WebHub Pinged : " + DateTime.Now);
}
}
}
When the console application runs, it creates an instance of the RoboHub class. Which in turn starts a connection to the SignalR hub and on a separate thread starts the method MonitorHubStatus which is something I implemented to check if the C# console application client is still actively connected to the hub.
This is my Web hub (within the Asp.net Web application)
public class WebHub : Hub
{
/// <summary>
/// This method should be called by the Web Clients.
/// This method should call the method on the robot clients.
/// </summary>
public void GetGoalsHub()
{
lock (UserHandler.Connections)
{
if (UserHandler.Connections.Any(connection => connection.Contains("Robot")))
{
Clients.All.GetGoals();
}
}
//TODO add Error method to call on the client
}
/// <summary>
/// Override Methods
/// </summary>
/// <returns></returns>
public override Task OnConnected()
{
lock (UserHandler.Connections)
{
//Find out who is connecting based on the User-Agent header
var query = (from r in Context.Headers
where r.Key == "User-Agent"
select r).SingleOrDefault().ToString();
if (query.Contains("SignalR.Client.NET45"))
{
UserHandler.Connections.Add("Robot : " + Context.ConnectionId);
}
else
{
UserHandler.Connections.Add("Web Application : " + Context.ConnectionId);
GetGoalsHub();
}
}
Clients.All.UpdateConnections(UserHandler.Connections);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
lock (UserHandler.Connections)
{
for (int i = 0; i < UserHandler.Connections.Count; i++)
{
if (UserHandler.Connections[i].Contains(Context.ConnectionId))
{
UserHandler.Connections.Remove(UserHandler.Connections[i]);
}
}
}
Clients.All.UpdateConnections(UserHandler.Connections);
return base.OnDisconnected(stopCalled);
}
public void Ping(string msg)
{
Clients.All.PrintMessageRobot("Pong : " + DateTime.Now);
}
}
public static class UserHandler
{
public static List<string> Connections = new List<string>();
}
Currently the 2 applications seem to work for a time, until after a while this error randomly appears:
Connection started reconnecting before invocation result was received.
Further more should the web hub call any other method on the C# console client such as the GetGoals method. The 'Ping Pong' method freezes and after time a similar exception is thrown. throughout this the web client continues to function perfectly and the web client can communicate back and forth with the hub server.
Can anyone suggest what the issue could be?
Edit: Further investigation leads me to believe it to be something to do with threading, however it is difficult to find the source of the issues.
The problem is with the invoke call:
_hub.Invoke("MethodName", "Parameters").Wait();
Here I am telling it to wait for a response however I did not program any reply mechanism in the web server.
This error was fix by switching to:
_hub.Invoke("MethodName", "Parameters");
Now it follows a 'fire and forget' methodology and it now no longer gets the error. Should anyone else get this error be sure to check whether you need a response or not.
You will get the same error message if the data being sent to server side is a 'Non-serializable' (e.g.) List of business objects which don't have [Serializable] attribute
I got this same exception when the payload was to big. I fixed it by changing the following line, in the Startup declaration for signlr.net. It removes the size limit
GlobalHost.Configuration.MaxIncomingWebSocketMessageSize = null;
I've just started to play around with WebSockets and ASP.NET and have run into a weird issue. I'm building a very primitive ASP.NET 4.5 WebAPI application that is supposed to function as an echo-server like so:
using Microsoft.Web.WebSockets;
// ...
namespace MyControllers
{
internal class EchoHandler : WebSocketHandler
{
public override void OnClose()
{
System.Diagnostics.Debug.Write("Close");
}
public override void OnError()
{
System.Diagnostics.Debug.Write("Error: " + this.Error.ToString());
}
public override void OnOpen()
{
System.Diagnostics.Debug.Write("Open");
}
public override void OnMessage(string message)
{
System.Diagnostics.Debug.Write("Message: " + message);
this.Send("Echo: " + message);
}
}
public class EchoController : ApiController
{
public HttpResponseMessage Get()
{
if (HttpContext.Current.IsWebSocketRequest)
{
HttpContext.Current.AcceptWebSocketRequest(new EchoHandler());
return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
}
}
I'm connecting to this service using a Windows Store Application written in C#. The relevant code looks like this:
class WebsocketTest
{
private MessageWebSocket webSocket;
private DataWriter messageWriter;
private async Task Connect()
{
var server = new Uri("ws://127.0.0.1:81/");
webSocket = new MessageWebSocket();
webSocket.Control.MessageType = SocketMessageType.Utf8;
webSocket.MessageReceived += messageWebSocket_MessageReceived;
webSocket.Closed += messageWebSocket_Closed;
await webSocket.ConnectAsync(server);
messageWebSocket = webSocket;
messageWriter = new DataWriter(webSocket.OutputStream);
}
private async Task Send(string message)
{
try
{
messageWriter.WriteString(message);
await messageWriter.StoreAsync();
}
catch (Exception ex)
{
var error = WebSocketError.GetStatus(ex.GetBaseException().HResult);
}
}
}
This works well for a while, but after an arbitrary number of messages have been sent back and forth, OnError() is invoked on the server and I get the following exception: "The I/O operation has been aborted because of either a thread exit or an application request" (It's the "this.Send(...)" that seems to be causing it). If I keep sending stuff on the client, I get a "ConnectionAborted" error when calling "dataWriter.StoreAsync()".
The error occurs every time, but it takes a varying number of messages before it does. Using longer messages seems to speed up the process.
For testing, I also tried using plain AspNetWebSockets instead of a WebSocketHandler but with the same outcome.
Any ideas?
Thanks a ton in advance,
Kai
Its a bug (reported by me):
https://connect.microsoft.com/VisualStudio/feedbackdetail/view/976851/server-websocket-closed-abruptly-the-i-o-operation-has-been-aborted-because-of-either-a-thread-exit-or-an-application-request
I have been trying to find a workaround for quite some time without being successful. I'm using the HttpListener but the symptom is the same. Now I have changed implementation to a third party library and the problem seems to have been resolved.