Win11共享打印0x0000bc4,三步解决共享难题 - AganTee - 博客园

mikel阅读(170)

来源: Win11共享打印0x0000bc4,三步解决共享难题 – AganTee – 博客园

在多人办公场景中,共享打印机能大幅提升效率,但Win11系统常因驱动、网络或权限问题出现错误,其中Win11共享打印0x0000bc4最为常见。这个错误通常提示找不到打印机,本文将给大家分享三种解决方法,帮你恢复打印机共享功能。

一、检查共享设置与网络配置

Win11共享打印0x0000bc4可先从基础设置错误,多数情况是网络或权限配置遗漏导致。

2.1 启用共享基础功能

  1. 设置网络配置文件:
    打开「设置→网络和Internet→高级网络设置」,将当前网络设为「专用」,公用网络会阻止打印机共享;

4c94a041425adf44bc1114bf541628cc.png

  1. 开启共享权限:
    进入「控制面板→网络和共享中心→更改高级共享设置」,勾选「启用网络发现」和「启用文件和打印机共享」,保存设置后重启电脑。

 

2.2 手动添加共享打印机

若系统自动搜索不到共享打印机,可通过IP地址直连:

  1. 在连接打印机的主机上,按 Win+R 输入 cmd,执行 ipconfig 获取IPv4地址;
  2. 在客户端电脑按 Win+R 输入 \\192.168.1.100,回车后找到共享打印机图标,右键「连接」

735c2cdf225729ed82801ee9df201651.png

 

二、修改组策略与服务配置

如果基础设置没问题,Win11共享打印0x0000bc4的原因可能是系统策略限制或服务异常,需要通过组策略编辑器调整。

3.1 配置RPC连接

  1. 按 Win+R 输入 gpedit.msc 打开组策略编辑器;
  2. 依次展开「计算机配置→管理模板→打印机」,双击「配置RPC连接设置」;
  3. 选择「已启用」,在「RPC连接设置」中勾选「命名管道上的RPC」,保存后重启

d9e1c3815f10e22dbf5f887082786bb7.png

3.2 修复Print Spooler服务

  1. 按 Win+R 输入 services.msc,找到「Print Spooler」服务;
  2. 若状态为「已停止」,右键选择「启动」,并将启动类型设为「自动」;
  3. 重启服务后,删除 C:\Windows\System32\spool\PRINTERS 路径下的临时文件,清除打印队列冲突。

5e4321ac80b05b2392461daf16baab89.png

家庭版Win11用户:组策略编辑器默认不可用,可通过「设置→Windows更新」安装累积更新,或手动导入注册表修复RPC配置。

三、驱动修复工具

这个打印机驱动修复管家是个驱动修复工具,可以执行「诊断-修复-驱动匹配」的流程服务。能够进行驱动匹配和故障排查。工具内置驱动库,覆盖惠普、佳能、联想等主流品牌,支持自动识别打印机型号并匹配稳定驱动。界面设计简洁,可通过一键式操作完成从扫描到修复的全过程。

针对Win11共享打印0x0000bc4错误,可以重点检测三类问题:

  • 驱动状态:扫描驱动是否过期、损坏或与Win11不兼容;
  • 共享配置:检查文件和打印机共享功能是否启用;
  • 服务状态:监测Print Spooler等关键服务是否正常运行

34e3964c26f24f01b2900e26c6547ab3.png

修复完成后需重启电脑,重新添加共享打印机。若问题仍存在,可尝试「手动安装模式」,从驱动库中选择历史版本驱动,部分老旧打印机可能需要降级驱动以适配Win11。

四、总结与注意事项

解决Win11共享打印0x0000bc4的关键在于分层排查,需注意:

  • 驱动匹配:老旧打印机优先选择Win10兼容驱动,避免最新版驱动与Win11冲突;
  • 隐私安全:在线工具处理敏感文件时,优先使用本地软件,防止隐私泄露
  • 权限管理:共享主机需为管理员账户,客户端添加时输入正确的主机用户名和密码。

硬盘突然坏掉,我花了半个月才把数据救回来…(附数据恢复工具) - 程序设计实验室 - 博客园

mikel阅读(143)

来源: 硬盘突然坏掉,我花了半个月才把数据救回来…(附数据恢复工具) – 程序设计实验室 – 博客园

硬盘突然坏掉,我花了半个月才把数据救回来…(附数据恢复工具)

前言#

上次说到我的硬盘坏了,真的是当头一棒。😅

因为平时很多工作资料都在这块盘里,数据恢复的过程持续了小半个月,堪称一场心理和体力的双重折磨。

好在最后,大部分文件都救回来了。虽然过程非常花时间,但至少没有全军覆没。🤣

这次经历也算是给我自己上了一课,顺便整理成笔记,分享一下过程和心得。

👉 如果你正好也需要相关工具,我已经整理好了本次用到的软件清单,可以在公众号 「曦远Life」 后台回复 「数据恢复」 获取。

工具的两种思路#

在接触数据恢复工具之前,我总结了一下它们大致有两种工作模式:

  1. 扫描 MFT(Master File Table)
    • 优点:能保留目录结构、文件名,恢复出来的文件比较“原汁原味”。
    • 缺点:如果 MFT 本身损坏严重,可能丢失部分文件,或者文件内容不完整。
  2. 扫描数据块(Raw Scan)
    • 优点:能尽可能多地找回文件内容。
    • 缺点:失去目录结构和文件名,恢复出来一大堆 file0001.jpgfile0002.doc,后期整理会非常痛苦。

大部分恢复软件都是这两种思路的结合,实际使用时需要灵活选择。

👉 我这次用到的工具(DMDE、OSFMount、FinalData 等)都整理在一起了,获取方式:在公众号 「曦远Life」 后台回复 「数据恢复」

关于 NTFS#

NTFS(New Technology File System)是 Windows 系统常用的文件系统,它的优点是:

  • 支持大文件和大容量分区
  • 有权限控制、安全日志
  • 目录和文件的元信息都存储在 MFT(Master File Table) 里

核心要点:MFT 是 NTFS 的总目录表。

如果 MFT 受损,文件系统就像失忆了一样,文件还在,但找不到入口。

MFT介绍#

MFT 记录了每一个文件的:

  • 文件名
  • 路径
  • 大小
  • 时间戳(创建/修改/访问时间)
  • 文件数据所在的位置

所以,当我们做「基于 MFT 的扫描」时,本质上就是在想办法读取这张总目录表。

一旦这张表坏了,就只能退而求其次,去“数据块级别”扫描了。

我这次的思路#

