[译]:Orchard扩展——模块开发入门

返回目录索引
原文链接:Getting Started with Modules
译文链接:Orchard扩展——模块开发入门

This four part course will get you started with a gentle introduction to extending Orchard at the code level. You will build a very simple module which contains a widget that shows an imaginary featured product.

It will teach you some of the basic components of module development and also encourage you to use best-practices when developing for Orchard.

  • Getting Started with Modules - Part 1 - Static Widget

    This first part walks you through creating a static widget and introduces you to many of the classes that form the basis of module development in Orchard.

  • Getting Started with Modules - Part 2 - Dynamic Widget

    The second part of the course explains how you can refine the widget to implement settings that are configurable via the admin dashboard.

  • Getting Started with Modules - Part 3 - Orchard API

    The third part examines Orchard at an API level. You will learn how to work with content items in code and make decisions based on the values of its fields. You will also learn about some of the support classes which make this easier.

  • Getting Started with Modules - Part 4 - Best Practices

    The final installment reviews the completed widget and brings to light loose-ends. You take a tour back through the code, applying development best-practices along the way.

When you have completed this course you will have a solid grounding in extending Orchard via code. With this knowledge you will be able to build your own simple modules. You will also understand the concepts underpinning the documentation found elsewhere in this site and around the web. These skills lay a solid groundwork for you to continue your studies in Orchard module development.


译:奇葩史

[译]:Orchard扩展——本地化提供程序使用

返回目录索引
原文链接:Using the Localization Providers
译文链接:Orchard扩展——本地化提供程序使用

Orchard利用一个简单的API来支持本地化,其将输入的默认语言字符串(en-us)作为本地化数据的主键,然后返回当前语言环境最准确的本地化版本。

在Razor视图引擎(.cshtml)中使用T()

简单示例 —— 转化后的字符串将会返回输出

@T("This was a triumph!")

简单字符串时使用上述内容。

格式化用户数据

有时,数据需要注入到本地化字符串中。

请勿使用字符串拼接,因为数据的位置在不同的语言中可能处于不同的位置:

// 不好的用法:
@T("You have ") + Model.SmsCredits + @T(" credits left.")

应该使用参数化格式字符串:

// 推荐用法:
@T("You have {0} credits left", Model.SmsCredits)

数据编码

请注意,参数应在添加之前进行编码。例如,如果以下示例的noteText中包含<b>huge</b>:

@T("I'm writing a note here: {0}.", noteText)

这样,默认语言环境输出时,用户将看到标记:

I'm writing a note here: <b>huge</b> success. 

在99%的情况下,这或许是你需要的。这种自动编码可以保护你免受黑客的注入攻击。

但在极少数情况下,你对你所做的事很了解,并且希望注入未编码的字符串,这是你可以执行以下操作:

@T("I'm writing a note here: {0}.", new HtmlString(noteText))

它的最终结果将是:

“I’m writing a note here: huge success.”

注意:实现IHtmlString类型的任何对象都将注入未编码,因为我们假设它已经正确编码。这就是使用new HtmlString(noteText)的功能。
上述编码未编码比较拗口,未找到合适翻译,其实际表述就是:未编码 —— 将按原样显示,编码 —— 某些内容会进行转换。

例如,如果你进行如下处理:

@T("{0}: We do what we must because {1}",
    Html.ItemDisplayLink(apertureScienceContentItem),
    justification)

这样,操作链接将不会编码,并且按预期显示,而justification字符串会编码。

还有需要注意的是,格式字符串本身是被认为安全的,因为它是由模块作者提供,所以下面内容将如预期的一样显示:

@T("It's <em>hard</em> to overstate my <strong>{0}</strong>",
    emotion)

如果emotion中包含"<satisfaction>“, 则其结果字符串为"It’s hard to overstate my <satisfaction>”.

注入没字符串值

基础值类型在格式化之前不是html编码的,并且当前的语言文化将会用于对它们进行格式化:

