C#8 和 .NET Core3 Azure 项目(四)

C#与.NET Core高效学习路径:3个月从入门到项目实战 本文提供了一份为期三个月的高效学习路线图,帮助开发者系统掌握C#与.NET Core。从环境搭建、C#核心语法与面向对象编程,到ASP.NET Core Web API、Entity Framework Core数据访问,最终完成全栈项目实战与部署。该路径强调动手实践,旨在快速构建可交付的实战能力。 阅读详情

原文:C# 8 and .NET Core 3 Projects Using Azure

协议:CC BY-NC-SA 4.0

十、21 世纪的伊莱扎——UWP 和微软机器人框架

图灵测试是由艾伦·图灵开发的,目的是回答机器是否可以被认为具有思考能力的问题。20 世纪 60 年代,一个名为伊莱扎的项目诞生了,这是第一批试图通过这项测试的项目之一。

图灵测试是在机器学习的概念还处于萌芽阶段时发展起来的(事实上,图灵本人在这个领域做出了贡献)。现在,我们有了使用机器学习来检测情绪的系统,我们有了能够理解语言的复杂系统,我们接到了机器人打来的电话,我们与之进行了完整的口头对话,却从未意识到他们不是人类。

伊莱扎是为了模仿一个心理治疗师而建立的,这个想法是,它挑选出关键词和短语,并把它们转换成问题,以便愚弄人类,让他们相信自己在和另一个人说话。

在本章中,我们将创建一个版本的伊莱扎。我们将学习如何使用在 UWP 创建客户端应用.NET Core 3。然后,我们将学习如何设置和配置 LUIS,最后,我们将学习如何将其集成到 MS Bot 框架中。

本章将涵盖以下主题:

  • 查特斯
  • 微软聊天机器人框架
  • 微软语言理解服务
  • 在 UWP 创建应用

聊天机器人可以用于一切,从信息技术支持到预订航班(下图显示了一个例子):

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/18521c41-3435-4258-b98c-0416ed8aa25c.png

技术要求

在本章中,我们将使用默认模板来使用机器人框架创建微软机器人。在撰写本文时,尽管发布了.NET Core 3,Bot 框架项目仍在创建中.NET 2.2。在后面的部分中,我们将看到如何将其升级到.NET Core 3;然而,当这本书出版时,这种情况可能已经改变了。

在本章中,您将需要一个 Azure 订阅(我们在第 2 章、使用 Cosmos DB 任务 Bug 日志记录 ASP.NET Core MVC 应用中简要介绍了如何创建一个)。

本章的代码可以在 https://github.com/PacktPublishing/C-8-and-.的 GitHub 上找到 NET-Core-3-项目-使用-Azure-第二版。

创建聊天机器人

让我们从聊天机器人开始。在写这篇文章的时候,聊天机器人无处不在,从你在网站上寻求帮助到你给呼叫中心打电话。本质上,聊天机器人只是一个复杂的状态机,框架,如微软的 Azure 机器人框架,只是向你隐藏了这种复杂性。想象一下人类和机器之间的对话( H 和 M ):

M: Hello, how can I help?
H: Hello, I'd like to check my balance, please.
M: I can help you with that. Which account is this for?
H: Current.
M: I'm sorry, I didn't get that. Which account is this for?
H: Current account.
M: What is the number of your current account?

好的,如果我们看一下这个交换,我们可以看到它是由机器人发起的。客户回答说,他们想检查他们的余额——所以在这里,机器人正在监听关键词,很明显balance就是一个。现在机器需要知道客户想知道哪个账户的余额。在幕后,它可能已经检查了向客户注册的账户是否不止一个。此时,客户说current,机器人需要澄清,所以它循环回来,再次询问。当它最终明白了答案,它就继续前进。

我们将使用一个模板创建我们的机器人,你需要从 https://marketplace.visualstudio.com/items?安装这个模板 item name = BotBuilder . BotBuilder 4 .

当你选择创建一个新的机器人时,你有三个选项:空机器人、回声机器人和核心机器人,如下图所示。对于这个项目,我们将选择回声机器人:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/f07dfd30-c833-4ccb-8b2d-0e46006133b1.png

These are prebuilt templates with varying degrees of existing functionality. Obviously, the empty template has no functionality and only boilerplate code. The Core Bot gives you a fully functioning bot that is linked to LUIS.

我们现在将选择项目的文件位置:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/db6ad7ef-907f-4c09-a875-3c2abc6d12b4.png

By default, the bot name will be set to the project name.

一旦你创建了你的机器人,运行它,你会看到下面的屏幕:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/2bad8bef-f7d9-41ca-a1dd-143fe9888572.png

这可能不完全是你所期望的,因为我们现在还有几个步骤要走,才能看到我们的机器人在行动(剧透:在这个阶段,实际上没有太多可看的!).记下截图中的网址,因为我们很快就会需要它。

Bot 仿真器

下一步是运行模拟器。事实上,我们刚刚创建的机器人只是一项服务。模拟器为我们提供了一个与之交互的用户界面。

在撰写本文时,模拟器的最新版本位于https://github . com/Microsoft/botformal-Emulator/releases/tag/v 4 . 2 . 1。

要安装它,有一个自解压可执行文件:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/85367152-08e2-45ae-91bb-c54fd58e28c0.png

下载并安装模拟器,然后运行它。

You’ll need to run the emulator as an administrator.

当你运行模拟器时,你需要给它一个名字和一个网址(你刚才记下了网址,名字可以是任何东西):

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/45d2de4b-2e04-4218-a2f6-79ebf0e15c51.png

一旦你连接了,你就能和你的机器人交谈了。让我们快速看一下模拟器屏幕:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/3a25b0a2-6fd5-4956-b14a-89479d359160.png

让我们看看它到底在告诉我们什么:

  1. 这个部分列出了你(人类)或机器人写的所有消息。

  2. 您可以选择聊天记录中的任何消息(见 4)。

  3. 这是你给机器人输入信息的地方。

  4. 这显示了原始 JSON 形式的消息。请记住,您的机器人只是一个应用编程接口,因此进出通信是 JSON 格式的。

  5. 这给出了对话的网络历史。如果您遇到身份验证或网络问题,这尤其有用。

  6. 整个聊天记录可以保存到.transcript文件中。本质上,这是会话历史的 JSON 表示。左侧的资源图标允许您打开这些文件并检查旧的成绩单。这在支持场景中非常有用,在这种场景中,出现了一些问题,您希望看到聊天过程中发生了什么。

  7. 任何时候,你都可以简单地从头开始聊天。

  8. 这将显示或隐藏资源部分。这允许您查看成绩单或聊天文件。

  9. 这显示了您当前正在与之通信的端点。

  10. 存储连接端点的历史;您可以通过简单地点击历史记录在这些之间切换。

你可能会注意到这个机器人的对话框很大程度上是受你自己的启发。让我们看看我们是否能确定为什么会这样,并稍微纠正一下。

回声机器人——除了你好

目前,回声机器人名副其实——它只是简单地回应你所说的一切。让我们开始把它做得更鲍里斯一点,在这个基础上增加一点变化。我们先把EchoBot改名为BorisBot:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/82546783-5532-4a7a-957b-322c7663c9d7.png

我们将通过以下步骤稍微修改机器人的代码:

  1. 首先,让我们重命名该类:
public class BorisBot : ActivityHandler
{
   . . .
}
  1. 接下来,我们将对OnMessageActivityAsync进行一个小的更改,以捕获用户键入Hello,并以一个更合理的消息进行响应:
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    if (turnContext.Activity.Text == "Hello")
    {
        await turnContext.SendActivityAsync(MessageFactory.Text($"Hello, my name is Boris"), cancellationToken);
    }
    else
    {
        await turnContext.SendActivityAsync(MessageFactory.Text($"Echo: {turnContext.Activity.Text}"), cancellationToken);
    }
}
  1. 在Startup.cs中,您还需要更改依赖注入来注册新的机器人:
services.AddTransient<IBot, BorisBot>();

现在,如果我们运行机器人,我们会得到不同的响应:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/a4a3f480-a839-4d36-8d75-86691073f6e0.png

好了,现在当我们输入Hello时,我们得到了一个合理的响应,但是任何其他的,我们回到了回声。为了通过图灵测试,我们需要在这方面进行改进。请记住,这都是关于感知的,所以我们首先需要涵盖说类似事情的各种方式,其次,我们需要改变我们的反应。我们可以在OnMessageActivityAsync里面写一个大量的 switch 语句,并尝试尽可能多地想出表达Hello的方式。然而,我们也可以使用微软为我们创建的一个名为 LUIS 的工具,它为我们做了一些事情。

介绍 LUIS

LUIS 代表语言理解智能服务。它允许你利用机器学习算法来训练预先构建的语言模型。事实上,这个服务(和 Bot 框架一样)比我们在这里看到的要强大得多。出于我们的目的,我们只需要训练一个简单的模型,它只有很少的关键词和短语。如果您还没有这样做,您需要首先在 https://www.luis.ai 注册。

注册后,您可以开始构建新模型:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/3db67670-8efe-4263-937f-ecc0e84edc07.png

