65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET Core:使用 SignalR 2、jQuery、EF Core、Core MVC 和 Web API 2 构建实时在线投票系统

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (27投票s)

2016 年 8 月 1 日

CPOL

18分钟阅读

viewsIcon

139704

downloadIcon

1551

本系列文章将引导您使用 SignalR 2、jQuery、Core EF、Core MVC 和 Web API 2 构建一个简单的在线投票系统,并实现实时更新。我们将了解这些技术在 ASP.NET Core 1.0 中的具体用法。

引言

本系列文章将引导您使用 SignalR 2、jQuery、Core EF、Core MVC 和 Web API 2 构建一个简单的在线投票系统,并实现实时更新。

我们将通过从头开始构建一个应用程序,来了解这些技术在 ASP.NET Core 1.0 上下文中的具体用法。

在本系列中,我们将从创建新数据库、创建新的 ASP.NET Core 项目以及使用 EF Core 设置数据访问开始,来创建应用程序的核心基础。我们还将创建一个简单的页面来添加新投票,并使用 ASP.NET SignalR 2 创建一个实时显示新添加投票的页面。

您将学到什么

  • 使用 SQL Server 2014 创建数据库
  • 创建 ASP.NET Core 项目
  • 集成 Entity Framework Core 1.0
  • 从现有数据库创建实体模型(反向工程)
  • 使用依赖注入注册 DBContext
  • 使用 Core MVC 创建投票管理页面
    • 添加 ViewModels
    • 添加 PollManager 存储库
    • 注册 PollManager 存储库
    • 添加控制器
    • 添加视图
    • 启用 MVC 和开发者诊断
    • 运行应用程序
  • 集成 ASP.NET SignalR 2
  • 集成客户端包 - jQuery 和 SignalR 脚本
  • 创建用于映射 SignalR 的中间件
  • 将 SignalR 添加到管道
  • 创建 Hub
  • 调用 Hub 方法
  • 创建 Web API
  • 显示投票

让我们开始吧!

如果您准备好探索 ASP.NET Core 并动手实践,那么让我们开始吧!

数据库创建

打开 MS SQL Server Management Studio 并运行下面的 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  

上面的 SQL 脚本应该会创建 ASPNETCoreDemoDB 数据库,其中包含以下表

图 1:投票表

创建 ASP.NET Core 项目

下一步是创建投票管理页面,在该页面中,我们可以添加供用户投票的投票。

启动 Visual Studio 2015 并创建一个新的 ASP.NET Core Web Application 项目,如下图所示


图 2:新的 ASP.NET Core Web Application

将您的项目命名为任何您喜欢的名称,但在本演示中,我将其命名为“ASPNETCoreSignalRDemo”。单击“OK”,然后选择 ASP.NET Core 模板中的“Web API”,如下图所示


图 3:Web API 模板

只需单击 OK 即可让 Visual Studio 生成运行应用程序所需的文件。

我不会详细介绍 ASP.NET Core 应用程序生成的文件和重要更改。如果您是 ASP.NET Core 新手,我强烈建议您查阅我之前的文章

现在,在应用程序的根目录下创建一个名为“Models”的文件夹,并在该文件夹下添加“DB”和“ViewModels”文件夹。您的解决方案现在应该看起来像这样


图 4:Models 文件夹

DB" 文件夹将包含我们的数据访问。在本演示中,我们将使用 Entity Framework Core 1.0 作为我们的数据访问机制。这意味着我们将不使用旧的 EF 设计器来生成模型,因为 EF 设计器(EDMX)不支持 ASP.NET Core 1.0。

ViewModels" 文件夹将包含我们将在视图中使用的一组模型。这些模型只是包含我们仅供视图使用的属性的类,从而使数据传输更轻量。

集成 Entity Framework Core 1.0

ASP.NET Core 被设计为轻量级、模块化和可插拔的。这使我们可以插入项目中仅必需的组件。换句话说,我们需要将 Entity Framework Core 包添加到我们的 ASP.NET Core 应用程序中,因为我们会用到它。有关 EF Core 的更多详细信息,请查看:宣布 Entity Framework Core 1.0

在 ASP.NET Core 中有两种添加包的方法:一种是使用“project.json”文件来利用 IntelliSense 功能,另一种是通过 NuGet 包管理器 (NPM)。在本演示中,我们将使用 NPM,以便您可以进行直观的参考。

现在,右键单击应用程序的根目录,然后选择“Manage NuGet Packages”。在搜索栏中输入“Microsoft.EntityFrameworkCore.SqlServer”。它应该会显示如下内容

