

























创建一个 Web CMS (内容管理系统)与创建普通的web应用程序不一样。它更像是建造一个应用程序容器。当设计一个这样的系统时,可扩展性必须作为首要的特性。 这会是一个挑战,必须允许具有可扩展性的挑战可能会危及应用程序的可用性:系统中所有的事情都需要与未知特性的模块组合,包括用户界面层。将那些未知的彼此并不了解的小部件组合成 一个连贯的整体,这就是orchard要做的事情。
这个文档解析了orchard架构的选择,和他们是如何解决灵活性和用户体验之间的矛盾。
| Modules | |||
| Core | |||
| Orchard Framework | |||
| ASP.NET MVC | NHibernate | Autofac | Castle |
| .NET | ASP.NET | ||
| IIS or Windows Azure | |||
Orchard CMS 建立在已有的frameworks 和libraries上,下面是一些最基本的:
Orchard 应用程序和基本架构创建在这些基本架构之上的附加的抽象层。他们在许多实现细节上并不需要了解 NHibernate, Castle, 和 Autofac,虽然orchard需要他们一起来工作。
Orchard framework 是 Orchard最下面的一层. 它包含应用程序引擎或至少不能独立成模块的部分。那些是典型的东西,甚至最基本的模块都必须依赖它。 你可以认为它是orchard的基本类库。
当一个Orchard web应用程序运转起来时,一个Orchard host被创建了。一个 host 在应用域层的一个单例.
下一步,host 会为当前的租户(tenant)用ShellContextFactory创建一个Shell . 租户是隔离用户通讯的应用程序实例,但他们郧西县在相同的应用域中,这样就增加了站点的密度。shell是在租户层的实例,确切的说就代表了租户。It's the object that will effectively provide the tenant-level isolation while keeping the module programming model agnostic about multi-tenancy.
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.
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 Appdata but alternative implementations can get those from different places. For example, we have an Azure implementation that is using blob storage instead because Appdata is not reliably writable in that environment.
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.
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.
在orchard中标准的创建依赖注入方法是创建一个接口从IDependency 或者它的继承者继承,然后实现这个接口。使用者时,你可以在构造函数载入一个接口参数. 应用程序基本库会发现所有的依赖性,在需要的时候照顾好相关的实例。
有三种不同范围来处理依赖性,选择从正确的接口中继承:
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.
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:
Features:
Orchard.Widgets.PageLayerHinting:
Name: Page Layer Hinting
Description: ...
Dependencies: Orchard.Widgets
Category: Widget
Priority: -1
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.
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.
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.
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.
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 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.
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 can be developed only once and apply to arbitrary content types, including those that the author of the commenting module did not know about.
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.
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.
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.
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.
A content type, as we've seen, is built from content parts. Content parts, code-wise, are typically associated with:
ContentPart<T> where T is the record typeFilters.Add(StorageFilter.For(myPartRepository)); and Orchard will take care of persisting to the database the data from myPartRepository.Filters.Add(new ActivatingFilter<BodyAspect>(BlogPostDriver.ContentType.Name)); adds the body content part to blog posts.ContentPartDriver<T>
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.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 has methods to query the content store, to version contents and to manage their publication status.
Orchard is automatically creating 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.
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.
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.
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.
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.
Widgets are content types that have the Widget content part and the widget stereotype. Like any other content types, 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.
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.
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.
Site settings are per tenant.
Orchard and its modules expose extensibility points by creating interfaces for dependencies, implementations of which can then get injected.
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.
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.
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.
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.
Search and indexing are implemented using Lucene by default, although that default implementation could be replaced with another indexing engine.
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 contains the requested entry.
The main advantage of using the Orchard API for caching is that it works per tenant transparently.
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 that uses that abstracted file system.
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. Roles are a content part that gets welded onto users.
Every module can expose a set of permissions as well as how those permissions should be granted by default to Orchard's default roles.
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.
Tasks are being run on a separate thread that comes from the ASP.NET thread pool.
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.
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
for more details and guidelines. Orchard's resource manager can load
localized resource strings from PO files located in specific places in
the application.
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.
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 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 for logging.
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.
Examples of core modules are feeds, navigation or routable.
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.
A module is just an ASP.NET MVC area with a manifest.txt file that is extending Orchard.
A module typically contains event handlers, content types and their default rendering templates as well as some admin 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 no require explicit compilation by the developer or even the use of an IDE such as Visual Studio.
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.
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.
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.
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.
The selection of the current theme is done by classes implementing IThemeSelector, which return 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:
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.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。