Click here to Skip to main content
15,867,308 members
Articles / Web Development / ASP.NET / ASP.NET Core

ASP.NET Core: Building a Real-Time Online Poll System with SignalR 2, jQuery, EF Core, Core MVC and Web API 2

Rate me:
Please Sign up or sign in to vote.
4.85/5 (29 votes)
5 Aug 2016CPOL18 min read 129.1K   1.5K   56   57
This series of article will walk you through on building a simple Online Poll System with real-time updates using SignalR 2, jQuery, Core EF, Core MVC and Web API 2.We will take a look at how each of the technologies will be used within ASP.NET Core 1.0.

Introduction

This series of article will walk you through on building a simple Online Poll System with real-time updates using SignalR 2, jQuery, Core EF, Core MVC and Web API 2.

We will take a look at how each of the technologies will be used within ASP.NET Core 1.0 context by building an application from scratch.

In this particular series, we will create the core foundation of the application starting from creating a new database, creating a new ASP.NET Core project, and setting up the Data Access using EF Core. We will also create a simple page for adding a new poll and a page for displaying the newly added poll in real-time using ASP.NET SignalR 2.

What you will learn:

  • Creating a Database using SQL Server 2014
  • Creating an ASP.NET Core Project
  • Integrating Entity Framework Core 1.0
  • Creating Entity Models from Existing Database (Reverse Engineer)
  • Registering DBContext using Dependency Injection
  • Creating a Poll Management Page using Core MVC
    • Adding ViewModels
    • Adding the PollManager Repository
    • Registering the PollManager Repository
    • Adding a Controller
    • Adding a View
    • Enabling MVC and Developer Diagnostics
    • Running the Application
  • Integrating ASP.NET SignalR 2
  • Integrating Client-Side Packages - jQuery and SignalR Scripts
  • Creating a Middleware for Mapping SignalR
  • Adding SignalR to the Pipeline
  • Creating a Hub
  • Invoking a Hub Method
  • Creating the Web API
  • Displaying the Poll

Let's Get Started!

If you're ready to explore ASP.NET Core and get your hands dirty, then let's get started!

Database Creation

Open MS SQL Server Management Studio and run the following SQL script below to create the database and tables:

SQL
CREATE DATABASE ASPNETCoreDemoDB  
GO

USE [ASPNETCoreDemoDB]  
GO


