dotnet 职业技术学院 发布于 2019-01-06
一个工程师团队使用 Slack 进行团队协作比 QQ / 微信流的效率高多啦。除了基本的 IM 之外,它的扩展性也是非常重要的一点。
本文介绍 Slack 的开发入门:Incoming Webhooks 篇。
如果你已经创建了一个 Slack 应用,可以跳过这一节。
在这里 https://api.slack.com/apps/new 创建一个新的 Slack 应用:

填写完两个信息之后,你就可以选择五种不同的应用类型:

本文,我们选中 Incoming Webhooks。
或者如果这已经是你创建好的应用了,可以左边的列表中选择 Incoming Webhooks。
然后按一下右上角的激活按钮,使得 Incoming Webhooks 功能激活。

如果顶部有提示因为权限问题需要重新安装,那么就点进去重新安装。

继续把网页往下滑,点击 [Add New Webhook to Workspace]。

然后选择需要发消息的 Channel:

这时,页面还会继续回到添加 Url 的地方,但示例 Demo 已经换上了真实的 Url,而且你可以复制到剪贴板。

为了迅速验证,我们可以使用 Postman 来发送这条消息。
关于下载和使用 Postman,你可以参考我的另一篇博客:使用 Postman 调试 ASP.NET Core 开发的 API。
填写要 POST 的 Url,然后在消息的 Body 中填写 JSON 格式的消息内容:
{
"text": "Hi! 给你个 **任务** 玩玩。"
}

这时,点击 Send 按钮,消息发送成功。
于是我的 Slack 通道中收到了一条来自这个应用发来消息:

当你可以随时向 Slack 的某个通道发送消息之后,你可以用来做什么呢?
你可以定时发送团队的代码审查发送发现的问题,可以发送自动化编译失败的信息,可以发送每周的任务计划和总结,等等。
当然,Slack 上本身就提供了大量的应用可以直接下载安装,自己做开发是解决更定制化的需求。
参考资料
lindexi 发布于 2018-08-10
本文告诉大家如何使用这个博客主题搭建自己的博客。这个主题是由 吕毅 - walterlv大神基于hcz-jekyll-blog 修改出来的,可以用于手机端和pc端。
刘俊杰 发布于 2018-06-16
介绍一种/两种可以提前做点什么事情的方法。
在UI线程中执行耗时操作,如读取大文件,为了不造成UI卡顿,常采用异步加载的方式,即 async/await 。
通常的写法是这样的:
private async Task DoSomething()
{
// init work
await Task.Run(()=>
{
// IO
});
// after work
}
这里虽然解决了UI卡顿的问题,但需要得到最终结果(即 after work 中的代码执行),仍然需要等待。
在部分场景中,如果可以提前执行耗时代码,则可以减少等待时间。
设想的代码类似与这样:
private void EarlierWork()
{
// 提前执行耗时操作,如从一个大文件中查找数据。
}
priavte void DoSomething()
{
// init
// 获取之前提前执行的结果,如果执行还没有结束,则异步等待执行完成,获取结果后继续执行。
// after work
}
这里适用的场景,需要是 EarlierWork 比 DoSomething 先执行一段时间,而且在 EasilerWork 执行时,就已经可以获取相关的数据,不缺失必要的参数。(这样的场景……额,可能不并不多)
这里的实现有两种方式,都是基于 C# 的 async/await 异步模型。
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
private TaskAwaiter<string> _getStringDataAwaiter;
private void EarlierWork()
{
// 提前执行耗时操作,如从一个大文件中查找数据。
_getStringDataAwaiter = Task.Run(()=>
{
// 耗时操作
return "Data";
}).GetAwaiter();
}
private async Task DoSomething()
{
// init
// 获取之前提前执行的结果,如果执行还没有结束,则异步等待执行完成,获取结果后继续执行。
string data = await Task.Run(()=>_getStringDataAwaiter.GetResult());
// after work
}
注意,这里需要使用 Task.Run() 的方式调用获取结果的 GetResult 方法,否则会是调用线程卡顿,即,如果调用线程是UI,则会造成UI卡顿。
需要注意的是,TaskAwaiter 这个API是提供给编译器使用的,不建议在生产环境中使用。
The System.Runtime.CompilerServices namespace provides functionality for compiler writers who use managed code to specify attributes in metadata that affect the run-time behavior of the common language runtime.
This API supports the product infrastructure and is not intended to be used directly from your code.
TaskAwaiter Struct (System.Runtime.CompilerServices) | Microsoft Docs
推荐的实现方式。
using System.Threading.Tasks;
private Task<string> _getStringDataTask;
private void EarlierWork()
{
// 提前执行耗时操作,如从一个大文件中查找数据。
_getStringDataTask = CreateGetDataTask();
}
private async Task<string> CreateGetDataTask()
{
return await Task.Run(()=>
{
// 耗时操作
return "Data";
}).ConfigureAwait(false); // 必须写 ConfigureAwait(false)
// 此处的代码将不会返回原线程执行;不过这里一般不写代码。
}
private void DoSomething()
{
// init
// 获取之前提前执行的结果,如果执行还没有结束,则异步等待执行完成,获取结果后继续执行。
string data = _getStringDataTask.Result;
// after work
}
这种实现方式需要注意死锁问题,如果不使用 ConfigureAwait(false) ,则会造成死锁。
关于死锁的更多内容,可以看这里:
END
lvyi 更新于 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;
}
lvyi 发布于 2018-05-22
dnSpy 是 0xd4d 开发的 .NET 程序调试神器。
说它是神器真的毫不为过!它能在完全没有源码的情况下即时调试程序,甚至还能修改程序!本文讲向大家介绍如何使用 dnSpy 修改 .NET 程序。
dnSpy 的主打功能是无需源码的调试,林德熙 有一篇文章 断点调试 Windows 源代码 介绍了这个方法。而本文主要说其另一项强大的功能 —— 修改程序集。

