.net core – 清风亦平凡 https://www.mlplus.net 关注IT世界,记录平凡生活 Wed, 05 Oct 2022 14:04:53 +0000 zh-CN hourly 1 https://wordpress.org/?v=6.4.3 使用ABP框架UI找不到文件的异常 https://www.mlplus.net/2022/10/05/abp-install-libs/ https://www.mlplus.net/2022/10/05/abp-install-libs/#respond Wed, 05 Oct 2022 14:03:03 +0000 https://www.mlplus.net/?p=4000 背景

自己的一个小应用使用了ABP框架最新内容创建了一个项目模板,为了快速的完成第一版就是用来ABP自带的UI框架。在测试项目模板正常与否的时候发现关于UI方面的异常信息,第一次使用ABP官方提供的UI框架,所以先在此做一下记录。

以下是异常信息:

AbpException: Could not find the bundle file ‘/libs/abp/core/abp.css’ for the bundle ‘LeptonXLite.Global’!

ABP框架UI异常图片

解决方法

安装ABP框架UI依赖包abp install-libs



转载请注明:清风亦平凡 » 使用ABP框架UI找不到文件的异常

]]>
https://www.mlplus.net/2022/10/05/abp-install-libs/feed/ 0
.Net Core获取Window系统机器码 https://www.mlplus.net/2022/10/04/dot-net-core-machinecode/ https://www.mlplus.net/2022/10/04/dot-net-core-machinecode/#respond Tue, 04 Oct 2022 06:27:45 +0000 https://www.mlplus.net/?p=3989 背景

因为朋友想做一个软件认证相关的东西,所以考虑到关于电脑唯一标识机器码的问题。关于机器码之前并没有真正实现过,也只是了解大概的原理。今天就做一下简单记录,以便以后使用。

机器码

机器码指的是软件根据计算机的硬件信息,例如:CPU、内存、主板序列号等,按照一定的算法生成的一串无规律的字符串,并且在不同计算机上生成的机器码是不一样的,因为每台计算机的硬件信息不一样。机器码,也叫序列号认证码注册申请码等。

代码实现


using System.Management;
using System.Runtime.InteropServices;

namespace HonourWorld.Core.Common
{
    public sealed class MachineCode
    {
        private readonly static MachineCode machineCode=new MachineCode();
        public static string GetMachineCodeString()
        {
            string machineCodeString = $"PC.{machineCode.GetCpuInfo()}.{machineCode.GetHDid()}.{machineCode.GetDiskSerialNumber()}.{machineCode.GetMacAddress()}.{machineCode.GetBiosSerialNumber()}";
            return machineCodeString;
        }
        private MachineCode()
        { 
        }
        ///   <summary>
        ///   获取cpu序列号
        ///   </summary>
        ///   <returns> string </returns>
        public string GetCpuInfo()
        {
            string? cpuInfo = "";
            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    using (ManagementClass cimobject = new ManagementClass("Win32_Processor"))
                    {
                        ManagementObjectCollection moc = cimobject.GetInstances();
                        foreach (ManagementObject mo in moc)
                        {
                            if (mo == null || mo.Properties == null)
                            {
                                continue;
                            }
                            var info = mo.Properties["ProcessorId"];
                            if (info!=null)
                            {
                                cpuInfo = info.Value.ToString();
                            }
                            mo?.Dispose();
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return (cpuInfo??"").Trim();
        }
        ///   <summary>
        ///   获取硬盘ID
        ///   </summary>
        ///   <returns> string </returns>
        public string GetHDid()
        {
            string HDid = "";
            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    using (ManagementClass cimobject1 = new ManagementClass("Win32_DiskDrive"))
                    {
                        ManagementObjectCollection moc1 = cimobject1.GetInstances();
                        foreach (ManagementObject mo in moc1)
                        {
                            HDid = (string)mo.Properties["Model"].Value;
                            mo.Dispose();
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return HDid.Trim();
        }

        /// <summary>
        /// 获得硬盘序列号
        /// </summary>
        /// <returns></returns>
        public string GetDiskSerialNumber()
        {
            string serialNumber = "";
            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    using (ManagementClass bios = new ManagementClass("Win32_PhysicalMedia"))
                    {
                        ManagementObjectCollection moc1 = bios.GetInstances();
                        foreach (ManagementObject mo in moc1)
                        {
                            serialNumber = (string)mo.Properties["SerialNumber"].Value;
                            mo.Dispose();
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return serialNumber.Trim();
        }

        ///   <summary>
        ///   获取网卡硬件地址
        ///   </summary>
        ///   <returns> string </returns>
        public string GetMacAddress()
        {
            string mocAddress = "";
            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    using (ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration"))
                    {
                        ManagementObjectCollection moc2 = mc.GetInstances();
                        foreach (ManagementObject mo in moc2)
                        {
                            if ((bool)mo["IPEnabled"]&& mo["MacAddress"]!=null)
                            {
                                mocAddress = (string)mo["MacAddress"];
                            }
                            mo.Dispose();
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return mocAddress.Trim();
        }

        /// <summary>
        /// 获得主板序列号
        /// </summary>
        /// <returns></returns>
        public string GetBiosSerialNumber()
        {

            string serialNumber = "";
            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    using (ManagementClass bios = new ManagementClass("Win32_BaseBoard"))
                    {
                        ManagementObjectCollection moc1 = bios.GetInstances();
                        foreach (ManagementObject mo in moc1)
                        {
                            serialNumber = (string)mo.Properties["SerialNumber"].Value;
                            mo.Dispose();
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return serialNumber.Trim();
        }

    }
}

调用方式


        private void btnIntroduction_Click(object sender, EventArgs e)
        {
            string machineCode = Honour.MachineCode.GetMachineCodeString();
            machineCode = EncryptionSha.GetSha1HashString(machineCode);
            machineCode = Regex.Replace(machineCode, @"\w{8}", "$0-", RegexOptions.IgnoreCase).TrimEnd('-');
            var result = MessageBox.Show($"机器码:{machineCode}", "提示", MessageBoxButtons.OK);
        }

机器码是使用CPU序列号硬盘ID硬盘序列号MAC地址主板序列号进行拼接的字符串。由于电脑硬件信息拼接而成的无规则字符串,所以显得杂乱。为了让其看上去比较规范就对拼接的取散列信息,散列算法有很多种,例如:MD5SHA1等。这里使用SHA1来实现需求。

机器码

示例下载

.Net Core机器码示例
以上获取机器相关信息的方式方法只支持Windows系统。



转载请注明:清风亦平凡 » .Net Core获取Window系统机器码

]]>
https://www.mlplus.net/2022/10/04/dot-net-core-machinecode/feed/ 0
.NET CORE实现SHA1 https://www.mlplus.net/2022/09/24/net-core-sha1/ https://www.mlplus.net/2022/09/24/net-core-sha1/#comments Sat, 24 Sep 2022 14:34:21 +0000 https://www.mlplus.net/?p=3956 背景

因业务需求,需要保存一些指定的字符串,但这些字符串可能相同。为了避免重复存储造成数据大量冗余,所以需要对这些字符串进行一些散列计算。使这一些相同的字符串产生唯一的标识,实现业务上的需求。关于字符串散列算法还是比较多的,例如:MD2MD4MD5Sha1Sha256Sha512等等,在结合字符串散列碰撞以及散列最终长度考虑,最后选中Sha1作为此次散列算法。

SHA-1

(英语:Secure Hash Algorithm 1,中文名:安全散列算法1)是一种密码散列函数,美国国家安全局设计,并由美国国家标准技术研究所(NIST)发布为联邦数据处理标准(FIPS)。SHA-1可以生成一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。

.NET CORE 实现 SHA1

以下是使用.Net Core自带类库实现Sha1的简单方法。

    
public static class EncryptionSha
{
        public static byte[] GetSha1Hash(string inputString)
        {
            var sha1 = SHA1.Create();
            byte[] data = sha1.ComputeHash(Encoding.UTF8.GetBytes(inputString));
            sha1.Dispose();
            return data;
        }

        public static string GetSha1HashString(string inputString)
        {
            StringBuilder sb = new StringBuilder();
            byte[] hashByte = GetSha1Hash(inputString);
            foreach (byte b in hashByte)
            {
                sb.Append(b.ToString("X2"));
            }
            return sb.ToString();
        }
    }

使用方式


using System;

namespace TestSha1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(EncryptionSha.GetSha1HashString("Hello World!"));
            Console.ReadKey();
        }
    }
}
.net core使用Sha1散列调用方式

示例

SHA1散列



转载请注明:清风亦平凡 » .NET CORE实现SHA1

]]>
https://www.mlplus.net/2022/09/24/net-core-sha1/feed/ 2
.NET CORE引用Aspose.Words的异常Could not load file or assembly https://www.mlplus.net/2022/08/01/net-core-could-not-load-file-or-assembly/ https://www.mlplus.net/2022/08/01/net-core-could-not-load-file-or-assembly/#respond Mon, 01 Aug 2022 07:22:50 +0000 https://www.mlplus.net/?p=3897 背景

引用了一个第三方的dll库,编译无任何异常提示,但是运行就报异常。自从有了Nuget基本没有再使用直接引用dll这种方式了。经过确认dll也设置了输出到目录,在 bin目录也的确存在此dll库。

异常信息

Could not load file or assembly ‘Aspose.Words, Version=20.6.0.0, Culture=neutral, PublicKeyToken=716fcc553a201e56’. 系统找不到指定的文件。
System.IO.FileNotFoundException: Could not load file or assembly ‘Aspose.Words, Version=20.6.0.0, Culture=neutral, PublicKeyToken=716fcc553a201e56’. 系统找不到指定的文件。
File name: ‘Aspose.Words, Version=20.6.0.0, Culture=neutral, PublicKeyToken=716fcc553a201e56’

.NET CORE引用Aspose.Words的异常Could not load file or assembly-第0张图片

解决

查看下引用dll库的项目文件,项目文件中少了dll的相关版本号。


<Reference Include="Aspose.Words">

在这条记录中添加对应的版本号后,问题得到解决!如下:


<Reference Include="Aspose.Words" Version="20.6.0.0">



转载请注明:清风亦平凡 » .NET CORE引用Aspose.Words的异常Could not load file or assembly

]]>
https://www.mlplus.net/2022/08/01/net-core-could-not-load-file-or-assembly/feed/ 0
.net core在Linux系统报Gdip异常的问题 https://www.mlplus.net/2022/04/08/net-core-linux-system-gdip-error/ https://www.mlplus.net/2022/04/08/net-core-linux-system-gdip-error/#respond Fri, 08 Apr 2022 01:00:00 +0000 https://www.mlplus.net/?p=3844 背景

.net core 应用中使用了Excel文件导出,发现Excel导出失败,导出的操作出现了异常。看了一下是Gdip的这个异常。这个异曾经见到过,记得也很快解决了,不晓得为什么又会出现呢?仔细看了一下,详细错误还是有点差别。经过确认上一次安装的libc6-devlibgdiplus是存在的,并没有丢失或者损坏。

根据异常信息,基本可以确定是引用System.Drawing.Common类库引起的问题,经过排查发现引用的Excel组件Magicodes.IE.Excel.Abp包中有使用System.Drawing.Common。前几天还好好的,现在为何System.Drawing.Common不支持非Windows平台呢?只是确定引用的System.Drawing.Common库的版本是6.0,其他的就没有太多线索。

最后又看一遍异常信息,根据异常提示打开所提示的网址看一下:

https://aka.ms/systemdrawingnonwindows

提示中断性变更,仅在Windows上支持 System.Drawing.Common

System.Drawing.Common NuGet 包现在被归为 Windows 特定的库。 在为非 Windows 操作系统编译时,平台分析器会在编译时发出警告。

在非 Windows 操作系统上,除非设置了运行时配置开关,否则将引发 TypeInitializationException 异常,其中 PlatformNotSupportedException 作为内部异常。

旧行为

.NET 6 之前,使用 System.Drawing.Common 包不会产生任何编译时警告,也不会引发任何运行时异常。

新行为

.NET 6 开始,当为非 Windows 操作系统编译引用代码时,平台分析器会发出编译时警告。 此外,除非设置了配置选项,否则将引发以下运行时异常:

.net core在Linux系统报Gdip异常的问题-第0张图片

The type initializer for ‘Gdip’ threw an exception.
System.TypeInitializationException: The type initializer for ‘Gdip’ threw an exception.
—> System.PlatformNotSupportedException: System.Drawing.Common is not supported on non-Windows platforms. See https://aka.ms/systemdrawingnonwindows for more information.
at System.Drawing.LibraryResolver.EnsureRegistered()
at System.Drawing.SafeNativeMethods.Gdip.PlatformInitialize()
at System.Drawing.SafeNativeMethods.Gdip..cctor()
— End of inner exception stack trace —
at System.Drawing.SafeNativeMethods.Gdip.GdipGetGenericFontFamilySansSerif(IntPtr& fontfamily)
at System.Drawing.FontFamily.GetGdipGenericSansSerif()
at System.Drawing.FontFamily.get_GenericSansSerif()
at System.Drawing.Font.CreateFont(String familyName, Single emSize, FontStyle style, GraphicsUnit unit, Byte charSet, Boolean isVertical)
at System.Drawing.Font..ctor(String familyName, Single emSize, FontStyle style, GraphicsUnit unit, Byte gdiCharSet, Boolean gdiVerticalFont)
at OfficeOpenXml.ExcelRangeBase.AutoFitColumns(Double MinimumWidth, Double MaximumWidth)
at OfficeOpenXml.ExcelRangeBase.AutoFitColumns(Double MinimumWidth)
at OfficeOpenXml.ExcelRangeBase.AutoFitColumns()
at OfficeOpenXml.ExcelColumn.AutoFit()
at Magicodes.ExporterAndImporter.Excel.Utility.ExportHelper1.AddStyle() at Magicodes.ExporterAndImporter.Excel.Utility.ExportHelper1.AddHeaderAndStyles()
at Magicodes.ExporterAndImporter.Excel.ExcelExporter.ExportHeaderAsByteArray(String[] items, String sheetName)

引入的版本

此更改会影响源兼容性和二进制兼容性。

更改原因

由于 System.Drawing.Common 被设计为 Windows 技术的精简包装器,因此其跨平台实现欠佳。

libgdiplus 是本机端 System.Drawing.Common 跨平台实现的主要提供程序。 libgdiplus 实际上是对 System.Drawing.Common 所依赖的 Windows 部分的重新实现。 该实现使 libgdiplus 成为一个重要的组件。 它大约有 30,000C 代码,大部分未经测试,而且缺少很多功能。 libgdiplus 还具有许多用于图像处理和文本呈现的外部依赖项,例如 cairopango 和其他本机库。 这些依赖项使得维护和交付组件更具挑战性。 自从包含 Mono 跨平台实现以来,我们已将许多从未得到修复的问题重定向到 libgdiplus。 相比之下,我们采用的其他外部依赖项,例如 icu 或 openssl,都是高质量的库。 使 libgdiplus 的功能集和质量与 .NET 堆栈的其余部分相媲美是不可行的。

通过对 NuGet 包的分析,我们观察到 System.Drawing.Common 主要用于跨平台的图像处理,例如 QR 代码生成器和文本呈现。 由于我们的跨平台图形支持不完整,我们还没有注意到大量的图形使用。 System.Drawing.Common 在非 Windows 环境中的使用通常得到 SkiaSharp ImageSharp 的良好支持。

System.Drawing.Common 将仅在 Windows 窗体和 GDI+ 的上下文中继续演变。

建议的操作

或者,可通过将 runtimeconfig.json 文件中的 System.Drawing.EnableUnixSupportSystem.Drawing.EnableUnixSupport设置为 true 来启用对 .NET 6 中非 Windows 平台的支持:

{
   "configProperties": {
      "System.Drawing.EnableUnixSupport": true
   }
}

添加此配置开关是为了让严重依赖此包的跨平台应用有时间迁移到更新式的库。 但是,不会修复非 Windows bug。 此外,此开关已在 .NET 7 中删除。

说白了就是在非Windows平台下.net 6中默认不支持System.Drawing.Common了,想继续使用要设置运行时配置,并且以后.NET 7中这个配置也要干掉。

问题解决

又核实了一遍,确认是引用了.Net 6.0版本下的System.Drawing.Common,应该是其他人引用了6.0版本导致的。目前应用使用的是.net 5,配置是不可能修改配置的。所以,最简单的办法就是降级,将System.Drawing.Common版本降级到5.0,问题就此解决。

.NET 5或更早版本解决

docker环境下使用EPPlus 导出Excel报Gdip异常

基于dotnet官方的aspnet5的镜像构建安装libgdiplus基础镜像



转载请注明:清风亦平凡 » .net core在Linux系统报Gdip异常的问题

]]>
https://www.mlplus.net/2022/04/08/net-core-linux-system-gdip-error/feed/ 0
.NET 5中使用GB2312编码报错的问题 https://www.mlplus.net/2021/12/17/net-core-register-encoding/ https://www.mlplus.net/2021/12/17/net-core-register-encoding/#respond Fri, 17 Dec 2021 02:53:22 +0000 https://www.mlplus.net/?p=3702 背景

