您可以使用 ISiteMapBuilder 执行此操作,但您最好还是实现 ISiteMapNodeProvider。原因是因为必须在所有节点都被实例化后,在流程的最后阶段将节点添加到 SiteMap,以确保每个节点都正确映射到父节点(当然,根节点除外,它没有需要父母)。这是在 4.3.0 中完成的主要设计更改。
现在已经设置了默认的 SiteMapBuilder 类以确保
- 节点正确映射到它们的父节点
- 只有1个根节点
- 所有节点都添加到 SiteMap
- 访问者在站点地图完全构建后最后执行
添加多个 ISiteMapBuilder 实例是没有意义的,因为这样可以规避这一重要逻辑。因此,最好不实现 ISiteMapBuilder,而是实现 ISiteMapNodeProvider。
SiteMapBuilder 类通过其构造函数将 ISiteMapNodeProvider 作为依赖项。您可以使用 CompositeSiteMapNodeProvider 类来处理此接口上的多重性,因此您可以在需要时添加多个 ISiteMapNodeProvider 实现。
ISiteMapNodeProvider 接口如下所示:
public interface ISiteMapNodeProvider
{
IEnumerable<ISiteMapNodeToParentRelation> GetSiteMapNodes(ISiteMapNodeHelper helper);
}
只有一种方法可以实现。此外,许多常见(但可选)服务是通过 ISiteMapNodeHelper 从 SiteMapBuilder 类通过接口自动注入的。
该类的级别低于 IDynamicNodeProvider。您直接与 ISiteMapNode 交互,但与 SiteMap 类的所有交互都由 SiteMapBuilder 处理。ISiteMapNode 包装在一个 ISiteMapNodeToParentRelation 实例中,该实例只是为了确保在将其添加到 SiteMap 对象之前可以跟踪其父节点键。
您的 SiteMapNodeProvider 应如下所示:
public class CustomSiteMapNodeProvider
: ISiteMapNodeProvider
{
private readonly string sourceName = "CustomSiteMapNodeProvider";
#region ISiteMapNodeProvider Members
public IEnumerable<ISiteMapNodeToParentRelation> GetSiteMapNodes(ISiteMapNodeHelper helper)
{
var result = new List<ISiteMapNodeToParentRelation>();
using (var db = new DatabaseContextClass())
{
foreach (var category in db.Categories.ToList())
{
var categoryRelation = this.GetCategoryRelation("Products", category, helper);
result.Add(categoryRelation);
}
foreach (var product in db.Products.Include("Category"))
{
var productRelation = this.GetProductRelation("Category_" + product.CategoryId, product, helper);
result.Add(productRelation);
}
}
return result;
}
#endregion
protected virtual ISiteMapNodeToParentRelation GetCategoryRelation(string parentKey, Category category, ISiteMapNodeHelper helper)
{
string key = "Category_" + category.Id;
var result = helper.CreateNode(key, parentKey, this.sourceName);
var node = result.Node;
node.Title = category.Name;
// Populate other node properties here
// Important - always set up your routes (including any custom params)
node.Area = "MyArea"; // Required - set to empty string if not using areas
node.Controller = "Category"; // Required
node.Action = "Index"; // Required
node.RouteValues.Add("id", category.Id.ToString());
return result;
}
protected virtual ISiteMapNodeToParentRelation GetProductRelation(string parentKey, Product product, ISiteMapNodeHelper helper)
{
string key = "Product_" + product.Id;
var result = helper.CreateNode(key, parentKey, this.sourceName);
var node = result.Node;
node.Title = product.Name;
// Populate other node properties here
// Important - always set up your routes (including any custom params)
node.Area = "MyArea"; // Required - set to empty string if not using areas
node.Controller = "Product"; // Required
node.Action = "Index"; // Required
node.RouteValues.Add("id", product.Id.ToString());
node.RouteValues.Add("categoryId", product.CategoryId.ToString()); // Optional - use if you have a many-to-many relationship.
return result;
}
}
上面的示例假设您已通过其他方式添加了一个节点,该节点的键设置为“Products”,所有类别都将是该节点的子级。您当然可以调整它以满足您的需求。
通常最好只实现此接口 1 次并使用单个数据库连接来加载整个 SiteMap。您始终可以将其重构为多个类,每个类处理您界面一侧的单个表以分离关注点。但通常最好将相关实体之间的所有键映射逻辑放在一起以使其更易于维护。
有关此接口实现的其他示例,请参阅XmlSiteMapNodeProvider和ReflectionSiteMapNodeProvider。