本文不定时更新中……
具备 RESTful 相关知识会更有利于学习 ASP.NET Web API:RESTful API 学习笔记
ASP.NET Web API 官网:https://www.asp.net/web-api
服务 URI Pattern
Action | Http verb | URI | 说明 |
Get contact list | GET | /api/contacts | 列表 |
Get filtered contacts | GET | /api/contacts?$top=2 | 筛选列表 |
Get contact by ID | GET | /api/contacts/id | 获取一项 |
Create new contact | POST | /api/contacts | 增加一项 |
Update a contact | PUT | /api/contacts/id | 修改一项 |
Delete a contact | DELETE | /api/contacts/id | 删除一项 |
返回类型 | Web API 创建响应的方式 |
---|---|
void | 返回空 204 (无内容) |
HttpResponseMessage | 转换为直接 HTTP 响应消息。 |
IHttpActionResult | 调用 ExecuteAsync 来创建 HttpResponseMessage,然后将转换为 HTTP 响应消息。 |
其他类型 | 将序列化的返回值写入到响应正文中;返回 200 (正常)。 |
HttpResponseMessage
可提供大量控制的响应消息。 例如,以下控制器操作设置的缓存控制标头。
public class ValuesController : ApiController
{
public HttpResponseMessage Get()
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, "value");
response.Content = new StringContent("hello", Encoding.Unicode);
response.Headers.CacheControl = new CacheControlHeaderValue()
{
MaxAge = TimeSpan.FromMinutes(20)
};
return response;
}
}
IHttpActionResult
public IHttpActionResult Get (int id)
{
Product product = _repository.Get (id);
if (product == null)
{
return NotFound(); // Returns a NotFoundResult
}
return Ok(product); // Returns an OkNegotiatedContentResult
}
其他的返回类型
响应状态代码为 200 (正常)。此方法的缺点是,不能直接返回错误代码,如 404。 但是,您可以触发 HttpResponseException 的错误代码。
完善 Help 页
一般都是通过元数据注释(Metadata Annotations)来实现的描述的。
显示接口和属性的描述:
打开 HelpPage 区域的 App_Start 目录下的 HelpPageConfig.cs 文件,在 Register 方法的首行取消注释行:
config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml")));
在项目右键属性“生成”页,勾选“XML 文档文件”,并填写与上述一致的路径(App_Data/XmlDocument.xml)。
给接口加上 ResponseType 显示响应描述和示例:[ResponseType(typeof(TEntity))]
在实体模型的属性上加入 RequiredAttribute 特性可提供请求或响应中的实体的属性描述,如:[Required]
推荐使用:Swashbuckle
关于格式
在 WebApiConfig.Register() 中加入以下代码以禁止以 XML 格式输出(请按需设置):
GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
Json 序列化去掉 k__BackingField
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver { IgnoreSerializableAttribute = true };
如果属性值为 null 则不序列化该属性
config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };

