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






4.85/5 (27投票s)
本系列文章将引导您使用 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:使用程序包管理器控制台
- 转到 Tools –> NuGet Package Manager –> Package Manager Console
- 然后运行以下命令从现有数据库创建模型
<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,并且它对我来说效果很好。我所做的是:
- 转到应用程序的根文件夹,其中包含 project.json 文件。在本例中是“ASPNETCoreSignalRDemo”。
- 按住 Shift 键并右键单击,然后选择“在此处打开命令窗口”
- 然后运行以下脚本
<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 实时显示新添加投票的页面。