Documentation

Ucommerce includes full API reference documentation and lots of helpful articles to help you build your e-commerce site as effortlessly as possible.

Topics Payment Providers
v7.18

Custom Data Type

Ucommerce comes with the ability to add custom data types. A custom data type gives you the ability to hook up custom editors to save special data for custom properties on products, categories, campaign items, data types, etc.

This guide will walk you through and show you how easily we can extend the list of available editors. If you want to know how data types are tied together with definitions, you can read more about definitions here.

Overview of Data Types

As you can see in the DataType table, it has a reference to a Definition which describes the data available on a DataType. A DataType will have at least two DefinitionField on its Definition and those two are named definition and editor. The definition property is used to store which Definition is selected for the DataType and the editor property stores which editor should be used when the data is presented. We'll use the latter in the custom data type to differ what to do with it.

image

The editor property are defined on the "Data Type" definition which is the base definition for data types, which all other data type definitions must inherit. You can read more about inherited definitions here.

The editor picked for the DataType is stored as an EntityProperty and to get the value, we will use the IEntityPropertyValueService which will be elaborated in the next section.

When creating a custom data type there are two interfaces that we must implement:

  • IControlFactory: has the responsibility to create a UI for the data type.
  • IControlAdapter: as 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 to the assembly UCommerce.Presentation.dll.

In this example, we'll create a control that allows us to pick a price group for a product.

image

We'll start of by creating the class and since we want to support price groups, we'll call it PriceGroupPickerControlFactory:

    
    
    	using System;
    	using System.Web.UI;
    	using UCommerce.EntitiesV2;
    	using UCommerce.EntitiesV2.Definitions;
    	using UCommerce.Presentation.Web.Controls;
    
    	namespace MyUCommerceApp.DataTypes
    	{
    		public class PriceGroupPickerControlFactory : 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 be used. Therefore, the order of the registration in configuration is very important.

Implementing Supports

The first method we'll need to implement is Supports which is what tells the framework if we have a provider registered for the specific DataType.

    
    
    	private readonly IRepository<PriceGroup> _priceGroupRepository;
    	private readonly IEntityPropertyValueService _entityPropertyValueService;
    
    	public PriceGroupPickerControlFactory(IRepository<PriceGroup> priceGroupRepository, IEntityPropertyValueService entityPropertyValueService)
    	{
    		_priceGroupRepository = priceGroupRepository;
    		_entityPropertyValueService = entityPropertyValueService;
    	}
    
    	public bool Supports(DataType dataType)
    	{
    		var editor = _entityPropertyValueService.GetPropertyValue(dataType.Definition, dataType.Guid, "Editor") as String;
    		return editor == "PriceGroupPicker";
    	}
    
    

We will use the IEntityPropertyValueService to get the value of editor property for the DataType and check if the selected editor is equal to "PriceGroupPicker" for which we want to create the control. 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 "PriceGroupPicker" as its editor, we'll be asked to deliver a control for every property with a data type that has the "PriceGroupPicker" as its editor.

The GetControl method needs to be able to handle scenarios where a value has previously been selected and should be shown as the selected value. It also needs to handle scenarios where no value has been selected.

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 being saved in the database as the value of the property as a EntityProperty.

    
    
    	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 = _priceGroupRepository.Select().ToList();
    
    		foreach (var priceGroup in priceGroups)
    		{
    			dropDownList.Items.Add(new ListItem
    			{
    				Value = priceGroup.PriceGroupId.ToString(),
    				Text = priceGroup.Name,
    				//We'll mark this item as selected if the value of the property is equal to the price group id.
    				Selected = property.GetValue().ToString() == priceGroup.PriceGroupId.ToString()
    			});
    		}
    
    		return dropDownList;
    	}
    
    

Below we have the full implementation of the "PriceGroupPickerControlFactory".

    
    
    	using System;
    	using System.Web.UI;
    	using UCommerce.EntitiesV2;
    	using UCommerce.EntitiesV2.Definitions;
    	using UCommerce.Presentation.Web.Controls;
    
    	namespace MyUCommerceApp.DataTypes
    	{
    		public class PriceGroupPickerControlFactory : IControlFactory
    		{
    			private readonly IRepository<PriceGroup> _priceGroupRepository;
    			private readonly IEntityPropertyValueService _entityPropertyValueService;
    
    			public PriceGroupPickerControlFactory(IRepository<PriceGroup> priceGroupRepository, IEntityPropertyValueService entityPropertyValueService)
    			{
    				_priceGroupRepository = priceGroupRepository;
    				_entityPropertyValueService = entityPropertyValueService;
    			}
    
    			public bool Supports(DataType dataType)
    			{
    				var editor = _entityPropertyValueService.GetPropertyValue(dataType.Definition, dataType.Guid, "Editor") as String;
    				return editor == "PriceGroupPicker";
    			}
    
    			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 = _priceGroupRepository.Select().ToList();
    
    				foreach (var priceGroup in priceGroups)
    				{
    					dropDownList.Items.Add(new ListItem
    					{
    						Value = priceGroup.PriceGroupId.ToString(),
    						Text = priceGroup.Name,
    						//We'll mark this item as selected if the value of the property is equal to the price group id.
    						Selected = property.GetValue().ToString() == priceGroup.PriceGroupId.ToString()
    					});
    				}
    
    				return dropDownList;
    			}
    		}
    	}
    
    

So where do the editor options 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 id "PriceGroupPickerControlFactory" as shown below.

    
    
    	<component
    		id="PriceGroupPickerControlFactory"
    		service="UCommerce.Presentation.Web.Controls.IControlFactory, UCommerce.Presentation"
    		type="MyUCommerceApp.DataTypes.PriceGroupPickerControlFactory, MyUCommerceApp" />
    
    

If you want to learn more about how to register a component check out How to register a component in Ucommerce.

Now that we have registered it with the id "PriceGroupPickerControlFactory", we can select "Price Group Picker" in the list. The framework 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.

image

Implementing IControlAdapter

The control adapter is responsible for extracting the value of the control, which is produced by the control factory. The reason for which this is 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 are 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 PriceGroupPickerControlAdapter : 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 PriceGroupPickerControlAdapter : 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 how 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 IControlAdapter component is less important this time, but using the same convention is always a good idea and best practice. Below you can see the registration of the new control adapter.

    
    
    	<component
    		id="PriceGroupPickerControlAdapter"
    		service="UCommerce.Presentation.Web.Controls.IControlAdapter, UCommerce.Presentation"
    		type="MyUCommerceApp.DataTypes.PriceGroupPickerControlAdapter, MyUCommerceApp" />
    
    

Add a definition field with the new picker.

First you must create a new data type and set the editor to "Price Group Picker". Right-click the data types node in the tree and click "Create". You can use whatever name you prefer.

image

You now have a new data type that you can use to create a definition fields. You can read more about definitions here.

image

Summary

In this article you've learned how to create a custom data type:

  • You can use a custom data type to select special values for custom properties on products, categories, campaign items, data types, etc.
  • IControlFactory is used to deliver a control to edit a custom property for a DataType.
    • The IControlFactory should support a specific editor 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 editor.
  • 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 pattern so the framework will ask all implementations of both interfaces until one returns true in Supports / Adapts.
    • Therefore, the order in which the registered components are picked up and what they return true for are important.