dnSpy 长着一身 Visual Studio 一样的外观,调试的时候给你熟悉的感觉。
我们只需要讲我们需要调试或修改的程序集拖入左侧的程序集列表中即可(它会自动为我们把此程序集依赖的程序集也添加进来)。我把以前我写过的一个程序 ManipulationDemo 拖进来了。
现在我们来修改它,修改什么好呢?为了让效果明显一点,我决定在启动时弹一个窗口。于是我们展开进入到 App 类中。

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

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


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

如果只是修改了可以立刻运行,那么充其量只是可以辅助调试。但是 dnSpy 是可以将程序集另存到本地的。
点击“File”->“Save Module”:

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

保存完之后,运行:

我们会发现,我们刚刚新增的对话框已经弹出来了。“OK”之后原来的窗口才会显示出来。
既然有如此简单的修改程序集的方法,那么我们可以用来做什么事儿呢?用来做什么事儿呢?做什么事儿呢?什么事儿呢?事儿呢?呢?
想象力时间
顺便说一下,就算程序集被混淆了也难不倒它。
lvyi 发布于 2018-05-22
自从微软推出 .NET Core 以来,新的项目文件格式以其优秀的可扩展性正吸引着更多项目采用。然而——微软官方的 WPF/UWP 项目模板依然还在采用旧的 csproj 格式!
这只是因为——官方 SDK 依然对 WPF/UWP 支持不够友好。
关于项目文件格式的迁移,我和 林德熙 都写过文章:
不过,这两篇文章中的迁移方法都是手动或半自动迁移的。而且迁移完毕之后,对新增的 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 官方支持后较快地迁移成官方版本。
虽说是第三方 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 项目格式之前,这应该算是最简单的迁移方案了!
至于项目结构的效果,可以看下图所示:

相比于此前的手工迁移,使用此新格式创建出来的 XAML 文件是可见的,而且 .xaml.cs 也是折叠在 .xaml 之下,且能正常编译!(当然,咱们还得考虑 UWP 和 WPF 在 XAML 书写上的细微差异)
官方提供了更多的使用方法,例如更简单的是安装 NuGet 包,而不修改 SDK。详见:onovotny/MSBuildSdkExtras: Extra properties for MSBuild SDK projects。
lvyi 发布于 2018-05-20
我之前写过一篇 理解 C# 项目 csproj 文件格式的本质和编译流程,其中,Target 节点就是负责编译流程的最关键的节点。但因为篇幅限制,那篇文章不便详说。于是,我在本文说说 Target 节点。
<Target> 内部几乎有着跟 <Project> 一样的节点结构,内部也可以放 PropertyGroup 和 ItemGroup,不过还能放更加厉害的 Task。按照惯例,我依然用思维导图将节点结构进行了总结:

▲ 上面有绿线和蓝线区分,仅仅是因为出现了交叉,怕出现理解歧义
<Hash> 和 <WriteCodeFragment> 都是 Task。我们可以看到,Task 是多种多样的,它可以占用一个 xml 节点。而本例中,WriteCodeFragment Task 就是生成代码文件,并且将生成的文件作为一项 Compile 的 Item 和 FileWrites 的 Item。在 理解 C# 项目 csproj 文件格式的本质和编译流程 中我们提到 ItemGroup 的节点,其作用由 Target 指定。所有 Compile 会在名为 CoreCompile 的 Target 中使用,而 FileWrites 在 Microsoft.NET.Sdk 的多处都生成了这样的节点,不过目前从我查看到的全部 Microsoft.NET.Sdk 中,发现内部并没有使用它。
既然 <Target> 内部节点很大部分跟 <Project> 一样,那区别在哪里呢?<Project> 里的 <PropertyGroup> 和 <ItemGroup> 是静态的状态,如果使用 Visual Studio 打开项目,那么所有的状态将会直接在 Visual Studio 的项目文件列表和项目属性中显示;而 <Target> 内部的 <PropertyGroup> 和 <ItemGroup> 是在编译期间动态生成的,不会在 Visual Studio 中显示;不过,它为我们提供了一种在编译期间动态生成文件或属性的能力。
总结起来就是——Target 是在编译期间执行的。
不过,同样是编译期间,那么多个 Target,它们之间的执行时机是怎么确定的呢?
一共有五个属性决定了 Target 之间的执行顺序:
InitialTargets 项目初始化的时候应该执行的 TargetDefaultTargets 如果没有指定执行的 Target,那么这个属性将指定执行的 TargetDependsOnTargets 在执行此 Target 之前应该执行的另一个或多个 TargetBeforeTargets 这是 MSBuild 4.0 新增的,指定应该在另一个或多个 Target 之前执行AfterTargets 这也是 MSBuild 4.0 新增的,指定应该在另一个或多个 Target 之后执行通过指定这些属性,我们的 Target 能够被 MSBuild 自动选择合适的顺序进行执行。例如,当我们希望自定义版本号,那么就需要赶在我们此前提到的 GenerateAssemblyInfo 之前执行。
有 Microsoft.NET.Sdk 的帮助,我们可以很容易地编写自己的 Target,因为很多功能它都帮我们实现好了,我们排列组合一下就好。
Copy 复制文件 Copy TaskMove 移动文件 Move TaskDelete 删除文件Message 显示一个输出信息(我在 如何创建一个基于 MSBuild Task 的跨平台的 NuGet 工具包 中利用这个进行调试)Warning 显示一个警告信息Error 报错(这样,编译就会以错误结束)CombinePath, ConvertToAbsolutePath 拼接路径,转成绝对路径CreateItem, CreateProperty 创建项或者属性Csc 调用 csc.exe 编译 Csc TaskMSBuild 编译一个项目 MSBuild TaskExec 执行一个外部命令(我在 如何创建一个基于命令行工具的跨平台的 NuGet 工具包 一文中利用到了这个 Task 执行命令)WriteCodeFragment 生成一段代码 WriteCodeFragment TaskWriteLinesToFile 向文件中写文字 WriteLinesToFile Task提供的 Task 还有更多,如果上面不够你写出想要的功能,可以移步至官方文档翻阅:MSBuild Task Reference - Visual Studio - Microsoft Docs。
我有另外的一篇文章来介绍如何创建一个基于 MSBuild Task 的跨平台的 NuGet 工具包 - 吕毅。如果希望自己写 Ta
如果你认为自己写的 Target 执行比较耗时,那么就可以使用差量编译。我另写了一篇文章专门来说 Target 的差量编译:每次都要重新编译?太慢!让跨平台的 MSBuild/dotnet build 的 Target 支持差量编译 - 吕毅。
lindexi 发布于 2018-05-19
如果在 WPF 需要用多进程通信,一个推荐的方法是 WCF ,因为 WCF 是 RPC 计算。先来讲下 RPC (Remote Procedure Call) 远程过程调用,他是通过特定协议,包括 tcp 、http 等对其他进程进行调用的技术。详细请看百度
lindexi 发布于 2018-05-18
以前的项目格式使用的是 csproj 的格式,但是 .net core 支持使用 project.json 格式的项目文件,后来还是决定不使用这个格式。 VS2017 的项目格式更好读、更简单而且减少了 git 冲突。 本文来告诉大家如何从 VS2015 和以前的项目格式修改为 VS2017 项目格式。
lindexi 发布于 2018-05-04
本文告诉大家如何使用 SharpDX 在 D3DImage 显示。在上一篇WPF 使用 SharpDX只是使用窗口,也就是无法使用其它的 WPF 控件。所以这一篇就来告诉大家如何使用 WPF 控件和使用 SharpDX 。
lindexi 发布于 2018-05-04
本文告诉大家如何通过 SharpDx 进行异步渲染,但是因为在 WPF 是需要使用 D3DImage 画出来,所以渲染只是画出图片,最后的显示还是需要 WPF 在他自己的主线程渲染。
lindexi 发布于 2018-04-23
本文来告诉大家一个新的技术DirectComposition,在 win7 之后(实际上是 vista),微软正在考虑一个新的渲染机制。