System.Net.WebSockets.ClientWebSocket no reliably working? - c#

I am implementing passing a web socket back from our api according to this url here;
http://blogs.msdn.com/b/youssefm/archive/2012/07/17/building-real-time-web-apps-with-asp-net-webapi-and-websockets.aspx
Now the idea is that the user will register for a web socket. This is done and works using the following code;
[HttpGet]
[Route("getsocket")]
public HttpResponseMessage GetWebSocket()
{
HttpContext.Current.AcceptWebSocketRequest(new TestWebSocketHandler());
return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
}
Then they make a call to the api to begin doing some specific functions, which will then report with message back down the same websocket.
private static WebSocketWrapper _socket;
[HttpGet]
[Route("begin")]
public async Task<IHttpActionResult> StartRunning(string itemid)
{
try
{
if (_socket == null ||
_socket.State() == WebSocketState.Aborted ||
_socket.State() == WebSocketState.Closed ||
_socket.State() == WebSocketState.None)
{
_socket = WebSocketWrapper.Create("wss://localhost:44301/api/v1/testcontroller/getsocket");
_socket.OnConnect(OnConnect);
_socket.OnDisconnect(OnDisconnect);
_socket.OnMessage(OnMessage);
await _socket.ConnectAsync();
}
//builds first message to be sent and sends it
_socket.QueueMessage(firstTest);
}
catch(Exception ex)
{
//logs error
}
return Ok();
}
So effectively the client cretes a new websocket connected to the server. They then call the second message to trigger the server to start a number of tests on the device passed. The server start the tests and broadcast the messages back down the socket (the json message model contains the deviceid, so the client can filter for relevent messages).
When they receive a message the client will then acknowledge it and the next test is done etc.
Now it works the first time I run it (after compilation). However, I want to be able to have multiple client connect to the websocket list (the solution is tabulated and the tests it will run may take a while, so its possible multiple tests will be ran at any one time). So I think it has something to do with the static WebSocketWrapper instance.
However, they have asked that a single websocket be used on the server, with a list of the devices being listened for. So in effect all messages are sent to all clients from the one server connection. The clients then filter out the messages they want to listen to based on the deviceid they pass.
When I try re-running, ore running a second test, Which is basically calling the getwebsocket and then the begin method, the code runs without error, but the onopen method never gets called? Its as though the socket just doesnt fire up?
Unfortunately we cannot use signalr as this is not specified
For reference the socket wrapper class is
public class WebSocketWrapper
{
private const int ReceiveChunkSize = 2048;
private const int SendChunkSize = 2048;
private readonly ClientWebSocket _ws;
private readonly Uri _uri;
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private readonly CancellationToken _cancellationToken;
private Action<WebSocketWrapper> _onConnected;
private Action<TestResultModel, WebSocketWrapper> _onMessage;
private Action<WebSocketWrapper> _onDisconnected;
//private static Queue<TestResultModel> _messageQueue = new Queue<TestResultModel>();
private static BlockingCollection<TestResultModel> _messageQueue = new BlockingCollection<TestResultModel>();
protected WebSocketWrapper(string uri)
{
_ws = new ClientWebSocket();
_ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(30);
_uri = new Uri(uri);
_cancellationToken = _cancellationTokenSource.Token;
}
/// <summary>
/// Creates a new instance.
/// </summary>
/// <param name="uri">The URI of the WebSocket server.</param>
/// <returns></returns>
public static WebSocketWrapper Create(string uri)
{
return new WebSocketWrapper(uri);
}
/// <summary>
/// Get the current state of the socket
/// </summary>
/// <returns>WebSocketState of the current _ws</returns>
public WebSocketState State()
{
return _ws.State;
}
/// <summary>
/// Disconnects from the websocket
/// </summary>
public async Task DisconnectAsync()
{
try
{
await _ws.CloseOutputAsync(
WebSocketCloseStatus.NormalClosure,
"Server has been closed by the disconnect method",
_cancellationToken);
CallOnDisconnected();
}
catch(Exception ex)
{
throw ex;
}
finally
{
_ws.Dispose();
}
}
/// <summary>
/// Connects to the WebSocket server.
/// </summary>
/// <returns></returns>
public async Task<WebSocketWrapper> ConnectAsync()
{
try
{
await _ws.ConnectAsync(_uri, _cancellationToken);
}
catch(Exception ex)
{
}
CallOnConnected();
RunInTask(() => ProcessQueueAsync());
RunInTask(() => StartListen());
return this;
}
/// <summary>
/// Set the Action to call when the connection has been established.
/// </summary>
/// <param name="onConnect">The Action to call.</param>
/// <returns></returns>
public WebSocketWrapper OnConnect(Action<WebSocketWrapper> onConnect)
{
_onConnected = onConnect;
return this;
}
/// <summary>
/// Set the Action to call when the connection has been terminated.
/// </summary>
/// <param name="onDisconnect">The Action to call</param>
/// <returns></returns>
public WebSocketWrapper OnDisconnect(Action<WebSocketWrapper> onDisconnect)
{
_onDisconnected = onDisconnect;
return this;
}
/// <summary>
/// Adds a message to the queu for sending
/// </summary>
/// <param name="message"></param>
public void QueueMessage(TestResultModel message)
{
//_messageQueue.Enqueue(message);
_messageQueue.Add(message);
}
/// <summary>
/// returns the size of the current message queue.
/// Usefult for detemning whether or not an messages are left in queue before closing
/// </summary>
/// <returns></returns>
public int QueueCount()
{
return _messageQueue.Count;
}
/// <summary>
/// Processes the message queue in order
/// </summary>
public async Task ProcessQueueAsync()
{
try
{
foreach(var current in _messageQueue.GetConsumingEnumerable())
{
await SendMessageAsync(current);
}
}
catch(Exception ex)
{
//TODO
}
}
/// <summary>
/// Set the Action to call when a messages has been received.
/// </summary>
/// <param name="onMessage">The Action to call.</param>
/// <returns></returns>
public WebSocketWrapper OnMessage(Action<TestResultModel, WebSocketWrapper> onMessage)
{
_onMessage = onMessage;
return this;
}
/// <summary>
/// Send a message to the WebSocket server.
/// </summary>
/// <param name="message">The message to send</param>
public async Task SendMessageAsync(TestResultModel result)
{
if (_ws.State != WebSocketState.Open)
{
throw new Exception("Connection is not open.");
}
var message = JsonConvert.SerializeObject(result);
var messageBuffer = Encoding.UTF8.GetBytes(message);
var messagesCount = (int)Math.Ceiling((double)messageBuffer.Length / SendChunkSize);
for (var i = 0; i < messagesCount; i++)
{
var offset = (SendChunkSize * i);
var count = SendChunkSize;
var lastMessage = ((i + 1) == messagesCount);
if ((count * (i + 1)) > messageBuffer.Length)
{
count = messageBuffer.Length - offset;
}
await _ws.SendAsync(new ArraySegment<byte>(messageBuffer, offset, count), WebSocketMessageType.Text, lastMessage, _cancellationToken);
}
}
private async Task StartListen()
{
var buffer = new byte[ReceiveChunkSize];
//part of a big hack, temporary solution
string prevResult = "";
try
{
while (_ws.State == WebSocketState.Open)
{
var stringResult = new StringBuilder();
WebSocketReceiveResult result;
do
{
result = await _ws.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationToken);
if (result.MessageType == WebSocketMessageType.Close)
{
await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
CallOnDisconnected();
}
else
{
var str = Encoding.UTF8.GetString(buffer, 0, result.Count);
stringResult.Append(str);
}
} while (!result.EndOfMessage);
if (!prevResult.Equals(stringResult.ToString()))
{
prevResult = stringResult.ToString();
CallOnMessage(stringResult);
}
}
}
catch (Exception ex)
{
CallOnDisconnected();
}
finally
{
_ws.Dispose();
}
}
private void CallOnMessage(StringBuilder stringResult)
{
if (_onMessage != null)
{
try
{
var message = JsonConvert.DeserializeObject<TestResultModel>(stringResult.ToString());
RunInTask(() => _onMessage(message, this));
}
catch (Exception ex)
{
//Ignore any other messages not TestResultModel
}
}
}
private void CallOnDisconnected()
{
if (_onDisconnected != null)
RunInTask(() => _onDisconnected(this));
}
private void CallOnConnected()
{
if (_onConnected != null)
RunInTask(() => _onConnected(this));
}
private static Task RunInTask(Action action)
{
return Task.Factory.StartNew(action);
}
}
As an update please see the debug screen taken when trying to call the websocket for the second time. As you can see it appears to be in the aborted state? (on first run obviousl its null). Any ideas?