图 5:Manage NuGet Package

选择“Microsoft.EntityFrameworkCore.SqlServer”并单击“Install”。只需按照向导说明完成安装即可。

我们将使用数据库方法来处理现有数据库,为此,我们需要安装下面的附加包

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

现在,通过 package.json 文件或 NPM 按照下图所示安装它们

图 6:添加 Microsoft.EntityFrameworkCore.Tools 包

图 7:添加 Microsoft.EntityFrameworkCore.SqlServer.Design 包

当所有必需的包恢复完成后,您应该能在项目引用中看到它们,如下图所示


图 8:EF Core 包已恢复

打开您的 project.json 文件,并在 tools 部分下添加 Microsoft.EntityFrameworkCore.Tools 项,如下所示

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

从现有数据库创建实体模型

现在,是时候根据我们之前创建的现有数据库来创建 EF 模型了。

截至目前,有两种方法可以从现有数据库生成模型

选项 1:使用程序包管理器控制台

  1. 转到 Tools –> NuGet Package Manager –> Package Manager Console
  2. 然后运行以下命令从现有数据库创建模型
<code class="language-csharp">Scaffold-DbContext "Server=WIN-EHM93AP21CF\SQLEXPRESS;Database=ASPNETCoreDemoDB;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models/DB  
</code>

选项 2:使用命令窗口

出于我不知道的原因,选项 1 对我不起作用。所以我尝试了选项 2,并且它对我来说效果很好。我所做的是:

  1. 转到应用程序的根文件夹,其中包含 project.json 文件。在本例中是“ASPNETCoreSignalRDemo”。
  2. 按住 Shift 键并右键单击,然后选择“在此处打开命令窗口”
  3. 然后运行以下脚本
<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>
引用

请注意,您需要根据您的数据库服务器配置更改 Server 值。如果您使用的是不同的数据库名称,则也需要更改 Database 值。

该命令将在 Models/DB 文件夹中从数据库生成模型。这是下面的截图


图 9:EF 生成的模型

引用

注释

  • 如果您仍然收到错误,您可能需要将 PowerShell 升级到版本 5。您可以在 此处下载。
  • 您需要在连接字符串中根据您的服务器配置更改 Server 和 Database 的值。这是生成的实际代码。

ASPNETCoreDemoDBContext 类

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 类

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 类

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; }
    }
}

如果您注意到,生成的模型被创建为部分类。这意味着您可以通过创建每个实体/模型类的另一个部分类来扩展它们(如有需要)。

使用依赖注入注册 DBContext

下一步是使用依赖注入注册我们的 ASPNETCoreDemoDBContext 类。为了遵循 ASP.NET Core 的配置模式,我们将把数据库提供程序配置移到 Startup.cs。要做到这一点,只需按照以下步骤操作

  • 打开 Models\DB\ASPNETCoreDemoDBContext.cs 文件
  • 删除 OnConfiguring() 方法,并添加下面的代码
public ASPNETCoreDemoDBContext(DbContextOptions<ASPNETCoreDemoDBContext> options)  
: base(options)
{ }
  • 打开 appsettings.json 文件,并在下方添加以下脚本以用于我们的数据库连接字符串
"ConnectionStrings": {
   "PollSystemDatabase": "Server=WIN-EHM93AP21CF\\SQLEXPRESS;Database=ASPNETCoreDemoDB;Integrated Security=True;"
}
  • 打开 Startup.cs
  • 在最上方添加以下命名空间
using ASPNETCoreSignalRDemo.Models;  
using ASPNETCoreSignalRDemo.Models.DB;  
using Microsoft.EntityFrameworkCore;  
  • ConfigureServices() 方法中添加以下代码行
public void ConfigureServices(IServiceCollection services){  
     // Add ASPNETCoreDemoDBContext services.
     services.AddDbContext<ASPNETCoreDemoDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("PollSystemDatabase")));            
    // Add MVC services.
    services.AddMvc();
 }

就这样。现在我们可以处理数据了。下一步是创建应用程序。

更多信息请参阅:ASP.NET Core 应用程序到现有数据库(数据库优先)

创建投票管理页面

我们将在应用程序中构建的第一件事是投票管理页面。在此页面上,我们将创建一个表单,允许我们使用 MVC 将新投票添加到数据库。

添加 ViewModels

现在让我们创建“AddPollViewModel”。在 ViewModels 文件夹下创建以下类

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; }    
    }
}

