探索-C--高级特性-二-

94 阅读57分钟

探索 C# 高级特性(二)

原文:Exploring Advanced Features in C#

协议:CC BY-NC-SA 4.0

三、C# 8.0 的新特性

C# 的设计过程是开源的。你可以前往位于 https://github.com/dotnet/csharplang 的知识库,看看围绕语言设计的一些讨论。事实上,会议文件非常吸引人。

一旦你进入了 GitHub repo,就可以在 dotnet/csharplang/meetings 上查看按年份组织的文档集合。

第一件让我印象深刻的事情是,围绕 C# 语言的思维是非常结构化和深思熟虑的。在整个存储库中,您会看到最后提交日期总是很近。因此,这证明了您正在查看的存储库是一个动态文档,您可以跟随它并保持更新。

C# 8.0 呢?事实是,即使 C# 团队发布了 C# 7 的增量点版本(C# 7.1 到 C# 7.3),他们也在开发 C# 8.0。

本章将介绍 C# 8.0 的以下新特性:

  • 可为空的引用类型

  • 递归模式

  • 范围和指数

  • 切换表达式

  • 目标类型的新表达式

  • 异步流

  • 使用声明

为了按照我将在本章中演示的代码清单编写代码,您需要一份 Visual Studio 2019。在撰写本章时,Visual Studio 2019 预览版(版本 16.0.0 预览版 2.0)已经可供下载。

确保如果使用的是 Visual Studio 2019 的预览版,已经从高级构建设置中选择了 C# 8.0 (beta)(图 3-1 )。为此,右击项目并选择属性。然后选择构建选项卡,然后点击高级按钮。

img/478788_1_En_3_Fig1_HTML.jpg

图 3-1。

高级构建设置

请注意,以下文本中说明的一些功能在 C# 8.0 的预览版和最终版本之间可能会有细微的变化。在写这本书的时候,本章的代码在语法上是正确的。

首先,让我们看看什么是可空引用类型。

可为空的引用类型

如果你回想一下第二章,我们讨论过可空类型。我们说过所有引用类型(比如字符串)都是可空的,引用类型的默认值是null。随着 C# 2.0 的发布,微软引入了可空值类型。

我不打算重复引用类型和值类型之间的区别。如果你不确定,我会让你自己去阅读。引用类型现在可以为空这一事实(在我看来)是开发人员长期以来所需要的。使引用类型可空背后的思想是帮助开发人员避免NullReferenceException异常。

你应该还记得上一章的内容,为了将一个变量标记为可空,你需要在声明一个变量时使用类型和?。例如,int?代表一个可空的int。现在你可以对引用类型做同样的事情,比如string?来声明一个可空的string

这一增加的好处是,你现在可以更清楚地表达你的设计意图。我可以说,一些变量可能有值,而另一些必须有值。

启用可为空的引用类型

在 C# 8.0 中,默认情况下不启用此功能。即使您正在创建 C# 8.0 应用,也必须选择可空引用类型特性。打开可空引用类型特性后,所有引用变量声明都将变成不可空的引用类型。因此,在启用可空引用类型时,您需要注意这一点。

即使启用了可空引用类型,Visual Studio 也只会在遇到设置为null的不可空引用类型时显示警告。

这意味着如果您创建一个引用类型(例如一个string变量声明)而没有启用可空引用类型,您将不会看到任何警告。请考虑以下情况。

img/478788_1_En_3_Fig2_HTML.jpg

图 3-2。

没有可为空的引用类型警告

图 3-2 中显示的警告是已分配的变量,但从未使用过警告。要在应用中启用可空引用类型特性,需要在源文件中的任意位置添加一个新的 pragma #nullable enable。这将打开可空引用类型特性。

img/478788_1_En_3_Fig3_HTML.jpg

图 3-3。

可空引用类型已打开

该警告显示在错误列表中(图 3-3 )。如果在现有项目上启用此功能,您可能会遇到一些这样的警告。

pragma #nullable enable还支持disable关闭可空引用类型特性。

如果您需要为整个项目启用可空引用类型,请打开您的.csproj文件并查找LangVersion元素。

img/478788_1_En_3_Fig4_HTML.jpg

图 3-4。

为项目启用可为空的引用类型

然后你需要在LangVersion元素后面添加<NullableReferenceTypes>true</NullableReferenceTypes>,如图 3-4 所示。

概述

概括地说,在 C# 8.0 中,我们现在有可空的引用类型和不可空的引用类型。这些使您能够告诉编译器您使用引用类型变量的确切意图。

为了在 C# 8.0 中启用可空引用类型变量,您需要使用一个新的 pragma #nullable。编译器将以两种方式之一解释您的意图。这些如下。

引用类型变量不能为空

如果引用类型变量不应该是null,编译器将强制执行该规则,以确保在不检查变量是否为null的情况下使用变量是安全的。这意味着变量必须初始化为非空值。因此,变量永远不会被赋予一个null值。

引用类型可能为空

当我们声明一个可空的引用类型变量时,我们是在告诉编译器变量值有可能是null。编译器现在将强制执行不同的规则,以确保您已经检查了空引用。因此,您可以用默认的null来初始化这些变量。

递归模式

递归模式是 C# 的一个受欢迎的补充。你会记得在 C# 7 中,我们看到了模式匹配的引入。C# 8.0 更进一步,允许模式包含其他模式。考虑下面的类。

public class Person
{
    public int Age { get; }
    public string Name { get; }
    public bool RegisteredToVote { get; set; }

    public Person(string name, int age, bool registered)
    {
        Name = name;
        Age = age;
        RegisteredToVote = registered;
    }
}

Listing 3-1Person class

该类包含一个布尔值,表明该人是否已注册投票。递归模式将允许我们通过以下操作提取那些没有注册投票的人。

foreach (var person in personList)
{
    if (person is Person { RegisteredToVote: false })
    {
        WriteLine($"{person.Name} has not registered.");
    }
}

Listing 3-2Recursive pattern

我们在这里说的是,如果列表中的一个对象属于类型Person,并且这个人的属性RegisteredToVote被设置为false,那么显示这个人的名字。

img/478788_1_En_3_Fig5_HTML.jpg

图 3-5。

智能感知可用

您还会注意到,如果您需要向模式添加另一个条件,Intellisense 是可用的(图 3-5 )。将以下资格属性添加到您的类中。

public class Person
{
    public int Age { get; }
    public string Name { get; }
    public bool RegisteredToVote { get; set; }
    public bool EligibleToVote { get => Age > 18; }

    public Person(string name, int age, bool registered)
    {
        Name = name;
        Age = age;
        RegisteredToVote = registered;
    }
}

Listing 3-3Person class with eligibility property

我们现在可以检查一个人是否没有注册投票,但是只返回那些有资格投票的人。

foreach (var person in personList)
{
    if (person is Person { RegisteredToVote: false, EligibleToVote: true })
    {
        WriteLine($"{person.Name} has not registered.");
    }
}

Listing 3-4Returning only eligible people not registered

递归模式允许您更加灵活,并允许更具表现力的代码。

范围和指数

范围和指数是在 2018 年的前几个月设计的。C# 8.0 允许我们对索引数据结构做的是抓取数组、字符串或跨度的一部分。

string[] names = { "Dirk", "Jane", "James", "Albert", "Sally" };
foreach (var name in names)
{
    // do something
}

Listing 3-5An array of names

考虑一个标准的名称数组,我们可以像前面的代码清单一样在一个foreach中迭代这个数组。然而,在 C# 8.0 中,我们现在可以轻松地只取出数组的一部分,如下所示。

string[] names = { "Dirk", "Jane", "James", "Albert", "Sally" };
foreach (var name in names[1..4])
{
    // do something
}

Listing 3-6Pulling out a part of the array

这允许我们迭代数组中的一部分名字。1..4 实际上是一个范围表达式。

请注意,前面示例中的端点 4 是排他的,这意味着元素 4 不包含在[1..4].

C# 对数组采用了 C 风格的方法,所以端点的唯一性与这种方法是一致的。这意味着在[1..4],我们想要的切片长度是 4-1 = 3。

需要注意的另一点是,范围表达式不必构成索引操作的一部分。可以用自己的类型Range拉出来放到自己的变量里。这将允许以下代码有效。

string[] names = { "Dirk", "Jane", "James", "Albert", "Sally" };
Range range = 1..4;
foreach (var name in names[range])
{
    // do something
}

Listing 3-7Using the Range type

在前面的代码示例中,范围表达式是一个integer 1..4.事实上,他们不必如此。实际上,它们属于一种叫做Index的类型。非负整数值转换为Index

因为范围表达式的类型是Index,所以您可以通过使用新的^操作符来创建一个Index

有时新的^操作员也被称为帽子操作员。当提到^操作符时,时间会告诉你什么会被粘住。

新的^运算符表示从末端开始的*,因此1..¹表示从末端开始的 1。因此,您可以拥有以下内容。*

string[] names = { "Dirk", "Jane", "James", "Albert", "Sally" };
foreach (var name in names[1..¹])
{
    // do something
}

Listing 3-8Using the “from-end” operator

¹实际上是删除数组末尾的一个元素,返回一个包含中间元素的数组。

  • 詹姆斯

  • 艾伯特