A lot of the functionality you are looking is "easy" to implement using SignalR. As where you create a "Hub" where you can broadcast to all clients or simply choose a single client.
I take it that you have previous experience with websockets, so I think you should take a look! It is a bit magical and can be a hell to troubleshoot in bigger applications and services.
A simple chat tutorial can be found here
You can also take a look here for a person that has had problems sending notications to single clients using SignalR

Related

Getting result from async call in a foreach loop

I am trying to get a list of offers that are fetched from an external service.
public void TriggerPurchase()
{
List<string> offers = listener.GetOffers();
Debug.Log("OFFERS " + offers[0]);
}
Here is the fetch function
public List<string> GetOffers()
{
var purchases = GetComponent<Purchases>();
purchases.GetOfferings((offerings, error) =>
{
if (offerings.Current != null && offerings.Current.AvailablePackages.Count != 0)
{
List<string> offers = new List<string>();
foreach (var package in offerings.Current.AvailablePackages)
{
//Debug.Log("Package " + package);
if (package == null) continue;
offers.Add(package.StoreProduct.PriceString);
}
return offers; // Anonymouse function converted to a void returning delegate cannot return a value
}
});
}
I get the Anonymouse function converted to a void returning
I would like to get a list with offers?
**UPDATE
Adding the implementation of GetOfferings
/// <summary>
/// Callback for <see cref="Purchases.GetOfferings"/>.
/// </summary>
/// <param name="offerings"> The <see cref="Offerings"/> object if the request was successful, null otherwise.</param>
/// <param name="error"> The error if the request was unsuccessful, null otherwise.</param>
public delegate void GetOfferingsFunc(Offerings offerings, Error error);
private GetOfferingsFunc GetOfferingsCallback { get; set; }
///
/// <param name="callback"> A completion block called when offerings are available.
/// Called immediately if offerings are cached. <see cref="Offerings"/> will be null if an error occurred.
/// </param>
///
/// <seealso href="https://docs.revenuecat.com/docs/displaying-products"/>
///
public void GetOfferings(GetOfferingsFunc callback)
{
GetOfferingsCallback = callback;
_wrapper.GetOfferings();
}
void methods don't return values. If you want your method to return a result you must change the type of the method and delegate:
public delegate Task<List<string>> GetOfferingsFuncAsync(Offerings offerings, Error error);
private GetOfferingsFunc GetOfferingsCallback { get; set; }
public async Task<List<string>> GetOfferingsAsync(GetOfferingsFunc callback)
{
GetOfferingsCallback = callback;
_wrapper.GetOfferings();
return await this.GetOfferingsCallback.Invoke(arg1, arg2);
}
Now you can handle the expected result properly:
public async Task<List<string>> GetOffers()
{
var purchases = GetComponent<Purchases>();
// Return the result.
return await purchases.GetOfferingsAsync(async (offerings, error) =>
{
if (offerings.Current != null && offerings.Current.AvailablePackages.Count != 0)
{
List<string> offers = new List<string>();
foreach (var package in offerings.Current.AvailablePackages)
{
//Debug.Log("Package " + package);
if (package == null) continue;
await SomeAsyncApiCall();
offers.Add(package.StoreProduct.PriceString);
}
// Because the delegate is no longer 'void'
// you can now return a result
return offers;
}
});
}