CREATE TABLE [dbo].[Poll](  
    [PollID] [int] IDENTITY(1,1) NOT NULL,
    [Question] [nvarchar](300) NOT NULL,
    [Active] [bit] NOT NULL,
 CONSTRAINT [PK_Poll] PRIMARY KEY CLUSTERED 
(
    [PollID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE TABLE [dbo].[PollOption](  
    [PollOptionID] [int] IDENTITY(1,1) NOT NULL,
    [PollID] [int] NOT NULL,
    [Answers] [nvarchar](200) NOT NULL,
    [Vote] [int] NOT NULL,
 CONSTRAINT [PK_PollOption] PRIMARY KEY CLUSTERED 
(
    [PollOptionID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [dbo].[PollOption]  WITH CHECK ADD  CONSTRAINT [FK_PollOption_PollOption] FOREIGN KEY([PollID])  
REFERENCES [dbo].[Poll] ([PollID])  
GO

ALTER TABLE [dbo].[PollOption] CHECK CONSTRAINT [FK_PollOption_PollOption]  
GO  

The SQL script above should create the ASPNETCoreDemoDB database with the following tables:

Figure 1: Poll tables

Creating an ASP.NET Core Project

Our next step is to create the poll management page where we can add polls to be available for users to cast their votes.

Fire-up Visual Studio 2015 and create a new ASP.NET Core Web Application project just like in the figure below:


Figure 2: New ASP.NET Core Web Application

Name your project to whatever you like, but in this demo, I named it as “ASPNETCoreSignalRDemo”. Click OK and then select “Web API” within ASP.NET Core templates as shown in the following figure below:


Figure 3: Web API Template

Just click OK to let Visual Studio generate the required files needed for us to run the App.

I will not elaborate on the details about the files generated in ASP.NET Core app and the significant changes. If you are new to ASP.NET Core then I would really recommend you to head over to my previous articles below:

Now create the “Models” folder within the root of your application and under that folder, add the folders: “DB” and “ViewModels”. Your solution should now look something like below:


Figure 4: The Models Folder

The “DB" folder will contain our data access. In this demo, we are going to use Entity Framework Core 1.0 as our data access mechanism. This could mean that we will not be using the old EF designer to generate models for us because EF designer (EDMX) isn’t supported in ASP.NET Core 1.0.

The “ViewModels” folder will contain a set of models that we are going to use in the View. These models are just classes that house some properties that we only need for our View, thus making the data-transfer much lighter.

Integrating Entity Framework Core 1.0

ASP.NET Core was designed to be light-weight, modular and pluggable. This allows us to plug-in components that are only required for our project. To put it in other words, we need to add the Entity Framework Core package in our ASP.NET Core app because we are going to need it. For more details about EF Core then check out: Announcing Entity Framework Core 1.0

There are two ways to add packages in ASP.NET Core; you could either use the “project.json” file to take advantage of the intelliSense feature, or via NuGet Package Manager (NPM). In this demo we are going to use NPM so you can have a visual reference.

Now, right-click on the root of your application and then select Manage NuGet Packages. In the search bar type in “Microsoft.EntityFrameworkCore.SqlServer”. It should result to something like this:

Figure 5: Manage NuGet Package

Select “Microsoft.EntityFrameworkCore.SqlServer” and click Install. Just follow the wizard instructions until it completes the installation.

We are going to use Database-Approach to work with existing database and in order to do that, we need to install the additional packages below:

  • Microsoft.EntityFrameworkCore.Tools (v1.0.0-preview2-final)
  • Microsoft.EntityFrameworkCore.SqlServer.Design (v1.0.0)

Now go ahead and install them via package.json file or NPM as shown in the figures below:

Figure 6: Adding Microsoft.EntityFrameworkCore.Tools package

Figure 7: Adding Microsoft.EntityFrameworkCore.SqlServer.Design package

When it’s done restoring all the required packages, you should be able to see them added to your project references as shown in the figure below:


Figure 8: EF Core Packages restored

Open your project.json file and add the Microsoft.EntityFrameworkCore.Tools item under the tools section just like below:

<code class="language-javascript">"tools": {
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
  },
</code>

Creating Entity Models from Existing Database

Now, it’s time for us to create the EF models based on our existing database that we have just created earlier.

As of this writing, there are two ways to generate models from our existing database:

Option 1: Using Package Manager Console

  1. Go to Tools –> NuGet Package Manager –> Package Manager Console
  2. And then run the following command to create a model from the existing database
<code class="language-csharp">Scaffold-DbContext "Server=WIN-EHM93AP21CF\SQLEXPRESS;Database=ASPNETCoreDemoDB;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models/DB  
</code>

Option 2: Using Command Window

Option 1 doesn’t seem to work for me for some reasons that I’m not aware of. So, I tried Option 2 and it worked pretty for me. Here’s what I did:

  1. Go to the root folder of your application where the project.json is located. In this case the “ASPNETCoreSignalRDemo”.
  2. Do a Shift + Right Click and select “Open command window here”
  3. Then run the following script:
<code class="language-csharp">dotnet ef dbcontext scaffold "Server=WIN-EHM93AP21CF\SQLEXPRESS;Database=ASPNETCoreDemoDB;Integrated Security=True;" Microsoft.EntityFrameworkCore.SqlServer --output-dir Models/DB  
</code>
Quote:

Note that you need to change the Server value based on your database server configuration. If you are using a different database name, you would need to change the Database value too.

That command will generate models from database within Models/DB folder. Here’s the screenshot below:


Figure 9: EF Generated Models

Quote:

Notes:

  • If you are still getting errors then you might want to upgrade the PowerShell to version 5. You can download it here.
  • You need to change the value of Server and Database in your connection string based on your server configuration. Here’s the actual code generated.

ASPNETCoreDemoDBContext Class

C#
using System;  
using Microsoft.EntityFrameworkCore;  
using Microsoft.EntityFrameworkCore.Metadata;

namespace ASPNETCoreSignalRDemo.Models.DB  
{
    public partial class ASPNETCoreDemoDBContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
          optionsBuilder.UseSqlServer(@"Server=WIN-EHM93AP21CF\SQLEXPRESS;Database=ASPNETCoreDemoDB;Integrated Security=True;");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Poll>(entity =>
            {
                entity.Property(e => e.PollId).HasColumnName("PollID");

                entity.Property(e => e.Question).HasMaxLength(300);
            });

            modelBuilder.Entity<PollOption>(entity =>
            {
                entity.Property(e => e.PollOptionId).HasColumnName("PollOptionID");

                entity.Property(e => e.Answers)
                    .IsRequired()
                    .HasMaxLength(200);

                entity.Property(e => e.PollId).HasColumnName("PollID");

                entity.HasOne(d => d.Poll)
                    .WithMany(p => p.PollOption)
                    .HasForeignKey(d => d.PollId)
                    .OnDelete(DeleteBehavior.Restrict)
                    .HasConstraintName("FK_PollOption_PollOption");
            });
        }
        public virtual DbSet<Poll> Poll { get; set; }
        public virtual DbSet<PollOption> PollOption { get; set; }
    }
}

Poll Class

C#
using System.Collections.Generic;

namespace ASPNETCoreSignalRDemo.Models.DB  
{
    public partial class Poll
    {
        public Poll()
        {
            PollOption = new HashSet<PollOption>();
        }

        public int PollId { get; set; }
        public string Question { get; set; }
        public bool Active { get; set; }

        public virtual ICollection<PollOption> PollOption { get; set; }
    }
}

PollOption Class

C#
using System;

namespace ASPNETCoreSignalRDemo.Models.DB  
{
    public partial class PollOption
    {
        public int PollOptionId { get; set; }
        public int PollId { get; set; }
        public string Answers { get; set; }
        public int Vote { get; set; }

        public virtual Poll Poll { get; set; }
    }
}

If you have noticed, the models generated are created as partial classes. This means that you can extend them by creating another partial class for each of the entity/model classes when needed.

Registering DBContext using Dependency Injection

The next step is to register our ASPNETCoreDemoDBContext class using Dependency Injection. To follow the ASP.NET Core configuration pattern, we will move the database provider configuration to Startup.cs. To do this, just follow these steps:

  • Open Models\DB\ASPNETCoreDemoDBContext.cs file
  • Remove the OnConfiguring() method and add the following code below
C#
public ASPNETCoreDemoDBContext(DbContextOptions<ASPNETCoreDemoDBContext> options)  
: base(options)
{ }
  • Open appsettings.json file and add the following script for our database connection string below:
C#
"ConnectionStrings": {
   "PollSystemDatabase": "Server=WIN-EHM93AP21CF\\SQLEXPRESS;Database=ASPNETCoreDemoDB;Integrated Security=True;"
}
  • Open Startup.cs
  • Add the following namespaces at very top
C#
using ASPNETCoreSignalRDemo.Models;  
using ASPNETCoreSignalRDemo.Models.DB;  
using Microsoft.EntityFrameworkCore;  
  • Add the following lines of code within ConfigureServices() method
C#
public void ConfigureServices(IServiceCollection services){  
     // Add ASPNETCoreDemoDBContext services.
     services.AddDbContext<ASPNETCoreDemoDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("PollSystemDatabase")));            
    // Add MVC services.
    services.AddMvc();
 }

That’s it. Now we’re ready to work with data. The next step is to create the application.

For more information see: ASP.NET Core Application to Existing Database (Database First)

Creating the Poll Management Page

The first thing that we are going to build in our application is the Poll management page. In this page, we will create a form that allows us to add a new poll in the database using MVC.

Adding ViewModels

Now let’s create the “AddPollViewModel”. Create the following class under ViewModels folder:

using System.ComponentModel.DataAnnotations;

namespace ASPNETCoreSignalRDemo.Models.ViewModels  
{
    public class AddPollViewModel
    {
        [Required(ErrorMessage = "Question is required.")]   
        public string Question { get; set; }
        [Required(ErrorMessage = "Answer is required.")]
        public string Answer { get; set; }    
    }
}

The class above contains two properties that are decorated with the Required attribute using Data Annotation to enforce pre-defined validation rules.

Create another new class under the ViewModels folder and name it as “PollDetailsViewModel”. Here’s the code for the PollDetailsViewModel class:

C#
using System.Collections.Generic;

namespace ASPNETCoreSignalRDemo.Models.ViewModels  
{
    public class PollDetailsViewModel
    {
        public int PollID { get; set; }
        public string Question { get; set; }
        public IEnumerable<DB.PollOption> PollOption { get; set; }
    }
}

The ViewModel classes above will be used in our View later.

Adding the PollManager Repository

Create a new class/interface file under the Models folder and name it as “IPollManager”. Update the code within that file so it would look similar to the following code below:

C#
using System.Collections.Generic;  
using ASPNETCoreSignalRDemo.Models.ViewModels;

namespace ASPNETCoreSignalRDemo.Models  
{
    public interface IPollManager
    {
        bool AddPoll(AddPollViewModel pollModel);
        IEnumerable<PollDetailsViewModel> GetActivePoll();
    }
}

If you have noticed, we are defining an interface instead of a class. This interface will be injected in the Controller so we will only need to talk to the interface rather than the actual implementation of our repository.

Next is we are going to create a concrete class that implements the IPollManager interface. Right-click on the Models folder and create a new class. Name the class as “PollManager” and then add the following code below:

C#
using System;  
using System.Collections.Generic;  
using System.Linq;  
using ASPNETCoreSignalRDemo.Models.DB;  
using ASPNETCoreSignalRDemo.Models.ViewModels;  
using Microsoft.EntityFrameworkCore;

namespace ASPNETCoreSignalRDemo.Models  
{
    public class PollManager : IPollManager
    {
        private readonly ASPNETCoreDemoDBContext _db;
        public PollManager(ASPNETCoreDemoDBContext context)
        {
            _db = context;
        }

        public IEnumerable<PollDetailsViewModel> GetActivePoll()
        {
            if (_db.Poll.Any())
                return _db.Poll.Include(o => o.PollOption).Where(o => o.Active == true)
                    .Select(o => new PollDetailsViewModel {
                        PollID = o.PollId,
                        Question = o.Question,
                        PollOption = o.PollOption
                    });

            return Enumerable.Empty<PollDetailsViewModel>();
        }

        public bool AddPoll(AddPollViewModel pollModel)
        {

            using (var dbContextTransaction = _db.Database.BeginTransaction())
            {
                try
                {
                    var answers = pollModel.Answer.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
                    Poll poll = new Poll();
                    poll.Question = pollModel.Question;
                    poll.Active = true;
                    _db.Poll.Add(poll);
                    _db.SaveChanges();

                    foreach (var answer in answers)
                    {
                        PollOption option = new PollOption();
                        option.PollId = poll.PollId;
                        option.Answers = answer;
                        option.Vote = 0;
                        _db.PollOption.Add(option);
                        _db.SaveChanges();
                    }

                    dbContextTransaction.Commit();

                }
                catch
                {
                    //TO DO: log error here
                    dbContextTransaction.Rollback();
                }
            }

            return true;
        }
    }
}

The PollManager class handles all database operations for our Poll system. The purpose of this class is to separate the actual data operation logic from the controller and to have a central class for handling create, update, fetch and delete (CRUD) operations.

At the moment, the PollManager class contains two methods: The GetActivePoll() method gets the active poll from the database using LINQ syntax and returns an IEnumerable of PollDetailsViewModel. The AddPoll() method adds a new poll data to the database. What it does is it adds a new record to the Poll table and then adds the associated records to the PollOption table by looping through the answers.

If you have noticed, I used a simple transaction within that method. This is because the table PollOption is related to the Poll table and we need to make sure that we only commit changes to the database if the operation for each table is successful. The Database.BeginTransaction() is only available in EF 6 onwards.

Registering the PollManager Repository

Now add the following line below within the Configure() method in Startup.cs file:

<code class="language-csharp">services.AddScoped<IPollManager, PollManager>();  
</code>

We need to register the IPollManager in the service container because the PollManager class requests an ASPNETCoreDemoDBContext object in its constructor. The container is responsible for resolving all of the dependencies in the graph and returning the fully resolved service. For more information, read on: Dependency Injection

Adding a Controller

Right-click on the Controllers folder and then select Add >New Item >MVC Controller Class. Name the class as “HomeController” and update the code within that class so it would look similar to this:

C#
using Microsoft.AspNetCore.Mvc;  
using ASPNETCoreSignalRDemo.Models;  
using ASPNETCoreSignalRDemo.Models.ViewModels;

namespace ASPNETCoreSignalRDemo.Controllers  
{
    public class HomeController : Controller
    {
        private readonly IPollManager _pollManager;

        public HomeController(IPollManager pollManager)
        {
            _pollManager = pollManager;
        }

        public IActionResult Index()
        {
            return View();
        }

        public IActionResult AddPoll()
        {
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult AddPoll(AddPollViewModel poll)
        {
            if (ModelState.IsValid) {
                if (_pollManager.AddPoll(poll))
                    ViewBag.Message = "Poll added successfully!";
            }
            return View(poll);
        }
    }
}

The class above uses constructor injection to gain access to the methods defined within PollManager class. The Index() and AddPoll() methods simply return their corresponding Views and nothing else. The overload AddPoll() method is decorated with the [HttpPost] attribute which signifies that the method can only be invoked for POST requests. This method is where we actually handle the adding of new data to database if the model state is valid on posts.

Keep in mind that validation of active polls upon insert will not be covered in this article.

Adding a View

We will need to create a Views folder to follow the MVC convention. Under Views folder, add another folder and name it as “Home”. Right-click on the Home folder and then add the following new MVC View Page:

  • AddPoll.cshtml
  • Index.cshtml

The AddPoll View is where we add new polls to database. The Index View is where we display the polls. Our solution structure should now look something like this:


Figure 10: The Views folder

At this point we will just focus on the AddPoll first. Now update your AddPoll.cshtml file with the following markup below:

Razor
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model ASPNETCoreSignalRDemo.Models.ViewModels.AddPollViewModel

<h2>Add a New Poll</h2>

<form asp-controller="Home" asp-action="AddPoll" method="post" role="form">  
    <div>
        @{ 
            if (ViewBag.Message != null) { @ViewBag.Message; }
        }
        <div asp-validation-summary="All"></div>
        <div>
            <label asp-for="Question"></label>
            <div>
                <input asp-for="Question"/>
            </div>
        </div>
        <div>
            <label asp-for="Answer"></label>
            <div>
                <textarea asp-for="Answer"></textarea>
            </div>
        </div>
        <div>
            <div>
                <input type="submit" value="Add"/>
            </div>
        </div>
    </div>
</form>  

The markup above uses Tag Helpers to create and render HTML elements in the View. Tag Helpers is a new feature in ASP.NET Core MVC (a.k.a MVC 6) which we can use as optional replacement for the previous MVC HTML Helpers. For more information, see: Introduction to Tag Helpers

Enabling MVC and Developer Diagnostics

Debugging is mandatory in any form of programming so we need to enable diagnostics in our ASP.NET Core application to troubleshoot future issues that we might face during development. To do this, add the following dependency within project.json file:

<code class="language-javascript">"Microsoft.AspNetCore.Diagnostics": "1.0.0"
</code>

The line above adds ASP.NET Core middleware for exception handling, exception display pages, and diagnostics information. For more information, read on: Microsoft.AspNetCore.Diagnostics

For those who are not aware, Web API and MVC was merged in ASP.NET Core. So technically, MVC is already integrated since we have created an ASP.NET Core Web API project. But just for the completeness of this demo, I’m going to show how it will be configured. Now open Startup.cs and modify the Cofigure() method so it would look something like this:

C#
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)  
{
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
}

The first two lines enable logging. ASP.NET Core has built-in support for logging, and allows us to easily use our preferred logging framework’s functionality as well. For more information, read on: Logging

The app.UseDeveloperExceptionPage() enable us to see details of the exceptions in the page when our application throws an error. This method is available in Microsoft.AspNet.Diagnostic assembly.

The last line enables MVC with pre-defined default route. In this case, the /Home/Index would be our default page.

Running the Application

Now run the application just to test if our app is working by accessing the following http://localhost:5000/home/addpoll

Leave the entries as blank and try to hit Add. It should be display something like this with the validation error messages:

Figure 11: Output

Integrating ASP.NET SignalR 2

For this particular demo we are going to use SignalR 2 to automatically display the newly added poll in the page.

The default templates for ASP.NET Core 1.0 RTM apps only target "netcoreapp1.0". Unfortunately, ASP.NET SignalR is not yet supported in .NET Core 1.0 (RTM) so we need to switch to framework .NET 4.6.1 to be able to utilize the ASP.NET SignalR features. You can view the ASP.NET road map here.

Quote:

Note: By targeting to net461 framework, we will lose the cross-platform ability in our app. Anyway once SignalR 3 is supported, I'm sure it will just take few tweaks to integrate that so our app can run on multiple platforms by targeting to netcore1.0 framework. For the time being, let's keep rolling and see how we can use the power of SignalR within our ASP.NET Core app.

If you want to target multiple frameworks then we can do so by first adding the full .NET framework to the list in project.json (e.g. “net461” for .NET Framework 4.6.1) under "frameworks". Next is to move the "Microsoft.NETCore.App" dependency out of global "dependencies" section and into a new "dependencies" section under the "netcoreapp1.0" node in "frameworks". We could then add the dependency to ASP.NET SignalR under “net461”. So, our updated project.json “frameworks” section would now look something like this:

JavaScript
"frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "dnxcore50",
        "portable-net45+win8"
      ],
      "dependencies": {
        "Microsoft.NETCore.App": {
          "version": "1.0.0",
          "type": "platform"
        }
      }
    },
    "net461": {
      "dependencies": {
        "Microsoft.AspNet.SignalR": "2.2.0"
      }
    }
  },

