
, localhost 直接换成公网域名即可,vs不需要使用管理员权限打开
.NET Core CLI 概述;
使用.NET Core CLI 实现 .NET Core 入门
dotnet工具(称为驱动程序)作为cli命令的入口,能够接收并执行命令。同时,它可以选择要使用的cli的sdk版本,如果未指定版本,默认使用最新的sdk版本,如果要指定版本使用dotnet --fx-version 命令。
dotnet --info
查看当前安装的版本。
指定项目使用的SDK版本
dotnet new global.json --sdk-version ,要注意的是最后的参数是SDK版本,不是.NET Core版本。
dotnet new
用于创建一个新项目,关于项目类型,可以用命令:dotnet new --help查看。
dotnet restore
恢复项目的依赖项和工具。
dotnet restore命令使用NuGet还原project.json文件中被指定的依赖项,以及特定于项目的工具。默认情况下,依赖项和工具的还原是并行完成的。
对于依赖项,你可以在还原操作时使用–packages参数指定还原包的位置。
如果没有指定,则默认使用NuGet包缓存。它可以在所有的操作系统上的用户目录下的.nuget/packages目录中找到(例如,Linux上的/home/user 或者是Wndows上的C:\Users\user)。Windows下可以使用%HOMEPATH%/.nuget/packages访问目录。
对于特定项目的工具,,dotnet restore 首先还原该工具包,然后继续还原在项目文件中指定的工具依赖项。
dotnet restore 选项如下。
-s,–source[SOURCE]
指定一个在还原操作期间使用的源。这覆盖所有在NuGet.config文件中指定的源。多个源可以通过多次指定该选项来提供。
–packages[DIR]
指定放置还原包的目录。
–disable-parallel
禁用并行还原多个项目。
–configfile[FILE]
用于还原操作的配置文件(NuGet.config)
–verbosity[LEVEL]
使用日志详细级别。允许的值:Debug,Verbose,Infomation,Minimal,Warning或者Error。
可以使用dotnet restore -h 来查看命令的帮助。
dotnet build
#创建MVC工程
[zebra Desktop]$mkdir work && cd work
[zebra work]$dotnet new console -n Zebra.Liu.Console
#添加Nuget包,这是添加了mysql的驱动包
[zebra work]$cd Zebra.Liu.Console/
[zebra Zebra.Liu.Console]$dotnet add package Pomelo.EntityFrameworkCore.MySql
[zebra Zebra.Liu.Console]$ls
Program.cs Zebra.Liu.Console.csproj obj
#编译工程,输出到build文件夹中
[zebra Zebra.Liu.Console]$dotnet build -o build/
[zebra Zebra.Liu.Console]$ls
Program.cs Zebra.Liu.Console.csproj build obj
#多了一个build文件夹,用于存放编译后的文件
[zebra Zebra.Liu.Console]$ls build/
Zebra.Liu.Console.dll #二进制文件(中间语言IL)
Zebra.Liu.Console.pdb #symbol文件,用于调试
Zebra.Liu.Console.deps.json # *.deps.json中列出应用程序的依赖项
Zebra.Liu.Console.runtimeconfig.dev.json
Zebra.Liu.Console.runtimeconfig.json # 指定共享运行时以其版本
上面的试验可以看出build命令最后的产物,如果有项目使用了第三方的依赖,如像上面一样使用了NuGet中的MySQL驱动,那么在执行build命令时,只会从本机的NuGet缓存库中解析依赖项,也就是说,生成的dll文件,不包含MySQL的驱动。这样build的dll文件,从本机拷贝到另外的机器上,可能不能运行。如果要将依赖项也打包,可以使用另一个命令dotnet publish。
publish命令和build命令最大的区别在于:publish命令将应用程序的依赖项,从NuGet缓存复制到输出文件夹中。
build后的程序是否为可执行,主要看.csproj文件中的选项
<!-- .csproj文件内容 -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!--
OutputType 输出类型设置为Exe,说明这个工程,可执行
可执行的dll和不可执行的dll文件最大的区别在于,有没有入口点以及是否可执行
-->
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
</Project>
dotnet watch run
dotnet watch 是一个开发阶段在源文件发生变动的情况下使用 dotnet 命令的工具。 当代码发生变动的时候可以用来执行编译,运行测试,或者发布操作。
dotnet clean
清除上一个生成的输出,包括中间文件夹(obj)和最终输出文件夹(bin)中的文件
dotnet sln
使用dotnet sln命令,可以在*.sln,文件中添加,删除,获取项目。
dotnet sln [] add
dotnet sln [] add
dotnet sln [] remove
dotnet sln [] remove
dotnet sln [] list
注意:dotnet sln命令只会对solution_name.sln文件中操作,要生成*.sln文件,可以使用dotnet new sln命令
dotnet add/remove/list reference
管理项目之间的引用关系
# 项目1引用项目2
[zebra work]$dotnet add 项目1 reference 项目2
本章将用cli,按照DDD的分层架构来创建包含多个项目的解决方案,方案的结构大致如下:
# 工程目标结构
- Shopping.sln
|- Shopping.UI
|- Shopping.Application
|- Shopping.Domain
|- Shopping.Infrastructure
# 工程引用关系
Shopping.UI
|
V
Shopping.Application
| \
V \
Shopping.Domain \
| \
V V
Shopping.Infrastructure
脚本如下:
# 创建目录
[zebra ~]$mkdir Shopping && cd Shopping
# 创建工程 UI为MVC类型,其余全部是classlib
[zebra Shopping]$dotnet new mvc -n Shopping.UI
[zebra Shopping]$dotnet new classlib -n Shopping.Application
[zebra Shopping]$dotnet new classlib -n Shopping.Domain
[zebra Shopping]$dotnet new classlib -n Shopping.Infrastructure
# 添加工程之间的引用
[zebra Shopping]$dotnet add Shopping.UI reference Shopping.Application
[zebra Shopping]$dotnet add Shopping.Application reference Shopping.Domain
[zebra Shopping]$dotnet add Shopping.Application reference Shopping.Infrastructure
[zebra Shopping]$dotnet add Shopping.Domain reference Shopping.Infrastructure
# 创建Shopping.sln文件,将工程文件加入到解决方案(.sln)文件中
[zebra Shopping]$dotnet new sln -n Shopping
[zebra Shopping]$dotnet sln add Shopping.*
# 添加NuGet依赖包,这里以在Shopping.Infrastructure工程中添加MySQL驱动为例
[zebra Shopping]$cd Shopping.Infrastructure/
[zebra Shopping.Infrastructure]$dotnet add package Pomelo.EntityFrameworkCore.MySql
dotnet new web
dotnet run
code .
http://localhost:5000
MSBuild 是 Microsoft Build Engine 的缩写,MSBuild 是 Visual Studio 的生成系统。我们在VS中使用“生成解决方案”的背后就是依赖MSBuild来实现,但我们完全可以在cmd中使用MSBuild来完成应用的构建。
它不仅仅是一个构造工具,应该称之为拥有相当强大扩展能力的自动化平台。MSBuild平台的主要涉及到三部分:执行引擎、构造工程、任务。其中最核心的就是执行引擎,它包括定义构造工程的规范,解释构造工程,执行“构造动作”;构造工程是用来描述构造任务的,大多数情况下我们使用MSBuild就是遵循规范,编写一个构造工程;MSBuild引擎执行的每一个“构造动作”就是通过任务实现的,任务就是MSBuild的扩展机制,通过编写新的任务就能够不断扩充MSBuild的执行能力。所以这三部分分别代表了引擎、脚本和扩展能力。
MSBuild 在如何处理和生成软件方面是完全透明的,使开发人员能够在未安装 Visual Studio 的生成实验室环境中组织和生成产品。
MSBuild 引入了一种新的基于 XML 的项目文件格式,这种格式容易理解、易于扩展并且完全受 Microsoft 支持。MSBuild 项目文件的格式使开发人员能够充分描述哪些项需要生成,以及如何利用不同的平台和配置生成这些项。另外,项目文件的格式还使开发人员能够创作可重用的生成规则,这些规则可以分解到不同的文件中,以便可以在产品内的不同项目之间一致地执行生成。以下各节描述了 MSBuild 项目文件格式的一些基本要素。
先说说构造工程,只要通过Notepad打开任何一个Visual Studio下的C#工程(csproj)文件,就知道构造工程到底是怎么回事了。
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Root>$(MSBuildStartupDirectory)Root>
PropertyGroup>
<Target Name="Build">
<ItemGroup>
<ProjectToBuild Include="$(Root)\..\src\Foundation\Common\Gimela.Common.sln" />
<ProjectToBuild Include="$(Root)\..\src\Foundation\Infrastructure\Gimela.Infrastructure.sln" />
<ProjectToBuild Include="$(Root)\..\src\Foundation\Management\Gimela.Management.sln" />
<ProjectToBuild Include="$(Root)\..\src\Foundation\Security\Gimela.Security.sln" />
<ProjectToBuild Include="$(Root)\..\src\Foundation\Tasks\Gimela.Tasks.sln" />
<ProjectToBuild Include="$(Root)\..\src\Foundation\Text\Gimela.Text.sln" />
<ProjectToBuild Include="$(Root)\..\src\Foundation\Net\Gimela.Net.sln" />
<ProjectToBuild Include="$(Root)\..\src\Foundation\ServiceModel\Gimela.ServiceModel.sln" />
<ProjectToBuild Include="$(Root)\..\src\Foundation\Data\Gimela.Data.sln" />
<ProjectToBuild Include="$(Root)\..\src\Foundation\Presentation\Gimela.Presentation.sln" />
<ProjectToBuild Include="$(Root)\..\src\Foundation\Media\Gimela.Media.sln" />
<ProjectToBuild Include="$(Root)\..\src\Foundation\Streaming\Gimela.Streaming.sln" />
<ProjectToBuild Include="$(Root)\..\src\Crust\Gimela.Crust.sln" />
ItemGroup>
<MSBuild Projects="@(ProjectToBuild)" Targets="Build" Properties="Configuration=Debug;">
<Output TaskParameter="TargetOutputs" ItemName="AssembliesBuiltByChildProjects" />
MSBuild>
Target>
Project>
在构造工程中我们可以定义和使用变量(通过Property/PropertyGourp/Item/ItemGroup等元素),可以使用条件分支(通过Choose/When/Otherwise等元素)、能够在运行时给变量赋值(通过执行任务,获取其返回类型参数的方式)、能够定义执行块(通过Target元素,相当于函数)、能够进行异常处理(通过OnError元素)、还可以复用已有工程定义的内容(通过Import元素)。拥有这些能力和高级语言已经相差无几了,所以笔者认为构造工程不是描述性语言,而是脚本语言。
这里还需要强调一点的是,项目级元素(Property)可以在元素下定义,也可以在构造过程中作为外部参数传入,这是一个非常有用的特性,一般编译时选择配置项(Debug或者Release)就是利用这个特性实现的。
这是每一个项目文件的最外层元素,它表示了一个项目的范围。如果缺少了这一元素,MSBuild会报错称Target元素无法识别或不被支持。
Project元素拥有多个属性,其中最常用到的是DefaultTargets属性。我们都知道,在一个项目的生成过程中可能需要完成几项不同的任务(比如编译、单元测试、check-in到源代码控制服务器中等),其中每一项任务都可以用Target来表示。对于拥有多个Target的项目,你可以通过设置Project的DefaultTargets(注意是复数)属性来指定需要运行哪(几)个Target,如果没有这个设置,MSBuild将只运行排在最前面的那个Target。
在项目中你肯定需要经常访问一些信息,例如需要创建的路径名、最终生成的程序集名称等。以name/value的形式添加进Property,随后就可以以$(PropertyName)的形式访问。这样你就无须为了改动一个文件名称而让整个项目文件伤筋动骨了。比如上面代码中的Bin就是将要创建的路径名称,而AssemblyName则是最终要生成的程序集名称。这些属性的名称不是固定的,你完全可以按自己的习惯来进行命名。在使用时,你需要把属性名称放在”$(“和”)”对内(不包括引号),以表示这里将被替换成一个Property元素的值。
另外,如果Property元素数量比较多,你还可以把它们分门别类地放在不同的PropertyGroup里,以提高代码的可阅读性。这对Property本身没有任何影响。
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">DebugConfiguration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPUPlatform>
<ProductVersion>8.0.30703ProductVersion>
<SchemaVersion>2.0SchemaVersion>
<ProjectGuid>{6C2561FB-4405-408F-B41B-ACE5E519A26E}ProjectGuid>
<OutputType>LibraryOutputType>
<AppDesignerFolder>PropertiesAppDesignerFolder>
<RootNamespace>Gimela.Infrastructure.PatternsRootNamespace>
<AssemblyName>Gimela.Infrastructure.PatternsAssemblyName>
<TargetFrameworkVersion>v4.0TargetFrameworkVersion>
<FileAlignment>512FileAlignment>
PropertyGroup>
在整个项目文件中你肯定要提供一些可被引用的输入性资源(inputs)信息,比如源代码文件、引用的程序集名称、需要嵌入的图标资源等。它们应该被放在Item里,以便随时引用。语法是:。
其中Type属性可以被看作是资源的类别名称,比如对于.cs源文件,你可以把它们的Type都设置为Source,对于引用的程序集把Type都设置为Reference,这样在随后想引用这一类别的资源时只要引用这个Type就可以了,方法是@(TypeName)。可千万别和Property的引用方法弄混了。
既然Type是资源的类名,那么Include就是具体的资源名称了,比如在上面的示例代码中,Include引用的就是C#源代码文件的名称。你也可以用使用通配符*来扩大引用范围。比如下面这行代码就指定了当前目录下的所有C#文件都可以通过@(Source)来引用:。
另外,你也可以通过与PropertyGroup类似的方法把相关的Item放在ItemGroup里。
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Xml" />
ItemGroup>
<ItemGroup>
<Compile Include="Commands\CommandBase.cs" />
<Compile Include="Commands\DuplexCommandBase.cs" />
<Compile Include="Commands\ICommand.cs" />
<Compile Include="Commands\IDuplexCommand.cs" />
<Compile Include="Extensions\BitConverterExtensions.cs" />
<Compile Include="Extensions\ConcurrentDictionaryExtensions.cs" />
<Compile Include="Extensions\StopwatchExtensions.cs" />
<Compile Include="Extensions\TimeSpanExtensions.cs" />
<Compile Include="Flyweight\FlyweightObjectPool.cs" />
<Compile Include="Singleton\StaticSingleton.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SmartQueue\ISmartQueueMapper.cs" />
<Compile Include="SmartQueue\SmartQueue.cs" />
<Compile Include="SmartQueue\SmartQueueBase.cs" />
<Compile Include="SmartQueue\SmartQueueMapper.cs" />
<Compile Include="UnitOfWork\IUnitOfWork.cs" />
<Compile Include="UnitOfWork\IUnitOfWorkFactory.cs" />
<Compile Include="UnitOfWork\UnitOfWork.cs" />
<Compile Include="WeakActions\IWeakActionExecuteWithObject.cs" />
<Compile Include="WeakActions\WeakAction.cs" />
<Compile Include="WeakActions\WeakActionGeneric.cs" />
<Compile Include="WeakFuncs\IWeakFuncExecuteWithObjectAndResult.cs" />
<Compile Include="WeakFuncs\WeakFunc.cs" />
<Compile Include="WeakFuncs\WeakFuncGeneric.cs" />
ItemGroup>
Target表示一个需要完成的虚拟的任务单元。每个Project可以包括一个或多个Target,从而完成一系列定制的任务。你需要给每个Target设置一个Name属性(同一Project下的两个Target不能拥有同样的Name)以便引用和区别。
举例来说,在你的项目生成过程中可能需要完成三个阶段的任务:首先check-out源代码,接下来编译这些代码并执行单元测试,最后把它们check-in。那么通常情况下你可以创建三个不同的Target以清晰划分三个不同的阶段:
<Target Name=”CheckOut” >Target>
<Target Name=”Build” DependsOnTargets=”CheckOut”> <Task Name=”Build”.../> <Task Name=”UnitTest” ... />Target>
<Target Name=”CheckIn” DependsOnTargets=”CheckOut;Build”>Target>
这样,你就可以非常清晰地控制整个生成过程。为了反应不同Target之间的依赖关系(只有Check-in后才能编译,只有编译完成才可能Check-out……),你需要设置Target的DependsOnTargets属性(注意是复数),以表示仅当这些Target执行完成之后才能执行当前的Target。当MSBuild引擎开始执行某项Target时(别忘了Project的DefaultTargets属性),会自动检测它所依赖的那些Target是否已经执行完成,从而避免因为某个生成环节缺失而导致整个生成过程发生意外。
你可以通过Project的DefaultTargets属性指定MSBuild引擎从哪(几)个Target开始执行,也可以在调用MSBuild.exe时使用t开关来手动指定将要运行的Target,方法如下:MSBuild /t:CheckOut 这样,只有CheckOut(以及它所依赖的Target,在上文中没有)会被执行。
这可能是整个项目文件中最重要的,因为它才是真正可执行的部分(这也是为什么我在上面说Target是虚拟的)。你可以在Target下面放置多个Task来顺序地执行相应的任务。
在Linux上安装需要的环境,然后将上面成功运行的项目发布:





将文件夹拷贝到linux下,进入文件夹运行:dotnet WebApplication1.dll。
在上面发布的core2.2版本文件夹下面创建名称为Dockerfile的文件,内容:
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2
EXPOSE 8080
WORKDIR /app
COPY . /app
ENTRYPOINT ["dotnet", "WebApplication1.dll"]
对于core3版本:
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
COPY . /app
ENTRYPOINT ["dotnet", "demoapi.dll"]
FROM是指定该镜像的运行环境;然后指定容器映射端口8080;最后dotnet运行项目。
把目录文件通过dockerfile加载到docker镜像:docker build -t gateway:1.0 .。
调用命令 docker images 会发现已经有这个镜像了。

最后,运行镜像:docker run -p 8080:8080 testsite:1.0(8080:8080表示的是容器内端口映射容器外端口)。
选中"启用Docker支持“,这样可以自动生成Dockerfile文件,这个文件是用来生成Docker镜像;如果没有选中"启用Docker支持“,也可在项目生成后,右击项目-添加-Docke支持;
选择Docker运行系统,代码编写阶段Dockerfile文件可以随意修改和删除,使用Docker调试或生成镜像时重新添加一个就行了。

生成了Dockerfile文件。生成的内容一大堆,在不修改情况下使用VS自带的Docker运行没有问题,但部署时却有麻烦,简化一下就行了;
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
COPY . .
ENTRYPOINT ["dotnet", "SamruoFrame.Service.Cache.Redis.dll"]
Dockerfile文件默认在发布时不会复制到发布文件夹中,属性修改为:始终复制。
程序以正常发布生成,使用”文件夹”模式,以上几个发布方式需要Azure,本文不涉及此内容(主要是没钱)