@T("when {0} qty {1:#,##0.00} unit price {2:C}", _clock.UtcNow, 5.782, 87)

复数

资源字符串(如 {0} comment{0} comments)的复数是比较棘手的,因为不同语言之间存在差异,其中包含复数的规则或者你需要多少字符串来应对所有情况。尽管Orchard尚未实现所有可能的情况,但其API已经准备在以后支持它们。

如果一个字符串需要进行复数处理,请为默认语言提供两个字符串,并将复数参数作为第一个格式化参数:

@T.Plural("1 Comment", "{0} Comments", commentCount)
@T.Plural("Deleted 1 item of type {1}", "Deleted {0} items of type {1}",
    deleteCount, contentType)

在单数字符串中使用1,以此可以为翻译者提供更好的上下文(如上所示)。

复数参数必须是整数。

不要再视图中使用自定义逻辑来决定字符串的内容,因为不同的文化下,其视图逻辑可能会不同。

通过代码使用T()

使用依赖 注入设置

你可以在代码(如驱动类、服务类以及其他类)中使用T()本地化辅助方法。

通过向你的类中添加公共属性,Orchard可以检测它,并利用Autofac的属性注入功能来使用当前的Localizer配置你的方法。

其过程如下:

  1. 向你的类中添加公共属性:

     public Localizer T { get; set; }
    

技术上,你可以使用任何变量名,并不一定要用T作为属性名,但在此约定使用T

  1. 在构造函数中,为T属性赋一个默认值:

     protected YourClassName() {
     T = NullLocalizer.Instance;
     }
    

这是为了防止在Autofac配置好localizer之前,由于没有默认localizer而导致应用结束。

利用继承自接口IDependency的Component类来自动获取T()

Orchard中提供了一个抽象类——Component,它定义Orchard.Framework项目的IDependency.cs文件中:

public abstract class Component : IDependency {
    protected Component() {
        Logger = NullLogger.Instance;
        T = NullLocalizer.Instance;
    }

    public ILogger Logger { get; set; }
    public Localizer T { get; set; }
}

如果你的类实现了IDependency接口,那么你可以改为继承Component。这样,你的类就可以自动含有方法T()的声明和初始化。

在配置T()时使用它的属性

你可以再次查看上面的Razor示例来使用它。

唯一的不同是,当你通过代码使用localizer时,你不需要使用Razor的@符号来表示代码块的开始。

例如,在Razor中,示例如下:

@T("You have {0} credits left", Model.SmsCredits)

而在代码中处理的示例如下:

T("You have {0} credits left", Model.SmsCredits)

在ASPX视图引擎(.aspx)中使用T()

其使用方法几乎与Razor方式一样,只需将@表达式格式替换为<%: 表达式 %>格式。

如简单使用,将:

@T("This was a triumph!")

替换为:

<%: T("This was a triumph!") %>

其他复杂使用以此类推。

关于<%= %><%: %>

<%: %>适用于所有情况,因为它会自动处理编码。请勿使用<%= %>。如果你确实需要注入未编码的字符串,请使用HtmlString处理,同时继续使用<%: %>


译:奇葩史

WPF快速入门——布局介绍

为什么要布局

布局和房子的格局具有着相似的概念,都是为了提供更好的用户体验而进行 设计。下面我们讲讲几个常用的布局面板

布局面板

WPF中有许多布局面板,下面将会介绍几个常用的布局面板,其中最常用的应该是Grid,StackPanel和DockPanel三大布局面板。

Grid和UniformGrid

Grid面板在WPF界面开发中,应该是使用最多的布局面板了。其功能为网格布局,下面以一个例子来解释一下其功能:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="2*"/>
        <ColumnDefinition Width="3*"/>
    </Grid.ColumnDefinitions>
</Grid>

在本示例中,Grid布局为3行3列,其中行高采用了等分,列宽比例为1:2:3。其中行高和列宽都用到了*表示,这里也可以写具体的数值,或者填写auto*auto都是一种自适应的布局,不同之处在于:*可以说是对于外部的自适应 —— 自动填满;而auto可以说是对于内部的自适应 —— 最小空间占用。