有一些开发者认为,用^来表示从始至终的是令人困惑的,尤其是因为在 regex 中^从一开始就表示。但是正如 Mads Torgersen(c# 的设计负责人)所评论的,他们决定在使用从开始从结束算法时遵循 Python。**

**范围表达式可以用几种方式编写。这些解释如下:

  • 表达式..¹0..¹相同

  • 表达式1..1..⁰相同

  • 表达式..0..⁰相同

表达式0..⁰从头到尾返回数组中的所有内容(例如)。您可以将视为最右边的元素。

切换表达式

在 C# 7.0 中,我们看到 switch 语句中包含了模式。你应该还记得,我们在第一章中看到了模式匹配。考虑下面的类示例。

public class Human : Species
{
    public string Name { get; }
    public bool RegisteredToVote { get; set; }
    public bool EligibleToVote { get => Age > 18; }

    public Human(string name, bool registered)
    {
        Name = name;
        RegisteredToVote = registered;
    }
}

public class Mammal : Species
{
    public string Name { get; }
    public Mammal(string name)
    {
        Name = name;
    }
}

public class Reptile : Species
{
    public string Name { get; }
    public bool LaysEggs { get; }
    public Reptile(string name, bool laysEggs)
    {
        Name = name;
        LaysEggs = laysEggs;
    }
}

public class Species
{
    public int Age { get; set; }
}

Listing 3-9Class examples

这些类是非常基本的,如果我们想在 switch 语句中使用模式匹配,我们通常会做以下事情。

Species species = new Reptile("Snake", true);
species.Age = 2;

switch (species)
{
    case Human h:
        WriteLine($"{h.Name} is a {nameof(Human)}");
        break;
    case Mammal m:
        WriteLine($"{m.Name} is a {nameof(Mammal)}");
        break;
    case Reptile r:
        WriteLine($"{r.Name} is a {nameof(Reptile)}");
        break;
    default:
       WriteLine("Species could not be determined");
       break;
}

Listing 3-10C# 7.0 switch statement

这是一段有效的代码,但是写起来有些麻烦。在 C# 8.0 中,您将能够重写前面清单中的代码,如下所示。

var result = species switch
{
    Human h => $"{h.Name} is a {nameof(Human)}",
    Mammal m => $"{m.Name} is a {nameof(Mammal)}",
    Reptile r => $"{r.Name} is a {nameof(Reptile)}",
    _ => "Species could not be determined"
};

WriteLine(result);

Listing 3-11Switch expression

C# 8.0 引入了开关表达式,其中事例是表达式。可以把它看作 switch 语句的轻量级版本。

您会注意到,default案例使用了一个丢弃_变量。如果你需要回顾一下,弃牌在这本书的第一章已经讨论过了。

你会注意到case关键字和:已经被λ=>箭头所取代。另一件要注意的事情是,主体现在是一个表达式,并且选定的主体成为开关表达式的结果。

我应该使用开关表达式吗?

就我个人而言,我发现开关表达式更好读和写,尤其是如图 3-6 所示的格式。更集中和简洁的代码的结果是显而易见的,我们将 15 行 case 语句减少到只有 7 行代码。

img/478788_1_En_3_Fig6_HTML.jpg

图 3-6。

更可读的代码

如果您希望使用更少的代码编写开关,并且更具表达性,请考虑使用开关表达式。

属性模式

让我们扩展我们的开关表达式,来区分产卵的爬行动物和产下幼仔的爬行动物。

是的,你会看到胎生的蛇,它们会生出活的幼蛇,例如绿色水蟒和大蟒蛇。

switch语句中包含一个案例,该案例将检查爬行动物的属性LayEggs何时等于true,并基于此输出不同的结果。

img/478788_1_En_3_Fig7_HTML.jpg

图 3-7。

检查胎生爬行动物

C# 8.0 现在允许模式更深入地挖掘模式匹配的值。这意味着作为开发人员,您可以通过添加花括号将其应用于值的属性或字段,从而使其成为属性模式。因此,您可以将图 3-7 中的开关表达式重写如下。

img/478788_1_En_3_Fig8_HTML.jpg

图 3-8。

使用属性模式切换表达式

C# 8.0 也允许更多可选的类型模式元素。如果我们在和一只产卵的Reptile打交道,那么我们想要它的年龄。这里我们可以将var模式应用于Age属性。

img/478788_1_En_3_Fig9_HTML.jpg

图 3-9。

省略爬行动物 r

记住var总是会成功,并声明一个新变量来保存该值(图 3-9 )。因此,变量age开始包含r.Age的值,我们可以删除r,因为它从未被使用过(图 3-10 )。

img/478788_1_En_3_Fig10_HTML.jpg

图 3-10。

r 可以被丢弃,因为它没有被使用

包括属性模式在内的所有模式都要求该值非空。用{}null替换回退情况将处理非空模式和空值(图 3-11 )。

img/478788_1_En_3_Fig11_HTML.jpg

图 3-11。

迎合非空对象和空

空属性模式由{}处理,而null将捕捉所有的空值。

目标类型的新表达式

微软已经走了很长一段路,从他们所在的地方开始拥抱开发者社区。C# 8.0 中引入的以下特性完美地展示了开发人员的思维过程以及他们对开发人员社区的意义。

这个特性的实现实际上是由社区成员 Alireza Habibi 贡献的。

例如,在过去,当创建一个数组Point时,您需要添加类型。

Point[] ps = { new Point(1, 4), new Point(3, 2), new Point(9, 5) };

Listing 3-12Point array before C# 8.0

在 C# 8.0 中,您现在可以简单地修改前面清单中的代码,如下所示。

Point[] ps = { new (1, 4), new (3, 2), new (9, 5) };

Listing 3-13Point array in C# 8.0

该类型已从上下文中给定。因此,在这些情况下,C# 将允许您省略类型。

异步流

让我们回想一下第二章中讨论的异步。异步编程将允许您编写能够执行长时间运行任务的代码,同时仍然保持应用的响应性。

基本的想法是我们把这些东西叫做任务。NET,它代表着对未来结果的承诺。我们可能有一个如下的异步方法。

static async Task Main(string[] args)
{
    var result = await GetSomethingAsync();

    WriteLine(result);
    ReadLine();
}

static async Task<int> GetSomethingAsync()
{
    await Task.Delay(1000);
    return 0;
}

Listing 3-14Async method

你会注意到我用 async 修饰符创建了Main方法。

Async Main 是在 C# 7.1 中引入的,现在它允许你用async修饰符为你的应用创建入口点。如果你的程序返回一个退出代码,你可以声明一个返回一个Task<int>Main方法。

这里需要注意的是await操作符。这允许您在代码的执行中插入一个暂停点,直到等待的任务完成它正在处理的任务。因此,这个任务代表了一些正在进行的工作,并且只有在用async关键字修改方法时才能使用await

我们称这样一个包含一个或几个await表达式的方法(使用async修饰符)为异步方法。前面清单中的代码对于单个结果很好,但是对于连续的结果流呢?

设想一个数据库,它被查询的数据不能一次全部返回。所以,它需要对它进行流式处理,数据会以一定的间隔到达调用代码。但是,您的代码希望在自己的时间内处理这些数据。正是因为这个原因,C# 8.0 引入了IAsyncEnumerable<T>,它是IEnumerable<T>的异步版本。这样,您就可以编写下面的代码了。

static IAsyncEnumerable<int> GetLotsAsync()
{
    await foreach(var item in GetSomethingAsync())
    {
        if (item > 8)
           yield return item;
    }
}

Listing 3-15IAsyncEnumerable<T>

代码执行普通的await,但是您可以使用普通的语言结构(例如,foreach)来消费数据。

当到达迭代器方法内部的yield return时,expression被返回并保留代码中的当前位置。如果再次运行这段代码,那么下次调用迭代器时,代码将从该位置重新开始执行。要结束迭代,请调用yield break

可以把它想象成一个async迭代器,它结合了async方法和迭代器方法,允许你在其中使用awaityield return

可观测量与异步流

在与 Mads Torgersen 的一次访谈中,有人提到异步流感觉类似于可观察的或反应式的扩展。Mads Torgersen 解释说,异步流基本上是一种拉模型,在这种模型中,作为开发人员,你需要一些东西,然后得到它。另一方面,当观察对象有数据时,它们使用推模型。

有了 observables,生产者决定了数据传递给消费者的时间。在异步流中,消费者决定何时准备好接收数据。

使用声明

C# 8.0 的另一个好的补充是简化使用语句的特性。传统上,using 语句引入了一定程度的嵌套。就我个人而言,我喜欢它,因为它总是让人觉得 using 语句清楚地显示了资源何时被清理。当代码执行越过右花括号时,就会发生这种情况。

然而,对于简单的情况,我们现在在 C# 8.0 中有了using声明。考虑下面的代码清单,它在使用 SQL 连接时有一个using语句。

string tsql = "[SQL QRY]";
string sqlConnStr = "[SQL Connection String]";
using (var con = new SqlConnection(sqlConnStr))
{
    SqlCommand cmd = new SqlCommand(tsql, con);
    //..
}

Listing 3-16using statement pre-C# 8.0

using语句将清理连接等。一旦代码执行移出 using 块。然而,使用 C# 8.0,我们可以做到以下几点。

string tsql = "[SQL QRY]";
string sqlConnStr = "[SQL Connection String]";
using var con = new SqlConnection(sqlConnStr);
SqlCommand cmd = new SqlCommand(tsql, con);

Listing 3-17Using declaration in C# 8.0

using声明只是局部变量声明。唯一不同的是,它现在前面有一个using关键字。因此,内容在当前语句块的末尾被释放。

包扎

C# 8.0 引入的语言特性确实令人兴奋。我确信随着时间的推移,C# 团队将会完善这些并添加更多的内容。另一个激动人心的发展是 C# 中点释放的速度。这在我们在 C# 7 中看到的点发布中是显而易见的。这是一个好主意,以保持与这些版本的更新,以防他们决定偷偷在一些非常酷的东西。

我们看了一下现在可用的可空引用类型,例如,它允许您使用string?来指示字符串的可空性。然后我们看了一下允许模式包含其他模式的递归模式。接下来讨论了范围和索引,它们允许您抓取数组、字符串或跨度的一部分。然后,我向您展示了 switch 表达式是如何工作的,这可以看作是轻量级的 switch 语句。目标类型的新表达式允许您在创建一个Point数组时省略类型,因为类型是从上下文中给定的。然后讨论了异步流,它允许你使用一个叫做IAsyncEnumerableIEnumerable的异步版本。最后,我们看了一下通过不引入嵌套层来简化语句使用的声明。

在下一章,我们将看看如何使用 ASP.NET MVC、Bootstrap、jQuery 和 SCSS 创建响应式 web 应用。**

四、使用 ASP.NET MVC 的响应式 Web 应用

响应式 web 应用在现代应用开发中至关重要。用户需要能够在任何设备上查看您的 web 应用的内容。这意味着 web 应用需要根据查看它的设备来调整自己的大小。

在这一章中,你将创建一个简单的任务管理系统,它使用引导代码框架来保持响应。我们将了解以下内容:

  • 创建您的 ASP.NET MVC 应用

  • 引用 jQuery 和引导

  • 设置和使用 SCSS

  • 创建模型、控制器、视图和使用 Razor

  • 添加插件

  • 使用 Chrome 测试你的响应式布局

  • 使用 Chrome 开发工具调试 jQuery

我将使用编写本章时可用的最新版本的 Visual Studio 2019。

创建您的 ASP.NET MVC 应用

Visual Studio 2019 中的新开始窗口看起来确实有点不同。你会注意到它现在有五个主要部分。这些是

  • 打开最近的

  • 克隆或签出代码

  • 打开项目或解决方案

  • 打开本地文件夹

  • 创建新项目

将您最近的项目放在左侧,锁定或取消锁定是非常方便的,这将告诉您一些关于新开始窗口的信息。

我将在后面的章节中更深入地介绍 Visual Studio 2019 的这一功能和其他新功能,请密切关注。

Visual Studio 团队使用新的“开始”窗口的目的是让您能够快速访问访问代码的最常用方式。对大多数人来说,这可能是从一个存储库克隆或者打开一个现有的项目,如图 4-1 所示。

img/478788_1_En_4_Fig1_HTML.jpg

图 4-1。

Visual Studio 2019 新项目屏幕

现在,您将创建一个新的项目,因此单击该选项即可开始。

img/478788_1_En_4_Fig2_HTML.jpg

图 4-2。

选择项目模板

创建新项目可能是您非常熟悉的事情。新项目对话框(如图 4-2 所示)已经被清理了一点,不再包括节点和子节点的目录样式。

它现在包括一个最近的项目模板部分,类似于开始窗口中的打开最近的。对于这个项目,我们将使用选择一个 ASP.NET Web 应用。NET 框架。

img/478788_1_En_4_Fig3_HTML.jpg

图 4-3。

配置您的项目

选择项目模板后,您可以配置您的新项目(图 4-3 )。我们将创建一个简单的任务管理应用,它将管理任务,并根据我们稍后将定义的一些状态对它们进行颜色编码。另请注意,您可以选择。最后一个组合菜单中的. NET Framework 版本。

img/478788_1_En_4_Fig4_HTML.jpg

图 4-4。

选择一个 MVC 项目

接下来您将看到熟悉的项目配置屏幕,在这里您可以选择想要创建的 web 应用的类型(图 4-4 )。在这里选择 MVC,不用担心启用 Docker 支持或添加单元测试。在这个项目中,我们也不需要任何身份验证。

img/478788_1_En_4_Fig5_HTML.jpg

图 4-5。

解决方案资源管理器中创建的项目

Visual Studio 现在继续用所有默认的样板代码创建您的 ASP.NET MVC 应用。完成后,您应该会看到解决方案资源管理器,其中包含如图 4-5 所示的项目。如果您看到这个,那么您就准备好开始创建您的应用了。

构建您的项目并按下 F5 运行您的项目。

img/478788_1_En_4_Fig6_HTML.jpg

图 4-6。

运行您的 ASP.NET MVC 应用

如果一切设置正确,您将看到默认的 web 应用在浏览器中启动。

引用 jQuery 和引导

在你的解决方案浏览器中,如果你展开 App_Start 文件夹,你会看到一个名为BundleConfig的类。在这里,您将看到对 CSS 和 JavaScript 文件的引用。

捆绑和缩小缩短了请求加载时间。他们通过减少对服务器的请求数量来做到这一点,并通过这样做来减少所请求的资产的大小。

您会注意到,RegisterBundles方法包含对存储在 Scripts 文件夹中的 jQuery 和引导文件的引用。它还包括内容文件夹中包含的样式表。

public static void RegisterBundles(BundleCollection bundles)
{
    bundles.Add(new ScriptBundle("~/bundles/jquery")
        .Include("~/Scripts/jquery-{version}.js"));

    bundles.Add(new ScriptBundle("~/bundles/jqueryval")
        .Include("~/Scripts/jquery.validate*"));

    // Use the development version of Modernizr to develop
    // with and learn from. Then, when you're ready for
    // production, use the build tool at
    // https://modernizr.com to pick only the tests you need.
    bundles.Add(new ScriptBundle("~/bundles/modernizr")
        .Include("~/Scripts/modernizr-*"));

    bundles.Add(new ScriptBundle("~/bundles/bootstrap")
        .Include("~/Scripts/bootstrap.js"));

    bundles.Add(new StyleBundle("~/Content/css")
        .Include("~/Content/bootstrap.css",
              "~/Content/site.css"));
}

Listing 4-1The BundleConfig class

您会注意到我们有一个用于 js 文件的ScriptBundle和一个用于 css 文件的StyleBundle。在这里的ScriptBundle中,我们将添加另一个对 jquery-ui.min.js 文件的引用。

jQuery UI 是构建在 jQuery JavaScript 库之上的 UI 控件、资产、小部件和主题的集合。如果您需要包含某种形式的用户交互,请使用此选项。

在你的浏览器中,进入 http://jqueryui.com/download/ ,在核心交互小部件效果类别中进行选择。

img/478788_1_En_4_Fig7_HTML.jpg

图 4-7。

下载的 jQuery UI 文件

我希望允许用户在网页上拖动元素(特别是任务项)。因此,我只需要包含可拖动的交互,但是我将继续包含所有内容,以防以后需要使用其他交互。

我感兴趣的两个文件是 jquery-ui.jsjquery-ui.min.js 。将这两个文件添加到项目的脚本文件夹中。

img/478788_1_En_4_Fig8_HTML.jpg

图 4-8。

添加 jQuery UI 文件

添加完文件后,您需要通过添加一个带有缩小文件路径的Include来更新BundleConfig类中的RegisterBundles方法。

bundles.Add(new ScriptBundle("~/bundles/jquery")
    .Include("~/Scripts/jquery-{version}.js")
    .Include("~/Scripts/jquery-ui.min.js"));

Listing 4-2Modified RegisterBundles method

这将创建一个名为~/bundles/jquery的包,它将包含您指定的所有适当的文件以及匹配通配符{version}字符串的文件。

创建捆绑包

我们可以通过在Include方法中指定一个字符串数组来创建包。每个字符串都是资源的虚拟路径。下面是一个 StyleBundle 的例子,它指定了几个 CSS 文件的虚拟路径。

bundles.Add(new StyleBundle("~/Content/css").Include(
    "~/Content/themes/base/jquery.ui.code.css",
    "~/Content/themes/base/jquery.ui.button.css",
    "~/Content/themes/base/jquery.ui.slider.css",
    "~/Content/themes/base/jquery.ui.tabs.css",
    "~/Content/themes/base/jquery.ui.datepicker.css",
    "~/Content/themes/base/jquery.ui.theme.css"));

Listing 4-3A StyleBundle

注意所有这些 CSS 文件都在同一个目录中吗?Bundle类还提供了一个名为IncludeDirectory的方法。这允许你修改你的StyleBundle,使其更加简洁。

bundles.Add(new StyleBundle("~/Content/css").IncludeDirectory(
    "~/Content/themes/base/"
    , "*.css"
    ,false));

Listing 4-4A StyleBundle using IncludeDirectory

我已经指定了一个虚拟目录路径,还指定了一个只匹配 CSS 文件的搜索模式。设置为false的最后一个参数指定从搜索中排除子目录。

在视图中引用束

我们将在本章的下一节更仔细地研究视图。然而,我需要在这里提到,在视图中使用Render方法引用包。对于 CSS 我们使用Styles.Render,对于 JavaScript 我们使用Scripts.Render。在 shared _ Layout.cshtml 视图中查看样式表和脚本是如何呈现的。 _Layout.cshtml 视图在所有其他视图之间共享(在旧的 ASP。网)。因此,此处引用的这些脚本和样式表包含在网站的所有页面中。

设置和使用 SCSS

现在我已经引用了 jQuery UI 文件,我想为我的应用创建一个定制样式表。为此,我将创建一个. scss 样式表。在您的项目中创建一个名为 scss 的文件夹,并将一个名为 customstyles.scss 的新 scss 文件添加到该文件夹中。

img/478788_1_En_4_Fig9_HTML.jpg

图 4-9。

添加新的 SCSS 样式表

将文件夹和文件添加到项目后,您的解决方案应该如下图所示。

img/478788_1_En_4_Fig10_HTML.jpg

图 4-10。

添加了 scss 文件夹和自定义样式文件

你会注意到内容文件夹包含了我们的 CSS 文件。从逻辑上讲,这就是我们想要放置 customstyles.css 文件的地方。这个 CSS 文件将从我们在 s CSS 文件夹下创建的 scss 文件中生成。为此,我们需要安装一个由 Mads Kristensen 创建的名为 Web 编译器的工具。前往 Visual Studio 2019 中的扩展菜单,点击扩展和更新

img/478788_1_En_4_Fig11_HTML.jpg

图 4-11。

扩展和更新

下载工具后,Visual Studio 2019 将安排安装 Web 编译器。

在开始安装 Web 编译器之前,您需要关闭 Visual Studio。

img/478788_1_En_4_Fig12_HTML.jpg

图 4-12。

Web 编译器安装

安装 Web 编译器后,启动 Visual Studio 2019。看看我们之前创建的 customstyles.scss 文件。它只包含以下代码。

body {
}

Listing 4-5Contents of customstyles.scss file

我们稍后将向该文件添加一些样式代码,但首先右键单击该文件,然后单击 Web 编译器➤编译文件或按住 Shift+Alt+Q 将该文件编译成 CSS。

我们之前安装的 Web 编译器开始工作,为我们创建名为 customstyles.csscustomstyles.min.css 的 CSS 文件。只有一个问题,生成的 CSS 文件不在正确的文件夹中。我们希望生成的 CSS 文件放在项目的 Content 文件夹中。

img/478788_1_En_4_Fig13_HTML.jpg

图 4-13。

生成的 CSS 文件

这很容易解决。当 Web 编译器生成 CSS 文件时,它还会在项目根目录中为您创建一个名为 compilerconfig.json 的文件。继续打开 compilerconfig.json 文件。

[
  {
    "outputFile": "scss/customstyles.css",
    "inputFile": "scss/customstyles.scss"
  }
]

Listing 4-6Compiler configuration for the scss file

您会注意到,该文件包含一个为生成的 CSS 文件设置的输出路径。该路径与输入文件路径相同。修改您的outputFile路径,如下面的代码清单所示。

[
  {
    "outputFile": "Content/customstyles.css",
    "inputFile": "scss/customstyles.scss"
  }
]

Listing 4-7Modified Compiler configuration for the scss file

当您保存 compilerconfig.json 文件时,另一个编译会自动完成。

img/478788_1_En_4_Fig14_HTML.jpg

图 4-14。

生成的 CSS 文件

这将在正确的内容文件夹中创建 CSS 文件。你可以删除 scss 文件夹下的 CSS 文件。当我们修改 scss 文件时,这些将永远不会更新。

SCSS 到底是什么?

SCSS 是 SASS(语法上很棒的样式表)的一个实现。事实上,SASS 支持两种类型的语法,即 SCSS 和 SASS。SCSS 和萨斯的主要区别是 SCSS 使用的大括号和分号。习惯了 C#,用 SCSS 更有意义。

SCSS 完全符合 CSS,所以你现有的所有代码仍然可以工作。SCSS 的好处是

  • 能够使用变量

  • 允许嵌套语法

  • 允许使用混合

  • 允许使用分部来模块化代码

  • 能够使用@extend 来继承和扩展类

  • 允许使用函数

这允许您拆分代码来设计应用的样式,并分离应用中关于特定样式的关注点。继续添加另一个名为 _variables.scss 的 scss 文件到你的 scss 文件夹中。请注意,您必须在文件名前包含下划线,以将其标记为部分 scss 文件。

img/478788_1_En_4_Fig15_HTML.jpg

图 4-15。

_variables.scss 文件

将以下代码添加到 _variables.scss 文件中。

/* Header Colors */
$h2-color: #9DB941;

Listing 4-8The color variable for H2 tags

这只是一个为标记中的H2元素设置值的变量(由一个$符号表示)。接下来,如下修改您的 customstyles.scss 文件。

@import "_variables.scss";

h2{
    color: $h2-color;
}

Listing 4-9Custom styling for H2 elements

这里我们导入了 _variables.scss 部分文件,然后将H2元素颜色设置为$h2-color变量的值。保存你的 scss 文件,看看 Content 文件夹里的 customstyles.css 文件。

/* Header Colors */
h2 {
  color: #9DB941; }

Listing 4-10The customstyles.css file

编译后的 CSS 包含H2元素的$h2-color变量值。这就是 SCSS 为您的 Visual Studio web 应用项目带来的强大功能。

你会注意到 Web 编译器没有创建一个变量. css 文件。这是因为它被标记为部分文件,文件名前带有下划线字符。我们用customstyles.scss文件中的@import关键字将它包含在编译后的 CSS 文件中。

将我们的自定义 CSS 文件添加到 BundleConfig

我们需要在BundleConfig类中包含自定义的 CSS 文件。继续编辑RegisterBundles方法并包含 customstyles.css 文件。我们的方法目前引用了 site.css 文件。

bundles.Add(new StyleBundle("~/Content/css")
                .Include("~/Content/bootstrap.css",
                      "~/Content/site.css"));

Listing 4-11StyleBundle referencing site.css

通过移除 site.css 引用并添加我们的 customstyles.css 引用,将其改为引用我们的自定义 CSS 文件。

bundles.Add(new StyleBundle("~/Content/css")
                .Include("~/Content/bootstrap.css",
                      "~/Content/customstyles.css"));

Listing 4-12StyleBundle referencing customstyles.css

现在,我们已经成功地引用了样式表,我们将在整个应用中根据需要使用该样式表来设置元素的样式。

创建模型、控制器、视图和使用 Razor

在我们创建视图之前,我们首先需要为我们的任务应用创建一个模型和一个控制器。MVC 的整个前提是根据应用每个部分的角色来分离关注点。你可能知道,MVC 代表 M 模型、 V 视图、 C 控制器。让我们回顾一下 MVC 每个部分的职责。

什么是控制器?

当用户向浏览器发出请求时,控制器决定向用户返回什么响应。它负责控制 ASP.NET MVC 应用中的逻辑流。您会注意到我们的应用默认包含一个 HomeController 。它仅仅是一个 C# 类,最初包含一些名为IndexAboutContact的方法。如果您必须输入 URL Home/Index ,那么控制器将调用Index方法。在这里,您可以添加额外的方法(或操作)来匹配您的视图。

什么是视图?

如果你看一下 HomeController ,你会注意到每个方法都返回一个视图。在您的解决方案浏览器中展开视图文件夹,您会注意到它包含一个 Home 文件夹,其中有三个视图匹配 HomeController 类中的方法。因此,当 URL Home/Index 请求Index方法时, HomeController 将寻找名为 Index 的视图。因此,在正确的位置创建视图非常重要。调用 Home/Index 将查找位于Views \ Home \ Index . cs htmlIndex 视图。这些视图包含网页的标记。

什么是模型?

模型也只是一个 C# 类,包含应用的所有业务逻辑、任何需要的验证以及所有数据库逻辑。例如,使用实体框架作为数据库,它的逻辑将包含在 Models 文件夹中。这意味着您的视图必须只包含在网页中显示数据所需的代码。您的控制器必须只包含最少量的代码,以便选择正确的视图并将用户重定向到其他操作。模型应该包含代码逻辑的其余部分。一个通用的经验法则是,如果您的控制器变得太复杂或者包含大量代码,那么您需要考虑将该逻辑转移到一个模型中。在大多数情况下,你应该争取瘦控制器和脂肪模型。

什么是路由?

你们当中来自 ASP.NET 的人会记得,创建一个 ASP.NET 网页意味着你需要在用户输入的 URL 和被请求的页面之间有一对一的匹配。我的意思是,如果用户请求一个名为 DisplayTasks.aspx 的页面,该页面必须存在。

在 ASP.NET MVC,这不是真的。用户键入的 URL 与应用中的文件不对应。使用 MVC,用户输入的 URL 与控制器中的动作(前面提到的方法之一)相匹配。在我们应用的 HomeController 中,我们有动作索引关于,以及联系人

img/478788_1_En_4_Fig16_HTML.jpg

图 4-16。

MVC 设计模式

这种浏览器请求到控制器动作的映射在 ASP.NET MVC 中被称为路由。传入的请求被路由到控制器动作。这意味着如果用户请求 Home/Contact ,那么 HomeController 上的 Contact 动作将会运行。这也不意味着返回了联系人视图。还记得我们说过控制器的工作是决定应用中的逻辑流程吗?您可以有不同的联系人视图,控制器将根据某种逻辑(例如,原籍国)决定返回哪个视图。如果原籍国的母语不是英语,则控制器可以用不同的联系方式和不同的语言返回不同的视图。

路由的工作原理

ASP.NET 通过应用首次启动时创建的路由表处理传入的请求。您可以在项目根目录下的 Global.asax.cs 文件中看到这一点。

img/478788_1_En_4_Fig17_HTML.jpg

图 4-17。

路由表创建

它是在一个名为Application_Start的方法中创建的,您还会注意到这也是包注册的地方。

您应该还记得我们在上一节中说过,捆绑和缩小可以改善请求加载时间。

如果你看一下 App_Start 文件夹中的 RouteConfig.cs 文件,你会看到我们的路由表只包含一条默认路由。

img/478788_1_En_4_Fig18_HTML.jpg

图 4-18。

RegisterRoutes 方法

所有传入的请求都被分成三个部分。您会注意到这些片段是正斜杠之间的部分。

img/478788_1_En_4_Fig19_HTML.jpg

图 4-19。

路线段

默认路线还为您的应用提供了三个路段的默认值。这意味着,默认情况下,当您的应用启动时,它将转到默认的 Home/Index 路径。第三部分id被标记为可选。

如果您必须在您的任务/显示浏览器中输入一个 URL,那么基于您的默认路线的组成,您将需要一个名为TaskController的控制器,它包含一个名为Display的动作(方法)。简而言之,这就是路由的工作方式。

创建您的模型

让我们开始添加任务应用的内容。我们将从添加模型开始,然后创建控制器,最后设计视图。这将给我们一个可行的应用,我们可以扩展它来满足设计规范的需要。

如果您的解决方案中没有名为 Models 的文件夹,那么创建一个,并在该文件夹中创建一个名为 Task 的类。

img/478788_1_En_4_Fig20_HTML.jpg

图 4-20。

创建任务模型

当您创建了您的任务模型后,将下面的代码添加到您的模型中。

public class Task
{
    public int TaskID { get; set; }
    public string TaskTitle { get; set; }
    public string TaskBody { get; set; }
    public DateTime DueDate { get; set; }
}

Listing 4-13The Task model code

我们将通过在一个名为GetTasks的方法中插入数据来模拟数据库查询,该方法返回一个List<Task>对象。将以下代码添加到您的任务模型中。

public List<Task> GetTasks()
{
    return new List<Task>()
    {
        new Task ()
        {
            TaskID = 1
            , TaskTitle = "Review MVC tutorials"
            , TaskBody = "Make some time to view MVA videos"
            , DueDate = DateTime.Now
        },
        new Task ()
        {
            TaskID = 2
            , TaskTitle = "Create Test Project"
            , TaskBody = "Create a test project for demo at work"
            , DueDate = DateTime.Now.AddDays(1)
        },
        new Task ()
        {
            TaskID = 3
            , TaskTitle = "Lunch with Mary"
            , TaskBody = "Remember to make lunch reservations"
            , DueDate = DateTime.Now.AddDays(2)
        },
        new Task ()
        {
            TaskID = 4
            , TaskTitle = "Car Service"
            , TaskBody = "Have the car serviced before trip to HQ"
            , DueDate = DateTime.Now.AddDays(3)
        }
    };
}

Listing 4-14GetTasks method

现在,我们将依靠这个方法返回我们的Task对象,就好像它们是从数据库中读取的一样。我们现在需要添加负责我们任务的控制器。让我们接下来做那件事。

创建控制器

如果您展开控制器文件夹,您将会看到默认的HomeController,这是在我们创建应用时为我们添加的。我们所有的控制器都将存放在这个控制器文件夹中。右键单击文件夹并为任务添加新的控制器。现在,只需选择添加一个空控制器。按照控制器的 MVC 惯例调用类TaskController,并单击添加按钮。

namespace Tasker.Controllers
{
    public class TaskController : Controller
    {
        // GET: Task
        public ActionResult Index()
        {
            return View();
        }
    }
}

Listing 4-15The default TaskController code

控制器是用Index动作的默认代码创建的。在这一点上,它实际上并没有做太多,但是它是一个很好的脚手架,您可以从它开始工作。

正是在这个搭建过程中,我们看到了一些有趣的事情发生。如果您展开您的视图文件夹,您会注意到一个名为任务的新文件夹。

img/478788_1_En_4_Fig21_HTML.jpg

图 4-21。

创建的模型、视图和控制器

这是 Visual Studio 告诉你,你为你的TaskController创建的视图应该存在于视图下的任务文件夹中。你还会注意到 MVC 的构造方式,在我看来这是一个非常符合逻辑的方式。TaskController需要更多一点的代码,我们稍后会谈到。现在,我们的应用需要一个视图来显示来自控制器的数据。让我们现在创建一个。

创建您的视图

为了显示我们从控制器收到的数据,我们将添加一个视图。正是为了这个视图,我们将为我们的应用添加标记。右键点击视图文件夹下的任务文件夹,点击添加,然后点击视图

img/478788_1_En_4_Fig22_HTML.jpg

图 4-22。

添加视图

我们将把这个视图称为Index,这样TaskController就可以正确地映射到这个视图。如果控制器中的动作被称为其他名称,那么这个视图名称必须与控制器中的动作名称相匹配。

我们也不会选择模板,但是如果您愿意,您可以选择。如果您选择了一个模板,那么您需要选择我们之前创建的Task模型,并将其输入到模型类字段中。

使用以下标记创建了一个非常基本的视图。

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

Listing 4-16Basic view markup

我们需要稍微扩展一下这段代码,这样我们就可以以一种逻辑的方式显示我们的任务。我们需要对模型中的任务进行计数。如下修改您的代码。

@model IEnumerable<Tasker.Models.Task>
@{
    ViewBag.Title = "Index";

    var iTaskCount = 0;
    iTaskCount = Model.Count();
}

Listing 4-17Modified multi-statement block

您会注意到第一行表明我们的Index视图被强类型化为我们的Task类。Razor 视图引擎现在能够理解Index视图已经被传递了一个Task对象。这样做的好处是,我们现在可以访问模型的所有属性。更重要的是,我们可以在网页的标记中使用智能感知来做到这一点。

接下来我们要做的是为我们的视图编写 HTML 代码。在这里,你可以比我更有创造力。我有一个 Trello 类型的页面的想法,在那里你可以自由地在列之间移动任务项。

我们将创建三列,分别称为管道进行中已完成。我们的第一列将包含任务,如下所示。

img/478788_1_En_4_Fig23_HTML.jpg

图 4-23。

第一个任务列

如前所述,您可以按照自己喜欢的任何方式设置标记的样式。我选择使用三列布局,所以这就是我所做的。考虑下面的图像。

img/478788_1_En_4_Fig24_HTML.jpg

图 4-24。

任务的索引视图

我使用col-md-4创建了三列,并且只在第一列添加了 Razor 逻辑。

我假设对 Bootstrap 有一些最起码的了解。如果您想了解更多关于引导网格布局的信息,请在 https://getbootstrap.com 上搜索引导网格示例。

我还为其中一个名为inprogress的元素添加了一个惟一的 ID。当我们在网页上添加一些脚本时,我们将需要它。最后,我添加了 Razor 语法。让我们详细介绍一下它是什么以及它是如何工作的。

剃刀是什么?

我想在这里暂停一下,解释一下剃刀是什么。它基于 C# 语言,但也支持 Visual Basic。它是一种编程语法,允许您在网页中嵌入基于服务器的代码。从前面的图像中,您可以看到我们留下了一个包含两种内容的页面。这些是客户端内容和服务器代码。客户端内容是您习惯在网页中看到的所有标记。这是所有的 HTML 元素、JavaScript、CSS 和纯文本。

我们的 CSS 将被提取到 SCSS 文件中,该文件将编译成我们的 customstyles CSS 样式表。

使用 Razor 将服务器代码添加到客户机内容之间。服务器代码(顾名思义)在页面发送到浏览器之前运行。这非常强大,因为这意味着您可以根据服务器代码中的条件动态创建客户端内容。考虑以下逻辑。

img/478788_1_En_4_Fig25_HTML.jpg

图 4-25。

使用 Razor 动态创建客户端内容

在这里,您可以看到我们正在根据我们的任务数量动态创建页面标题。

如果你的标题没有显示,考虑暂时删除位于项目的共享文件夹中的_Layout.cshtml文件中定义的navbar元素。

我们将任务计数存储在一个名为iTaskCount的变量中。这个变量很容易在我们的页面中使用。我们可以将它混合在 HTML 语法和其他 Razor 语法之间。当变量单独使用时,必须在变量前加上@符号。

剃刀怎么写

以下是在网页中使用 Razor 语法的真实情况。当你想在页面上添加 Razor 代码时,你需要使用@字符。@字符可用于开始一个内联表达式、多语句块或单个语句块。

@{ var iTotal = 3; }

Listing 4-18Single statement block

这个语句块可以用在网页标记中的任何地方。接下来,如果您需要定义一个内联表达式,您需要执行以下操作。

<h2>You have @iTaskCount Tasks</h2>

Listing 4-19Inline expression

如果您需要在网页中显示变量值,这非常有用。在代码示例中,它用于显示任务的数量。最后,您可以使用多语句块。

@{
    ViewBag.Title = "Index";

    var iTaskCount = 0;
    iTaskCount = Model.Count();
}

Listing 4-20Multi-statement block

这是我们在清单 4-17 中修改的代码。如果您需要在页面中包含几个代码语句,多语句块是一个不错的选择。

请记住,在@{ }块中,代码语句仍然必须以分号结束。唯一不需要包含分号的时候是添加内联表达式的时候。

Razor 的强大之处在于,它允许您直接在 web 页面上使用变量,并在其他 HTML 标记之间混合使用变量。

将一切联系在一起

在运行我们的任务应用之前,我们需要将我们编写的片段链接在一起。我们已经创建了一个模型、一个控制器和一个视图。

您创建的完整的Index视图需要包含以下代码。

@model IEnumerable<Tasker.Models.Task>
@{
    ViewBag.Title = "Index";

    var iTaskCount = 0;
    iTaskCount = Model.Count();
}

@if (iTaskCount > 1)
{
    <h2>You have @iTaskCount Tasks</h2>
}
else if (iTaskCount > 0)
{
    <h2>You only have @iTaskCount Task</h2>
}
else
{
    <h2>You have no Tasks</h2>
}

<div class="container">
    <div class="row">
        <div class="col-md-4 task-pipeline">
            <div><h2>Pipeline</h2></div>
            @foreach (var item in Model)
            {
                <div class="task">
                    <div class="task-id">
                        @item.TaskID
                    </div>
                    <div class="task-title">
                        @item.TaskTitle
                    </div>
                    <div class="task-body">
                        @item.TaskBody
                    </div>
                    <div class="task-date">
                        @item.DueDate.ToString("MMMM dd, yyyy")
                    </div>
                </div>
            }
        </div>
        <div class="col-md-4 task-in-progress" id="inprogress">
            <div><h2>In progress</h2></div>
        </div>
        <div class="col-md-4 task-completed">
            <div><h2>Completed</h2></div>
        </div>
    </div>

</div>

Listing 4-21The Index view code

让我们回到我们的TaskController类,修改那里的代码,将Task模型传递给我们的视图。

public class TaskController : Controller
{
    // GET: Task
    public ActionResult Index()
    {
        Task task = new Task();
        List<Task> tasks = task.GetTasks();
        return View(tasks);
    }
}

Listing 4-22Modified TaskController class

接下来,我想告诉我的应用,当我的应用启动时,需要运行我的TaskControllerIndex动作。这将显示我们刚刚完成的Index视图。为此,我们需要更改默认路由。在你的解决方案中展开 App_Start 文件夹。

img/478788_1_En_4_Fig26_HTML.jpg

图 4-26。

RouteConfig 类

在这里,您将看到以下代码。

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new
    {
        controller = "Home"
        , action = "Index"
        , id = UrlParameter.Optional
    });

Listing 4-23Default routing

这里我们声明默认控制器是HomeController,而HomeController中的默认动作需要是Index。我们想改变默认值,所以修改您的代码,使用TaskController作为默认值。

routes.MapRoute(
   name: "Default",
   url: "{controller}/{action}/{id}",
   defaults: new
   {
       controller = "Task"
       , action = "Index"
       , id = UrlParameter.Optional
   });

Listing 4-24Modified default routing

完成这些之后,就可以运行应用了。按 F5 将启动您的应用,并显示Index视图。它目前是未样式化的,看起来有点难看,所以我们接下来需要修复它。

添加样式

为了给我们的应用添加一些样式,我们将修改之前添加的 customstyles.scss 文件。您还记得,这个文件被编译成应用中使用的 CSS 文件。编辑 customstyles.scss 文件,并向其中添加以下代码。

.task {
    border: 1px solid blue;
    border-radius: 5px;
    padding: 5px;
    margin: 5px;

    .task-id {
        display: none;
    }

    .task-title {
        font-weight: bold;
    }
}

.task-pipeline, .task-in-progress, .task-completed {
    min-height: 500px;
}

.task-pipeline {
    background-color: powderblue;
}

.task-in-progress {
    background-color: thistle;
    z-index: -1;
}

.task-completed {
    background-color: plum;
    z-index: -1;
}

Listing 4-25Custom styling

您会注意到这些是我们在任务条目的Index视图中添加的类名。保存该文件以确保它可以编译并再次运行您的应用。这一次,您将看到应用了样式,并且页面看起来更好了。

因此,使用这种方法来设计应用是合乎逻辑的。如前一节所述,SCSS 提供了一些您可以使用的非常强大的特性。

添加一些 jQuery

在上一节中,我们向应用添加了 jQuery UI 脚本文件。我这样做是因为我希望允许用户在网页上拖动任务项。要添加代码,打开任务的Index视图,并向代码添加一个脚本部分。

@section scripts {
    <script type="text/javascript">
        $(function () {
            $(".task").draggable();
        });
    </script>
}

Listing 4-26Scripts section in Index view

构建您的项目并再次运行它。现在,您可以单击任务项并在网页上拖动它们。

img/478788_1_En_4_Fig27_HTML.jpg

图 4-27。

四处拖动任务项目

这为您的 web 应用打开了一个全新的局面。能够向您的网页添加脚本部分使您能够向您的应用添加现成可用的附加功能。

但是让我们仔细看看我们添加到索引页面的这个@section scripts块。回到 _Layout.cshtml 页面,向下滚动到页面底部。您将看到下面的代码。

@RenderSection("scripts", required: false)

Listing 4-27Rendering sections

方法RenderSectionRenderBodyRenderPage告诉 ASP.NET 在哪里添加特定的页面元素。您将看到我们已经设置了一个参数来告诉 ASP.NET 脚本部分是可选的。

img/478788_1_En_4_Fig28_HTML.jpg

图 4-28。

部分名称必须匹配

最后,您必须记住在 _Layout.cshtml 共享视图中指定的部分名称必须与在 Index.cshtml 视图中包含您的脚本的部分名称相匹配。如果名称不匹配,您将在运行应用时收到一个错误。

包扎

本节采取了一种迂回的方式来解释视图、模型、控制器和 Razor,但是我觉得这样做是必要的,以便给你一个我们正在讨论的更完整的视图(注意双关语)。

添加插件

有时,您可能希望向 web 应用添加额外的功能。当然,你可以自己开发,但是如果功能存在于插件中,为什么要重新发明轮子呢?让我们假设我们想要过滤我们的事件,以便只显示关键任务。关键任务是在 1 天内到期的任务。为了提供这个功能,我们将看看一个名为同位素的插件,它可以从 https://isotope.metafizzy.co/ 获得。

安装同位素

该插件允许您提供过滤和排序,以及为您的项目指定布局模式。它尤其适用于项目组。假设您有一个显示博客文章的页面。你可能想按日期或类型(文章、播客、视频等)过滤这些内容。).也许你需要为你的项目指定一个特殊的布局。这就是同位素真正能够提供你所需要的功能的地方。