这次我走的是 「先保护现场,再尝试修复」 的思路。

  1. 对分区做镜像
    • 用 DMDE 给损坏的分区做了完整镜像(img 格式)。
    • 原因:避免在原盘上直接操作,防止二次损坏。
  2. 挂载镜像
    • 使用 OSFMount 将镜像挂载为虚拟磁盘分区,方便后续操作。
  3. 文件系统检查
    • 执行 chkdsk /f /r X:
    • /f 修复错误,/r 尝试恢复坏扇区。
    • 过程中系统自动生成了一些 found.000 文件夹,里面是修复时捡回来的“孤立文件”。
  4. 扫描与恢复
    • 使用 FinalData 对挂载盘进行深度扫描。
    • 部分文件恢复成功,保留了文件名和目录结构;部分则散落在 found.000 里,需要人工整理。

成果#

恢复结果:大部分文件都找回来了!

遗憾点:有一部分目录结构和文件名乱了,需要大量时间手动分类、重命名。

总结感受:能把关键文件救回来就算赢了,剩下就是慢工出细活。

小结#

这次经历让我深刻认识到

  • 一定要重视 数据安全
  • 重要数据要 多地备份:本地 + NAS + 多种不同云存储。
  • 不要依赖单一云存储,服务商也可能出问题。

接下来,我也打算升级我的 NAS,把数据安全短板补齐,做到真正的有备无患。

数据无价,别等丢了才后悔!

Vue 配置中解决 CORS 跨域问题【亲测,跨坑!】 - 淡定=淡定 - 博客园

mikel阅读(143)

来源: Vue 配置中解决 CORS 跨域问题【亲测,跨坑!】 – 淡定=淡定 – 博客园

看了网上很多资料,很多不全,只是针对部分场景。请看到最后:

一、CORS 跨域问题解决
1.1、前言

如果你后端使用的是微服务项目,通过配置网关可以很好的解决跨域问题;

如果你使用 SpringBoot + Vue 来写项目,可以在 Controller 上加 @CrossOrigin 来解决跨域,但是一旦你在后端配置了拦截器(比如通过 HandlerInterceptor 配置登录拦截器),那么此时再解决跨域这个问题就很困难了.

Ps:在网上搜索 “Spring Boot 拦截器跨域问题解决” 会有大把大把的办法,但亲测没一个能用…

实际上,可以从 Vue 的角度来解决这个问题,只需要在 Vue 中进行如下配置即可.

1.2、解决办法
a)修改统一配置的 axios 实例
在 Vue 脚手架中,按照标准开发,一般会创建一个 utils 文件夹,里面创建一个 request.js 文件来统一封装 axios.

这里我们修改 axios 实例中的 baseURL 如下:

//构建统一 axios 实例
const instance = axios.create({
baseURL: “/api”,
timeout: 5000
});
Ps:不要管为什么,你先配置!修改的这些配置将来都不会影响你的原有的操作!

b)修改 config 文件夹下的 index.js 文件
在 Vue2 中,config 目录下有一个 index.js 文件.

添加如下配置

‘/api’: {
target: ‘http://localhost:8010’, //目标 url
changeOrigin: true, //是否跨域
pathRewrite: {
‘^/api’: ” //其中 /api 等价于前面的目标地址
}
}
c)完成
配置完了以后,一定要重启前端项目,重新 npm run dev !!

通过以上配置,后端也就无需担忧跨域问题了~
一般情况下,到这就OK了!But,不稳定,过一会又失效了@_@-,总结一下解决方案:

step 1:

修改 config 文件夹下的 index.js 文件
在 Vue2 中,config 目录下有一个 index.js 文件.

 

添加如下红色框里的配置 :

step 2:

修改 config 文件夹下的 dev.env.js 文件:
增加红框里的内容

Step 3:

在实际引入 axios使用的 js 文件里,增加一行:

如下调用:

