dotnet 职业技术学院

博客

dotnet 职业技术学院

如何让 .NET Core 命令行程序接受密码的输入而不显示密码明文

dotnet 职业技术学院 更新于 2018-05-26,发布于 2018-05-26 16:51

如果是在 GUI 中要求用户输入密码,各 UI 框架基本都提供了用于输入密码的控件;在这些控件中,用户在输入密码的时候会显示掩码。然而对于控制台程序来说,并没有用于输入密码的原生方法。

本文将讲述一种在控制台中输入密码,并仅显示掩码的方法。


开始简单的程序

让我们开始一个简单的 .NET Core 控制台程序。

static void Main(string[] args)
{
    Console.Write("用户名: ");
    var userName = Console.ReadLine();
    Console.Write("密  码: ");
    var password = Console.ReadLine();
    Console.ReadKey();
}

初步的程序

密码直接显示,暴露无遗。而且,由于我们后面持续不断的有输出,控制台不会清除掉这些输出,所以密码会一直显示到缓冲区中——这显然是不能接受的

写一个让用户输入密码并显示掩码的方法

既然控制台本身并没有提供可以为密码进行掩码的方法,那么我们只能自己来写了:

public static SecureString ReadPassword(string mask = "*")
{
    var password = new SecureString();
    while (true)
    {
        var i = Console.ReadKey(true);
        if (i.Key == ConsoleKey.Enter)
        {
            Console.WriteLine();
            break;
        }

        if (i.Key == ConsoleKey.Backspace)
        {
            if (password.Length > 0)
            {
                password.RemoveAt(password.Length - 1);
                Console.Write("\b \b");
            }
        }
        else
        {
            password.AppendChar(i.KeyChar);
            Console.Write(mask);
        }
    }
    return password;
}

方法内部接受用户的输入——如果是回车,则确认;如果是退格,则删除一个字;其他情况下输出掩码。全程使用安全的字符串 SecureString,这种字符串是没有办法直接通过托管代码获取值的。

这时再输入字符串,将只能看到掩码——再也看不出来 walterlv 是不是一个逗比 了……

有掩码的输入

转换密码

当然,只有对安全级别比较高的库才会接受 SecureString 类型的字符串作为密码;一些简单的库只接受字符串类型的密码。那么在这些简单的库中我们如何才能得到普通的字符串呢?

可以使用 Marshal 来完成:

private static string ConvertToString(SecureString value)
{
    var valuePtr = IntPtr.Zero;
    try
    {
        valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
        return Marshal.PtrToStringUni(valuePtr);
    }
    finally
    {
        Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
    }
}

也可以间接使用 NetworkCredential 完成:

private static string ConvertToString(SecureString secureString)
{
    return new NetworkCredential(string.Empty, secureString).Password;
}

因为 NetworkCredential 的内部其实也是使用类似的方式获取到字符串的(详见 SecureStringHelper.CreateString - Reference Source)。

internal static string CreateString(SecureString secureString)
{
    string plainString;
    IntPtr bstr = IntPtr.Zero;

    if (secureString == null || secureString.Length == 0)
        return String.Empty;

    try
    {
        bstr = Marshal.SecureStringToBSTR(secureString);
        plainString = Marshal.PtrToStringBSTR(bstr);
    }
    finally
    {
        if (bstr != IntPtr.Zero)
            Marshal.ZeroFreeBSTR(bstr);
    }
    return plainString;
}

参考资料

win10 uwp 商业游戏

lindexi 发布于 2018-05-23

本文告诉大家去做一个商业游戏,游戏很简单,几乎没有什么技术。

C# 已知点和向量,求距离的点

lindexi 发布于 2018-05-23

已知一个点 P 和向量 v ,求在这个点P按照向量 v 运行距离 d 的点 B 。

神器如 dnSpy,无需源码也能修改 .NET 程序

dotnet 职业技术学院 发布于 2018-05-22

dnSpy0xd4d 开发的 .NET 程序调试神器。

说它是神器真的毫不为过!它能在完全没有源码的情况下即时调试程序,甚至还能修改程序!本文讲向大家介绍如何使用 dnSpy 修改 .NET 程序。


dnSpy 的主打功能是无需源码的调试,林德熙 有一篇文章 断点调试 Windows 源代码 介绍了这个方法。而本文主要说其另一项强大的功能 —— 修改程序集。

看看 dnSpy

dnSpy

dnSpy 长着一身 Visual Studio 一样的外观,调试的时候给你熟悉的感觉。

我们只需要讲我们需要调试或修改的程序集拖入左侧的程序集列表中即可(它会自动为我们把此程序集依赖的程序集也添加进来)。我把以前我写过的一个程序 ManipulationDemo 拖进来了。