在添加同位素之前,我想分离出我的div列中的Task项。我还需要提供一些东西来触发过滤器。为此,我将只添加两个按钮。这意味着我需要修改我的页面标记以及 customstyles.scss 文件。

我们需要做到以下几点:

  • 添加两个按钮来筛选关键任务和原始任务。

  • 将列标题移到单独的行中。

  • 为标题指定新的 CSS 类。

  • 修改 customstyles.scss 文件以设置标题样式。

说明这些对索引视图的改变的最简单的方法是在一个图形中总结它们。您将看到添加的按钮是标准的引导按钮。每个按钮都有一个 ID,因此我们可以在 jQuery 中将一个 click 事件附加到这些按钮上,以过滤我们的任务。

我已经将标题移到了它们自己的行中,因为我希望任务项在单独的div中。最后,我在标题中添加了三个新的 CSS 类。这些是

  • 任务管道标题

  • 进行中的任务标题

  • 任务完成标题

这些允许我专门针对标题,并对它们应用样式。修改你的索引视图,看起来如图 4-29 所示。

img/478788_1_En_4_Fig29_HTML.jpg

图 4-29。

修改的 HTML

接下来我们要做的是更改 customstyles.scss 文件,以适应标题的新类。我会让它非常简单,只是让颜色相同。

