.NET 中安全高效跨平台的模板引擎 Fluid 使用文档

Liquid 是一门开源的模板语言,由 Shopify 创造并用 Ruby 实现。它是 Shopify 主题的主要构成部分,并且被用于加载店铺系统的动态内容。它是一种安全的模板语言,对于非程序员的受众来说也非常容易理解。

Fluid 是一个基于 Liquid 模板语言的开源 .NET 模板引擎。由 Sébastien Ros 开发并发布在 GitHub 上,NuGet 上的引用地址是:https://www.nuget.org/packages/Fluid.Core 。

Liquid 模板语言

如果你对 Liquid 模板语言还不了解,可以先行查看笔者翻译的 Liquid 模板语言中文文档:https://www.coderbusy.com/archives/1219.html 。Liquid 模板的文件扩展名为 .liquid ,假如我们有以下 Liquid 模板:

<ul id="products">{% for product in products %}<li><h2>{{product.name}}</h2>Only {{product.price | price }}{{product.description | prettyprint | paragraph }}</li>{% endfor %}
</ul>

该模板被渲染后将会产生以下输出:

<ul id="products"><li><h2>Apple</h2>$329Flat-out fun.</li><li><h2>Orange</h2>$25Colorful.</li><li><h2>Banana</h2>$99Peel it.</li>
</ul>

在项目中使用 Fluid

你可以直接在项目中引用 NuGet 包。

Hello World

C# 代码:

var parser = new FluidParser();var model = new { Firstname = "Bill", Lastname = "Gates" };
var source = "Hello {{ Firstname }} {{ Lastname }}";if (parser.TryParse(source, out var template, out var error))
{var context = new TemplateContext(model);Console.WriteLine(template.Render(context));
}
else
{Console.WriteLine($"Error: {error}");
}

运行结果:

Hello Bill Gates

线程安全

FluidParser 类型是线程安全的,可以被整个应用程序共享。常规做法是将其定义为一个本地的静态变量:

private static readonly FluidParser _parser = new FluidParser();

IFluidTemplate 类型也是线程安全的,其实例可以被缓存起来,并被多个线程并发使用。

TemplateContext 不是线程安全的,每次使用时都应该新建一个实例。

过滤器

过滤器改变 Liquid 对象的输出,通过一个 | 符号分隔。

{{ "/my/fancy/url" | append: ".html" }}
/my/fancy/url.html

多个过滤器可以共同作用于同一个输出,并按照从左到右的顺序执行。

{{ "adam!" | capitalize | prepend: "Hello " }}
Hello Adam!

Fluid 实现了 Liquid 所有的标准过滤器,同时支持自定义过滤器。

自定义的过滤器可以是同步的,也可以是异步的。过滤器被定义为一个委托,该委托接收一个输入,一个参数集合和当前的渲染上下文。以下是一个实现文字转小写过滤器的代码:

public static ValueTask<FluidValue> Downcase(FluidValue input, FilterArguments arguments, TemplateContext context)
{return new StringValue(input.ToStringValue().ToLower());
}

过滤器需要注册在 TemplateOptions 对象上,该 Options 对象可以被重用。

var options = new TemplateOptions();
options.Filters.AddFilter('downcase', Downcase);var context = new TemplateContext(options);

成员属性白名单

Liquid 是一种安全的模板语言,它只允许白名单中的成员属性被访问,并且成员属性不能被改变。白名单成员需要被加入到 TemplateOptions.MemberAccessStrategy 中。

另外,MemberAccessStrategy 可以被设置为 UnsafeMemberAccessStrategy ,这将允许模板语言访问所有成员属性。

将特定类型加入白名单

下面的代码会将 Person 类型加入白名单,这意味着该类型下所有公开的字段和属性都可以被模板读取:

var options = new TemplateOptions();
options.MemberAccessStrategy.Register<Person>();

注意:当用 new TemplateContext(model) 传递一个模型时,模型对象会被自动加入白名单。该行为可以通过调用 new TemplateContext(model, false) 来禁用。

将特定成员加入白名单

下面的代码只允许模板读取特定的成员:

var options = new TemplateOptions();
options.MemberAccessStrategy.Register<Person>("Firstname", "Lastname");

访问拦截