在这里,如果我们选择意图,我们可以确定对话的关键领域;如您所见,我在这里确定了一些领域。您选择“创建新意图”选项,然后给该意图命名(事实上,给该意图命名并不重要)。一旦你这样做了,你将被要求给出一些可能被输入的例子——例如,对于感觉,你可能有这样的东西:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/2ae01775-2950-483a-bd09-5a13a831faa3.png

You should ensure that you add at least five phrases for each intent, but the more phrases you have, the better the model.

一旦你做到了这一点,你就可以训练和测试你的模型。

You can add these examples all day, but unless you train the model, it will not change.

让我们试着训练和测试我们的模型。一旦您添加了多个意图(不一定是全部,也不一定与这里列出的相同),请选择 Train。正如您在前面的截图中看到的,您将知道您的模型何时需要训练,因为 Train 按钮旁边会出现一个红色的小红绿灯。训练好模型后,选择测试进行测试:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/0a510d6a-32fd-45fb-9b72-b0aa3cf9b856.png

在这里,我已经尝试了三个短语。你可以看到,在每个短语下面,它返回一个意图。好像输入其中一个没错,但是另外两个有点偏。这里最棒的是,在我测试的时候,如果它出错了,我可以简单地告诉 LUIS 它是错的,方法是点击 Edit,然后告诉它正确的意图:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/ef024deb-930f-459e-9344-3a35ad38c6f6.png

显然,你的意图越细,你的机器人听起来就越真实。一旦你对你训练的机器人感到满意,我们就可以进入下一部分,我们将把 LUIS 集成到我们的机器人中。

We can always come back and retrain the model, but make sure that you’re happy with the intents, as we’ll be coding against them.

将 LUIS 集成到 Bot 框架中

值得注意的是,这个过程被微软很好地记录了下来;请随时关注本部分或查看在https://docs . Microsoft . com/en-us/azure/bot-service/bot-builder-how to-v4-Luis 找到的文档。视图=azure-bot-service-4.0 &选项卡=csharp 。

让我们完成以下步骤:

  1. 为了集成 LUIS,我们首先需要添加连接细节。在 LUIS 门户中,选择管理,并从应用信息中复制应用标识:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/b244aedc-65ef-42fa-9e79-723dbc98fc6e.png

  1. 将此添加到您的appsettings.json文件中:
{
  "MicrosoftAppId": "",
  "MicrosoftAppPassword": "",
  "LuisAppId": "ed7252fa-43c1-4d4a-a084-2b8a58c69028",
  "LuisAPIKey": "",
  "LuisAPIHostName": ""
}
  1. 现在,选择密钥和端点并复制创作密钥:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/f66e7d77-2380-4811-9f2a-e6e7dee73ad4.png

  1. 在同一页面的底部,如果向下滚动,您将找到端点:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/197ef5e2-778c-4259-af62-5af0e26dedda.png

这些值也需要复制到appsettings.json中:

{
  "MicrosoftAppId": "",
  "MicrosoftAppPassword": "",
  "LuisAppId": "ed7252fa-43c1-4d4a-a084-2b8a58c69028",
  "LuisAPIKey": "51d06b40b7df4f41be26b7d80cd80e73",
  "LuisAPIHostName": "https://westus.api.cognitive.microsoft.com"
}

Note that the hostname is trimmed after .microsoft.com.

  1. 完成后,您需要发布您的模型:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/35a7d9e4-550d-4491-bf4a-31438b7eda83.png

  1. 下一步是安装 LUIS NuGet 包:
Install-Package Microsoft.Bot.Builder.AI.Luis
  1. 最后,我们需要探究代码;让我们打电话给 LUIS 来代替我们的支票Hello:
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    var luisApplication = new LuisApplication(
                    _configuration["LuisAppId"],
                    _configuration["LuisAPIKey"],
                    _configuration["LuisAPIHostName"]
                );

    var recognizer = new LuisRecognizer(luisApplication);

    var recognizerResult = await recognizer.RecognizeAsync(turnContext, cancellationToken);

    var (intent, score) = recognizerResult.GetTopScoringIntent();
    if (intent == "Hello")
    {
        await turnContext.SendActivityAsync(MessageFactory.Text($"Hello, my name is Boris"), cancellationToken);
    }
    else
    {
        await turnContext.SendActivityAsync(MessageFactory.Text($"Echo: {turnContext.Activity.Text}"), cancellationToken);
    }
}

让我们快速回顾一下我们在这里改变了什么。首先,我们实例化一个新的LuisApplication,并提供我们在appsettings.json中收集的凭证。为了做到这一点,我们还将IConfiguration注入到类中,稍后我们将回到这个类。

然后我们调用RecognizeAsync,它执行短语的实际分析,最后,我们要求它告诉我们它认为意图是什么。让我们看看这对机器人意味着什么:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/489f537a-28c6-4dce-9332-858d24c14ff2.png

如你所见,鲍里斯现在明白了一点;然而,我们的反应仍然相当木讷,它只对Hello意图起作用。接下来让我们解决其他意图,但是首先,我们将IConfiguration注入到类中。让我们来看看这个变化:

private readonly IConfiguration _configuration;

public BorisBot(IConfiguration configuration)
{
    _configuration = configuration;
}

下一阶段是升级我们的项目使用.NET Core 3。

在编写时,模板使用.NET Core 2.1;但是,如果在您阅读本文时情况并非如此,请随意跳过下一部分。

从升级模板.NET Core 2.1 到 3.0

在这个阶段,我们需要升级.NET Core 2.1 到.NET Core 3.0。这可能会导致我们需要做一些小调整。

第一个调整是将IHostingEnvironment替换为IHostEnvironment(关于这个方法的完整抄本,见下文):

public void Configure(IApplicationBuilder app, IHostEnvironment env)

这是为了避免 2.1 中引入的名称空间冲突。

下一个变化是兼容性版本:当您得知应用现在需要设置为与版本 3 兼容时,应该不会感到惊讶;

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_3_0);                

现在我们已经向依赖注入系统添加了 MVC,我们需要在应用中将它注册为中间件。

UseMvc

最后,端点路由发生了一些变化。这样做的净效果是UseMvc现在似乎一次性配置了太多设置;因此,Configure方法应该更像这样:

public void Configure(IApplicationBuilder app, IHostEnvironment env)
{
    if (env.IsDevelopment())
    {   
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    app.UseDefaultFiles();
    app.UseStaticFiles();

    //app.UseHttpsRedirection();
    //app.UseMvc();
    app.UseRouting();
    app.UseCors();
    app.UseEndpoints(e =>
        e.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}")
    );
}

我们不再调用UseMvc,而是调用我们需要的中间件的具体方面。

allowsynchroniousio

英寸 NET Core 3.0 引入了一个突破性的变化,允许同步调用的缺省值被更改为不允许。在撰写本文时,BotFrameworkAdaptor打破了这一规则(可能是因为它正在使用 Newtonsoft JSON 序列化——在这种情况下,到本书出版时,这可能还不是问题)。运行项目时,可能会出现以下错误:

System.InvalidOperationException: Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.'

只有在运行项目时出现上述错误,才应实施以下更改:

  1. 在BotController中,更改AllowSynchronousIO标志:
[HttpPost]
public async Task PostAsync()
{    
    var syncIOFeature = _httpContextAccessor.HttpContext.Features.Get<IHttpBodyControlFeature>();
    if (syncIOFeature != null)
    {
        syncIOFeature.AllowSynchronousIO = true;
    }

    // Delegate the processing of the HTTP POST to the adapter.
    // The adapter will invoke the bot.
    await _adapter.ProcessAsync(Request, Response, _bot);
}
  1. 我们需要将IHttpContextAccessor注入BotController;这就是构造函数现在的样子:
private readonly IHttpContextAccessor _httpContextAccessor;

public BotController(IBotFrameworkHttpAdapter adapter, 
    IBot bot, IHttpContextAccessor httpContextAccessor)
{
    _adapter = adapter;
    _bot = bot;
    _httpContextAccessor = httpContextAccessor;
}
  1. 最后,在Startup.ConfigureServices中,添加上下文访问器:
services.AddHttpContextAccessor();

您的项目现在应该使用编译.NET Core 3.0。现在让我们研究一下如何扩展我们的代码来处理其他意图。

意图/响应矩阵

本节基本上涵盖了该应用编程接口的最后两个要求。我们将创建一个 JSON 文件,将一个意图映射到一个可能的回复(或多个回复)。事实上,我们将每个意图链接到多个回复,然后随机使用一个;这样,机器人会给人一种对每条信息做出独特响应的印象。

让我们从 JSON 文档开始。可能是这样的:

{
  "Hello": [
    "Hi there!",
    "Hello, My name is Boris",
    "Nice to meet you",
    "Hello"
  ],
  "Family": [
    "Tell me more about your family",
    "Do you have any siblings?",
    "Let's talk about your family some more"
  ],
  "Feelings": [
    "It's important to talk about your feelings",
    "It's healthy to talk about how you feel",
    "Please tell me: how are you feeling"
  ],
  "Weather": [
    "How's the weather where you are",
    "In 1987, the BBC weatherman Michael Fish failed to predict a Hurricane",
    "The weather is very unpredictable",
    "What's the forecast for tomorrow?"
  ],
  "None": [
    "I'm afraid I'm not sure what you mean",
    "That sounds interesting, please tell me more",
    "Wow - really?"
  ]
}