axios.post(XXXX),then(……,这里的XXX不需要写要访问的服务端http地址了,也不需要写”/api”即可

 step 4:

重新启动 :

npm run dev

.NET 生态洞察:NuGet 下载量 Top 100 包深度解析

mikel阅读(247)

来源: .NET 生态洞察:NuGet 下载量 Top 100 包深度解析

在 .NET 开发中,NuGet 已经成为开发者管理依赖的标配工具。无论是后端 API、微服务,还是桌面应用,很多功能都不必自己重写——直接引用成熟的 NuGet 包即可。

本文整理了 下载量排名前 100 的 NuGet 社区包,并结合实际应用场景分析,让你快速了解哪些库最受欢迎,以及它们的典型用途。

数据来源:NuGet 包下载排行


🔹 JSON & 序列化

  • • Newtonsoft.Json:最流行的 JSON 序列化/反序列化库(虽然 .NET Core 内置 System.Text.Json,但很多项目仍使用它)。
  • • Newtonsoft.Json.Bson:支持 BSON(二进制 JSON)格式的序列化。

🔹 日志 Logging

  • • Serilog:结构化日志库,支持 JSON 输出和多种 sink。
  • • Serilog.Sinks.Console / Serilog.Sinks.File / Serilog.Sinks.Debug:输出到控制台、文件或 Visual Studio 调试窗口。
  • • Serilog.Extensions.Logging / Serilog.Extensions.Hosting:与 .NET Host 和 Microsoft.Extensions.Logging 集成。
  • • Serilog.Settings.Configuration:支持 appsettings.json 配置。
  • • Serilog.Formatting.Compact:压缩 JSON 日志格式。
  • • Serilog.AspNetCoreASP.NET Core 集成。
  • • Serilog.Sinks.PeriodicBatching:支持批量写入日志。

🔹 云服务 SDK

  • • AWSSDK.Core:AWS SDK 核心库。
  • • AWSSDK.S3:操作 Amazon S3。
  • • AWSSDK.SecurityToken:STS 认证(临时凭证)。
  • • AWSSDK.SQS:Amazon 队列服务。
  • • AWSSDK.Lambda:调用 AWS Lambda。

🔹 弹性与容错

  • • Polly:弹性策略库(重试、断路器、超时)。
  • • Polly.Core:核心功能。
  • • Polly.Extensions.Http:专为 HttpClient 提供策略扩展。

🔹 gRPC 生态

  • • Google.Protobuf:Protocol Buffers 序列化。
  • • Grpc.Core.Api / Grpc.Net.Client / Grpc.Net.ClientFactory:核心 API 与客户端。
  • • Grpc.Net.Common:共享组件。
  • • Grpc.Tools:编译 .proto 文件。
  • • Grpc.AspNetCore.Server:在 ASP.NET Core 上搭建 gRPC 服务。

🔹 API 文档 & Swagger

  • • Swashbuckle.AspNetCore.SwaggerGen:生成 Swagger 文档。
  • • Swashbuckle.AspNetCore.Swagger / Swashbuckle.AspNetCore:核心包与集合包。
  • • Swashbuckle.AspNetCore.SwaggerUI:可视化 UI。
  • • Swashbuckle.AspNetCore.Annotations:Swagger 注解支持。

🔹 测试相关

  • • xUnit 系列xunitxunit.corexunit.runner.visualstudioxunit.extensibility.corexunit.extensibility.executionxunit.assertxunit.analyzersxunit.abstractions
  • • Moq:Mock 框架。
  • • FluentAssertions:断言库,语法自然。
  • • NUnit / NUnit3TestAdapter:另一种测试框架及 VS 集成。
  • • Coverlet.Collector:代码覆盖率统计。

🔹 数据库 & ORM

  • • StackExchange.Redis:Redis 客户端。
  • • NpgSQL / NpgSQL.EntityFrameworkCore.PostgreSQL:PostgreSQL 驱动及 EF Core Provider。
  • • Dapper:轻量 ORM。
  • • RabbitMQ.Client:消息队列客户端。
  • • MongoDB.Driver / MongoDB.Bson:MongoDB 驱动及 BSON 类型支持。
  • • SQLitePCLRaw.Core:SQLite 驱动。
  • • DocumentFormat.OpenXml:操作 Word/Excel 等 Office 文档。

🔹 OpenTelemetry(可观测性)

  • • OpenTelemetry.Api / OpenTelemetry.Api.ProviderBuilderExtensions:核心 API 与扩展。
  • • OpenTelemetry / OpenTelemetry.Extensions.Hosting:完整 SDK 与 IHost 集成。
  • • OpenTelemetry.Instrumentation.AspNetCore / OpenTelemetry.Instrumentation.Http:ASP.NET Core 与 HTTP 监控。
  • • OpenTelemetry.Exporter.OpenTelemetryProtocol:OTLP 协议导出器。

🔹 实用工具库

  • • Humanizer.Core:字符串/日期/数字人性化显示(如 “2 days ago”)。
  • • FluentValidation / FluentValidation.DependencyInjectionExtensions:验证框架及 DI 集成。
  • • AutoMapper:对象映射工具。
  • • MediatR / MediatR.Contracts:CQRS 与中介者模式。
  • • Autofac:IoC 容器。
  • • YamlDotNet / CsvHelper:YAML 与 CSV 解析。
  • • DnsClient:DNS 查询。
  • • System.Linq.Async:异步 LINQ 扩展。
  • • RestSharp:HTTP 客户端。
  • • System.Reactive:响应式扩展。
  • • JetBrains.Annotations:代码注解。
  • • Hangfire.Core:后台任务调度。
  • • NJsonSchema:JSON Schema 支持。
  • • SixLabors.ImageSharp / SixLabors.Fonts:图像处理与字体渲染。
  • • SharpCompress / SharpZipLib / ZstdSharp.Port:压缩与解压缩库。
  • • BouncyCastle.Cryptography / Portable.BouncyCastle:加密库。
  • • SSH.NET:SSH 客户端。
  • • log4net:老牌日志库。
  • • HtmlAgilityPack:HTML 解析与操作。

🔹 其他精选库

  • • Pipelines.Sockets.Unofficial:高性能 Socket 库。
  • • Mono.TextTemplating:T4 模板。
  • • Google.Apis 系列:OAuth2 与 API 客户端。
  • • EO.WebBrowser:基于 Chromium 的浏览器控件(商用)。
  • • Fare:正则表达式生成字符串。
  • • Namotion.Reflection:增强反射功能。
  • • SFA.Core.ServiceModel:WCF 相关库。

🔹 总结

这些 NuGet 包覆盖了 序列化、日志、云 SDK、容错、gRPC、API 文档、测试、数据库、可观测性及实用工具 等各类常用场景,是 .NET 开发者最常依赖的生态组成部分。

合理利用这些成熟的库,可以显著提升开发效率和项目稳定性,让你专注于业务逻辑,而不是重复造轮子。

 

··············  END  ··············

GDI+中发生一般性错误_51CTO博客_gdi+发生一般性错误

mikel阅读(166)

来源: GDI+中发生一般性错误_51CTO博客_gdi+发生一般性错误

 

 在开发.NET应用中,使用 System.Drawing.Image.Save 方法而导致“GDI+ 中发生一般性错误”的发生,通常有以下三种原因:

1. 相应的帐户没有写权限。

解决方法:赋予 NETWORK SERVICE 帐户以写权限。

2. 指定的物理路径不存在。

解决方法:

在调用 Save 方法之前,先判断目录是否存在,若不存在,则创建。

if (!Directory.Exists(dirpath))

Directory.CreateDirectory(dirpath);

3. 保存的文件已存在并因某种原因被锁定。

解决方法:

重启IIS,解除锁定。并在代码中使用 using 语句,确保释放 Image 对象所使用的所有资源。

我遇到的情况:

在先用openFileDialog打开图片文件,然后用saveFileDialog保存文件时就出现了 “GDI+中发生一般性错误”,我当时就想到是打开的文件还没有释放出来,于是用openFileDialog1.Dispose()来释放,可是没有成功。同样从一个MemorySream 实例打开一个Image后,立即关闭了这个流,结果在Image.Save时也会发生这种错误。我“摆渡”了很久都是遇到和我一样问题的人,CSDN上面的同志也没有给出一个实用的答案。最后终于还是在微软的网站上找到了答案:(以下是官方解决办法)

症状

Bitmap 对象或一个 图像 对象从一个文件, 构造时该文件仍保留锁定对于对象的生存期。 因此, 无法更改图像并将其保存回它产生相同的文件。

 

替代方法

•    创建非索引映像。

•    创建索引映像。

这两种情况下, 原始 位图 上调用 Bitmap.Dispose() 方法删除该文件上锁或删除要求, 流或内存保持活动。

 

创建非索引图像

即使原始映像被索引格式中该方法要求新图像位于每像素 (超过 8 位 -) -, 非索引像素格式。 此变通方法使用 Graphics.DrawImage() 方法来将映像复制到新 位图 对象:

1.    构造从流、 从内存, 或从文件原始 位图 。

2.    创建新 位图 的相同大小, 带有是超过 8 位 – – 像素 (BPP) 每像素格式。

3.    使用 Graphics.FromImage() 方法以获取有关二 位图 Graphics 对象。

4.    用于 Graphics.DrawImage() 绘制首 位图 到二 位图 。

5.    用于 Graphics.Dispose() 处置是 图形 。

6.    用于 Bitmap.Dispose() 是首 位图 处置。

 

创建索引映像

此解决办法在索引格式创建一个 Bitmap 对象:

1.    构造从流、 从内存, 或从文件原始 位图 。

2.    创建新 位图 具有相同的大小和像素格式作为首 位图 。

3.    使用 Bitmap.LockBits() 方法来锁定整个图像对于两 Bitmap 对象以其本机像素格式。

4.    使用 Marshal.Copy 函数或其他内存复制函数来从首 位图 复制到二 位图 图像位。

5.    使用 Bitmap.UnlockBits() 方法可以解锁两 Bitmap 对象。

6.    用于 Bitmap.Dispose() 是首 位图 处置。

由于外国人的思维和我们不一样,我重新用实例解释一下,我这里使用的是创建非索引图像。

private void ToolStripMenuItem_Click(object sender, EventArgs e)

{

if (openFileDialog1.ShowDialog() == DialogResult.OK)

{

//创建一个bitmap类型的bmp变量来读取文件。

Bitmap bmp = new Bitmap(openFileDialog1 .FileName );

//新建第二个bitmap类型的bmp2变量,我这里是根据我的程序需要设置的。

Bitmap bmp2 = new Bitmap(1024, 768, PixelFormat.Format16bppRgb555);

//将第一个bmp拷贝到bmp2中

Graphics draw = Graphics.FromImage(bmp2);

draw.DrawImage(bmp,0,0);

pictureBox1.Image = (Image)bmp2 ;//读取bmp2到picturebox

FILE = openFileDialog1.FileName;

openFileDialog1.Dispose();

draw.Dispose();

bmp.Dispose();//释放bmp文件资源

}

}

通过以上的读取文件,在保存的时候就不会出现错误了。​

*********************************************************************************************

我写的一个生成缩略图程序, 生成gif,bmp,png 文件都可以, 就是不能生成jpg 文件, 何解??

 

//按比例缩小上传的图片

System.Drawing.Image img;

img = System.Drawing.Image.FromFile(all);

float width = img.Width;

float height = img.Height;

float newwidth = 0;

float newheight = 0;

if (width > height)

{

newwidth = 135;

newheight = height / width * newwidth;

}

else

{

newheight = 135;

newwidth = width / height * newheight;

}

System.Drawing.Image outimg = img.GetThumbnailImage((int)newwidth,(int)newheight,null,IntPtr.Zero);

string newfiles = path + newfilename + “.jpg”;

outimg.Save(newfiles);

 

outimg.Dispose();

img.Dispose();

——————————————————————————————-

绘图坐标超过了画布大小。建议先把画布设大写,看看效果再确定实际大小

跟画布大小应该是无关的,

如果上传jpg文件, 用以下方法保存是会报GDI 中发生一般性错误,

outimg.Save(newfiles,System.Drawing.Imaging.ImageFormat.Jpeg);

如果改成outimg.Save(newfiles,System.Drawing.Imaging.ImageFormat.Gif) 就能顺利生成,

但改成outimg.Save(newfiles,System.Drawing.Imaging.ImageFormat.Gif) 后, 上传gif文件生成缩略图就报GDI 中发生一般性错误, 现在连问题的根源都不清楚, 唉…

监测一下你的newwidth和newheight是不是未预期的数据

检查是否是权限问题

1. 确认没有同名jpg文件存在

2. 确认aspnet或者network service有覆盖文件等相应权限

vivianfdlpw() 一言惊醒梦中人啊!! 果然是重名了!!!

**************************************************************************************

private void DrawImg(int width)

{

//图片的地址

string path=String.Format(@”D:\共享文件夹\EnterpriseLicences\1\123.jpg”);

//保存水印图片的文件夹

string catchPath=Server.MapPath(@”..\temp\cache\”);

Image newImage = Image.FromFile(path);    //取出图片

 

if(width==0)

{

width=newImage.Width;

}

int hight=newImage.Height*width/newImage.Width;

System.Drawing.Bitmap bitmap=new Bitmap(width,hight);

System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap);

g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;

g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

g.Clear(System.Drawing.Color.Transparent);

g.DrawImage(newImage,0,0,width,hight);

 

//水印图片

Bitmap copyImage = (Bitmap)Bitmap.FromFile(Server.MapPath(@”..\Images\EnterpriseLisence\Logos.gif”));

copyImage.MakeTransparent(Color.White);

 

float copyW=width*4/10; float copyH=copyImage.Height*copyW/copyImage.Width;

g.DrawImage(copyImage,width-copyW-10,hight-copyH-5,copyW,copyH);

try

{

bitmap.Save(catchPath+”123.jpg”);  //这个文件夹有权限

 

}

catch (Exception e)

{

throw e;

}

bitmap.Dispose();

newImage.Dispose();

g.Dispose();

}

 