To reduce the complexity, and for the simplicity of the demo, I have decided to target just the “net461” framework. So, I’ve removed the “netcoreapp1.0” dependency at this moment and added the following dependencies:

  • "Microsoft.AspNetCore.StaticFiles": "1.0.0"
  • "Microsoft.AspNet.SignalR": "2.2.0"
  • "Microsoft.AspNet.SignalR.Owin": "1.2.2"
  • "Microsoft.AspNetCore.Owin": "1.0.0"

Integrating Microsoft.AspNetCore.StaticFiles enables our app to serve JavaScript, CSS and Image files directly to clients. We need enable this because we are going to add the SignalR JavaScript file within wwwroot. The Microsoft.AspNet.SignalR is responsible for pulling in the server components and JavaScript client required to use SignalR in our application. Microsoft.AspNet.SignalR.Owin contains the OWIN components for ASP.NET SignalR. Microsoft.AspNetCore.Owin is component for OWIN middleware in an ASP.NET Core application, and to run ASP.NET Core middleware in an OWIN application.

Now, update your project.json file so it would like this:

{
  "dependencies": {
    "Microsoft.AspNetCore.Mvc": "1.0.0",
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
    "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
    "Microsoft.Extensions.Configuration.Json": "1.0.0",
    "Microsoft.Extensions.Logging": "1.0.0",
    "Microsoft.Extensions.Logging.Console": "1.0.0",
    "Microsoft.Extensions.Logging.Debug": "1.0.0",
    "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
    "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.0",
    "Microsoft.AspNetCore.Diagnostics": "1.0.0",
    "Microsoft.AspNetCore.StaticFiles": "1.0.0",
    "Microsoft.AspNet.SignalR": "2.2.0",
    "Microsoft.AspNet.SignalR.Owin": "1.2.2",
    "Microsoft.AspNetCore.Owin": "1.0.0"
  },

  "tools": {
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
  },

  "frameworks": {
    "net461": {}
  },

  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },

  "runtimeOptions": {
    "gcServer": true
  },

  "publishOptions": {
    "include": [
      "wwwroot",
      "Views",
      "appsettings.json",
      "web.config"
    ]
  },

  "scripts": {
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}

