针对 IIS 部署 ASP.NET 5 应用程序的问题,在上面博文中主要采用两种方式尝试:

  1. VS2015 的 Publish 方式(成功)。

  2. 手动复制文件的方式(失败)。

本篇博文主题:

  1. 初步理解 ASP.NET 5 的编译及部署问题。

  2. 解决 ASP.NET 5 预编译,并成功部署到 IIS 上。

先啰嗦两句:

首先,现在的 IIS 对于 ASP.NET 5 来说,其实已经没有太大的关系了,用不用它都无所谓,ASP.NET 5 应用程序可以采用 Self-Hosting(自寄宿)的方式,也就是自己当作 Web 服务器,实现方式只要在 project.json 中添加 Microsoft.AspNet.Hosting 程序包就可以了,我们知道,Web 服务器的作用其实就是监听请求,并分发请求给相应模块进行处理,最后再输出处理结果,如果我们把这个请求处理过程进行分化,交给不同的组件或模块进行处理,我们就可以脱离集成化的 IIS,在 Owin 体系结构中,ASP.NET 5 应用程序部署到 IIS 上,这个 IIS 的作用就只是一个 Host(管理应用程序的线程,打开、关闭或加载组件),Server(如 Microsoft.AspNet.Server.WebListener)和 Middleware(如 Microsoft.AspNet.StaticFiles)都有相应的处理组件,不需要用到 IIS 的那一套东西,比如之前我们需要在 IIS 中进行 HttpModule 配置等。

既然我们不使用 IIS 的那一套东西,如果将 ASP.NET 5 应用程序部署到 IIS 上,那就必须有个东西将 IIS 和 ASP.NET 5 独立运行时(KRE)之间的通道(Pipeline)打通,这个东西就是 AspNet.Loader.dll(位置:application/wwwroot/bin),使用它的条件是在 project.json 中添加 Microsoft.AspNet.Server.IIS 程序包,而且只在 IIS 中才会出现,如果采用的是 Self-Hosting 方式,这个程序集将会“消失”,关于 AspNet.Loader.dll,一篇非常好的解答文章:AspNet.Loader.dll – what is this for?

另外,在 IIS 中部署 ASP.NET 5 应用程序,不像我们之前的方式,直接把相应的文件和程序集拷贝到站点目录下,因为你会发现,在生成 ASP.NET 5 项目的时候没有生成对应的程序集,只是在 Bin 目录下有一个 AspNet.Loader.dll,dudu 已经试过在 ASP.NET 5 项目属性页的 Build 选项中,把“Produce outputs on build”选项打勾,就会生成相应的程序集文件(位置:../application/artifacts/bin),既然有了程序集,那是不是就可以像之前部署站点的方式进行操作呢?答案当然不是,这也就是为什么手动复制文件会失败,因为 ASP.NET 5 并没想象中的那么简单,呵呵,采用 VS2015 的 Publish 方式部署是成功的,那我们就从它下手,观察下它到底是怎么回事,dudu 没做完这步,我们下面接着做。

言归正传

用 VS2015 创建一个简单的 ASP.NET 5 项目:

project.json 配置:

{
    "webroot": "wwwroot",
    "version": "1.0.0-*",
    "exclude": [
        "wwwroot"
    ],
    "dependencies": {
        "Microsoft.AspNet.Server.IIS": "1.0.0-beta1",
        "Microsoft.AspNet.Server.WebListener": "1.0.0-beta1",
        "HelloClassLibrary": "1.0.0-*"
    },
    "commands": {
        /* Change the port number when you are self hosting this application */
        "web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000"
    },
    "frameworks": {
        "aspnet50": { },
        "aspnetcore50": { }
    }
}

Startup.cs 代码:

using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;

namespace HelloAspNet5
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.Run(context => context.Response.WriteAsync("Hello, ASP.NET 5 world! \{HelloClassLibrary.TestClass.testContent}"));
        }
    }
}

为了演示 ASP.NET 5 与依赖类库之间的关系,我们还创建了一个 HelloClassLibrary 项目,并在 Startup 中进行调用。在 project.json 中配置 commands 是为了方便我们通过 Publish 发布出来的程序检测是否可以正常运行。

Publish 发布文件结构:

approot 为程序文件及程序包目录,packages 下面是整个应用程序运行所需要的程序包,src 为程序源代码目录,wwwroot 为站点目录(我们等下 IIS 部署的时候,就是指向的这个目录),里面有一个 bin 目录(只有一个 AspNet.Loader.dll)和一个 web.config 文件(里面主要配置一些地址路径,比如程序包的地址路径)。web.cmd 是批处理文件,它其实就是我们在 commands 中制定的命令(还可以配置 EF 迁移的命令)。

上面是本地运行结果(注意,我并没有安装 IIS),你也可以直接点击 web.cmd 来启动站点。

既然本机运行没问题,那我们就把它发布到 IIS 上试试,但是却报下面的错误:

错误信息:Couldn't find package 'KRE-CLR-amd64.1.0.0-beta1'. Locations probed,什么意思?本机是 32 位系统,服务器是 64 位系统,发布设置我们选的是“KRE-CLR-x86.1.0.0-beta1”,不报错才怪,重新更改发布设置为“KRE-CLR-amd64.1.0.0-beta1”:

然后把发布生成 HelloAspNet5.Web\approot\packages\KRE-CLR-amd64.1.0.0-beta1 下的文件,添加到服务器相应目录下,重新刷新浏览器,运行成功:

Hello, ASP.NET 5 world!  HeiHei

上面这些操作其实 dudu 已经完成了,并且也是成功的,但这并不是我们想要的,因为这时候,服务器运行的程序是“动态编译”的,程序代码地址就在 HelloAspNet5.Web\approot\src 目录下,里面直接是源代码,并不是生成的程序集文件,那该如何“预编译”我们的 ASP.NET 5 应用程序呢?

首先,我们需要生成我们的应用程序,就像我们之前的 ASP.NET 应用程序一样,而 ASP.NET 5 默认是“动态编译”的,如果想设置成预编译,需要我们手动设置一下:

在项目属性页的 Build 选择中,把“Produce outputs on build”打勾就可以了,HelloClassLibrary 类库项目同样这样设置,设置好了,重新生成项目,生成的程序集目录为:\HelloAspNet5\artifacts\bin\HelloAspNet5\Debug,在相应目录下,就会看到我们想要的两个程序集文件:HelloAspNet5.dll 和 HelloClassLibrary.dll,既然项目程序集生成成功,那部署应该没有什么问题,但 dudu 已经尝试过,是失败的,问题出在哪?直接把相应的程序集文件拷贝过去不行吗?当然不行,因为我们使用 VS2015 Publish 的时候,运行环境是“动态编译”,那怎么使 Pulish 的时候预编译呢?答案就在下面:

在 Publish 的时候,还有一个发布选项“Precompile during publishing”,就是配置预编译的,重新发布一下,你会发现在 packages 目录下多了两个文件夹:

这就是预编译生成的项目文件地址,我们再来看下 HelloAspNet5 的文件目录:

一个是 lib 目录,里面就是生成的程序集文件,root 是程序的其他类型文件,比如 MVC Views 文件就在这里,而不是在 wwwroot 目录下。另外一个非常重要的地方就是,wwwroot 下的 web.config 变成了:



  
    
    
    
    
    
    
  

好像也没怎么变化?跟之前的自习对比一下,会发现 key(kre-app-base)的 value 值不见了,之前的配置为

看到这你应该懂了,没错,你可以把 approot\src 目录下的 HelloAspNet5 源代码文件删掉了,使用 k web 命令重新启用项目,运行成功:

Hello, ASP.NET 5 world!  HeiHei

接着我们重新在服务器的 IIS 上也这样搞,但是却出现了下面的错误:

错误信息:Couldn't find package 'KRE-CLR-amd64.1.0.0-beta1',找不到“KRE-CLR-amd64.1.0.0-beta1”程序包,在 approot\packages 目录下明明就有啊,查看下面的提示发现 IIS 并没有从这个目录下进行查找,怎么办?手动配置一下:

需要注意的是,要配置到 root 目录下,重新启动项目,运行成功:

Hello, ASP.NET 5 world!  HeiHei

我们接下来修改代码,然后通过编译程序集上传到 IIS 服务器。

app.Run(context => context.Response.WriteAsync("Hello, ASP.NET 5 world!!! \{HelloClassLibrary.TestClass.testContent}"));
public static string testContent = " HeiHeiHei";

可以看出,上面代码和之前的代码是有些不同的,之前有说过使用 VS2015 编译的程序集地址为 artifacts,我们把相应的程序集文件上传到服务器的 approot\packages 目录下,奇怪的事,我运行后发现改的代码并没有效果,还是之前的,这个问题搞了我很久,都快被搞死了,我以为是生成的程序集文件有问题,没有生成最新的,就各种设置生成测试,最后还是没有成功,然后无意间把 IIS 站点停掉,然后再进行上传程序集文件,最后再启动站点,刷新浏览器,你猜怎么着?Oh, My God !!!,居然出现了:

Hello, ASP.NET 5 world!!!  HeiHeiHei

最后再啰嗦两句:

从上面整个配置过程中可以看出,ASP.NET 5 是如何在 IIS 进行运行的(只是从 Publish 上来看),首先,站点目录为 wwwroot,包含一个 bin 目录的 AspNet.Loader.dll 文件(用来打通 IIS 和 Owin 组件之间的管道),还有一个 web.config 文件(主要配置程序包及应用程序路径地址),除此之外,还有一些静态文件(如 css、js 和图片文件),如果是动态编译站点,默认直接运行 approot\src 目录下的源代码文件,如果是预编译站点,根据 kre-app-base 的路径配置(如 approot\packages\HelloAspNet5\1.0.0\root),运行 ASP.NET 5 运行程序,注意在 root\project.json 文件中,有如下配置:"webroot": "..\..\..\..\..\wwwroot",这个指向的地址就是 IIS 站点。。。

其实,上面虽然只是一个简单的 IIS 部署过程,但涉及到非常多的东西,也有很多的感触,或许有些没表达出来,总的来说,这次部署给我最重要的感受是:ASP.NET 5 完全的组件化,不只是开发,部署也是如此,这样的结果就是充满着无限可能,看我七十二变!!!