Message “GDI+ 中发生一般性错误。”

Source “System.Drawing”

StackTrace

at System.Drawing.Image.Save(String filename, ImageCodecInfo encoder, EncoderParameters encoderParams)

at System.Drawing.Image.Save(String filename, ImageFormat format)

at System.Drawing.Image.Save(String filename)

at KSI.KSI_Web.EnterpriseLisence.Drawing.DrawImg(Int32 width) in D:\\worksharp\\ksi\\ksi_web\\enterpriselisence\\drawimg.aspx.cs:line 98″

—————————————————————————————————-

1.尽量使用 引用图片,避免直接加载物理图片

eg://图片的地址

string path=String.Format(@”D:\共享文件夹\EnterpriseLicences\1\123.jpg”);

Image newImage = Image.FromFile(path);    //取出图片

g.DrawImage(newImage,0,0,width,hight);

这种方式不是很可取,而且容易造成 GDI+错误

2.VS03、05对GIF支持不是很完好!当你的程序执行较大操作的时候,且你会更新你的图片的时候,GDI+错误 偶尔、或者经常出现。

我本人不知道解决方法。只能使用别的方式,加载GIF图片 或者不加载GIF图片

注(您的代码比较严谨,不存在实质性的漏洞,加载释放都有,虽然位置不佳,但是依然可以达到 释放的目的)请你 更换GIF 图片 再 重新生成项目

 

C# 自定义项写入配置文件appsettings.json中(如url和token)_c# appsettings.json-CSDN博客

mikel阅读(113)

来源: C# 自定义项写入配置文件appsettings.json中(如url和token)_c# appsettings.json-CSDN博客