前言:
本文适合 6.x 环境,8.x 请结合参考:如何升级 ASP.NET 项目 MySql.Data 和 Connector/NET 至 8.0.x
使用 ASP.NET Core 的同学请移步:ASP.NET Core 使用 DBFirst 模式连接 MySQL 数据库
安装
开发环境
软件安装:
MySQL for Visual Studio - 用于在 Visual Studio 中连接 MySQL 数据库
Connector/NET - ADO.NET 托管提供程序
Nuget 中搜索安装 MySql.Data 和 MySql.Data.Entity
版本选择:
MySQL for Visual Studio 最新版
Connector/NET 版本必须与 MySql.Data 和 MySql.Data.Entity 版本相同,否则会出现 MySql.Data.MySqlClient.MySqlConnection 无法强制转换的错误
生产环境
软件安装:
MySQL Server - 数据库
Connector/NET - ADO.NET 托管提供程序
版本选择:
MySQL Server 最新版
Connector/NET 应与项目中使用的 MySql.Data 和 MySql.Data.Entity 版本一致。(某些项目在确保 Web.config 的 MySql.Data 版本号与 MySql.Data 和 MySql.Data.Entity 版本一致的前提下,允许服务端的 Connector/NET 小版本略低于 MySql.Data 和 MySql.Data.Entity 版本,某些项目允许安装比 MySql.Data 和 MySql.Data.Entity 版本略高的 Connector/NET 版本,为确保不出问题,严格要求版本全部一致)
升级
MySQL for Visual Studio:直接更新至最新版
MySQL Server:直接更新至最新版
MySql.Data 和 MySql.Data.Entity:升级至两者共有的新版本,更改 Web.config 中 MySql.Data 的版本号,并更新开发环境和生产环境的 Connector/NET 至相同版本(详细步骤见下方说明)
Connector/NET:视是否有相同版本的 MySql.Data 和 MySql.Data.Entity,若有,同时升级。(详细步骤见下方说明)
关于 Connector/NET 和 MySql.Data 及 MySql.Data.Entity 版本一致的说明:
某些项目在确保 Web.config 的 MySql.Data 版本号与 MySql.Data 和 MySql.Data.Entity 版本一致的前提下,允许服务端的 Connector/NET 小版本略低于 MySql.Data 和 MySql.Data.Entity 版本;
某些项目允许安装比 MySql.Data 和 MySql.Data.Entity 版本略高的 Connector/NET 版本;
为确保不出问题,严格要求版本全部一致。
但服务器上同时运行多个项目,一旦出现问题涉及面就很广,为了尽量减少影响,列出下面的升级步骤:
升级开发环境的 Connector/NET
升级各项目的 MySql.Data 和 MySql.Data.Entity
更改 Web.config 中关于 MySql.Data 的版本号(若未自动更新,项目中搜索原版本号即可)
测试运行各项目
同时发布各项目,同时升级服务器上的 Connector/NET(升级 Connector/NET 仅需几十秒)
部分项目需要删除并重新上传 bin 目录才能生效
尽管如此,发布成功以后还是会有各种各样的问题导致无法正常打开,须要有针对性地去解决,所以升级还是要趁夜深人静的时候进行

“/”应用程序中的服务器错误。
无法为具有固定名称“MySql.Data.MySqlClient”的 ADO.NET 提供程序加载在应用程序配置文件中注册的实体框架提供程序类型“MySql.Data.MySqlClient.MySqlProviderServices, MySql.Data.Entity.EF6, Version=6.10.7.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d”。请确保使用限定程序集的名称且该程序集对运行的应用程序可用。有关详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=260882。
打开 nuget,卸载并重新安装 MySql.Data 和 MySql.Data.Entity
如果没有效果,打开 web.config,检查是否多次定义了 MySql.Data.MySqlClient

“/”应用程序中的服务器错误。
The host xxx does not support SSL connections.
说明: 执行当前 Web 请求期间,出现未经处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。
异常详细信息: MySql.Data.MySqlClient.MySqlException: The host xxx does not support SSL connections.
在 Web.config 的 connectionStrings 节点找到相关数据库连接,在 connectionString 中增加属性值:SslMode=none

如果 .NET Core 项目发布到服务器上报以下错误:
HTTP Error 502.5 - Process Failure
Common causes of this issue:
The application process failed to start
The application process started but then stopped
The application process started but failed to listen on the configured port
Troubleshooting steps:
Check the system event log for error messages
Enable logging the application process' stdout messages
Attach a debugger to the application process and inspect
For more information visit: https://go.microsoft.com/fwlink/?LinkID=808681
在服务器上安装最新的 .NET Core Runtime 即可(Windows Server 选择 Hosting Bundle Installer)。