实操修改程序集

现在我们来修改它,修改什么好呢?为了让效果明显一点,我决定在启动时弹一个窗口。于是我们展开进入到 App 类中。

App 类

然后在类中右键“Edit class (C#)”:

右键 -> 编辑类

在里面重写 OnStartup 方法。发现,它竟然连智能感知提示都做了!

重写 OnStartup 方法

MessageBox.Show

改完只需要点击一下右下角的编译,即可讲修改应用到我们刚刚打开的程序集中。

编译

保存修改的程序集

如果只是修改了可以立刻运行,那么充其量只是可以辅助调试。但是 dnSpy 是可以将程序集另存到本地的。

点击“File”->“Save Module”:

保存模块

为了以示区分,我写了一个新的名字:

保存

保存完之后,运行:

运行

我们会发现,我们刚刚新增的对话框已经弹出来了。“OK”之后原来的窗口才会显示出来。

发挥想象力的时候到了

既然有如此简单的修改程序集的方法,那么我们可以用来做什么事儿呢?用来做什么事儿呢?做什么事儿呢?什么事儿呢?事儿呢?呢?

想象力时间

顺便说一下,就算程序集被混淆了也难不倒它。

新 csproj 对 WPF/UWP 支持不太好?有第三方 SDK 可以用!MSBuild.Sdk.Extras

dotnet 职业技术学院 发布于 2018-05-22

自从微软推出 .NET Core 以来,新的项目文件格式以其优秀的可扩展性正吸引着更多项目采用。然而——微软官方的 WPF/UWP 项目模板依然还在采用旧的 csproj 格式!

这只是因为——官方 SDK 依然对 WPF/UWP 支持不够友好。


为什么要使用第三方的 SDK?

关于项目文件格式的迁移,我和 林德熙 都写过文章:

不过,这两篇文章中的迁移方法都是手动或半自动迁移的。而且迁移完毕之后,对新增的 WPF/UWP XAML 文件的支持非常不友好——新增的 XAML 文件是看不见的,除非手工去 csproj 文件中去掉自动生成的 Remove XAML 的代码。

这确实阻碍着我们在 WPF/UWP 项目中体会到新风格 csproj 的好处。

微软在 Build 2018 大会上宣布,WPF/UWP 将能够在 .NET Core 3 中运行。想必,微软会为未来版本的 Microsoft.NET.Sdk 这样的官方 SDK 添加更多的 WPF/UWP 这类格式的支持吧!即便没有这样的原生支持,想必也会提供官方的扩展方案。

但在此之前呢?感谢小伙伴 KodamaSakuno (神樹桜乃) 提醒我第三方 SDK 的存在 —— MSBuild.Sdk.Extras。我想,在 .NET Core 3 推出之前,这是一种不错的中转方案。既能体会到新风格 csproj 格式的好处,也能在将来 .NET Core 3 官方支持后较快地迁移成官方版本。

如何使用 MSBuild.Sdk.Extras

虽说是第三方 SDK,但实际使用的方便程度却如官方般简洁!只需要将 SDK 替换成 MSBuild.Sdk.Extras/1.5.4 即可。1.5.4 是目前 MSBuild.Sdk.Extras 在 NuGet 上的最新版本,建议访问 NuGet Gallery - MSBuild.Sdk.Extras 使用最新稳定版本。

以下是最简同时支持 WPF 和 UWP 双框架的代码:

<Project Sdk="MSBuild.Sdk.Extras/1.5.4">
  <PropertyGroup>
    <TargetFrameworks>net47;uap10.0</TargetFrameworks>
  </PropertyGroup>
</Project>

没错,真的如此简单!在我们猜测的 .NET Core 3 支持 WPF/UWP 项目格式之前,这应该算是最简单的迁移方案了!

至于项目结构的效果,可以看下图所示:

net47 和 uap10.0

相比于此前的手工迁移,使用此新格式创建出来的 XAML 文件是可见的,而且 .xaml.cs 也是折叠在 .xaml 之下,且能正常编译!(当然,咱们还得考虑 UWP 和 WPF 在 XAML 书写上的细微差异)

官方提供了更多的使用方法,例如更简单的是安装 NuGet 包,而不修改 SDK。详见:onovotny/MSBuildSdkExtras: Extra properties for MSBuild SDK projects

参考资料

如何编写基于 Microsoft.NET.Sdk 的跨平台的 MSBuild Target

dotnet 职业技术学院 发布于 2018-05-20

我之前写过一篇 理解 C# 项目 csproj 文件格式的本质和编译流程,其中,Target 节点就是负责编译流程的最关键的节点。但因为篇幅限制,那篇文章不便详说。于是,我在本文说说 Target 节点。


Target 的节点结构

<Target> 内部几乎有着跟 <Project> 一样的节点结构,内部也可以放 PropertyGroupItemGroup,不过还能放更加厉害的 Task。按照惯例,我依然用思维导图将节点结构进行了总结:

Target 的节点结构
▲ 上面有绿线和蓝线区分,仅仅是因为出现了交叉,怕出现理解歧义

<Hash><WriteCodeFragment> 都是 Task。我们可以看到,Task 是多种多样的,它可以占用一个 xml 节点。而本例中,WriteCodeFragment Task 就是生成代码文件,并且将生成的文件作为一项 Compile 的 Item 和 FileWrites 的 Item。在 理解 C# 项目 csproj 文件格式的本质和编译流程 中我们提到 ItemGroup 的节点,其作用由 Target 指定。所有 Compile 会在名为 CoreCompileTarget 中使用,而 FileWrites 在 Microsoft.NET.Sdk 的多处都生成了这样的节点,不过目前从我查看到的全部 Microsoft.NET.Sdk 中,发现内部并没有使用它。

Target 执行的时机和先后顺序

既然 <Target> 内部节点很大部分跟 <Project> 一样,那区别在哪里呢?<Project> 里的 <PropertyGroup><ItemGroup> 是静态的状态,如果使用 Visual Studio 打开项目,那么所有的状态将会直接在 Visual Studio 的项目文件列表和项目属性中显示;而 <Target> 内部的 <PropertyGroup><ItemGroup> 是在编译期间动态生成的,不会在 Visual Studio 中显示;不过,它为我们提供了一种在编译期间动态生成文件或属性的能力。

总结起来就是——Target 是在编译期间执行的

不过,同样是编译期间,那么多个 Target,它们之间的执行时机是怎么确定的呢?

一共有五个属性决定了 Target 之间的执行顺序:

  • Project 的属性
    • InitialTargets 项目初始化的时候应该执行的 Target
    • DefaultTargets 如果没有指定执行的 Target,那么这个属性将指定执行的 Target
  • Target 的属性
    • DependsOnTargets 在执行此 Target 之前应该执行的另一个或多个 Target
    • BeforeTargets 这是 MSBuild 4.0 新增的,指定应该在另一个或多个 Target 之前执行
    • AfterTargets 这也是 MSBuild 4.0 新增的,指定应该在另一个或多个 Target 之后执行

通过指定这些属性,我们的 Target 能够被 MSBuild 自动选择合适的顺序进行执行。例如,当我们希望自定义版本号,那么就需要赶在我们此前提到的 GenerateAssemblyInfo 之前执行。

Microsoft.NET.Sdk 为我们提供的现成可用的 Task

有 Microsoft.NET.Sdk 的帮助,我们可以很容易地编写自己的 Target,因为很多功能它都帮我们实现好了,我们排列组合一下就好。

提供的 Task 还有更多,如果上面不够你写出想要的功能,可以移步至官方文档翻阅:MSBuild Task Reference - Visual Studio - Microsoft Docs

使用自己写的 Task

我有另外的一篇文章来介绍如何创建一个基于 MSBuild Task 的跨平台的 NuGet 工具包 - 吕毅。如果希望自己写 Ta

差量编译

如果你认为自己写的 Target 执行比较耗时,那么就可以使用差量编译。我另写了一篇文章专门来说 Target 的差量编译:每次都要重新编译?太慢!让跨平台的 MSBuild/dotnet build 的 Target 支持差量编译 - 吕毅


参考资料

WPF 使用RPC调用其他进程

lindexi 发布于 2018-05-19

如果在 WPF 需要用多进程通信,一个推荐的方法是 WCF ,因为 WCF 是 RPC 计算。先来讲下 RPC (Remote Procedure Call) 远程过程调用,他是通过特定协议,包括 tcp 、http 等对其他进程进行调用的技术。详细请看百度

从以前的项目格式迁移到 VS2017 新项目格式

lindexi 发布于 2018-05-18

以前的项目格式使用的是 csproj 的格式,但是 .net core 支持使用 project.json 格式的项目文件,后来还是决定不使用这个格式。 VS2017 的项目格式更好读、更简单而且减少了 git 冲突。 本文来告诉大家如何从 VS2015 和以前的项目格式修改为 VS2017 项目格式。

WPF 使用 SharpDX

lindexi 发布于 2018-05-04

本文告诉大家如何在 WPF 使用 SharpDX ,只是入门。

WPF 使用 SharpDX 在 D3DImage 显示

lindexi 发布于 2018-05-04

本文告诉大家如何使用 SharpDX 在 D3DImage 显示。在上一篇WPF 使用 SharpDX只是使用窗口,也就是无法使用其它的 WPF 控件。所以这一篇就来告诉大家如何使用 WPF 控件和使用 SharpDX 。

WPF 使用 SharpDx 异步渲染

lindexi 发布于 2018-05-04

本文告诉大家如何通过 SharpDx 进行异步渲染,但是因为在 WPF 是需要使用 D3DImage 画出来,所以渲染只是画出图片,最后的显示还是需要 WPF 在他自己的主线程渲染。

win10 uwp 渲染原理 DirectComposition 渲染

lindexi 发布于 2018-04-23

本文来告诉大家一个新的技术DirectComposition,在 win7 之后(实际上是 vista),微软正在考虑一个新的渲染机制。

WPF 使用 Direct2D1 画图入门

lindexi 发布于 2018-04-20

本文来告诉大家如何在 WPF 使用 D2D 画图。

WPF 使用 Direct2D1 画图 绘制基本图形

lindexi 发布于 2018-04-20

本文来告诉大家如何在 Direct2D1 绘制基本图形,包括线段、矩形、椭圆

Moq基础(六)

dotnet 职业技术学院 发布于 2018-04-19

这一章是最后一讲,我们讲一下Moq中值得注意的小技巧,以及对Moq使用的评价


MockBehavior:伪对象行为

在使用Moq创建伪对象时,可以在构造函数里传入MockBehavior

MockBehavior有了两种:

  • Loose:默认行为,任何未显示伪造的方法和属性都会返回默认值,且不会抛出异常。
  • Strict: 任何调用都需要显式Setup,并使用VerifyAll验证。

什么意思呢?如下图所示,你要验证公共方法A,A中做了T.B()T.C()两件事。

public void A()
{
   Name = T.B();
   Age = T.C();
}

Loose允许你测试A时只伪造方法B,并验证Name状态,

Strict要求必须同时伪造方法BC,否则会抛出异常。

同样,另一方面,如果后期方法A又调用了一个T.D(),那么前者的测试会过,后者会失败,提醒用户修改测试。

至于选择,我个人是没有什么偏好,大家自己喜欢就好。


CallBase :调用基类方法

如果你期望某些方法调用原类型虚方法的默认实现,可以使用

var mock = new Mock<IFoo> { CallBase = true };

这个在测试有一大堆虚方法的基类时十分有效,不用为了测一个方法,伪造过多其他方法。


SetupSequence :伪造序列

如果你期望,一个方法每次调用返回值都不同,那么可以试试下面的写法。

var mock = new Mock<IFoo>();
mock.SetupSequence(f => f.GetCount())
    .Returns(3)  // will be returned on 1st invocation
    .Returns(2)  // will be returned on 2nd invocation
    .Returns(1)  // will be returned on 3rd invocation
    .Returns(0)  // will be returned on 4th invocation
    .Throws(new InvalidOperationException());  // will be thrown on 5th invocation

值得注意的是如果你期望,该方法被调用4次,那么一定要在第5次(最后一句)

Throws(new InvalidOperationException()) 中断测试,否则会返回Null


Protected():伪造Protected成员

如果需要测试Protected成员的行为,你可以使用下面的方式(不过到了这一步,可能已经意味着你的代码需要再审查一遍结构是否合理了)

//无参数
mock.Protected()
     .Setup<int>("Execute")
     .Returns(5);
//带参数
mock.Protected()
    .Setup<string>("Execute",
        ItExpr.IsAny<string>())
    .Returns(true);

值得注意的是,因为Protected成员“不可见”,因此只能使用字符串进行处理


Internal程序集可见

有时候我们会需要测试一些Internal的类和方法,此时我们不仅需要对测试项目可见,还要对测试框架的生成器可见。

因此需要在AssemblyInfo.cs添加

[assembly:InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly:InternalsVisibleTo("YourTestProject")]

ok,以上是Moq基础的全部知识性内容。

下面说说对Moq的看法。

Moq作为一个受限的单元测试框架,做到了免费,简单,易用。

应该说能够满足大部分的应用需求。

对于一个项目来说,如果Moq能够满足使用需求,那这个项目一定是SOLID

当然,对于一些遗留代码来说,通常需要非受限框架对他进行支持(例如typemock isolator,MS Fakes)

不足之处是,Moq的概念存在一些混淆,往往容易将初学者带偏,一些API设计也有待商榷。

但是,Moq只是我们书写单元测试的工具,他真正的威力在于使用者。

兵无常势,水无常形。框架总会更新,创建测试的能力才是需要保留的。