Fluid 提供了一种可以在运行时拦截属性访问的方式,通过该方式你可以允许访问成员并返回自定义值,或者阻止访问。

下面的代码演示了如何拦截对 JObject 的调用并返回相应的属性:

var options = new TemplateOptions();
options.MemberAccessStrategy.Register<JObject, object>((obj, name) => obj[name]);

继承处理

当被注册到白名单中的类型包含继承关系时,情况将变得复杂:默认情况下被注册类型的父类实例成员将不能被访问,子类实例中的派生成员可以被访问。

类型定义

public class Animal
{public string Type { get; set; }
}
public class Human : Animal
{public string Name { get; set; }public Int32 Age { get; set; }
}
public class Boy : Human
{public string Toys { get; set; }
}

测试代码

var parser = new FluidParser();var model = new { };
var source = @"Animal=Type:{{Animal.Type}}Human=Type:{{Human.Type}},Name:{{Human.Name}},Age:{{Human.Age}}Boy=Type:{{Boy.Type}},Name:{{Boy.Name}},Age:{{Boy.Age}},Toys:{{Boy.Toys}}";var options = new Fluid.TemplateOptions { };
options.MemberAccessStrategy.Register(typeof(Human));if (parser.TryParse(source, out var template, out var error))
{var context = new TemplateContext(model, options);context.SetValue("Animal", new Animal { Type = "Human" });context.SetValue("Human", new Human { Type = "Human", Name = "码农很忙", Age = 30 });context.SetValue("Boy", new Boy { Type = "Human", Name = "小明", Age = 10, Toys = "小汽车" });Console.WriteLine(template.Render(context));
}
else
{Console.WriteLine($"Error: {error}");
}

输出结果

 Animal=Type:Human=Type:Human,Name:码农很忙,Age:30Boy=Type:Human,Name:小明,Age:10,Toys:

成员名称风格

默认情况下,注册对象的属性是区分大小写的,并按照其源代码中的内容进行注册。例如,属性 FirstName 将使用 {{ p.FirstName }} 访问。

同时,也可以配置使用不同的名称风格。比如小驼峰(firstName)或者蛇形(first_name)风格。

以下代码可以配置为使用小驼峰风格:

var options = new TemplateOptions();
options.MemberAccessStrategy.MemberNameStrategy = MemberNameStrategies.CamelCase;

执行限制

限制模板递归

当调用 {% include 'sub-template' %} 语句时,有些模板可能会产生无限的递归,从而阻塞服务器。为了防止这种情况,TemplateOptions 类定义了一个默认的 MaxRecursion = 100 ,防止模板的深度超过100 。

限制模板执行

模板可能会不经意地创建无限循环,这可能会使服务器无限期地运行而堵塞。为了防止这种情况,TemplateOptions 类定义了一个默认的 MaxSteps。默认情况下,这个值没有被设置。

转换 CLR 类型

当一个对象在模板中被操作时,它会被转换为一个特定的 FluidValue 实例。该机制与 JavaScript 中的动态类型系统有些类似。

在Liquid中,它们可以是数字、字符串、布尔值、数组或字典。Fluid会自动将CLR类型转换为相应的Liquid类型,同时也提供专门的类型。

为了能够定制这种转换,你可以添加自定义的转换器。

添加一个值转换器

当转换逻辑不能直接从一个对象的类型中推断出来时,可以使用一个值转换器。

值转换器可以返回:

  • null 代表值不能被转换。

  • 一个 FluidValue 实例,代表停止进一步的转换,并使用这个值。

  • 其他对象实例,代表需要继续使用自定义和内部类型映射进行转换。

以下的代码演示了如何将实现接口的任意实例转换为自定义字符串值:

var options = new TemplateOptions();
options.ValueConverters.Add((value) => value is IUser user ? user.Name : null);

注意:类型映射的定义是全局的,对整个程序都生效。

在模型中使用 Json.NET 对象

Json.NET 中使用的类并不像类那样有直接命名的属性,这使得它们在 Liquid 模板中无法开箱使用。

为了弥补这一点,我们可以配置 Fluid,将名称映射为 JObject 属性,并将 JValue 对象转换为 Fluid 所使用的对象。