Why is AutoResetEvent.waitone returning false in my test?

Hello so I'm doing a unit test in c# with Mono. I'm testing a async method that uses delegates to return the response of a web service.
So here is my test:
[Test]
public void TestCreateUser()
{
Debug.Log("TestCreateUser");
Vizzario.Instance.OnCreateUserSuccess += delegate(CreateUserResponse x)
{
Debug.Log("TestCreateUser: " + x.Response);
UnityEngine.Assertions.Assert.IsNotNull(x.TokenResponse.AuthToken);
UnityEngine.Assertions.Assert.AreEqual(BaseResponse.ResponseEnum.SUCCESS, x.Response);
// Signal that work is finished.
bool s = autoResetEvent.Set();
Debug.Log(s);
};
// Does the request
InitData.NewUserData.Email = InitData.GetRandomEmail();
Vizzario.Instance.CreateUser(InitData.NewUserData);
// Wait for work method to signal.
bool a = autoResetEvent.WaitOne(TimeSpan.FromSeconds(5), true);
Debug.Log(a);
}
So when I run this test the result from WaitOne executes before executing the body of my MyClass.Instance.OnCreateUserSuccess and it prints false.
So it prints something like: 1. false, 2. TestCreateUser: Success
When it should print something like: 1. TestCreateUser: Success, 2. true
Why is the waitone not working, I try increasing autoResetEvent.WaitOne(TimeSpan.FromSeconds(5), true); to autoResetEvent.WaitOne(TimeSpan.FromSeconds(60), true); but I got the same results I know as a fact that the CreateUser method doesn't take more than 1 or 2 seconds so if I try autoResetEvent.WaitOne(); my test hangs and I have to restart the ide.
Any ideas.
Thanks in advance!
As requested I'm adding the method that is called in my vizzario class:
public delegate void CreateUserCallback(CreateUserResponse response);
public event CreateUserCallback OnCreateUserSuccess;
public event CreateUserCallback OnCreateUserError;
public void CreateUser(NewUserData data)
{
CreateUserResponse response = new CreateUserResponse();
this.DefApi.UserPost(data, ApiKey, ToolkitVersion, dryRun)
.Then(x =>
{
response.TokenResponse = x;
response.Response = BaseResponse.ResponseEnum.SUCCESS;
DoCreateUserNext(response);
})
.Catch(e =>
{
response.Response = BaseResponse.ResponseEnum.ERROR;
});
}
/// <summary>
/// Return callback in case of an exception inside the sdk.
/// </summary>
/// <param name="e"></param>
private void DoCreateUserError(Exception e)
{
if (OnCreateUserError != null)
{
CreateUserResponse response = new CreateUserResponse();
response.Response = BaseResponse.ResponseEnum.ERROR;
response.message = e.Message;
OnCreateUserError(response);
}
}
/// <summary>
/// Return callback from the sdk to the developer.
/// </summary>
/// <param name="response"></param>
private void DoCreateUserNext(CreateUserResponse response)
{
if (OnCreateUserSuccess != null)
{
if (response.Response == BaseResponse.ResponseEnum.SUCCESS)
{
OnCreateUserSuccess(response);
}
}
if (OnCreateUserError != null)
{
if (response.Response == BaseResponse.ResponseEnum.ERROR)
{
OnCreateUserError(response);
}
}
}