在网站开发过程中遇到上传图片等文件的功能,需要在服务器上设置该目录可写入,并且必须防止被上传 .php 等可执行的脚本文件。
根据文件所有者的不同,我们分两种情况讨论:
一、程序上传的用户与执行 PHP 的用户不同(例如程序代码通过 SSH 上传(root 用户),而 PHP 以 www 身份运行)
由于 Linux 上的文件默认权限是 644,目录默认权限是 755,所以在这种情况下,PHP 不能修改 root 上传的程序文件,也不能将客户端上传的图片文件保存到服务器上。
例如我们将客户端上传的文件指定到 /upload/ 目录,那么,
第 1 步,赋予它写入权限:
chmod 777 upload
递归所有子目录请加 -R 参数,但会将文件也更改为 777,赋予了执行权限,这是不建议的。文件若需写入权限请设为 666。
第 2 步,设置该目录下不允许执行 PHP:
我们无法保证客户端上传完全符合我们要求的文件,那么必须禁止任何文件的执行权限。(此处指 PHP 的执行权限,注意与 sh 执行权限的区别,后者由 chmod 命令修改)
nginx 的 .conf 文件配置示例:
location ~ /upload/.*\.(php|php5)?$
{
deny all;
}
Apache 的 .htaccess 文件配置示例:
RewriteRule upload/(.*).(php)$ – [F]
特别注意:上面的代码必须加在 PHP 引用配置的上方才有效(如 nginx 的 PHP 引用配置 include enable-php-**.conf;)
默认 Linux 系统是大小写敏感的,所以规则中的正则表达式无须忽略大小写(但必须与实际的目录或文件名大小写一致),因为 MIME 类型中设置的是小写的 .php,那么类似大写的 .PHP 文件是不被 php-fpm 解释的,结果是被当作普通文本返回到客户端。
提问:有些目录需要设置为可写入,但里面还有正常的 php 文件,禁止执行会有问题吗?(例如 Discuz! 的 config 目录,ThinkPHP 的 Runtime 目录)
解答:以 Discuz! 为例,config 下面虽然有 config_*.php 等配置文件,但这些文件并非直接供客户端浏览,而是被其它 .php 文件引用(include 方式、IO 方式等),当 config 目录禁止执行 PHP 后并不会对它们造成影响。
我们在开发程序的过程中应避免在允许写入的目录下放置直接供客户端浏览的 .php 文件。
二、程序上传的用户与执行 PHP 的用户相同(例如程序代码通过 FTP 上传,且和 PHP 一样,都使用 www 用户)
常见于虚拟空间。这种情况下,通过 PHP 上传的文件可以保存到该网站下面的任何目录下(甚至是网站目录之外,即传说中的跨站),原因大家都懂,默认 755 中第一个是“7”,即所有者拥有写入权限。
因此,除了做到上述设置,我们必须严格控制客户端上传文件的保存路径,做到以下几点:
必须更改客户端上传的文件名,而非直接使用原文件名;
不得从客户端文件名获取扩展名,或者只控制允许的后缀名。
最可靠的文件类型判别方式是分析文件签名,参考:文件签名表,以防篡改后缀名欺骗。
举个早期的栗子:上传文件名为 abc.asp;.jpg,未改文件名保存到服务器,浏览器直接访问该文件,IIS 6 当作 abc.asp 来解析。