Note that you could also install the SignalR and other package references via NPM if you would like. Take a look at the following figure below:


Figure 12: Adding SignalR Packages

As of this writing, the latest version of ASP.NET SignalR is 2.2. Once the required references are installed, you should be able to see them added to your project like below:

Figure 13: References

Integrating Client-Side Packages - jQuery and SignalR Scripts

In order for us to use the client-side features of SignalR, we need to reference the following scripts dependencies in your View:

  • jQuery
  • jQuery.signalR
  • /signalr/hub

In this demo, I’m going to use jQuery CDN to reference the jQuery library. Keep in mind that you can also use NPM or Bower to manage client-side resources such as jQuery in your project.

Now, the tricky part is to add the SignalR 2 Core JavaScript file to our project. The corresponding SignalR Core client scripts are added within Microsoft.AspNet.SignalR.JS components. This component is automatically added as a dependency of the Microsoft.AspNet.SignalR component. Now browse through the following location in your machine:

C:\Users\<UserName>\.nuget\packages\Microsoft.AspNet.SignalR.JS\2.2.0\content

Then copy the Scripts folder and paste it within the wwwroot folder in your application. In this case, within the “ASPNETCoreSignalRDemo/wwwroot”. You should have something like this now in your project:

Figure 14: wwwroot folder

