Skip to content

Building a Web CMS (Content Management System) is unlike building a regular web application: it is more like building an application container. When designing such a system, it is necessary to build extensibility as a first-class feature. This can be a challenge as the very open type of architecture that's necessary to allow for great extensibility may compromise the usability of the application: everything in the system needs to be composable with unknown future modules, including at the user interface level. Orchestrating all those little parts that don't know about each other into a coherent whole is what Orchard is all about.

构建Web CMS(内容管理系统)与构建常规Web应用程序不同:它更像是构建一个_application container_。在设计这样的系统时,有必要将可扩展性构建为一流的特性。这可能是一个挑战,因为非常开放的架构类型允许很大的可扩展性可能会损害应用程序的可用性:系统中的所有内容都需要与未知的未来模块组合,包括在用户界面级别。将所有那些彼此不了解的小部分编排成一个连贯的整体是Orchard的意义所在。

This document explains the architectural choices we made in Orchard and how they are solving that particular problem of getting both flexibility and a good user experience.

本文档解释了我们在Orchard中所做的架构选择,以及它们如何解决获得灵活性和良好用户体验的特定问题。

Architecture

建筑

Modules 模块
Core 核心
Orchard Framework Orchard Framework
ASP.NET MVC ASP.NET MVC NHibernate NHibernate Autofac Autofac Castle 城堡
.NET .NET ASP.NET ASP.NET
IIS or Windows Azure IIS或Windows Azure
# Orchard Foundations # 果园基金会 # The Orchard CMS is built on existing frameworks and libraries. Here are a few of the most fundamental ones: Orchard CMS基于现有框架和库构建。以下是一些最基本的内容: - [ASP.NET MVC](http://www.asp.net/mvc): ASP.NET MVC is a modern Web development framework that encourages separation of concerns. - [ASP.NET MVC](http://www.asp.net/mvc):ASP.NET MVC是一个现代Web开发框架,鼓励关注点分离。 - [NHibernate](http://nhforge.org/): NHibernate is an object-relational mapping tool. It handles the persistence of the Orchard content items to the database and considerably simplifies the data model by removing altogether the concern of persistence from module development. You can see examples of that by looking at the source code of any core content type, for example _Pages_. - [NHibernate](http://nhforge.org/):NHibernate是一个对象关系映射工具。它处理Orchard内容项对数据库的持久性,并通过完全消除模块开发持久性的关注来大大简化数据模型。您可以通过查看任何核心内容类型的源代码来查看示例,例如_Pages_。 - [Autofac](https://github.com/autofac/Autofac): Autofac is an [IoC container](http://en.wikipedia.org/wiki/Inversion_of_control). Orchard makes heavy use of dependency injection. Creating an injectable Orchard dependency is as simple as writing a class that implements `IDependency` or a more specialized interface that itself derives from `IDependency` (a marker interface), and consuming the dependency is as simple as taking a constructor parameter of the right type. The scope and lifetime of the injected dependency will be managed by the Orchard framework. You can see examples of that by looking at the source code for `IAuthorizationService`, `RolesBasedAuthorizationService` and `XmlRpcHandler`. - [Autofac](https://github.com/autofac/Autofac):Autofac是[IoC容器](http://en.wikipedia.org/wiki/Inversion_of_control)。 Orchard大量使用依赖注入。创建一个可注入的Orchard依赖就像编写一个实现`IDependency`的类或一个更专业的接口本身派生自`IDependency`(一个标记接口)一样简单,并且使用依赖就像获取右边的构造函数参数一样简单。类型。注入依赖项的范围和生命周期将由Orchard框架管理。您可以通过查看“IAuthorizationService”,“RolesBasedAuthorizationService”和“XmlRpcHandler”的源代码来查看示例。 - [Castle Dynamic Proxy](http://www.castleproject.org/projects/dynamicproxy/): we use Castle for dynamic proxy generation. - [Castle Dynamic Proxy](http://www.castleproject.org/projects/dynamicproxy/):我们使用Castle进行动态代理生成。 The Orchard application and framework are built on top of these foundational frameworks as additional layers of abstraction. They are in many ways implementation details and no knowledge of NHibernate, Castle, or Autofac should be required to work with Orchard. Orchard应用程序和框架构建在这些基础框架之上,作为额外的抽象层。它们在很多方面都是实现细节,并且不需要NHibernate,Castle或Autofac的知识来与Orchard一起工作。 # Orchard Framework # 果园框架 # The Orchard framework is the deepest layer of Orchard. It contains the engine of the application or at least the parts that couldn't be isolated into modules. Those are typically things that even the most fundamental modules will have to rely on. You can think of it as the base class library for Orchard. Orchard框架是Orchard的最深层。它包含应用程序的引擎或至少包含无法隔离到模块中的部分。这些通常是最基本的模块必须依赖的东西。您可以将其视为Orchard的基类库。 ## Booting Up Orchard ## 启动果园 ## When an Orchard web application spins up, an Orchard `Host` gets created. A host is a singleton at the app domain level. 当Orchard Web应用程序旋转时,会创建一个Orchard`Host`。主机是应用程序域级别的单身人士。 Next, the host will get the `Shell` for the current tenant using the `ShellContextFactory`. Tenants are instances of the application that are isolated as far as users can tell, but that are running within the same appdomain, improving the site density. The shell is a singleton at the tenant level and could actually be said to represent the tenant. It's the object that will effectively provide the tenant-level isolation while keeping the module programming model agnostic about multi-tenancy. 接下来,主机将使用`ShellContextFactory`获取当前租户的`Shell`。租户是应用程序的实例,就用户而言是孤立的,但它们在同一个应用程序域中运行,从而提高了站点密度。外壳是租户级别的单身人士,实际上可以说代表租户。它的目标是有效地提供租户级隔离,同时保持模块编程模型不受多租户的影响。 The shell, once created, will get the list of available extensions from the `ExtensionManager`. Extensions are modules and themes. The default implementation is scanning the modules and themes directories for extensions. shell一旦创建,将从`ExtensionManager`获取可用扩展名列表。扩展是模块和主题。默认实现是扫描模块和主题目录以获取扩展。 At the same time, the shell will get the list of settings for the tenant from the `ShellSettingsManager`. The default implementation gets the settings from the appropriate subfolder of `App_Data` but alternative implementations can get those from different places. For example, we have an Azure implementation that is using blob storage instead because `App_Data` is not reliably writable in that environment. 同时,shell将从`ShellSettingsManager`获取租户的设置列表。默认实现从“App_Data”的相应子文件夹中获取设置,但是替代实现可以从不同的位置获取这些设置。例如,我们有一个使用blob存储的Azure实现,因为`App_Data`在该环境中不可靠。 The shell then gets the `CompositionStrategy` object and uses it to prepare the IoC container from the list of available extensions for the current host and from the settings for the current tenant. The result of this is not an IoC container for the shell, it is a `ShellBlueprint`, which is a list of dependency, controller and record blueprints. 然后shell获取`CompositionStrategy`对象,并使用它从当前主机的可用扩展列表和当前租户的设置中准备IoC容器。结果不是shell的IoC容器,它是一个`ShellBlueprint`,它是依赖,控制器和记录蓝图的列表。 The list of `ShellSettings` (that are per-tenant) and the `ShellBluePrint` are then thrown into `ShellContainerFactory.CreateContainer` to get an `ILifetimeScope`, which is basically enabling the IoC container to be scoped at the tenant level so that modules can get injected dependencies that are scoped for the current tenant without having to do anything specific. 然后将`ShellSettings`列表(即per-tenant)和`ShellBluePrint`列入`ShellContainerFactory.CreateContainer`以获得`ILifetimeScope`,这基本上使IoC容器能够在租户级别进行作用,以便模块可以获取为当前租户确定范围的注入依赖项,而无需执行任何特定操作。 ## Dependency Injection ## 依赖注入 ## The standard way of creating injectable dependencies in Orchard is to create an interface that derives from `IDependency` or one of its derived interfaces and then to implement that interface. On the consuming side, you can take a parameter of the interface type in your constructor. The application framework will discover all dependencies and will take care of instantiating and injecting instances as needed. 在Orchard中创建可注入依赖项的标准方法是创建一个从“IDependency”或其派生接口派生的接口,然后实现该接口。在消费方面,您可以在构造函数中获取接口类型的参数。应用程序框架将发现所有依赖项,并将根据需要负责实例化和注入实例。 There are three different possible scopes for dependencies, and choosing one is done by deriving from the right interface: 依赖关系有三种不同的可能范围,选择一种范围是通过从正确的接口派生来完成的: - _Request_: a dependency instance is created for each new HTTP request and is destroyed once the request has been processed. Use this by deriving your interface from `IDependency`. The object should be reasonably cheap to create. - _Request_:为每个新的HTTP请求创建一个依赖项实例,并在处理完请求后销毁。通过从`IDependency`派生您的接口来使用它。该对象应该相当便宜创建。 - _Object_: a new instance is created every single time an object takes a dependency on the interface. Instances are never shared. Use this by deriving from `ITransientDependency`. The objects must be extremely cheap to create. - _Object_:每次对象对接口具有依赖关系时,都会创建一个新实例。实例永远不会共享。通过从`ITransientDependency`派生来使用它。创建对象必须非常便宜。 - _Shell_: only one instance is created per shell/tenant. Use this by deriving from `ISingletonDependency`. Only use this for objects that must maintain a common state for the lifetime of the shell. - _Shell_:每个shell / tenant只创建一个实例。通过从`ISingletonDependency`派生来使用它。仅用于必须在shell的生命周期内保持公共状态的对象。 ### Replacing Existing Dependencies ## 替换现有的依赖项 It is possible to replace existing dependencies by decorating your class with the `OrchardSuppressDependency` attribute, that takes the fully-qualified type name to replace as an argument. 可以通过使用`OrchardSuppressDependency`属性来修改类来替换现有的依赖项,该属性将完全限定的类型名称替换为参数。 ### Ordering Dependencies ## 订购依赖关系 Some dependencies are not unique but rather are parts of a list. For example, handlers are all active at the same time. In some cases you will want to modify the order in which such dependencies get consumed. This can be done by modifying the manifest for the module, using the `Priority` property of the feature. Here is an example of this: 某些依赖项不是唯一的,而是列表的一部分。例如,处理程序同时处于活动状态。在某些情况下,您需要修改此类依赖项的使用顺序。这可以通过使用功能的“Priority”属性修改模块的清单来完成。这是一个例子: Features: Orchard.Widgets.PageLayerHinting: Name: Page Layer Hinting Description: ... Dependencies: Orchard.Widgets Category: Widget Priority: -1 ## ASP.NET MVC ## ASP.NET MVC ## Orchard is built on ASP.NET MVC but in order to add things like theming and tenant isolation, it needs to introduce an additional layer of indirection that will present on the ASP.NET MVC side the concepts that it expects and that will on the Orchard side split things on the level of Orchard concepts. Orchard是基于ASP.NET MVC构建的,但是为了添加主题和租户隔离之类的东西,它需要引入一个额外的间接层,它将在ASP.NET MVC端提供它所期望的概念以及它在Orchard上的概念在乌节概念层面上分裂的东西。 For example, when a specific view is requested, our `LayoutAwareViewEngine` kicks in. Strictly speaking, it's not a new view engine as it is not concerned with actual rendering, but it contains the logic to find the right view depending on the current theme and then it delegates the rendering work to actual view engines. 例如,当请求特定视图时,我们的`LayoutAwareViewEngine`就会启动。严格来说,它不是新的视图引擎,因为它不关心实际渲染,但它包含根据当前主题查找正确视图的逻辑然后它将渲染工作委托给实际的视图引擎。 Similarly, we have route providers, model binders and controller factories whose work is to act as a single entry point for ASP.NET MVC and to dispatch the calls to the properly scoped objects underneath. 类似地,我们有路由提供程序,模型绑定程序和控制器工厂,它们的工作是充当ASP.NET MVC的单个入口点,并将调用分派给下面的正确范围的对象。 In the case of routes, we can have `n` providers of routes (typically coming from modules) and one route publisher that will be what talks to ASP.NET MVC. The same thing goes for model binders and controller factories. 在路由的情况下,我们可以有`n`路由提供者(通常来自模块)和一个路由发布者,它将与ASP.NET MVC进行对话。模型粘合剂和控制器工厂也是如此。 ## Content Type System ## 内容类型系统 ## Contents in Orchard are managed under an actual type system that is in some ways richer and more dynamic than the underlying .NET type system, in order to provide the flexibility that is necessary in a Web CMS: types must be composed on the fly at runtime and reflect the concerns of content management. Orchard中的内容在实际类型系统下进行管理,该系统在某些方面比底层.NET类型系统更丰富,更动态,以便提供Web CMS中必需的灵活性:类型必须在运行时动态组合并反映内容管理的关注点。 ### Types, Parts, and Fields ## 类型,部件和字段 Orchard can handle arbitrary content types, including some that are dynamically created by the site administrator in a code-free manner. Those _content types_ are aggregations of _content parts_ that each deal with a particular concern. The reason for that is that many concerns span more than one content type. Orchard可以处理任意内容类型,包括由站点管理员以无代码方式动态创建的内容类型。那些_content types_是_content parts_的聚合,每个都处理一个特定的问题。原因是许多问题涉及多种内容类型。 For example, a blog post, a product and a video clip might all have a routable address, comments and tags. For that reason, the routable address, comments and tags are each treated in Orchard as a separate content part. This way, the comment management module needs to be developed only once, and can be applied to arbitrary content types, including those that the author of the commenting module did not know about. 例如,博客文章,产品和视频剪辑可能都有可路由的地址,评论和标签。出于这个原因,可路由的地址,注释和标签在Orchard中作为单独的内容部分处理。这样,评论管理模块只需要开发一次,并且可以应用于任意内容类型,包括评论模块的作者不知道的内容类型。 Parts themselves can have _properties_ and _content fields_. Content fields are also reusable in the same way that parts are: a specific field type will be typically used by several part and content types. The difference between parts and fields resides in the scale at which they operate and in their semantics. 零件本身可以有_properties_和_content fields_。内容字段也可以与部件相同的方式重复使用:特定字段类型通常由多个部件和内容类型使用。部件和字段之间的区别在于它们的操作规模和语义。 Fields are a finer grain than parts. For example, a field type might describe a phone number or a coordinate, whereas a part would typically describe a whole concern such as commenting or tagging. 田地比零件更精细。例如,字段类型可能描述电话号码或坐标,而部分通常会描述整个问题,例如评论或标记。 But the important difference here is semantics: you want to write a part if it implements an **is a** relationship, and you would write a field if it implements a **has a** relationship. 但是这里的重要区别是语义:如果它实现**是一个**关系,你想编写一个部分,如果它实现一个**有一个**关系,你会写一个字段。 For example, a shirt **is a** product and it **has a** SKU and a price. You wouldn't say that a shirt has a product or that a shirt is a price or a SKU. 例如,衬衫**是**产品,** **具有** SKU和价格。你不会说衬衫有产品或衬衫是价格还是SKU。 From that you know that the _Shirt_ content type will be made of a _Product_ part, and that the _Product_ part will be made from a _Money_ field named "price" and a _String_ field named SKU. 由此您知道_Shirt_内容类型将由_Product_部分组成,_Product_部分将由名为“price”的_Money_字段和名为SKU的_String_字段组成。 Another difference is that you have only one part of a given type per content type, which makes sense in light of the "is a" relationship, whereas a part can have any number of fields of a given type. Another way of saying that is that fields on a part are a dictionary of strings to values of the field's type, whereas the content type is a list of part types (without names). 另一个区别是每个内容类型只有一个给定类型的一部分,这根据“是一个”关系是有意义的,而一个部分可以有给定类型的任意数量的字段。另一种说法是,部分字段是字符串字典,字段类型的值,而内容类型是部分类型列表(没有名称)。 This gives another way of choosing between part and field: if you think people would want more than one instance of your object per content type, it needs to be a field. 这提供了另一种在部分和字段之间进行选择的方法:如果您认为人们每个内容类型需要多个对象实例,则它需要是一个字段。 ### Anatomy of a Content Type ## 内容类型的剖析 A _content type_, as we've seen, is built from _content parts_. Content parts, code-wise, are typically associated with: 正如我们所见,_content type_是从_content parts_构建的。代码方面的内容部分通常与以下内容相关联: - a Record, which is a POCO representation of the part's data - 记录,是部件数据的POCO表示 - a model class that is the actual part and that derives from `ContentPart`, where `T` is the record type - 一个模型类,它是实际的部分,派生自`ContentPart `,其中`T`是记录类型 - a repository. The repository does not need to be implemented by the module author as Orchard will be able to just use a generic one. - 存储库。存储库不需要由模块作者实现,因为Orchard将只能使用通用存储库。 - handlers. Handlers implement `IContentHandler` and are a set of event handlers such as `OnCreated` or `OnSaved`. Basically, they hook onto the content item's lifecycle to perform a number of tasks. They can also participate in the actual composition of the content items from their constructors. There is a `Filters` collection on the base `ContentHandler` that enables the handler to add common behavior to the content type. - 处理程序。处理程序实现`IContentHandler`并且是一组事件处理程序,例如`OnCreated`或`OnSaved`。基本上,他们挂钩内容项的生命周期来执行许多任务。他们还可以参与构造者的内容项的实际组成。在基础`ContentHandler`上有一个`Filters`集合,它使处理程序能够为内容类型添加常见行为。 For example, Orchard provides a `StorageFilter` that makes it very easy to declare how persistence of a content part should be handled: just do `Filters.Add(StorageFilter.For(myPartRepository));` and Orchard will take care of persisting to the database the data from `myPartRepository`. 例如,Orchard提供了一个`StorageFilter`,它可以很容易地声明应该如何处理内容部分的持久性:只需执行`Filters.Add(StorageFilter.For(myPartRepository));`和Orchard将保持持久化数据库来自`myPartRepository`的数据。 Another example of a filter is the `ActivatingFilter` that is in charge of doing the actual welding of parts onto a type: calling `Filters.Add(new ActivatingFilter(BlogPostDriver.ContentType.Name));` adds the body content part to blog posts. 过滤器的另一个例子是`ActivatingFilter`,它负责将部件实际焊接到一个类型上:调用`Filters.Add(new ActivatingFilter (BlogPostDriver.ContentType.Name));`添加正文内容博客文章的一部分。 - drivers. Drivers are friendlier, more specialized handlers (and as a consequence less flexible) and are associated with a specific content part type (they derive from `ContentPartDriver` where `T` is a content part type). Handlers on the other hand do not have to be specific to a content part type. Drivers can be seen as controllers for a specific part. They typically build shapes to be rendered by the theme engine. - 驱动程序。驱动程序是更友好,更专业的处理程序(因此更不灵活)并且与特定内容部件类型相关联(它们来自`ContentPartDriver `其中`T`是内容部件类型)。另一方面,处理程序不必特定于内容部件类型。可以将驱动程序视为特定部件的控制器。它们通常构建由主题引擎呈现的形状。 ## Content Manager ## 内容管理者 ## All contents are accessed in Orchard through the `ContentManager` object, which is how it becomes possible to use contents of a type you don't know in advance. 所有内容都通过`ContentManager`对象在Orchard中访问,这就是如何使用您事先不知道的类型的内容。 `ContentManager` has methods to query the content store, to version contents and to manage their publication status. ## Transactions ## 交易 ## Orchard automatically creates a transaction for each HTTP request. That means that all operations that happen during a request are part of an "ambient" transaction. If code during that request aborts that transaction, all data operations will be rolled back. If the transaction is never explicitly cancelled on the other hand, all operations get committed at the end of the request without an explicit commit. Orchard自动为每个HTTP请求创建一个事务。这意味着请求期间发生的所有操作都是“环境”事务的一部分。如果该请求期间的代码中止该事务,则将回滚所有数据操作。如果事务从未明确取消,则所有操作都会在请求结束时提交,而不会显式提交。 ## Request Lifecycle ## 请求生命周期 ## In this section, we'll take the example of a request for a specific blog post. 在本节中,我们将以特定博客帖子的请求为例。 When a request comes in for a specific blog post, the application first looks at the available routes that have been contributed by the various modules and finds the blog module's matching route. The route can then resolve the request to the blog post controller's item action, which will look up the post from the content manager. The action then gets a Page Object Model (POM) from the content manager (by calling `BuildDisplay`) based on the main object for that request, the post that was retrieved from the content manager. 当针对特定博客帖子发出请求时,应用程序首先查看各个模块提供的可用路由,并查找博客模块的匹配路由。然后,路由可以将请求解析为博客帖子控制器的项目操作,该操作将从内容管理器中查找帖子。然后,该操作从内容管理器(通过调用`BuildDisplay`)获取基于该请求的主对象(从内容管理器检索的帖子)的页面对象模型(POM)。 A blog post has its own controller, but that is not the case for all content types. For example, dynamic content types will be served by the more generic `ItemController` from the Core `Routable` part. The `Display` action of the `ItemController` does almost the same thing that the blog post controller was doing: it gets the content item from the content manager by slug and then builds the POM from the results. 博客文章有自己的控制器,但并非所有内容类型都是如此。例如,动态内容类型将由Core`Routable`部分中更通用的`ItemController`提供。 `ItemController`的`Display`动作几乎与博客文章控制器正在做的事情相同:它通过slug从内容管理器获取内容项,然后从结果中构建POM。 The layout view engine will then resolve the right view depending on the current theme and using the model's type together with Orchard conventions on view naming. 然后,布局视图引擎将根据当前主题解析右视图,并在视图命名上使用模型的类型和Orchard约定。 Within the view, more dynamic shape creation can happen, such as zone definitions. 在视图中,可以发生更多动态形状创建,例如区域定义。 The actual rendering is done by the theme engine that is going to find the right template or shape method to render each of the shapes it encounters in the POM, in order of appearance and recursively. 实际渲染由主题引擎完成,主题引擎将按照外观和递归的顺序找到正确的模板或形状方法,以呈现它在POM中遇到的每个形状。 ## Widgets ## 小工具 ## Widgets are content types that have the `Widget` content part and the `widget` stereotype. Like any other content type, they are composed of parts and fields. That means that they can be edited using the same edition and rendering logic as other content types. They also share the same building blocks, which means that any existing content part can potentially be reused as part of a widget almost for free. 窗口小部件是具有“窗口小部件”内容部分和“窗口小部件”构造型的内容类型。与任何其他内容类型一样,它们由部分和字段组成。这意味着可以使用与其他内容类型相同的版本和渲染逻辑来编辑它们。它们也共享相同的构建块,这意味着任何现有的内容部分都可以作为窗口小部件的一部分重新使用,几乎是免费的。 Widgets are added to pages through _widget layers_. Layers are sets of widgets. They have a name, a rule that determines what pages of the site they should appear on, and a list of widgets and associated zone placement and ordering, and settings. 窗口小部件通过_widget layers_添加到页面。图层是一组小部件。它们有一个名称,一个规则,用于确定它们应显示在哪个页面上,以及一个窗口小部件列表以及相关的区域放置和排序以及设置。 The rules attached to each of the layers are expressed with IronRuby expressions. Those expressions can use any of the `IRuleProvider` implementations in the application. Orchard ships with two out of the box implementations: "url" and "authenticated". 附加到每个层的规则用IronRuby表达式表示。这些表达式可以使用应用程序中的任何`IRuleProvider`实现。 Orchard附带两个开箱即用的实现:“url”和“authenticated”。 ## Site Settings ## 网站设置 ## A site in Orchard is a content item, which makes it possible for modules to weld additional parts. This is how modules can contribute site settings. Orchard中的站点是一个内容项,使模块可以焊接其他部件。这就是模块可以提供站点设置的方式。 Site settings are per tenant. 网站设置是每个租户。 ## Event Bus ## 活动巴士 ## Orchard and its modules expose extensibility points by creating interfaces for dependencies, implementations of which can then get injected. Orchard及其模块通过为依赖项创建接口来公开可扩展性点,然后可以实现其实现。 Plugging into an extensibility point is done either by implementing its interface, or by implementing an interface that has the same name and the same methods. In other words, Orchard does not require strictly strongly typed interface correspondence, which enables plug-ins to extend an extensibility point without taking a dependency on the assembly where it's defined. 插入扩展点可以通过实现其接口,也可以通过实现具有相同名称和相同方法的接口来完成。换句话说,Orchard不需要严格强类型的接口通信,这使插件能够扩展可扩展点而不依赖于定义它的程序集。 This is just one implementation of the Orchard event bus. When an extensibility point calls into injected implementations, a message gets published on the event bus. One of the objects listening to the event bus dispatches the messages to the methods in classes that derive from an interface appropriately named. 这只是Orchard事件总线的一个实现。当可扩展点调用注入的实现时,将在事件总线上发布消息。侦听事件总线的对象之一将消息调度到类中的方法,这些类派生自适当命名的接口。 ## Commands ## 命令 ## Many actions on an Orchard site can be performed from the command line as well as from the admin UI. These commands are exposed by the methods of classes implementing `ICommandHandler` that are decorated with a `CommandName` attribute. 可以从命令行以及管理UI执行Orchard站点上的许多操作。这些命令由实现`ICommandHandler`的类的方法公开,这些类使用`CommandName`属性进行修饰。 The Orchard command line tool discovers available commands at runtime by simulating the web site environment and inspecting the assemblies using reflection. The environment in which the commands run is as close as possible to the actual running site. Orchard命令行工具通过模拟Web站点环境并使用反射检查程序集,在运行时发现可用命令。命令运行的环境尽可能接近实际运行的站点。 ## Search and Indexing ## 搜索和索引 ## Search and indexing are implemented using Lucene by default, although that default implementation could be replaced with another indexing engine. 默认情况下,使用Lucene实现搜索和索引,但默认实现可以替换为另一个索引引擎。 ## Caching ## 高速缓存 ## The cache in Orchard relies on the ASP.NET cache, but we expose a helper API that can be used through a dependency of type `ICache`, by calling the `Get` method. `Get` takes a key and a function that can be used to generate the cache entry's value if the cache doesn't already contain the requested entry. Orchard中的缓存依赖于ASP.NET缓存,但我们通过调用`Get`方法公开了一个可以通过类型`ICache`的依赖项使用的辅助API。如果缓存尚未包含所请求的条目,则“Get”接受一个键和一个可用于生成缓存条目值的函数。 The main advantage of using the Orchard API for caching is that it works per tenant transparently. 使用Orchard API进行缓存的主要优点是它可以透明地为每个租户工作。 ## File Systems ## 文件系统 ## The file system in Orchard is abstracted so that storage can be directed to the physical file system or to an alternate storage such as Azure blob storage, depending on the environment. The `Media` module is an example of a module which uses that abstracted file system. Orchard中的文件系统是抽象的,因此可以将存储定向到物理文件系统或Azure blob存储等备用存储,具体取决于环境。 “Media”模块是使用该抽象文件系统的模块的示例。 ## Users and Roles ## 用户和角色 ## Users in Orchard are content items (albeit not routable ones) which makes it easy for a profile module for example to extend them with additional fields. Orchard中的用户是内容项(尽管不是可路由的),这使得配置文件模块可以轻松地使用其他字段扩展它们。 Roles are a content part that gets welded onto users. 角色是一个内容部分,可以焊接到用户身上。 ## Permissions ## 权限 ## Every module can expose a set of permissions as well as how those permissions should be granted by default to Orchard's default roles. 每个模块都可以公开一组权限,以及默认情况下如何将这些权限授予Orchard的默认角色。 ## Tasks ## 任务 ## Modules can schedule tasks by calling `CreateTask` on a dependency of type `IScheduledTaskManager`. The task can then be executed by implementing `IScheduledTaskHandler`. The `Process` method can examine the task type name and decide whether to handle it. 模块可以通过在类型为“IScheduledTaskManager”的依赖项上调用“CreateTask”来调度任务。然后可以通过实现`IScheduledTaskHandler`来执行该任务。 `Process`方法可以检查任务类型名称并决定是否处理它。 Tasks are run on a separate thread that comes from the ASP.NET thread pool. 任务在来自ASP.NET线程池的单独线程上运行。 ## Notifications ## 通知 ## Modules can surface messages to the admin UI by getting a dependency on `INotifier` and calling one of its methods. Multiple notifications can be created as part of any request. 模块可以通过获取对“INotifier”的依赖并调用其中一个方法来向管理UI显示消息。可以在任何请求中创建多个通知。 ## Localization ## 本土化 ## Localization _of the application and its modules_ is done by wrapping string resources in a call to the T method: `@T("This string can be localized")`. See [Using the localization helpers](Using-the-localization-helpers) for more details and guidelines. Orchard's resource manager can load localized resource strings from `.po` files located in specific places in the application. 应用程序及其modules_的本地化_是通过在调用T方法时包装字符串资源来完成的:`@T(“这个字符串可以被本地化”)`。有关详细信息和指南,请参阅[使用本地化帮助程序](使用本地化帮助程序)。 Orchard的资源管理器可以从位于应用程序中特定位置的`.po`文件加载本地化资源字符串。 _Content item_ localization is done through a different mechanism: localized versions of a content item are physically separate content items that are linked together by a special part. _Content item_本地化是通过不同的机制完成的:内容项的本地化版本是物理上独立的内容项,由特殊部分链接在一起。 The current culture to use is determined by the culture manager. The default implementation returns the culture that has been configured in site settings, but an alternate implementation could get it from the user profile or from the browser's settings. 目前使用的文化由文化经理决定。默认实现返回已在站点设置中配置的区域性,但是备用实现可以从用户配置文件或浏览器的设置中获取。 ## Logging ## 记录 ## Logging is done through a dependency of type `ILogger`. Different implementations can send the log entries to various storage types. Orchard comes with an implementation that uses [Castle.Core.Logging](https://github.com/castleproject/Windsor/blob/master/docs/logging-facility.md) for logging. 记录是通过类型`ILogger`的依赖项完成的。不同的实现可以将日志条目发送到各种存储类型。 Orchard附带了一个使用[Castle.Core.Logging](https://github.com/castleproject/Windsor/blob/master/docs/logging-facility.md)进行日志记录的实现。 # Orchard Core # 果园核心 # The `Orchard.Core` assembly contains a set of modules that are necessary for Orchard to run. Other modules can safely take dependencies on these modules that will always be available. `Orchard.Core`程序集包含一组Orchard运行所必需的模块。其他模块可以安全地依赖于始终可用的这些模块。 Examples of core modules are `Feeds`, `Navigation` or `Dashboard`. 核心模块的示例是“Feeds”,“Navigation”或“Dashboard”。 # Modules # 模块 # The default distribution of Orchard comes with a number of built-in modules such as blogging or pages, but third party modules are being built as well. Orchard的默认发行版附带了许多内置模块,如博客或页面,但也正在构建第三方模块。 A module is just an ASP.NET MVC area with a `Module.txt` file that is extending Orchard. 模块只是一个ASP.NET MVC区域,其中包含一个扩展Orchard的`Module.txt`文件。 A module typically contains event handlers, content types and their default rendering templates as well as some admin UI. 模块通常包含事件处理程序,内容类型及其默认呈现模板以及一些管理UI。 Modules can be dynamically compiled from source code every time a change is made to their `.csproj` file or to one of the files that the csproj file references. This enables a "notepad" style of development that does not require explicit compilation by the developer or even the use of an IDE such as Visual Studio. 每次对`.csproj`文件或csproj文件引用的文件之一进行更改时,可以从源代码动态编译模块。这使得“记事本”开发风格不需要开发人员进行显式编译,甚至不需要使用IDE等IDE。 Modules must be placed in the `Modules` folder (e.g. `Orchard.Web/Modules/MyModule`) and the folder name *must* match the name of the compiled DLL produced by the project. So, if you have a custom module project called `My.Custom.Module.csproj` and it compiles to `My.Custom.Module.dll`, then the module root folder must be named `My.Custom.Module`. [`~/Modules/My.Custom.Module/`]. There is support for defining [Custom Folders](Orchard-module-loader-and-dynamic-compilation#custom-folders). 模块必须放在`Modules`文件夹中(例如`Orchard.Web / Modules / MyModule`),文件夹名*必须*与项目生成的编译DLL的名称相匹配。因此,如果您有一个名为“My.Custom.Module.csproj”的自定义模块项目并且它编译为“My.Custom.Module.dll”,那么模块根文件夹必须命名为“My.Custom.Module”。 [`〜/模块/ My.Custom.Module /`]。支持定义[自定义文件夹](Orchard-module-loader-and-dynamic-compilation#custom-folders)。 # Themes # 主题 # It is a basic design principle in Orchard that all the HTML that it produces can be replaced from themes, including markup produced by modules. Conventions define what files must go where in the theme's file hierarchy. Orchard的一个基本设计原则是它生成的所有HTML都可以从主题中替换,包括模块生成的标记。约定定义了主题文件层次结构中必须包含的文件。 The whole rendering mechanism in Orchard is based on _shapes_. The theme engine's job is to find the current theme and, given that theme, determine what the best way to render each shape is. Each shape can have a default rendering that may be defined by a module as a template in the _views_ folder or as a shape method in code. That default rendering may be overridden by the current theme. The theme does that by having its own version of a template for that shape or its own shape method for that shape. Orchard中的整个渲染机制基于_shapes_。主题引擎的工作是找到当前主题,并根据该主题确定渲染每个形状的最佳方式。每个形状都可以具有默认渲染,可以将模块定义为_views_文件夹中的模板,也可以定义为代码中的形状方法。当前主题可以覆盖默认渲染。主题是通过为该形状创建自己的模板版本或者为该形状创建自己的形状方法。 Themes can have a parent, which enables child themes to be specializations or adaptations of a parent theme. Orchard comes with a base theme called the _Theme Machine_ that has been designed to make it easy to use as a parent theme. 主题可以具有父级,父级可以使子主题成为父主题的特化或修改。 Orchard附带一个名为_Theme Machine_的基本主题,旨在使其易于用作父主题。 Themes can contain code in much the same way modules do: they can have their own `.csproj` file and benefit from dynamic compilation. This enables themes to define shape methods, but also to expose admin UI for any settings they may have. 主题可以包含与模块大致相同的代码:它们可以拥有自己的`.csproj`文件,并从动态编译中受益。这使主题可以定义形状方法,但也可以显示管理UI以获取它们可能具有的任何设置。 The selection of the current theme is done by classes implementing `IThemeSelector`, which returns a theme name and a priority for any request. This allows many selectors to contribute to the choice of the theme. Orchard comes with four implementations of `IThemeSelector`: 当前主题的选择由实现`IThemeSelector`的类完成,该类返回主题名称和任何请求的优先级。这允许许多选择器为主题的选择做出贡献。 Orchard附带了四个`IThemeSelector`实现: - `SiteThemeSelector` selects the theme that is currently configured for the tenant or site with a low priority. - `SiteThemeSelector`选择当前为租户或具有低优先级的站点配置的主题。 - `AdminThemeSelector` takes over and returns the admin theme with a high priority whenever the current URL is an admin URL. - 只要当前URL是管理URL,“AdminThemeSelector”就会接管并返回具有高优先级的管理主题。 - `PreviewThemeSelector` overrides the site's current theme with the theme being previewed if the current user is the one that initiated the theme preview. - 如果当前用户是启动主题预览的用户,则“PreviewThemeSelector”会覆盖网站的当前主题,并预览主题。 - `SafeModeThemeSelector` is the only selector available when the application is in "safe mode", which happens typically during setup. It has a very low priority. - 当应用程序处于“安全模式”时,“SafeModeThemeSelector”是唯一可用的选择器,通常在安装过程中发生。它的优先级很低。 An example of a theme selector might be one that promotes a mobile theme when the user agent is recognized to belong to a mobile device. 主题选择器的示例可以是当用户代理被识别为属于移动设备时促销移动主题的示例。