在使用.net 5构建应用时,在处理某些编码问题使用了GB2312,应用运行后报错。经过确认知道了编码 GB2312 默认不支持。

异常信息

Not Support Encoding’GB2312′ is not a supported encoding name. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method. (Parameter ‘name’)
System.Exception: Not Support Encoding’GB2312′ is not a supported encoding name. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method. (Parameter ‘name’)

解决办法

添加引用包

.NET 5中使用GB2312编码报错的问题-第0张图片
System.Text.Encoding.CodePages

注册

在使用System.Text.Encoding.GetEncoding ("GB2312")之前,在代码中执行注册

 System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);



转载请注明:清风亦平凡 » .NET 5中使用GB2312编码报错的问题

]]>
https://www.mlplus.net/2021/12/17/net-core-register-encoding/feed/ 0
ASP.NET Core Web Api实现大文件切/分片上传 https://www.mlplus.net/2020/12/31/asp-net-core-big-file-uploader/ https://www.mlplus.net/2020/12/31/asp-net-core-big-file-uploader/#respond Thu, 31 Dec 2020 08:15:58 +0000 https://www.mlplus.net/?p=3313 一年前的一个项目,有人反馈上传超时,超时原因是文件大小超出限制。由于原来维护项目的人员离开,现在上传超时的问题就有我来处理。解决方案也比较简单,就是切片上传。前端不想自己写了,就利用了上传组件,这个上传组件是百度WebUploaderWebUploader这个组件被使用的也比较广泛,为了省事就用它啦!

上传中

ASP.NET Core Web Api实现大文件切/分片上传-第0张图片
ASP.NET Core Web Api实现大文件切/分片上传-第1张图片

合并后

ASP.NET Core Web Api实现大文件切/分片上传-第2张图片

代码实现

前端代码

前端上传以及分片使用的百度上传组件WebUploader


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Home Page - WebUpload</title>

    
        <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
    
    
    <link rel="stylesheet" href="/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" href="/">WebUpload</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" href="/">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" href="/Home/Privacy">Privacy</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        


        <main role="main" class="pb-3">
            <link rel="stylesheet" type="text/css" href="/webuploader/webuploader.css">
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
    <script src="/webuploader/webuploader.js"></script>
    <div class="text-center">
        <h1 class="display-4">Welcome</h1>
        <div id="uploader" class="wu-example">
            <div class="container-fluid">
                <div class="col-md-10">
                    <div class="row">文件上传示例:</div>
                    <div class="row">
                        <div id="uploader" class="wu-example">
                            <!--用来存放文件信息-->
                            <div id="fileList" class="uploader-list"></div>
                            <div class="btns">
                                <div id="picker" class="btn btn-primary">选择文件</div>

                            </div>

                        </div>
                    </div>

                    <div class="row">

                    </div>
                    <div class="row">  <button id="ctlBtn" class="btn btn-default">开始上传</button></div>
                </div>
                <div>
                </div>
            </div>
        </div>
        <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
    </div>
<script>
    jQuery(function () {
        var $ = jQuery,
            $list = $('#fileList'),
            $btn = $('#ctlBtn'),
            state = 'pending',
            uploader, guid = {};
        uploader = WebUploader.create({
            // 不压缩image
            resize: false,

            // swf文件路径
            swf: "~/webuploader/Uploader.swf",

            server: 'http://localhost:8011/api/File/UplaodFile?isConvert=true',

            // 选择文件的按钮。可选。
            // 内部根据当前运行是创建,可能是input元素,也可能是flash.
            pick: '#picker',
            chunked: true

        });
        
        // 当有文件添加进来的时候
        uploader.on('fileQueued', function (file) {
            guid[file.id] = +(new Date());
            $list.append('<div id="' + file.id + '" class="item">' +
                '<h4 class="info">' + file.name + '</h4>' +
                '<p class="state">等待上传...</p>' +
                '</div>');
            uploader
                .md5File(file)
                .progress(function (percentage) {
                    // console.log("Percentage:", percentage);
                    $("#ctlBtn").hide();
                })
                // MD5计算完毕,可以点击上传了
                .then(function (fileMd5) {
                    file.fileMd5 = fileMd5;
                    $("#ctlBtn").show()
                    console.log(fileMd5);
                });

        });
        uploader.on('uploadBeforeSend', function (file, data, headers) {
            $.extend(data, { "fileId": guid[data.id], "fileMd5": file.file.fileMd5 });
            $.extend(headers, {
                "Authorization":"Bearer "
            });
            if (data.chunks) {
                data["chunkStart"] = file.start;
                data["chunkEnd"] = file.end;
            }
        });
        // 文件上传过程中创建进度条实时显示。
        uploader.on('uploadProgress', function (file, percentage) {

            var $li = $('#' + file.id),
                $percent = $li.find('.progress .progress-bar');
            // 避免重复创建
            if (!$percent.length) {
                $percent = $('<div class="progress progress-striped active">' +
                    '<div class="progress-bar" role="progressbar" style="width: 0%">' +
                    '</div>' +
                    '</div>').appendTo($li).find('.progress-bar');
            }
            $li.find('p.state').text('上传中');
            $percent.css('width', percentage * 100 + '%');

        });

        uploader.on('uploadSuccess', function (file) {
            $('#' + file.id).find('p.state').text('已上传');
        });

        uploader.on('uploadError', function (file) {
            $('#' + file.id).find('p.state').text('上传出错');
        });

        uploader.on('uploadComplete', function (file) {
            $('#' + file.id).find('.progress').fadeOut();
        });
        uploader.on('all', function (type) {
            if (type === 'startUpload') {
                state = 'uploading';
            } else if (type === 'stopUpload') {
                state = 'paused';
            } else if (type === 'uploadFinished') {
                state = 'done';
            }
            if (state === 'uploading') {
                $btn.text('暂停上传');
            } else {
                $btn.text('开始上传');
            }

        });

        $btn.on('click', function () {
            if (state === 'uploading') {
                uploader.stop();
            } else {
                uploader.upload();
            }
        });
    });
</script>

        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2020 - WebUpload - <a href="/Home/Privacy">Privacy</a>
        </div>
    </footer>

    
        <script src="/lib/jquery/dist/jquery.js"></script>
        <script src="/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    
    
    <script src="/js/site.js?v=4q1jwFhaPaZgr8WAUSrux6hAuh0XDg9kPS3xIVq36I0"></script>

    
</body>
</html>

后台实现