The reference to the SignalR generated proxy is dynamically generated JavaScript code, not to a physical file. SignalR creates the JavaScript code for the proxy on the fly and serves it to the client in response to the "/signalr/hubs" URL/. For more information, read on: ASP.NET SignalR Hubs API Guide - JavaScript Client

Creating a Middleware for Mapping SignalR

We need to create a middleware for SignalR so we can configure to use it by creating an IApplicationBuilder extention method.

Now create a new class within the root of the application and name it as “AppBuilderExtensions”. Here’s the code block below:

C#
using Owin;  
using System;  
using System.Collections.Generic;  
using System.Threading.Tasks;  
using Microsoft.AspNetCore.Builder; 

namespace ASPNETCoreSignalRDemo  
{
    using Microsoft.Owin.Builder;
    using AppFunc = Func<IDictionary<string, object>, Task>;

    public static class AppBuilderExtensions 
    { 
        public static IApplicationBuilder UseAppBuilder(this IApplicationBuilder app, Action<IAppBuilder> configure) 
        { 
            app.UseOwin(addToPipeline => 
            { 
                addToPipeline(next => 
                { 
                    var appBuilder = new AppBuilder(); 
                    appBuilder.Properties["builder.DefaultApp"] = next; 

                    configure(appBuilder); 

                    return appBuilder.Build<AppFunc>(); 
                }); 
            }); 

            return app; 
        } 