一、打开项目的 `appsettings.json` 文件,通常位于项目的根目录。
二、在 `appsettings.json` 文件中添加相应的配置项。可以使用类似下面的格式添加配置项:
{
“ApiConfiguration”: {
“BaseApiUrl”: “your_url_here”,
“Token”: “your_token_here”
},
// 其他配置项…
}
AI写代码
JavaScript
运行
三、在 `Startup.cs` 文件中的 `ConfigureServices` 方法中,注册配置项,并使其可供应用程序的其他部分访问。确保引用 `Microsoft.Extensions.Configuration` 和 `Microsoft.Extensions.Options` 命名空间。
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace YourNamespace
{
public class Startup
{
//…

public void ConfigureServices(IServiceCollection services)
{
//…

services.Configure<ApiConfiguration>
Configuration.GetSection(“ApiConfiguration”));
//…
}
}
}
AI写代码
cs
运行

四、创建一个包含配置项属性的类 `ApiConfiguration`,用于将配置项映射到实际的类属性。
public class ApiConfiguration
{
public string BaseApiUrl { get; set; }
public string Token { get; set; }
}
AI写代码
cs
运行
五、在所需使用配置文件的类中,如 `ApiDataProcessor` 类使用 `IOptions<ApiConfiguration>` 注入配置项,并将其作为构造函数参数。
public class ApiDataProcessor : BackgroundService
{
private readonly ApiConfiguration _apiConfiguration;

public ApiDataProcessor(IOptions<ApiConfiguration> apiConfigurationOptions)
{
_apiConfiguration = apiConfigurationOptions.Value;
}

//…
}
AI写代码
cs
运行

现在,就可以在 ApiDataProcessor 类(可替换为实际类)中使用 _apiConfiguration.BaseApiUrl 和 _apiConfiguration.Token 来访问配置项的值,而不是硬编码在代码中。这样的好处是,可以在不更改代码的情况下更改配置项的值。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_42060801/article/details/132576647

如何使用 appsettings.json 配置文件?

mikel阅读(124)

来源: 如何使用 appsettings.json 配置文件?

appsettings.json 是一个相较于 App.config 更加灵活的配置文件,是 .NET Core 以来新增的一种配置方式,提供了更多的灵活性

在 .NET Core 项目中,我们可以使用 appsettings.json 配置文件来存储应用程序的配置信息。在这篇文章中,我们将学习如何使用 appsettings.json 配置文件。

appsettings.json 是一个相较于 App.config 更加灵活的配置文件,是 .NET Core 以来新增的一种配置方式,提供了更多的灵活性。

快速入门 

我们可以在项目中创建一个 appsettings.json 文件,然并将其生成操作设置为「较新时复制」或「总是复制」,这样在项目构建时,appsettings.json 文件会被复制到输出目录中。

然后我们可以在其中添加如下内容:

1
2
3
4
5
6
7
8
{
    "AppSettings": {		
        "LogLevel":"Warning",
        "ConnectionStrings": {
            "Default": "this is the connection string"
        }	
    }
}

这样我们就可以尝试读取了。我们使用 NuGet 包管理器安装 Microsoft.Extensions.Configuration.Json 包。它会隐式安装 Microsoft.Extensions.Configuration 等依赖项,这些我们不需要显式安装。

然后我们可以在代码中读取配置文件:

1
2
3
4
5
using Microsoft.Extensions.Configuration;

var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .Build();

这样我们就可以获取上面的配置信息了:

1
2
var logLevel = configuration["AppSettings:LogLevel"];
var connectionString = configuration["AppSettings:ConnectionStrings:Default"];

这里的形如 AppSettings.LogLevel 是一种特殊的写法,简单来说就是借助 : 来表示 JSON 中的层级关系。

如果要获取的配置项是一个数字,我们除了可以先通过上述方式获取到字符串,进而使用 int.Parse 或 Convert.ToInt32 等方法进行转换,还可以使用 GetValue 方法:

1
2
3
4
// 传统方法
var logLevel = int.Parse(configuration["AppSettings:LogLevel"]);
// 使用 GetValue 方法
var logLevel = configuration.GetValue<int>("AppSettings:LogLevel");

对于连接字符串,我们还可以使用 GetConnectionString 方法:

1
var connectionString = configuration.GetConnectionString("Default");

可选与自动重载 

在上面的代码中,我们可以看到 AddJsonFile 方法有两个参数,optional 和 reloadOnChange

  • optional 参数表示是否允许配置文件不存在,如果设置为 false,则会抛出异常,否则会忽略。
  • reloadOnChange 参数表示是否在配置文件发生变化时重新加载配置文件。如果设置为 true,则会在配置文件发生变化时重新加载配置文件。

比如我们可以用下面的例子测试自动重载的效果:

1
2
3
4
5
6
7
8
9
var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .Build();

while (true)
{
    Console.WriteLine(configuration["AppSettings:LogLevel"]);
    Thread.Sleep(1000);
}

在运行程序后,我们可以修改 appsettings.json 文件中的 LogLevel 配置,然后我们会发现程序会自动重新加载配置文件。注意这里我们修改的是输出目录(也就是 .exe 文件所在位置)下的 appsettings.json 文件,而不是项目中的 appsettings.json 文件。

添加多个 JSON 文件 

如果只能添加一个 JSON 文件,那么配置文件的灵活性就大大降低了。事实上,我们可以通过多次调用 AddJsonFile 方法来添加多个 JSON 文件。一个典型的情形是添加一个 appsettings.Development.json 文件,用于存储开发环境的配置信息。

1
2
3
4
var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true)
    .Build();

这样我们就可以在 appsettings.Development.json 文件中存储开发环境的配置信息,而在 appsettings.json 文件中存储通用的配置信息。

不仅如此,这二者之间存在优先级,或者说覆盖关系。具体来说:

  • 如果 appsettings.json 和 appsettings.Development.json 中都有相同的配置项,那么 appsettings.Development.json 中的配置项会覆盖 appsettings.json 中的配置项
  • 如果 appsettings.Development.json 中没有某个配置项,而 appsettings.json 中有,那么会使用 appsettings.json 中的配置项
  • 如果 appsettings.Development.json 中有某个配置项,而 appsettings.json 中没有,那么会使用 appsettings.Development.json 中的配置项

使用强类型配置 

在上面的例子中,我们使用 configuration["AppSettings:LogLevel"] 来获取配置信息,这种方式是一种弱类型的方式。我们也可以使用强类型的方式来获取配置信息。

我们修改一下 appsettings.json 文件中的配置项:

1
2
3
4
5
6
7
{
    "UserSettings": {
        "Name": "Alice",
        "Age": 18,
        "IsActive": true
    }
}

然后我们定义一个强类型的配置类:

1
2
3
4
5
6
public class UserSettings
{
    public string Name { get; set; }
    public int Age { get; set; }
    public bool IsActive { get; set; }
}

在获取配置前,我们还需要安装一个 NuGet 包:Microsoft.Extensions.Options.ConfigurationExtensions。然后我们就可以这样获取配置信息:

1
var userSettings = configuration.GetSection("UserSettings").Get<UserSettings>();

这样我们就可以获取到 UserSettings 对象了,然后就可以使用 userSettings.NameuserSettings.AgeuserSettings.IsActive 来获取配置信息了。

但是需要注意,因为这里的 userSettings 实例已经初始化,所以前面提到的自动重载功能不再生效。如果需要自动重载,我们需要重新获取 userSettings 对象。

添加环境变量和命令行参数 

在 .NET Core 中,我们还可以通过环境变量和命令行参数来覆盖配置文件中的配置信息。我们需要再安装两个 NuGet 包:

  • Microsoft.Extensions.Configuration.EnvironmentVariables
  • Microsoft.Extensions.Configuration.CommandLine

然后我们可以这样添加环境变量和命令行参数:

1
2
3
4
5
var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddEnvironmentVariables()
    .AddCommandLine(args)
    .Build();

这样我们就可以通过环境变量和命令行参数来覆盖配置文件中的配置信息了。

比如我们可以创建一个 .bat 批处理文件:

1
2
3
4
5
@echo off
set UserSettings__Name=Bob
set UserSettings__Age=20

.\Demo.exe

或者还可以使用 PowerShell:

1
2
3
4
$env:UserSettings__Name = "Bob"
$env:UserSettings__Age = 20

.\Demo.exe

总结 

相信通过这篇文章,大家已经认识到了 appsettings.json 配置文件的强大之处。它不仅提供了一种灵活的配置方式,还提供了多种配置方式的组合,使得我们可以更加灵活地配置应用程序。

但是它也有一些局限性。最重要的一条就是它的配置项是“只读”的,也就是不能像 App.config 那样在运行时方便地修改配置项。毕竟,一个项目中可能存在多个配置项,而不是只有一个 appsettings.json 文件。此时如果修改了,该保存到哪个文件呢?

当然,如果只有一个配置文件,那么 appsettings.json 是一个不错的选择。比如我们可以使用 Newtonsoft.Json 来轻松地写入 JSON 文件,这样就可以实现配置项的修改了。

最后,其实通常情况下,我们并不会使用上面的方式读取配置项,而是会更进一步,使用 Host 作为整个程序的入口,并读取配置、注入服务等。在之后的文章中,我们会学习如何使用 Host 来构建一个 .NET 应用程序。

windows server使用 LetsEncrypt-Win-Simple来安装和使用用Let's Encrypt免费SSL证书 - 米高佐敦 - 博客园

mikel阅读(174)

来源: windows server使用 LetsEncrypt-Win-Simple来安装和使用用Let’s Encrypt免费SSL证书 – 米高佐敦 – 博客园

一、网站部署

LetsEncrypt-Win-Simple可以自动发现已经部署的网站供我们选择要生成证书的网站,而且还需要进行验证。所以在生成证书之前,确保网站已经部署好并可以正常访问。

二、生成证书

软件下载地址如下:

https://github.com/Lone-Coder/letsencrypt-win-simple/releases

直接下载zip压缩包就好,下载完之后解压运行里面的letsencrypt.exe打开控制台窗口,第一次运行会提示你输入一个邮箱以供后续使用。

Let’s Encrypt (Simple Windows ACME Client)

Renewal Period: 60

Certificate Store: WebHosting

ACME Server: https://acme-v01.api.letsencrypt.org/

Config Folder: C:\Users\ling\AppData\Roaming\letsencrypt-win-simple\httpsacme-v01.api.letsencrypt.org

Certificate Folder: C:\Users\ling\AppData\Roaming\letsencrypt-win-simple\httpsacme-v01.api.letsencrypt.org

Getting AcmeServerDirectory

Enter an email address (not public, used for renewal fail notices):

输入一个可用邮箱回车即开始注册,并问你是否同意协议

Calling Register

Do you agree to https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf? (Y/N)

输入Y回车,邮箱注册完之后,程序会自动获取当前服务器部署的所有网站,如下:

Scanning IIS Site Bindings for Hosts

1: IIS xxling.com (D:\www\xxx)

2: IIS www.xxling.com (D:\www\xxx)

W: Generate a certificate via WebDav and install it manually.

F: Generate a certificate via FTP/ FTPS and install it manually.

M: Generate a certificate manually.

A: Get certificates for all hosts

Q: Quit

Which host do you want to get a certificate for:

输入1或者2就会为相应的域名生成证书,M为手动生成一个证书,A则为所有域名生成证书。这里我们选择A然后回车继续,生成完证书程序还会自动为相应网站配置IIS的SSL。选择了A之后生成的证书在C:\Users\Administrator\AppData\Roaming\letsencrypt-win-simple\httpsacme-v01.api.letsencrypt.org下,具体看后面输出的提示会有位置信息。

由于Let’s Encrypt免费SSL证书只有90天的有效期,过期需要更新,该程序会生成一个计划任务,每天上午9点钟运行检查过期的证书并更新。所以生成完成证书会提示你是不是需要指定用户运行刷新证书的计划任务(会显示下次过期的时间),选择Y,输入相应的用户和密码即可。

注意:生成的计划任务的执行程序默认是生成证书时的letsencrypt.exe的路径,请到管理工具-计划任务查看相应任务属性,确认action的letsencrypt.exe路径对不对,如果移动到其它地方了也需要进行相应修改,否则会执行失败!

三、IIS配置

打开IIS,查看域名绑定,就会看到多了443的端口绑定,证书也以配置好。重启一下iis,通过https访问就可以了。

如果是手动配置,在IIS7配置https绑定是host name那栏灰掉,不可输入,此时如果需要指定相应的域名,可以打开以下位置文件:
C:\Windows\System32\inetsrv\config\applicationHost.config

搜索相应域名找到该网站的配置节点,你会发现不输入域名是的https配置如下

<binding protocol=”https” bindingInformation=”*:443:” />

将其改为

<binding protocol=”https” bindingInformation=”:443:www.xxling.com” />

即可,域名改为自己实际的域名。

其次,要注意的是,IIS只能绑定一个SSL证书,这生成证书的新版软件好像没有多域名证书生成的选项了,所以只能把其他域名重定向到有证书的域名上。

 

四、强制HTTPS访问

首先IIS配置选中要求SSL选项

IIS6中,站点属性-》目录安全性-》编辑中把“要求安全通道(SSL)”勾选上即可。