生成镜像有多种方式,我使用本地生成;进入发布目录,运行Powershell,使用命令行生成镜像
docker build -t samruo/redis:1.1 .
//语法:docker build [OPTIONS] PATH | URL | -
//--tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
//需要注意的是 1:name不能用大写;2:语句最后必须加“ .” 且前面要有一个空格。

这里出现了一个错误信息:
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have ‘-rwxr-xr-x’ permissions. It is recommended to double check and reset permissions for sensitive files and directories.
经查是因为我在Windows下生成,但将使用在Linux下,所以有个目录权限问题。考虑到我的程序只用来做服务,且采用集群,不会在本地保存文件,理论上不会有此问题,就先使用这个方法。
为方便集群化部署,以及后继运维方便,将镜像放到仓库中是个好办法。实现Docker仓库的方法有很多,国内和国外都的,Docker Hub速度太慢,我使用阿里的镜像仓库,因为本身服务器是在阿里云。
首先要有个阿里云帐号;登录后进入控制台,搜索”容器镜像服务“,首次使用需要开通服务,根据阿里云的使用经验,貌似使用容器镜像服务免费,还有个”弹性容器实例ECI“是需要收费的,其实就是个Docker,按秒计费的。
第一步:创建命名空间

第二步:创建镜像仓库

