Custom Data Type
With Ucommerce comes the ability to add custom data types. A custom data type gives you the ability to hook up external editors to save special data for custom properties on products, categories and campaign items.
This guide will walk you through and show you how easily we can extend the list of available data types.
Overview of Data Types
As you can see in the DataType
class, it contains a DefinitionName
property. The definition name is the identifier for what the underlying datatype and hence the UI is for a specific data type.
That little piece of information is what we'll use in the custom data type to differ what to do with it.
There are two interfaces we must implement to achieve this:
-
IControlFactory
- Has the responsibility to create a UI for the data type
-
IControlAdapter
- Has the responsibility to extract the entered/selected value from the control
Implementing IControlFactory
The interface is found under the namespace UCommerce.Presentation.Web.Controls
and you'll need a reference for the assembly UCommerce.Presentation.dll
.
In this example we'll create a control that allows us to pick a price group for a product.
We'll start of by creating the class and since we want to support price groups we'll call it PriceGroupControlFactory:
using System; using System.Web.UI; using UCommerce.EntitiesV2; using UCommerce.EntitiesV2.Definitions; using UCommerce.Presentation.Web.Controls; namespace MyUCommerceApp.DataTypes { public class PriceGroupControlFactory : IControlFactory { public bool Supports(DataType dataType) { throw new NotImplementedException(); } public Control GetControl(IProperty property) { throw new NotImplementedException(); } } }
The interface has two methods that we'll need to implement, which are:
- Supports
- GetControl
The use of IControlFactory
is provider based, which means that the underlying framework will ask every registration of IControlFactory
whether they support a specific Data type. You can create multiple control factories, which support the same data types, but the first IControlFactory
that returns true from the Supports
method will beused. So the order of the registration in config is important.
Implementing Supports
The first method we'll need to implement is Supports
which is what tells the framework if we can provide a control for a specific DataType.
private readonly DataTypeDefinition _priceGroupDefinition = new DataTypeDefinition("PriceGroup"); public bool Supports(DataType dataType) { return dataType.DefinitionName == _priceGroupDefinition.Name; }
Since the data type definition name is the context, we'll ask if the dataType.DefinitionName is equal to PriceGroup. Remember it is possible to create multiple data types with the same definition and we do want to support them all.
Implementing GetControl
Now that we have told the framework we support properties with a data type that has the definition name of "PriceGroup", we'll be asked to deliver a control for every property with a data type that has the definition name of "PriceGroup".
We will get both existing and new properties, so for existing properties it is important that we set the selected value of the control to what is already selected.
For new properties with the value of null or empty, we can just find a default strategy for what we'll do.
In this case we'll provide a drop down list where we will set the id of the price group as the value that may be selected. Ultimately this results in the value of the selected element is saved in the database as the value of the property.
public Control GetControl(IProperty property) { //This is the drop down list we'll return to use in the UI. var dropDownList = new SafeDropDownList(); var priceGroups = PriceGroup.All().ToList(); foreach (var priceGroup in priceGroups) { var listItem = new ListItem(); listItem.Value = priceGroup.PriceGroupId.ToString(); listItem.Text = priceGroup.Name; //We'll mark this item as selected if the value of the property is equal to the price group id. listItem.Selected = property.GetValue().ToString() == priceGroup.PriceGroupId.ToString(); dropDownList.Items.Add(listItem); } return dropDownList; }
So where does the DataType.DefinitionName come from?
That's a good question. It comes from the id of the registration of the IControlFactory. So the id of the component we'll register now is very important.
In this case we'll register the new factory with the name "PriceGroupControlFactory".
If you do not know how to register a component check out How to register a component in Ucommerce
Now that we have registered it with PriceGroupControlFactory, we can select "Price Group" in the list. The frame work will trim away ControlFactory and add spaces where there's camel casing, so if we registered another factory with: "MultiNodeTreePickerControlFactory" it would result in : "Multi Node Tree Picker" being in the list.
Implementing IControlAdapter
As mentioned the control adapter is responsible for extracting the value of the control produced by the our control factory. The reason it's necessary is that .NET doesn't provide a unified method for reading values from controls, e.g. a TextBox
has Text
property while a CheckBox
has a Checked
property and so forth.
The ControlAdapter
smoothes out these differences for us.
You don't always have to build a new control adapter to support your data type because they tend to be more reusable across controls, e.g. you may have control factories to display drop downs for price groups and shipping methods, but they both use the underlying control DropDownList
and thus you only need a single control adapter for both of them.
Ucommerce ships with a number of control adapters out of the box, which means you might not have to create your own adapter at all. You can see these in "DataTypes.config" under the "Control Adapter" section. Please note that ""Shell.config will potentially hold factories and adapters specific to the CMS you're using as well.
using System.Web.UI; using UCommerce.Presentation.Web.Controls; namespace MyUCommerceApp.DataTypes { public class PriceGroupControlAdapter : IControlAdapter { public bool Adapts(Control control) { throw new NotImplementedException(); } public object GetValue(Control control) { throw new NotImplementedException(); } } }
Now that we've created a provider for selecting Price Groups we want to let the framework know how to get at the actual value of the control.
using System.Web.UI; using UCommerce.Presentation.Web.Controls; namespace MyUCommerceApp.DataTypes { public class PriceGroupControlAdapter : IControlAdapter { public bool Adapts(Control control) { //We can easy tell that we know to Adapt and get a value from a SafeDropDownList //If we can cast the control to one of that type, we knows hhow to get the value. return (control as SafeDropDownList != null); } public object GetValue(Control control) { //Since the framework was told that we adapt the control, it will ask for the value. //We know we can cast it, so we do that and call SelectedValue. return (control as SafeDropDownList).SelectedValue; } } }
All there's left to do is register it like you registered the other component. The id of the component is less important this time, but using the same convention is always a good idea and best practice.
Add a definition field with the new picker.
First you must create a new data type and give it the definition of Price Group. Right-click the data types node in the tree and click "Create" and do so.
You can use whatever name you prefer.
With that you have a new data type that you can use to create a definition field with.
Summary
In this article you've learned how to create a custom data type and that:
- You can use a custom data type to select special values for custom properties on Products, Categories and Campaign Items.
-
IControlFactory is used to deliver a control to edit a custom property for a datatype.
- The IControlFactory should support a specific DataType.DefinitionName and not the datatype itself.
- The id you use for the IControlFactory when registering it, is important since it will be used to display and as the actual name of the definition.
- IControlAdapter is used to deliver the value of the control since the Framework Itself does not know how to extract the value.
-
Both interfaces are used in a provider based way so the framework will ask all implementations of both interfaces untill one returns true in Supports / Adapts.
- The order and what you return true for is important.