Session 有多种存储模式,默认是 InProc
,顾名思义是“在进程内”,即在 IIS 中,随网站重启而重启,网站发布后(或改动 .dll 文件)会导致应用重新运行,Session 清空。
Session 可以使用 StateServer
来保存,需要服务器开启 ASP.NET State Service 服务,此种方式要求保存在 Session 的信息必须序列化,然后从 Session 中获取的时候也要反序列化,这就导致性能有略微的损失。
Session 还可以配置为 SQLServer
,将 Session 存储在数据库中,同样需要序列化。
当配置为 Custom
时,可以在使用 Memcached、Redis 等第三方缓存技术来实现 Session 的管理,本文不作讨论。
Off
模式即关闭 Session,这种方式如果要实现用户登录功能,就必须依赖 Cookie 等来实现。
下面针对常见的三种模式进行比较:
InProc | StateServer | SQLServer | |
存储位置 | 应用进程内 | ASP.NET State Service 服务 | SQL Server 数据库 |
远程存储 | 不支持 | 支持 | 支持 |
存储格式 | 任意 | 序列化的 | 序列化的 |
可能导致重启(丢失)的情况 | 配置文件中 processModel 标签的 memoryLimit 属性; Global.asax 或者 Web.config 文件被更改; Bin 文件夹中的 Web 程序(DLL)被修改; 杀毒软件扫描了一些 .config 文件; 系统资源紧张进行资源回收导致 IIS 进程崩溃或重启等。 | ASP.NET State Service 服务停止; | - |
Session_End() 事件 | 有 | 无 | 无 |
部署难度 | 无 | 简单 | 稍复杂 |
Web.config 配置示例 (<system.web> 节点内) | <sessionState mode="InProc" /> | <sessionState mode="StateServer" /> | <sessionState mode="SQLServer" sqlConnectionString ="data source=x.x.x.x; user id=[user]; password=[pwd]" /> |
因此,如果你仅仅为了标题所述的“每次发布后丢失 Session”而烦恼,那么推荐使用 StateServer
模式来存储 Session。
注:
如果 Session 的 Key 是在应用启动时随机生成的话,使用 StateServer 和 SQLServer 还是会“丢失” Session。
这是对 Session 的配置,对 Cache 无效。

删除根目录的 PrecompiledApp.config 文件(记得备份)
在 IIS 中“重新启动”该网站
原因:之前应该是发布的时候选了“在发布期间预编译”。发布后,服务器上会有这个预编译相关的文件(包含 bin 目录下也会有),其实 Application_BeginRequest() 方法是执行的,只是重新发布后没有更新而已(猜的)。具体关于发布的配置请自行了解。