仓库创建后,点击仓库名称进入详情页

1、 登录阿里云Docker Registry
sudo docker login --username=【这里是阿里云登录帐号】 registry.cn-hangzhou.aliyuncs.com

2、将镜像推送到Registry
sudo docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/samruo_docker/redis:[镜像版本号]
sudo docker push registry.cn-hangzhou.aliyuncs.com/samruo_docker/redis:[镜像版本号]
正确执行命令后,镜像将出现在”镜像版本“之中;

版本更新只需要修改命令行后面的”镜像版本号“即可,非常简单。到此,镜像仓库部署完成。
建立了镜像后在部署操作将非常简单。执行以下命令前需要执行登录操作,见上节上传镜像,如已登录则无需执行。
//从Registry中拉取镜像,在一台服务器中,同一个版本的镜像只需要拉取一次即可
docker pull registry.cn-hangzhou.aliyuncs.com/samruo_docker/redis:[镜像版本号]

//部署镜像
//语法:docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
docker run --name samruo-redis-1 -d -p 5902:80 registry.cn-hangzhou.aliyuncs.com/samruo_docker/redis:1.1
// --name="nginx-lb": 为容器指定一个名称;
// -d: 后台运行容器,并返回容器ID;
// -p: 指定端口映射,格式为:主机(宿主)端口:容器端口;这里和80端口与Dockerfile文件中的EXPOSE 80对应;