How to wait until item goes through pipeline?

So, I'm trying to wrap my head around Microsoft's Dataflow library. I've built a very simple pipeline consisting of just two blocks:
var start = new TransformBlock<Foo, Bar>();
var end = new ActionBlock<Bar>();
start.LinkTo(end);
Now I can asynchronously process Foo instances by calling:
start.SendAsync(new Foo());
What I do not understand is how to do the processing synchronously, when needed. I thought that waiting on SendAsync would be enough:
start.SendAsync(new Foo()).Wait();
But apparently it returns as soon as item is accepted by first processor in pipeline, and not when item is fully processed. So is there a way to wait until given item was processed by last (end) block? Apart from passing a WaitHandle through entire pipeline.
In short that's not supported out of the box in data flow. Essentially what you need to do is to tag the data so you can retrieve it when processing is done. I've written up a way to do this that let's the consumer await a Job as it gets processed by the pipeline. The only concession to pipeline design is that each block take a KeyValuePair<Guid, T>. This is the basic JobManager and the post I wrote about it. Note the code in the post is a bit dated and needs some updates but it should get you in the right direction.
namespace ConcurrentFlows.DataflowJobs {
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
/// <summary>
/// A generic interface defining that:
/// for a specified input type => an awaitable result is produced.
/// </summary>
/// <typeparam name="TInput">The type of data to process.</typeparam>
/// <typeparam name="TOutput">The type of data the consumer expects back.</typeparam>
public interface IJobManager<TInput, TOutput> {
Task<TOutput> SubmitRequest(TInput data);
}
/// <summary>
/// A TPL-Dataflow based job manager.
/// </summary>
/// <typeparam name="TInput">The type of data to process.</typeparam>
/// <typeparam name="TOutput">The type of data the consumer expects back.</typeparam>
public class DataflowJobManager<TInput, TOutput> : IJobManager<TInput, TOutput> {
/// <summary>
/// It is anticipated that jobHandler is an injected
/// singleton instance of a Dataflow based 'calculator', though this implementation
/// does not depend on it being a singleton.
/// </summary>
/// <param name="jobHandler">A singleton Dataflow block through which all jobs are processed.</param>
public DataflowJobManager(IPropagatorBlock<KeyValuePair<Guid, TInput>, KeyValuePair<Guid, TOutput>> jobHandler) {
if (jobHandler == null) { throw new ArgumentException("Argument cannot be null.", "jobHandler"); }
this.JobHandler = JobHandler;
if (!alreadyLinked) {
JobHandler.LinkTo(ResultHandler, new DataflowLinkOptions() { PropagateCompletion = true });
alreadyLinked = true;
}
}
private static bool alreadyLinked = false;
/// <summary>
/// Submits the request to the JobHandler and asynchronously awaits the result.
/// </summary>
/// <param name="data">The input data to be processd.</param>
/// <returns></returns>
public async Task<TOutput> SubmitRequest(TInput data) {
var taggedData = TagInputData(data);
var job = CreateJob(taggedData);
Jobs.TryAdd(job.Key, job.Value);
await JobHandler.SendAsync(taggedData);
return await job.Value.Task;
}
private static ConcurrentDictionary<Guid, TaskCompletionSource<TOutput>> Jobs {
get;
} = new ConcurrentDictionary<Guid, TaskCompletionSource<TOutput>>();
private static ExecutionDataflowBlockOptions Options {
get;
} = GetResultHandlerOptions();
private static ITargetBlock<KeyValuePair<Guid, TOutput>> ResultHandler {
get;
} = CreateReplyHandler(Options);
private IPropagatorBlock<KeyValuePair<Guid, TInput>, KeyValuePair<Guid, TOutput>> JobHandler {
get;
}
private KeyValuePair<Guid, TInput> TagInputData(TInput data) {
var id = Guid.NewGuid();
return new KeyValuePair<Guid, TInput>(id, data);
}
private KeyValuePair<Guid, TaskCompletionSource<TOutput>> CreateJob(KeyValuePair<Guid, TInput> taggedData) {
var id = taggedData.Key;
var jobCompletionSource = new TaskCompletionSource<TOutput>();
return new KeyValuePair<Guid, TaskCompletionSource<TOutput>>(id, jobCompletionSource);
}
private static ExecutionDataflowBlockOptions GetResultHandlerOptions() {
return new ExecutionDataflowBlockOptions() {
MaxDegreeOfParallelism = Environment.ProcessorCount,
BoundedCapacity = 1000
};
}
private static ITargetBlock<KeyValuePair<Guid, TOutput>> CreateReplyHandler(ExecutionDataflowBlockOptions options) {
return new ActionBlock<KeyValuePair<Guid, TOutput>>((result) => {
RecieveOutput(result);
}, options);
}
private static void RecieveOutput(KeyValuePair<Guid, TOutput> result) {
var jobId = result.Key;
TaskCompletionSource<TOutput> jobCompletionSource;
if (!Jobs.TryRemove(jobId, out jobCompletionSource)) {
throw new InvalidOperationException($"The jobId: {jobId} was not found.");
}
var resultValue = result.Value;
jobCompletionSource.SetResult(resultValue);
}
}
}
I ended up using the following pipeline:
var start = new TransformBlock<FooBar, FooBar>(...);
var end = new ActionBlock<FooBar>(item => item.Complete());
start.LinkTo(end);
var input = new FooBar {Input = new Foo()};
start.SendAsync(input);
input.Task.Wait();
Where
class FooBar
{
public Foo Input { get; set; }
public Bar Result { get; set; }
public Task<Bar> Task { get { return _taskSource.Task; } }
public void Complete()
{
_taskSource.SetResult(Result);
}
private TaskCompletionSource<Bar> _taskSource = new TaskCompletionSource<Bar>();
}
Less than ideal, but it works.

