ASP.NET Core:一次编译,随处托管





5.00/5 (15投票s)
如何托管跨平台的 ASP.NET Core 应用程序
引言
这在某种程度上是关于 .NET Core 系列的第二部分。在第一部分(.NET Core:一次编译,随处运行)中,我们学习了 .NET Core 如何在不同平台上使用的基本事实,在这一部分中,我想将这个想法扩展到在不同平台上托管同一个 Web 应用程序...
背景
你会找到几个 ASP.NET Core“入门”示例。我创建了自己的版本,因为我发现那些示例不完整,甚至与跨平台的理念相悖……有些只关注代码,对托管只字不提;另一些则在项目内部解决托管问题,这自动使得项目无法跨平台;还有一些则只解释了单个平台的托管……
在这篇文章中,我将较少关注代码(代码示例将非常简单和简短),而更多地关注如何在不同平台上进行托管...
在本文中,我使用与第一部分(上面的链接)相同的设置。如果您没有完成设置过程,请打开该文章并按照其说明直到“真正的魔法”部分(不包括在内)。
一个非常基本的 Web 应用程序
为了处理所有代码,我将使用 VS Code 编辑器及其集成终端,此时最好按照此步骤操作...
对于第一步(创建项目),打开集成终端。视图->集成终端或Ctrl+`,然后运行这些命令来创建新的 .NET Core 项目...
mkdir aspcore
cd aspcore
dotnet new
添加 Web 相关内容
现在使用文件->打开文件夹菜单打开刚刚创建的项目。您将在左侧边栏看到两个文件,打开project.json以添加对 ASP.NET Core HTTP 服务器 (Kestrel) 和 IIS 集成的新依赖项。
最终结果应该是这样的:
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {},
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.AspNetCore.Server.IISIntegration": "*",
"Microsoft.AspNetCore.Server.Kestrel": "*"
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.1.*"
},
},
"imports": "dnxcore50"
}
}
}
下一步是刷新刚刚声明的依赖项(下载 DLLs)……为此,您有两种选择……如果 VS Code 已经弹出了如下所示的通知,您可以选择“恢复”并等待下载完成……
如果您喜欢命令行,请返回集成终端并运行dotnet restore
...
修改 main 以运行服务器
就像任何普通的控制台应用程序一样,main 函数是入口点,但现在它不是实际运行某些东西,而是启动 HTTP 主机,该主机将监听请求...
打开Program.cs并将其修改为如下所示
using Microsoft.AspNetCore.Hosting;
namespace WebApi
{
public class Program
{
public static void Main(string[] args)
{
var oHost = new WebHostBuilder()
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
oHost.Run();
}
}
}
UseKestrel
- 这一行将利用 Kestrel web 服务器(ASP.NET Core 的内置、跨平台 HTTP 主机)作为托管进程。UseIISIntegration
- 好的。这看起来像是违反了跨平台 IDE 的原则,但事实并非如此。这一行将帮助我们将应用程序与 IIS 服务器集成,但不会影响应用程序在每个受支持的平台上运行的能力,即使没有 IIS 存在...UseStartup
- 标识了具有配置应用程序/环境方法的类...您很快就会看到它。
Startup - 配置环境
这个类通过定义 Configure
(必须有)和 ConfigureServices
(可选)方法来提供应用程序和托管环境的配置信息。
让我们看看我们的示例,然后是一些解释……在您的项目中添加一个名为Startup.cs的新文件,并将下面的代码复制进去...
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace WebApi
{
public class Startup
{
public void ConfigureServices(IServiceCollection Services)
{
Services.AddMvcCore()
.AddJsonFormatters();
}
public void Configure(IApplicationBuilder App)
{
App.UseMvc(
Routes => {
Routes.MapRoute(
name: "Default",
template: "{controller=Home}/{action=Index}/{id?}"
);
}
);
}
}
}
如果你曾经在 Visual Studio 中使用过 Web API 或 MVC 应用程序,这段代码会非常熟悉,但即使没有,它也很容易理解...
在ConfigureServices
方法中,我加载了 MVC Core 层(它使我们能够以简单的方式使用控制器和模型),并向其中添加了 JSON 格式化程序,因为我更喜欢它而不是 XML...
Configure
方法初始化刚刚加载的 MVC,并为其添加一个带有某些默认值的路由映射...
此部分将无法编译,直到您添加我们在此处使用的类的依赖项。因此,将以下两行添加到project.json中,并再次从集成终端运行dotnet restore
命令...
"Microsoft.AspNetCore.Mvc.Core": "*",
"Microsoft.AspNetCore.Mvc.Formatters.Json": "*"
一些实际代码
在这一部分,我们将添加一个控制器,它将提供用户列表,如果 ID 已知,则提供单个用户...
您需要添加一个名为Models的文件夹,其中包含一个名为Users.cs的文件,以及另一个名为Controllers的文件夹,其中包含一个名为UsersController.cs的文件。
Models/Users.cs
namespace WebApi
{
public class User
{
public int ID { get; set; }
public string Name { get; set; }
}
}
Controllers/UsersController.cs
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
namespace WebApi
{
[Route("/api/users")]
public class UsersController
{
// some data
private static List<User> _Users = new List<User>(new[] {
new User() { ID = 1, Name = "Kornfled" },
new User() { ID = 2, Name = "Eliyahu" },
new User() { ID = 3, Name = "Peter" },
});
[HttpGet]
public IEnumerable<User> Get()
{
return( _Users );
}
[HttpGet("{id}")]
public IActionResult Get(int ID)
{
User oUser = _Users.FirstOrDefault(User => User.ID == ID);
if (oUser == null)
{
return(new NotFoundResult());
}
return(new OkObjectResult(oUser));
}
}
}
现在我们已经把所有的代码和配置都准备好了,是时候检查一下了...
打开集成终端并运行dotnet run
,当它启动并运行后,打开浏览器并使用这些 URL 进行测试...
localhost:5000/api/users
localhost:5000/api/users/2
你应该会得到这样的答案...
[{"id":1,"name":"Kornfled"},{"id":2,"name":"Eliyahu"},{"id":3,"name":"Peter"}]
还有这个...
{"id":2,"name":"Eliyahu"}
出版
最后一步,与代码相关,是发布我们刚刚创建的代码……为此,再次使用集成终端,并输入dotnet publish -c Release
命令……结果应该像这样
publish: Published to /home/peter/Applications/aspcore/bin/Release/netcoreapp1.0/publish
Published 1/1 projects successfully
结果是一长串您需要运行应用程序的文件。此文件夹现在可以在(安装了 .NET Core 的)机器之间移动,并在每个受支持的平台上运行...
要检查它,您所需要做的就是从已发布的文件夹中运行dotnet run
,并在浏览器中导航到localhost:5000/api/users。它在 Linux 和 Windows 上也会以相同的方式工作(我不提 Mac,因为我没有实际测试过,但根据微软的说法,它在那里也应该一样)。
托管 - 背景
既然我们有了跨平台的 Web 应用程序,我们就可以谈谈我们来的主要目的了……托管……
为什么没有 Kestrel
你可能在想:等一下!我们已经拥有了所需的一切!我们有一个 Web 服务器 (Kestrel) 已经成为我们应用程序的一部分,并且正在运行。让我们把它暴露给我们的用户,然后开始盛宴吧!
Kestrel 的所有优点(大小、速度、跨平台)都是有代价的!Kestrel 并不是为黄金时段而构建的。它不具备真正的 Web 服务器应有的功能。
- 一个 Kestrel 只能运行一个应用程序
- 每个 Kestrel 实例都必须使用不同的端口
- 没有安全功能,例如 SSL 或 IP 过滤
- 以及更多...
所以 Kestrel 在现实世界中是无法使用的(但在开发周期中进行测试和在一些真正的网络服务器后面进行实际托管是完全可以的)。
解决方案
这是一种设置,它将完全隐藏我们除了暴露的 Web 服务器之外还有其他东西的事实。它使我们能够使用 Web 服务器中的任何安全措施,并将我们的应用程序与底层操作系统分离,并使其保持跨平台...此外,它还可以在相同的地址/端口组合下运行多个 ASP.NET Core 站点,就像它们是同一个站点的一部分一样...
当然,这就是我们告别跨平台的地方,因为每个 Web 服务都有其自己的反向代理服务器解决方案,但这完全是关于设置和配置——我们将不再触及代码!
如何托管
Windows
在 Windows 中,我们将使用 IIS 作为 Kestrel 的反向代理服务器。我看不出有任何理由使用 Apache,因为两者都是免费且优秀的,但 IIS 在 Windows 上感觉更好...
第一步是安装一个专门为我们的需求创建的特殊 IIS 模块 - AspNetCoreModule
... ASP.NET Core 服务器托管包。如果您已经安装了 .NET Core SDK,那么您已经拥有它,所以跳过安装。要查看是否一切就绪,请转到 IIS 管理器并检查已安装模块的列表...
这个模块不仅会处理请求重定向,更重要的是,它会确保我们的应用程序始终运行。
这个模块不仅是原生的,而且级别非常低,因此它会拦截所有请求,并且任何看似合适的请求都将被重定向到 ASP.NET Core 进程,即使原始请求会转到其他处理程序,例如 ASPX...解决方案是设置一个单独的应用程序池。由于此池将作为简单的代理,我们不需要 .NET 运行时,因此将 .NET CLR 版本设置为“无托管代码”。
下一步是创建一个新的应用程序,指向您已发布的 ASP.NET Core 代码所在的文件夹...
安全性方面会出现问题。默认的池标识 (ApplicationPoolIdentity
) 没有权限从 IIS 运行可执行文件 (dotnet.exe),因此您必须将其更改为具有这些权限的用户。为了测试,我将其更改为 LocalSystem
,但在真正的生产环境中,您应该联系您的系统管理员以创建更适合的用户...
最后一步是在您的应用程序根目录中创建一个web.config文件,其内容如下(此配置文件实际上也可以作为项目的一部分,并随项目发布到每个平台——它不会伤害任何人)
<?xml version="1.0"?>
<configuration>
<system.webServer>
<handlers>
<add name="ASP.NET Core" path="*" verb="*"
modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\aspcore.dll" />
</system.webServer>
</configuration>
如果您想查看 .NET Core 环境中的日志(如果您在启动 dotnet 可执行文件时遇到问题,这是一个好主意),您应该将aspNetCore
行更改为以下内容
<aspNetCore processPath="dotnet" arguments=".\aspcore.dll" stdoutLogEnabled="true"
stdoutLogFile=".\stdout.log" />
现在打开您的浏览器并导航到https:///ASPDOTNET/api/users... 您也可以使用 IP/机器名从同一网络上的另一台计算机进行测试,或者如果您的计算机确实在线,则可以从外部世界进行测试...
发布 Linux 守护程序
在 Linux 系统上,我们没有像 IIS 那样得到的帮助。没有集成模块来处理 dotnet 可执行进程,因此我们必须采取的第一步是确保我们的应用程序正在运行...
Fedora(以及大多数其他 Linux 版本)使用systemd
作为运行不同服务(在 Linux 世界中称为守护进程)的框架。我们要做的是为我们的应用程序创建一个描述文件,从那时起,它将在每次系统重启后运行...
作为第一步,我将已发布的应用程序移动到了一个更方便的路径……在我的情况下,是从/home/peter/aspcore/bin/Release/netcoreapp1.0/publish到/home/peter/aspcoreapp。
现在您需要打开一个终端并创建描述文件。命令是
sudo gedit /etc/systemd/system/dotnet-aspcoreapp.service
文件的内容应如下所示
[Unit]
Description=Sample ASP.NET Core Web API Application
[Service]
ExecStart=/usr/local/bin/dotnet /home/peter/aspcoreapp/aspcore.dll
Restart=always
RestartSec=10
SyslogIdentifier=dotnet-aspcoreapp
User=root
Environment=ASPNETCORE_ENVIRONMENT=Production
[Install]
WantedBy=multi-user.target
现在运行这 3 个命令来启用、启动和验证新服务...
sudo systemctl enable dotnet-aspcoreapp.service
sudo systemctl start dotnet-aspcoreapp.service
sudo systemctl -l status dotnet-aspcoreapp.servicesud
如果一切顺利,您应该会看到以下状态输出
● dotnet-aspcoreapp.service - Sample ASP.NET Core Web API Application
Loaded: loaded (/etc/systemd/system/dotnet-aspcoreapp.service; enabled; vendor preset: disabled)
Active: active (running) since Wed 2016-11-16 10:08:06 IST; 8min ago
Main PID: 749 (dotnet)
CGroup: /system.slice/dotnet-aspcoreapp.service
└─749 /usr/local/bin/dotnet /home/peter/aspcoreapp/aspcore.dll
现在,这个服务条目将在操作系统重启后或任何类型的崩溃后启动我们的应用程序...
代理设置 - nginx
nginx Web 服务器是 Linux 上最受欢迎的 Web 服务之一。它的流行度不断增长,主要是因为它轻量且可伸缩...
第一步是使用终端install nginx
...
sudo dnf install nginx
sudo service nginx start
现在您可以通过在浏览器中导航到https://来查看它是否确实有效...
下一步是配置nginx
以将传入请求转发到我们的应用程序。为此,我们必须编辑nginx
的配置文件...
sudo gedit /etc/nginx/nginx.conf
原版看起来是这样的
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
}
更改后,它应该看起来像这样
server {
listen 80 default_server;
listen [::]:80 default_server;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
}
location /kestrel {
proxy_pass http://127.0.0.1:5000/api;
}
第二个location
定义将把kestrel文件夹下的所有请求重定向到我们的 ASP.NET Core 应用程序的api文件夹。因此,如果以前我们浏览的是https://:5000/api/users,现在我们浏览的是https:///kesterl/users……这个地址也可以从服务器外部访问,并且可以进行安全保护等等。
最后一步是启用两个服务器之间的重定向。为此,我们必须设置 SELinux(Linux 中的一个安全层)的一个属性,以启用这种连接...然后重启...
sudo setsebool httpd_can_network_connect on -P
reboot
代理设置 - Apache
注意:此部分并非承接上一部分关于 nginx 的内容,而是与其并行/替代!
虽然nginx
是一个强大的玩家,但apache仍然占据了大部分网络服务器市场,而且您的目标服务器上很可能已经安装了它(就像您在 Fedora 上安装的一样),所以让我们看看如何为 Kestrel 配置它...
要启用和运行 apache 服务器,请使用以下命令
sudo systemctl enable httpd.servic
sudo systemctl start httpd.service
如果您现在导航到https://,您应该会看到一个类似这样的页面
下一步是编辑配置文件以定义我们需要的代理设置...
sudo gedit /etc/httpd/conf/httpd.conf
在该文件的末尾,添加这些行
<VirtualHost *:80>
DocumentRoot /home/peter/aspcoreapp
ProxyPreserveHost On
ProxyPass /kestrel/ http://127.0.0.1:5000/api/
ProxyPassReverse /kestrel/ http://127.0.0.1:5000/api/
</VirtualHost>
/home/peter/aspcoreapp部分是您的已发布应用程序所在的路径,并且必须与您在守护程序定义文件中使用的路径相同...
最后一步——就像之前一样——是启用两个服务器之间的重定向。为此,我们必须设置 SELinux(Linux 中的一个安全层)的一个属性,以启用这种连接...然后重启...
sudo setsebool httpd_can_network_connect on -P
reboot
摘要
我是一个资深的 Web 开发人员,无论是前端还是后端。很长一段时间以来,在为浏览器的标准支持而挣扎时,我没有空闲时间,也没有兴趣在服务器端做同样的事情。而今天,我看到了一个巨大的机会,可以构建能够针对越来越多的客户的应用程序,而无需首先强制他们使用特定的操作系统...
今天,我可以专注于代码——一切都在一个地方,而将配置留给系统管理员。感觉很棒!:-)