后台使用的是ASP .NET Core Web API,实现方式也很简单。文件的校验,分块的校验等这里就省略了。
    
    [Route("api/[controller]")]
    public class FileController : Controller
    {
        public static readonly object locker = new object();
        [HttpPost]
        [Route("UplaodFile")]
        public IActionResult UplaodFile([FromForm] IFormCollection form)
        {
            try
            {
                string fileId = form["fileId"];
                if (string.IsNullOrWhiteSpace(fileId)||form.Files==null||form.Files.Count==0)
                {
                    return BadRequest();
                }
                string tempFileFolder = Path.Combine(AppContext.BaseDirectory, $"FileUploader/{fileId}");
                CreateFolder(tempFileFolder);
                if (form.ContainsKey("chunks"))
                {
                    string chunksString = form["chunks"];
                    int chunks = int.Parse(chunksString);
                    string chunkString = form["chunk"];
                    int chunk = int.Parse(chunkString);
                    string sizeString = form["size"];
                    long size = long.Parse(sizeString);
                    string chunkStartString = form["chunkStart"];
                    string chunkEndString = form["chunkEnd"];
                    string chunkMd5 = form["chunkMd5"];
                    string fileMd5 = form["fileMd5"];
                    string lastModifiedDate = form["lastModifiedDate"];
                    var file = form.Files.FirstOrDefault();
                    string name = file.FileName;
                    string ext = Path.GetExtension(name);
                    Stream stream = file.OpenReadStream();
                    byte[] bytes = new byte[stream.Length];
                    stream.Read(bytes, 0, bytes.Length);
                    string factFilePath = Path.Combine(tempFileFolder, $"{name}");
                    string chunkFilePath = $"{factFilePath}.chunk{chunk}";
                    string chunkFileTempPath = $"{chunkFilePath}.temp";
                    System.IO.File.Delete(chunkFileTempPath);
                    FileStream fs = new FileStream(chunkFileTempPath, FileMode.Create);
                    BinaryWriter bw = new BinaryWriter(fs);
                    bw.Write(bytes);
                    bw.Flush();
                    bw.Close();
                    bw.Dispose();
                    fs.Close();
                    fs.Dispose();
                    stream.Close();
                    stream.Dispose();
                    System.IO.File.Move(chunkFileTempPath, chunkFilePath);
                    bool isMerge = true;
                    for (int i = 0; i < chunks; i++)
                    {
                        if (!System.IO.File.Exists($"{factFilePath}.chunk{i}"))
                        {
                            isMerge = false;
                            break;
                        }
                    }

                    if (isMerge)
                    {
                        lock (locker)
                        {
                            if (isMerge)
                            {
                                if (System.IO.File.Exists(factFilePath))
                                {
                                    return Ok();
                                }
                                var fileStream = new FileStream(factFilePath, FileMode.Create);
                                for (int i = 0; i < chunks; i++)
                                {
                                    string chunkFile = $"{factFilePath}.chunk{i}";
                                    var chunkBytes = System.IO.File.ReadAllBytes(chunkFile);
                                    fileStream.Write(chunkBytes, 0, chunkBytes.Length);
                                    fileStream.Flush();
                                    System.IO.File.Delete(chunkFile);//删除分块
                                }
                                fileStream.Close();
                                fileStream.Dispose();
                            }
                        }

                    }
                }
                else
                {
                    var file = form.Files.FirstOrDefault();
                    Stream stream = file.OpenReadStream();
                    byte[] bytes = new byte[stream.Length];
                    stream.Read(bytes, 0, bytes.Length);
                    FileStream fileStream = new FileStream(Path.Combine(tempFileFolder, $"{file.FileName}"), FileMode.Create);
                    BinaryWriter bw = new BinaryWriter(fileStream);
                    bw.Write(bytes);
                    bw.Flush();
                    bw.Close();
                    bw.Dispose();
                    fileStream.Close();
                    fileStream.Dispose();
                    stream.Close();
                    stream.Dispose();
                }
            }
            catch (Exception ex)
            {
                Console.Write(ex);
            }

            return Ok();
        }
        private void CreateFolder(string folderPath)
        {
            if (!Directory.Exists(folderPath))
            {
                Directory.CreateDirectory(folderPath);
            }
        }
        [HttpGet]
        [Route("Index")]
        public IActionResult Index()
        {
            return Ok();
        }
    }

示例下载

.NET CORE大文件分/切片上传示例



转载请注明:清风亦平凡 » ASP.NET Core Web Api实现大文件切/分片上传

]]>
https://www.mlplus.net/2020/12/31/asp-net-core-big-file-uploader/feed/ 0
.NET CORE实现MD5加密 https://www.mlplus.net/2020/09/03/net-core-md5/ https://www.mlplus.net/2020/09/03/net-core-md5/#respond Wed, 02 Sep 2020 16:00:00 +0000 https://www.mlplus.net/?p=3154 背景

因某些需求,需要与合作单位进行相应的数据交互,而交互均使用了加密方式处理并用MD5作为签名,以便校验传输内容是否遭到篡改。

MD5

md5是一种信息摘要算法,它可以从一个字符串或一个文件中按照一定的规则生成一个特殊的字符串,并且一个文件所对应的MD5摘要是固定的,当文件内容变化后,其MD5值也会不一样,因此,在应用中经常使用MD5值来验证一段数据有没有被篡改。

.NET CORE 实现MD5

以下简单实现,支持返回大小写、32位以及16位MD5字符串。


    public static class EncryptionMd5
    {
         /// <summary>
        /// md5加密
        /// </summary>
        /// <param name="content">要加密的内容</param>
        /// <param name="isUpper">是否大写,默认小写</param>
        /// <param name="is16">是否是16位,默认32位</param>
        /// <returns></returns>
        public static string Md5(string content,bool isUpper=false,bool is16=false)
        {
            using (var md5 = MD5.Create())
            {
                var result = md5.ComputeHash(Encoding.UTF8.GetBytes(content));
                string md5Str = BitConverter.ToString(result);
                md5Str = md5Str.Replace("-", "");
                md5Str= isUpper ? md5Str : md5Str.ToLower();
                return is16 ? md5Str.Substring(8,16) : md5Str;
            }
        }
    }
以上是使用.NET CORE自身提供的MD5加密进行实现,如果想了解原理以及具体实现方式,请使用万能搜索引擎查找。

使用方式


using System;
using System.Text;
using System.Security.Cryptography;
namespace CryptographyApplication
{
   class SecurityCryptography
   {
      static void Main(string[] args)
      {
         Console.WriteLine(EncryptionMd5.Md5("Hello World!"));
         Console.ReadKey();
      }
   }
}
.NET CORE实现MD5加密-第0张图片



转载请注明:清风亦平凡 » .NET CORE实现MD5加密

]]>
https://www.mlplus.net/2020/09/03/net-core-md5/feed/ 0
.Net Core实现AES加解密 https://www.mlplus.net/2020/09/02/net-core-encryption-aes/ https://www.mlplus.net/2020/09/02/net-core-encryption-aes/#respond Wed, 02 Sep 2020 04:30:29 +0000 https://www.mlplus.net/?p=3148 背景

因业务需求,需要与其他平台实现融合登录,根据提供融合登录方的文档内容,对AES加密解密相关内容使用.NET CORE来进行一次实现。以下记录实现AES加解密后的内容,便以后备用。

AES

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。严格地说,AESRijndael加密法并不完全一样(虽然在实际应用中二者可以互换),因为Rijndael加密法可以支持更大范围的区块和密钥长度:AES的区块长度固定为128 比特,密钥长度则可以是128192256比特;而Rijndael使用的密钥和区块长度可以是32位的整数倍,以128位为下限,256比特为上限。包括AES-ECBAES-CBCAES-CTRAES-OFBAES-CFB

.NET CORE 的AES加解密实现


    public enum CiphertextType
    {
        /// <summary>
        /// Base64
        /// </summary>
        Base64 = 1,
        /// <summary>
        /// 16进制字符串
        /// </summary>
        Hex = 2
    }
    public static class EncryptionAes
    {
        #region AES加密

        /// <summary>
        /// AES加密
        /// </summary>
        /// <param name="source"></param>
        /// <param name="key">密钥</param>
        /// <param name="iv">初始向量</param>
        /// <param name="padding">填充模式</param>
        /// <param name="mode">加密模式</param>
        /// <param name="ciphertextType">密文类型</param>
        /// <returns></returns>
        public static (bool isSuccess, string text) AESEncrypt(this string source, string key, string iv = "", PaddingMode padding = PaddingMode.PKCS7, CipherMode mode = CipherMode.CBC, CiphertextType ciphertextType = CiphertextType.Base64)
        {
            try
            {
                byte[] keyBytes = Encoding.UTF8.GetBytes(key);
                byte[] textBytes = Encoding.UTF8.GetBytes(source);
                byte[] ivBytes = Encoding.UTF8.GetBytes(iv);

                byte[] useKeyBytes = new byte[16];
                byte[] useIvBytes = new byte[16];

                if (keyBytes.Length > useKeyBytes.Length)
                {
                    Array.Copy(keyBytes, useKeyBytes, useKeyBytes.Length);
                }
                else
                {
                    Array.Copy(keyBytes, useKeyBytes, keyBytes.Length);
                }
                if (ivBytes.Length > useIvBytes.Length)
                {
                    Array.Copy(ivBytes, useIvBytes, useIvBytes.Length);
                }
                else
                {
                    Array.Copy(ivBytes, useIvBytes, ivBytes.Length);
                }

                Aes aes = System.Security.Cryptography.Aes.Create();
                aes.KeySize = 256;//秘钥的大小,以位为单位,128,256等
                aes.BlockSize = 128;//支持的块大小
                aes.Padding = padding;//填充模式
                aes.Mode = mode;
                aes.Key = useKeyBytes;
                aes.IV = useIvBytes;//初始化向量,如果没有设置默认的16个0

                ICryptoTransform cryptoTransform = aes.CreateEncryptor();
                byte[] resultBytes = cryptoTransform.TransformFinalBlock(textBytes, 0, textBytes.Length);
                return (true, CipherByteArrayToString(resultBytes, ciphertextType));
            }
            catch (Exception ex)
            {
                return (false, ex.Message);
            }
        }

        #endregion



        #region AES解密

        /// <summary>
        /// AES解密
        /// </summary>
        /// <param name="source"></param>
        /// <param name="key">密钥</param>
        /// <param name="iv">初始向量</param>
        /// <param name="padding">填充模式</param>
        /// <param name="mode">加密模式</param>
        /// <param name="ciphertextType">密文类型</param>
        /// <returns></returns>
        public static (bool isSuccess, string text) AESDecrypt(this string source, string key, string iv = "", PaddingMode padding = PaddingMode.PKCS7, CipherMode mode = CipherMode.CBC, CiphertextType ciphertextType = CiphertextType.Base64)
        {
            try
            {
                byte[] keyBytes = Encoding.UTF8.GetBytes(key);
                byte[] textBytes = CiphertextStringToByteArray(source, ciphertextType);
                byte[] ivBytes = Encoding.UTF8.GetBytes(iv);

                byte[] useKeyBytes = new byte[16];
                byte[] useIvBytes = new byte[16];

                if (keyBytes.Length > useKeyBytes.Length)
                {
                    Array.Copy(keyBytes, useKeyBytes, useKeyBytes.Length);
                }
                else
                {
                    Array.Copy(keyBytes, useKeyBytes, keyBytes.Length);
                }

                if (ivBytes.Length > useIvBytes.Length)
                {
                    Array.Copy(ivBytes, useIvBytes, useIvBytes.Length);
                }
                else
                {
                    Array.Copy(ivBytes, useIvBytes, ivBytes.Length);
                }

                Aes aes = System.Security.Cryptography.Aes.Create();
                aes.KeySize = 256;//秘钥的大小,以位为单位,128,256等
                aes.BlockSize = 128;//支持的块大小
                aes.Padding = padding;//填充模式
                aes.Mode = mode;
                aes.Key = useKeyBytes;
                aes.IV = useIvBytes;//初始化向量,如果没有设置默认的16个0

                ICryptoTransform decryptoTransform = aes.CreateDecryptor();
                byte[] resultBytes = decryptoTransform.TransformFinalBlock(textBytes, 0, textBytes.Length);
                return (true, Encoding.UTF8.GetString(resultBytes));
            }
            catch (Exception ex)
            {
                return (false, ex.Message);
            }
        }

        /// <summary>
        /// 通过密文返回加密byte数组
        /// </summary>
        /// <param name="ciphertext"></param>
        /// <param name="ciphertextType"></param>
        /// <returns></returns>
        public static byte[] CiphertextStringToByteArray(string ciphertext, CiphertextType ciphertextType)
        {
            byte[] cipherByte;
            switch (ciphertextType)
            {
                case CiphertextType.Base64:
                    cipherByte = Convert.FromBase64String(ciphertext);
                    break;
                case CiphertextType.Hex:
                    cipherByte = HexStringToByteArray(ciphertext);
                    break;
                default:
                    cipherByte = Convert.FromBase64String(ciphertext);
                    break;
            }
            return cipherByte;
        }

        /// <summary>
        /// 通过加密的Byte数组返回加密后的密文
        /// </summary>
        /// <param name="cipherByte"></param>
        /// <param name="ciphertextType"></param>
        /// <returns></returns>
        public static string CipherByteArrayToString(byte[] cipherByte, CiphertextType ciphertextType)
        {
            string ciphertext = "";
            switch (ciphertextType)
            {
                case CiphertextType.Base64:
                    ciphertext = Convert.ToBase64String(cipherByte);
                    break;
                case CiphertextType.Hex:
                    ciphertext = ByteArrayToHexString(cipherByte);
                    break;
                default:
                    ciphertext = Convert.ToBase64String(cipherByte);
                    break;
            }
            return ciphertext;
        }

        /// <summary>
        /// Hex字符串转Byte
        /// </summary>
        /// <param name="hexContent"></param>
        /// <returns></returns>
        public static byte[] HexStringToByteArray(string hexContent)
        {
            hexContent = hexContent.Replace(" ", "");
            byte[] buffer = new byte[hexContent.Length / 2];
            for (int i = 0; i < hexContent.Length; i += 2)
            {
                buffer[i / 2] = (byte)Convert.ToByte(hexContent.Substring(i, 2), 16);
            }
            return buffer;
        }
        /// <summary>
        /// Byte转Hex字符串
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static string ByteArrayToHexString(byte[] data)
        {
            StringBuilder sb = new StringBuilder(data.Length * 3);
            foreach (byte b in data)
            {
                sb.Append(Convert.ToString(b, 16).PadLeft(2, '0'));
            }
            return sb.ToString().ToUpper();
        }
        #endregion
    }