将其保存在名为Data的文件夹中,并更改属性使其成为嵌入资源:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/b052952f-5315-4bc3-92db-756d0fdb2afe.png

让我们深入了解一下。我们需要的改变在机器人文件BorisBot.cs中。我们要改变OnMessageActivityAsync方法。在此过程中,我们将在中遇到另外几个新功能.NET Core 3 和 C# 8:

  1. 让我们从这里删除不再需要的代码开始:
if (intent == "Hello")
{
    await turnContext.SendActivityAsync(MessageFactory.Text($"Hello, my name is Boris"), cancellationToken);
}
else
{
    await turnContext.SendActivityAsync(MessageFactory.Text($"Echo: {turnContext.Activity.Text}"), cancellationToken);
}

我们将用一些生成更真实响应的代码来替换这个占位符代码。

  1. 我们下一步是将 JSON 文件读入文本,得到result:
var assembly = Assembly.GetExecutingAssembly();
var resourceName = "Boris.Data.Intent-Response.json";

using Stream stream = assembly.GetManifestResourceStream(resourceName);
using StreamReader reader = new StreamReader(stream);

string result = reader.ReadToEnd();

Note that we’re using the new using syntax in C# 8. This is especially nice in methods such as this because without the new syntax, the method would be longer, and in the following code the using statements would need to be nested. The new using statement has an implicit scope from the declaration point to the end of the function; in all other respects, it behaves exactly like a standard using statement.

  1. 然后,我们将 JSON 文件的文本内容和意图传递给一个新方法,然后该方法返回一个回复数组。然后,我们从该数组中选择一个随机回复,并发送:
var response = ReadResponse(result, intent);

string selectedResponse = response[_rnd.Next(response.Length)]; await turnContext.SendActivityAsync(MessageFactory.Text(selectedResponse), cancellationToken);

在我们谈论新方法ReadResponse()之前,让我们看看_rnd是如何声明的,在哪里声明的。它实际上是在类的顶部声明的:

private Random _rnd = new Random();

A discussion about how to reliably generate random numbers in C# is probably outside the scope of this chapter (and book); however, it’s worth bearing in mind that the random number algorithm uses a seed based on the time of day, meaning that if you instantiate the class twice in rapid succession and call _rnd.Next(number), you will very likely get the same number back twice.

  1. 最后,让我们看看我们的新方法:
public static string[] ReadResponse(string jsonString, string key)
{
    using var document = JsonDocument.Parse(jsonString);

    var root = document.RootElement;            
    var possibleResponses = root.GetProperty(key);

    return possibleResponses.EnumerateArray().Select(a => a.GetString()).ToArray();
}

我们再次使用新的using语法。我们还利用了新的 JSON 库,它们是.NET Core 3。期间所做的一些更改.NET Core 2.1 和 2.2 为新的、更快的.NET Core JSON 解析库。

The decision to bring this in-house wasn’t entirely because Microsoft thought they could make a better JSON parsing library than JSON.NET (in fact, at the time of writing, the author of that library was working at Microsoft.) The decision may have been driven more by the fact that some of the lowest-level libraries within the framework had a dependency on JSON.NET.

让我们简单谈谈这个新的 JSON 解析代码在做什么:一旦我们实例化了一个新的JsonDocument,我们就找到了root元素,然后通过在root上调用GetProperty返回我们正在寻找的特定文本。最后,我们调用EnumerateArray并将它放入Linq Select语句中以提取字符串。

这里的最后一步是发布机器人。

发布机器人

要开始发布过程,只需右键单击项目文件并选择“发布”。您将看到以下屏幕:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/f34f6f3f-17b0-47ac-a655-2eb8fb247e19.png

保持默认设置不变,并选择“发布”。现在,您将被问及一系列关于如何以及在哪里部署您的应用的问题:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/529ce58e-f4aa-44c3-b04b-09783822d209.png

同样,您可以将这些保留为默认值,然后单击创建。

At the time of writing, this prompted an error from Visual Studio, informing you that .NET Core 3.0 is not supported. While that is technically true, following the steps outlined in this chapter should result in a working bot. The team are hoping to get full .NET Core 3 support very soon.

您应该会看到一个网页,表明您的机器人已成功部署。记下这个页面的网址,因为我们很快就会需要它。就我而言,是这样的:

https://boris20190704074938.azurewebsites.net/

下一步是创建频道注册。

Note that an alternative to creating the App Service and Channel Registration separately is to create a bot in the portal. This will create both for you, and you can then publish your bot into that service.

创建频道注册

通道注册允许其他应用与我们的机器人通信。让我们创建一个。从 Azure 门户中选择创建资源:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/5fc39096-a909-4f8f-9e3c-0d1e4e144ee5.png

在下一个屏幕中,选择机器人频道注册:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/35d3a05a-bf87-4bb1-ae89-a7ea630dd582.png

一旦您选择了创建机器人的选项,您将看到一个类似于下面的屏幕。屏幕截图后面的列表中描述了页面的标记部分:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/ea979c11-bf43-4284-94ad-2f06cd1408db.png

  1. Bot 句柄是一个全球唯一的引用。
  2. 消息传递端点是您之前提到的网址。您应该将/api/messages附加到网址的末尾;就我而言,这就是https://boris20190704074938.azurewebsites.net/api/messages。
  3. 完成设置后,选择“创建资源”选项。

我们现在有了一个与 LUIS 集成的工作的、公开的机器人。下一步是创建我们的 UWP 应用。

微软应用标识和微软应用密码

在这一章的前面,你可能还记得我们在appsettings.json内部创造了一些价值;上次我们见到它们时,它们大致如下:

{
  "MicrosoftAppId": "",
  "MicrosoftAppPassword": "",
  "LuisAppId": "ed7252fa-43c1-4d4a-a084-2b8a58c69028",
  "LuisAPIKey": "51d06b40b7df4f41be26b7d80cd80e73",
  "LuisAPIHostName": "https://westus.api.cognitive.microsoft.com"
}

现在我们已经发布了我们的机器人,我们需要填充这些值。如果您在门户中导航到您的新频道注册并选择设置屏幕,您应该会看到类似于以下内容的内容:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/36eb20c0-0e91-4ccd-bda4-22045f8cfcf1.png

记下MicrosoftAppId。选择管理以显示证书&秘密刀片:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/019299c5-cf52-4150-be01-e85ae4f8a977.png

在这里,我们可以创造一个新的秘密:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/c5eea49a-60e9-4167-a175-7d9a1c8bf619.png

只需提供描述,然后单击添加。一旦你创造了秘密,你就只有一次机会记录下来;你再也看不到它了,所以如果你丢失了它,你需要移除并重新创建它。

一旦您记下了应用标识和密码,有两个选项可以激活它们。第一种方法(也可能是最简单的调试方法)是将它们粘贴到appsettings.json中,然后再次发布应用。但是,您不需要来完成此操作—您可以简单地导航到应用服务的配置刀片,并在那里添加一个AppSetting:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/299b9d6c-8d11-4290-b884-9e245c6f79cc.png

This is a very useful trick: any app settings that are added here will override anything in the appsettings.json. This means that you don’t need to keep sensitive data in your source code.

现在我们已经正确设置了注册,我们可以继续创建客户端应用了。

创建 UWP 应用

我们的 UWP 应用将非常简单。我们将创建一个单一的屏幕,允许我们输入一条消息,然后一个日志,用户可以看到机器人的反应,以及他们自己的聊天记录。首先,让我们创建一个新的 UWP 应用:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/0cf7ea47-84d2-4b94-92ce-71b354455810.png

初始应用由两个 XAML 文件创建:App.xaml和MainPage.xaml:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/cd80dc50-dc2b-44d3-ac32-a3797e36d979.png

程序的主要结构其实是在App.xaml : App.xaml.cs的代码隐藏中。如果你看一下那个文件,你会看到当应用被启动和挂起时,以及当应用中的导航失败时处理的代码。

主页

我们的第一个任务是改变MainPage.xaml来构建我们的屏幕。让我们看看新 XAML 的样子,然后看看我们的变化:

<Page
    x:Class="Boris_Client.MainView"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Boris_Client"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    xmlns:models="using:Boris_Client.Models"
    Loaded="Page_Loaded">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <ListView Grid.Row="0" ItemsSource="{Binding ChatHistory}">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="models:ChatMessage">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Sender}"/>
                        <TextBlock Text=":"/>
                        <TextBlock Text="{Binding Message}"/>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <TextBox Grid.Column="0" Grid.Row="0" x:Name="SendMsg" Text="{Binding MessageText}"/>
            <Button Grid.Row="0" Grid.Column="1" 
                    Command="{Binding SendMessageCommand}" 
                    CommandParameter="{Binding ElementName=SendMsg, Path=Text, Mode=OneWay}"
                    Content="Send" />
        </Grid>

    </Grid>
</Page>

我们将在接下来的章节中详细讨论这个模块。让我们从查看网格布局开始。

行定义

在 XAML,我们有一个网格的概念:本质上,每个 XAML 页面都被细分为一个网格,你可以告诉系统你想要在你的网格中有多少行和列。在我们的示例中,网格有两行。第二行需要多大就有多大(Auto)才能容纳其内容,第一行占用剩余空间(*)。