var options = new TemplateOptions();// When a property of a JObject value is accessed, try to look into its properties
options.MemberAccessStrategy.Register<JObject, object>((source, name) => source[name]);// Convert JToken to FluidValue
options.ValueConverters.Add(x => x is JObject o ? new ObjectValue(o) : null);
options.ValueConverters.Add(x => x is JValue v ? v.Value : null);var model = JObject.Parse("{\"Name\": \"Bill\"}");var parser = new FluidParser();parser.TryParse("His name is {{ Name }}", out var template);
var context = new TemplateContext(model, options);Console.WriteLine(template.Render(context));

编码

默认情况下,Fluid 不会对输出进行编码。在模板上调用 Render() 或 RenderAsync() 时可以指定编码器。

HTML 编码

可以使用 System.Text.Encodings.Web.HtmlEncoder.Default 实例来渲染 HTML 编码的模板。

该编码被 MVC View engine 作为默认编码使用。

在上下文中禁用编码

当一个编码器被定义后,你可以使用一个特殊的 raw 过滤器或 {% raw %} … {% endraw %} 标签来阻止一个值被编码。例如,如果你知道这个内容是 HTML 并且是安全的:

代码

{% assign html = '<em>This is some html</em>' %}Encoded: {{ html }}
Not encoded: {{ html | raw }

结果

&lt;em%gt;This is some html&lt;/em%gt;
<em>This is some html</em>

Capture 块不会被二次编码

当使用 capture 块时,内部内容被标记为预编码,如果在 {{  }} 标签中使用,就不会被再次编码。

代码

{% capture breaktag %}<br />{% endcapture %}{{ breaktag }}

结果

<br />

本地化

默认情况下,模板使用不变的文化( Invariant culture ,对应 CultureInfo.InvariantCulture 。)进行渲染,这样在不同的系统中可以得到一致的结果。这项设置在输出日期、时间和数字时很重要。

即便如此,也可以使用 TemplateContext.CultureInfo 属性来定义渲染模板时使用的文化信息(你也可以称之为多语言信息)。

代码

var options = new TemplateOptions();
options.CultureInfo = new CultureInfo("en-US");
var context = new TemplateContext(options);
var result = template.Render(context);

模板

{{ 1234.56 }}
{{ "now" | date: "%v" }}

结果

1234.56
Tuesday, August 1, 2017

时区

系统时区

TemplateOptions 和 TemplateContext 提供了一个定义默认时区的属性,以便在解析日期和时间时使用。该属性的默认值是当前系统的时区。当日期和时间被解析而没有指定时区时,将会使用默认时区。设置一个自定义的时区可以防止在不同环境(数据中心)时产生不同的结果。

注意:date 过滤器符合 Ruby 的日期和时间格式:https://ruby-doc.org/core-3.0.0/Time.html#method-i-strftime 。要使用 .NET 标准的日期格式,请使用 format_date 过滤器。

代码

var context = new TemplateContext { TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time") } ;
var result = template.Render(context);

模板

{{ '1970-01-01 00:00:00' | date: '%c' }}

结果

Wed Dec 31 19:00:00 -08:00 1969

时区转换

日期和时间可以使用 time_zone 标签转换为特定的时区,格式为:time_zone:<iana> 。

代码

var context = new TemplateContext();
context.SetValue("published", DateTime.UtcNow);

模板

{{ published | time_zone: 'America/New_York' | date: '%+' }}

结果

Tue Aug  1 17:04:36 -05:00 2017

自定义标签和块

Fluid 的语法可以被修改,以使其接受任何新的标记(tag)和带有任何自定义参数的块(block)。Fluid 使用了 Parlot 作为语法分析器,这使得 Fluid 完全可扩展。

与块(block)不同,标记(tag)没有结束元素(例如:循环,自增)。当把一个模板的某个部分作为一组语句来操作时,块很有用。

Fluid 提供了用于注册常见标签和块的帮助方法。所有的标签和块总是以他们的名称作为标识符开始。

自定义标签时需要提供一个委托(delegate),该委托会在标签被匹配时执行。该委托可以使用使用以下三个属性:

  1. writer,TextWriter的实例,用于渲染文字。

  2. encode,TextEncoder 的实例,例如 HtmlEncoder 或者 NullEncoder。由模板的调用者定义。

  3. context,TemplateContext 的实例。

注册自定义标签

自定义标签可以分为三种类型:

  1. Empty:空白标签,没有任何参数,例如 {% renderbody %} 。

  2. Identifier:标识符。将标识符作为标签参数,例如{% increment my_variable %} 。

  3. Expression:表达式。以表达式作为参数,例如 {% layout 'home' | append: '.liquid' %} 。

代码

parser.RegisterIdentifierTag("hello", (identifier, writer, encoder, context) =>
{writer.Write("Hello ");writer.Write(identifier);
});

模板

{% hello you %}

结果

Hello you

注册自定义块

块的创建方式与标记相同,可以在委托中访问块内的语句列表。

源码

parser.RegisterExpressionBlock("repeat", (value, statements, writer, encoder, context) =>
{for (var i = 0; i < value.ToNumber(); i++){await return statements.RenderStatementsAsync(writer, encoder, context);}return Completion.Normal;
});

模板

{% repeat 1 | plus: 2 %}Hi! {% endrepeat %}

结果

Hi! Hi! Hi!

自定义模板解析

如果 identifier、 empty 和 expression 解析器不能满足你的要求,RegisterParserBlock 和 RegisterParserTag 方法可以接受自定义的解析结构。这些结构可以是 FluidParser 中定义的标准解析器,例如 Primary或者其他任意组合。

例如,RegisterParseTag(Primary.AndSkip(Comma).And(Primary), …) 将期望两个 Primary 元素用逗号隔开。然后,该委托将被调用,使用 ValueTuple<Expression, Expression> 代表这两个 Primary 表达式。

注册自定义运算符

运算符是用来比较数值的,比如 > 或 contains 。如果需要提供特殊的比较,可以定义自定义运算符。

自定义 xor 运算符

下面的例子创建了一个自定义的 xor 运算符,如果左或右表达式被转换为布尔时只有一个是真的,它将为真。

using Fluid.Ast;
using Fluid.Values;
using System.Threading.Tasks;namespace Fluid.Tests.Extensibility
{public class XorBinaryExpression : BinaryExpression{public XorBinaryExpression(Expression left, Expression right) : base(left, right){}public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context){var leftValue = await Left.EvaluateAsync(context);var rightValue = await Right.EvaluateAsync(context);return BooleanValue.Create(leftValue.ToBooleanValue() ^ rightValue.ToBooleanValue());}}
}

配置解析器

parser.RegisteredOperators["xor"] = (a, b) => new XorBinaryExpression(a, b);

模板

{% if true xor false %}Hello{% endif %}

结果

Hello

空白控制

Liquid 在支持空白方面遵循严格的规则。默认情况下,所有的空格和新行都从模板中保留下来。Liquid 的语法和一些 Fluid 选项允许自定义这种行为。

通过连字符控制空白输出

例如有以下模板:

{%  assign name = "Bill" %}
{{ name }}

在 assign 标签之后的换行将被保留下来。输出如下:

Bill

标签和值可以使用连字符来剥离空白。

{%  assign name = "Bill" -%}
{{ name }}

这将输出:

Bill

模板中的 -%} 将 assign 标签右侧的空白部分剥离。