在布局中处理好行列后,就可以向里面添加控件了,在使用控件时,需要为控件设置在哪一行哪一列(如,第一行第一列[从0开始]:Grid.Row="1" Grid.Column="1")。

与Grid非常相似的一个面板就是UniformGrid了。虽然UniformGrid不是在Grid上继承下来的,但是在使用上可以说UniformGrid是Grid的一个特殊化处理。下面列举几点它们的区别:

  • Grid需要通过RowDefinitions和ColumnDefinition来定义行和列的个数,UniformGrid只需要通过属性Rows和Columns设置
  • Grid行高列宽手动设置,UniformGrid中完全等分。
  • Grid中的控件必须手动指定行列,否则只能在第0行第0列;UniformGrid中自动排列。
  • Grid中某行某列可以放多个控件;而在UniformGrid中必须嵌套布局面板才可以。

Grid和UniformGrid还有其他的不同,此处不再赘述。而且相对来说,UniformGrid的使用范围较小,最有用的使用情况就是业务表单录入类的界面 —— 需要大量的网格。

WrapPanel和StackPanel

此处之所以将WrapPanel和StackPanel放在一起,是因为他们有一定的相似之处。他们关系和上面的Grid和UniformGrid差不多,没有继承关系,但是StackPanel又有些像WrapPanel的特殊化处理。WrapPanel——流式布局,就像我们的写字一样,一行一行的,或者一列一列的;而StackPanel——堆栈式布局,就像一个不会换行/列的WrapPanel。下面分别说一下它们的使用场景:

WrapPanel除了上面说的写字以外,最常见的应该就是日历了,然后就是各大电商/视频网站中常会见到。而对于StackPanel,最熟悉的场景就是买票排队了,然后在应用中最常见的就是新闻列表、消息列表等(列表类内容)。如果你做的应用需要块状显示结果(多行多列),就用WrapPanel,如果需要条状展示结果(单行多列或多行单列),就用StackPanel。

在上述两个面板中,相对来说,StackPanel会使用的更加频繁一些。

DockPanel

DockPanel是三大最常用面板的最后一个,与前两个相比,使用量上会相对少些。但在我们的生活当中却是最常见的,比如你家里摆放物品时,常会需要哪个哪个靠墙边放。所以这个布局会出现在最不起眼的地方,但有时却是必不可少的。它的作用是让控件靠边(上下左右)停靠,软件应用中的菜单栏是一个比较典型的应用场景。

对于DockPanel还有一点需要注意的是:先到先得,就是先添加的控件先占用部分位置,然后后面添加的控件在剩余部分分割空间,不影响先前的控件 —— 这里控件的前后是指xaml文件中控件定义的位置先后。

Canvas及元素InkCanvas

Canvas是一个以坐标来确认控件位置的布局面板。在Canvas中的控件,需要以Canvas面板左上角为原点来设置坐标。相比之下,Canvas使用的会很少。只有在比较特殊的领域中,Canvas才有其展现的机会。而提到Canvas,还有一个比较特殊的元素InkCanvas,它与Canvas比较相似,但 它并不是布局面板,它直接继承自FrameworkElement;另外,InkCanvas主要用于手写 输入领域。

综合上述内容,Canvas的效率最高,当然它的功能也最弱;如果你去看它们的实现源码,会发现DockPanel和Canvas实现是比较简单的,所以效率也高,而Grid是最复杂的,所以它的效率相对是最低的。故在应用中选择最合适布局面板,有利于提高软件应用的性能;如果自己有能力,也可以自己实现Panel。
面板性能参考:Canvas略高于DockPanel,高于WrapPanel,高于StackPanel,高于Grid —— 由源代码复杂程度大概估算,未进行详细实测。

常与布局组合使用的控件

在使用布局面板时,常常会需要一些辅助的控件来实现一些特殊的功能,下面会介绍几个常见的辅助控件。