Should you wish, you can have grids nested inside grids. There is no limit (except the available memory) as to how far you can nest controls. Obviously, the deeper you nest your controls, the more work there is for the UI renderer, which can have a performance impact.

网格中的每个后续控件都指定它希望占据的行,甚至是子网格。指定行和列始终适用于直接父网格。

列表视图

接下来,我们来到ListView。理解 XAML 的关键是构图:你几乎可以达到任何效果,因为你可以把一个控件放在另一个控件里面。在ListView控件的情况下,控件本质上有两个元素:它显示的数据和它如何显示数据。

<ListView Grid.Row="0" ItemsSource="{Binding ChatHistory}">

我们稍后会回到ChatHistory到底是什么,但现在,就当它是一个集合。我们将ItemSource(即控件的数据源)绑定到事物的集合,称为ChatHistory。那就是什么;下一部分是怎么:

<ListView.ItemTemplate>
    <DataTemplate x:DataType="models:ChatMessage">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Sender}"/>
            <TextBlock Text=":"/>
            <TextBlock Text="{Binding Message}"/>
        </StackPanel>
    </DataTemplate>
</ListView.ItemTemplate>

这里,我们用DataTemplate告诉ListView每个元素都是ChatMessage类型;再次,我们将回到那是什么——重要的是它至少有两个属性:Sender和Message。在DataTemplate内部,我们声明了一个StackPanel,在这个内部,我们声明了一些TextBlock元素。这就是我所说的组合——如果你想在一个列表中显示一个元素列表,那么你需要定义数据在哪里以及如何显示它。

While this process is more verbose than other technologies, it affords a much greater degree of flexibility—for example, you could have an image control, another StackPanel, or even another ListView inside the DataTemplate. Additionally, the DataTemplate can be reused across files or even applications.

消息和命令绑定

最后,我们有第二行,它只是第一行中的另一个网格。有趣的部分显示在下面的代码中:

<TextBox Grid.Column="0" Grid.Row="0" x:Name="SendMsg" Text="{Binding MessageText}"/>
<Button Grid.Row="0" Grid.Column="1"  Command="{Binding SendMessageCommand}"  CommandParameter="{Binding ElementName=SendMsg, Path=Text, Mode=OneWay}"
        Content="Send" />

在TextBox中,我们将内容绑定到一个叫做MessageText *的东西上。*还是那句话,不要担心MessageText是什么,尽管,考虑到它与Text属性绑定,我们可以有把握地假设它是(或者必须是)一个字符串。

最后,我们有一个按钮和一个叫做命令绑定的东西。与其他绑定不同,命令绑定本质上只是获取数据并显示它,它绑定到一个方法。还是那句话,我们不要太担心SendMessageCommand到底是什么;现在,让我们接受它做了一些事情。

数据绑定和视图模型

现在我们已经创建了前端 XAML 文件,让我们来看看代码隐藏(MainPage.xaml.cs):

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();

        DataContext = new MainViewModel();    

    } 
}

如你所见,这里没什么可讨论的。事实上,我们实际上只有一行额外的代码,它将DataContext设置为MainViewModel的一个实例。

本质上,我们在这里做的是获取一个类,并将该类分配给 XAML 代码文件的DataContext。这意味着该文件中的任何内容现在都可以绑定到前端 XAML。

It is entirely your choice, but at this stage, I renamed the MainPage file to MainView. The reason that I did this is that it binds to MainViewModel, and I find it much easier to read it if the names are aligned by convention. But don’t worry: the code will still work if you don’t do this.

现在我们已经绑定了视图模型,我们可以在ViewModel上调用方法。加载页面后,我们将调用一个方法来初始化所有内容:

private async void Page_Loaded(object sender, RoutedEventArgs e)
{
    await ((MainViewModel)DataContext).Initialise();
}

我们可以先看一下ViewModel文件;然而,首先,让我们花一分钟时间来谈谈视图模型作为一个概念。想象一下,你不是在编写应用,而是在安装水槽。如果你去当地的 DIY 商店,你可能会看到几十种类型的水槽,但是你可以挑选任何一个水龙头配件数量正确的水槽,并把它带回家,相信你可以把它放在旧水槽的地方,连接几根管道,并有自来水。

这与视图和视图模型有什么关系?想象一下,进入你家的管道是景观模型。它们以原始形式携带必要的信息——也就是水。如果你没有把水槽垂直放进去,只是打开了总水管,你会看到信息从管道中涌出。水槽就像视图一样,以你想看到的任何形式排列信息或水。

Please don’t take any aspect of this analogy as reliable information on plumbing—it’s for illustrative purposes only. I am not a qualified plumber!

When designing your view and view model, you should bear this in mind. If you start to see local relating to the arrangement of the data creeping into your view model, that should be a red flag that you’re crossing a line and tightly coupling the two. One question to ask yourself is, if I wanted to display this differently, could I do so without changing the view model? If the answer is no, then consider a different approach.

让我们首先声明我们的ViewModel:

public class MainViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged([CallerMemberName]string name = null)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    } 
}

这是一个正常运行的 UWP 所需要的绝对最低代码。INotifyPropertyChanged界面是一个只有一个事件的界面:PropertyChanged。本质上,这里的目的是,当您更改属性时,您需要告诉绑定系统您已经这样做了,然后可以更新用户界面。我们在我们的 ViewModel中首先需要的是一种叫做Initialise的方法——如果你还记得的话,我们实际上更进一步地称之为:

internal async Task Initialise()
{
    await _wrapper.StartConversation(); 
}

让我们添加我们在应用中显示的数据:

public ObservableCollection<ChatMessage> ChatHistory { get; set; } = new ObservableCollection<ChatMessage>();

private string _messageText;
public string MessageText
{
    get { return _messageText; }
    set
    {
        _messageText = value;
        RaisePropertyChanged();
    }
}

MessageText短语只是一个字符串(正如我们之前猜测的那样)。如您所见,我们称之为RaisePropertyChanged事件,它是我们在设置属性时创建的。

You can choose to enhance this by only raising the event when the value changes.

第二条数据保存在一个特殊的集合中,称为ObservableCollection。

ObservableCollection

当用户界面的内容改变时,一个ObservableCollection通知用户界面。也就是说,每次添加或删除一个项目,UI 都会自动更新。一个ObservableCollection在其他方面的表现就像任何其他集合一样(比如一个List)。

It’s worth bearing in mind that, while an ObservableCollection will update the UI when you add or remove an item, it will not update the UI if you change the collection itself. For example, look at the following:

MyCollection = new ObservableCollection<MyObject>()

This would not update the UI. Furthermore, changing the contents of MyObject (in this case) would not update the UI.

目前,我们不会担心ChatHistory,尽管到目前为止你可能已经知道它会是什么样子。接下来,我们来看看命令。

命令绑定

在用户界面中,我们有一个设置了Command属性的按钮。在本节中,我们将不会看到Command实际上是什么样子的。在这段代码中,我们将使用名为RelayCommand的wrapper类。我们将很快回到RelayCommand,但是命令只是实现ICommand接口的任何类。我们在这里使用的helper类仅仅意味着我们可以在ViewModel中声明命令功能。

最初,我们只是宣布RelayCommand:

public RelayCommandAsync<string> SendMessageCommand { get; set; }

然后,我们在构造函数中实例化它:

public MainViewModel()
{
    SendMessageCommand = new RelayCommandAsync<string>(SendMessage);
}

如您所见,我们将一个方法作为参数传递给命令。在我们看方法本身之前,我们需要走一点弯路;否则,这个方法就没有多大意义了。

DirectLineWrapper

最终,整个应用的目的是向我们的机器人发送一条消息,并将回复打印在屏幕上。我们接下来要介绍的是一个包装类来实现这一点。我们将在本章后面填充实际的类代码,但是在这里,我们将使用这个类,就好像它做了我们需要的一切一样。让我们从将它声明为类级变量开始:

BotClientSdk.DirectLineWrapper _wrapper = null;

现在,我们将在构造函数中实例化它,现在应该如下所示:

public MainViewModel()
{
    SendMessageCommand = new RelayCommandAsync<string>(SendMessage); 
    _wrapper = new BotClientSdk.DirectLineWrapper(PopulateHistory);
}

你会注意到我们正在传递一个变量到DirectLineWrapper类;我们将很快回到这个方法。

Broadly speaking, instantiating a dependency inside a constructor is bad practice. It makes testing the class very difficult; should you wish to extend this project, moving this into an injected dependency would be a good start.

SendMessage法体应做如下更改:

private async Task SendMessage(string message)
{            
    await _wrapper.SendMessage(message);
    MessageText = string.Empty;
}

_wrapper有一个叫SendMessage的方法。这将在我们稍后创建的类中调用一个方法。最后,我们清除文本框,以便用户可以输入另一条消息。

我们还需要一种方法来填充历史:

private void PopulateHistory(List<KeyValuePair<string, string>> response)
{ 
    var ignored = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        foreach (var historyItem in response)
        {
            ChatHistory.Add(new ChatMessage(historyItem.Key, historyItem.Value));
        }
    });
}

这被传递到我们新的wrapper类的构造函数中——也就是说,整个函数都被传入。这里的想法是我们可以从我们的wrapper类内部更新用户界面,但是wrapper类仍然不知道它的调用上下文。我们需要在这里补充最后一点:

private CoreDispatcher Dispatcher =>
     (Window.Current == null) ?
     CoreApplication.MainView.CoreWindow.Dispatcher :
     CoreApplication.GetCurrentView().CoreWindow.Dispatcher;

这才允许我们使用Dispatcher;因为我们本质上是在一个后台线程上工作,没有这个,我们将无法更新 UI,这必然是一个前台线程。

当我们迭代这个的时候,我们把它添加到ObservableCollection;我们之前说过,以这种方式向集合中添加数据将为我们更新 UI。

在我们介绍包装器内部外观的细节之前,我们有一些已经使用过的helper类和模型。让我们从模型开始。

模型

我们使用了一个模型来存储聊天消息(单数),因此我们需要看看这个类是什么样子的。正如我之前所说的,如果你已经读完了这一章,它的形状应该不会令人惊讶:

public class ChatMessage
{
    public ChatMessage(string sender, string message)
    {
        Sender = sender;
        Message = message;
    }

    public string Message { get; set; }
    public string Sender { get; set; }
}

在我们继续之前,我们需要看RelayCommandAsync课。

RelayCommandAsync

RelayCommandAsync的代码不太繁琐。我们实现了一个名为ICommand的接口,它只有两种方法。ICommand的基本实现如下:

public class RelayCommandAsync<T> : ICommand
{ 
    public bool CanExecute(object parameter)
    {
    }

    public void Execute(object parameter)
    {
    } 
}

显然,这没有任何作用,所以让我们填写那些方法。我们先从Execute开始;我们需要将一个动作传递给Execute方法,我们可以通过构造函数来实现:

public class RelayCommandAsync<T> : ICommand
{
    readonly Func<T, Task> _execute = null;

    public RelayCommandAsync(Func<T, Task> execute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
    }

    public void Execute(object parameter)
    {
        _execute.Invoke((T)parameter);
    } 

    . . .
}

所以在这里,我们只是将一个函数委托作为构造函数传递到类中,然后在调用Execute时调用它。

The reason that we’re using Func and not Action here is that we are, in fact, returning something—namely, a Task. If the command were synchronous, we could return an Action.

最后,我们将添加CanExecute代码:

public class RelayCommandAsync<T> : ICommand
{
    readonly Func<T, Task> _execute = null;
    readonly Func<T, bool> _canExecute = null;

    public RelayCommandAsync(Func<T, Task> execute)
        : this(execute, null)
    {
    }

    public RelayCommandAsync(Func<T, Task> execute, Func<T, bool> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute.Invoke((T)parameter);
    }

    public event EventHandler CanExecuteChanged;

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }

    public void Execute(object parameter)
    {
        _execute.Invoke((T)parameter);
    } 
}

CanExecute短语本质上返回一个布尔值,表示所讨论的命令是否可以有效执行。通过将用户界面绑定到此,如果返回False,您可以禁用用户界面上的按钮。我们实际上并没有在这里使用这个功能;然而,如果我们这样做了,这段代码将会起作用。

One of the advantages of CommandBinding is that you can unit test the command itself without touching the UI—that is, your unit test covers the code immediately after pressing the button.

现在,我们已经填补了剩余的空白,我们需要谈谈直接线,以及如何与我们的机器人沟通。

通道

在我们谈论直线和它是什么之前,我们应该讨论什么是关于机器人的通道。让我们看看下面的图片:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/d70f4aaa-75b1-4da0-8f53-5d50034c8251.png

这并不是一个详尽的通道列表,而是试图说明一个通道只是一个应用和机器人之间的接口——例如,如果你愿意,你可以将你的机器人链接到 Slack。

这里有两个突出的渠道:直线和网络聊天。让我们简单地看一下它们。

直拨电话和网络聊天

我们已经看到了网络聊天:本质上,这是一种测试你的机器人的方法,当你注册一个频道时,它是免费的,这就是为什么你可以在本章的前面使用它来测试你的机器人。

直接线允许你直接与你的机器人互动。现在让我们配置一个直通通道。在 Azure 门户中,导航到您的机器人并选择频道:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/46014bf6-05d7-47a7-b056-3ae952c72bef.png

抛开奇怪扭曲的图形不谈,直达线频道就是地球仪;如果你点击它,你应该被要求命名网站:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/12f65856-e072-4c6e-adfa-1de44c6b9078.png

在下一个屏幕中,您将获得两个密钥。单击第一页上的“显示”,并保留一份副本,因为我们很快就会需要它。完成后,点按“完成”,您应该会看到现在有两个频道。

Bot 客户端

最后一步是创建我们在 UWP 应用中引用的客户端。让我们从创建一个新项目开始,并立即从 UWP 应用中引用它。. NET 标准 2.0 类库足以满足我们的需求:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/a14cd5e3-5033-4e0d-aab6-79781e3f0805.png

我们需要做的第一件事是导入两个 NuGet 包:直接行包本身和 Rest 客户端运行时:

Install-Package Microsoft.Bot.Connector.DirectLine
Install-Package Microsoft.Rest.ClientRuntime

在这个项目中,我们只需要一个类——让我们称之为DirectLineWrapper。

As a general principle, I always like to try to wrap external dependencies in a Wrapper Project to protect the main project from having too many direct external dependencies: this allows you to swap out the wrapper much easier if, for example, you wished to use a different bot service.

我们需要一些类级别的变量,包括DirectLineClient本身;我们还需要实例化它,如下所示:

public class DirectLineWrapper
{
    private string? _conversationId = null;
    private readonly DirectLineClient _client;
    Action<List<KeyValuePair<string, string>>> _updateMessages;

    public DirectLineWrapper(Action<List<KeyValuePair<string, string>>> updateMessages)
    {
        _client = new DirectLineClient("A33UCbCQoP1.QNPYS_u2Z7LhobFce9mA2ZWt47n7VzEuTjTGWHO-aL0");
        _updateMessages = updateMessages;
    }
}

传递到DirectLineClient构造函数中的字符串参数是您之前记录的秘密。我们将很快回到对话标识。如你所见,我们正在给班级注入一个动作;这将允许我们在消息进来时更新屏幕。

在这个类中,我们只需要三个方法,其中只有两个是公共的。让我们看看第一个:

public async Task SendMessage(string message)
{ 
    if (string.IsNullOrWhiteSpace(_conversationId))
    {
        throw new Exception("No active conversation");
    }

    Activity userMessage = new Activity
    {
        From = new ChannelAccount("User"),
        Text = message,
        Type = ActivityTypes.Message
    };

    var resourceResponse = await _client.Conversations.PostActivityAsync(_conversationId, userMessage); 
}

在这里,我们创建一个新的Activity实例。本质上,这是我们的信息;然而,它不一定是一条消息——它可能只是一个通知,表明您仍在键入或 ping。有很多选择,但我们对这里的Message感兴趣。

每次你和机器人开始对话,它都会给你一个对话标识。如果你每次都要开始一个新的对话,你就要清除聊天的上下文,所以你需要维护一个对 ID 的引用,如果 ID 不存在,就调用StartConversationAsync(),就能做到这一点。这就带来了我们的第二个公开方法usd:

public async Task StartConversation()
{
    if (string.IsNullOrWhiteSpace(_conversationId))
    {
        var conversation = await _client.Conversations.StartConversationAsync();
        _conversationId = conversation.ConversationId;

        new System.Threading.Thread(async () => await ReadBotMessagesAsync(_client, conversation.ConversationId)).Start();
    }
}

一旦我们开始对话,我们就开始一个新的线程来读取消息。我们的第三种方法是循环读取消息:

private async Task ReadBotMessagesAsync(DirectLineClient client, string conversationId)
{
    string watermark = string.Empty;
    var messages = new List<KeyValuePair<string, string>>();

    while (true)
    {
        var activitySet = await client.Conversations.GetActivitiesAsync(conversationId, watermark);

        lock (_lock)
        {
            watermark = activitySet.Watermark;

            var activities = from x in activitySet.Activities
                             select x;

            messages.Clear();
            foreach (Activity activity in activities)
            {
                messages.Add(new KeyValuePair<string, string>(activity.From.Id, activity.Text));
            }

            _updateMessages(messages);
       }

       await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); 
    }
}

这本质上只是读回活动;然而,这里需要注意的重要一点是水印。尽管这个名字不好,但水印是一个指针,指向我们当前在活动集中的位置。如果您没有存储水印,那么每次您都可以简单地恢复整个对话。一旦我们得到一个活动列表,我们只需调用委托_updateMessages,它会更新用户界面。

我们的项目差不多到此结束。我们现在有了一个聊天客户端,它可以与用户通信,并有希望欺骗他们相信他们正在与另一个人交谈:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/531c6f1d-a6d6-48e6-8ff1-98fb48247b38.png

正如你所看到的,机器人正在对用户做出反应,并且,不可否认地,模糊地,正在进行对话。为了提高这种交流的质量,我们只需要回到 LUIS 部署,并使用具体的响应对其进行进一步的培训。

摘要

在这一章中,我们创建了一个聊天机器人,并使用 LUIS 对语言进行了基本的理解。我们采用了默认模板,并从.NET Core 2.2 到.NET Core 3.0,并创建了一个 UWP 客户端应用,使用直接线应用编程接口与机器人通信。