IIS7、8中,SSL设置-》把“要求SSL”勾选即可

此时通过http访问的话会报403错误,我们找到403的html页面,加入以下JavaScript脚本进行跳转:

<script type=”text/JavaScript”>

var url = window.location.href;

if (url.indexOf(“https”) < 0) {

url = url.replace(“http:”, “https:”);

window.location.replace(url);

}

</script>

403文件所在路径如下:

IIS6.0 :C:\WINDOWS\Help\iisHelp\common\403-4.htm

IIS7.0以上 :C:\inetpub\custerr\zh-CN\403.htm

如果使用的英文版系统zh-CN可能为en-US或者其它。

此时我们通过http访问我们的网站时就会自动跳转到https了。

ASP.NET HttpRuntime.Cache缓存类使用总结 - Kencery - 博客园

mikel阅读(166)

来源: ASP.NET HttpRuntime.Cache缓存类使用总结 – Kencery – 博客园

  1.高性能文件缓存key-value存储—Redis

  2.高性能文件缓存key-value存储—Memcached

备注:三篇博文结合阅读,简单理解并且使用,如果想深入学习,请多参考文章中给出的博文地址。

1.前言

a.在Web开发中,我们经常能够使用到缓存对象(Cache),在ASP.NET中提供了两种缓存对象,HttpContext.Current.Cache和HttpRuntime.Cache,那么他们有什么区别呢?下面简单描述一下:

(1):HttpContext.Current.Cache 为当前Http请求获取Cache对象,通俗来说就是由于此缓存封装在了HttpContenxt中,而HttpContext只局限于Web中,所以此缓存信息只能够在Web中使用。

(2):HttpRuntime.Cache 获取当前应用程序的Cache,通俗来说就是此缓存信息虽然被放在了System.Web命名空间下,但是非Web程序也可以使用此缓存。

上面两种类型作对比,我们就能够看出,一般情况下我们都建议使用HttpRuntime.Cache 。

b.在缓存领域中,现在不止只有ASP.NET提供的缓存,还有Redis和Memcached等开源的Key_Value存储系统,也是基于内存去管理的,那么我们在这些文章中基本也都会有说到。

c.那么这篇文章我们就简单的封装一下HttpRuntime.Cache类的公用类,提供给别人直接使用,如有错误或者问题,请留言,必在第一时间去处理。

d.缓存详解:http://www.cnblogs.com/caoxch/archive/2006/11/20/566236.html

e.GitHub地址:https://github.com/kencery/Common/tree/master/KenceryCommonMethod/%E7%BC%93%E5%AD%98

2.为什么使用缓存

a.那么说了这么多,最终要的一个问题是我们为什么要使用缓存呢?

(1):降低延迟,使响应速度加快。

(2):降低网络传输,使响应速度加快。

………………………

简单总结就是一句话,使用缓存是为了使系统更加稳定和快速。

b.上面我们知道了为什么使用缓存,那么在什么情况下我们能够使用缓存呢?

(1):数据可能会被频繁的使用。

(2):数据的访问不频繁,但是它的生命周期很长,这样的数据建议也缓存起来,比如:淘宝的商品明细。

………………….

c.当然一般系统中我们建议使用Memcached和Redis键值对来存储缓存信息。

d.那么是不是我们在写程序的时候想写缓存就写缓存呢?当然不是,我们还要判断哪里该用,哪里不该用,比如:如果我们整页输出缓存的话,会影响我们数据的更新等等…….

e.这篇博文我们整理了HttpRuntime Cache的共同类,将如何使用此类都已经在代码中展示出来,代码如下:

3.代码展示

复制代码
  1 // 源文件头信息:
  2 // <copyright file="HttpRuntimeCache.cs">
  3 // Copyright(c)2014-2034 Kencery.All rights reserved.
  4 // 个人博客:http://www.cnblogs.com/hanyinglong
  5 // 创建人:韩迎龙(kencery)
  6 // 创建时间:2015-8-11
  7 // </copyright>
  8 
  9 using System;
 10 using System.Collections;
 11 using System.Web;
 12 using System.Web.Caching;
 13 
 14 namespace KenceryCommonMethod
 15 {
 16     /// <summary>
 17     /// HttpRuntime Cache读取设置缓存信息封装
 18     /// <auther>
 19     ///     <name>Kencery</name>
 20     ///     <date>2015-8-11</date>
 21     /// </auther>
 22     /// 使用描述:给缓存赋值使用HttpRuntimeCache.Set(key,value....)等参数(第三个参数可以传递文件的路径(HttpContext.Current.Server.MapPath()))
 23     /// 读取缓存中的值使用JObject jObject=HttpRuntimeCache.Get(key) as JObject,读取到值之后就可以进行一系列判断
 24     /// </summary>
 25     public class HttpRuntimeCache
 26     {
 27         /// <summary>
 28         /// 设置缓存时间,配置(从配置文件中读取)
 29         /// </summary>
 30         private const double Seconds = 30*24*60*60;
 31 
 32         /// <summary>
 33         /// 缓存指定对象,设置缓存
 34         /// </summary>
 35         public static bool Set(string key, object value)
 36         {
 37             return Set(key, value, null, DateTime.Now.AddSeconds(Seconds), Cache.NoSlidingExpiration,
 38                 CacheItemPriority.Default, null);
 39         }
 40 
 41         /// <summary>
 42         ///  缓存指定对象,设置缓存
 43         /// </summary>
 44         public static bool Set(string key, object value, string path)
 45         {
 46             try
 47             {
 48                 var cacheDependency = new CacheDependency(path);
 49                 return Set(key, value, cacheDependency);
 50             }
 51             catch
 52             {
 53                 return false;
 54             }
 55         }
 56 
 57         /// <summary>
 58         /// 缓存指定对象,设置缓存
 59         /// </summary>
 60         public static bool Set(string key, object value, CacheDependency cacheDependency)
 61         {
 62             return Set(key, value, cacheDependency, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration,
 63                 CacheItemPriority.Default, null);
 64         }
 65 
 66         /// <summary>
 67         /// 缓存指定对象,设置缓存
 68         /// </summary>
 69         public static bool Set(string key, object value, double seconds, bool isAbsulute)
 70         {
 71             return Set(key, value, null, (isAbsulute ? DateTime.Now.AddSeconds(seconds) : Cache.NoAbsoluteExpiration),
 72                 (isAbsulute ? Cache.NoSlidingExpiration : TimeSpan.FromSeconds(seconds)), CacheItemPriority.Default,
 73                 null);
 74         }
 75 
 76         /// <summary>
 77         /// 获取缓存对象
 78         /// </summary>
 79         public static object Get(string key)
 80         {
 81             return GetPrivate(key);
 82         }
 83 
 84         /// <summary>
 85         /// 判断缓存中是否含有缓存该键
 86         /// </summary>
 87         public static bool Exists(string key)
 88         {
 89             return (GetPrivate(key) != null);
 90         }
 91 
 92         /// <summary>
 93         /// 移除缓存对象
 94         /// </summary>
 95         /// <param name="key"></param>
 96         /// <returns></returns>
 97         public static bool Remove(string key)
 98         {
 99             if (string.IsNullOrEmpty(key))
100             {
101                 return false;
102             }
103             HttpRuntime.Cache.Remove(key);
104             return true;
105         }
106 
107         /// <summary>
108         /// 移除所有缓存
109         /// </summary>
110         /// <returns></returns>
111         public static bool RemoveAll()
112         {
113             IDictionaryEnumerator iDictionaryEnumerator = HttpRuntime.Cache.GetEnumerator();
114             while (iDictionaryEnumerator.MoveNext())
115             {
116                 HttpRuntime.Cache.Remove(Convert.ToString(iDictionaryEnumerator.Key));
117             }
118             return true;
119         }
120 
121         //------------------------提供给上面方法进行调用-----------------------------------
122         /// <summary>
123         /// 设置缓存
124         /// </summary>
125         public static bool Set(string key, object value, CacheDependency cacheDependency, DateTime dateTime,
126             TimeSpan timeSpan, CacheItemPriority cacheItemPriority, CacheItemRemovedCallback cacheItemRemovedCallback)
127         {
128             if (string.IsNullOrEmpty(key) || value == null)
129             {
130                 return false;
131             }
132             HttpRuntime.Cache.Insert(key, value, cacheDependency, dateTime, timeSpan, cacheItemPriority,
133                 cacheItemRemovedCallback);
134             return true;
135         }
136 
137         /// <summary>
138         /// 获取缓存
139         /// </summary>
140         private static object GetPrivate(string key)
141         {
142             return string.IsNullOrEmpty(key) ? null : HttpRuntime.Cache.Get(key);
143         }
144     }
145 }
复制代码