通过模板选项控制空白输出

Fluid 提供了 TemplateOptions.Triming 属性,可以用预定义的偏好来设置何时应该自动剥离空白,即使标签和输出值中不存在连字符。

贪婪模式

当 TemplateOptions.Greedy 中的贪婪模式被禁用时,只有第一个新行之前的空格被剥离。贪婪模式默认启用,这是 Liquid 语言的标准行为。

自定义过滤器

Fliud 默认提供了一些非标准过滤器。

format_date

使用标准的 .NET 日期和时间格式来格式化日期和时间。它使用系统当前的多语言信息。

输入

"now" | format_date: "G"

输出

6/15/2009 1:45:30 PM

详细的文档可以看这里:https://docs.microsoft.com/zh-cn/dotnet/standard/base-types/standard-date-and-time-format-strings

format_number

使用 .NET 数字格式来格式化数字。

输入

123 | format_number: "N"

输出

123.00

详细的文档可以看这里:https://docs.microsoft.com/zh-cn/dotnet/standard/base-types/standard-numeric-format-strings

format_string

格式化字符串

输入

"hello {0} {1:C}" | format_string: "world" 123

输出

hello world $123.00

详细的文档可以看这里:https://docs.microsoft.com/zh-cn/dotnet/api/system.string.format?view=net-5.0