我们版本的伊莱扎(鲍里斯)比原版更有可能通过图灵测试吗?也许吧。如果你想改进 LUIS 中的模型,那么你可能会给它更多的战斗机会,但是聊天机器人和语言识别的现实应用远远超出了愚弄某人以为他们在和人类说话的能力。您可以很容易地将它与语音合成器和电话集成软件(如 Twilio)联系起来,您将拥有一个自动呼叫中心。

通用视窗平台,至少目前是微软吹捧的首选桌面平台。这种情况是否会持续,我们是否会走向单一.NET 平台,我不能说。

在本书中,我们介绍了 C#语言的许多新特性。我们利用它们来使我们的代码更加易读和简洁,并且,在可空引用类型的情况下,我们实际上减少了代码中潜在错误的数量。

我们也看了一些好处.NET Core 3 提供了,允许我们将现有的桌面应用迁移到新的.NET Core 3 框架,并且使用 XAML 群岛,甚至在遗留软件中使用来自较新应用的控件。

我们广泛研究了可供使用的 Azure 服务,发现将这些服务集成到我们的代码库中是多么容易。

十一、WebAssembly

在撰写本文时,每个人都在谈论 WebAssembly。2015 年首次推出。本质上,原则是您的浏览器(所有主要浏览器都支持 WebAssembly)可以运行一种类型的编译代码(以前,您仅限于 JavaScript)。

以下链接显示了浏览器对网络组装的支持:https://caniuse.com/#feat=wasm。

火狐、Chrome、Edge、Safari 都支持。值得注意的是,一些较旧的浏览器(例如 IE)不支持它,所以如果您正在编写一些需要与这些较旧浏览器兼容的东西,您可能不得不求助于更传统的 JavaScript。

你最初可能会问自己的问题是:我为什么要在乎?希望本附录将涵盖为什么 WebAssembly 准备在未来几年接管 web 的原因。

然后,我们将运行两个示例,一个使用原生的 WebAssembly 编译,第二个使用 Blazor,这是微软的一项技术,允许您编写 C#并让它在浏览器中运行。

本章将涵盖以下主题:

  • 为什么是网络组装?
  • 编写网络程序集
  • 理解布拉佐

为什么是网络组装

就目前的情况而言,您可以编写 JavaScript,并且它在任何浏览器中都可以非常愉快地运行。假设你正在读一本关于 C#的书,你会(至少是模糊地)熟悉 JavaScript 语法。在本节中,我将尝试概述为什么您可能选择使用网络组装而不是可能的替代方法。

Due to the nature of this comparison, it inevitably contains some opinion. For example, I’m about to extol the virtues of statically typed languages over dynamic ones. If you know and are happy with using JavaScript, you may wish to skip the sections that don’t interest you.

原因一——静态类型

我个人对 JavaScript 的不满之一是它是动态键入的。这意味着您可以使用尚未显式声明的变量。这里的连锁反应是,您可能会得到以下代码:

var myInt = 2;
myNum++;

假设您希望这两个数字是相同的,那么您会希望在编译时对此进行陷印;然而,有了 JavaScript,这将运行——尽管它显然不会像您期望的那样。

There are many tools available to help to alleviate this problem, from ESLint to TypeScript. However, this involves you including a step in your build chain to capture what, in a statically-typed language, would simply be a compilation error.

原因二-已编译

这是一个微妙的问题:虽然 JavaScript 肯定不是编译的,而是解释的,但是 WebAssemby 是 JIT 编译的。话虽如此,C 或 C#的编译过程将捕捉到前一节中提到的大多数问题。此外,与 JavaScript 相比,代码的执行速度显著提高(因为 JavaScript 是解释的)。有人声称速度提高了 20 倍;然而,正如您将在下一节中看到的,这并不像最初看起来那样清晰。

原因三——速度

和原因二一样,原因三也是脆弱的。笼统地说网络组装比 JavaScript 快是错误的。如前所述,执行相同的代码比 JavaScript 更快——事实上,处理器密集型代码的速度是 WebAssembly 存在的原因之一。

但是,请记住,您仍然在浏览器的上下文中运行,因此您可能希望使用的任何外部库都需要下载。虽然这对于运行编译成 WASM 的 C 程序来说不是什么问题,但对于像 Blazor 这样的东西来说可能是个问题。必须下载. NET 运行时。

In a later section, we will look into this in more detail. As it currently stands, the version of client-side Blazor uses the Mono runtime.

原因四–您知道的语言/前端和后端的相同语言

如果你知道 JavaScript,那么你可以选择使用 Node.js 作为你的后端。选择这样做可能有很多原因,其中一个原因可能是您可以为团队雇佣 JavaScript 开发人员。我们先深究一分钟:在写这篇文章的时候,IT 行业(至少在英国)存在技能短缺;如果你能分离出你需要招聘的一项技能,那肯定会让你的工作变得更容易,因为除了编写 C#和 JavaScript 程序的开发人员之外,你还可以雇佣只使用 JavaScript 的开发人员,或者使用 Python、Ruby、VB.NET 或任何后端语言并在前端使用 JavaScript 的开发人员——事实上,任何在 2019 年为 web 编写程序的人都需要至少了解 JavaScript 的基本水平。

直到现在,如果你是一名 C#开发人员(或任何其他前述语言),你将不得不学习 JavaScript 来编写应用的前端:现在你可以用 C#编写前端和后端。

At the time of writing, client-side Blazor was not officially part of the .NET Core ecosystem. We’ll cover this in more detail later in this chapter.

还值得记住的是,随着 Xamarin 的出现,这实质上意味着你可以在任何平台上用 C#编程。

There is, in fact, an open source project that translates Xamarin.Forms code into WASM; it can be found here: https://github.com/praeclarum/Ooui.

Uno is another project that allows you to write XAML and translates it into platform-specific code (including WASM): https://platform.uno/.

原因五-现有代码

由于许多语言已经被各种工具支持编译到 WASM,您可能已经有一些代码可以简单地编译到网络汇编中。有几个例子可以说明这种成功是如何发生的。

想象你有一个完全用 C 写的游戏;您可以将它移植到浏览器上运行。就我个人而言,我正在焦急地等待一个工具的创建,它将把 Spectrum ZX80 汇编语言翻译成 WASM 语,然后我就可以在网上玩所有的老 Spectrum 游戏了!

原因六–部署

web 开发比桌面开发更受欢迎的一个主要原因是,web 浏览器解决了一个困扰我们程序员多年的问题:部署。编写一个桌面应用,并部署它——当您不能完全控制目标机器时,您会惊讶于部署它变得多么困难。

在我看来,这个原因可能是所有原因中最令人信服的:网络组装提供了桌面和网络开发环境之间的混合解决方案。

原因七——安全性

需要明确的是,WebAssembly 与 JavaScript 在相同的上下文中运行,所以它并不比用 JavaScript 编写 web 应用安全多少。然而,当你考虑你在做什么时,它实际上是相当安全的。这比给人们发送可执行文件并要求他们在自己的机器上运行要安全得多。

我已经列出了您可能希望使用网络组装的一些原因。对于某些应用,它显然比传统的 web 模型有一些优势。然而,和技术一样,这是一个权衡问题。

在下一节中,我们将看看您实际上是如何编写一些网络程序集的。

编写网络程序集

为了尝试编写一些网络组装,我们将使用网络组装工作室工具:https://webassembly.studio/。

这是一个在线工具,允许你用各种语言编写代码,并把它们编译成 WASM。在 WebAssembly Studio web 应用中,如果您创建了一个新的C Hello World应用,您可以通过构建项目非常快速地看到一些 WASM 代码:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/6b0d80af-abee-467c-a0ea-988a64ea18e0.png

如你所见,WASM 并不完全是直觉。但是,您可以自己编写(或制作)并让网络浏览器运行它。

这个工具肯定会让你对这项技术的可能性有所了解;但是,如果您不太熟悉 C 或 Rust,那么除了在浏览器中显示 42 之外,您可能很难使用它。

幸运的是,微软正在研究一项名为 Blazor 的实验技术,该技术在一个看起来很熟悉的项目结构背后抽象出了大量这项工作。

理解布拉佐

在撰写本文时,Blazor 大约两岁(2017 年首次演示)。这个想法是采用 WebAssembly 的概念,并将其与 web 开发人员熟悉的现有概念(如 Razor)混合在一起。

Part of the reason that this section is an appendix is that only server-side Blazor was released with .NET Core 3. While the client side is in a workable state, it is not in the official release at the time of writing.

Blazor 有两种风格:客户端和服务器。服务器端 Blazor 与一起发布.NET Core 3。它的工作原理是在服务器上运行 C#代码,然后使用 SignalR 向客户端发送屏幕更新。虽然这是一个有趣的方法,因为它不使用网络组装,我们不会在本章中进一步讨论它。

要创建一个布拉佐项目,你需要 Visual Studio 插件:https://marketplace.visualstudio.com/items?itemName=aspnet.blazor 。

您还需要为 Blazor 安装 Visual Studio 模板;在命令行中,键入以下内容:

dotnet new -i Microsoft.AspNetCore.Blazor.Templates

By the time this book is published, this step may no longer be necessary.