        public static void UseSignalR2(this IApplicationBuilder app) 
        { 
            app.UseAppBuilder(appBuilder => appBuilder.MapSignalR()); 
        } 
    }
}

The UseAppBuilder extension method adds a new middleware to the ASP.NET pipeline using the UseOwin() extension method. It then adds the MapSignalR() middleware by invoking the UseAppBuilder() method within UseSignalR2() extension method. For more information about OWIN, read on: Open Web Interface for .NET (OWIN)

Adding SignalR to the Pipeline

Now open Startup.cs file update the Configure() method so it would look similar to this:

C#
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory){  
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseStaticFiles();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

            app.UseSignalR2();
}

Calling the app.UseSignalR2() method will add the SignalR middleware to the pipeline.

Creating a Hub

Create a new folder called Hubs within the root of your application then add a new class, and name it as “PollHub”. You project structure should now look something like this:

Figure 15: Project Structure

Now, add the following code below in your “PollHub” class:

C#
using Microsoft.AspNet.SignalR;

namespace ASPNETCoreSignalRDemo.Hubs  
{
    public class PollHub: Hub
    {
        public static void FetchPoll()
        {
            IHubContext context = GlobalHost.ConnectionManager.GetHubContext<PollHub>();
            context.Clients.All.displayPoll();
        }
    }
}

To provide you a quick overview, the Hub is the center piece of the SignalR. Similar to the Controller in ASP.NET MVC, a Hub is responsible for receiving input and generating the output to the client. The methods within the Hub can be invoked from the server or from the client.

Invoking a Hub Method

In this demo, we’re going to see how to invoke a method from the Hub in the controller action. You can also use this technique if for example, you have a mobile app that syncs data to your database using Web API calls and you need to display real-time results to your website.

Now, open the HomeController file and update the AddPoll() method to this:

C#
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult AddPoll(AddPollViewModel poll)  
{
            if (ModelState.IsValid) {
                if (_pollManager.AddPoll(poll))
                {
                    ViewBag.Message = "Poll added successfully!";
                    ASPNETCoreSignalRDemo.Hubs.PollHub.FetchPoll();
                }      
            }
            return View(poll);
}

