Logger WPF en tiempo real de una web Asp.net MVC con SignalR y NLog

viernes, 13 de diciembre de 2013


En un post anterior ya vimos una introducción a SignalR y un ejemplo de un chat, en este post comenté que en SignalR también era posible un cliente no solo en javascript sino también en un proyecto .Net.

Pues bien en esta ocasión vamos a crear un proyecto sencillo de Asp.Net MVC que levantará un servidor SignalR y vamos a crear un target personalizado de NLog que mediante SignalR que envíe los mensajes a los clientes que haya conectados, para este ejemplo vamos a crear un logger sencillo en WPF como cliente que se actualizará en tiempo real.

Proyecto Asp.net MVC

Creamos un proyecto Asp.net MVC y ahora como este ejemplo lo estoy haciendo con Visual Studio 2013, añadiendo una clase Hub al proyecto web, automáticamente se añaden al proyecto las referencias necesarias de SignalR 2.0, si lo hiciera desde Visual Studio 2012 se añadirían las de la versión 1.0 de esta forma. Para añadirlas manualmente se puede hacer mediante NuGet como en el post de introducción  a SignalR.

Creamos la clase LoggerHub, pero sin ningún método, ya que en nuestro escenario los clientes no van a invocar ningún método del Hub, sino que sera el servidor el que mandará una notificación a los clientes conectados al hub e invocara un método definido en ellos. Por lo tanto en nuestro escenario el cliente se conecta al Hub pero la comunicación es de servidor a cliente.



public class LoggerHub : Hub
{
}


Añadimos la clase Startup para que OWIN levante el servicio en la ruta /signalR/hubs. Si nuestra clase Startup se llama así tal cual, el motor lo detecta directamente porque por convención ese es el nombre esperado, si le damos otro nombre, tenemos que indicar cual es la clase de inicio para SignalR. Podemos hacerlo mediante config o mediante un atributo de assembly, aquí os dejo un enlace donde lo explica con detalle.



public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.MapSignalR();
    }
}


Ahora ya si arrancamos la web podemos acceder a la ruta de signalR y vemos que se ha generado un javascript dinámicamente que hace de proxieentre el servidor y el cliente, la ruta es http://localhost:12218/signalR/hubs


Añadimos NLog a nuestro proyecto


En NLog hay targets que vienen por defecto como escribir en fichero, email, etc.. pero también podemos crearnos un target personalizado que haga lo que nosotros queramos cuando le llegan eventos de log. Así que creamos una clase SignalRTarget que va a heredar de TargetWithLayout, sobreescribimos el método write. En este método accedemos al contexto del LoggerHub para poder acceder a los clientes conectados al hub y así poder invocar un método de los clientes.

[Target("SignalR")]
public class SignalRTarget:TargetWithLayout
{
    protected override void Write(LogEventInfo logEvent)
    {
        var hubContext = GlobalHost.ConnectionManager.GetHubContext();

        if (hubContext != null)
            hubContext.Clients.All.write(Layout.Render(logEvent));
    }
}

Ya tenemos creada la arquitectura para poder escribir en un log en el lado de servidor y que este se lo comunique a los clientes conectados. Vamos a crear funcionalidad en la web y a escribir trazas en el log.

Creamos el HomeController

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

creamos la vista para la página home. La vista contiene un form que invoca la pagina search
@{
    ViewBag.Title = "Home Page";
}

Home


Creamos el SearchController. En el controller nos creamos el logger y creamos las trazas.
public class SearchController : Controller
{
    //
    // GET: /Search/
    public ActionResult Index(string q)
    {
        NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

        logger.Info("Begin Search");

        logger.Info("Search results for " + q);

        ViewBag.SearchText = q;

        logger.Info("End Search");

        return View();
    }
}

Creamos la vista index para el search

@{
    ViewBag.Title = "Index";
}

Search Results for: @ViewBag.SearchText


Creamos el fichero de configuracón NLog.config para que use como target nuestra clase que hemos creado, tenemos que añadir el tag add assembly para que pueda reconocer el target.

  
    
  
  
    
  

  
    
  


Proyecto WPF

Creamos un proyecto WPF que será nuestro logger cliente. Añadimos desde NuGet el paquete de SignalR para un cliente .Net


Creamos las vista xaml, donde tenemos un botón para conectar con el servidor SignalR junto con un TextBlock que nos va a mostrar el estado de la conexión. Debajo añadimos un ListBox donde se irán escribiendo los mensajes que nos llegan.
<Window x:Class="LoggerSignalR.WPFClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
    <StackPanel Orientation="Vertical">
        <StackPanel Orientation="Horizontal">
            <Button Margin="10" Padding="5" Click="Button_Click">Conectar</Button>
            <TextBlock Name="txtSate" Width="100" Margin="0 10" Padding="5"/>
        </StackPanel>
        <ListBox Name="lstMessages" Height="250">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</Window>

Modificamos el código c# de la vista de forma que en el constructor enlazamos el ListBox con nuestra colección observable de mensajes, para que el ListBox al estar enlazado se refresque automáticamente. En el evento click del botón conectar iniciamos conexión con el servidor SignalR y Nos suscribimos dentro del método conectar el evento write que se dispara cuando el servidor nos manda un nuevo mensaje lo añadimos al listbox y al evento StateChanged para actualizar el estado de la conexión en el TextBlock.
public partial class MainWindow : Window
{
    ObservableCollection _messages = new ObservableCollection();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        lstMessages.ItemsSource = _messages;       
    }

    private async Task Connect()
    {
        HubConnection hubConnection = new HubConnection("http://localhost:12218/signalr");

        IHubProxy logHubProxy = hubConnection.CreateHubProxy("LoggerHub");

        //method LogEvent to be invoke from server
        logHubProxy.On("Write", logMessage =>
        {
            Application.Current.Dispatcher.Invoke(() => _messages.Add(logMessage));
        });

        hubConnection.StateChanged += change =>
            {
                Application.Current.Dispatcher.Invoke(()=>txtSate.Text = change.NewState.ToString());    
            };

        //connect
        await hubConnection.Start();
    }

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        await Connect();
    }
}

Ya tenemos nuestro ejemplo de logger en tiempo real con Nlog y SignalR.


Código fuente

Libros Relacionados

Introducción a ASP.NET SignalR es de Jose M. Aguilar.
Signalr: Realtime Application Development

Pro ASP.Net Signalr: Real-Time Communication in .Net with Signalr 2.0

SignalR Programming in Microsoft ASP.NET

3 comentarios:

  1. Muy interesante.

    No sé si encajaría SignalR en algo así:

    Windows Forms (también pudiera ser WPF, VSIX o Addin VS) llama a Servicio WCF.

    El Servicio WCF ejecuta cierto proceso complejo (bien acceso a base de datos o bien llamada a powershell.exe).

    La idea es la notificación al cliente Windows Forms.

    No sé si encaja ahí bien SignalR, y cómo sería la implementación ?

    Cada llamada a un método de WCF se tendría un Hub signalR, vamos se crearía y dispose en cada llamada ?? Lo veo confuso.

    Saludos.

    ResponderEliminar
  2. Pues para serte sincero, he probado SignalR en varios entornos como WinForms, WPF, Asp.Net y WCF con varias combinaciones entre cliente, servidor y la única vez que tuve problemas fue cuando intente levantar servidor SignalR en un servicio WCF, finalmente en esa ocasión lo moví a Asp.Net así que no te puedo ayudar mucho con el caso de WCF.

    Saludos

    ResponderEliminar