Kentico 9 e-Commerce Checkout 添加步骤时类型转换失败
结论
ShoppingCartStep 不是 Web Part,不能注册为普通 Web Part,也不能直接放进页面的 Web Part Zone。
出现报错是因为 Kentico 在加载“EntityUse Code”时,将对应的 ASCX 控件视为 Web Part,并尝试把它转换成 CMSAbstractWebPart。但该控件继承的是 ShoppingCartStep,与 CMSAbstractWebPart 没有可用的继承关系,所以转换失败。
正确的处理方式是:
- 保持 ASCX 控件继承
ShoppingCartStep。 - 使用 Kentico 9 结账页原有的购物车或结账 Web Part 作为宿主。
- 在宿主 Web Part 的步骤配置中注册自定义 ASCX 路径,不要把 ASCX 本身注册为 Web Part。
- 如果需要把控件直接拖入页面,必须另外编写一个继承
CMSAbstractWebPart的包装 Web Part。不过,添加结账步骤通常不应采用这种方式。
为什么会出现类型转换错误
当前代码声明如下:
public partial class CMSModules_Ecommerce_Controls_ShoppingCart_ShoppingCartNonProfit
: ShoppingCartStep
{
}
因此,运行时生成的 ASCX 类型是 ShoppingCartStep。普通 Kentico Web Part 的代码隐藏类则必须直接或间接继承:
CMS.PortalControls.CMSAbstractWebPart
将这个 ASCX 注册为 Web Part 后,Kentico 会执行类似下面的转换:
CMSAbstractWebPart webPart = (CMSAbstractWebPart)loadedControl;
由于 ShoppingCartStep 不是 CMSAbstractWebPart,系统便会报错:
Unable to cast object of type '...shoppingcartnonprofit_ascx'
to type 'CMS.PortalControls.CMSAbstractWebPart'
这个错误与 ShoppingCartCustomData 或 ProcessStep() 无关,问题出在控件的注册和加载方式上。
正确添加结账步骤
1. 不要把步骤控件注册为普通 Web Part
如果已经在 Kentico 的 Web Parts 应用中创建了指向该 ASCX 文件的“EntityUse Code”Web Part,请先从页面模板中移除它。
只删除页面上的 Web Part 实例,就可以消除加载时的类型转换异常。是否删除对应的 Web Part 定义,取决于它是否还有其他用途。
2. 以现有结账步骤为模板创建控件
建议复制 Kentico 9 项目中已有的 ShoppingCartStep 控件结构,包括:
- ASCX 文件中的指令和控件标记;
- 代码隐藏文件;
- designer 文件;
- 继承
ShoppingCartStep的类; - 原有结账步骤使用的命名空间和引用。
ASCX 中的 Inherits 必须与代码隐藏类完全一致。例如:
<%@ Control Language="C#"
AutoEventWireup="true"
CodeFile="ShoppingCartNonProfit.ascx.cs"
Inherits="CMSModules_Ecommerce_Controls_ShoppingCart_ShoppingCartNonProfit" %>
代码隐藏类继续继承 ShoppingCartStep:
public partial class CMSModules_Ecommerce_Controls_ShoppingCart_ShoppingCartNonProfit
: ShoppingCartStep
{
// Step implementation
}
不要为了消除转换错误而把基类改成 CMSAbstractWebPart。这样虽然满足了普通 Web Part 的加载要求,却会失去结账步骤需要的 ShoppingCart、ShoppingCartControl、ProcessStep() 和前后导航机制。
3. 在结账宿主 Web Part 中配置步骤
进入结账页面的 Design 模式,找到承载结账流程的购物车或结账 Web Part,然后打开其配置。
Kentico 9 的结账实现可能因项目模板、升级历史和定制情况而有所不同,但步骤配置通常会引用各个 ShoppingCartStep ASCX 控件。请在该配置中添加自定义步骤,并将控件路径指向你的 ASCX 文件,例如:
~/CMSModules/Ecommerce/Controls/ShoppingCart/ShoppingCartNonProfit.ascx
步骤顺序由结账宿主控件的配置决定,与页面树中新建页面的顺序无关。
具体字段名称和配置界面应以当前项目已有的结账步骤为准。可以先找到地址、配送或付款等现有步骤的 ASCX 路径,再用相同格式加入自定义控件。
代码调整示例
除了加载方式,原代码还有一个常见问题。Page_Load 会在每次回发时重新读取 ShoppingCartCustomData 并给文本框赋值,这可能覆盖用户刚提交的内容。初始化表单时应检查 IsPostBack。
using System;
using CMS.Ecommerce;
public partial class CMSModules_Ecommerce_Controls_ShoppingCart_ShoppingCartNonProfit
: ShoppingCartStep
{
private const string FEDERAL_TAX_ID = "FederalTaxID";
private const string ENTITY_USE_CODE = "EntityUseCode";
protected void Page_Load(object sender, EventArgs e)
{
lblTitle.Text = "Tax Exempt Order?";
if (!IsPostBack)
{
object fedTaxId =
ShoppingCart.ShoppingCartCustomData.GetValue("federaltaxid");
object entityUseCode =
ShoppingCart.ShoppingCartCustomData.GetValue("entityusecode");
if (fedTaxId != null)
{
tbTaxID.Text = fedTaxId.ToString();
}
if (entityUseCode != null)
{
tbEntityUseCode.Text = entityUseCode.ToString();
}
}
}
public override void ButtonBackClickAction()
{
ShoppingCartControl.SetTempValue(FEDERAL_TAX_ID, tbTaxID.Text);
ShoppingCartControl.SetTempValue(
ENTITY_USE_CODE,
tbEntityUseCode.Text
);
base.ButtonBackClickAction();
}
public override bool ProcessStep()
{
try
{
ShoppingCart.ShoppingCartCustomData.SetValue(
"federaltaxid",
tbTaxID.Text.Trim()
);
ShoppingCart.ShoppingCartCustomData.SetValue(
"entityusecode",
tbEntityUseCode.Text.Trim()
);
if (!ShoppingCartControl.IsInternalOrder)
{
ShoppingCartInfoProvider.SetShoppingCartInfo(ShoppingCart);
}
return true;
}
catch (Exception ex)
{
lblError.Visible = true;
lblError.Text = ex.Message;
return false;
}
}
}
如果希望用户点击“返回”后再次进入该步骤时,仍能恢复尚未写入数据库的值,初始化时还需要读取 ShoppingCartControl 中的临时值。具体取值 API 请参考当前 Kentico 9 项目中已有结账步骤的实现,因为不同修订版本或定制代码使用的辅助方法可能不同。
需要检查的事项
控件类型必须与加载位置匹配
这两类控件不能混用:
普通页面 Web Part
→ 继承 CMSAbstractWebPart
→ 放入 Web Part Zone
结账流程步骤
→ 继承 ShoppingCartStep
→ 由结账宿主控件加载
ASCX 路径必须正确
确认步骤配置引用了实际文件,并使用应用相对路径:
~/CMSModules/Ecommerce/Controls/ShoppingCart/ShoppingCartNonProfit.ascx
同时检查文件名大小写、所在目录,以及 ASCX 中的 CodeFile 和 Inherits。
自定义数据键应保持一致
写入和读取时必须使用完全相同的键:
"federaltaxid"
"entityusecode"
建议将键定义为常量,避免出现大小写或拼写不一致。原代码中的:
private const string PAYMENT_OPTION_ID = "OrderPaymenOptionID";
似乎缺少字母 t,而且当前代码没有使用这个常量,可以将其删除或修正。
不要依赖新建页面来定义步骤
在页面树的 Checkout 节点下新建页面,不会自动把该页面变成 ShoppingCartStep。结账步骤通常由结账 Web Part 在内部加载和切换。页面树用于承载整个流程,步骤控件则负责流程中的具体界面和处理逻辑。
因此,解决这个错误不需要强制转换类型,也不需要修改 ProcessStep()。应撤销错误的 Web Part 注册,让现有结账宿主按照 ShoppingCartStep 的方式加载 ShoppingCartNonProfit.ascx。
备注:内容仅供参考。