Calling the FetchPoll() method from the Hub will invoked the displayPoll() ,and all connected clients will get the updates. If in case you are using AJAX to add a new Poll and wanted to trigger the FetchPoll() method in your JavaScript then you can call it like this:

JavaScript
var poll = $.connection.pollHub;  
poll.server.fetchPoll();  

For more information about SignalR Hub API, read here.

Creating the Web API

Create a new folder called API and then add a new Web API Controller class and name it as “PollController”. Your project structure should now look something like this:

Figure 14: Project Structure

Replace everything within your PollController class with this code:

C#
using System.Collections.Generic;  
using System.Linq;  
using Microsoft.AspNetCore.Mvc;  
using ASPNETCoreSignalRDemo.Models.ViewModels;  
using ASPNETCoreSignalRDemo.Models;

namespace ASPNETCoreSignalRDemo.API  
{
    [Route("api/[controller]")]
    public class PollController : Controller
    {
        private readonly IPollManager _pollManager;

        public PollController(IPollManager pollManager)
        {
            _pollManager = pollManager;
        }

        [HttpGet]
        public IEnumerable<PollDetailsViewModel> Get()
        {
           var res =  _pollManager.GetActivePoll();
           return res.ToList();
        }

    }
}

The class above make use of Attribute Routing to determine that the class is an API by decorating with this attribute: [Route("api/[controller]")]
Just like in our HomeController, it also uses a constructor injection to initialize the PollManager. The Get() method simply returns the result of the Poll’s data from the database.

Displaying the Poll

Open Index.cshtml and update the markup so it would look something like this:

HTML
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>  
<script src="../Scripts/jquery.signalR-2.2.0.js"></script>  
<script src="../signalr/hubs"></script>

<script>  
    $(function () {
        var poll = $.connection.pollHub;

        poll.client.displayPoll = function () {
            LoadActivePoll();
        };

        $.connection.hub.start();
        LoadActivePoll();
    });

    function LoadActivePoll() {
        var $div = $("#divQuestion");
        var $tbl = $("#tblPoll");
        $.ajax({
            url: '../api/poll',
            type: 'GET',
            datatype: 'json',
            success: function (data) {
                if (data.length > 0) {
                    $div.html('<h3>' + data[0].question + '</h3>');
                    $tbl.empty();
                    var rows = [];
                    var poll = data[0].pollOption;
                    for (var i = 0; i < poll.length; i++) {
                        rows.push('<tbody><tr><td>' + poll[i].answers + '</td><td><input name="poll" type="radio"/></td></tr></tbody>');
                    }
                    $tbl.append(rows.join(''));
                }
            }
        });
    }
</script>

<h2>ASP.NET Core Online Poll System with SignalR 2</h2>  
<div id="divQuestion"></div>  
<table id="tblPoll"></table>  

Take note of the sequence for adding the Scripts references. jQuery should be added first, then the SignalR Core JavaScript and finally the SignalR Hubs script.

The LoadActivePoll() function uses a jQuery AJAX to invoke a Web API call through AJAX GET request. If there’s any data from the response, it will generate an HTML by looping through the rows. The LoadActivePoll() function will be invoked when the page is loaded or when the displayPoll() method from the Hub is invoked. By subscribing to the Hub, ASP.NET SignalR will do all the complex plumbing for us to do real-time updates without any extra work needed in our side.

Final Output

Here’s the actual output after adding a new poll in AddPoll page and automatically fetches the result in Index page without doing any interaction.

That’s it! Check out part 2 of the series here: Real-Time Poll Vote Results Using SignalR 2, MVC, Web API 2, jQuery And HighCharts

Source code can be found in my Github repo at: https://github.com/proudmonkey/ASPNETCoreSignalR2App

Summary

In this article, we’ve done making the core foundation of the application starting from creating a new database, creating a new ASP.NET Core project, and setting up the Data Access using EF Core. We also learned how to create a simple page for adding a new poll and a page for displaying the newly added poll in real-time using ASP.NET SignalR 2.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
A code monkey who loves to drink beer, play guitar and listen to music.

My Tech Blog: https://vmsdurano.com/
My Youtube Channel: https://www.youtube.com/channel/UCuabaYm8QH4b1MAclaRp-3Q

I currently work as a Solutions Architect and we build "cool things" to help people improve their health.

With over 14 years of professional experience working as a Sr. Software Engineer specializing mainly on Web and Mobile apps using Microsoft technologies. My exploration into programming began at the age of 15;Turbo PASCAL, C, C++, JAVA, VB6, Action Scripts and a variety of other equally obscure acronyms, mainly as a hobby. After several detours, I am here today on the VB.NET to C# channel. I have worked on Web Apps + Client-side technologies + Mobile Apps + Micro-services + REST APIs + Event Communication + Databases + Cloud + Containers , which go together like coffee crumble ice cream.