HTTP WCF Service keep the stream open

I'm trying to stream data to a client over HTTP. To achieve this I'm using a WCF service with a WebHttpBinding. The problem is that the System.IO.Stream that my operation returns is closed before I can write something to it. I'd like to keep the stream open until data needs to be written to it. Usually this will not be more than half a minute.
In the service request method I create a new instance of System.IO.MemoryStream I put that into a collection of all streams and return it as the function output. Later on when there is audio data available I'm writing to all streams that are in the collection. But by then all the the requests have been closed. When I go to the endpoint url I get the browsers standard player completely greyed out. I also tested with a REST client, it showed me that the request closes immediately after the return statement.
The problem is that we use the Libspotify SDK to retrieve music. This sends 8192 bytes of PCM data per cycle. We would like to make it possible for users to play their music from a Chromecast device. The Chromecast doesn't support PCM data that's why we convert it to MP3 with libmp3lame and then send it through the output stream to the Chromecast. For this approach to work we need the connection to stay alive even though there is no actual data being send through the Stream.
The Libspotify music delivery callback can be found here.
This is how I set-up the service:
/// <summary>
/// The WCF service host.
/// </summary>
private ServiceHost ServiceHost;
/// <summary>
/// Start the HTTP WCF service.
/// </summary>
public void startListening()
{
if (ServiceHost == null)
{
ServiceHost = new ServiceHost(typeof(StreamingService));
var binding = new WebHttpBinding(WebHttpSecurityMode.None);
binding.TransferMode = TransferMode.StreamedResponse;
var endpoint = ServiceHost.AddServiceEndpoint(typeof(StreamingContract), binding, new Uri(streamAddress));
endpoint.EndpointBehaviors.Add(new WebHttpBehavior());
ServiceHost.Open();
}
}
This is the Service implementation:
[ServiceContract(Name="StreamingContract")]
interface StreamingContract
{
[WebGet(UriTemplate="audio")]
[OperationContract()]
Stream Audio();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
IncludeExceptionDetailInFaults = true)]
public class StreamingService : StreamingContract
{
public System.IO.Stream Audio()
{
var stream = new System.IO.MemoryStream();
App.Logic.streaming.streams.Add(stream);
WebOperationContext.Current.OutgoingResponse.ContentType = "audio/mp3";
WebOperationContext.Current.OutgoingResponse.ContentLength = 1000;
return stream;
}
}
I also tried setting: [OperationContext(AutoDisposeParameter=false)] on Audio() in the ServiceContract. This only started throwing a System.InvalidOperationException. I also thought maybe it's a problem that the content length is unknown, that didn't help either.
Your service is doing exactly what it should - returning an empty stream and then closing the connection.
It sounds like you want to wait for that stream to asynchronously be filled. In order to do that, you'll have to implement some kind of callback. You should look into the Task.Run() method, as that would be the standard way of implementing asynchronous logic in .NET.
Hope you can use this sample as an answer. In this sample you can PUT an stream async to the server. Solution is tested and verified.
Here is an example of an HTTP WCF service(server) hosting the service with async setup:
Uri baseAddress = new Uri("http://localhost:8000/Service1/");
// Step 2 Create a ServiceHost instance to host the service
using (ServiceHost selfHost = new ServiceHost(typeof(Service1), baseAddress)) // type of class that implements service contract, and base address of service.
{
try
{
WebHttpBinding binding = new WebHttpBinding();
//BasicHttpBinding binding = new BasicHttpBinding();
binding.TransferMode = TransferMode.Streamed;
binding.MaxReceivedMessageSize = int.MaxValue; //"1000000000000"
binding.ReceiveTimeout = new TimeSpan(1, 0, 0); //"01:00:00";
binding.SendTimeout = new TimeSpan(1, 0, 0); //"01:00:00";
//binding.ReaderQuotas. = int.MaxValue;
// Step 3 Add a service endpoint to host. Endpoint consist of address, binding and service contract.
// Note this is optional in Framework 4.0 and upward. generate auto default.
selfHost.AddServiceEndpoint(typeof(IService1), binding, "").EndpointBehaviors.Add(new WebHttpBehavior()); // service contract interface, binding, address
// Step 5 Start the service.
// Open host to listen for incoming messages.
selfHost.Open();
Console.WriteLine("The service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
// Close the ServiceHostBase to shutdown the service.
selfHost.Close();
}
catch (CommunicationException ce)
{
Console.WriteLine("An exception occurred: {0}", ce.Message);
selfHost.Abort();
}
}
}
}
}
Here is the actual service interface impl:
[ServiceContract]
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public interface IService1
{
/// <summary>
/// An asynchronous service side upload operation.
/// </summary>
/// <param name="token">An application arbitrary piece of data. Can be used for request obfuscation.</param>
/// <param name="data">The data being uploaded.</param>
/// <param name="callback">Callback for async pattern, client does not pass this.</param>
/// <param name="asyncState">User state for async pattern, client does not pass this.</param>
/// <remarks>
/// The <paramref name="token"/> parameter is the only parameter passed in the URL by the client. The <paramref name="data"/>
/// parameter is the request body, the file being uploaded.
/// </remarks>
/// <returns></returns>
[OperationContract(AsyncPattern = true)]
[WebInvoke(Method = "PUT", UriTemplate = "asyncupload/")]
IAsyncResult BeginAsyncUpload(Stream data, AsyncCallback callback, object asyncState);
/// <summary>
/// Ends the asynchonous operation initiated by the call to <see cref="BeginAsyncUpload"/>.
/// </summary>
/// <remarks>
/// This is called by the WCF framework service side. NOTE: There is no <see cref="OperationContractAttribute"/> decorating
/// this method.
/// </remarks>
/// <param name="ar"></param>
void EndAsyncUpload(IAsyncResult ar);
}
And the implementation:
public class Service1 : IService1
{
/// <summary>
/// <see cref="IUpload.Upload"/>
/// </summary>
/// <param name="token">This parameter is ignored.</param>
/// <param name="data">Data being uploaded.</param>
/// <param name="callback">Async callback.</param>
/// <param name="asyncState">Async user state.</param>
public IAsyncResult BeginAsyncUpload(Stream data, AsyncCallback callback, object asyncState)
{
return new CompletedAsyncResult<Stream>(data);
}
/// <summary>
/// <see cref="IUpload.EndAsyncUpload"/>
/// </summary>
public void EndAsyncUpload(IAsyncResult ar)
{
Stream data = ((CompletedAsyncResult<Stream>)ar).Data;
_streamToFile(data);
}
/// <summary>
/// Writes the uploaded stream to a file.
/// </summary>
/// <remarks>
/// This function is just to prove a test. This simple saves the uploaded data into a file named "upload.dat" in a subdirectory
/// whose name is created by a generated guid.
/// </remarks>
private static void _streamToFile(Stream data)
{
// create name of subdirectory
string subDir = Guid.NewGuid().ToString("N");
// get full path to and create the directory to save file in
string uploadDir = Path.Combine(Path.GetDirectoryName(typeof(Service1).Assembly.Location), subDir);
Directory.CreateDirectory(uploadDir);
// 64 KiB buffer
byte[] buff = new byte[0x10000];
// save the file in chunks
using (FileStream fs = new FileStream(Path.Combine(uploadDir, "upload.xml"), FileMode.Create))
{
int bytesRead = data.Read(buff, 0, buff.Length);
while (bytesRead > 0)
{
fs.Write(buff, 0, bytesRead);
bytesRead = data.Read(buff, 0, buff.Length);
}
}
}
In addition add an class in this project with the following content:
internal class CompletedAsyncResult<T> : IAsyncResult
{
T data;
public CompletedAsyncResult(T data)
{ this.data = data; }
public T Data
{ get { return data; } }
#region IAsyncResult Members
public object AsyncState
{ get { return (object)data; } }
public WaitHandle AsyncWaitHandle
{ get { throw new Exception("The method or operation is not implemented."); } }
public bool CompletedSynchronously
{ get { return true; } }
public bool IsCompleted
{ get { return true; } }
#endregion
}
internal class AsyncResultNoResult : IAsyncResult
{
// Fields set at construction which never change while
// operation is pending
private readonly AsyncCallback m_AsyncCallback;
private readonly Object m_AsyncState;
// Fields set at construction which do change after
// operation completes
private const Int32 c_StatePending = 0;
private const Int32 c_StateCompletedSynchronously = 1;
private const Int32 c_StateCompletedAsynchronously = 2;
private Int32 m_CompletedState = c_StatePending;
// Field that may or may not get set depending on usage
private ManualResetEvent m_AsyncWaitHandle;
// Fields set when operation completes
private Exception m_exception;
public AsyncResultNoResult(AsyncCallback asyncCallback, Object state)
{
m_AsyncCallback = asyncCallback;
m_AsyncState = state;
}
public void SetAsCompleted(
Exception exception, Boolean completedSynchronously)
{
// Passing null for exception means no error occurred.
// This is the common case
m_exception = exception;
// The m_CompletedState field MUST be set prior calling the callback
Int32 prevState = Interlocked.Exchange(ref m_CompletedState,
completedSynchronously ? c_StateCompletedSynchronously :
c_StateCompletedAsynchronously);
if (prevState != c_StatePending)
throw new InvalidOperationException(
"You can set a result only once");
// If the event exists, set it
if (m_AsyncWaitHandle != null) m_AsyncWaitHandle.Set();
// If a callback method was set, call it
if (m_AsyncCallback != null) m_AsyncCallback(this);
}
public void EndInvoke()
{
// This method assumes that only 1 thread calls EndInvoke
// for this object
if (!IsCompleted)
{
// If the operation isn't done, wait for it
AsyncWaitHandle.WaitOne();
AsyncWaitHandle.Close();
m_AsyncWaitHandle = null; // Allow early GC
}
// Operation is done: if an exception occured, throw it
if (m_exception != null) throw m_exception;
}
#region Implementation of IAsyncResult
public Object AsyncState { get { return m_AsyncState; } }
public Boolean CompletedSynchronously
{
get
{
return Thread.VolatileRead(ref m_CompletedState) ==
c_StateCompletedSynchronously;
}
}
public WaitHandle AsyncWaitHandle
{
get
{
if (m_AsyncWaitHandle == null)
{
Boolean done = IsCompleted;
ManualResetEvent mre = new ManualResetEvent(done);
if (Interlocked.CompareExchange(ref m_AsyncWaitHandle,
mre, null) != null)
{
// Another thread created this object's event; dispose
// the event we just created
mre.Close();
}
else
{
if (!done && IsCompleted)
{
// If the operation wasn't done when we created
// the event but now it is done, set the event
m_AsyncWaitHandle.Set();
}
}
}
return m_AsyncWaitHandle;
}
}
public Boolean IsCompleted
{
get
{
return Thread.VolatileRead(ref m_CompletedState) !=
c_StatePending;
}
}
#endregion
}
internal class AsyncResult<TResult> : AsyncResultNoResult
{
// Field set when operation completes
private TResult m_result = default(TResult);
public AsyncResult(AsyncCallback asyncCallback, Object state) :
base(asyncCallback, state) { }
public void SetAsCompleted(TResult result,
Boolean completedSynchronously)
{
// Save the asynchronous operation's result
m_result = result;
// Tell the base class that the operation completed
// sucessfully (no exception)
base.SetAsCompleted(null, completedSynchronously);
}
new public TResult EndInvoke()
{
base.EndInvoke(); // Wait until operation has completed
return m_result; // Return the result (if above didn't throw)
}
}
Then the client impl:
try
{
//string txtDescription = "Test";
string txtFileName = "Invoice_50000.xml";
//byte[] fileToSend = File.ReadAllBytes(txtFileName)
// Create the REST request.
string url = "http://localhost:8000/Service1/";//ConfigurationManager.AppSettings["serviceUrl"];
//string requestUrl = string.Format("{0}/Upload/{1}/{2}", url, System.IO.Path.GetFileName(txtFileName), txtDescription);
/* Asynchronous */
string requestUrl = string.Format("{0}/asyncupload/", url);
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(requestUrl);
using (FileStream inputStream = File.Open(txtFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
//new BufferedStream
//new Buffer
request.SendChunked = true;
request.AllowWriteStreamBuffering = false;
request.Method = "PUT";
request.ContentType = "application/octet-stream";
//request.ContentType = MediaTypeNames.Application.Octet
request.ContentLength = inputStream.Length;
/* BEGIN: Solution with chunks */
// 64 KB buffer
byte[] chunkBuffer = new byte[0x10000];
Stream st = request.GetRequestStream();
// as the file is streamed up in chunks, the server will be processing the file
int bytesRead = inputStream.Read(chunkBuffer, 0, chunkBuffer.Length);
while (bytesRead > 0)
{
st.Write(chunkBuffer, 0, bytesRead);
bytesRead = inputStream.Read(chunkBuffer, 0, chunkBuffer.Length);
}
st.Close();
}
try
{
HttpWebResponse resp = (HttpWebResponse)request.GetResponse();
Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);
resp.Close();
}
catch (System.Exception)
{
//TODO: error handling here.
}
/* END: Solution with chunks */
}

Send handled Exception report using HockeyApp(Windows)

I am trying to Send Error reports with hockeyapp without having to let the whole app crash and burn. I dont think the HockeyApp.WPF library has this capability, so I started to mess around with implementing my own CrashHandler.
This quickly got confusing and very hackey. Does anyone have any code examples for this? At my current rate I will end up reproducing half of the HockeyApp Library, so I would appreciate some help.
I am not posting my code because I don't think it will help and its too much.
Edit: now I will post a shortened version of code that doesnt seem to work:
private static void HandleException(Exception e) {
try {
string crashID = Guid.NewGuid().ToString();
String filename = String.Format("{0}{1}.log", CrashFilePrefix, crashID);
CrashLogInformation logInfo = new CrashLogInformation() {
PackageName = Application.Current.GetType().Namespace,
Version = ((HockeyClient)HockeyClient.Current).VersionInfo,
OperatingSystem = Environment.OSVersion.Platform.ToString(),
Windows = Environment.OSVersion.Version.ToString() + Environment.OSVersion.ServicePack,
Manufacturer = "",
Model = ""
};
ICrashData crash = ((HockeyClient)HockeyClient.Current).CreateCrashData(e);
using (FileStream stream = File.Create(Path.Combine(GetPathToHockeyCrashes(), filename))) {
crash.Serialize(stream);
stream.Flush();
}
}
catch (Exception ex) {
((HockeyClient)HockeyClient.Current).HandleInternalUnhandledException(ex);
}
}
private static string GetPathToHockeyCrashes() {
string path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (!path.EndsWith("\\")) { path += "\\"; }
path += "HockeyCrashes\\";
if (!Directory.Exists(path)) { Directory.CreateDirectory(path); }
return path;
}
private struct CrashLogInformation {
/// <summary>
/// name of app package
/// </summary>
public string PackageName;
/// <summary>
/// version of app
/// </summary>
public string Version;
/// <summary>
/// os
/// </summary>
public string OperatingSystem;
/// <summary>
/// device manufacturer
/// </summary>
public string Manufacturer;
/// <summary>
/// device model
/// </summary>
public string Model;
/// <summary>
/// product id of app
/// </summary>
public string ProductID;
/// <summary>
/// windows phone version
/// </summary>
public string WindowsPhone;
/// <summary>
/// windows version
/// </summary>
public string Windows;
}
I was able to make a post after formatting the logs as described in the documentation for the crashes/upload endpoint(http://support.hockeyapp.net/kb/api/api-crashes#-u-post-api-2-apps-app_id-crashes-upload-u-).
Although I ended up hitting "crashes/", which from my understanding is different from crashes/upload(Therefore this is a solution that is hitting an undocumented endpoint).
private static readonly string HOCKEYUPLOADURL = #"https://rink.hockeyapp.net/api/2/apps/{0}/crashes/";
private static async Task SendDataAsync(String log, String userID, String contact, String description) {
string rawData = "";
rawData += "raw=" + Uri.EscapeDataString(log);
if (userID != null) {
rawData += "&userID=" + Uri.EscapeDataString(userID);
}
if (contact != null) {
rawData += "&contact=" + Uri.EscapeDataString(contact);
}
if (description != null) {
rawData += "&description=" + Uri.EscapeDataString(description);
}
WebRequest request = WebRequest.Create(new Uri(String.Format(HOCKEYUPLOADURL, HOCKEYAPPID)));
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
using (Stream stream = await request.GetRequestStreamAsync()) {
byte[] byteArray = Encoding.UTF8.GetBytes(rawData);
stream.Write(byteArray, 0, rawData.Length);
stream.Flush();
}
try {
using (WebResponse response = await request.GetResponseAsync()) { }
}
catch (WebException e) {
WriteLocalLog(e, "HockeyApp SendDataAsync failed");
}
}

Categories

Resources