我们只需将新的类名添加到实现背景色的现有类中就可以做到这一点。在 scss 中,我们可以“链接”将相同样式应用于页面元素的类。您会看到类名由逗号分隔(参见task-pipelinetask-pipeline-heading)。

.task {
    border: 1px solid blue;
    border-radius: 5px;
    padding: 5px;
    margin: 5px;

    .task-id {
        display: none;
    }

    .task-title {
        font-weight: bold;
    }
}

.task-pipeline, .task-in-progress, .task-completed {
    min-height: 500px;
}

.task-pipeline, .task-pipeline-heading {
    background-color: powderblue;
}

.task-in-progress, .task-in-progress-heading {
    background-color: thistle;
    z-index: -1;
}

.task-completed, .task-completed-heading {
    background-color: plum;
    z-index: -1;
}

Listing 4-28Modified customstyles.scss

一旦我们做到了这一点,我们需要一种识别关键任务的方法。如前所述,关键任务应在 1 天内完成。这里我们可以使用 Razor 执行一些条件逻辑来创建动态客户端代码。我们将在这里动态生成的客户端代码是 CSS 类。

关键任务将添加一个类别critical。这将允许同位素插件识别我们想要过滤的项目。

请注意,您过滤的类别可以是您想要的任何类别。它可以是日期、名称、颜色、类型或您需要的任何其他分类。您将告诉 Isotope jQuery 中的过滤器是什么。