性能

缓存

如果你在渲染之前对解析过的模板进行缓存,你的应用程序可以获得一些性能提升。解析是内存安全的,因为它不会引起任何编译(意味着如果你决定解析大量的模板,所有的内存都可以被收集),你可以通过存储和重用 FluidTemplate 实例来跳过解析步骤。

只要每次对 Render() 的调用使用一个独立的 TemplateContext实例,这些对象就是线程安全的。

基准测试

Fluid 项目的源代码中提供了一个基准测试应用程序,用于比较 Fluid、Scriban、DotLiquid 和 Liquid.NET 。在本地运行该项目,分析执行特定模板所需的时间。

Fluid 比所有其他知名的 .NET Liquid 模板分析器更快,分配的内存更少。对于解析,Fluid 比 Scriban快30%,分配的内存少 3 倍。对于渲染,Fluid 比 Scriban 快 3 倍,分配的内存少 5 倍。与 DotLiquid 相比,Fluid 的渲染速度快 10 倍,分配的内存少 40 倍。

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.201[Host]   : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJITShortRun : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJITJob=ShortRun  IterationCount=3  LaunchCount=1
WarmupCount=3|             Method |          Mean |        Error |      StdDev |  Ratio | RatioSD |     Gen 0 |    Gen 1 |   Gen 2 |   Allocated |
|------------------- |--------------:|-------------:|------------:|-------:|--------:|----------:|---------:|--------:|------------:|
|        Fluid_Parse |      7.056 us |     1.081 us |   0.0592 us |   1.00 |    0.00 |    0.6714 |        - |       - |     2.77 KB |
|      Scriban_Parse |      9.209 us |     2.989 us |   0.1638 us |   1.31 |    0.03 |    1.8005 |        - |       - |     7.41 KB |
|    DotLiquid_Parse |     38.978 us |    13.704 us |   0.7512 us |   5.52 |    0.14 |    2.6855 |        - |       - |    11.17 KB |
|    LiquidNet_Parse |     73.198 us |    25.888 us |   1.4190 us |  10.37 |    0.29 |   15.1367 |   0.1221 |       - |    62.08 KB |
|                    |               |              |             |        |         |           |          |         |             |
|     Fluid_ParseBig |     38.725 us |    11.771 us |   0.6452 us |   1.00 |    0.00 |    2.9907 |   0.1831 |       - |    12.34 KB |
|   Scriban_ParseBig |     49.139 us |     8.313 us |   0.4557 us |   1.27 |    0.02 |    7.8125 |   1.0986 |       - |    32.05 KB |
| DotLiquid_ParseBig |    208.644 us |    45.839 us |   2.5126 us |   5.39 |    0.15 |   13.1836 |   0.2441 |       - |    54.39 KB |
| LiquidNet_ParseBig | 24,211.719 us | 3,862.113 us | 211.6955 us | 625.30 |    8.32 | 6843.7500 | 375.0000 |       - | 28557.49 KB |
|                    |               |              |             |        |         |           |          |         |             |
|       Fluid_Render |    414.462 us |    12.612 us |   0.6913 us |   1.00 |    0.00 |   22.9492 |   5.3711 |       - |    95.75 KB |
|     Scriban_Render |  1,141.302 us |   114.127 us |   6.2557 us |   2.75 |    0.02 |   99.6094 |  66.4063 | 66.4063 |   487.64 KB |
|   DotLiquid_Render |  5,753.263 us | 7,420.054 us | 406.7182 us |  13.88 |    0.96 |  867.1875 | 125.0000 | 23.4375 |  3879.18 KB |
|   LiquidNet_Render |  3,262.545 us | 1,245.387 us |  68.2639 us |   7.87 |    0.18 | 1000.0000 | 390.6250 |       - |   5324.5 KB |

以上结果的测试时间是 2021年3月26 日,使用的组件详情如下:

  • Scriban 3.6.0

  • DotLiquid 2.1.405

  • Liquid.NET 0.10.0

测试项目说明

Parse:解析一个包含过滤器和属性的简单 HTML 模板。
ParseBig:解析一个博客文章模板。
Render:使用 500 个产品渲染一个包含过滤器和属性的简单 HTML 模板。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/302784.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