I have been awarded Microsoft MVP each year since 2009, awarded C# Corner MVP for 2015, 2016,2017 and 2018, CodeProject MVP, MVA, MVE, Microsoft Influencer, Dzone MVB, Microsoft ASP.NET Site Hall of Famer with All-Star level and a regular contributor at various technical community websites such as CSharpCorner, CodeProject, ASP.NET and TechNet.

Books written:
" Book: Understanding Game Application Development with Xamarin.Forms and ASP.NET
" Book (Technical Reviewer): ASP.NET Core and Angular 2
" EBook: Dockerizing ASP.NET Core and Blazor Applications on Mac
" EBook: ASP.NET MVC 5- A Beginner's Guide
" EBook: ASP.NET GridView Control Pocket Guide

Comments and Discussions

 
Questionhow to upload an image along with a poll answer Pin
Member 1295284714-Mar-17 6:54
Member 1295284714-Mar-17 6:54 
QuestionLeads to not understanding .Net Core Pin
Dewey10-Oct-16 9:53
Dewey10-Oct-16 9:53 
AnswerRe: Leads to not understanding .Net Core Pin
Vincent Maverick Durano10-Oct-16 10:20
professionalVincent Maverick Durano10-Oct-16 10:20 
GeneralCouple of steps I had to take to get Entity Framework classes generated Pin
rossk1410-Oct-16 9:12
rossk1410-Oct-16 9:12 
GeneralRe: Couple of steps I had to take to get Entity Framework classes generated Pin
Vincent Maverick Durano10-Oct-16 9:16
professionalVincent Maverick Durano10-Oct-16 9:16 
GeneralRe: Couple of steps I had to take to get Entity Framework classes generated Pin
rossk1420-Oct-16 3:55
rossk1420-Oct-16 3:55 
QuestionMy vote of work Pin
Ahmet Abdi9-Sep-16 18:39
Ahmet Abdi9-Sep-16 18:39 
AnswerRe: My vote of work Pin
Vincent Maverick Durano10-Sep-16 1:51
professionalVincent Maverick Durano10-Sep-16 1:51 
QuestionProject is not loaded completely Pin
7Sahil12-Aug-16 9:09
7Sahil12-Aug-16 9:09 
AnswerRe: Project is not loaded completely Pin
Vincent Maverick Durano12-Aug-16 10:26
professionalVincent Maverick Durano12-Aug-16 10:26 
GeneralRe: Project is not loaded completely Pin
7Sahil21-Aug-16 6:53
7Sahil21-Aug-16 6:53 
GeneralRe: Project is not loaded completely Pin
Vincent Maverick Durano22-Aug-16 2:38
professionalVincent Maverick Durano22-Aug-16 2:38 
GeneralRe: Project is not loaded completely Pin
7Sahil25-Sep-16 4:56
7Sahil25-Sep-16 4:56 
GeneralRe: Project is not loaded completely Pin
Vincent Maverick Durano26-Sep-16 4:37
professionalVincent Maverick Durano26-Sep-16 4:37 
GeneralRe: Project is not loaded completely Pin
7Sahil26-Sep-16 6:34
7Sahil26-Sep-16 6:34 
GeneralRe: Project is not loaded completely Pin
Vincent Maverick Durano26-Sep-16 7:08
professionalVincent Maverick Durano26-Sep-16 7:08 
QuestionIncomplete sample for .NET CORE Pin
Jagger B11-Aug-16 17:44
Jagger B11-Aug-16 17:44 
AnswerRe: Incomplete sample for .NET CORE Pin
Vincent Maverick Durano11-Aug-16 23:54
professionalVincent Maverick Durano11-Aug-16 23:54 
General[My vote of 2] My Vote 2 Pin
Chrris Dale7-Aug-16 13:18
Chrris Dale7-Aug-16 13:18 
GeneralRe: [My vote of 2] My Vote 2 Pin
Vincent Maverick Durano7-Aug-16 14:51
professionalVincent Maverick Durano7-Aug-16 14:51 
GeneralRe: [My vote of 2] My Vote 2 Pin
Chrris Dale7-Aug-16 14:57
Chrris Dale7-Aug-16 14:57 
GeneralRe: [My vote of 2] My Vote 2 Pin
Vincent Maverick Durano7-Aug-16 15:06
professionalVincent Maverick Durano7-Aug-16 15:06 
GeneralRe: [My vote of 2] My Vote 2 Pin
Chrris Dale7-Aug-16 15:16
Chrris Dale7-Aug-16 15:16 
GeneralRe: [My vote of 2] My Vote 2 Pin
Vincent Maverick Durano7-Aug-16 15:27
professionalVincent Maverick Durano7-Aug-16 15:27 
GeneralRe: [My vote of 2] My Vote 2 Pin
Chrris Dale7-Aug-16 15:36
Chrris Dale7-Aug-16 15:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.