索引页面的foreach循环中,我们将添加一个条件,如果任务项在一天之内到期,则需要将其归类为critical。如果没有,它只是不添加类。按如下方式修改 foreach 循环。

@foreach (var item in Model)
{
    <div class="task @(item.DueDate <= DateTime.Now.AddDays(1) ? "critical" : "")">
        <div class="task-id">
            @item.TaskID
        </div>
        <div class="task-title">
            @item.TaskTitle
        </div>
        <div class="task-body">
            @item.TaskBody
        </div>
        <div class="task-date">
            @item.DueDate.ToString("MMMM dd, yyyy")
        </div>
    </div>
}

Listing 4-29Modified foreach loop

这个逻辑的关键在于给某些任务项增加了critical分类。既然我们已经添加了正确设置任务类型和分类所需的代码,我们需要添加同位素插件。转到同位素网站,下载同位素. pkgd.min.js 文件。将这个文件添加到你的脚本文件夹中。

接下来,修改BundleConfig类的RegisterBundles方法,为同位素增加一个ScriptBundle

bundles.Add(new ScriptBundle("~/bundles/isotope")
    .Include("~/Scripts/isotope.pkgd.min.js"));

Listing 4-30Adding Isotope ScriptBundle

最后,要在应用运行时添加这个包,需要修改 _Layout.cshtml 文件。就在 Bootstrap 的@Scripts.Render下面,修改您的代码以包含同位素包。

img/478788_1_En_4_Fig30_HTML.jpg

图 4-30。

添加同位素包

我们现在准备向任务的索引视图添加一点 jQuery。

让同位素发挥作用