调用AES加解密


    class Program
    {
        static void Main(string[] args)
        {
            var ddd = "111111111" .AESEncrypt("3EDsw9[A4W5FdS{]3W","0102030504030201", PaddingMode.PKCS7);
            Console.WriteLine($"加密:{ddd.text}");
            Console.WriteLine($"解密:{(ddd.text.AESDecrypt("3EDsw9[A4W5FdS{]3W", "0102030504030201", PaddingMode.PKCS7)).text}");
            Console.Read();
        }
    }
.Net Core实现AES加解密-第0张图片

示例下载

.Net Core实现AES加解密示例



转载请注明:清风亦平凡 » .Net Core实现AES加解密

]]>
https://www.mlplus.net/2020/09/02/net-core-encryption-aes/feed/ 0
asp .net core 不使用构造函数获得注入的对象 https://www.mlplus.net/2020/08/08/aspnetcorediget/ https://www.mlplus.net/2020/08/08/aspnetcorediget/#comments Sat, 08 Aug 2020 01:00:00 +0000 https://www.mlplus.net/?p=3026 使用asp .net core 2.1使用自带的依赖注入,自带的依赖注入是构造函数注入。有些情况,构造函数注入并不能满足需求,所以需要使用其他方法获取指定实例。

    
public interface IEngine
 {
        /// <summary>
        /// 类型构建
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        object Resolve(Type type);
}
    
public class GetEngine: IEngine
{
        private IServiceProvider _serviceProvider;
        public GetEngine(IServiceProvider serviceProvider)
        {
            this._serviceProvider = serviceProvider;
        }

        /// <summary>
        /// 类型构建
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public object Resolve(Type type)
        {
            return _serviceProvider.GetService(type);
        }
}

public class EnginContext
 {
        private static IEngine _engine;

        [MethodImpl(MethodImplOptions.Synchronized)]
        public static IEngine Initialize(IEngine engine)
        {
            if (_engine == null)
                _engine = engine;
            return _engine;
        }

        public static IEngine Current
        {
            get
            {
                return _engine;
            }
        }
 }

需要在 Startup.csConfigure方法中调用,如下:


EnginContext.Initialize(new GetEngine(app.ApplicationServices));

完整示例如下:


public void Configure(IApplicationBuilder app, IHostingEnvironment env)
 {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }

            EnginContext.Initialize(new GetEngine(app.ApplicationServices));

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
  }

新增一个类Information进行测试,如下:


    public class Information
    {
        private string Name => "Dot Net Core";
        public string GetName()
        {
            return $"你好!{Name}";
        }
    }

ConfigureServices方法中将新增的类进行注入。


        
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddTransient<Information>();
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

通过以下方式进行获取已经注入的对象。


EnginContext.Current.Resolve(typeof(Information));

使用示例如下:


        public IActionResult About()
        {
            ViewData["Message"] = "Your application description page.";
            var info= EnginContext.Current.Resolve(typeof(Information)) as Information;
            ViewData["Message"] = info.GetName();
            return View();
        }
asp .net core 不使用构造函数获得注入的对象-第0张图片

示例下载

示例



转载请注明:清风亦平凡 » asp .net core 不使用构造函数获得注入的对象

]]>
https://www.mlplus.net/2020/08/08/aspnetcorediget/feed/ 2
ASP.NET Core 配置跨域(CORS) https://www.mlplus.net/2020/01/13/aspnetcorecors/ https://www.mlplus.net/2020/01/13/aspnetcorecors/#respond Mon, 13 Jan 2020 01:00:00 +0000 https://www.mlplus.net/?p=2235 由于项目中需要实时消息,所以就使用了ASP.NET(Core) SignalR实时通讯库。因为业务服务与通讯服务是独立的,所以涉及到跨域的问题, 浏览器抛出的异常非常明显,这个是明显跨域相关内容。 报错信息如下:

ASP.NET Core 配置跨域(CORS)-第0张图片

Access to XMLHttpRequest at ‘http://192.168.2.13:5005/api/v1/commommessage/messageHub/negotiate’ from origin ‘http://127.0.0.1:5500’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

CORS(跨域资源共享)是一种W3C标准,允许服务器放宽同源策略。使用CORS,服务器可以在显式允许某些跨域请求时拒绝其他跨域请求。CORS是相比其他跨域技术(比如JSONP)更安全、更灵活。

ASP.NET Core跨域问题需要再 StartUp.cs 文件中进行相关配置。

ConfigureServices方法增加内容

        services.AddCors(options => options.AddPolicy("CorsPolicy",
            builder =>
            {
                builder.AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowAnyOrigin()
                    .AllowCredentials();
            }));

完整的ConfigureServices方法示例如下:


        public IServiceProvider ConfigureServices(IServiceCollection services)
        {

            services.AddCors(options => options.AddPolicy("CorsPolicy",
                builder =>
                {
                    builder.AllowAnyMethod()
                        .AllowAnyHeader()
                        .AllowAnyOrigin()
                        .AllowCredentials();
                }));

            services.AddMvc(options => { options.Filters.Add(); })
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
                //全局配置Json序列化处理
                .AddJsonOptions(options =>
                {
                    options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
                })
                .AddFluentValidation();

            services.AddSignalR();

            return services.AutofacServiceProvider();
        }

在Configure方法中添加

app.UseCors("CorsPolicy");

完整Configure方法示例:


        public void Configure(IApplicationBuilder app, IHostingEnvironment env,
                              ILoggerFactory loggerFactory,
                              IOptions config)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            if (env.IsProduction())
            {
                //注册服务到consul
                app.UseConsul();

                using (HttpClient client = new HttpClient())
                {
                    StringContent content = new StringContent(JsonConvert.SerializeObject(config.Value.RegisterParams));
                    content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                    HttpResponseMessage message = client.PostAsync(config.Value.Server.Address, content).Result;
                };
            }
            app.UseCors("CorsPolicy");
            loggerFactory.AddNLog();
            env.ConfigureNLog("nlog.config");//读取Nlog配置文件
            app.UseSignalR(routes =>
            {
                routes.MapHub("/api/v1/examinations/messageHub");
            });
            app.UseMvc();
        }
特别注意:app.UseCors()必须放在app.UseMvc()之前。

本以为这就算完了,但是万万没想到啊,依然没有什么用。浏览器控制台错误依然存在,.net core也提示了相关警告信息,差点就忘了现在使用.net core 版本和以前不一样了。

The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the policy by listing individual origins if credentials needs to be supported.

ASP.NET Core 配置跨域(CORS)-第1张图片

目前使用的是ASP.NET CORE 2.1, 其Cors组件已经升级,出于安全考虑必须明确要允许的内容。 这是安全的一部分,不可以这样操作。 如果要允许凭据,则Access-Control-Allow-Origin不能使用*通配符。 必须指定确切的协议++端口。

这个问题其实也很好解决,只需要给予一个可信列表即可。修改内容如下:


        services.AddCors(options => options.AddPolicy("CorsPolicy",
            builder =>
            {
                builder.WithOrigins(new string[] { "http://127.0.0.1:5500" })
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials();
            }));

如果真的就不想做任何限制,其实也是有办法的。只需要将AllowAnyOrigin替换为SetIsOriginAllowed(_ => true)就可以解决。


       services.AddCors(options => options.AddPolicy("CorsPolicy",
            builder =>
            {
                builder.AllowAnyMethod()
                    .SetIsOriginAllowed(_ => true)
                    .AllowAnyHeader()
                    .AllowCredentials();
            }));

除了前面的两个方法以外,其实还可以自定义中间件。添加Cors处理类。如下:


    public class CorsMiddleware
    {
        private readonly RequestDelegate next;

        public CorsMiddleware(RequestDelegate next)
        {
            this.next = next;
        }
        public async Task Invoke(HttpContext context)
        {
            if (context.Request.Headers.ContainsKey(CorsConstants.Origin))
            {
                context.Response.Headers.Add("Access-Control-Allow-Origin", context.Request.Headers["Origin"]);
                context.Response.Headers.Add("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS,HEAD,PATCH");
                context.Response.Headers.Add("Access-Control-Allow-Headers", context.Request.Headers["Access-Control-Request-Headers"]);
                context.Response.Headers.Add("Access-Control-Allow-Credentials", "true");

                if (context.Request.Method.Equals("OPTIONS"))
                {
                    context.Response.StatusCode = StatusCodes.Status200OK;
                    return;
                }
            }

            await next(context);
        }
    }

Configure方法中添加如下内容即可。

app.UseMiddleware<CorsMiddleware>();