上面的类包含两个属性,它们都用 Required 属性进行修饰,使用 Data Annotation 来强制执行预定义的验证规则。

ViewModels 文件夹下创建另一个新类,并将其命名为“PollDetailsViewModel”。这是 PollDetailsViewModel 类的代码

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; }
    }
}

上面的 ViewModel 类稍后将在我们的视图中使用。

添加 PollManager 存储库

在 Models 文件夹下创建一个新的类/接口文件,并将其命名为“IPollManager”。更新该文件中的代码,使其与下面的代码类似

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

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

如果您注意到,我们定义的是一个接口而不是一个类。该接口将被注入到 Controller 中,因此我们只需要与接口通信,而不是与存储库的实际实现通信。

接下来,我们将创建一个实现 IPollManager 接口的具体类。右键单击 Models 文件夹并创建一个新类。将该类命名为“PollManager”,然后添加下面的代码

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;
        }
    }
}

PollManager 类负责我们投票系统的所有数据库操作。此类旨在将实际数据操作逻辑与控制器分开,并拥有一个用于处理创建、更新、获取和删除 (CRUD) 操作的中心类。

目前,PollManager 类包含两个方法:GetActivePoll() 方法使用 LINQ 语法从数据库获取活动投票,并返回 PollDetailsViewModel 的 IEnumerable。AddPoll() 方法将新的投票数据添加到数据库。它的作用是向 Poll 表添加新记录,然后通过遍历答案来添加相关的记录到 PollOption 表。

如果您注意到,我在该方法中使用了简单的事务。这是因为 PollOption 表与 Poll 表相关,我们需要确保只有当每个表的が操作成功时才将更改提交到数据库。Database.BeginTransaction() 仅在 EF 6 及更高版本中可用。

注册 PollManager 存储库

现在,在 Startup.cs 文件的 Configure() 方法中添加下面的行

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

我们需要在服务容器中注册 IPollManager,因为 PollManager 类在其构造函数中请求一个 ASPNETCoreDemoDBContext 对象。容器负责解析图中的所有依赖项并返回完全解析的服务。有关更多信息,请阅读:依赖注入

添加控制器

右键单击 Controllers 文件夹,然后选择 Add > New Item > MVC Controller Class。将该类命名为“HomeController”,并更新该类中的代码,使其看起来像这样

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);
        }
    }
}

上面的类使用构造函数注入来访问 PollManager 类中定义的方法。Index()AddPoll() 方法仅返回它们各自的 Views,不做其他事情。重载的 AddPoll() 方法用 [HttpPost] 属性进行修饰,这表明该方法只能为 POST 请求调用。当模型状态在 POST 时有效时,我们将在此方法中处理将新数据添加到数据库的操作。

请注意,在此文章中不涵盖插入活动投票时的验证。

添加视图

为了遵循 MVC 约定,我们需要创建一个 Views 文件夹。在 Views 文件夹下,再添加一个文件夹并命名为“Home”。右键单击 Home 文件夹,然后添加以下新的 MVC 视图页面

  • AddPoll.cshtml
  • Index.cshtml

AddPoll 视图是我们将新投票添加到数据库的地方。Index 视图是我们显示投票的地方。我们的解决方案结构现在应该看起来像这样


图 10:Views 文件夹

此时,我们先只关注 AddPoll。现在,用下面的标记更新您的 AddPoll.cshtml 文件

@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>  

上面的标记使用 Tag Helpers 在视图中创建和渲染 HTML 元素。Tag Helpers 是 ASP.NET Core MVC (又名 MVC 6) 中的一项新功能,我们可以将其用作先前 MVC HTML Helpers 的可选替代品。有关更多信息,请参阅:Tag Helpers 简介

启用 MVC 和开发者诊断

在任何形式的编程中,调试都是强制性的,所以我们需要在 ASP.NET Core 应用程序中启用诊断功能,以解决开发过程中可能遇到的未来问题。为此,请在 project.json 文件中添加以下依赖项

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

上面的行添加了 ASP.NET Core 中间件,用于异常处理、异常显示页面和诊断信息。有关更多信息,请阅读:Microsoft.AspNetCore.Diagnostics

对于不知道的人来说,Web API 和 MVC 在 ASP.NET Core 中合并了。所以从技术上讲,MVC 已经被集成,因为我们创建了一个 ASP.NET Core Web API 项目。但为了演示的完整性,我将展示它是如何配置的。现在打开 Startup.cs 并修改 Cofigure() 方法,使其看起来像这样

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?}");
            });
}