我们将在文档就绪部分添加一些 jQuery。在 jQuery 中,我们可以通过简单地输入$(function() { //code });来使用传统$(document).ready(function(){ //code });的简写代码。因此,我们的script部分将如下所示。

<script type="text/javascript">

    var $grid;

    $(function () {
        $(".task").draggable();

        $grid = $('.task-pipeline').isotope({
            // options
            itemSelector: '.task'
        });

        $("#btn-order-default").click(function () {
            $grid = $grid.isotope({ filter: '*' });
        });

        $("#btn-order-name").click(function () {
            $grid = $grid.isotope({ filter: '.critical' });
        });
    });
</script>

Listing 4-31Modified script section

代码一开始可能看起来有点混乱,但是一旦我们把它分解成功能部分,就很容易理解了。

img/478788_1_En_4_Fig31_HTML.jpg

图 4-31。

同位素逻辑

我们需要为同位素网格指定一个容器。这是包含我们的Task项的div的类。包含我们的Task项目的div类是.task-pipeline类。

接下来,我们需要告诉同位素它将包含的每个项目类是什么。在我们的标记中,我们的.task-pipeline div 包含多个.task类 div。告诉同位素我们的包含类和项目类本质上是什么,实例化同位素网格。

我称之为网格,因为从逻辑上讲,这对我有意义。

然后我需要为我的两个按钮添加点击事件。第一个按钮#btn-order-default将告诉同位素网格根据它包含的所有项目进行过滤。

您会注意到一些元素是通过类名来引用的(例如,.task.task-pipeline),而另一些元素是通过它们的 id 来引用的(例如,#btn-order-default)。在 jQuery 中,如果引用元素的 ID,可以使用#[ID]符号。如果您引用该类,请使用句点[classname]。

第二个按钮#btn-order-name将只显示也有critical类的.task项。查看生成的 HTML,我们可以看到只有两个任务被标记为关键任务。

img/478788_1_En_4_Fig32_HTML.jpg

图 4-32。

生成的 HTML

运行您的应用,您将看到显示了四个任务项。如果您单击关键任务按钮,您将看到项目过滤器,仅显示关键任务。

img/478788_1_En_4_Fig33_HTML.jpg

图 4-33。

按关键任务筛选

当您点击原始按钮时,任务列表被重置并显示所有任务。

img/478788_1_En_4_Fig34_HTML.jpg

图 4-34。

显示的原始任务

Isotope 插件为您的 web 应用提供了丰富的附加功能。在这里,我们只看了过滤,但它在排序项目方面同样出色,甚至有特定的方式为您的项目提供流畅的布局。

一般来说,这就是插件的力量。您可以通过使用支持良好、设计良好的插件来为您的 web 应用添加功能,这些插件可以省去您自己编写功能代码的麻烦。

使用 Chrome 测试你的响应式布局

谷歌 Chrome 浏览器无疑已经成为当今世界上最受欢迎的浏览器之一。能够用扩展向浏览器添加功能的能力允许用户将它变成他们自己的。对于开发者来说,它还以 Chrome 的开发者工具的形式提供了许多功能。在这一节中,我们将看到它的一部分,称为设备工具栏。这有助于开发人员跨多种设备测试 web 应用布局的响应性。

从开发人员工具开始

要开始使用开发者工具,按住 Ctrl+Shift+I 或右键单击您的网页并从上下文菜单中选择 Inspect

img/478788_1_En_4_Fig35_HTML.jpg

图 4-35。

Chrome DevTools(铬 DevTools)

在左上角,您将看到设备工具栏切换图标。单击此按钮将显示您的网页,就像在移动设备上查看一样。

img/478788_1_En_4_Fig36_HTML.jpg

图 4-36。

设备工具栏

设备工具栏允许我们选择特定的移动设备来查看页面。它的另一个很棒的特性是能够旋转你的网页,就像在移动设备上以旋转的方式被浏览一样。

这可能是您能够在多个设备上呈现您的网页,而不使用物理设备来呈现您的页面的最接近的方式。

使用 SCSS 的断点和媒体查询

既然我们已经看到了如何在多种设备上呈现我们的 web 页面,那么是时候看看我们的 web 应用如何在移动设备上呈现了。对于这个例子,我只是选择使用 iPhone X。

当我们将设备工具栏中的设备更改为 iPhone X 时,我们会发现有问题。多个移动设备(不包括平板电脑)可能都存在同样的问题。

img/478788_1_En_4_Fig37_HTML.jpg

图 4-37。

不正确的移动布局

我之前创建的标题 div 堆叠不正确。事实上,堆叠是 100%正确的,因为这就是引导系统的工作方式。然而,这并不是我们想要的 web 应用。

我们可以解决这个问题的方法是使用断点和媒体查询。这些允许我们在生成的 CSS 中为特定的移动设备指定特定的样式。

我不打算解决这个问题,我只是打算在移动设备上查看时隐藏标题。这将说明断点和媒体查询的使用以及它们是如何工作的。首先在你的 scss 文件夹中创建一个名为 _mixins.scss 的新文件。

img/478788_1_En_4_Fig38_HTML.jpg

图 4-38。

Add _mixins.scss 文件

在 your _variables.scss 文件中,添加一个名为$screen-mobile-max 的新变量,并将其设置如下。

$screen-mobile-max: 414px;

Listing 4-32New variable

现在编辑新的 _mixins.scss 文件,并向其中添加以下代码。

@import "_variables.scss";

@mixin mobile {
    @media (max-width: #{$screen-mobile-max}) {
        @content;
    }
}

Listing 4-33Add mixin

因为我们在 mixin 中使用我们的新变量,我们需要导入 _variables.scss 文件。现在保存项目以编译代码。然后编辑 customstyles.scss 文件,并将以下媒体查询添加到该文件中,以针对移动设备。

@include mobile {
    .task-pipeline-heading, .task-in-progress-heading, .task-completed-heading {
        display: none;
    }
}

Listing 4-34Target mobile devices

这是针对移动设备,最大宽度为414px,然后将display: none样式应用于列标题。这将隐藏移动设备上的列标题。如果屏幕宽度超过414px,则列标题将再次显示。

这使您可以随意处理媒体查询,并针对特定的移动设备应用特定的样式。

使用 Chrome 开发工具调试 jQuery

能够调试 jQuery 和 JavaScript 让您可以完全控制自己编写的代码。不用再猜测和试错调试错误了(看看你的 SYSPRO VbScript 开发人员)。你可以完全放心地编写你的 jQuery 并使用 Chrome 开发工具来调试你的代码。

我想做的是允许用户检查某些任务,并将其标记为已完成。为此,我需要在我的任务上有一个复选框,上面有文本标记已完成。当用户选中此复选框时,文本必须更改为已完成,并且页面上的任务元素应更改为绿色。这一切都可以通过 jQuery 实现。我想做的第一件事是将所有的.task元素设置为transparent背景色。我希望能够取消选中该任务,并在取消选中时,将颜色设置为transparent。修改 customstyles.scss 文件,给.task类添加一个background-color: transparent属性。

.task {
    border: 1px solid blue;
    border-radius: 5px;
    padding: 5px;
    margin: 5px;
    background-color: transparent;

    .task-id {
        display: none;
    }

    .task-title {
        font-weight: bold;
    }
}

Listing 4-35Customstyles for Task element

接下来我想做的是给我的任务添加一个复选框,并给它一个惟一的 ID。

@{ var iCount = 0; }
@foreach (var item in Model)
{
    iCount += 1;
    <div class="task @(item.DueDate <= DateTime.Now.AddDays(1) ? "critical" : "")">
        <div class="task-id">
            @item.TaskID
        </div>
        <div class="task-title">
            @item.TaskTitle
        </div>
        <div class="task-body">
            @item.TaskBody
        </div>
        <div class="task-date">
            @item.DueDate.ToString("MMMM dd, yyyy")
        </div>
        <div class="form-check">
            <input type="checkbox" class="form-check-input" id="chkCompleted@(iCount)">
            <label class="form-check-label" for="chkCompleted@(iCount)" id="chkLabel@(iCount)">Mark completed</label>
        </div>
    </div>
}

Listing 4-36Modified task item

我所做的是声明一个名为iCount的计数器,它在每次迭代中递增。然后,我将这个值连接到复选框 ID,以确保复选框元素的 ID 是惟一的。我对label元素做了同样的事情。

img/478788_1_En_4_Fig39_HTML.jpg

图 4-39。

设置唯一的 id

保存您的更改,运行您的应用并查看任务项。它们都有添加了文本标记已完成的复选框。

img/478788_1_En_4_Fig40_HTML.jpg

图 4-40。

带有复选框的任务项目

如果我们查看为我们的任务项生成的代码,我们会发现 id 确实是惟一的。

img/478788_1_En_4_Fig41_HTML.jpg

图 4-41。

为任务生成的客户端代码

我们现在准备编写一些 jQuery。这是我们面临的第一个挑战。我们不知道我们将有多少任务。因此,我们不知道复选框的 id 是什么。当用户选中一个复选框时,我们需要添加一个事件,但是为了做到这一点,我们需要知道该复选框的 ID。我们使用iCount变量动态添加的那些 id。

在这里,我们将使用一些 jQuery 选择器和奇特的技巧来获得我们想要的元素。

请注意,我将要添加的代码包含一个 bug。本节的目的是演示如何使用 DevTools 进行调试。

继续将下面的 jQuery 添加到索引视图的脚本部分。

$('[id^="chkCompleted"]').click(function () {
    var $div = $(this).closest('div');

    if (this.checked) {
        $("label[for='" + this.id + "']")["0"].innerText = "Completed";
        $div.css("background-color","#89ea31");
    }
    else {
        $("label[for='" + this.id + "']")["0"].innerText = "Mark completed";
        $div.css("background-color","transparent");
    }
});

Listing 4-37jQuery code to mark completed items

让我们分开来解释一下。图 4-42 中图像的代码与清单 4-37 中的代码完全相同。

img/478788_1_En_4_Fig42_HTML.jpg

图 4-42。

jQuery 逻辑

该逻辑对应于上图中的步骤,如下所示:

  1. 对于 ID 以文本 chkCompleted 开始的所有元素,添加一个click事件。

  2. 找到对最近的div元素的引用。

  3. 如果复选框已被选中…

  4. 获取for属性等于复选框 ID 的label元素,并将其文本设置为已完成

  5. 使用我们之前找到的div引用,设置背景颜色为绿色。

  6. 如果复选框未被选中,则将具有与复选框 ID 相等的for属性的label元素重置回原始文本。

  7. 使用我们之前找到的div引用,设置背景色为transparent

我们似乎已经开始工作了,所以让我们运行我们的应用并测试我们的 jQuery。

img/478788_1_En_4_Fig43_HTML.jpg

图 4-43。

选中任务项目复选框

不幸的是,我们遇到了障碍。当 check 事件起作用并且 checkbox 标签的文本被正确更改时,整个任务项的背景色没有被设置为绿色。要查看发生了什么,按住 Ctrl+Shift+I 或右键单击您的网页并从上下文菜单中选择 Inspect 。选择选项卡,并滚动到您的索引页面上的 jQuery 代码。

img/478788_1_En_4_Fig44_HTML.jpg

图 4-44。

在 jQuery 代码上添加断点

添加断点后,选中和取消选中您的任务项复选框。您将看到,在每个选中和取消选中操作中,断点被命中,并且您的网页进入暂停状态。

当代码暂停时,找到手表窗口,通过单击 + 图标并将表达式粘贴到提供的文本框中,将表达式$(this).closest('div')添加到新手表中。

img/478788_1_En_4_Fig45_HTML.jpg

图 4-45。

chrome devtools watch(chrome devtools 观察)

我马上就能看出问题出在哪里。我们的 jQuery 指向了错误的div元素。

img/478788_1_En_4_Fig46_HTML.jpg

图 4-46。

正确和不正确 div 的位置

我们的目标是最近的div元素,而不是保存任务项的div。如下修改您的 jQuery。

$('[id^="chkCompleted"]').click(function () {
    var $div = $(this).closest('div[class^="task"]');

    if (this.checked) {
        $("label[for='" + this.id + "']")["0"]
            .innerText = "Completed";
        $div.css("background-color","#89ea31");
    }
    else {
        $("label[for='" + this.id + "']")["0"]
            .innerText = "Mark completed";
        $div.css("background-color","transparent");
    }
});

Listing 4-38Correct jQuery code

变化在于我们找到任务项div的方式。我们不是只查找最近的div元素,而是告诉 jQuery 查找最近的div元素,该元素也有一个以文本“task开头的类。保存四个更改并刷新您的网页。这一次,如果您检查您的任务项,它将完成并且任务项变为绿色。

Chrome Developer Tools 提供了一系列调试工具,这一章甚至没有提到。你绝对可以写一整本书来介绍使用 Chrome DevTools 的好处。然而,我们已经用完了空间,我鼓励你仔细看看 Google Chrome 为开发者提供的功能。

包扎

唷,这是一个很长的章节。我们对创建 ASP.NET MVC 应用以及 MVC 如何工作有了一个高层次的看法(相信我,MVC 有比本章所讨论的更多的东西)。

然而,这一章的重点并不是通常的 MVC 主题。我想带您更进一步,探索围绕开发响应式 web 应用和轻松设计这些应用的鲜为人知的特性。

在了解如何设置和使用 scss 来设计网页风格之前,我们先了解了如何引用 jQuery 和 Bootstrap。我们看到 scss 向下编译为 css,并且 scss 在语法上类似于 C#。

然后我们简单看了一下什么是模型、控制器和视图,以及如何在视图中使用 Razor。Razor 的强大之处显而易见,例如,我们可以基于来自数据库的逻辑动态创建客户端代码。

我们看了看如何通过添加一个名为 Isotope 的插件来扩展 web 应用的功能。它为我们提供了开箱即用的过滤,让我们不必自己动手。

最后,我们看了一下在各种移动设备上测试 web 应用的响应布局。更重要的是,我们是在谷歌 Chrome 开发者工具中这样做的。我们还看到了如何使用 DevTools 控制台中的监视窗口调试 jQuery 代码。

在下一章,我们将会看到。NET Core 并弄清楚所有这些大惊小怪的到底是什么,所以请继续关注。

五、开始使用 .NET Core 3.0

如今,很难在没有听说过这个词的情况下使用微软技术栈进行编码 .NET Core。这可能会让一些人想知道它到底是什么。嗯,。NET Core 是一个开源开发平台,由微软和。网络社区。它允许开发人员编写支持 Windows、Linux 和 macOS 的应用。事实上.NET Core 可以概括为以下特征:

  • 它是跨平台的,可以在 macOS、Windows 和 Linux 上运行。

  • 它是开源的,使用 MIT 和 Apache 2 许可证,也是一个. NET 基础项目( https://dotnetfoundation.org/About )。

  • 它在包括 x86、x64 和 ARM 在内的多种架构上执行代码完全相同。

  • 它允许使用命令行工具进行本地开发。

  • 它可以与 Docker 容器一起使用,并排安装或包含在您的应用中,使 .NET Core 部署非常灵活。

  • .NET Core 兼容性扩展到 Mono、Xamarin 和。NET Framework 通过。净标准。

  • 它由微软公司提供支持。净核心支持( https://dotnet.microsoft.com/platform/support/policy/dotnet-core )。

我们还需要看一看 .NET Core。它由以下部分组成:

在这一章中,我们将看一看创建和运行 .NET Core 应用。NET Core 3.0 预览版 2 和 Visual Studio 2019 预览版。我们将讨论

  • 创造。Visual Studio 2019 中的. NET 核心应用

  • 中的新内容.NET Core 3.0

  • 正在安装。带有 Snap 的 Linux 上的 NET Core 3.0 预览版

  • 在 Linux 上创建和运行 ASP.NET MVC 应用

  • 使用 Visual Studio 代码在 Linux 上编辑 ASP.NET 核心 MVC 应用

  • 用 Visual Studio 代码调试 ASP.NET 核心 MVC 项目

在我们开始之前,您需要确保您已经下载并安装了。NET Core 3.0 安装在您的系统上。点击这个网址,下载你的平台的安装程序: https://dotnet.microsoft.com/download/dotnet-core/3.0

创造。Visual Studio 2019 中的. NET 核心应用

一旦你安装了。NET Core 3.0,我们就可以开始创建应用了。我刚刚创建了一个简单的 .NET Core 控制台应用。创建项目时,确保您的目标是。NET Core 3.0 framework 从项目属性页(图 5-1 )。

img/478788_1_En_5_Fig1_HTML.jpg

图 5-1。

目标.NET Core 3.0

我不打算详细介绍如何创建. NET 核心控制台应用。这里的重点是告诉你如何瞄准。网芯 3.0。

创建 ASP.NET 核心应用时,确保从下拉列表中选择 ASP.NET 核心 3.0(图 5-2 )。

img/478788_1_En_5_Fig2_HTML.jpg

图 5-2。

创建 ASP.NET 核心 3.0 应用

我在 Visual Studio 中的解决方案现在包含两个项目,如图 5-3 所示。这是一个. NET 核心控制台应用和一个 ASP.NET 核心 MVC 应用。

img/478788_1_En_5_Fig3_HTML.jpg

图 5-3。

解决方案浏览器

创建了两个应用模板之后,让我们来看看。网芯 3.0 可以为开发者提供。

中的新内容.NET Core 3.0

中有许多新功能。网芯 3.0,有些我就不讨论了。然而,我将强调一些更有趣的特性。

Windows 桌面

随着的发布。NET Core 3.0,您现在可以使用 Windows 窗体和 WPF 创建 Windows 桌面应用,如图 5-4 所示。如果向解决方案中添加一个新项目,并按。NET Core,你会注意到你有两个新的模板可以选择。

img/478788_1_En_5_Fig4_HTML.jpg

图 5-4。

新的 .NET Core 项目模板

的前两个版本迭代。NET Core 支持的 web 应用和 API、物联网和控制台应用。

请注意即使。NET Core 3.0 增加了对使用 WinForms 和 WPF 构建 Windows 桌面应用的支持,但你仍然只能在 Windows 上运行这些应用。

因为实体框架被很多桌面 app 使用,。NET Core 3.0 也支持实体框架 6。Visual Studio 2019 让你能够创建 WinForm 和 WPF 应用,但你可以在命令行中使用dotnet new做同样的事情。创建一个新的 .NET Core 应用的 WPF 和 WinForms,您可以从命令行运行以下命令。

dotnet new wpf
dotnet new winforms

Listing 5-1Using dotnet new in the command line

看到多简单了吗?事实上,看一下命令行就会看到图 5-5 中的截图。

img/478788_1_En_5_Fig5_HTML.jpg

图 5-5。

带 dotnet 的新 WinForms 应用新

如果我们现在将鼠标悬停在我们创建的文件夹上,我们可以看到由dotnet new创建的解决方案文件(图 5-6 )。

img/478788_1_En_5_Fig6_HTML.jpg

图 5-6。

的文件。NET Core WinForm app

现在,您可以通过在命令行中键入dotnet run来运行新的 WinForms 应用。编译和显示您的应用可能需要几秒钟的时间,但是很快您就会看到。NET Core WinForm app 如图 5-7 所示。

img/478788_1_En_5_Fig7_HTML.jpg

图 5-7。

运行。NET Core WinForms 应用

如果您查看用于创建应用的文件夹,您会注意到现在添加了一个 bin 文件夹。创作的时候 .NET Core 控制台应用,该项目的目标是Microsoft.NET.Sdk SDK。如果您查看的 netcoredemo.csproj 文件,您会看到这一点 .NET Core 控制台应用。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

</Project>

Listing 5-2.NET Core Console csproj file

那个。NET Core WinForms 应用使用不同的 SDK(WPF 应用也顺便使用),但也声明它使用哪个 UI 框架。

那个。NET Core WPF 应用将在 csproj 文件中声明一个<UseWPF>true</UseWPF>属性,而。NET Core WinForms app 会在 csproj 文件中声明一个<UseWindowsForms>true</UseWindowsForms>属性。

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>

</Project>

Listing 5-3.NET Core WinForms csproj file

我敢肯定,随着微软宣布推出 WinForms 和 WPF 桌面应用,你们中的一些人可能会期待它们能在 Linux 或 macOS 上运行。网芯 3.0。开发者社区对此似乎有些失望。然而使用的好处是。NET Core for Windows 应用意味着我们拥有

  • 提高性能

  • 开源带来的好处

  • 能够安装多个 .NET Core 版本并行

  • 发布独立应用的能力

  • 获得。仅网络核心功能(如Span<T>)

是的,他们让我在改进性能。如你所知。NET Core 是开源的,但是 WPFWindows FormsWinUI 也都是开源的。在 GitHub 上找到它们:

作为。NET Core 的发展,我们肯定会看到对 Windows 桌面应用中常用的 API 的更多支持。

支持 C# 8.0

回想一下我们讨论 C# 8.0 的第三章。开发人员可以使用的功能现在可以在中使用。网芯 3.0。随着每个新预览版的发布,更多的 C# 8.0 特性被引入。

这样做是有意义的,因为 C# 8.0 在别处可用,而在。NET Core 会有些令人沮丧。通读第三章(如果还没有通读的话),看看 C# 8.0 在语言改进方面为你提供了什么。

默认可执行文件

对于使用全局安装版本的应用 .NET Core,它们是用默认的 exe 文件构建的。在此之前,您只有一个自带应用的 exe。这意味着您可以双击 exe 或从命令行启动它,而无需使用 dotnet 工具。

在窗口上

在 Windows 上,您可以执行以下操作在 c:\temp 文件夹中创建一个新目录,创建一个新的 .NET Core 控制台应用并运行它。

cd c:\temp
md coreconsoletest
cd c:\temp\coreconsoletest
c:\temp\coreconsoletest>dotnet new console
dotnet build
cd c:\temp\coreconsoletest\bin\debug\netcoreapp3.0
coreconsoletest.exe

Listing 5-4Creating a .NET Core Console app on Windows

如果你运行你的 exe,你会看到文本 Hello World!在控制台窗口输出,如图 5-8 所示。通过点网运行 dll 会产生相同的结果。

img/478788_1_En_5_Fig8_HTML.jpg

图 5-8。

运行默认的 exe 和 dll

在 macOS 上

我们可以在 macOS 上做同样的事情。一定要下载。NET Core 3.0 预览版,安装在 macOS 上。接下来,打开终端。

在终端中,我在桌面上创建了一个名为 netcoremac 的文件夹,然后切换到该目录。然后我创建一个新的 .NET Core 控制台应用中的目录并构建它。然后我把目录换到可执行文件所在的位置,也就是 netcoreapp3.0 目录。

mkdir ~/Desktop/netcoremac
cd ~/Desktop/netcoremac
dotnet new console
dotnet build
cd ~/Desktop/netcoremac/bin/Debug/netcoreapp3.0

Listing 5-5Creating a .NET Core Console app on macOS

然后我就可以用./netcoremac运行 netcoremac 可执行文件,也可以用dotnet命令运行 netcoremac.dll,如图 5-9 所示。

img/478788_1_En_5_Fig9_HTML.jpg

图 5-9。

在 macOS 上运行默认的可执行文件和 dll

在 Linux 上

在 Linux 上,对于新的 .NET Core 控制台应用。打开终端,在桌面上创建一个名为 netcorelinux 的目录。在那个目录中,我创建了一个新的 .NET Core 控制台应用。

cd ~/Desktop
mkdir netcorelinux
cd netcorelinux
dotnet new console
dotnet build
cd ~/Desktop/netcorelinux/bin/Debug/netcoreapp3.0

Listing 5-6Creating a .NET Core Console app on Linux

我可以使用在 macOS 上使用的相同命令来运行默认的可执行文件。运行命令./netcorelinux运行默认的可执行文件,然后运行命令dotnet netcorelinux.dll运行 dll。输出如图 5-10 所示。

img/478788_1_En_5_Fig10_HTML.jpg

图 5-10。

在 Linux 上运行默认的可执行文件和 dll

快速内置 JSON 支持

JSON 已经成为现代社会的一部分。网络开发。首选图书馆是 Json.Net。从……开始。NET Core 3.0 中,System.Text.Json名称空间中添加了三种主要的 JSON 相关类型,以提供内置的 JSON 支持。这些是

  • 系统。Text.Json.Utf8JsonReader

  • 系统。Text.Json.Utf8JsonWriter

  • 系统。text . JSON . JSON 文档

这意味着新的内置 JSON 支持提供了高性能和低分配,并且基于Span<byte>。你可以在这里阅读更多关于Span<T>的内容: https://docs.microsoft.com/en-us/dotnet/api/system.span-1

密码系统

System.Security.Cryptography.AesGcmSystem.Security.Cryptography.AesCcm名称空间增加了对 AES-GCM 和 AES-CCM 密码的支持。这些是添加到中的第一批经过身份验证的加密算法 .NET Core。让我们看看我们之前创建的 netcoredemo 控制台应用。我们将添加基本的加密和解密方法。确保您已经添加了System.Security.Cryptography名称空间。

public static byte[] Encrypt(out byte[] key, out byte[] nonce, out byte[] tag, byte[] dataToEncrypt)
{
    key = new byte[16];
    nonce = new byte[12];
    RandomNumberGenerator.Fill(key);
    RandomNumberGenerator.Fill(nonce);

    tag = new byte[16];
    byte[] ciphertext = new byte[dataToEncrypt.Length];

    using (AesGcm aes = new AesGcm(key))
        aes.Encrypt(nonce, dataToEncrypt, ciphertext, tag);

    return ciphertext;
}

Listing 5-7
AES-GCM encryption method

我们使用 out 参数将keynoncetag值传递回调用代码。

使用加密技术时,我们会创建一个随机数,它是一个随机值,用于防止重放攻击。这是如果有人拦截了第一条消息并试图第二次发送该消息。每条消息的随机数必须是唯一的。如果接收应用收到重复的 nonce,它知道需要丢弃该消息。

加密的数据被返回给调用代码,并传递给 Decrypt 方法。

public static void Decrypt(byte[] key, byte[] nonce, byte[] tag, byte[] ciphertext)
{
    byte[] decryptedData = new byte[ciphertext.Length];
    using (AesGcm aes = new AesGcm(key))
        aes.Decrypt(nonce, ciphertext, tag, decryptedData);

    string decryptedText = Encoding.UTF8.GetString(decryptedData);
    Console.WriteLine(decryptedText);
}

Listing 5-8
AES-GCM decryption method

调用代码将调用EncryptDecrypt方法,如下所示。

byte[] dataToEncrypt = Encoding.UTF8.GetBytes("String to encrypt");

var encrData = Encrypt(out byte[] key, out byte[] nonce, out byte[] tag, dataToEncrypt);
Decrypt(key, nonce, tag, encrData);
Console.ReadLine();

Listing 5-9Calling Encrypt and Decrypt

运行应用,您将看到解密的文本显示在decryptedText变量中。见图 5-11 我检查过的变量。

img/478788_1_En_5_Fig11_HTML.jpg

图 5-11。

解密文本

如果您想实现 AES-CCM 密码,您基本上可以做同样的事情,只是使用不同的类名(AesCcm)。

正在安装。带有 Snap 的 Linux 上的 NET Core 3.0 预览版

推荐的安装方式。Linux 上的 NET Core 3.0 预览版是通过 Snap 实现的。在写本章的时候,下列 Linux 发行版支持 Snap:

  • Arch Linux

  • 一种自由操作系统

  • 深度

  • 基本操作系统

  • 一种男式软呢帽

  • 加利亚姆斯

  • KDE 霓虹灯

  • 库班图

  • Linux 作为

  • 卢班图

  • manjaro

  • 大蜥蜴

  • Parrot 安全操作系统

  • Raspbian

  • Solus

  • 人的本质

  • 徐邦图

  • 佐伦·OS

出于我的目的,我使用了 Linux Mint。在 Linux 系统上配置 Snap 之后,运行以下命令来安装。网芯 3.0 预览版。

sudo snap install dotnet-sdk --beta --classic

Listing 5-10Install .NET Core 3.0 Preview with Snap

这是现在的默认设置。通过 Snap 安装时的 NET Core 命令dotnet-sdk.dotnet。这是一个命名空间命令,不会与全局安装的冲突。您可能在 Linux 系统上安装了. NET Core 版本。我更喜欢使用默认的dotnet命令,因为这只是我使用的 Linux 的一个测试安装。

为此,您可以在终端中运行以下命令,为您的dotnet-sdk.dotnet命令创建一个别名。

sudo snap alias dotnet-sdk.dotnet dotnet

Listing 5-11Creating the dotnet alias

有关设置的更多信息。Linux 上的 NET Core,参考以下链接: https://github.com/dotnet/core/blob/master/Documentation/linux-setup.md

在 Linux 上创建并运行 ASP.NET MVC 应用

在 Linux 桌面上创建一个新文件夹。出于我的目的,我使用 Linux Mint。打开“终端”并导航到您创建的新文件夹。若要查看哪些模板可供您使用,请在“终端”中键入以下命令。

dotnet new -l

Listing 5-12Listing the dotnet new templates

现在这里列出了所有可用的项目模板,您可以使用dotnet new命令创建这些模板。

请注意,在安装之后,我通过键入sudo snap alias dotnet-sdk.dotnet dotnet将我的dotnet-sdk.dotnet命令别名为dotnet。NET Core 3.0 预览版。

列出的模板之一是 ASP.NET 核心 MVC 应用。要创建此项目类型,请在之前在桌面上创建的目录中,在终端中运行以下命令。

dotnet new mvc

Listing 5-13Create a .NET Core MVC app on Linux

这将在我们之前创建的文件夹中创建您的 ASP.NET 核心 MVC 应用。

img/478788_1_En_5_Fig12_HTML.jpg

图 5-12。

ASP.NET 核心 MVC 项目

打开文件夹,您会注意到我们有所有我们通常在 Visual Studio 中看到的熟悉文件(图 5-12 )。

如果在上遇到访问路径错误。创建 MVC 应用时,nuget/packages 文件夹或 csproj 文件上的权限被拒绝错误,运行sudo dotnet restore并在提示时键入您的密码。

要运行新的 ASP.NET 核心 MVC 应用,请在终端中键入以下命令。

dotnet run

Listing 5-14Running your ASP.NET Core MVC app

您将看到终端显示一些信息消息。其中一条消息应该是正在监听: https://localhost:[port],其中【端口】是一个有效的端口号。在你的浏览器中输入那个 URL(我用的是 Firefox),你会看到你的 ASP.NET 核心 MVC 应用运行在 Linux 上,如图 5-13 所示。

img/478788_1_En_5_Fig13_HTML.jpg

图 5-13。

Linux 上 Firefox 中的 ASP.NET 核心 MVC 应用

整个过程甚至不需要我们花 2 分钟来创建一个项目并在 Linux 上运行它。

使用 Visual Studio 代码在 Linux 上编辑您的 ASP.NET 核心 MVC 应用

微软做了大量的工作将 Visual Studio 带到所有平台上。Visual Studio 代码为开发人员在 Linux 和 macOS 上创建应用提供了一个极好的 IDE。如果我需要快速处理一个文件,我甚至每天都在我的 Windows 机器上使用它。

Visual Studio 代码可以从以下网址下载: https://code.visualstudio.com/ 。使用 Visual Studio 代码的好处是

  • 这是一个免费的 IDE,对于想要尝试新事物的开发人员来说是完美的。

  • 它也是开源的。你可以在 GitHub 上查看这里的资源库: https://github.com/Microsoft/vscode

  • 它可以在 Windows、macOS 和 Linux 上运行。

这意味着我可以在 Linux 的 Visual Studio 代码中编辑我的 ASP.NET 核心 MVC 项目。

编辑您的项目

我已经下载了 Visual Studio 代码并安装在我的 Linux Mint 安装上。让我们用它来打开我们的 ASP.NET 核心 MVC 应用,并为 HomeController 修改 Index.cshtml 文件。首先打开 Visual Studio 代码并点击 Explorer 或者按下 Ctrl+Shift+E 。图 5-14 显示了在 Visual Studio 代码中可以找到资源管理器的地方。

img/478788_1_En_5_Fig14_HTML.jpg

图 5-14。

开启 Visual Studio 程式码总管

单击“打开文件夹”,然后打开项目的顶层文件夹。您将看到如图 5-15 所示的项目文件。

img/478788_1_En_5_Fig15_HTML.jpg

图 5-15。

在 Visual Studio 代码中打开项目

视图文件夹中,选择主页文件夹,点击 Index.cshtml 。这将在代码编辑器中打开文件。如下修改您的代码。

@{
    ViewData["Title"] = "Home Page";
    var longAgoDate = DateTime.Today.AddYears(-100);
    var longDayOfWeek = longAgoDate.DayOfWeek;
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <h2 class="display-4">100 Years ago was @longDayOfWeek, @longAgoDate.ToString("MMMM dd, yyyy")</h2>
</div>

Listing 5-15The Index.cshtml view

将更改保存到文件中,然后在终端中键入dotnet build命令,然后键入dotnet run。在 Firefox 中运行你的应用(图 5-16 )。

img/478788_1_En_5_Fig16_HTML.jpg

图 5-16。

修改的 ASP.NET 核心 MVC 项目

用 Visual Studio 代码调试您的 ASP.NET 核心 MVC 项目

Visual Studio 代码允许您调试代码。你需要做一点繁重的工作来完成所有的设置,但是一旦你完成了这些,你就一切顺利了。

我在 Linux 上设置了这个例子,所以如果您不在 Linux 上工作,这些步骤可能会与您的系统不同。我用的是 Linux Mint。

为此,打开 Visual Studio 代码,查看一下扩展窗格,或者按住 Ctrl+Shift+X 。搜索由 OmniSharp 驱动的 C# 扩展(图 5-17 )。

img/478788_1_En_5_Fig17_HTML.jpg

图 5-17。

Visual Studio 代码扩展的 C#

在 Visual Studio 代码中,打开您之前在其中创建项目的 aspnetmvc 文件夹。当你这样做的时候,你应该看到下面的消息显示:‘aspnet MVC’中缺少构建和调试所需的资产。加他们?

当您单击“是”时,Visual Studio 代码将添加一个*。vscode* (图 5-18 )文件夹到你的项目中。

img/478788_1_En_5_Fig18_HTML.jpg

图 5-18。

确保. vscode 文件夹存在

在这个里面。vscode 文件夹,你会看到应该有两个 Visual Studio 代码创建的文件。这些文件是

  • launch.json

  • tasks.json

您将需要这些文件(图 5-19 )来调试您的 ASP.NET 核心 MVC 应用。如果这些文件不存在,删除*。vscode* 文件夹,重启 Visual Studio 代码,再次打开 aspnetmvc 项目文件夹。

img/478788_1_En_5_Fig19_HTML.jpg

图 5-19。

确保启动和任务文件存在

打开 launch.json 文件,检查其中的内容。注意,需要为 bin 文件夹中的【aspnetcore.dll】文件设置正确的路径。launch.json 文件配置并保存所有调试设置细节。调试项目时会用到这些调试配置信息。

   "version": "0.2.0",
   "configurations": [
       {
           "name": ".NET Core Launch (console)",
           "type": "coreclr",
           "request": "launch",
           "preLaunchTask": "build",
           "program": "${workspaceFolder}/bin/Debug/netcoreapp3.0/aspnetmvc.dll",
           "args": [],
           "cwd": "${workspaceFolder}",
           "stopAtEntry": false,
           "console": "internalConsole"
       },
        {
            "name": ".NET Core Launch (web)",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            // If you have changed target frameworks, make sure to update the program path.
            "program": "${workspaceFolder}/bin/Debug/netcoreapp3.0/aspnetmvc.dll",
            "args": [],
            "cwd": "${workspaceFolder}",
            "stopAtEntry": false,
            "internalConsoleOptions": "openOnSessionStart",
            "launchBrowser": {
                "enabled": true,
                "args": "${auto-detect-url}",
                "windows": {
                    "command": "cmd.exe",
                    "args": "/C start ${auto-detect-url}"
                },
                "osx": {
                    "command": "open"
                },
                "linux": {
                    "command": "xdg-open"
                }
            },
            "env": {
                "ASPNETCORE_ENVIRONMENT": "Development"

            },
            "sourceFileMap": {
                "/Views": "${workspaceFolder}/Views"
            }
        },
        {
            "name": ".NET Core Attach",
            "type": "coreclr",
            "request": "attach",
            "processId": "${command:pickProcess}"
        }
    ,]
}

Listing 5-16The launch.json file contents

我们看到的下一个文件是 tasks.json 文件。这将在您的 ASP.NET 核心 MVC 项目上运行构建任务。它可以包含其他几个任务,但目前这是我们所需要的。

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "command": "dotnet build",
            "type": "shell",
            "group": "build",
            "presentation": {
                "reveal": "silent"
            },
            "problemMatcher": "$msCompile"
        }
    ]
}

Listing 5-17The tasks.json file contents

接下来,展开视图主页文件夹,打开 Index.cshtml 文件。点击空白处,在图 5-20 第 4 行放置一个断点。

img/478788_1_En_5_Fig20_HTML.jpg

图 5-20。

放置断点

我们现在需要按住 Ctrl+Shift+D 来调出调试窗格,如图 5-21 所示。首先,您会注意到您可以访问熟悉的变量、观察和调用堆栈。您还会看到断点组,当前设置的断点显示在 Index.cshtml 文件上。

img/478788_1_En_5_Fig21_HTML.jpg

图 5-21。

调试窗格

从调试组合框中,选择*。NET Core Launch (web)* 点击绿色播放按钮(图 5-22 )。

img/478788_1_En_5_Fig22_HTML.jpg

图 5-22。

调试开始

项目将通过一个生成过程,如果成功,调试栏将显示在 Visual Studio 代码 IDE 窗口的顶部。现在你要打开调试控制台。你可以通过视图调试控制台菜单或者按住 Ctrl+Shift+Y 来实现。您将看到我们之前看到的熟悉的输出,并且您会注意到它表明Microsoft.Hosting.Lifetime web 主机正在本地主机上侦听(图 5-23 )。

img/478788_1_En_5_Fig23_HTML.jpg

图 5-23。

调试控制台

回到浏览器(我用的是 Firefox ),输入调试控制台中指定的 URL。

img/478788_1_En_5_Fig24_HTML.jpg

图 5-24。

断点命中

当你的网页加载时,你的断点将被点击,如图 5-24 所示。现在,您可以像平常一样逐句通过代码、查看变量、使用"监视"窗口以及执行所有正常的调试任务。您还可以在单步执行代码行时,通过将鼠标悬停在编辑器中的变量上来检查变量。

包扎

我们以微软文档( https://docs.microsoft.com )的形式提供了丰富的在线信息,这些信息正是您所需要的。我希望这一章至少激起了你对。网芯 3.0。随着框架的发展,我们将会在每个版本中看到更多的特性。

这一章看了一下创造 .NET Core 应用。我们看到现在可以在上创建一个 Windows 桌面应用 .NET Core,但这些仅在 Windows 上受支持。

然后我们看了一些更有趣的新特性。网芯 3.0。我们看到我们支持 C# 8、快速内置 JSON 支持和加密技术。

我们看到我们可以安装。NET Core 3.0 在 Linux 上使用 Snap。继续使用 Linux,我们创建了一个 ASP.NET 核心 MVC 应用,并在 Linux 上运行。使用 Visual Studio 代码,我们看到可以编辑我们的项目文件,最后,我们看到可以使用 Visual Studio 代码在 Linux 上调试我们的应用。

如果您喜欢 Visual Studio 代码的灵活性,请继续关注。在下一章中,我们将浏览 Visual Studio 2019,并了解这款世界级 IDE 即将发布的新功能。你兴奋吗?我知道我是。