风靡全球的人工智能,如何赶上这班车?

目前&#xff0c;机器学习的使用日渐成为趋势。作为人工智能的核心&#xff0c;机器学习是一门多领域的交叉学科&#xff0c;专门研究计算机模拟或实现人类学习行为的方法&#xff0c;以获取新的知识或技能&#xff0c;重新组织已有的知识结构使之不断改善自身的性能。简单来说…

java spark读写hdfs_Spark读取HDFS数据输出到不同的文件

最近有一个需求是这样的&#xff1a;原来的数据是存储在MySQL&#xff0c;然后通过Sqoop将MySQL的数据抽取到了HDFS集群上&#xff0c;抽取到HDFS上的数据都是纯数据&#xff0c;字段值之间以\t分隔&#xff0c;现在需要将这部分数据还原为json格式的&#xff0c;因为这样做的原…

15个创意的电梯广告

如果你走进任何一个城市&#xff0c;几乎每一个地方有电梯&#xff0c;但是你发现具有创意的广告电梯了吗&#xff0c;分享给大家15个不同城市的创意的电梯广告&#xff0c;作为设计师可以帮助你 Accor Air Asia Becel Body World Coke Zero Consol Energy Fiat Punto Forklift…

揭秘全球开发最新趋势!JS开发者达1380万,C#超越PHP,Rust增长最快

文 | 白开水出品 | OSC开源社区&#xff08;ID&#xff1a;oschina2013&#xff09;研究公司 SlashData 最新发布的”State of the Developer Nation“第 20 版报告指出&#xff0c;全球开发者社区在过去六个月中的经历了巨大的增长。据估计&#xff0c;截至 2021 年第一季度&a…

怎样判断漂亮女孩是不是单身的?

全世界有3.14 % 的人已经关注了数据与算法之美不解风情的死理性派们在情感生活中不免会遇到这样悲催的一幕&#xff1a;偶然间遇到一位心仪的漂亮女孩&#xff0c;从此日思夜想&#xff0c;废寝忘食&#xff0c;开始了漫长的暗恋之旅。等到一日&#xff0c;在无尽的纠结中&…

java开源cad_寻找 AutoCAD 替代品,5款免费开源 CAD 软件推荐

FreeCADFreeCAD 是一种通用的3D CAD建模。完全开源(LGPL许可证)FreeCAD 直接的目的是在机械工程和产品设计&#xff0c;也适合在更广泛的用途&#xff0c;如建筑行业或其他工程专业&#xff0c;工程相关领域。FreeCAD 全平台通用&#xff0c;能完美工作在 Windows、Linux 和 ma…

JavaScript对SEO的影响及解决之道

不只是Google&#xff0c;yahoo在官方文档中也有类似的夸大&#xff1a;  尽量使用搜索引擎能够识别的文本信息&#xff0c;防止过多的JavaScript、Cookie、框架、DHTML 或 Flash 等繁杂技术。  http://help.cn.yahoo.com/answerpage_2911.html  百度虽然没有明确的说明&…

快速弄懂陌生领域是一项“赚钱”的能力

大家好&#xff0c;我是Z哥。有时候&#xff0c;我们被动的需要去了解一个新行业或者领域。比如&#xff0c;工作需要、投资需要等等。在这个时候&#xff0c;你能不能快速弄懂一个行业的80%&#xff0c;成为一个内行就很关键了。毕竟时机可是很重要的。比如&#xff0c;你想了…

java弹窗 触发事件_关于ElementUI中MessageBox弹框的取消键盘触发事件(enter,esc)关闭弹窗(执行事件)的解决方法...

好久没见了在项目中遇到一个小小的需求&#xff0c;总结了一下&#xff01;详细我就不介绍了&#xff0c;相信大家用过的话&#xff0c;很了解。详见文档----------->http://element-cn.eleme.io/#/zh-CN/component/message-box#messagebox-dan-kuang项目需求——关于Elemen…

6年后再一次Hello World!这本书让你久等了!

移动互联网和手机智能化浪潮带来了全新的手机游戏模式。而随着手机游戏开发逐渐成熟&#xff0c;手机游戏开发门槛的降低&#xff0c;越来越多的开发者希望加入到这一行业中。Unity作为一款优秀的游戏引擎&#xff0c;为广大游戏开发者提供了高效、简洁的开发流程&#xff0c;使…

