Prior to NetServer version 8 it was possible to extend Archive Providers by either the addition of new entity rows to an existing provider or add extenders to add columns to existing row types.
While the "add-an-extender" model works reasonably well, it is fairly complicated to use. If interested, read about how archive providers work in the Archives Providers in Depth section of the NetServer Core documentation.
The complication stems from the fact that a new extender is tagged with the ArchiveExtender attribute that specifies the name of the provider to extend. This works OK when adding columns to one particular provider, but not when the desired effect is to add columns to a particular table or other well-defined entity.
For instance, adding extra columns to a person that should be present in all contexts where person information is shown (Find, Contact archive under company card, Activity archive, Selection members, Shadow selection members, etc) would require a lot of coding and overriding; as well as a fair amount of reverse-engineering to find out how the providers are actually structured. In the case of Find and Selection this can be a Big Deal.
Add Columns to Related Archive, Wherever!
Enter the new dimension: An ExtenderExtender! This is actually just a small tweak to the ArchiveProviderFactory, where it searches for a new attribute during the construction of an archive provider. The attribute is called ArchiveExtenderExtender, and takes the type of an existing ArchiveExtender as its parameter.
During construction, the Factory recursively traverses all extenders that are part of the Provider being built. Whenever it finds an extender that IsAssignable from the type specified in the attribute, it will instantiate and add the new component.
As an example, consider the following declaration:
[ArchiveExtenderExtender("PersonArchiveExtension", typeof(PersonExtenderBase), int.MaxValue / 2)]
public class PersonArchiveExtension : TableExtenderBase<TableInfo>
The type parameter to the attribute is typeof(PersonExtenderBase), so the factory will attach a new instance of PersonArchiveExtension to every extender that is derived from PersonExtenderBase, in every provider it constructs.
The implementation of the PersonArchiveExtension can safely assume that Parent.TableToExtend is an instance of PersonTableInfo, since the factory will attach it to PersonExtenderBase instances, and the PersonExtenderBase will always have a PersonTableInfo as its TableToExtend.
In short, we can do things like:
protected override TableInfo SetJoin()
var parentPersonInfo = Parent.TableToExtend as PersonTableInfo;
Here, we join the parent Person table to ForeignKey, because there is something interesting there that we’d like to fetch and show as columns related to persons – everywhere a person is shown.
This is an extremely powerful mechanism and a big simplification. Columns exposed by this extender will appear in every context where a Person appears – all the archives mentioned above.
Rather than appearing under the entity fields, the new columns are available under the new menu item "Other" - as seen in the Find dialog image above.
If these columns are searchable then they can immediatley be used as criteria in dynamic and static selections, you can filter on them in archives, and generally do everything you can with the «native» person columns.
Here is the example code used to demonstrate how easy it is to show the new Sale columns anywhere:
[ArchiveExtenderExtender("MySaleTableExtender", typeof(SaleExtenderBase), int.MaxValue / 2)]
public class MySaleTablelSystem : TableExtenderBase<TableInfo>
private ArchiveColumnInfo _colMySaleId =
new ArchiveColumnInfo("mySaleId", "My Sale Record Identifier", "My Sale Record Identifer Tooltip",
Constants.DisplayTypes.Int, true, true, "5c", Constants.RestrictionTypes.Int);
private ArchiveColumnInfo _colMySaleName =
new ArchiveColumnInfo("mySaleName", "My Sale Name", "My Sale Name Tooltip",
Constants.DisplayTypes.String, true, true, "10%", Constants.RestrictionTypes.String);
// Uncomment following line to force restriction to invoke InnerModifyQuery
// ForceRestriction = true;
protected override void InnerModifyQuery()
protected override TableInfo SetJoin()
var parentSaleInfo = Parent.TableToExtend as SaleTableInfo;
//... establish joins
protected override void InnerPopulateRowFromReader(SoDataReader reader, ArchiveRow row)
// use this overload to add columns to archiverow / output
The key takeaways are:
- Use an ExtenderExtender to easily expose new columns anywhere.
- Use the same techniques as before to:
- automatic detection of columns declared with _col prefix.
- use SetJoin to expand existing table joins, or add criteria
- use ForceRestriction to invoke InnerModifyQuery to add return fields, or change existing query
- use InnerPopulateRowFromReader to access reader and add column data to ArchiveRow output
This article demonstrates how easy it is to add columns of related data to existing archive extenders through out SuperOffice without the complexity of creatign multiple extenders and base extenders previously required before SuperOffice CRM version 8.