WebSocketAccept Options

Aug 4, 2014 at 11:43 AM
Edited Aug 4, 2014 at 11:44 AM
Currently I am using the WebSocket extension 0.4.0 to create a middleware that looks though a dictionary of urls and extracts a class type to create a new instance of that class.

However I cannot figure out how to send that instance in to the WebSocketAccept method via the dictionary argument as everything I add to the context does not appear in the dictionary that is pass to the WebSocketAccept method.

Example:
        /// <summary>
        /// Function for HTTP server to invoke with a OWIN request.
        /// </summary>
        /// <param name="context">An OWIN Context.</param>
        /// <returns></returns>
        public async override Task Invoke(IOwinContext context)
        {
            string requestedRoute = context.Request.Path.ToString();
            Type controller;

            if (_services.TryGetValue(requestedRoute, out controller))
            {
                IWebSocketController instance = _dispatcher.GetService(controller);
                context.Environment.Add(OwinConstants.WebSocketController, instance);
                context.Set<IWebSocketController>(OwinConstants.WebSocketController, instance);

                bool accept = true;
                if (_handlerStack != null && _handlerStack.HasStack)
                {
                    HttpContent requestContent = CreateStreamedRequestContent(context.Request);
                    HttpRequestMessage request = CreateRequestMessage(context.Request, requestContent);
                    HttpResponseMessage response = (HttpResponseMessage)null;
                    try
                    {
                        response = await _handlerStack.ProcessStackAsync(request, context.Get<CancellationToken>(OwinConstants.CallCancelled));
                        if (response != null)
                        {
                            accept = false;
                        }
                    }
                    finally
                    {
                        request.Dispose();
                        if (response != null)
                            response.Dispose();
                    }
                }

                if (accept)
                {
                    WebSocketAccept acceptWebSocket = context.Get<WebSocketAccept>(OwinConstants.WebSocketAccept);
                    Dictionary<string, object> param = new Dictionary<string, object>();
                    param.Add(OwinConstants.WebSocketController, controller);

                    if (acceptWebSocket != null)
                        acceptWebSocket(param, AcceptCallbackAsync);
                }
            }
            else
            {
                context.Response.StatusCode = 404;
                context.Environment.Add(OwinConstants.NoRouteMatched, true);
                await Next.Invoke(context);
            }
        }

        /// <summary>
        /// Call-back for when WebSocket Accept has been completed.
        /// </summary>
        /// <param name="env">OWIN WebSocket environment server keys.</param>
        /// <returns></returns>
        public async Task AcceptCallbackAsync(IDictionary<string, object> env)
        {
            WebSocketContext context = env[OwinConstants.WebSocketContext] as WebSocketContext;
            IWebSocketController controller = env[OwinConstants.WebSocketController] as IWebSocketController;
            CancellationToken cancellationToken = env[OwinConstants.WebSocketCallCancelled] == null ? CancellationToken.None : (CancellationToken)env[OwinConstants.WebSocketCallCancelled];

            try
            {
                controller.Initialise(new WebSocketConnection(context, cancellationToken));
                await controller.ExecuteAsync();
            }
            finally
            {
                controller.Dispose();
            }
        }
The above code gives me a Web.Api style framework and I can reuse message handlers, however as before I have no way of passing information to the accept, now I can move the service class lookup to the WebSocketAccept method however this feels wrong...

So to sum up how can I pass options in to the WebSocketAccept method?
Coordinator
Aug 4, 2014 at 2:42 PM
You're trying to pass data from Invoke to AcceptCallbackAsync? You're right, there's no direct way to do that. Your best option is via an additional type and/or closure.
        /// <summary>
        /// Function for HTTP server to invoke with a OWIN request.
        /// </summary>
        /// <param name="context">An OWIN Context.</param>
        /// <returns></returns>
        public async override Task Invoke(IOwinContext context)
        {
            string requestedRoute = context.Request.Path.ToString();
            Type controller;

            if (_services.TryGetValue(requestedRoute, out controller))
            {
                IWebSocketController instance = _dispatcher.GetService(controller);
                context.Environment.Add(OwinConstants.WebSocketController, instance);
                context.Set<IWebSocketController>(OwinConstants.WebSocketController, instance);

                bool accept = true;
                if (_handlerStack != null && _handlerStack.HasStack)
                {
                    HttpContent requestContent = CreateStreamedRequestContent(context.Request);
                    HttpRequestMessage request = CreateRequestMessage(context.Request, requestContent);
                    HttpResponseMessage response = (HttpResponseMessage)null;
                    try
                    {
                        response = await _handlerStack.ProcessStackAsync(request, context.Get<CancellationToken>(OwinConstants.CallCancelled));
                        if (response != null)
                        {
                            accept = false;
                        }
                    }
                    finally
                    {
                        request.Dispose();
                        if (response != null)
                            response.Dispose();
                    }
                }

                if (accept)
                {
                    WebSocketAccept acceptWebSocket = context.Get<WebSocketAccept>(OwinConstants.WebSocketAccept);
                    Dictionary<string, object> param = new Dictionary<string, object>();
                    var callbackState = new CallbackState() { Controller = controller };

                    if (acceptWebSocket != null)
                        acceptWebSocket(param, callbackState.AcceptCallbackAsync);
                }
            }
            else
            {
                context.Response.StatusCode = 404;
                context.Environment.Add(OwinConstants.NoRouteMatched, true);
                await Next.Invoke(context);
            }
        }