转载请注明:清风亦平凡 » ASP.NET Core 配置跨域(CORS)

]]>
https://www.mlplus.net/2020/01/13/aspnetcorecors/feed/ 0
docker环境下使用EPPlus 导出Excel报Gdip异常 https://www.mlplus.net/2019/12/31/dockerepplusexportexceleror/ https://www.mlplus.net/2019/12/31/dockerepplusexportexceleror/#respond Tue, 31 Dec 2019 02:32:29 +0000 https://www.mlplus.net/?p=2133 网站内容在Windows环境下并没有出现任何问题,但是在Linux 系统CentosDocker环境下就出现了相关错误。这让人一脸蒙逼啊, 未能加载libdl库。使用了EPPlus处理Excel,其内部设置了字体大小以及颜色之类的东西,使用了‘ System.Drawing.Font ’的相关内容。由于Linux系统中Docker环境下缺少相关库,故而出现异常。具体错误请看以下内容。

docker环境下使用EPPlus 导出Excel报Gdip异常-第0张图片

报错内容:

fail: System.Exception[-2146233036]
The type initializer for ‘Gdip’ threw an exception.
System.TypeInitializationException: The type initializer for ‘Gdip’ threw an exception. —> System.DllNotFoundException: Unable to load shared library ‘libdl’ or one of its dependencies. In order to help diagnose loading problems, consider setting the LD_DEBUG environment variable: liblibdl: cannot open shared object file: No such file or directory
at Interop.Libdl.dlopen(String fileName, Int32 flag)
at System.Drawing.SafeNativeMethods.Gdip.LoadNativeLibrary()
at System.Drawing.SafeNativeMethods.Gdip..cctor()
— End of inner exception stack trace —
at System.Drawing.SafeNativeMethods.Gdip.GdipGetGenericFontFamilySansSerif(IntPtr& fontfamily)
at System.Drawing.FontFamily.GetGdipGenericSansSerif()
at System.Drawing.FontFamily.get_GenericSansSerif()
at System.Drawing.Font.CreateFont(String familyName, Single emSize, FontStyle style, GraphicsUnit unit, Byte charSet, Boolean isVertical)
at OfficeOpenXml.ExcelRangeBase.AutoFitColumns(Double MinimumWidth, Double MaximumWidth)
at ExcelCake.Intrusive.ExportExtension.FillExcelWorksheet[T](ExcelWorksheet sheet, IEnumerable1 list, ExportExcelSetting exportSetting) at ExcelCake.Intrusive.ExportExtension.ExportMultiToBytes(IDictionary2 dic)
at ExcelCake.Intrusive.ExportExtension.ExportToExcelBytes[T](IEnumerable`1 list, String sheetName)
at lambda_method(Closure , Object , Object[] )
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()

其实,解决办法也非常简单,只需要在Dockerfile中执行相关安装相关库的命令即可,当然在安装前要更新源。在Dockerfile中新增如下内容:

RUN echo "deb http://mirrors.aliyun.com/debian/ stretch main non-free contrib" > /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/debian/ stretch main non-free contrib" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.aliyun.com/debian-security stretch/updates main" >> /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/debian-security stretch/updates main" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.aliyun.com/debian/ stretch-updates main non-free contrib" >> /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/debian/ stretch-updates main non-free contrib" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.aliyun.com/debian/ stretch-backports main non-free contrib" >> /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/debian/ stretch-backports main non-free contrib" >> /etc/apt/sources.list

RUN apt-get clean

RUN apt-get update \
    && apt-get install -y --allow-unauthenticated \
       libc6-dev \
       libgdiplus \
       libx11-dev \
    && rm -rf /var/lib/apt/lists/*

新增相关安装库的命令后完整的Dockerfile内容如下:

FROM microsoft/dotnet:2.1-sdk AS build
COPY ./common/src/common/ProjectCommon
COPY ./services/src/services/ProjectName
WORKDIR /src/services/ProjectName
RUN dotnet restore ProjectName.csproj --configfile "nuget.config"
RUN dotnet build ProjectName.csproj -c Release -o /app

FROM build AS publish
RUN dotnet publish ProjectName.csproj -c Release -o /app

FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80

FROM base AS final
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /app
COPY --from=publish /app .

RUN echo "deb http://mirrors.aliyun.com/debian/ stretch main non-free contrib" > /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/debian/ stretch main non-free contrib" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.aliyun.com/debian-security stretch/updates main" >> /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/debian-security stretch/updates main" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.aliyun.com/debian/ stretch-updates main non-free contrib" >> /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/debian/ stretch-updates main non-free contrib" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.aliyun.com/debian/ stretch-backports main non-free contrib" >> /etc/apt/sources.list
RUN echo "deb-src http://mirrors.aliyun.com/debian/ stretch-backports main non-free contrib" >> /etc/apt/sources.list

RUN apt-get clean

RUN apt-get update \
    && apt-get install -y --allow-unauthenticated \
       libc6-dev \
       libgdiplus \
       libx11-dev \
    && rm -rf /var/lib/apt/lists/*


ENTRYPOINT ["dotnet", "ProjectName.dll"]

重新更新后,报错问题解决。当然也看了其他的相关解决方案,但是很遗憾没有解决问题。如下:

https://github.com/VahidN/EPPlus.Core/issues/40

https://q.cnblogs.com/q/109061/

https://www.cnblogs.com/sunnytrudeau/p/9384620.html



转载请注明:清风亦平凡 » docker环境下使用EPPlus 导出Excel报Gdip异常

]]>
https://www.mlplus.net/2019/12/31/dockerepplusexportexceleror/feed/ 0
使用Quartz.NET实现定时(计划)任务 https://www.mlplus.net/2019/12/16/quartz-net-execute-task/ https://www.mlplus.net/2019/12/16/quartz-net-execute-task/#respond Mon, 16 Dec 2019 01:03:00 +0000 https://www.mlplus.net/?p=2006 Quartz.NET

Quartz.NET是一个强大、开源、轻量的作业调度框架,是 OpenSymphony 的 Quartz API 的.NET移植,用C#改写,可用于winform和asp.net应用中。它灵活而不复杂。你能够用它来为执行一个作业而创建简单的或复杂的作业调度。它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等。

官网:http://www.quartz-scheduler.net/

源码:https://github.com/quartznet/quartznet

示例:http://www.quartz-scheduler.net/documentation/quartz-2.x/quick-start.html

其实Quartz是一个完全由java编写的开源作业调度框架,Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。而Quartz.Net与NPOI一样是一个DoNet平台下的对应版本。如果您使用Java直接访问这里就好了http://www.quartz-scheduler.org/

使用Quartz.NET实现定时(计划)任务-第0张图片

搭建Quartz定时任务

安装

新建一个Quartz.NET.Apply项目, 使用Quartz.net之前,我们需要引入Nuget包,在Nuget包管理器中搜索Quartz,点击就可以安装了,当然也可以在包控制台里输入:

Install-Package Quartz

Quartz.NET现在已经支持.NET CORE项目。

实现IJob


    public class ConsoleWriteJobShowDatetime : IJob
    {
        public Task Execute(IJobExecutionContext context)
        {
            Console.WriteLine($"任务执行时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
            return Task.CompletedTask;
        }
    }

Execute就是我们要执行任务入口,此方法会根据相关配置执行一次或者多次。

任务触发器


    public class QuartzRunJob
    {
        public static async Task RunJob()
        {
            ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
            IScheduler scheduler = await schedulerFactory.GetScheduler();

            DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(DateTime.Now, 1);

            IJobDetail job = JobBuilder.Create()
                             .WithIdentity("ConsoleWriteJobShowDatetime","job")
                             .Build();

            ITrigger trigger = TriggerBuilder.Create()
                                       .StartAt(startTime)
                                       .WithIdentity("ConsoleWriteJobShowDatetime", "job")
                                       .WithSimpleSchedule(x=>x.WithIntervalInSeconds(1).RepeatForever())
                                       .Build();

            await scheduler.ScheduleJob(job, trigger);
            await scheduler.Start();
        }
    }

运行


 using Quartz.Impl;
using System;
using System.Threading.Tasks;

namespace Quartz.NET.Apply
{
    class Program
    {
        static void Main(string[] args)
        {
            Task job= QuartzRunJob.RunJob();
            job.Wait();
            Console.Read();
        }
    }
}


使用Quartz.NET实现定时(计划)任务-第1张图片

我们可以封装一个通用的方法,实现Ijob后就可以直接调用公共方法,使简单的定时任务更加方面快捷。内容如下:


public static class QuartzHelpers
{
           public static async Task StartAsync(TimeSpan ts, string jobName, string triggerName, string groupName) where T : IJob
        {
            DateTimeOffset runTime = DateBuilder.EvenSecondDate(DateTime.Now);
            IJobDetail job = JobBuilder.Create().WithIdentity(jobName, groupName).Build();
            ITrigger trigger = TriggerBuilder.Create().WithIdentity(triggerName, groupName).StartAt(runTime).WithSimpleSchedule(x => x.WithInterval(ts).RepeatForever()).Build();

            ISchedulerFactory factory = new StdSchedulerFactory();
            IScheduler scheduler = await factory.GetScheduler();
            await scheduler.ScheduleJob(job, trigger);
            await scheduler.Start();
        }

        public static async Task StartAsync(string cronExp, string jobName, string triggerName, string groupName) where T : IJob
        {
            DateTimeOffset runTime = DateBuilder.EvenSecondDate(DateTime.Now);
            IJobDetail job = JobBuilder.Create().WithIdentity(jobName, groupName).Build();
            ITrigger trigger = TriggerBuilder.Create().WithIdentity(triggerName, groupName).WithCronSchedule(cronExp).StartAt(runTime).Build();

            ISchedulerFactory factory = new StdSchedulerFactory();
            IScheduler scheduler = await factory.GetScheduler();
            await scheduler.ScheduleJob(job, trigger);
            await scheduler.Start();
        }
    }
}

简单调用如下:


using Quartz.Impl;
using System;
using System.Threading.Tasks;

namespace Quartz.NET.Apply
{
    class Program
    {
        static void Main(string[] args)
        {
            Task job= QuartzRunJob.RunJob();
            Task job1=QuartzHelpers.StartAsync(new TimeSpan(0,0,1),"job1", "triggerName1", "groupName");
            Task job2 = QuartzHelpers.StartAsync("0/1 * * * * ? ", "job2", "triggerName2", "groupName1");
            Console.Read();
        }
    }
}

以上代码出现的“0/1 * * * * ?”为Cron表达式。
使用Quartz.NET实现定时(计划)任务-第2张图片

Cron表达式

关于Quart.net说是用的Cron表达式的说明,请参考这篇内容:Quartz.net的Cron表达式

下载

示例下载



转载请注明:清风亦平凡 » 使用Quartz.NET实现定时(计划)任务

]]>
https://www.mlplus.net/2019/12/16/quartz-net-execute-task/feed/ 0
.net core 使用 IHostedService 实现定时执行的任务 https://www.mlplus.net/2019/12/07/net-core-ihostedservice-task/ https://www.mlplus.net/2019/12/07/net-core-ihostedservice-task/#respond Sat, 07 Dec 2019 00:00:00 +0000 https://www.mlplus.net/?p=1968 背景后台任务和计划任务最终可能需要在基于微服务的应用程序或任何类型的应用程序中实现。 使用微服务体系结构的区别在于,可以实现一个微服务进程/容器来托管这些后台任务,以便根据需要对其进行减少/增加,或者甚至可以确保它运行该微服务进程/容器的单个实例。

一般在 .NET Core 中,我们将这些类型的任务称为托管服务 ,因为它们是托管在主机/应用程序/微服务中的服务/逻辑。 请注意,在这种情况下,托管服务仅表示具有后台任务逻辑的类。

前言

从 .net core 2.0 开始,开始引入 IHostedService,可以通过 IHostedService 来实现后台任务,但是只能在 WebHost 的基础上使用。从 .net core 2.1 开始微软引入通用主机(Generic Host),使得我们可以在不使用 Web 的情况下,也可以使用 IHostedService 来实现 定时任务/Windows服务/后台任务,并且引入了一个 BackgroundService 抽象类来更方便的创建后台任务。 基本理念是,可以注册多个后台任务(托管服务),在 Web 主机或主机运行时在后台运行,如下图所示:

.net core 使用 IHostedService 实现定时执行的任务-第0张图片

ASP.NET Core 2.0 中的 WebHost(实现 IWebHost 的基类)是用于为进程提供 HTTP 服务器功能的基础结构项目,例如,如果正在实现 MVC Web 应用或 Web API 服务。 它提供 ASP.NET Core 中所有新的基础结构优点,使用户能够使用依赖关系注入,在请求管道中插入中间件等,并精确地将这些 IHostedServices 用于后台任务。

.NET Core 2.1 中引入了 Host(实现 IHost 的基类)。 基本上,Host 能让用户拥有与 WebHost(依赖项注入、托管服务等)相似的基础结构,但在这种情况下,只需拥有一个简单轻便的进程作为主机,与 MVC、Web API 或 HTTP 服务器功能无关。

因此,可以选择一个专用主机进程,也可使用 IHost 创建一个来专门处理托管服务,例如仅用于托管 IHostedServices 的微服务,或者可以选择性地扩展现有的 ASP.NET Core WebHost,例如现有的 ASP.NET Core Web API 或 MVC 应用。
每种方法都有优缺点,具体取决于业务和可伸缩性需求。 重要的是,如果后台任务与 HTTP (IWebHost) 无关,则应使用 IHost。

IHostedService 接口

IHostedService 后台任务的执行与应用程序(就此而言,为主机或微服务)的生存期相协调。 当应用程序启动时注册任务,当应用程序关闭时,有机会执行某些正常操作或清理。

始终可以启动后台线程来运行任何任务,而无需使用 IHostedService。 不同之处就在于应用的关闭时间,此时会直接终止线程,而没有机会执行正常的清理操作。

当注册 IHostedService 时,.NET Core 会在应用程序启动和停止期间分别调用 IHostedService 类型的 StartAsync() 和 StopAsync() 方法。 具体而言,即在服务器已启动并已触发 IApplicationLifetime.ApplicationStarted 后调用 start。

在 .NET Core 中定义的 IHostedService 如下所示。


namespace Microsoft.Extensions.Hosting
{
    //
    // 摘要:
    //     Defines methods for objects that are managed by the host.
    public interface IHostedService
    {
        //
        // 摘要:
        //     Triggered when the application host is ready to start the service.
        //
        // 参数:
        //   cancellationToken:
        //     Indicates that the start process has been aborted.
        Task StartAsync(CancellationToken cancellationToken);
        //
        // 摘要:
        //     Triggered when the application host is performing a graceful shutdown.
        //
        // 参数:
        //   cancellationToken:
        //     Indicates that the shutdown process should no longer be graceful.
        Task StopAsync(CancellationToken cancellationToken);
    }
}

如你所想,可以创建 IHostedService 的多个实现,并在 ConfigureService() 方法中将它们注册到 DI 容器中,如前所示。 所有这些托管服务将随应用程序/微服务一起启动和停止。

当主机触发 StopAsync() 方法时,需负责处理服务的停止操作。

使用从 BackgroundService 基类派生的自定义托管服务类来实现 IHostedService

可以从头开始创建自定义托管服务类并实现 IHostedService,因为在使用 .NET Core 2.0 时需执行这些操作。

但是,由于大多数后台任务在取消令牌管理和其他典型操作方面都有类似的需求,因此有一个非常方便且可以从中进行派生的抽象基类,名为 BackgroundService(自 .NET Core 2.1 起提供)。

该类提供设置后台任务所需的主要工作。

下一个代码是在 .NET Core 中实现的抽象 BackgroundService 基类。


// Copyright (c) .NET Foundation. Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
    private Task _executingTask;
    private readonly CancellationTokenSource _stoppingCts =
                                                   new CancellationTokenSource();

    protected abstract Task ExecuteAsync(CancellationToken stoppingToken);

    public virtual Task StartAsync(CancellationToken cancellationToken)
    {
        // Store the task we're executing
        _executingTask = ExecuteAsync(_stoppingCts.Token);

        // If the task is completed then return it,
        // this will bubble cancellation and failure to the caller
        if (_executingTask.IsCompleted)
        {
            return _executingTask;
        }

        // Otherwise it's running
        return Task.CompletedTask;
    }

    public virtual async Task StopAsync(CancellationToken cancellationToken)
    {
        // Stop called without start
        if (_executingTask == null)
        {
            return;
        }

        try
        {
            // Signal cancellation to the executing method
            _stoppingCts.Cancel();
        }
        finally
        {
            // Wait until the task completes or the stop token triggers
            await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
                                                          cancellationToken));
        }

    }

    public virtual void Dispose()
    {
        _stoppingCts.Cancel();
    }
}

从上一抽象基类派生时,得益于该继承的实现,用户只需在自定义的托管服务类中实现 ExecuteAsync() 方法 ,使用以上作为基类实现简单定时任务:


class TimedBackgroundService : BackgroundService
    {
        private Timer _timer;
        private int i = 0;

        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));           //每5秒开启一个线程
            return Task.CompletedTask;
        }

        private void DoWork(object state)
        {
            i++;
            int name = i;       //i表示这是第几个线程

            for (int j = 0; j < 100; j++)
            {
                Console.WriteLine($"这里是线程{name}:  正在执行j = {j}");
                Task.Delay(TimeSpan.FromSeconds(1)).Wait();        //等待1秒
            }
        }

        public override Task StartAsync(CancellationToken cancellationToken)
        {
            return ExecuteAsync(cancellationToken);
        }

        public override void Dispose()
        {
            base.Dispose();
            _timer?.Dispose();
        }
    }

向 WebHost 或 Host 添加一个或多个 IHostedServices 的方式是,通过 ASP.NET Core WebHost(或 .NET Core 2.1 及更高版本中的 Host)中的 AddHostedService  扩展方法对它们进行注册。 基本上,必须在常见的 Startup 类的 ConfigureServices() 方法中注册托管服务,如以下典型的 ASP.NET WebHost 中的代码所示。


public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddHostedService<TimedBackgroundService>();
}

.net core 使用 IHostedService 实现定时执行的任务-第1张图片

上图显示多个与 IHostedService 相关的类和接口的类图 。

类图: IWebHost 和 IHost 可以托管许多服务,这些服务从实现 IHostedService 的 BackgroundService 继承。

IHostedService实现一个的后台定时任务

根据所了解的信息,可以基于 IHostedService 实现一个简单的后台定时任务服务

public abstract class ScheduedService : IHostedService, IDisposable
    {
        protected readonly Timer _timer;
        private readonly TimeSpan _period;
        protected readonly ILogger logger;
        protected bool IsDisallowConcurrentExecution = false;

        protected ScheduedService(TimeSpan period)
        {
             logger = LogManager.GetCurrentClassLogger();
            _period = period;
            _timer = new Timer(Execute, null, Timeout.Infinite, 0);
        }

        public void Execute(object state = null)
        {
            if (IsDisallowConcurrentExecution)
            {
               _timer?.Change(Timeout.Infinite, 0);
            }
            try
            {
                ExecuteAsync().Wait();
            }
            catch (Exception ex)
            {
                logger.Error(ex, $"Execute exception:{ex}");
            }
            if (IsDisallowConcurrentExecution)
            {
                _timer?.Change(_period _period);
            }
        }

        protected abstract Task ExecuteAsync();

        public virtual void Dispose()
        {
            _timer?.Dispose();
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            logger.Info("Schedued Service is starting.");
            _timer?.Change(TimeSpan.FromSeconds(RandomHelper.Next(10)), _period);
            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            logger.Info("Schedued Service is stopping.");

            _timer?.Change(Timeout.Infinite, 0);

            return Task.CompletedTask;
        }
    }

根据上面抽象类使用Timer实现的后台定时任务类实现一个定时任务:


    public class ProcessExamsDataService : ScheduedService
    {
       
        public ProcessExamsDataService() : base(TimeSpan.FromSeconds(4))
        {
         
            IsDisallowConcurrentExecution = true;
     
        }

        protected override Task ExecuteAsync()
        {
            
            string guid = Guid.NewGuid().ToString("N");
            Console.WriteLine($"定时任务{guid}执行开始:{DateTime.Now.ToLongDateString()} {DateTime.Now.ToLongTimeString()}");
            System.Threading.Thread.Sleep(1000*20);
            Console.WriteLine($"定时任务{guid}执行结束:{DateTime.Now.ToLongDateString()} {DateTime.Now.ToLongTimeString()}");
   
            return Task.FromResult(true);
        }
    }

在程序启动的时候注册服务:

services.AddHostedService<ProcessExamsDataService>();

部署注意事项和要点

请务必注意,部署 ASP.NET Core WebHost 或 .NET Core Host 的方式可能会影响最终解决方案。 例如,如果在 IIS 或常规 Azure 应用服务上部署 WebHost,由于应用池回收,主机可能会被关闭。 但是,如果将主机作为容器部署到 Kubernetes 或 Service Fabric 等业务流程协调程序中,则可以控制主机的实时实例数量。 此外,还可以考虑云中专门针对这些方案的其他方法,例如 Azure Functions。 最后,如果需要服务一直处于运行状态并在 Windows Server 上部署,可以使用 Windows 服务。

但即使对于部署到应用池中的 WebHost,也存在如重新填充或刷新应用程序的内存中缓存这样的情况,这仍然适用。

IHostedService 接口为在 ASP.NET Core Web 应用程序(在 .NET Core 2.0 中)或任何进程/主机(从使用 IHost 的 .NET Core 2.1 开始)中启动后台任务提供了一种便捷方式。 其主要优势在于,当主机本身将要关闭时,可以有机会进行正常取消以清理后台任务的代码。



转载请注明:清风亦平凡 » .net core 使用 IHostedService 实现定时执行的任务

]]>
https://www.mlplus.net/2019/12/07/net-core-ihostedservice-task/feed/ 0
ML.NET 来对鸢尾花分类 https://www.mlplus.net/2019/05/27/mlnetiris/ https://www.mlplus.net/2019/05/27/mlnetiris/#respond Mon, 27 May 2019 01:10:56 +0000 https://www.mlplus.net/?p=1651 主要目的演示如何使用 ML.NET 为鸢尾花数据集构建聚类分析模型。了解ML.NET使用基本流程。

问题

问题的本质即基于花卉特征将鸢尾花数据归入不同的组。 这些特征包括:花萼的长度和宽度以及花瓣的长度和宽度。 需通过这些特征了解数据集的结构,并预测数据实例与此结构的拟合相似分类。

创建控制台应用程序

  1. 打开 Visual Studio。 从菜单栏中选择“文件” > “新建” > “项目”。 在“新项目”对话框中,依次选择“Visual C#”和“.NET Core”节点。 然后,选择“控制台应用程序(.NET Core)”项目模板。 在“名称”文本框中,键入“MLNet”,然后选择“确定”按钮
  2. 在项目中创建一个名为“数据”的目录来保存数据集和模型文件:
    在“解决方案资源管理器”中,右键单击项目,然后选择“添加” > “新文件夹”。 键入“Data”,然后按 Enter。
  3. 安装“Microsoft.ML NuGet”包:
    在“解决方案资源管理器”中,右键单击项目,然后选择“管理 NuGet 包”。 选择“nuget.org”作为包源,然后选择“浏览”选项卡并搜索“Microsoft.ML”,选择列表中的“v0.11.0”包,再选择“安装”按钮。 选择“预览更改”对话框上的“确定”按钮,如果你同意所列包的许可条款,则选择“接受许可”对话框上的“我接受”按钮。

准备数据

  1. 下载 iris.data 数据集并将其保存至在上一步中创建的“data”文件夹。 若要详细了解鸢尾花数据集,请参阅鸢尾花数据集维基百科页面,以及该数据集的源鸢尾花数据集页面。
  2. 在“解决方案资源管理器”中,右键单击“iris.data”文件并选择“属性” 在“高级”下,将“复制到输出目录”的值更改为“如果较新则复制”。

该 iris.data 文件包含五列,分别代表以下内容:

  • 花萼长度(厘米)
  • 花萼宽度(厘米)
  • 花瓣长度(厘米)
  • 花瓣宽度(厘米)
  • 鸢尾花类型

创建数据类

创建输入数据和预测类:

  1. 在“解决方案资源管理器”中,右键单击项目,然后选择“添加” > “新项”。
  2. 在“添加新项”对话框中,选择“类”并将“名称”字段更改为“IrisData.cs”。 然后,选择“添加”按钮。
  3. 将以下 using 指令添加到新文件:
    using Microsoft.ML.Data;

删除现有类定义并向“IrisData.cs”文件添加以下代码,其中定义了两个类 IrisData 和 ClusterPrediction


    /// <summary>
    /// 数据类
    /// </summary>
    public class IrisData
    {
        /// <summary>
        /// 花萼长度
        /// </summary>
        [LoadColumn(0)]
        public float SepalLength;
        /// <summary>
        /// 花萼宽度
        /// </summary>
        [LoadColumn(1)]
        public float SepalWidth;
        /// <summary>
        /// 花瓣长度
        /// </summary>
        [LoadColumn(2)]
        public float PetalLength;
        /// <summary>
        /// 花瓣宽度
        /// </summary>
        [LoadColumn(3)]
        public float PetalWidth;
        /// <summary>
        /// 预测类型
        /// </summary>
        [LoadColumn(4)]
        public string FlowerType;
    }

    /// <summary>
    /// 预测类
    /// </summary>
    public class ClusterPrediction
    {
        /// <summary>
        /// 包含所预测的群集
        /// </summary>
        //[ColumnName("PredictedLabel")]
        //public string PredictedClusterId;

        //预测输出类型
        public string PredictedLabelPlant;
        /// <summary>
        /// 列包含一个数组,该数组中的数与群集形心之间的距离为欧氏距离的平方。
        /// 该数组的长度等于群集数。
        /// </summary>
        [ColumnName("Score")]
        public float[] Distances;
    }

IrisData 是输入数据类,并且具有针对数据集每个特征的定义。 使用 LoadColumn 属性在数据集文件中指定源列的索引。ClusterPrediction 类表示应用到 IrisData 实例的聚类分析模型的输出。

 使用 ColumnName属性将 Distances 字段绑定至 Score 列。PredictedLabelPlant做为预测显示鸢尾花类型。 在聚类分析任务中,这些列具有以下含义:

  • Score 列包含一个数组,该数组中的数与群集形心之间的距离为欧氏距离的平方。 该数组的长度等于群集数。
使用 float 类型来表示输入和预测数据类中的浮点值。

定义数据和模型路径

返回到 Program.cs 文件并添加两个字段,以保存数据集文件以及用于保存模型的文件的路径:

  • _dataPath 包含具有用于定型模型的数据集的文件的路径。
  • _modelPath 包含用于存储定型模型的文件的路径。

将以下代码添加到 Main 方法上方,以指定这些路径:


static readonly string _dataPath = Path.Combine(Environment.CurrentDirectory, "Data", "iris.data");
static readonly string _modelPath = Path.Combine(Environment.CurrentDirectory, "Data", "IrisClusteringModel.zip");

要编译前面的代码,请将以下 using 指令添加到 Program.cs 文件顶部:


using System;
using System.IO;

创建 ML 上下文

将以下附加 using 指令添加到 Program.cs 文件顶部:

using Microsoft.ML;
using Microsoft.ML.Data;

在 Main 方法中,请使用以下代码替换 Console.WriteLine("Hello World!"); 行:

var mlContext = new MLContext(seed: 0);

Microsoft.ML.MLContext 类表示机器学习环境,并提供用于数据加载、模型定型、预测和其他任务的日志记录和入口点的机制。 这在概念上相当于在实体框架中使用 DbContext

设置数据加载

将以下代码添加到 Main 方法以设置加载数据的方式:

IDataView dataView = mlContext.Data.LoadFromTextFile<IrisData>(_dataPath, hasHeader: false, separatorChar: ',');

泛型 MLContext.Data.LoadFromTextFile 扩展方法根据所提供的 IrisData 类型推断数据集架构,并返回可用作转换器输入的 IDataView

创建学习管道

聚类分析任务的学习管道包含两个以下步骤:

  • 将加载的列连接到“Features”列,由聚类分析训练程序使用;
  • 借助 KMeansTrainer 训练程序使用 k – 平均值 + + 聚类分析算法来定型模型。

将以下代码添加到 Main 方法中:


string featuresColumnName = "Features";
var pipeline = mlContext.Transforms
                .Concatenate(featuresColumnName, "SepalLength", "SepalWidth", "PetalLength", "PetalWidth")
                .Append(mlContext.Transforms.Normalize(featuresColumnName))
                .Append(mlContext.Transforms.Conversion.MapValueToKey("Label", "FlowerType"), TransformerScope.TrainTest)
                .AppendCacheCheckpoint(mlContext)
                .Append(mlContext.MulticlassClassification.Trainers.StochasticDualCoordinateAscent())
                .Append(mlContext.Transforms.Conversion.MapKeyToValue(("PredictedLabelPlant", "PredictedLabel")))
                .Append(mlContext.Clustering.Trainers.KMeans(featuresColumnName, clustersCount: 3));

该代码指定该数据集应拆分为三个群集。

定型模型

前述部分中添加的步骤准备了用于定型的管道,但尚未执行。 将以下行添加到 Main 方法以执行数据加载和模型定型:

var model = pipeline.Fit(dataView);

保存模型

此时,你具有可以集成到任何现有或新 .NET 应用程序的模型。 要将模型保存为 .zip 文件,请将以下代码添加到 Main 方法中

            using (var fileStream = new FileStream(_modelPath, FileMode.Create, FileAccess.Write, FileShare.Write))
            {
                mlContext.Model.Save(model, fileStream);
            }

使用预测模型

要进行预测,请使用通过转换器管道获取输入类型实例和生成输出类型实例的 PredictionEngine<TSrc,TDst> 类。 将以下行添加到 Main 方法以创建该类的实例:

var predictor=model.CreatePredictionEngine<IrisData, ClusterPrediction>(mlContext);

将 TestIrisData 类创建到房屋测试数据实例:

  1. 在“解决方案资源管理器”中,右键单击项目,然后选择“添加” > “新项”。
  2. 在“添加新项”对话框中,选择“类”并将“名称”字段更改为“TestIrisData.cs”。 然后,选择“添加”按钮。
  3. 将类修改为静态,如下面的示例所示:
    static class TestIrisData
    {
        internal static readonly IrisData Setosa = new IrisData
        {
            SepalLength = 5.1f,
            SepalWidth = 3.5f,
            PetalLength = 1.4f,
            PetalWidth = 0.2f
        };
    }

此类中的一个鸢尾花数据实例。 可以添加其他方案来体验此模型。

若要查找指定项所属的群集,请返回至 Program.cs 文件并将以下代码添加进 Main 方法:

            Console.WriteLine($"Cluster: {prediction.PredictedLabelPlant}");
            Console.WriteLine($"Distances: {string.Join(" ", prediction.Distances)}");
            Console.Read();

运行该程序以查看预测到哪个分类,以及从该实例到群集形心的距离的平方值。 结果应如下所示:

Cluster: Iris-setosa
Distances: 0.9660053 0.0008847713 0.4192338

已成功地生成用于鸢尾花聚类分析的机器学习模型并将其用于预测。

鸢尾花预测结果
鸢尾花预测结果

示例下载

鸢尾花分类预测示例



转载请注明:清风亦平凡 » ML.NET 来对鸢尾花分类

]]>
https://www.mlplus.net/2019/05/27/mlnetiris/feed/ 0
.net core配置信息appsettings读取 https://www.mlplus.net/2019/03/12/netcoreappsettingsread/ https://www.mlplus.net/2019/03/12/netcoreappsettingsread/#respond Tue, 12 Mar 2019 01:00:36 +0000 https://www.mlplus.net/?p=1421 因为使用.net core写了一些简单的控制台应用程序,并且需要一些配置信息。于是想是用.net framework中的 ConfigurationManager进行处理,发ConfigurationManager在.net core 中不被支持。

.net core配置说明

.NET CORE 配置主要涉及三个对象,它们分别是Configuration、ConfigurationBuilder和ConfigurationProvider,Configuration对象承载着在编程过程中使用的配置信息,ConfigurationProvider则是配置信息原始数据源的提供者,两者之间沟通由ConfigurationBuilder来完成,它利用ConfigurationProvider提取源数据将其转换为Configuration对象。


IConfigurationRoot Configuration { get; set; }
 var builder = new ConfigurationBuilder()
           .SetBasePath(Directory.GetCurrentDirectory())
           .AddJsonFile("appsettings.json",optional:true,reloadOnChange:true);
            Configuration = builder.Build();

键值读取

Configuration 类只是一个提供了读写名/值对能力的 Providers 集合。通过ConfigurationProvider数据源提供者,配置生成对应的Configuration对应,可以获取名/值对。采用appsettings.json作为数据源,我们配置如下的数据。

{
  "version": "1.0.0",
  "AppConfig": {
    "SiteUrl": [
      {
        "AppName": "清风博客",
        "CheckUrl": "https://www.mlplus.net",
        "SendMobile": "130********"
      },
      {
        "AppName": "蚂蚁部落",
        "CheckUrl": "http://www.softwhy.com",
        "SendMobile": "130********"
      }
    ]
  }
}

对于存储在一个有层次的结构中,尤其是使用外部文件(如 JSON、XML、INI)时,可以使用以 : 符号分隔(从层次结构的根开始)的键来取回配置值。使用生成的Configuration读取version和AppConfig的值,如下所示:


Configuration.GetSection("version").Value;
Configuration["version"];

对象读取

将一个对象的属性视为它的子节点,这样对象同样具有类似于Configuration对象的树形层次化结构。可以根据配置的结构来定义这个Option类型,那么Option类型的属性成员将与某个配置节具有一一对应的关系,从而将配置信息绑定为一个具体的Option对象。.NET Core针对配置的Option模型(OptionModel)帮助我们实现了从配置到Option对象之间的绑定,Option模型实现在Microsoft.Extensions.OptionsModel这个NuGet包。创建一个简单的 MyOptions 类如下所示:


    public class AppConfig
    {
        public IList SiteUrl { get; set; }
    }
    public class Site
    {
        public string AppName { get; set; }
        public string CheckUrl { get; set; }
        public string SendMobile { get; set; }
    }

通过调用 Configure 将一个 IConfigureOptions 服务加入服务容器,是为了之后应用程序或框架能通过 IOptions 服务来获取配置选项。这里需要.net core 的依赖注入Microsoft.Extensions.DependencyInjection。


using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
    public sealed class ConfigUtil
    {
        private IConfigurationRoot Configuration { get; set; }
        private static ConfigUtil configUtil = null;
        private static readonly object locker = new object();
        private AppConfig appseting;
        public AppConfig AppSeting { get { return appseting; } }
        public static ConfigUtil GetConfigUtil()
        {
            if (configUtil == null)
            {
                lock (locker)
                {
                    if (configUtil == null)
                    {
                        configUtil = new ConfigUtil();
                    }
                }
            }
            return configUtil;
        }
        private ConfigUtil()
        {
            string path = Directory.GetCurrentDirectory();
            var builder = new ConfigurationBuilder()
                .SetBasePath(path)
                .AddJsonFile("appsettings.json",true,true);
            Configuration = builder.Build();
            var sp = new ServiceCollection()
                .AddOptions()
                .Configure(Configuration.GetSection("AppConfig"))
                .BuildServiceProvider();
           var config=sp.GetService>();
            appseting = config.Value;
        }
    }




转载请注明:清风亦平凡 » .net core配置信息appsettings读取

]]>
https://www.mlplus.net/2019/03/12/netcoreappsettingsread/feed/ 0
HttpClient在.net Core 中报字符集报错的问题 https://www.mlplus.net/2019/03/10/httpclientcorenotsuportcharacter/ https://www.mlplus.net/2019/03/10/httpclientcorenotsuportcharacter/#respond Sun, 10 Mar 2019 14:45:43 +0000 https://www.mlplus.net/?p=1412 在.net core 中使用了HttpClient库进行网络访问,有些网站成功,有些网络失败。报错内容如下:

The character set provided in ContentType is invalid. Cannot read content as string using an invalid character set.

‘gbk’ is not a supported encoding name. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method.Parameter name: name

报错
报错
详细错误
详细错误

“gbk”字符集不被支持,需要 Encoding.RegisterProvider 进行注册。需要下载包
System.Text.Encoding.CodePages来进行处理。使用如下:

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

使用以上代码进行注册后错误就不再出现。.net core处理还真的有少许麻烦。



转载请注明:清风亦平凡 » HttpClient在.net Core 中报字符集报错的问题

]]>
https://www.mlplus.net/2019/03/10/httpclientcorenotsuportcharacter/feed/ 0
ASP.NET Core简单的文件上传 https://www.mlplus.net/2019/01/10/aspnetcorefileupload/ https://www.mlplus.net/2019/01/10/aspnetcorefileupload/#respond Thu, 10 Jan 2019 01:00:29 +0000 https://www.mlplus.net/?p=1336 朋友发消息说他老婆公司资料无法拷贝出来,U盘以及常见的网盘全部无效。问我有没有其他办法,于是就使用asp.net core做了一个最简单的文件上传来试一试。理论上应该可行,因为新的上传网站以及页面元素没有在他们公司网监出现过,死马当作活马医吧!离职最后一天的最后2个小时了,时间紧任务哦!
对话
对话

ASP.NET MVC操作支持使用简单模型绑定对较小文件进行单个或者多个文件上传。

使用模型绑定上传小文件

要上传较小的文件,可以使用多部分HTML表单或使用JavaScript构建POST请求。


<form action="/Home/UploadFiles" method="post" name="upload" enctype="multipart/form-data">
    <div class="form-group">
        <div class="col-md-10">
            <p>选择文件:</p>
            <input type="file" name="files" multiple />
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-10">
            <input type="submit" value="上传" />
        </div>
    </div>
</form>

为了支持文件上传,HTML表单必须指定enctype的multipart/form-data。

以上HTML代码最终呈现如下图:

ASP.NET Core简单的文件上传-第1张图片

可以使用IFormFile接口通过模型绑定访问上载到服务器的各个文件。具有以下结构:IFormFile


public interface IFormFile
{
    string ContentType { get; }
    string ContentDisposition { get; }
    IHeaderDictionary Headers { get; }
    long Length { get; }
    string Name { get; }
    string FileName { get; }
    Stream OpenReadStream();
    void CopyTo(Stream target);
    Task CopyToAsync(Stream target, CancellationToken cancellationToken = null);
}

使用模型绑定以及IFormFile接口上传文件时候,action方法可以接受单个(
IFormFile )或多个( IEnumerable )文件。以下循环处理一个或多个
文件上传,将它们保存到本地磁盘,并返回上载的文件的总数和大小。

添加必要引用


using Microsoft.AspNetCore.Hosting;

添加私有成员


 private IHostingEnvironment hostingEnv;

在构造函数中为成员hostingEnv赋值,.net core 默认的依赖注入。


        public HomeController(IHostingEnvironment hostingEnvironment)
        {
            hostingEnv = hostingEnvironment;
        }

接收上传文件的处理方法


         public IActionResult UploadFiles(List files)
        {
            long size = 0;
            foreach (var file in files)
            {
                var fileName = ContentDispositionHeaderValue
                                .Parse(file.ContentDisposition)
                                .FileName
                                .Trim('"');
                string pathFolder =Path.Combine(hostingEnv.WebRootPath,$"upload");
                if (!Directory.Exists(pathFolder))
                {
                    Directory.CreateDirectory(pathFolder);
                }
                fileName = Path.Combine(pathFolder,fileName);
                size += file.Length;
                using (FileStream fs = System.IO.File.Create(fileName))
                {
                    file.CopyTo(fs);
                    fs.Flush();
                }
            }
            ViewBag.Message = $"{files.Count}个文件 /{size}字节上传成功!";
            return View();
        }
filename未经过验证,这里仅仅做为试例。如果生产环境使用,请自行对文件进行安全性验证效验。

上传文件返回页面呈现如下:

成功上传文件
文件上传成功



转载请注明:清风亦平凡 » ASP.NET Core简单的文件上传

]]>
https://www.mlplus.net/2019/01/10/aspnetcorefileupload/feed/ 0
.net core的MemoryCache缓存存取MemoryCacheHelper https://www.mlplus.net/2018/10/14/netcorememorycachehelper/ https://www.mlplus.net/2018/10/14/netcorememorycachehelper/#respond Sun, 14 Oct 2018 01:00:19 +0000 http://www.skyfinder.cc/?p=586 /// <summary> /// 缓存帮助类 /// </summary> public class MemoryCacheHelper { private static readonly MemoryCache Cache = new MemoryCache(new MemoryCacheOptions()); /// <summary> /// 验证缓存项是否存在 /// </summary> /// <param name="key">缓存Key</param> /// <returns></returns> public static bool Exists(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return Cache.TryGetValue(key, out _); } /// <summary> /// 添加缓存 /// </summary> /// <param name="key">缓存Key</param> /// <param name="value">缓存Value</param> /// <param name="expiresSliding">滑动过期时长(如果在过期时间内有操作,则以当前时间点延长过期时间)</param> /// <param name="expiressAbsoulte">绝对过期时长</param> /// <returns></returns> public static bool Set(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte) { if (key == null) throw new ArgumentNullException(nameof(key)); if (value == null) throw new ArgumentNullException(nameof(value)); Cache.Set(key, value, new MemoryCacheEntryOptions().SetSlidingExpiration(expiresSliding) .SetAbsoluteExpiration(expiressAbsoulte)); return Exists(key); } /// <summary> /// 添加缓存 /// </summary> /// <param name="key">缓存Key</param> /// <param name="value">缓存Value</param> /// <param name="expiresIn">缓存时长</param> /// <param name="isSliding">是否滑动过期(如果在过期时间内有操作,则以当前时间点延长过期时间)</param> /// <returns></returns> public static bool Set(string key, object value, TimeSpan expiresIn, bool isSliding = false) { if (key == null) throw new ArgumentNullException(nameof(key)); if (value == null) throw new ArgumentNullException(nameof(value)); Cache.Set(key, value, isSliding ? new MemoryCacheEntryOptions().SetSlidingExpiration(expiresIn) : new MemoryCacheEntryOptions().SetAbsoluteExpiration(expiresIn)); return Exists(key); } #region 删除缓存 /// <summary> /// 删除缓存 /// </summary> /// <param name="key">缓存Key</param> /// <returns></returns> public static void Remove(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); Cache.Remove(key); } /// <summary> /// 批量删除缓存 /// </summary> /// <returns></returns> public static void RemoveAll(IEnumerable<string> keys) { if (keys == null) throw new ArgumentNullException(nameof(keys)); keys.ToList().ForEach(item => Cache.Remove(item)); } #endregion #region 获取缓存 /// <summary> /// 获取缓存 /// </summary> /// <param name="key">缓存Key</param> /// <returns></returns> public static T Get<T>(string key) where T : class { if (key == null) throw new ArgumentNullException(nameof(key)); return Cache.Get(key) as T; } /// <summary> /// 获取缓存 /// </summary> /// <param name="key">缓存Key</param> /// <returns></returns> public static object Get(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); return Cache.Get(key); } /// <summary> /// 获取缓存集合 /// </summary> /// <param name="keys">缓存Key集合</param> /// <returns></returns> public static IDictionary<string, object> GetAll(IEnumerable<string> keys) { if (keys == null) throw new ArgumentNullException(nameof(keys)); var dict = new Dictionary<string, object>(); keys.ToList().ForEach(item => dict.Add(item, Cache.Get(item))); return dict; } #endregion /// <summary> /// 删除所有缓存 /// </summary> public static void RemoveCacheAll() { var l = GetCacheKeys(); foreach (var s in l) { Remove(s); } } /// <summary> /// 删除匹配到的缓存 /// </summary> /// <param name="pattern"></param> /// <returns></returns> public static void RemoveCacheRegex(string pattern) { IList<string> l = SearchCacheRegex(pattern); foreach (var s in l) { Remove(s); } } /// <summary> /// 搜索 匹配到的缓存 /// </summary> /// <param name="pattern"></param> /// <returns></returns> public static IList<string> SearchCacheRegex(string pattern) { var cacheKeys = GetCacheKeys(); var l = cacheKeys.Where(k => Regex.IsMatch(k, pattern)).ToList(); return l.AsReadOnly(); } /// <summary> /// 获取所有缓存键 /// </summary> /// <returns></returns> public static List<string> GetCacheKeys() { const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; var entries = Cache.GetType().GetField("_entries", flags).GetValue(Cache); var cacheItems = entries as IDictionary; var keys = new List<string>(); if (cacheItems == null) return keys; foreach (DictionaryEntry cacheItem in cacheItems) { keys.Add(cacheItem.Key.ToString()); } return keys; } }



转载请注明:清风亦平凡 » .net core的MemoryCache缓存存取MemoryCacheHelper

]]>
https://www.mlplus.net/2018/10/14/netcorememorycachehelper/feed/ 0
.net Core 中DateTime在Linux Docker中与Windows时间不一致 https://www.mlplus.net/2018/10/01/dotnetcoredatetimenotunified/ https://www.mlplus.net/2018/10/01/dotnetcoredatetimenotunified/#respond Sun, 30 Sep 2018 23:00:33 +0000 http://www.skyfinder.cc/?p=477 最近写了一个.net core项目,部署到CentOS并在docker上运行的时候,发现DateTime.Now获取的时间与Windows不一致(定时执行的任务,晚了8个小时),在Windows中可以正确的获得本地时间,而在Linux CentOS 的Docker环境中获取的时间少了8个小时。通过Linux命令查看本机时间,本地时间正确,但.net core在Docker运行获取时间依然是少8个小时。猜测可能是时区的问题,然后就找到了NodaTime的一个库进行获取统一时区的时间,完成后Linux CentOS的Docker环境上获取的时间就与Windows中保持了一致。

NodaTime库需要下载相关包,也可以直接使用Nuget下载。

项目地址

nodatime项目git开源nodatime官网

新增一个DateTime扩展方法:

public class TimeUtil
    {
        public static DateTime GetCstDateTime()
        {
            Instant now = SystemClock.Instance.GetCurrentInstant();
            var shanghaiZone = DateTimeZoneProviders.Tzdb["Asia/Shanghai"];
            return now.InZone(shanghaiZone).ToDateTimeUnspecified();
        }

    }
    public static class DateTimeExtentions
    {
        public static DateTime ToCstTime(this DateTime time)
        {
            return TimeUtil.GetCstDateTime();
        }
    }
   

在程序中获取时间都通过如下方法,即可实现在Windows和Linux保持统一:

DateTime.Now.ToCstTime();

除了以上方式之外,可以将Linux时间和Docker容器进行同步。具体详情,请参考。docker容器与Linux主机环境获取时间不一致



转载请注明:清风亦平凡 » .net Core 中DateTime在Linux Docker中与Windows时间不一致

]]>
https://www.mlplus.net/2018/10/01/dotnetcoredatetimenotunified/feed/ 0