在同一台宿主服务器上只要”主机(宿主)端口“不重复,可以部署多个同一镜像,不会相互干扰,这也将用于集群化需求。成功执行后将只返回容器ID;
查看镜像部署情况:docker ps -a。

1.Dockerfile完全不需要修改,可保持自动创建时的内容.EXPOSE可根据开放端口不同,自行调整.
2.使用"发布"->“容器注册表”:首次发布需要创建发布模板,如下:

在下图中填写阿里容器仓库地址:
格式为:https://服务区域地址/命名空间 (具体见自己开通仓库的公网地址)
注意不要填写仓库名称,仓库名称VS将自动补充,也就是项目名称,这也是唯一于手工上传相比不可控之处.
填写用户名和密码,好像没什么用,最好在上传时,以代码登录一下.见以下内容.

保存后分发镜像模板就完成了,以后只要点击发布就能自动上传到私有仓库中.

每次发布镜像最好区分版本号,见上图"图像标记"(话说这翻译也是够直接),修改上图所未标记后,点击"发布"即可.
VS自动创建的Dockerfile文件有20行,发布需要点时间,这时可以打开PowerShell 运行 登录阿里云Docker Registry命令:$ sudo docker login --username=[阿里帐号] registry.cn-hangzhou.aliyuncs.com。
我这里执行手动登录的原因,是遇到发布时提示帐号验证错误,应该是VS调用本地Docker Desktop来执行上传,但Docker Desktop本身未登录,所以会执行出错.
好了,现在每次发布新的版本只需要点一下"发布"并修改一下版本号,就可以方便的更新程序镜像了。
将物理机的串口映射到容器中去:docker run --rm -it --device /dev/ttyS1:/dev/ttyS0 ubuntu:14.04 /bin/sh
将物理机的串口映射到Web镜像容器中去:docker run --rm -it --device /dev/ttyS1:/dev/ttyS0 -p 9600:9694 gateway:1.0