public class CallbackState
{
       public IWebSocketController Controller { get; set }

        /// <summary>
        /// Call-back for when WebSocket Accept has been completed.
        /// </summary>
        /// <param name="env">OWIN WebSocket environment server keys.</param>
        /// <returns></returns>
        public async Task AcceptCallbackAsync(IDictionary<string, object> env)
        {
            WebSocketContext context = env[OwinConstants.WebSocketContext] as WebSocketContext;
            CancellationToken cancellationToken = env[OwinConstants.WebSocketCallCancelled] == null ? CancellationToken.None : (CancellationToken)env[OwinConstants.WebSocketCallCancelled];

            try
            {
                Controller.Initialise(new WebSocketConnection(context, cancellationToken));
                await Controller.ExecuteAsync();
            }
            finally
            {
                Controller.Dispose();
            }
        }
}
Marked as answer by SilentBlueCode on 8/4/2014 at 12:54 PM
Aug 4, 2014 at 7:47 PM
Hi Tratcher,

Thank you, so much for your help! What you provided was exactly what I needed, I had never before come across the idea of wrapping the callback in to an object in its self and accessing properties instead of being able to use args or a state object directly though a delegate...

Here's what I ended up with
/// <summary>
/// Function for HTTP server to invoke with a OWIN request.
/// </summary>
/// <param name="context">An OWIN Context.</param>
/// <returns></returns>
public async override Task Invoke(IOwinContext context)
{
    WebSocketAccept acceptWebSocket = context.Get<WebSocketAccept>(OwinConstants.WebSocketAccept);
    if (acceptWebSocket == null)
    {
        // Not a WebSocket request so pass to the next middleware.
        await Next.Invoke(context);
    }
    else
    {
        string requestedRoute = context.Request.Path.ToString();
        Type controllerType;

        if (_services.TryGetValue(requestedRoute, out controllerType))
        {
            IWebSocketController controllerInstance = _dispatcher.GetService(controllerType);
            context.Set<IWebSocketController>(OwinConstants.WebSocketController, controllerInstance);

            bool accept = true;
            if (_handlerStack != null && _handlerStack.HasStack)
            {
                HttpContent requestContent = CreateStreamedRequestContent(context.Request);
                HttpRequestMessage request = CreateRequestMessage(context.Request, requestContent);
                HttpResponseMessage response = (HttpResponseMessage)null;
                try
                {
                    response = await _handlerStack.ProcessStackAsync(request, context.Get<CancellationToken>(OwinConstants.CallCancelled));
                    if (response != null)
                    {
                        accept = false;
                        //TODO: Process response message and/or send back to the client...
                    }
                }
                finally
                {
                    request.Dispose();
                    if (response != null)
                        response.Dispose();
                }
            }

            if (accept)
            {
                WebSocketAcceptState callbackState = new WebSocketAcceptState(controllerInstance);
                acceptWebSocket(null, callbackState.AcceptCallbackAsync);
            }
        }
        else
        {
            context.Response.StatusCode = 404;
            context.Set<bool>(OwinConstants.NoRouteMatched, true);
            await Next.Invoke(context);
        }
    }
}

/// <summary>
/// Represents data for a WebSocket accept callback.
/// </summary>
internal class WebSocketAcceptState
{
    /// <summary>
    /// Creates a default instance of the class.
    /// </summary>
    internal WebSocketAcceptState()
    {
    }

    /// <summary>
    /// Creates a default instance of the class with the specified controller.
    /// </summary>
    /// <param name="controller">A WebSocket Controller instance.</param>
    internal WebSocketAcceptState(IWebSocketController controller)
        : this()
    {
        Controller = controller;
    }

    /// <summary>
    /// Gets or sets the controller to execute once the connection has been accepted.
    /// </summary>
    internal IWebSocketController Controller { get; set; }

    /// <summary>
    /// Call-back for when WebSocket Accept has been completed.
    /// </summary>
    /// <param name="env">OWIN WebSocket environment server keys.</param>
    /// <returns></returns>
    internal async Task AcceptCallbackAsync(IDictionary<string, object> env)
    {
        if (Controller == null)
            throw new NullReferenceException("Controller cannot be null.");

        WebSocketContext context = env[OwinConstants.WebSocketContext] as WebSocketContext;
        CancellationToken cancellationToken = env[OwinConstants.WebSocketCallCancelled] == null ? CancellationToken.None : (CancellationToken)env[OwinConstants.WebSocketCallCancelled];

        try
        {
            Controller.Initialise(new WebSocketConnection(context, cancellationToken));
            await Controller.ExecuteAsync();
        }
        finally
        {
            Controller.Dispose();
        }
    }
}