C# -- HttpWebRequest 和 HttpWebResponse 的使用
C# -- HttpWebRequest 和 HttpWebResponse 的使用
结合使用HttpWebRequest 和 HttpWebResponse,来判断一个网页地址是否可以正常访问。
1.举例
class Program { static void Main(string[] args) { string strUrl = "https://www.baidu.com"; HttpWebRequest wreq = (HttpWebRequest)WebRequest.Create(strUrl); HttpWebResponse wrsp = (HttpWebResponse)wreq.GetResponse(); if (wrsp.StatusCode == HttpStatusCode.OK) { Console.WriteLine(strUrl+ " --http response正常\r\n"); } else { Console.WriteLine(strUrl + " --http response出现异常!\r\n"); } Console.ReadKey(); } }
2. 运行结果:
C#编写扫雷游戏
翻看了下以前大学学习的一些小项目,突然发现有个项目比较有意思,觉得有必要把它分享出来。当然现在看来,里面有很多的不足之处,但因博主现在已经工作,没有时间再去优化。这个项目就是利用C#编写一个Windows系统下的扫雷小游戏。
首先讲下扫雷小游戏的玩法:
(1)扫雷就是要把所有非地雷的格子揭开即胜利;踩到地雷格子就算失败。
(2)当点开的格子不是地雷区域的时候,该格子会显示一个数字,该数字表示的含义就是该格子周边有多少个地雷。
(3)同时点开的如果非地雷的格子,周边连贯的非地雷区域都会自动被扫描打开,直到遇到旁边有雷区的时候停止。
(4)当你判断出格子是地雷的时候,你可以使用鼠标右键将该块方格标记为雷区。当不确定的时候,你可标记个问号以待确定。
下面来说下我大学时候实现这个扫雷小游戏的思路:
(1)因为雷区是一个个格子联合组成的,那我们可以使用winform程序自带的系统按钮控件Button来实现雷区方格。
(2)代表雷区方格的Button按钮需要实现下面几个事件:鼠标左键点击扫雷事件,鼠标右键点击标记雷区事件,鼠标右键点击标记问号区域事件。
(3)为了更好的实现游戏的可玩性,增加一个自由设置地雷数量的小功能,可自行设置雷区包含的地雷数量,设置完成后,自动刷新界面,重新部署地雷。
(4)我们将雷区的方格存储在一个全局的二维数组中,Form窗体在初始化的时候,自动生成面板区域的Button按钮列表。
(5)为了实现每次玩游戏的时候,地雷分布不一致,我们在Button列表生成后。随机抽取出某些Button按钮作为地雷分布点,并记录该Button的雷区属性为含有地雷。
(6)算法中的关键:递归算法计算雷区。当点击某个方格的时候,如果该方格是雷区,则直接Game Over,如果不是的话,则我们需要一个算法去计算旁边区域的地雷数量,以及旁边区域没有地雷的区域,当没有地雷的区域连成一片的时候,我们需要使用递归算法,去查找二维数组,找到对应的连片非雷区,将之打开。
(7)如何设置方格的状态:当鼠标左键点击的方块区域非雷区的时候,我们将Button按钮的属性设置为Disabled即可呈现打开的状态。当鼠标左键打开的方格是雷区时候,此时我们可以将所有地雷区域块的Button的背景图设置为地雷图片,并播放相应的爆炸音效,弹出游戏终止界面即可。当鼠标右键标记雷区或者待确定区域的时候,只需要更改Button的背景图即可。当然上述所有点击操作,都得判断Button方格区域当前的状态值:初始化状态、已标记为待确定状态、已标记为雷区。
游戏的最终效果图如下:
我们下面来剖析下几个关键点,因为代码量比较多,我就不全部详细剖析了。
首先我们定义一个LeiButton类,这个类继承于系统控件Button,增加x,y,youlei三个字段,x表示二维数组的第一个索引,y表示二维数组的第二个索引值,youlei用于标记Button方块区域按钮的状态(0表示无雷,1表示有雷)。同时我们使用Button按钮类自带的一个Tag属性标记该方块区域是否被翻开。具体定义如下:
然后我们在窗体对象Form类中定义一些常用的变量之类,如下图,都有相应注释
下面我们来看下生成Button的二维数组,即生成雷区的Button列表。我们需要在Form中添加GroupBox组件,然后将动态生成的Button列表添加到这个groupbox组件中。生成Button的二维数组方法体如下:
生成完Button列表后,我们就开始部署地雷了,地雷随机部署到Button列表中,部署地雷的方法如下:
到了这一步,我们就应该将雷区的界面渲染出来了,这时候我们可以将上面两个方法放入窗体的Form_Load事件中即可渲染出游戏界面。如下所示
我们继续,下面写扫雷的算法,当鼠标左键点开某个方格的时候,如果该方格不是雷区,那我们需要计算该方格周边的地雷数量,计算方法如下:
下面是个递归计算的核心算法,非常关键。当我们点开的方格非雷区的时候,周边连片的非雷区的方格块会被打开。这一块的核心算法参考下列代码,row表示行,col表示列
下面我们来添加鼠标的点击事件,我这边采用的是bt_MouseUp事件来处理。点击后,我们首先判断游戏是否结束,如果没结束,则进行下列操作,获取到被点击的按钮的x,y值以及点击事件按下的键值(判断按下的是鼠标左键还是右键)。x,y值获取到了,我们就可以到Button二维数组中找到对应元素。
假如点击的是鼠标左键,则我们进行扫雷操作,具体的代码如下:
如果按下的是鼠标右键,则是标记方块是雷区或者待确定区域,具体代码如下:
最后再给出一个判断是否扫雷完毕的方法。
扫雷游戏的设计大概到此结束,中间还有很多可以优化的地方,比如将扫雷的逻辑代码抽离Form类独立出来等,这些都靠读者自行去优化了。
附上博主源码下载的百度云链接,需要代码学习的可自行下载研究:https://pan.baidu.com/s/1T4zVndyypzY9i9HsLiVtGg 提取码: b2im
最后,附上博主的IT技术学习群,欢迎各位同行入群指导交流。
使用IIS调试ASP.NET网站程序
在实际的开发当中,相信很多的开发者在开发调试ASP.NET网站时候都是直接通过Visual Studio工具的编译运行来调试的。
一般情况下,这种调试方式也不会有多少问题,但有时候我们会发现这样的一个情况,就是明明在本地调试运行都没有问题的网站,部署到服务器IIS上,就出现无法描述的线上问题。这时候,就要排除服务器环境是否跟本地环境一样,其中一点还要涉及到IIS的配置的排除。
那问题来了,我们能不能在开发的时候,就部署在IIS上调试,而不是直接使用Visual Studio的运行调试,这样涉及到IIS配置的相关问题就能及早的暴露出来,比如IIS用户的权限调用等。举个很典型的例子,如我们调用微软的Office Excel组件导出Excel的时候,就需要设置IIS用户组对Com组件的调用权限。
在开发阶段就通过IIS对网站进行调试,可以避开很多因为IIS设置问题而导致的问题。
下面来说下配置IIS调试的步骤。
第一步 打开你的IIS管理器,没有安装IIS服务器的请上网百度如何安装,此处不再阐述。在IIS中新建一个网站,给定一个主机域名为test.com,并将网站目录指向你web项目的根目录,如下图。
第二步 网站配置的应用程序池很关键,上图中的应用程序池的名字叫testWeb,那这个应用程序池的.NetFrameWork的版本号应该跟你项目中使用的版本一致。我们点击上图左侧的菜单【应用程序池】,打开应用程序池列表,选中testWeb名称的应用程序池,确认编辑好该程序池的版本号。如下图
第三步 确保你的IIS的状态为正在运行而非停止的状态,并且确认你网站的状态以及网站对应应用程序池的状态为正在运行。
第四步 设置本地电脑的Host文件,配置本地域名test.com。让浏览器识别test.com域名的指向是本地计算机。(此步骤非常关键,不然你在浏览器中输入test.com会告知你无法解析)
我们在电脑的C盘中查找系统的Host文件,Host文件一般在以下路径中:C:\Windows\System32\drivers\etc。
找到Host文件后,用文本编辑器打开host文件,推荐使用notepad++文本编辑器。打开后,我们在最后一行加入127.0.0.1 test.com即可,配置完成后保存(Win10系统可能无法编辑保存,我们一般采取复制文件出来,编辑完成后覆盖回原文件),在浏览器中输入test.com即可指向本地IIS中的网站,即你刚才配置的网站。
第五步 此时配置完毕后,我们在自己本地电脑的浏览器中输入test.com的时候,就会指向你本地IIS配置的test.com网站中,即你当前开发的网站。
第六步 如果test.com网站测试中发现相应的问题,我们就可以通过附加IIS进程的方式对网站进行调试。当然调试的方式与之前不同了,此时就不是直接点击Visual Studio中的调试按钮或者F5进行调试了。我们可以通过下面的方式进入调试模式,选择菜单栏上的调试,打开子菜单选中附加到进程,如下图(当然可以通过快捷键Ctrl+Alt+P直接调取出附加界面)
打开后的附加界面如下图,我们找到w3wp.exe进程,然后点击附加按钮即可。
附加完成后,你就可在代码中加入相应的断点调试了,在前台浏览器中输入test.com访问出现的错误,你可在Visual Studio中找到对应代码段加入断点。
通过上述步骤,即可很好的进行网站的调试,IIS相应的配置问题也能及早发现。
WCF入门教程
这一系列文章的内容是从MSDN中COPY过来的,讲述的是最简单的WCF程序示例:如何在控制台应用程序实现和承载WCF服务,以及如何创建、配置和使用WCF客户端。
文章主体可分为两部分,分别介绍服务器端和客户端的编程实现。细分的话,可以分为六项任务。
- 服务器端
定义WCF服务协定(任务一)
这是创建基本 Windows Communication Foundation (WCF) 服务和可以使用该服务的客户端所需的六项任务中的第一项任务。
创建基本 WCF 服务时,第一项任务是为与外界共享的服务创建协定,并在其中描述如何与该服务进行通信。
具体步骤为:
1、 创建新的控制台应用程序项目。 在“新建项目”对话框中,选中“Visual Basic”或“Visual C#”,并选择“控制台应用程序”模板,并命名为Service。 使用默认的位置。
2、将默认的Service 命名空间更改为 Microsoft.ServiceModel.Samples。
3、为项目提供对 System.ServiceModel 命名空间的引用:右击“解决方案资源管理器”中的“Service”项目,选择“添加引用”项,在弹出的对话框中的“.NET”选项卡里的“组件名称”中选择“System.ServiceModel”,然后单击“确定”。
下面是编程步骤:
1、为 System.ServiceModel 命名空间添加一个 using 语句。
using System.ServiceModel;
2、创建一个新的ICalculator 接口,并将 ServiceContractAttribute 属性应用于该接口,并将 Namespace 值设置为“http://Microsoft.ServiceModel.Samples”。 此命名空间指定该服务在计算机上的路径,并构成该服务的基址部分。 请注意,在通过采用方括号表示法的属性来批注接口或类时,该属性类可以从其名称中去掉“Attribute”部分。
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface ICalculator
3、在接口中创建方法声明,并将 OperationContractAttribute 属性应用于每个要作为公共 WCF 协定的一部分公开的方法。
[OperationContract]
double Add(double n1, double n2);
[OperationContract]
double Subtract(double n1, double n2);
[OperationContract]
double Multiply(double n1, double n2);
[OperationContract]
double Divide(double n1, double n2);
下面是创建服务协定的完整代码段:
using System;
// Add the using statement for the Sytem.ServiceModel namespace
using System.ServiceModel;
namespace Microsoft.ServiceModel.Samples
{
// Define a service contract.
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface ICalculator
{
// Create the method declaration for the contract.
[OperationContract]
double Add(double n1, double n2);
[OperationContract]
double Subtract(double n1, double n2);
[OperationContract]
double Multiply(double n1, double n2);
[OperationContract]
double Divide(double n1, double n2);
}
}
........(整个服务器端的代码未完,等讲完任务三时,再给出完整代码)
实现WCF服务协定(任务二)
1、创建一个新 CalculatorService 类,该类从用户定义的 ICalculator 接口继承而来并实现该接口定义的协定功能。
public class CalculatorService : ICalculator
2、实现每个算术运算符的功能。
public double Add(double n1, double n2)
{
double result = n1 + n2;
Console.WriteLine("Received Add({0},{1})", n1, n2);
// Code added to write output to the console window.
Console.WriteLine("Return: {0}", result);
return result;
}
public double Subtract(double n1, double n2)
{
double result = n1 - n2;
Console.WriteLine("Received Subtract({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Multiply(double n1, double n2)
{
double result = n1 * n2;
Console.WriteLine("Received Multiply({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Divide(double n1, double n2)
{
double result = n1 / n2;
Console.WriteLine("Received Divide({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
在创建和实现了服务协定后,下一步是运行该服务。 运行服务由三个步骤组成:配置、承载和打开服务。
配置、承载和运行服务(任务三)
- 为服务配置基址
为服务的基址创建 Uri 实例。 此 URI 指定 HTTP 方案、本地计算机、端口号 8000,以及服务协定中为服务命名空间指定的服务路径ServiceModelSample/Services。
Uri baseAddress = new Uri("http://localhost:8000/ServiceModelSamples/Service");
- 承载服务
- 创建一个新的 ServiceHost 实例以承载服务。 必须指定实现服务协定和基址的类型。 对于此示例,我们将基址指定为http://localhost:8000/ServiceModelSamples/Services,并将 CalculatorService 指定为实现服务协定的类型。
ServiceHost selfHost = new ServiceHost(typeof(CalculatorService), baseAddress); |
- 添加一个捕获 CommunicationException 的 try-catch 语句,并在接下来的三个步骤中将该代码添加到 try 块中。
- 添加公开服务的终结点。 为此,必须指定终结点公开的协议、绑定和终结点的地址。 此例中,将 ICalculator 指定为协定,将 WSHttpBinding 指定为绑定,并将 CalculatorService 指定为地址。 在这里请注意,我们指定的是相对地址。终结点的完整地址是基址和终结点地址的组合。 在此例中,完整地址是 http://localhost:8000/ServiceModelSamples/Services/CalculatorService。
selfHost.AddServiceEndpoint( typeof(ICalculator), new WSHttpBinding(), "CalculatorService"); |
- 启用元数据交换。 为此,必须添加服务元数据行为。 首先创建一个 ServiceMetadataBehavior 实例,将 HttpGetEnabled 属性设置为 true,然后为服务添加新行为。
ServiceMetadataBehavior smb = new ServiceMetadataBehavior(); smb.HttpGetEnabled = true; selfHost.Description.Behaviors.Add(smb); |
- 打开 ServiceHost 并等待传入消息。 用户按 Enter 键时,关闭 ServiceHost。
selfHost.Open();
Console.WriteLine("The service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
// Close the ServiceHostBase to shutdown the service.
selfHost.Close();
下面是服务器端(即“Service”项目中program.cs文件中)的完整程序代码:
using System;
using System.ServiceModel;
using System.ServiceModel.Description;
namespace Microsoft.ServiceModel.Samples
{
// Define a service contract.
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface ICalculator
{
[OperationContract]
double Add(double n1, double n2);
[OperationContract]
double Subtract(double n1, double n2);
[OperationContract]
double Multiply(double n1, double n2);
[OperationContract]
double Divide(double n1, double n2);
}
// Service class that implements the service contract.
// Added code to write output to the console window.
public class CalculatorService : ICalculator
{
public double Add(double n1, double n2)
{
double result = n1 + n2;
Console.WriteLine("Received Add({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Subtract(double n1, double n2)
{
double result = n1 - n2;
Console.WriteLine("Received Subtract({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Multiply(double n1, double n2)
{
double result = n1 * n2;
Console.WriteLine("Received Multiply({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
public double Divide(double n1, double n2)
{
double result = n1 / n2;
Console.WriteLine("Received Divide({0},{1})", n1, n2);
Console.WriteLine("Return: {0}", result);
return result;
}
}
class Program
{
static void Main(string[] args)
{
// Create a URI to serve as the base address.
Uri baseAddress = new Uri("http://localhost:8000/ServiceModelSamples/Service");
// Create ServiceHost
ServiceHost selfHost = new ServiceHost(typeof(CalculatorService), baseAddress);
try
{
// Add a service endpoint.
selfHost.AddServiceEndpoint(
typeof(ICalculator),
new WSHttpBinding(),
"CalculatorService");
// Enable metadata exchange.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
selfHost.Description.Behaviors.Add(smb);
// Start (and then stop) the service.
selfHost.Open();
Console.WriteLine("The service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
// Close the ServiceHostBase to shutdown the service.
selfHost.Close();
}
catch (CommunicationException ce)
{
Console.WriteLine("An exception occurred: {0}", ce.Message);
selfHost.Abort();
}
}
}
}
若要运行服务,启动项目文件夹下bin目录中的 Service.exe即可。
前面三篇文章是讲服务器端的部署和运行,下面讲讲客户端如何配置和使用。
创建WCF客户端(任务四)
- 通过执行以下步骤,在 Visual Studio 2005 中为客户端创建新项目:
- 在包含该服务(之前文章所述的服务)的同一解决方案中的“解决方案资源管理器”(位于右上角)中,右击当前解决方案,然后选择“添加新项目”。
- 在“添加新项目”对话框中,选择“Visual Basic”或“Visual C#”,选择“控制台应用程序”模板,然后将其命名为 Client。 使用默认的位置。
- 单击“确定”。
- 为项目提供对 System.ServiceModel 命名空间的引用:在“解决方案资源管理器”中右击“Service”项目,从“.NET”选项卡上的“组件名称”列中选择“System.ServiceModel”,然后单击“确定”。
- 为 System.ServiceModel 命名空间添加 using 语句:using System.ServiceModel;
- 启动在前面的步骤中创建的服务。(即打开在服务器项目中生成的Service.exe可执行文件)
- 通过执行以下步骤,使用适当的开关运行Service Model Metadata Utility Tool (SvcUtil.exe) 以创建客户端代码和配置文件:
- 通过选择“开始”菜单中的“Microsoft Windows SDK”项下的“CMD Shell”,启动 Windows SDK 控制台会话。
- 导航到要放置客户端代码的目录。 如果使用默认设置创建 Client 项目,则目录为 C:\Documents and Settings\<用户名>\Documents\Visual Studio 2008\Projects\Service\Client。
- 将命令行工具Service Model Metadata Utility Tool (SvcUtil.exe) 与适当的开关一起使用以创建客户端代码。 下面的示例生成服务的代码文件和配置文件。
svcutil.exe /language:cs /out:generatedProxy.cs /config:app.config http://localhost:8000/ServiceModelSamples/service
- 在 Visual Studio 中将生成的代理添加到 Client 项目中,方法是在“解决方案资源管理器”中右击“Client”并选择“添加现有项”。 然后选择在上一步中生成的 generatedProxy.cs 文件。
配置WCF客户端(任务五)
在 Visual Studio 中,将在前一过程中生成的 App.config 配置文件添加到客户端项目中。 在“解决方案资源管理器”中右击该客户端,选择“添加现有项”,然后从 C:\Documents and Settings\<用户名>\Documents\Visual Studio 2008\Projects\Service\Client\bin 目录中选择 App.config 配置文件。
将app.config添加到项目中后,就算是完成了wcf客户端的配置。因为具体的配置信息,我们在使用svcutil.exe工具时,它就帮我们配置好并写入了app.config文件。
使用WCF客户端(任务六)
1、为要调用的服务的基址创建 EndpointAddress 实例,然后创建 WCF Client 对象。
//Create an endpoint address and an instance of the WCF Client.
EndpointAddress epAddress = new EndpointAddress("http://localhost:8000/ServiceModelSamples/Service/CalculatorService");
CalculatorClient client = new CalculatorClient(new WSHttpBinding(), epAddress);
2、从 Client 内调用客户端操作。
// Call the service operations.
// Call the Add service operation.
double value1 = 100.00D;
double value2 = 15.99D;
double result = client.Add(value1, value2);
Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
// Call the Subtract service operation.
value1 = 145.00D;
value2 = 76.54D;
result = client.Subtract(value1, value2);
Console.WriteLine("Subtract({0},{1}) = {2}", value1, value2, result);
// Call the Multiply service operation.
value1 = 9.00D;
value2 = 81.25D;
result = client.Multiply(value1, value2);
Console.WriteLine("Multiply({0},{1}) = {2}", value1, value2, result);
// Call the Divide service operation.
value1 = 22.00D;
value2 = 7.00D;
result = client.Divide(value1, value2);
Console.WriteLine("Divide({0},{1}) = {2}", value1, value2, result);
3、在 WCF 客户端上调用 Close。
// Closing the client gracefully closes the connection and cleans up resources.
client.Close();
下面是客户端的完整代码:
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
namespace ServiceModelSamples
{
class Client
{
static void Main()
{
//Step 1: Create an endpoint address and an instance of the WCF Client.
EndpointAddress epAddress = new EndpointAddress("http://localhost:8000/ServiceModelSamples/Service/CalculatorService");
CalculatorClient client = new CalculatorClient(new WSHttpBinding(), epAddress);
// Step 2: Call the service operations.
// Call the Add service operation.
double value1 = 100.00D;
double value2 = 15.99D;
double result = client.Add(value1, value2);
Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
// Call the Subtract service operation.
value1 = 145.00D;
value2 = 76.54D;
result = client.Subtract(value1, value2);
Console.WriteLine("Subtract({0},{1}) = {2}", value1, value2, result);
// Call the Multiply service operation.
value1 = 9.00D;
value2 = 81.25D;
result = client.Multiply(value1, value2);
Console.WriteLine("Multiply({0},{1}) = {2}", value1, value2, result);
// Call the Divide service operation.
value1 = 22.00D;
value2 = 7.00D;
result = client.Divide(value1, value2);
Console.WriteLine("Divide({0},{1}) = {2}", value1, value2, result);
//Step 3: Closing the client gracefully closes the connection and cleans up resources.
client.Close();
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
}
}
}
若要启动客户端,请在“开始”菜单中的“Microsoft Windows SDK”项下选择“CMD Shell”,从而启动 Windows SDK 控制台会话。 定位至 C:\Documents and Settings\<用户名>\Documents\Visual Studio 2008\Projects\Service\Client\obj\Debug 目录,键入 client,然后按Enter。 操作请求和响应将出现在客户端控制台窗口中,如下所示。
Add(100,15.99) = 115.99
Subtract(145,76.54) = 68.46
Multiply(9,81.25) = 731.25
Divide(22,7) = 3.14285714285714
Press <ENTER> to terminate client.
ASP.Net Core开发(踩坑)指南
ASP.NET与ASP.NET Core很类似,但它们之间存在一些细微区别以及ASP.NET Core中新增特性的使用方法,在此之前也写过一篇简单的对比文章ASP.NET MVC应用迁移到ASP.NET Core及其异同简介,但没有进行深入的分析和介绍,在真正使用ASP.NET Core进行开发时,如果忽略这些细节可能会出现奇怪的问题,特此将这些细节进行分享。
本文主要内容有:
- 无处不在的依赖注入(Dependency Injection, DI)
- Configuration&Options
- ASP.NET Core 请求管道建立
- ASP.NET Core Mvc
- Web API
- Signalr
- 小结
注:本文基于ASP.Net Core 2.1版本,.Net Core SDK版本需要2.1.401+。长篇预警( ╯□╰ )
无处不在的依赖注入
ASP.NET与ASP.NET Core之间最大区别之一就是内置了依赖注入机制,虽然ASP.NET中也有DI机制,但没有内置容器,一般都需要使用第三方的容器来提供服务,另外依赖注入的概念也不像ASP.NET Core中这样无处不在。
简单来说依赖注入的目的是为了让代码解耦以提高代码的可维护性,同时也要求代码设计符合依赖导致原则使得代码更加灵活,而其原理实际上就是在应用程序中添加一个对象容器,在应用初始化时将实际的服务“放”到容器中,然后当需要相应服务时从容器中获取,由容器来组装服务。
服务的注册
ASP.NET Core的Startup(注:Startup仅仅只是约定名称,实际使用是在Program类型中创建 WebHost时使用的),该类型中包含两个方法分别是ConfigureServices和Configure,其中ConfigureServices的主要作用就是用来将服务“放”置到容器中
代码来自:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1
替换默认的依赖注入容器
ASP.NET Core的默认容器仅提供了构造注入功能,如果需要使用属性注入等功能或者在迁移时原有应用依赖于其它容器,那么可以通过使用第三方容器实现。
将默认容器替换为其它容器仅需三步:
1. 将ConfigureServices方法的返回类型改为IServiceProvider。
2. 将ASP.NET Core中的服务注册到第三方容器中。
3. 使用第三方容器实现IServiceProvider接口并返回。
官方文档以Autofac为例,Autofac已经实现了ASP.NET Core服务注册到Autofac容器中,以及Autofac容器的IServiceProvider接口封装,仅需安装Autofac以及Autofac.Extensions.DependencyInjection包即可。
详情参考:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1#default-service-container-replacement
使用windsor或其它容器可以参考:
https://stackoverflow.com/questions/47672614/how-to-use-windsor-ioc-in-asp-net-core-2
将Controller注册为服务
虽然Controller在激活时是通过容器来获取Controller的依赖(即构造方法需要的参数),在代码运行的时候给人一种Controller是从容器中组装的错觉,但是实际上默认情况下Controller的组装过程不是直接由容器组装的,如果要让Controller从容器组装,那么在配置MVC服务时需要通过.AddControllerAsServices()方法将Controller注册到容器中:
注:一般情况下是否将Controller注册为服务对Controller的开发和代码的运行并没有很大区别,但是如果当容器变更为其它容器,并且使用了容器提供的如属性注入等功能时,如果没有将Controller注册为服务,那么相应的属性注入的过程也不会被触发,简单来说就是只有将Controller注册为服务,那么实例化Controller的工作才会由容器完成,才会触发或者使用到容器提供的其它特性。
服务的获取
前面介绍了服务的注册,现在来介绍一下在ASP.NET Core中有哪些方法可以获取服务:
1. Controller构造方法参数。
2. 通过Controller注入IServiceProvider类型,通过IServiceProvider来获取服务:
3. 在Action方法或者Mvc过滤器(过滤器的上下文参数中包含HttpContext)中通过HttpContext的RequestServices对象获取服务:
4. 在View上通过@inject注入服务:
5. 在Action方法中,通过FormServices特性注入:
注:一般来说尽可能显式的标明类型的依赖(即通过构造参数的方式声明当前类型所依赖的组件),上面的2和3两点分别都是通过服务提供器在方法内部来获取依赖,这样做依赖对于外界来说是不可知的,可能会对代码的可维护、可测试性等造成一定影响,这种模式被称为Service Locator模式,在开发过程中尽可能避免Service Locator模式的使用。
常用的服务
ASP.NET Core相对于ASP.NET来说取消了一些常用的静态类型,比如HttpContext、ConfigurationManager等,取而代之的是通过将类似的组件以服务的形式注册到容器中,使用时通过容易来获取相应的服务组件,这些常用的服务有:
1. IHostingEnvironment:包含了环境名称、应用名称以及当前应用程序所处的根目录及Web静态内容的根目录(默认wwwroot)。
2. IHttpContextAccessor:从名字可以看出,它用来访问当前请求的HttpContext。
3. IConfiguration:ASP.NET Core配置信息对象。
4. IServiceProvider: ASP.NET Core服务提供器。
5. DbContext: 这里的DbContext指的是EFCore的DbContext,在ASP.NET Core中,EFCore的DbContext也是在ConfigureServices方法中进行配置并添加到容器,使用时直接从容器中获取(但要注意的是对于分层结构的开发风格来说,DbContext不会直接被Controller依赖,而是被Controller中依赖的业务服务类型所以来,就是说编写Controller代码的时候不会直接与DbContext发生直接交互)。
Configuration&Options
在ASP.NET的开发中,通常某个变量需要从配置文件读取,一般都是在相应类型的构造方法中,通过静态类型ConfigurationManager的AppSettings方法来读取并初始化变量。虽然ASP.NET Core也可以在类型中注入IConfiguration实例来直接读取配置文件,但该方法由于Options模式的出现已经不再建议使用,使用组件通过依赖相应的组件Options可以做到关注点分离,提高程序的灵活性、可拓展性,Options使用方法见文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.1
ASP.NET Core 请求管道建立
ASP.NET由于是基于IIS请求管道的,ASP.NET应用程序仅仅是管道中的一个处理环节,管道中还包含如身份验证、静态文件处理等环节,但ASP.NET Core不一样,它脱离了IIS处理管道,所以整个管道的建立均需要靠程序自身完成,而ASP.NET Core建立管道的代码就是Startup类型的Configure方法,该方法通过IApplicationBuilder实例来添加不同功能的中间件,通过中间件的串联形成处理管道,下图是ASP.NET Mvc模板生成的管道代码:
图片来自:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-2.1#the-configure-method
该管道主要包含了错误处理(开发环境显示异常信息,其它环境跳转错误页面)中间件、静态文件处理中间件以及Mvc中间件。
更多中间件可参考文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/index?view=aspnetcore-2.1
ASP.NET Core Mvc
ASP.NET Core Mvc与ASP.NET Mvc相比整体上区别不大,但仍然有很多细节上的变化,下面就开始一一介绍:
路由
路由的作用是将请求根据Url映射到“对应”的处理器上,在Mvc中请求的终点就是Controller的Action方法,而这里所谓的“对应”指的是Url与路由模板的匹配,ASP.NET Core Mvc通过以下的方式添加路由模板:
上图中的路由模板是最常用的路由模板,使用花括号内的内容为路由参数及其默认值,Url中通过路由参数控制器名称、活动方法名称来匹配到相应控制器的活动方法。
在注册路由时可以为相应路由添加默认值、路由参数约束以及对应路由的相关附加数据(datatokens):
路由的功能除了处理请求匹配外,还具有链接生成的功能,特别是Mvc程序的View中使用IUrlHelper或TagHelper来生成页面的超链接:
其生成原理是通过链接参数(如上图所示的Controller和Action)去路由表中匹配,然后使用匹配结果中的第一个路由(可能会匹配到多个路由对象,具体内容在后续Area章节介绍)来生成链接。
更多路由信息及路由模板定义参考文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-2.1
控制器
ASP.NET Core Mvc的Controller一般继承Controller类型实现,基类Controller中包含了Mvc中常用的返回方法(如Json以及View等)以及用于数据存储的ViewBag、ViewData、TempData。
Area
Area是Mvc应用中用来进行功能拆分或分组的一种方式,Area一般有自己的命名空间和目录结构,一般Area的默认目录结构如下:
ASP.NET Core Mvc和ASP.NET Mvc中的概念和用法基本上是一致的,但也存在一些区别:
1. Area下面的Controller需要使用Area特性标明当前Controller属于哪一个Area:
注:Area的目录结构不是必须的,只需要通过特性标记的Controller都会被正确识别,但目录结构的改变会导致无法找到View,关于View的查找路径会在后续介绍。
2. Area的路由注册也是在UseMvc方法中完成:
注:携带Area的路由模板需要放在前面,否则在生成通过IUrlHelper或TagHelper生成链接时,由于Controller以及action会匹配到没有area的模板并使用该模板生成链接,导致area参数被忽略,而生成类似:/controller/action?area=area的结果(在生成Url时,ASP.NET Core会将多余的路由参数放置到查询字符串中)
View
View是基于Razor的HTML模板,Razor的详细语法参考文档:
https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-2.1
ASP.NET Core Mvc的View与ASP.NET Mvc中的使用方法基本一致,主要区别如下:
1. 引入了TagHelper,使用TagHelper可以让View的代码更接近Html。更多TagHelper信息参考文档:https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-2.1
2. Controller将参数传输到View的方法添加了ViewData特性,使用方法如下:
View中访问被ViewData标记的方式:
更多详情参考文档:https://docs.microsoft.com/en-us/aspnet/core/mvc/views/overview?view=aspnetcore-2.1#passing-data-to-views
3. 新增View组件:https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-2.1
配置View的查找路径:
ASP.NET Core可以在ConfigureServices方法中对RazorViewEngineOptions进行配置,如下图所示,在默认查找位置基础上添加了View以及AreaView的查找路径:
模型绑定
模型绑定指的是ASP.NET Core Mvc将请求携带的数据绑定到Action参数的过程,ASP.NET Core Mvc的模型绑定数据源默认使用Form Values、Route Values以及Query Strings,所有值都以Name-Value的形式存在,模型绑定时主要通过参数名称、参数名称.属性名称、参数名称[索引]等方式与数据源的Name进行匹配。
除了默认的数据源之外还可以从Http请求Header、Http请求Body甚至从依赖注入容器中获取数据,要从这些数据源中获取数据需要在相应参数上使用[FromHeader]、[FromBody]、[FromServices]特性。
如果需要获取的数据在不同数据源中都存在时(Name存在于多个数据源中),还可以通过特性指明从哪一个数据源中获取,如[FromForm]、[FromQuery]及[FromRoute]。
需要注意的是[FromBody]默认只支持Json格式的内容,如果需要支持其它格式,如XML需要添加相应的格式化器,添加方法如下图所示:
更多模型绑定及验证内容请参考文档:https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-2.1
https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-2.1
其中模型验证的使用方式与ASP.NET Mvc一致,仍然是通过相应的验证特性对模型或模型属性进行标记。
Action的返回值与Json序列化
说完Action方法参数的绑定,再来看一下Action方法的返回类型,在ASP.NET Mvc中Controller提供了返回页面内容的View方法以及返回Json内容的Json方法(当然还有文件、重定向、404等等其它内容返回方法,详见Controller与ControllerBase类型)。
这里有一个需要注意的地方是当使用Json方法返回一个对象实例时,默认使用首字母小写的驼峰命名方式序列化实例的属性名称,如下图所示:
访问结果:
要使用大写驼峰形式命名需要在配置Mvc服务时添加以下代码来修改Json默认的序列化配置:
注:同样的问题也存在于WebAPI的Ok方法以及Signalr的Json格式协议。
静态资源
由于ASP.NET Core已经不再使用IIS请求管道,所以对于静态资源的访问来说需要在请求管道中添加相应的处理中间件来完成:
默认的无参UseStaticFiles方法将wwwroot目录作为静态资源存放目录,如果要添加其它静态内容目录可以再次使用UseStaticFiles方法,并通过StaticFileOptions对目录的访问路径以及实际路径进行配置:
注:由于ASP.NET Core可以在Linux下运行,所以对于Linux来说路径是大小写敏感的,另外由于Windows和Linux类系统的路径分隔符也不一致,所以为了保证路径的统一,可以使用Path.Combine方法,该方法会根据操作系统的不同对路径进行不同的处理。
另外对于css及js资源文件的打包、压缩功能,最新版本(ASP.NET Core 2.1)的应用模板以及不会自动添加相关功能,需要在拓展工具中添加Bunlder& Minifier拓展:
然后通过右键js等资源文件来创建bundleconfig.json文件:
WebAPI
API控制器的创建
ASP.NET Core将Mvc和WebAPI进行了合并,它们的实现都直接或间接继承了ControllerBase类型,只不过Mvc的基类Controller在ControllerBase的基础上添加了一些用于处理View的功能。
用ASP.NET Core开发WebAPI时,Controller类型直接继承ControllerBase。然后这个API的Controller就具有了基类的特性,返回一个结果仅需要使用Ok方法即可,如下图所示:
然后在路由表中添加路由:
即可通过/api/default/index访问到这个API:
但对于REST风格的API来说,它需要通过ApiController特性对Controller类型进行标记,并且通过Route特性来设置路由:
然后就可以通过HTTP谓词来访问API:
但要注意的是在ASP.NET Core中实现的REST风格的Controller,它不会再根据action方法的名称来匹配谓词,所以存在多个方法时会,那怕对方法进行了命名,但仍然会出现以下错误:
为了解决这个问题,需要通过添加谓词特性解决:
模型绑定
WebAPI中的模型绑定与MVC存在一些区别,首先当使用ApiController标记Controller类型时,如果模型绑定验证未通过,会直接返回400错误,不会执行Action方法(免去了使用!ModelState.IsValid进行判断):
执行结果:
其次使用ApiController标记的Controller在执行模型绑定时会使用默认的推断规则,该规则分别从Body、Form、Header、Query、Route、Services(它们分别对应FromBody、FromForm、FromHeader、FromQuery、FromRoute、FromServices特性)中推断获取数据并绑定,为什么说推断?
因为有一些特殊的规则:
1. FromBody用于复杂类型推断,如果不是复杂类型(如int、string等)以及特殊的内置类型(IFormCollection文档例子),则不会从Body中获取数据,除非通过[FromBody]特性指明,例子如下:
请求结果:
当使用[FormBody]指明参数数据源后可以正常访问:
注:当请求参数为简单类型时,请求体内容类型需要为application/json,内容不能为Json字符串,使用参数值作为内容即可(上图id没有提供的异常并不是因为Json格式问题,而是没有指明从body中获取数据导致的)。
2. 只能存在一个参数从Body中获取数据,如果出现多个参数时,只能保证一个参数从Body中获取数据,其它参数需要指明获取数据的位置:
该API的调用方式如下:
3. FromForm默认只推断文件(IFormFile)及文件集合类型(IFormFileCollection),其余类型默认均不会从Form中获取。
4. 使用FromForm特性时会推断multipart/form-data请求内容类型。
以上推断行为可以通过如下配置禁用:
更多信息参考文档:https://docs.microsoft.com/zh-cn/aspnet/core/web-api/?view=aspnetcore-2.1
SignalR
SignalR是用于客户端服务器实时通信的工具库,从ASP.NET中就具有该功能,ASP.NET Core中的SignalR概念与用法与原来基本一致,但也存在一些区别:
1. 支持更多的客户端,.Net客户端、Java客户端、Js客户端以及非官方的C++客户端、Swift客户端。
2. 当链接SignalR并通过身份验证后,SignalR会保存当前用户链接SignalR的ID以及通过验证后的用户名,可以通过用户名向用户客户端推送消息。
3. 在应用程序中可以通过IHubContext<HubType>方式,对SignalR上下文进行注入,并且可以直接通过该上下文推送数据给已经链接的客户端,IHubContext<HubType>实际上是GlobalHost.ConnectionManager.GetHubContext<HubType>()的替代方式。
4. ASP.NET Core中通过app.UserSignalR以及route参数来映射一个Hub,每一个Hub拥有独立的上下文,因此如果要使用IHubContext<HubType>来向客户端推送信息,那么必须准确注明Hub的类型,如下图代码应该使用IHubContext<ChatHub>,不能使用除ChatHub以外的类型(基类也不行)。
5. SignalR默认使用Json协议传输数据,默认情况下使用首字母小写的驼峰命名方式序列化对象,要更改该默认行为需要通过一下代码,替换默认的序列化行为:
6. ASP.NET Core的客户端代码(特指Js客户端)有变更,需要对应版本使用。
关于更多SignalR内容请参考文档:https://docs.microsoft.com/en-us/aspnet/core/signalr/introduction?view=aspnetcore-2.1
小结
本文主要介绍了ASP.NET Core中Mvc、WebAPI以及SignalR开发时与原来ASP.NET中的一些细小区别和新特性,整体来说ASP.NET Core与ASP.NET从使用方式上基本上是一致的,这也使得从ASP.NET迁移到ASP.NET Core变得更加容易,但可能因为这些细小的问题往往会向代码中埋入一些坑,所以特别编写了本文来解释这些问题。
总的来说ASP.NET Core的文档相当齐全,本文中大部分内容实际都是文档中提到的,所以建议大家在使用ASP.NET Core开发时,首先第一步就是熟读文档,避免遗漏细节。希望本篇文章对大家有帮助(*^_^*)
参考:
https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-2.1
本文链接:https://www.cnblogs.com/selimsong/p/10047321.html
ASP.Net Core Razor+AdminLTE 小试牛刀
AdminLTE
一个基于 bootstrap 的轻量级后台模板,这个前端界面个人感觉很清爽,对于一个大后端的我来说,可以减少较多的时间去承担前端的工作但又必须去独立去完成一个后台系统开发的任务,并且,文档还算比较齐全,对着demo可以完成一个基本的前端框架搭建了。大家如有更为好看的又方便后端上手的前端框架,也可以在留言区分享一下呗。
AdminLTE 文档
在线中文Demo:http://adminlte.la998.com/
在线中文文档:http://adminlte.la998.com/documentation/index.html
Github:https://github.com/almasaeed2010/AdminLTE/releases
AdminLTE 布局
AdminLTE依赖于两个主要框架:JQ和Bootstrap,其他插件可以按需增加。
从文档可以知道,使用AdminLTE主要有四个部分:
- 包装
.wrapper
。包裹整个网站的div。 - 主标题
.main-header
。包含徽标和导航栏。 - 边栏
.sidebar-wrapper
。包含用户面板和侧边栏菜单。 - 内容
.content-wrapper
。包含页眉和内容。
在文档中,可以找到下载地址,本文示例是使用最新的版本V2.4.5。
Asp.Net Core Razor
新建项目Asp.net Core Web应用程序,默认就是Razor Pages,然后添加相应的模块,如图:本文使用的SDK版本为:dotNet Core 2.1。
First
在Asp.Net Core项目中,引用AdminLTE,在wwwroot仅添加如图三个文件夹即可:
- bower_components 基本组件。
- dist adminlte的主要文件。
- plugins 其他插件。
Second
在_Layout.cshtml文件中添加引入相关文件:
<!-- Tell the browser to be responsive to screen width --> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <!-- Bootstrap 3.3.7 --> <link rel="stylesheet" href="~/adminlte/bower_components/bootstrap/dist/css/bootstrap.min.css"> <!-- Font Awesome --> <link rel="stylesheet" href="~/adminlte/bower_components/font-awesome/css/font-awesome.min.css"> <!-- Ionicons --> <link href="~/adminlte/bower_components/Ionicons/css/ionicons.min.css" rel="stylesheet" /> <!-- Theme style --> <link rel="stylesheet" href="~/adminlte/dist/css/AdminLTE.min.css"> <!-- AdminLTE Skins. Choose a skin from the css/skins folder instead of downloading all of them to reduce the load. --> <link rel="stylesheet" href="~/adminlte/dist/css/skins/_all-skins.min.css"> <!-- Pace style --> <link href="~/adminlte/plugins/pace/pace.min.css" rel="stylesheet" /> <link href="~/css/common.css" rel="stylesheet" /> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> <!-- Google Font --> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">
在body中,添加js:
<!-- jQuery 3 --> <script src="~/adminlte/bower_components/jquery/dist/jquery.min.js"></script> <!-- jQuery UI 1.11.4 --> <script src="~/adminlte/bower_components/jquery-ui/jquery-ui.min.js"></script> <!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip --> <script> $.widget.bridge('uibutton', $.ui.button); </script> <!-- Bootstrap 3.3.7 --> <script src="~/adminlte/bower_components/bootstrap/dist/js/bootstrap.min.js"></script> <!-- Slimscroll --> <script src="~/adminlte/bower_components/jquery-slimscroll/jquery.slimscroll.min.js"></script> <!-- FastClick --> <script src="~/adminlte/bower_components/fastclick/lib/fastclick.js"></script> <!-- AdminLTE App --> <script src="~/adminlte/dist/js/adminlte.min.js"></script> <!-- Skin --> <script type="text/javascript" src="~/adminlte/dist/js/sidebarskins.js" charset="gbk"></script>
sidebarskins.js是本人汉化的侧边栏皮肤
坑1:一般情况,发现某些功能运行不起来的都是引用不正确导致的,这个要耐心对照好Demo来检查,或者直接用Demo来修改吧。
Third
开始使用AdminLTE,这里直接贴图吧,图上有注释和代码折叠比较直观点,重要的地方在放代码。
最后就可以运行项目来预览一下效果了:
都使用到bootstrap,必须得看看移动端的效果,还不错吧。
坑2:需要注意的是,点击这个小图标可以实现左侧边栏收缩展开的功能,当只有侧边栏可以正常收缩展开但Logo无动于衷的时候,你可能是少了【sidebar-mini】样式和【logo-mini】logo小图的引用
添加一个登陆
登录界面写得比较简约,我比较喜欢这种风格。前端写得不多,所以还得前端的女票指导一二,不然就是后端的设计的界面了,你懂的。
在Pages文件夹中,添加一个Razor界面,并撸好界面代码:
@page @model AdminLTE.Net.Web.Pages.LoginModel @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>登录 - AdminLTE.Net.Web</title> <meta name="developer" content="EminemJK"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css" rel="stylesheet" /> <link href="~/adminlte/dist/css/AdminLTE.min.css" rel="stylesheet" /> <link href="~/css/login.css" rel="stylesheet" /> </head> <body> <div> <div class="row"> <div class="loginHeader"> <img class="logo-img" src="~/images/banana_logo.ico" /> <h1 class="logo-name">Banana</h1> <div class="clearfix"></div> </div> </div> <div class="row login-bg"> <div class="loginInBox"> @if (!string.IsNullOrEmpty(Model.Message)) { <p class="login-box-msg" style="color:red">@Model.Message</p> } else { <p class="login-box-msg">Sign in to start your session</p> } <form method="post"> <div class="form-group has-feedback"> <input type="text" class="form-control" asp-for="Login.UserName"> <span class="glyphicon glyphicon-user form-control-feedback"></span> </div> <div class="form-group has-feedback"> <input type="password" class="form-control" asp-for="Login.Password"> <span class="glyphicon glyphicon-lock form-control-feedback"></span> </div> <button type="submit" class="btn btn-primary btn-block btn-flat login-btn">Sign In</button> </form> </div> </div> </div> <footer class="footer"> <strong>Copyright © 2018 <a href="http://www.cnblogs.com/EminemJK/">EminemJK</a>.</strong> All rights reserved. </footer> </body> </html>
在Startup中引入Authentication身份验证:
services.AddAuthentication(CookieService.AuthenticationScheme) .AddCookie(CookieService.AuthenticationScheme, o => { o.LoginPath = new PathString("/Login"); });
Configure方法内调用
app.UseAuthentication();
在Login.cshtml.cs中增加一个OnPostAsync的方法:
[HttpPost] public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { Message = ModelState.Root.Errors[0].ErrorMessage; } else { var user = userService.Login(Login.UserName, Login.Password); if (user != null) { VUserModel model = new VUserModel() { Id = user.Id, UserName = user.UserName, Time = DateTime.Now }; var identity = new ClaimsIdentity(CookieService.AuthenticationScheme); identity.AddClaim(new Claim(ClaimTypes.Sid, CookieService.GetDesEncrypt(model))); await HttpContext.SignInAsync(CookieService.AuthenticationScheme, new ClaimsPrincipal(identity), new AuthenticationProperties() { //记住我 IsPersistent = true, //过期时间 ExpiresUtc = DateTimeOffset.Now.Add(TimeSpan.FromMinutes(30)) }); return RedirectToPage("./Index"); } Message = "登录失败,用户名密码不正确。"; } return Page(); }
userService和CookieService都是在业务层定义的,gayhub会在文章末尾。
在.Net Core Razor中,xx.cshtml.cs中默认触发的是Get和Post方法,
- OnGet
- OnPost
- OnGetAsync
- OnPostAsync
如果是需要自定义的,举个栗子,定义为:OnPostLoginAsync,然后在Form表单提交的【按钮】增加asp-page-handler="Login",详细的推荐大家阅读这篇文章:ASP.NET Core - Razor页面之Handlers处理方法。
接着,然后再Index和需要身份验证的地方都加上Authorize特性即可:
namespace AdminLTE.Net.Web.Pages { [Authorize(AuthenticationSchemes = CookieService.AuthenticationScheme)] public class IndexModel : BasePageModel { public void OnGet() { } } }
踩坑
一、Ajax Post请求,HttpCore 400
function uploadfile() { var file = $("#input-userimg")[0].files[0]; var data = new FormData(); data.append('file', file); $.ajax({ url: "/Account/UserList?handler=Upload", type: 'POST', data: data, contentType: false, processData: false, success: function (returndata) { $("#user-img").attr('src', returndata.path); }, error: function (a, b, c) { alert('上传失败') } }); };
折腾许久,原因是Razor被设计为可以自动防止跨站请求伪造(CSRF / XSRF)攻击。你不必编写任何其他代码。Razor页面中自动包含防伪令牌生成和验证。这里请求失败,是因为POST没有提交AntiForgeryToken。
解决方法:
1.增加"XSRF-TOKEN"标识到框架中
//增加了"XSRF-TOKEN"标识,值为表单自动生成的防伪标记 services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
2.页面*.cshtml头部加上
@Html.AntiForgeryToken()
3.ajax引入
function uploadfile() { var file = $("#input-userimg")[0].files[0]; var data = new FormData(); data.append('file', file); $.ajax({ url: "/Account/UserList?handler=Upload", type: 'POST', data: data, contentType: false, processData: false, beforeSend: function (xhr) { xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val()); }, success: function (returndata) { $("#user-img").attr('src', returndata.path); }, error: function (a, b, c) { alert('上传失败') } }); };
然后既可以正常访问Handler
二、DataTables参数实例加说明
var table = $('#userListTable').DataTable({ "processing": true, "serverSide": true, "ajax": function (data, callback, settings) { //data的参数请参考: https://segmentfault.com/a/1190000004478726 var param = {}; param.draw = data.draw; param.pageNum = (data.start / data.length) + 1; param.pageSize = data.length; param.sex = $('#select-sex option:selected').val(); param.phone = $('#input-phone').val(); param.name = $('#input-name').val(); $.ajax({ type: "GET", data: param, url: "/Account/UserList?handler=UserPage", dataType: "json", success: function (data) { //成功后回调自动渲染 callback(data); } }); }, 'columns': [ { 'data': 'id' }, { 'data': 'name' }, { 'data': 'userName' }, { 'data': 'sexString' }, { 'data': 'phone' }, { 'data': 'createTime' }, { 'data': 'enableString', 'render': function (data, type, row) { if (row.enable == 1) return '<span style="color:#19be6b" >' + row.enableString + '</span>'; else return '<span style="color:#ed3f14" >' + row.enableString + '</span>'; } }, { 'data': null, 'render': function (data, type, row) { return '<a id="btn-edit" class="btn btn-success btn-xs" title="编辑" onClick=btn_edit(' + row.id + ')><i class="fa fa-edit"></i>编辑</a> ' + '<a id="btn-edit" class="btn btn-danger btn-xs" title="删除" onClick=btn_edit(' + row.id + ')><i class="fa fa-trash " title="删除" style="cursor:pointer"></i>删除</a>'; } }, ], //datatable设置参数 http://www.datatables.club/reference/option/ 'paging': true, //启用分页 'lengthChange': true, //设置每页数量 'searching': false, 'ordering': false, 'info': true, 'autoWidth': false, //设置中文 'language': { "sProcessing": "玩命加载中...", "sLengthMenu": "每页显示显示 _MENU_", "sZeroRecords": "没有匹配结果", "sInfo": "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项", "sInfoEmpty": "显示第 0 至 0 项结果,共 0 项", "sInfoFiltered": "(由 _MAX_ 项结果过滤)", "sInfoPostFix": "", "sSearch": "搜索:", "sUrl": "", "sEmptyTable": "表中数据为空", "sLoadingRecords": "玩命加载中...", "sInfoThousands": ",", "oPaginate": { "sFirst": "首页", "sPrevious": "上页", "sNext": "下页", "sLast": "末页" }, "oAria": { "sSortAscending": ": 以升序排列此列", "sSortDescending": ": 以降序排列此列" } } });
Last
附上这些天来的成果,发现,我并不适合写前端,太丑了,哈哈。
最后,Show me the code。
Github:https://github.com/EminemJK/AdminLTE.Net.Web
Banana
Banana Github:https://github.com/EminemJK/Banana
Demo中会使用到这两个个人封装的组件:
Banana.Uow是基于Dapper封装的工作单元和仓储;
Banana.Utility是常用的工具类,有Redis,加解密,拼音等等;
欢迎大家在Issues中提出意见,大家共同进步。
webservice创建、部署和调用
webservice 可以用于分布式应用程序之间的交互,和不同程序之间的交互。
下面通过一个简单的例子来创建一个webservice,用的是vs2010开发工具
首先创建一个web应用程序
接着我们添加一个web服务
一开始WebService1.asmx这个文件中有一个HelloWord方法
这样我们创建webserver服务就算完成了,接着我们创建一个加减乘除的方法
这样,我们的webservice的方法就算编写完成了,接着我们将其发布到我们iis服务器上
接着我们创建一个webform程序来调用我们的webservice
首先我们需要添加有个服务引用
接着我们就可以编写代码测试我们webserver
这样我们webservice创建、部署和调用就算完成了。
.net接收post请求并把数据转为字典格式
public SortedDictionary<string, string> GetRequestPost()
{
int i = 0;
SortedDictionary<string, string> sArray = new SortedDictionary<string, string>();
NameValueCollection coll = Request.Form;
String[] requestItem = coll.AllKeys;
for (i = 0; i < requestItem.Length; i++)
{
sArray.Add(requestItem[i], Request.Form[requestItem[i]]);
}
coll.Clear();
return sArray;
}
调用:
SortedDictionary<string, string> sPara = GetRequestPost();
.net接收post请求,并转为字符串
Stream s = Request.InputStream;
int count = 0;
byte[] buffer = new byte[1024];
StringBuilder reqXml = new StringBuilder();
while ((count = s.Read(buffer, 0, 1024)) > 0)
{
reqXml.Append(Encoding.UTF8.GetString(buffer, 0, count));
}
s.Flush();
s.Close();
s.Dispose();