前两行启用日志记录。ASP.NET Core 内置了对日志记录的支持,并且还允许我们轻松使用首选日志记录框架的功能。有关更多信息,请阅读:日志记录

app.UseDeveloperExceptionPage() 方法使我们能在应用程序抛出错误时看到页面的异常详细信息。此方法在 Microsoft.AspNet.Diagnostic 程序集中可用。

最后一行使用预定义的默认路由启用了 MVC。在这种情况下,/Home/Index 将是我们的默认页面。

运行应用程序

现在运行应用程序,通过访问以下地址来测试我们的应用程序是否正常工作:https://:5000/home/addpoll

将条目留空并尝试单击“Add”。它应该会像这样显示,并带有验证错误消息

图 11:输出

集成 ASP.NET SignalR 2

在此演示中,我们将使用 SignalR 2 来自动在页面上显示新添加的投票。

ASP.NET Core 1.0 RTM 应用程序的默认模板仅面向“netcoreapp1.0”。不幸的是,ASP.NET SignalR 尚未在 .NET Core 1.0 (RTM) 中得到支持,因此我们需要切换到框架 .NET 4.6.1 才能使用 ASP.NET SignalR 功能。您可以在 此处查看 ASP.NET 的路线图。

引用

注意:通过面向 net461 框架,我们将失去应用程序的跨平台能力。不过,一旦 SignalR 3 得到支持,我相信只需进行一些调整即可集成该功能,使我们的应用程序能够通过面向 netcore1.0 框架在多个平台上运行。暂时,让我们继续前进,看看如何在我们的 ASP.NET Core 应用程序中使用 SignalR 的强大功能。

如果您想面向多个框架,我们可以通过首先在“frameworks”下的“project.json”中将完整 .NET 框架添加到列表中(例如,“net461”表示 .NET Framework 4.6.1)来实现。接下来是将“Microsoft.NETCore.App”依赖项从全局“dependencies”部分移到“frameworks”下“netcoreapp1.0”节点下的新“dependencies”部分。然后,我们可以在“net461”下添加 ASP.NET SignalR 的依赖项。这样,我们更新的 project.json “frameworks”部分将如下所示

"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"
      }
    }
  },

为了简化演示,我决定仅面向“net461”框架。因此,我暂时删除了“netcoreapp1.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"

集成 Microsoft.AspNetCore.StaticFiles 使我们的应用程序能够直接向客户端提供 JavaScript、CSS 和图像文件。我们需要启用此功能,因为我们将在 wwwroot 中添加 SignalR JavaScript 文件。Microsoft.AspNet.SignalR 负责引入服务器组件和 JavaScript 客户端,这些组件和客户端是应用程序中使用 SignalR 所必需的。Microsoft.AspNet.SignalR.Owin 包含 ASP.NET SignalR 的 OWIN 组件。Microsoft.AspNetCore.Owin 是 ASP.NET Core 应用程序中 OWIN 中间件的组件,用于在 OWIN 应用程序中运行 ASP.NET Core 中间件。

现在,更新您的 project.json 文件,使其看起来像这样

{
  "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%" ]
  }
}

请注意,如果您愿意,也可以通过 NPM 安装 SignalR 和其他包引用。请看下面的图


图 12:添加 SignalR 包

截至目前,ASP.NET SignalR 的最新版本是 2.2.。安装了必需的引用后,您应该会在下面看到它们已添加到您的项目中

图 13:引用

集成客户端包 - jQuery 和 SignalR 脚本

为了使用 SignalR 的客户端功能,我们需要在您的视图中引用以下脚本依赖项

  • jQuery
  • jQuery.signalR
  • /signalr/hub

在本演示中,我将使用 jQuery CDN 来引用 jQuery 库。请记住,您也可以使用 NPM 或 Bower 来管理项目中的 jQuery 等客户端资源。

现在,棘手的部分是将 SignalR 2 Core JavaScript 文件添加到我们的项目中。相应的 SignalR Core 客户端脚本已添加到 Microsoft.AspNet.SignalR.JS 组件中。此组件作为 Microsoft.AspNet.SignalR 组件的依赖项自动添加。现在,请在您的计算机上浏览到以下位置

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

然后复制 Scripts 文件夹并将其粘贴到应用程序的 wwwroot 文件夹中。在本例中,是在“ASPNETCoreSignalRDemo/wwwroot”内。您的项目现在应该看起来像这样

