我有一个ASP.NET MVC站点地图提供程序的问题,它给我带来了很多麻烦。当服务器负载很重时,URL被错误地解决了。我刚刚升级到最新版本(3.1.0 RC),我希望这可以修复,但不幸的是它没有。
我尝试生成本地测试来验证这一点,但我无法复制服务器上的负载。因此,我将向您展示我对实时服务器进行的单元测试:
[TestMethod]
public void ForumTopic_Breadcrumb() {
// Arrange
var isValid = true;
for (var i = 0; i < 100; i++) {
try {
// Send the request
var request = Http.WebRequest("http://www.mysite.com/Forum/ViewTopic/38044"); // Http.WebRequest is a utility method to send a request to the server and retrieve the content (Note: now the question has been answered i have remove the reference to the actual site)
// Parse the html document
var document = new HtmlDocument(); // HTML Agility Pack
document.LoadHtml(request.Data);
// Get the required info
var forumNode = document.DocumentNode.SelectSingleNode("//div[@id='breadcrumb']/a[@href='/Forum/ViewForum/1']");
var topicNode = document.DocumentNode.SelectSingleNode("//div[@id='breadcrumb']/span");
// Test if the info is valid
if (forumNode == null || topicNode.InnerText != "Test Topic")
throw new Exception();
} catch {
isValid = false;
break;
}
}
// Asset
Assert.IsTrue(isValid);
}
此测试失败,因为显示错误的痕迹和/或标题。
我的ViewTopic操作方法中包含以下代码:
// Override the parent node title and url
SiteMap.CurrentNode.ParentNode.Title = topic.Forum.ForumName;
SiteMap.CurrentNode.ParentNode.Url = Url.GenerateUrl("ViewForum", new { id = topic.Forum.ForumID });
// Set the meta description
SiteMap.CurrentNode["MetaDescription"] = topic.Subject;
以及应用SiteMapTitle属性以将当前节点标题更改为主题的主题。
如果你能提供帮助,我真的很感激。感谢
答案 0 :(得分:1)
我收到开发人员的回复说这是由于ASP.NET的限制。他希望通过完全隔离这个功能在版本4中删除它。在此之前,最好的解决方案是通过将其作为ViewData传递来覆盖标题。
编辑(以下是我提出的临时解决方案):
创建以下SiteMapAttribute.cs文件:
public class SiteMapAttribute : ActionFilterAttribute {
protected string TitlePropertyName { get; set; }
protected string PageTitlePropertyName { get; set; }
protected string MetaDescriptionPropertyName { get; set; }
protected string MetaKeywordsPropertyName { get; set; }
protected string ParentTitlePropertyName { get; set; }
public SiteMapAttribute() {
}
public SiteMapAttribute(string titlePropertyName) {
TitlePropertyName = titlePropertyName;
}
public SiteMapAttribute(string titlePropertyName, string pageTitlePropertyName, string metaDescriptionPropertyName, string metaKeywordsPropertyName)
: this(titlePropertyName) {
PageTitlePropertyName = pageTitlePropertyName;
MetaDescriptionPropertyName = metaDescriptionPropertyName;
MetaKeywordsPropertyName = metaKeywordsPropertyName;
}
public SiteMapAttribute(string titlePropertyName, string pageTitlePropertyName, string metaDescriptionPropertyName, string metaKeywordsPropertyName, string parentTitlePropertyName)
: this(titlePropertyName, pageTitlePropertyName, metaDescriptionPropertyName, metaKeywordsPropertyName) {
ParentTitlePropertyName = parentTitlePropertyName;
}
public override void OnActionExecuted(ActionExecutedContext filterContext) {
if (filterContext.Result is ViewResult) {
var result = (ViewResult)filterContext.Result;
// Get the current node
var currentNode = filterContext.Controller.GetCurrentSiteMapNode();
// Make sure the node is found
if (currentNode != null) {
// Set the title and meta information (if applicable)
if (!result.ViewData.ContainsKey("Title"))
result.ViewData["Title"] = Resolve(result, TitlePropertyName) ?? currentNode.Title;
if (!result.ViewData.ContainsKey("PageTitle"))
result.ViewData["PageTitle"] = Resolve(result, PageTitlePropertyName) ?? (currentNode["PageTitle"] ?? currentNode.Title);
if (!result.ViewData.ContainsKey("MetaDescription"))
result.ViewData["MetaDescription"] = Resolve(result, MetaDescriptionPropertyName) ?? currentNode["MetaDescription"];
if (!result.ViewData.ContainsKey("MetaKeywords"))
result.ViewData["MetaKeywords"] = Resolve(result, MetaKeywordsPropertyName) ?? currentNode["MetaKeywords"];
if (!result.ViewData.ContainsKey("ParentTitle"))
result.ViewData["ParentTitle"] = Resolve(result, ParentTitlePropertyName);
}
}
}
private string Resolve(ViewResult result, string propertyName) {
if (string.IsNullOrEmpty(propertyName))
return null;
var target = ResolveTarget(result.ViewData.Model, propertyName);
if (target == null)
target = ResolveTarget(result.ViewData, propertyName);
return target != null ? target.ToString() : null;
}
private object ResolveTarget(object target, string expression) {
try {
var parameter = Expression.Parameter(target.GetType(), "target");
var lambdaExpression = DynamicExpression.ParseLambda(new[] { parameter }, null, "target." + expression);
return lambdaExpression.Compile().DynamicInvoke(target);
} catch {
return null;
}
}
}
然后,您需要确保所有控制器都应用了此属性。在ASP.NET MVC 3中,这可以更简单,因为您可以将其注册为全局过滤器。
现在您需要修改您的母版页并说出如下内容:
<head>
<title><%= ViewData["PageTitle"] %></title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<% if (ViewData["MetaDescription"] != null && !string.IsNullOrEmpty(ViewData["MetaDescription"].ToString())) { %>
<meta name="Description" content="<%= ViewData["MetaDescription"] %>" />
<% } %>
<% if (ViewData["MetaKeywords"] != null && !string.IsNullOrEmpty(ViewData["MetaKeywords"].ToString())) { %>
<meta name="Keywords" content="<%= ViewData["MetaKeywords"] %>" />
<% } %>
</head>
现在要修补面包屑(SiteMapPath),我必须得到一点点hacky。首先,我创建了自己的帮手:
/// <summary>
/// MvcSiteMapHtmlHelper extension methods
/// </summary>
public static class SiteMapBreadcrumbExtensions {
/// <summary>
/// Source metadata
/// </summary>
private static Dictionary<string, object> SourceMetadata = new Dictionary<string, object> { { "HtmlHelper", typeof(SiteMapBreadcrumbExtensions).FullName } };
/// <summary>
/// Gets SiteMap path for the current request
/// </summary>
/// <param name="helper">MvcSiteMapHtmlHelper instance</param>
/// <returns>SiteMap path for the current request</returns>
public static MvcHtmlString SiteMapBreadcrumb(this MvcSiteMapHtmlHelper helper) {
return SiteMapBreadcrumb(helper, null);
}
/// <summary>
/// Gets SiteMap path for the current request
/// </summary>
/// <param name="helper">MvcSiteMapHtmlHelper instance</param>
/// <param name="templateName">Name of the template.</param>
/// <returns>SiteMap path for the current request</returns>
public static MvcHtmlString SiteMapBreadcrumb(this MvcSiteMapHtmlHelper helper, string templateName) {
var model = BuildModel(helper, helper.Provider.CurrentNode);
return helper
.CreateHtmlHelperForModel(model)
.DisplayFor(m => model, templateName, new { Title = helper.HtmlHelper.ViewData["Title"], ParentTitle = helper.HtmlHelper.ViewData["ParentTitle"], ParentUrl = helper.HtmlHelper.ViewData["ParentUrl"] });
}
/// <summary>
/// Builds the model.
/// </summary>
/// <param name="helper">The helper.</param>
/// <param name="startingNode">The starting node.</param>
/// <returns>The model.</returns>
private static SiteMapPathHelperModel BuildModel(MvcSiteMapHtmlHelper helper, SiteMapNode startingNode) {
// Build model
var model = new SiteMapPathHelperModel();
var node = startingNode;
while (node != null) {
var mvcNode = node as MvcSiteMapNode;
// Check visibility
var nodeVisible = true;
if (mvcNode != null)
nodeVisible = mvcNode.VisibilityProvider.IsVisible(node, HttpContext.Current, SourceMetadata);
// Check ACL
if (nodeVisible && node.IsAccessibleToUser(HttpContext.Current))
model.Nodes.Add(SiteMapNodeModelMapper.MapToSiteMapNodeModel(node, mvcNode, SourceMetadata));
node = node.ParentNode;
}
model.Nodes.Reverse();
return model;
}
}
这与内置的唯一区别在于它将ViewData传递给模板。最后我创建了2个DisplayTemplates:
SiteMapNodeModel.axcx:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MvcSiteMapProvider.Web.Html.Models.SiteMapNodeModel>" %>
<% if (Model.IsCurrentNode) { %>
<span class="orange-text"><%= (ViewData["Title"] ?? Model.Title).ToString() %></span>
<% } else if ((bool)ViewData["IsParent"]) { %>
<a href="<%= (ViewData["ParentUrl"] ?? Model.Url).ToString() %>"><%= (ViewData["ParentTitle"] ?? Model.Title).ToString() %></a>
<% } else { %>
<a href="<%= Model.Url %>"><%= Model.Title %></a>
<% } %>
和SiteMapPathHelperModel.ascx:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MvcSiteMapProvider.Web.Html.Models.SiteMapPathHelperModel>" %>
<% for (var i = 0; i < Model.Nodes.Count(); i++) { %>
<%= Html.DisplayFor(m => Model.Nodes[i], new { isParent = Model.Count() - 2 == i }) %>
<% if (Model.Nodes[i] != Model.Last()) { %>
>
<% } %>
<% } %>
您现在可以在视图中说出以下内容以显示被覆盖的痕迹:
<%= Html.MvcSiteMap().SiteMapBreadcrumb() %>
现在完成了这项工作,您只需要知道如何覆盖特定操作的元/面包屑信息。最简单的方法是覆盖特定操作的SiteMapAttribute,例如
[SiteMap("Subject", "Subject", "Subject", "", "Forum.ForumName")]
public ActionResult ViewTopic(int id, [DefaultValue(1)] int page) {
}
这将相应地设置标题,页面标题,元信息和父标题。如果你希望标题绑定到比单个属性更复杂的东西,你可以在action方法中设置这个,如下所示:
ViewData["Title"] = "My Title - " + DateTime.UtcNow.ToShortDateString();
希望这有帮助。