[This post is part of the developer's manual preview published on this blog. You can find temporary TOC here.]
All this has changed in Sitefinity 3.6.
Views: Encapsulation of presentation and logic into simple building units
So far we have mentioned Views and the new architecture many times, giving more theoretic reasoning behind it. The sample products module, based on Generic Content module, will give a real-world justification of the new architecture.
The key idea of the improvements in the new architecture is to allow certain amount of functionality to be easily reused, while at the same time making modifications to the base implementation equally simple. While, reuse was not a problem even in the prior versions, things did get more complex when functionality or presentation needed to be changed.
With the new concept of the Views, developers are now able to cherry pick the Views (single contained unit of functionality) which they wish to reuse completely, reuse partially or implement from scratch, while leaving the rest of module intact.
Let us examine the architectural overview of the Products module, with special focus on the Views and their reuse.
As the legend suggests, we have been able to modify different Views to different degrees depending on our requirements.
First, we have ProductsControlPanel and ProductsView Views which have been completely implemented from the scratch, since we had no use of inheriting from base implementation.
Then, we have different products Views in yellow color which we have modified mostly to add new meta fields that Products module will use and change the labels on the user interface (e.g. where in Generic Content it would say: “Save this item”, we changed to “Save this product”).
Next are the three Views in the gray color on which we only changed the assembly info for embedded templates and localization, since we wanted to use the templates and localization resources from Telerik.Samples.Products assembly as opposed to Telerik.Cms.Engine assembly (where Generic Content module is located). The reason we have done this is to change several labels.
Finally, we have the Views for Comments administration in bluish color. We have not modified anything on these Views and we are using them directly from the Generic Content module. Since we are adding Views by specifying its type (or alternatively virtual path if we are working with user control based views), there is nothing preventing us to add a view which resides in different assembly - provided that we have referenced the view and that its entire implementation matches our requirements.
Implementing View from a scratch
When one wishes to implement a View from scratch for the Generic Content based module, the procedure is exactly the same as if it would be for implementing any other View, as it was described in this article.
One of such Views in Products module is the ProductsView View. Since ProductsView is only a container View (does not have its own graphical user interface, but rather displays the first view in the collection of its child views), the code for it is rather simple:
using Telerik.Cms.Engine; |
using Telerik.Cms.Web.UI; |
using Telerik.Cms.Engine.WebControls.Admin; |
using Telerik.Samples.Products.WebControls.Admin.Products; |
using Telerik.Samples.Products.Resources; |
namespace Telerik.Samples.Products.WebControls.Admin |
{ |
/// <summary> |
/// Container view for all product related views |
/// </summary> |
public class ProductsView : ViewModeControl<ProductsControlPanel>, IGenericContentHost |
{ |
/// <summary> |
/// Content Manager used by the control |
/// </summary> |
public ContentManager Manager |
{ |
get |
{ |
return this.Host.Manager; |
} |
} |
/// <summary> |
/// Gets the products manager. |
/// </summary> |
/// <value>The products manager.</value> |
public ProductsManager ProductsManager |
{ |
get |
{ |
return this.Host.ProductsManager; |
} |
} |
/// <summary> |
/// Loads configured views. |
/// </summary> |
protected override void CreateViews() |
{ |
AddView<ProductsItemListView>("ProductsItemListView", "ProductsItemListView_Title", "ProductsItemListView_Description", null, Messages.ResourceManager); |
AddView<ProductEditView>("ProductEditView", "ProductEditView_Title", "ProductEditView_Description", null, Messages.ResourceManager); |
AddView<ProductHistoryView>("ProductHistoryView", "ProductHistoryView_Title", "ProductHistoryView_Description", null, Messages.ResourceManager); |
AddView<ProductNewView>("ProductNewView", "ProductNewView_Title", "ProductNewView_Description", null, Messages.ResourceManager); |
AddView<ProductPreviewView>("ProductPreviewView", "ProductPreviewView_Title", "ProductPreviewView_Description", null, Messages.ResourceManager); |
} |
} |
} |
Moderately modifying the View
When talking about moderately modified Views, we understand that user interface (template), labels (localization) and certain business logic have been modified, while most of the functionality has been reused. Any of the product views such as ProductNewView or ProductHistoryView are good examples of this approach. Let us take a look at the ProductHistoryView first. ProductHistoryView inherits from ContentItemHistoryView and at the same time sets its host to the ProductsView, which is a host View in this context:
public class ProductHistoryView : ContentItemHistory<ProductsView> |
After we have declared the View, we will define a new embedded template for the View in which we will be able to change our user interface as needed. To do that we are overriding LayoutTemplateName and LayoutTemplatePath properties like follows:
/// <summary> |
/// Gets or sets the path to a custom layout template for the control. |
/// </summary> |
/// <value></value> |
[WebSysTemplate(ProductHistoryView.layoutTemplateName, "ProductHistoryView_Template_Desc", "/Products", false, "2009-04-10")] |
public override string LayoutTemplatePath |
{ |
get |
{ |
return base.LayoutTemplatePath; |
} |
set |
{ |
base.LayoutTemplatePath = value; |
} |
} |
/// <summary> |
/// Gets the name of the embedded layout template. |
/// </summary> |
/// <value></value> |
protected override string LayoutTemplateName |
{ |
get |
{ |
return ProductHistoryView.layoutTemplateName; |
} |
} |
private const string layoutTemplateName = |
"Telerik.Samples.Products.Resources.ControlTemplates.Backend.ProductHistoryView.ascx"; |
On the other hand, by overriding LayoutTemplateName, we are specifying the name of the embedded template which we wish to use with this View.
Now, that user interface has been modified, we are left with modifications of some functionality. Since Generic Content module in Sitefinity 3.6 has been designed for extensibility, certain virtual methods have been exposed for the purpose of simple View reuse. Following code demonstrates some of them exposed on ContentItemHistory base View:
/// <summary> |
/// Gets the edit command. |
/// </summary> |
/// <param name="itemId">The item id.</param> |
/// <returns></returns> |
protected override string GetEditCommand(Guid itemId) |
{ |
return CreateHostViewCommand<ProductEditView>(itemId.ToString()); |
} |
/// <summary> |
/// Gets the list command. |
/// </summary> |
/// <returns></returns> |
protected override string GetListCommand() |
{ |
return CreateHostViewCommand<ProductsItemListView>(); |
} |
/// <summary> |
/// Gets the preview command. |
/// </summary> |
/// <param name="itemId">The item id.</param> |
/// <returns></returns> |
protected override string GetPreviewCommand(Guid itemId) |
{ |
return CreateHostViewCommand<ProductPreviewView>(itemId.ToString()); |
} |
Barely modified Views
Sometimes we have no need to change the business logic of the View, but simply we wish to provide View with a different template and our own localization messages. In this case, all we need to do is override the AssemblyInfo and LocalizationAssemblyInfo properties of the base View and set them to the desired assembly.
The important thing to understand about those two properties is that they will default to the assembly in which View is located if not set differently. Now, let us see how in ProductsCategoriesView (View which belongs to Telerik.Samples.Products assembly) we have managed to use the templates and localization already defined in Telerik.Cms.Engine (since we didn’t need to make any changes):
/// <summary> |
/// Gets the type from the assembly containing the embedded resources. |
/// Cannot be null reference. |
/// </summary> |
/// <value></value> |
public override Type AssemblyInfo |
{ |
get |
{ |
return typeof(GenericControlPanel); |
} |
set |
{ |
base.AssemblyInfo = value; |
} |
} |
/// <summary> |
/// Gets the type from the assembly containing the embedded localization resource. |
/// Override if embedded templates are using ASP.NET localization. |
/// </summary> |
/// <value></value> |
public override Type LocalizationAssemblyInfo |
{ |
get |
{ |
return typeof(GenericControlPanel); |
} |
set |
{ |
base.LocalizationAssemblyInfo = value; |
} |
} |
Completely reusing Views
Sometimes we don’t need to change anything on the base implementation of the View. In such cases the simplest thing to do is to completely reuse the View. A good example of this approach are the comment Views. Namely, CommentsView View located implemented on Generic Content module has three child views:
- CommentsList
- CommentsEdit and
- CommentsPreview
Conclusion
Careful reader could come up with even more efficient way of reusing the base implementations on Products module, however, since this is a sample module different approaches have been used to demonstrate the versitality of the new backend architecture introduced in Sitefinity 3.6.
The sample products module discussed in this article can be downloaded from here.