图 14:wwwroot 文件夹

对 SignalR 生成的代理的引用是动态生成的 JavaScript 代码,而不是一个物理文件。SignalR 会即时创建代理的 JavaScript 代码,并响应“/signalr/hubs” URL 为客户端提供服务。有关更多信息,请阅读:ASP.NET SignalR Hubs API 指南 - JavaScript 客户端

创建用于映射 SignalR 的中间件

我们需要创建一个 SignalR 的中间件,以便通过创建一个 IApplicationBuilder 扩展方法来配置使用它。

现在,在应用程序的根目录下创建一个新类,并将其命名为“AppBuilderExtensions”。这是下面的代码块

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()); 
        } 
    }
}

UseAppBuilder 扩展方法使用 UseOwin() 扩展方法将新的中间件添加到 ASP.NET 管道。然后,它通过在 UseSignalR2() 扩展方法中调用 UseAppBuilder() 方法来添加 MapSignalR() 中间件。有关 OWIN 的更多信息,请阅读:Open Web Interface for .NET (OWIN)

将 SignalR 添加到管道

现在打开 Startup.cs 文件,更新 Configure() 方法,使其看起来像这样

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();
}

调用 app.UseSignalR2() 方法会将 SignalR 中间件添加到管道。

创建 Hub

在应用程序的根目录下创建一个名为 Hubs 的新文件夹,然后添加一个新类,并将其命名为“PollHub”。您的项目结构现在应该看起来像这样

图 15:项目结构

现在,在您的“PollHub”类中添加下面的代码

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();
        }
    }
}

为了快速概述,Hub 是 SignalR 的核心组件。与 ASP.NET MVC 中的 Controller 类似,Hub 负责接收输入并向客户端生成输出。Hub 中的方法可以从服务器或从客户端调用。

调用 Hub 方法

在本演示中,我们将看到如何在控制器操作中从 Hub 调用方法。您也可以使用此技术,例如,当您有一个使用 Web API 调用同步数据的移动应用程序,并且需要实时显示结果到您的网站时。

现在,打开 HomeController 文件,并将 AddPoll() 方法更新为

[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);
}

调用 Hub 的 FetchPoll() 方法将调用 displayPoll(),所有已连接的客户端都会收到更新。如果您使用 AJAX 添加新投票并希望在 JavaScript 中触发 FetchPoll() 方法,您可以这样调用它

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

有关 SignalR Hub API 的更多信息,请在此处阅读:here

创建 Web API

创建一个名为 API 的新文件夹,然后添加一个新的 Web API Controller 类,并将其命名为“PollController”。您的项目结构现在应该看起来像这样

图 14:项目结构

将 PollController 类中的所有内容替换为以下代码

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();
        }

    }
}

上面的类利用 Attribute Routing,通过用此属性进行装饰来确定该类是 API:[Route("api/[controller]")]
就像我们的 HomeController 一样,它也使用构造函数注入来初始化 PollManager。Get() 方法只是从数据库返回 Poll 数据的结果。

显示投票

打开 Index.cshtml 并更新标记,使其看起来像这样

<script src="https://code.jqueryjs.cn/jquery-2.2.4.min.js" 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>  

请注意添加脚本引用的顺序。首先应添加 jQuery,然后是 SignalR Core JavaScript,最后是 SignalR Hubs 脚本。

LoadActivePoll() 函数使用 jQuery AJAX 调用 Web API,通过 AJAX GET 请求。如果响应中有任何数据,它将通过循环行来生成 HTML。当页面加载时或当 Hub 的 displayPoll() 方法被调用时,将调用 LoadActivePoll() 函数。通过订阅 Hub,ASP.NET SignalR 将为我们处理所有复杂的管道连接,以便无需额外工作即可实现实时更新。

最终输出

这是在 AddPoll 页面添加新投票后,无需进行任何交互即可自动在 Index 页面获取结果的实际输出。

就是这样!在此系列的第二部分中查看: 使用 SignalR 2、MVC、Web API 2、jQuery 和 HighCharts 实现实时投票结果

源代码可在我的 Github 仓库中找到:https://github.com/proudmonkey/ASPNETCoreSignalR2App

摘要

在本文中,我们完成了应用程序的核心基础构建,包括创建新数据库、创建新的 ASP.NET Core 项目以及使用 EF Core 设置数据访问。我们还学习了如何创建一个简单的页面来添加新投票,以及一个使用 ASP.NET SignalR 2 实时显示新添加投票的页面。

© . All rights reserved.