Border

Border是一个非常特殊的“控件”。它像是一个面板,但由不是;说它是个控件,它又不继承自Control,而是继承自Decorator。它常用于为面板添加边框。因为面板是不具有边框相关的属性的,如果需要在 布局中添加边框,就需要在相应的布局面板外侧添加Border,以此添加边框。而且Border不仅仅是可以添加边框,还可以处理一些圆角等处理,可以让软件应用更加圆滑。Border与布局面板还有一个主要的区别就是:

  • Border内只能包含一个元素(面板、控件等),所以,想要添加多个控件,就必须先添加一个面板,然后向面板中添加控件。

ScrollViewer

ScrollViewer也是一个常与布局组合使用的控件,与Border不同的是,它确实是一个控件(继承自Control)。用到ScrollView的原因是,布局面板本身是不提供滚动支持的,当内容超出布局面板外侧的大小限制时,我们就无法查看超出内容,此时就需要ScrollViewer来处理了。还有一点与Border相同的是,它里面也只能有一个元素,到添加多个,则必须嵌套布局面板。

GridSplitter

看到GridSplitter名字就应该 知道它是 和Grid组合 使用的,它是用于在运行期间调整界面布局大小的。最常见的就是在左侧列表,右侧具体内容的界面布局中,需要看左侧列表的内容,但列表宽度又太小,此时,添加GridSplitter就可以让用户自己拖动来扩大列表的宽度,以便于查看更多内容。

附加属性

附加属性简单点说就是,这个属性不是元素(如控件)自己的,是别人给它的。最常见的使用场景就是上面的布局面板了,如Grid中,内部控件会有Grid.Column的 属性,这就是一个附加属性;还有如DockPanel中,内部控件会有DockPanel.Dock属性,这也是一个附加属性。当然,附加属性并不是外侧面板给它的,只是上面提到的两个属性,只有在相应的面板中才有效而已。

或者,在往深了说,附加属性不是真正的属性,它是一个方法调用,只是xaml中封装了方法调用,让它看起来是个属性的样子。而在个人所遇到的情况中,自定义附加属性的情况真的是很少很少 ———— 最典型的一次是使用PasswordBox,由于PasswordBox的Password不支持绑定,所以使用到了附加属性来处理,其中内容涉及到了依赖属性,此处不做详细介绍,仅做举例。

上述内容继承结构

WPF快速入门——Hello,World和XAML介绍

个人开发环境 —— 按个人喜好选择:

  • IDE:Visual Studio Community 2015 —— 愿意尝鲜的可以试下VS2017
  • System:Windows 8.1

Hello World

尽管hello,world的例子已经用烂了,但你不可否认它所带来的无限魅力。如果你去查找hello,world起源,会发现许多有趣的事。所以本文仍以hello,world为例开始我们的WPF学习之旅。

创建第一个WPF项目

操作步骤:打开Visual Studio —— 依次点击文件-新建-项目 —— 在弹出的新建项目对话框中的索引目录区域选择已安装-模板-Visual C#-Windows —— 选中对话框列表项中的WPF 应用程序 —— 在对话框下方的名称中输入名称Hello_World —— 点击确定完成项目创建。

创建Hello,World项目

项目结构说明

  • 解决方案Hello_World —— VS解决方案,可以用于同时管理多个项目
    • Hello_World —— 项目,即刚刚新建的Hello,World项目
      • Properties —— 项目属性相关内容设置,包括程序集信息、一些资源等内容
      • 引用 —— 项目中需要的依赖程序集
      • App.config —— 项目配置,在Hello,World项目中可有可无。
      • App.xaml —— 含两个文件App.xaml和App.xaml.cs;此部分功能主要是设置应用的启动窗体及一些样式资源的引用,常用于主窗体显示前的一些准备工作设置。
      • MainWindow.xaml —— 含有两个文件MainWindow.xaml和MainWindow.xaml.cs;.xaml文件为界面设计文件,.cs文件为后台代码文件