打开:\WeiXinMPSDK-master\src\Senparc.Weixin.MP.Sample\Senparc.Weixin.MP.Sample.sln
工具 - NuGet 包管理器 - 管理解决方案的 NuGet 程序包 - 更新
选择所有的包(Microsoft.Net.Http 无法更新,暂不勾选),更新
Microsoft.Net.Http 是个老顽固,无法更新也无法卸载,原因是它的语言包 Microsoft.Net.Http.zh-Hans 无法安装。我们切换到“已安装”选项卡,搜索“Microsoft.Net.Http”,选中 Microsoft.Net.Http.zh-Hans 并卸载它,现在我们可以更新 Microsoft.Net.Http 了。
【可能】运行报错:
“/”应用程序中的服务器错误。
配置错误
说明: 在处理向该请求提供服务所需的配置文件时出错。请检查下面的特定错误详细信息并适当地修改配置文件。
分析器错误消息: 创建 system.web.webPages.razor/host 的配置节处理程序时出错: 未能加载文件或程序集“System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)
源错误:行 4: <configSections>
行 5: <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
行 6: <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
行 7: <section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
行 8: </sectionGroup>
源文件: E:\WeiXinMPSDK-master\src\Senparc.Weixin.MP.Sample\Senparc.Weixin.MP.Sample\views\web.config 行: 6展开 Senparc.Weixin.MP.Sample 引用,查看 System.Web.Razor 的属性,复制版本号。打开 \views\web.config,修改 System.Web.WebPages.Razor 的 Version(有 3 处)。
对于小型开发项目来说,我习惯将所有代码放在同一个项目(Project)中来,所以做了以下处理:(盛派官方分离这些代码是为了同时供 MVC 和 WebForms 重用)
在 Senparc.Weixin.MP.Sample 中添加文件夹 CommonService,将 Senparc.Weixin.MP.Sample.CommonService 项目中的文件夹和文件(.config 除外)拖到 CommonService 文件夹中。
卸载 Senparc.Weixin.MP.Sample.CommonService 项目并删除引用。
在解决方案的 Libraries 文件夹中,我们看到 Sample 项目了以下功能模块,这些就是 Senparc.Weixin 的源代码,如果不需要修改,可以卸载它们改为使用从 NuGet 获取,以便随时更新到最新版本:
Senparc.WebSocket
Senparc.Weixin
Senparc.Weixin.MP
Senparc.Weixin.MP.MvcExtension NuGet 中对应的包名为:Senparc.Weixin.MP.MVC
Senparc.Weixin.Open
Senparc.Weixin.Work
Senparc.Weixin.WxOpen
Senparc.Weixin.Cache.Memcached
Senparc.Weixin.Cache.Redis
卸载这些项目,并在 NuGet 中安装这些模块(搜索“Senparc.Weixin”第一页都在了,认准作者 Jeffrey Su)
单元测试项目中的引用也按需更换,当然也可以卸载这些单元测试项目(视情况保留 Senparc.Weixin.MP.Sample.Tests) 。
【可能】运行报错
HTTP Error 500.19 - Internal Server Error
无法访问请求的页面,因为该页的相关配置数据无效。
配置源:
134: </sessionState>
135: <sessionState customProvider="Memcached" mode="Custom">
136: <providers>
可以在 Global.asax 的 RegisterWeixinCache() 中修改 Memcached 配置。
或者打开 Web.config,找到 Memcached 所在的 sessionState 标签,注释掉,这样程序自动使用上方的 InProc(应用进程内)。
如果开启了 ASP.NET State Service 服务,那么建议改用 StateServer,与 Custom 一样需要序列化,方便日后改用 Memcached 或 Redis。
再次更新 NuGet,否则会提示无法加载 Enyim.Caching 的错误(报错行:MemcachedObjectCacheStrategy.RegisterServerList(memcachedConfig);)。
接下来,进入微信公众平台,在左侧 开发 - 基本配置 中设置相关参数(填写“服务器地址(URL)”如“https://weixin.xoyozo.net/weixin/”),打开 Sample 项目中的 Web.config,配置我们的公众号参数。
将项目发布到服务器上,尝试向公众号发送消息吧。
单元测试
设置单元测试中的公众号配置项:打开 Senparc.Weixin.MP.Test/CommonAPIs/CommonApiTest.cs,在 AppConfig 方法中可以看到支持两种配置方式,在 Senparc.Weixin.MP.Test/Config/test.config 配置文件中配置,或直接在 CommonApiTest.cs 文件中配置,前者格式如下,后者建议只在个人测试项目中使用。
<Config> <AppId>YourAppId</AppId> <Secret>YourSecret</Secret> <MchId>YourMchId</MchId> <TenPayKey>YourTenPayKey</TenPayKey> <TenPayCertPath>YourTenPayCertPath</TenPayCertPath> <!-- 小程序 --> <WxOpenAppId>YourOpenAppId</WxOpenAppId> <WxOpenSecret>YourWxOpenSecret</WxOpenSecret> </Config>
另外,将 _testOpenId 值改为自己的微信号在本公众号上的 openid。
微信网页授权(OAuth2)
打开 OAuth2Controller 控制器,将“Index”Action 中的盛派的网址(有 2 处)改成:
Request.Url.Scheme + "://" + Request.Url.Authority + "/Oauth2/……
未完待续……

在 NuGet 中安装
Microsoft.AspNetCore.Session
和Newtonsoft.Json
打开 Startup.cs,在 ConfigureServices 方法中加入(视情况配置相关属性)
services.AddSession(options => { // 设置了一个较短的时间,方便测试 options.IdleTimeout = TimeSpan.FromSeconds(10); options.CookieHttpOnly = true; });
在 Configure 方法中加入
app.UseSession();
添加 Extensions 文件夹并添加类 SessionExtensions.cs
using Microsoft.AspNetCore.Http; using Newtonsoft.Json; public static class SessionExtensions { public static void Set<T>(this ISession session, string key, T value) { session.SetString(key, JsonConvert.SerializeObject(value)); } public static T Get<T>(this ISession session, string key) { var value = session.GetString(key); return value == null ? default(T) : JsonConvert.DeserializeObject<T>(value); } }
使用方法
HttpContext.Session.Set("abc", 123); var v1 = HttpContext.Session.Get<int>("abc"); HttpContext.Session.Remove("abc"); var v2 = HttpContext.Session.Get<int>("abc"); // v1 = 123, v2 = 0