我们现在可以创建一个新的 Blazor 项目:

  1. 从 Visual Studio 2019 创建新的 Blazor 应用项目:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/0a1444ab-c3cf-4ea2-bb9f-eac44af44a39.png

  1. 在下一个屏幕中,选择您的项目名称和位置,就像您通常会做的那样:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/277fb5c8-9c6b-4b95-9e3f-43e04e6fc7bc.png

  1. 在下一个屏幕中,您应该会看到几个选项。在本例中,我们将创建一个客户端 Blazor 应用:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/10a4965b-5a54-4453-bf12-5fbc2c080a65.png

  1. 如果您现在选择创建,您将看到一个完整的 Blazor 工作示例。尝试运行这个:您应该会看到一个功能齐全的单页应用。如果你看过 React 模板,你应该对这个结构非常熟悉。

While this is true at the time of writing, the template may have changed by the time you read this.

如果您在应用周围单击,它应该感觉很像现代的单页应用。让我们看得更深一点。我们将从项目结构开始:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/8bd6f183-8235-4774-b57a-baa9a4711cd7.png

这个结构你应该很熟悉。值得注意的是.razor的扩展:事实上,名称 Blazor 来自单词 B rowser 和 R azor 的融合。

对于这个快速的概述,我们不会过多地讨论细节,但是让我们看看计数器页面;运行应用时,您应该会看到一个允许您单击按钮并增加值的页面:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/0ee1dacd-9126-41df-b394-84db42a00ddf.png

这一页给人一种极好的感觉,确切地说,这种语言是如何工作的;让我们看看驱动这个的代码。Counter.razor文件是这样的:

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="@IncrementCount">Click me</button>

@code {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
    }
}

我们有一些 HTML:页面标题、当前计数等等。然后,我们有了 Razor 语法——几乎在任何地方你都能在符号(@)上看到*。注意,该按钮将点击事件处理程序映射到@code块内的方法。Blazor 跟踪 DOM,当某些东西发生变化时,例如currentCount,页面就会被刷新。*

让我们再次运行该应用,但这一次,在浏览器中打开开发人员工具:

https://github.com/OpenDocCN/freelearn-csharp-zh/raw/master/docs/cs8-dnet-core3-proj-azure/img/055fce88-bd50-48f2-971f-271bd0dcdd7f.png

For most browsers, this can be achieved by pressing F12.

这告诉我们,在与 Blazor 打交道时,两件事非常重要。首先,看看运行应用所需的所有 dll。本质上,这是下载单声道运行时。如您所见,文件并不是特别大,但是在选择 Blazor 技术时需要记住这一点。

要注意的第二件事是,单击按钮不会进行服务器调用。所有代码都在客户端上执行。这就是 Blazor 的真正吸引人之处:你可以编写 C#代码,它在你的浏览器中运行。

我们已经快速浏览了原始形式的 WebAssembly。我们已经讨论了为什么网络组装是如此吸引人的新兴网络技术。然后我们看了一下 Blazor 项目,看它如何简化创建 web 应用的过程。

汽车以太网- IEEE 802.3标准 关于汽车以太网,大家看到最多的标准大概就是IEEE802.3bw,也就是我们车内使用的百兆以太网,专业称呼是100BASE-T1,该标准是车内百兆以太网的物理层协议,是我们学习汽车以太网必须要了解的协议。在正式了解协议之前,我们先介绍下什么是IEEE802.3标准。............ 阅读详情

相关推荐

动态博弈、威胁与承诺(博弈论入门学习笔记四)

0 动态博弈 动态博弈:行动有先后顺序,不同的参与人在不同时点行动,先行动者的选择影响后行动者的选择空间,后行动者可以观察到先行动者做了什么选择。例如下棋、消费者和商家讨价还价、谈婚论嫁、企业之间的价格战等等。 因此,为了做出最优的行动选择,每个参与人都应该思考:如果我如此选择,对方将如何应对?如果我是他,我将会如何行动?给定他的应对,什么是我的最优选择? 动态博弈中的战略:在博弈开始之前就...

stepho的博客 1万+

C#8 和 .NET Core3 Azure 项目(一)

.NET Core 是. NET 的通用、模块化、跨平台和开源实现.NET Core 3 具有改进的性能以及对桌面应用的支持。.NET Core 3 不仅应该吸引新开发人员开始学习框架,还应该说服旧开发人员开始迁移他们的应用。这本书是第二版 C# 7 和.NET Core 2.0 蓝图,更新了 C# 8 和的最新特性和增强功能.NET Core 3.0。这本书是一个全面的指南,提供了 10 个真实世界的企业应用。它将帮助您同时学习和实现这些概念,并通过在 ASP.NET Core 和 Azure 上构建满足

龙哥盟 1783

从零构建物联网监控系统:STM32+迪文屏+WiFi模组的实战解析

本文详细解析了基于STM32微控制器、迪文串口屏和WiFi模组构建物联网监控系统的完整方案。从硬件选型、系统架构设计到数据交互实现,提供了包括迪文屏GUI开发、STM32通信协议、低功耗优化等实战经验,帮助开发者快速掌握物联网监控系统的核心技术。

docker7nomad的博客 729

C#8 和 .NET Core3 Azure 项目(二)

通常,在客户机-服务器关系中,例如访问网站,通信是由客户机发起的。你可以访问一个显示股价的网站,点击一个特定的股票代码,网站就会为你检索该股票的价格。一旦你得到了价格,你可能会让页面打开,并在一个小时内返回。股票的价格完全一样;你刷新网页,股票价格被重新提取,现在显示正确。解决这个问题的一个可能的方法是让服务器在准备好的时候发送信息给客户端。SignalR 提供这种能力。然而,SignalR 不是一种单一的技术——它实际上是一堆技术,被抽象化了。

龙哥盟 1296

从零到一:桌面宠物的硬件架构设计与工程实践

本文详细介绍了基于STM32F103C8T6主控平台的桌面宠物机器人硬件架构设计与工程实践。内容涵盖电源管理、舵机控制、PCB布局及系统调试等核心环节,为硬件爱好者提供从零到一的完整开发指南,助力打造稳定可靠的智能桌面伴侣。

alice7model的博客 1112

【YOLOv11多模态涨点改进】独家创新首发| TGRS 2026 |引入 CIFusion 通道交互融合模块,通过跨特征交互机制强化目标区域响应,适合多模态融合目标检测,小目标检测高效涨点

🔥本文提出了一种改进YOLOv11多模态目标检测模型的方法,通过引入CIFusion通道交互融合模块来提升RGB与红外特征融合效果。CIF模块采用通道级自适应交互机制,根据全局上下文动态分配不同模态通道的重要性,实现跨模态特征交互与互补信息融合。相比传统拼接或加权融合方式,CIF能够增强目标区域响应,抑制背景噪声,提高小目标和低对比度目标的检测性能。实验表明,该方法在DroneVehicle等数据集上显著提升了检测精度。论文还详细介绍了CIF模块的结构原理、实现代码及模型配置方法。

Ai缝合怪、助力小伙伴高效发论文 187

C#8 和 .NET Core3 Azure 项目(三)

自 1997 年我开始第一份工作以来,计算已经经历了一些有趣的变化,自 20 世纪 80 年代初我得到第一台计算机(Spectrum ZX81)以来,变化更大。在光谱上,如果你想保存一些信息,你可以把它录在磁带上;一个典型的游戏大约需要 5 到 10 分钟才能从磁带中载入。当然,在当时,更复杂的计算机正在使用,但大部分繁重的存储实际上仍然是在磁带上完成的。甚至在我买了第一台电脑后,我还记得为它买了一个磁带机,这样我就可以存储高达 10 亿字节的信息(10 亿字节的信息在当时是一个很大的数据量)。如今,你的手

龙哥盟 1847

【回眸】Tessy集成测试软件使用指南(一)新手使用篇

这个专栏的文章前4篇都在记录如何使用Tessy进行单元测试,集成测试需要有一定单元测试基础,且做集成测试之前,需要做好单元测试,否则将会大幅增加软件单元代码纠错的代价。集成测试所花费的时间通常远远超过单元测试。如果直接从单元测试到系统测试,结果往往是不理想的。集成测试可以检测由于单元相互交互而导致的错误,通常由一组专业的测试人员执行。

啤酒鸭的物联网开发记录博客 5281

C#8 和 .NET Core3 微服务实用手册(一)

这本书的目的是让你对现代软件开发中的微服务有一个广泛的了解,同时也用具体的例子来深入了解更多的细节.NET Core 应用接口。这本书涵盖了微服务的理论,然后是对用于开发虚拟应用的模式的高级理解,其中我们将涵盖解决方案中使用的概念,这些解决方案可以轻松配置为在云中或内部运行。值得注意的是,微服务架构确实具有关键优势,尤其是在支持敏捷改进和复杂风险应用的传输方面。然而,对于如何在微软生态系统中实现微服务,尤其是如何利用 Azure 和.NET Core 框架。这本书试图填补这一空白。

龙哥盟 1270

scratch绘制四个三角形 2024年6月中国电子学会 图形化编程 scratch编程等级考试二级真题和答案解析

​ scratch绘制四个三角形 一、题目要求 2024年6月电子学会图形化编程Scratch等级考试二级真题 1、准备工作 1.保留默认角色小猫; 2.添加背景Stars。 2、功能实现 1 .隐藏角色小猫,设置画笔裙始位置为(0,0),画笔颜色为黄色,画笔的粗细为5; 2. 点击绿旗,绘制如下图所示图形,三角形的边长为50。 二、案例分析 1、角色分析 角色:小猫 2、背景分析 背景:Stars背景 3、前期准备 1. 保留默认的小猫不变 ​