[转]模拟电路设计经典教材推荐

终于开通博客了&#xff0c;给大家推荐基本经典教材&#xff0c;我也是从一位师兄那得到的&#xff01; 1. 拉扎维的《模拟CMOS集成电路设计》&#xff0c;我们研二模电课的教材&#xff0c;汪宁老师把这门课讲得可圈可点。当时没意识到有其他书&#xff0c;于是我就把此书读了…

MATLAB图像处理与数字信号处理资料分享来袭

小天从大学开始接触数学建模&#xff0c;便开启资料收集功能。经过近几年的积累和沉淀&#xff0c;再加上对数学建模领域的深入研究&#xff0c;收集整理了丰富的数学建模资料&#xff0c;内容涵盖“MATLAB图像处理”&#xff0c;“数字信号处理与MATLAB实现”等。截止到今天&a…

java换水_java-交流灌水之谁是水王?

设计思想&#xff1a;水王是发帖和回帖最多的那个&#xff0c;总数会超过总贴数的一半还要多&#xff0c;我的思想是&#xff0c;当两个挨着的人发帖的id不同就进行抵消&#xff0c;最后剩下来的就是总数超过一半的“水王”的id;代码实现:package demo;public class text1 {sta…

如何在.NET Core中为gRPC服务设计消息文件(Proto)

如何在.NET Core中为gRPC服务设计消息使用协议缓冲区规范定义gRPC服务非常容易&#xff0c;但从需求转换为.NET Core&#xff0c;然后管理服务的演变时&#xff0c;需要注意几件事。创建gRPC服务的核心是.proto文件&#xff0c;该文件以与语言无关的格式描述了该服务。使用.pro…

五大原则之----里氏替换原则(LSP)

阐述&#xff1a;子类型&#xff08;subtype&#xff09;必须能够替换掉它们的基类型&#xff08;basetype&#xff09; 先提出一个问题&#xff1a;正方形是不是一种特殊的长方形&#xff08;IS - A关系&#xff09;&#xff1f; 先不要回答这个问题&#xff0c;看下面的分析。…

数学学得好,才可以发现别人发现不了的挣钱良机

全世界有3.14 % 的人已经关注了数据与算法之美2011年&#xff0c;美国波士顿地区的一种彩票 “Cash WinFal”爆出了一个存在已久的漏洞。让人惊奇的是&#xff0c;一对 73 岁的夫妇已经利用这个漏洞赚了超过 600 万美元 。一时间风雨满城&#xff0c;马萨诸塞州也宣布要开始调查…

java富文本如何转义_富文本编辑器wangEditor中转义字符的问题

前段时间做项目的时候&#xff0c;要使用富文本编辑器&#xff0c;采用的是wangEditor&#xff0c;结果当用户在为文本添加样式的时候&#xff0c;发现居然无法直接保存&#xff0c;遂查看后台数据。发现很多样式都被过滤掉了&#xff0c;后台接受的数据中根本没有样式。在网上…

Async和Await异步编程的原理

1. 简介 从4.0版本开始.NET引入并行编程库&#xff0c;用户能够通过这个库快捷的开发并行计算和并行任务处理的程序。在4.5版本中.NET又引入了Async和Await两个新的关键字&#xff0c;在语言层面对并行编程给予进一步的支持&#xff0c;使得用户能以一种简洁直观的方式实现并行…

Lang.NEXT 2012相关Session

2012年4.2-4日的Lang.NEXT 2012是.NET(CLR, DLR 以及其他平台)上语言及相关工具的设计开发者的盛会。会议的相关Session已经放出&#xff0c;绝对值得好好的学习&#xff0c;地址是&#xff1a;http://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2012?sortsequential&…

细数那些让人难以抗拒的经典数学书

如果有人不相信数学是简单的,那是因为他们没有意识到人生有多复杂。——冯诺依曼近期有关数学的好消息还是蛮多的&#xff0c;先有阿里巴巴举办全国数学竞赛&#xff0c;奖金百万&#xff0c;只为爱好数学的你。快来看看下面这些竞赛试题&#xff0c;我想应该难不倒我们的小伙伴…