Tips:全局异常处理

为了避免应用出现异常后直接退出,我们常会需要添加一个全局的异常捕获处理。处理方法为,在App.xaml.cs中重载OnStartup方法,为DispatcherUnhandleException事件添加处理内容。代码如下:

protected override void OnStartup(StartupEventArgs e)
{
    this.DispatcherUnhandledException += App_DispatcherUnhandledException;
    
    base.OnStartup(e);
}

private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
    //throw new NotImplementedException();
    e.Handled = true;
}

注意:在事件处理中添加你自己的实现内容,可以添加对话框提示等。如果有些异常处理完后,不需要应用退出,则将e.Handled设置为true;如果此处为致命错误,必须退出应用软件,则e.Handled保留原来的false。

XAML基础介绍

XAML文件是以.xaml为后缀的XML文件。其是为了简化UI的创建过程而推出的。它不仅仅在WPF中应用,在微软后续的UMP开发中也是利用此技术进行UI处理。

注:XAML读音为“zammel”

命名空间说明

在XAML文件中,命名空间利用xmlns(xml namespace)来引入。命名空间引入基本格式为:

xmlns:别名="命名空间"

其中默认命名空间可以省略“冒号”和“别名”,另,此处引用的命名空间与cs代码中的还有所不同,cs代码中只能一个空间一个空间引入,而此处的空间可以是多个命名空间的集合 —— URL表示,当然也可以一个一个空间引入。

类对应关系

在我们的示例中MainWindow.xaml根节点Window上有一个属性设置“x:Class”,此属性设置了其所对应的C#代码类。而在我们的MainWindow.xaml.cs的定义中,可以看到MainWindow类含有partial(分部类)修饰符。其实在最终的生成结果中,他们是同一个类,我们可以在obj\Debug目录下看到 MainWindow.g.i.cs文件,它是在生成过程中的一个中间状态,从其内容结构上可以看出它也是MainWindow的一个分部类。如果将XAML文件中的类名修改,程序生成过程就会出现错误,因为MainWindow构造函数 中 的InitializeComponent()方法是xaml文件生成出来的。

注:分部类 —— 就是让你可以将一个类的内容定义到多个文件。—— 据我所知,这应该是.NET中特有的设计 。

基本属性及事件

属性设置其实和在cs代码中设置一样,仅仅是因为换了种写法,封装了一些处理而已。

简单的属性设置

简单属性设置,即可以用一个字符串表示的属性设置,如Name属性。还有就是一些位置Alignment的设置,如HorizontalAlignment可以设置为Center,在XAML文件中,Center是一个字符串,而在类中HorizontalAlignment是一个System.Windows.HorizontalAlignment的枚举类型的属性,所以此处就封装了字符串到Alignment枚举类型的一个转换。不过在我们界面设置的时候,是不太需要关注这一块的。

注:许多控件的Text或Content属性会与标签值(如<a>此处为标签值</a>)冲突 —— 即它们表示同一个属性,所以为了避免冲突,建议,可以直接设置Text或Content的值时,尽量设置在属性上设置,避免在标签中设置属性值。

// 建议
<TextBox Text="hello"></TextBox>
<Button Content="hello"></Button>

// 不建议
<TextBox >hello</TextBox>
<Button >hello</Button>

虽然效果一样,但看得不爽 —— 强迫症。如下方式也是可以的,主要用于复杂的属性设置:

<TextBox >
    <TextBox.Text>hello</TextBox.Text>
</TextBox>
<Button >
    <Button.Content>hello</Button.Content>
</Button>

复杂属性设置

复杂属性的设置表现在无法用一个字符串表示的情况,如颜色背景设置,但我们是单色的情况时,可以直接用一个字符串表示,但是当需要设置渐变色时,就无法一个值表示,所以需要多行语句来表示。

以下为背景色的渐变色设置,其位置变化为默认值,从左上角到右下角渐变:

<Button >
    <Button.Content>hello</Button.Content>
    <Button.Background>
        <LinearGradientBrush>
            <LinearGradientBrush.GradientStops>
                <GradientStop Offset="0" Color="Red"></GradientStop>
                <GradientStop Offset="1" Color="Blue"></GradientStop>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </Button.Background>
</Button>

还有一些属性相关内容,将在后面介绍,如附加属性,资源设置等。

添加事件

事件的添加有两种方式,一种是直接在XAML文件中和写属性一样添加 —— 利用自动创建,在后台代码中创建事件的实现。还有一种是在后台代码手动实现。写法纯粹看个人喜好了。

当然,后台代码手动实现的自由度更高一些,如某些事件的注册可能需要在界面加载完后注册会更好一些。个人是推荐在后台代码自己手动加,虽然写的时候会麻烦些,但可以自己整理的比较清晰一些。

Tips:学WPF的好处

  • 就本节内容而言,WPF界面设计采用xml格式,以后以此基础学习网页设计和Android开发会相对简单一些。

MEF基础指南——MEF导入导出

文章缘起

要说写MEF中的导入导出,我想没有哪一篇文章可以比得上微软的官方文档了(至少是我看到的里面没有)。
个人强烈建议看MEF导入导出的人去看微软官方文档,文档地址:

个人喜欢用 Microsoft Help查看器(Help Viewer) 离线查看,如果你也喜欢这样,你可以直接在里面搜索MEF,找到特性化编程模型概述即可。

既然微软官方文档已经很完善了,那本文自然不会在写一遍,故本文更倾向与一篇快速索引参考文。

快速参考

导入导出基础

  1. 无参数导入导出,默认使用当前类或接口,示例如下:

示例中最后a有效,而ia无效;

    public interface IA { }

    /// <summary>
    /// 等价于 [Export(typeof(A))]
    /// </summary>
    [Export]
    public class A : IA { }

    public class UseA
    {
        /// <summary>
        /// 等价于 [Import(typeof(A))]
        /// </summary>
        [Import]
        public A a { get; set; }

        /// <summary>
        /// 等价于 [Import(typeof(IA))]
        /// </summary>
        [Import]
        public IA ia { get; set; }
    }
  1. 含参数导入导出,要求类型和协定名完全匹配,未指定类型时,同上,使用当前声明的类或接口。

如下示例中,最终ib有效 ,而b是无效的。

    public interface IB { }

    [Export(typeof(IB))]
    public class B : IB { }

    public class UseB
    {
        [Import]
        public B b { get; set; }

        [Import]
        public IB ib { get; set; }
    }
  1. 延迟导入及导入多个;其中匹配类型时,均使用繁星内的类型。

在如下示例中,三个导入均有效。其中ImportMany主要用于多个具有相同协定名和类型的导出,如你有两个类使用了相同的类型和协定,用Import是会报错的,此时可以用ImportMany来处理。

    public interface IC { }

    [Export(typeof(IC))]
    public class C : IC { }

    public class UseC
    {
        [Import]
        public Lazy<IC> ic { get; set; }

        [ImportMany]
        public IEnumerable<IC> iclist { get; set; }

        [ImportMany]
        public IEnumerable<Lazy<IC>> icllist { get; set; }
    }

元数据

元数据可以简单理解为:针对具有相同协定和类型的导出,添加元数据来区分它们。

继承

在MEF中使用InheritedExport来设置继承导出,在继承结构中,导出的匹配类型为父类/接口的类型,示例如下:

在如下代码中,最后id是有效的,d是无效的,即类型D的导出继承ID,使用的类型为ID。

    [InheritedExport]
    public interface ID { }

    public class D : ID { }

    public class UseD
    {
        [Import]
        public D d { get; set; }

        [Import]
        public ID id { get; set; }
    }

结语

本文主要列出了最常用的导入导出参考,详细信息及更多扩展内容还是建议看微软官方文档:特性化编程模型概述 (MEF)


奇葩史