青少年编程学习指导 1717

【安卓逆向】python版本 sha源码

免责声明本文内容仅供学习交流使用,严禁用于商业和非法用途。抓包数据、敏感网址以及数据接口已进行脱敏处理。 注:如有引用,请标注文章地址!!!!!!!

Coodekun的博客 817

C#8 和 .NET Core3 软件架构实用指南(一)

本书涵盖了软件架构中涉及的最常见的设计模式和框架。它通过提供实际的真实场景,讨论何时以及如何使用每个模式。本书还介绍了 DevOps、微服务、持续集成和云计算等技术流程,以便你能够为你的客户提供一流的软件解决方案。本书将帮助你理解客户希望从你这里得到的产品。它将指导你解决开发过程中可能遇到的最大问题。它还涵盖了你在基于云的环境中管理应用时需要遵循的“做”和“不做”事项。你将了解不同的架构方法,如分层架构、面向服务的架构、微服务和云架构,并了解如何将它们应用于特定的业务需求。最后,你将使用 Azure 在远程

龙哥盟 1115

零基础学写《扫雷》游戏(Qt扫雷源码)

扫雷游戏是在windows系统中自带的游戏,他的游戏玩法其实很简单,20世纪初期电脑上没有啥游戏可以玩,所以扫雷游戏也算是当时是最常火爆的一个电脑游戏。以下文章详细介绍扫雷游戏的实现。实现语言为C++。

啊渊的专栏 1万+

.NET Core 和 .NET Framework 的不同

一、各自优势 .NET Core的优势: 如果您正在构建一个新应用程序并在 .NET Core 和 .NET Framework 之间进行选择,那么 .NET Core 是您的最佳选择。 微软刚刚发布了 .NET Core v 3.0,它是 .NET Core 的一个大大改进的版本。如果您想为未来学习和构建,那么 .NET Core 就是您的最佳选择。 .NET 3.0 现在支持 WPF 和 Windows 窗体。.NET Core 3.0 还支持 UWP、WPF 和 Windows 窗..

weixin_50498482的博客 1万+

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

与时俱进 | 博客现已运行在 .NET Core 3.0 及 Azure 上

点击上方蓝字关注“汪宇杰博客”导语9月23日,微软正式发布了 .NET Core 3.0,这个版本具有大量新功能和改进。我也在第一时间将自己的博客网站更新到了 .NET ...

dotNET跨平台 430

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

intro-to-dotnet-web-dev项目概览:从C到Azure部署的完整学习路径

intro-to-dotnet-web-dev是一个全面的.NET Web开发入门项目,通过6个模块化课程带您从C#基础逐步掌握Razor Pages、Minimal APIs、Blazor等核心技术,并最终实现Azure云部署。项目包含丰富的实践案例和可视化教学资源,特别适合新手快速构建现代化Web应用。 ## 🚀 为什么选择这个学习路径? 对于Web开发新手而言,选择合适的学习资源至关重

gitblog_00941的博客 827

ASP.NET Core Webhooks 示例项目推荐

ASP.NET Core Webhooks 示例项目推荐 1. 项目基础介绍和主要编程语言 ASP.NET Core Webhooks 示例项目是由 Microsoft Graph 团队开发的开源项目,旨在展示如何使用 ASP.NET Core 应用程序订阅 Microsoft Graph 的 Webhook 通知。该项目的主要编程语言是 C#,并且基于 .NET 平台。通过这个项目,开发者可以学...

gitblog_00171的博客 487

通俗易懂,什么是.NET Core以及.NET Core能做什么

与其他软件框架不同,.NET Core是最通用的框架,可用于构建各种软件,包括Web应用程序、移动应用程序、桌面应用程序、云服务、微服务、API、游戏和物联网应用程序。.NET Core支持微服务体系结构,它允许跨平台服务与.NET Core一起工作,包括使用.NET Framework、Java、Ruby或其他开发的服务。ASP.NET Core是.NET Core生态系统的核心组件。与.NET Framework和.NET Core 2.2及以前的版本相比,.NET Core 3.0的速度很快。

1442

从本地到云端:intro-to-dotnet-web-dev项目Azure部署完整教程

intro-to-dotnet-web-dev项目是一个面向Web开发者的入门教程,使用.NET、C#和ASP.NET Core技术栈。本教程将详细介绍如何将该项目从本地环境部署到Azure云平台,让你的Web应用快速拥有企业级的托管服务。 ## 为什么选择Azure部署.NET应用? Azure是微软提供的云平台,专为简化现代应用构建流程而设计。对于.NET开发者而言,Azure提供了无缝的

gitblog_00722的博客 787

C#8 和 .NET Core3 软件架构实用指南(二)

数据库设计,就像整个应用程序设计一样,是有组织的迭代过程。让我们假设在第一次迭代中,我们需要一个包含两个数据库表的原型:一个用于所有旅行套餐,另一个用于所有由套餐引用的位置。每个套餐只覆盖一个位置,而单个位置可能被多个套餐覆盖,因此两个表通过一对一的关系连接。因此,让我们从位置数据库表开始。正如我们在上一节末尾提到的,我们需要一个实体类来表示这个表的行。让我们将实体类命名为set;set;set;set;所有数据库字段都必须由可读/写的 C#属性表示。

龙哥盟 826

Azure SDK for .NET 安装与配置指南

Azure SDK for .NET 是一个开源项目,用于开发与 Microsoft Azure 云服务交互的 .NET 应用程序。该项目提供了丰富的库和工具,使得 .NET 开发者能够轻松地在自己的应用程序中集成 Azure 服务。主要编程语言为 C#。 ## 项目使用的关键技术和框架 - **.NET Core/.NET 5+**:项目支持最新的 .NET Core 和 .NET 5 及以...

gitblog_00792的博客 984

C#7 和 .NET Core 秘籍(四)

源代码控制是每个开发人员工具包的重要组成部分。无论您是业余爱好者还是专业程序员,当您离开办公桌回家时,您最好确保您的代码是安全的。在本章中,我们将讨论选择和使用源代码控制策略。我们将讨论一些主题,比如:设置 Visual Studio 帐户管理并确定哪种源代码控制解决方案最适合您设置 Visual Studio GitHub 集成,首次检入代码,以及检入更改使用 GitHub 作为团队合作,处理和解决代码冲突在我的职业生涯中,我使用过 Visual SourceSafe、SVN、VSTS、Bitbucket

龙哥盟 1169

Azure Data Factory 开源项目安装与配置指南

Azure Data Factory 是由微软开源的一个数据集成和调度服务项目。它允许用户创建和调度数据驱动的工作流(称为管道),以自动移动和转换数据。这个项目主要使用 C# 编程语言,同时也包含了 PowerShell、Jupyter Notebook 等其他语言编写的代码。 ## 2. 项目使用的关键技术和框架 该项目使用以下关键技术和框架: - .NET Framework:用于构建和

gitblog_00241的博客 1001

完整的DevOps实战指南:如何使用PartsUnlimited开源项目掌握.NET Core和Azure部署

PartsUnlimited是一个基于.NET Core和SQL Azure的汽车零部件电商开源项目,专为DevOps培训场景设计。这个完整的电商网站示例不仅展示了现代Web开发技术,更是一个完美的DevOps实战平台,帮助开发者掌握持续集成、持续部署和云原生应用开发的核心技能。 ## 🚀 项目概述与核心价值 PartsUnlimited项目源于《凤凰项目》一书中的概念,是一个功能完整的汽车

gitblog_00235的博客 296

C#/.NET/.NET Core技术前沿周刊 | 第 10 期(2024年10.14-10.20)

C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与视野拓宽。欢迎投稿、推荐或自荐优质文章、项目、学习资源等。每周一定期发布!!!📰C#/.NET/.NET Core技术前沿周刊汇总🏆C#/.NET/.NET Core技术前沿周刊Issues(投稿地址)文章简介:每天5分钟玩转C#/.NET之C#语言详细介绍。

专注于C#/.NET/.NET Core学习、工作、面试干货和实战教程分享。这里聚焦了大量的C#/.NET/.NET Core优质文章、开源项目、实用工具和学习、工作、面试心得。 1236

终极指南:解决Azure DevOps .NET示例项目的10个常见问题

Azure DevOps .NET示例项目(azure-devops-dotnet-samples)是微软官方提供的C#代码示例库,帮助开发者快速集成Azure DevOps Services和Azure DevOps Server。本文将解答新手使用过程中最常见的10个问题,让你轻松掌握这个强大工具的使用技巧。 ## 一、如何正确克隆项目仓库? 许多用户在第一步就遇到了问题。正确的克隆命令是

gitblog_01135的博客 879
上一篇: C#8 和 .NET Core3 Azure 项目(三)
下一篇: C# 数据结构与算法第二版(一)
绝不原创的飞龙
绝不原创的飞龙 优质创作者: 人工智能、数据科学与机器学习技术领域 优质创作者: 人工智能、数据科学与机器学习技术领域
博客等级 码龄15年 4万+粉丝 2万+原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值