C# 6.0 内插字符串 (Interpolated Strings ) - 刀是什么样的刀 - 博客园

mikel阅读(226)

来源: C# 6.0 内插字符串 (Interpolated Strings ) – 刀是什么样的刀 – 博客园

看Interpolated Strings之前,让我们先看EF Core 2.0 的一个新的特性:String interpolation in FromSql and ExecuteSqlCommand

var city = "London";

using (var context = CreateContext())
{
    context.Customers
       .FromSql($@"
           SELECT *
           FROM Customers
           WHERE City = {city}")
       .ToArray();
}

SQL语句以参数化的方式执行,所以是防字符串注入的。

@p0='London' (Size = 4000)

SELECT *
FROM Customers
WHERE City = @p0

一直认为Interpolated Strings只是String.Format的语法糖,传给FromSQL的方法只是一个普通的字符串,已经移除了花括号,并把变量替换成了对应的值。FromSQL获取不到变量信息,怎么实现参数化查询的呢? OK,让我们从头看起吧。

什么是内插字符串 (Interpolated Strings)

内插字符串是C# 6.0 引入的新的语法,它允许在字符串中插入表达式。

var name = "world";
Console.WriteLine($"hello {name}");

这种方式相对与之前的string.Format或者string.Concat更容易书写,可读性更高。就这点,已经可以令大多数人满意了。事实上,它不仅仅是一个简单的字符串。

内插字符串 (Interpolated Strings) 是什么?

用代码来回答这个问题:

var name = "world";
string str1 = $"hello {name}";  //等于 var str1 = $"hello {name}";
IFormattable str2 =  $"hello {name}";
FormattableString str3 = $"hello {name}";

可以看出,Interpolated Strings 可以隐式转换为3种形式。实际上式编译器默默的为我们做了转换:

var name = "world";
string str1 = string.Format("hello {0}",name);  //等于 var str1 = $"hello {name}";
IFormattable str2 = FormattableStringFactory.Create("hello {0}",name);
FormattableString str3 = FormattableStringFactory.Create("hello {0}",name);
  • IFormattable 从.net Framwork 1 时代就有了,只有一个ToString方法,可以传入IFormatProvider来控制字符串的格式化。今天的主角不是他。
  • FormattableString 伴随Interpolated Strings引入的新类。

FormattableString 是什么?

先看一段代码

var name = "world";
FormattableString fmtString = $"hello {name}";
Console.WriteLine(fmtString.ArgumentCount);  //1
Console.WriteLine(fmtString.Format);  //hello {0}
foreach (var arg in fmtString.GetArguments())
{
    Console.WriteLine(arg);  //world
    Console.WriteLine(arg.GetType()); //System.String
}

可以看出FormattableString保存了Interpolated Strings的所有信息,所以EF Core 2.0能够以参数化的方式来执行SQL了。

EF Core 中的注意事项

因为隐式转换的原因,在使用EF Core的FromSql 方法和 ExecuteSqlCommand方法时,需要特别小心。一不留神就会调入陷阱。

var city = "London";

using (var context = CreateContext())
{
    //方法一,非参数化
    var sql = $" SELECT * FROM Customers WHERE City = {city}";
    context.Customers.FromSql(sql).ToArray();

    //方法二,参数化
    context.Customers.FromSql($" SELECT * FROM Customers WHERE City = {city}").ToArray();

   //方法三,参数化
    FormattableString fsql = $" SELECT * FROM Customers WHERE City = {city}";
    context.Customers.FromSql(fsql).ToArray();

    //方法四,非参数化
    var sql = " SELECT * FROM Customers WHERE City = @p0";
    context.Customers.FromSql(sql, city).ToArray();

}

第一种方法,因为sql的赋值被编译成String.Format方法的调用,返回的是字符串。sql变量传入FromSql方法时,又经过一次System.String 到Microsoft.EntityFrameworkCore.RawSqlString隐式转换。但sql变量本身已经丢失了参数信息,所以无法实现参数化的查询。
第四种方法, 也是Interpolated Strings -> String -> RawSqlString的转换过程,但因为变量是分开传入FromSql方法的,所以是以参数化的方式执行的。

其他

熟悉ES2015的同学可以看看JavaScript中的实现,Tagged template literals,这和Interpolated Strings 非常类似。

昨晚凌晨12点发帖,不知道为什么被移除首页了。感觉是篇幅不够的原因,重新加了点EF Core注意事项,但超过1小时没办法重新回首页了。七年来的第一篇文章,有点遗憾。希望大家喜欢。