Как можно добиться динамических панировок с помощью ASP.net MVC?
Как можно сделать динамические панировки с помощью ASP.net MVC?
Если вам интересно, какие сухари:
Что такое панировочные сухари? Хорошо, если вы когда-либо просматривали интернет-магазин или читали сообщения в форуме, вы, скорее всего, столкнулись с сухарями. Они обеспечивают простой способ увидеть, где вы находитесь на сайте. Сайты, такие как Craigslist, используют панировочные сухари для описания местоположения пользователя. Над списками на каждой странице есть что-то похожее на это:
s.f. bayarea craigslist > город Сан-Франциско > велосипеды
ИЗМЕНИТЬ
Я понимаю, что возможно с SiteMapProvider. Я также знаю о провайдерах в сети, которые позволят вам сопоставлять sitenodes с контроллерами и действиями.
Но как насчет того, когда вы хотите, чтобы текст breadcrumb соответствовал некоторому динамическому значению, например:
Главная > Продукты > Автомобили > Toyotap >
Главная > Продукты > Автомобили > Chevy
Главная > Продукты > Оборудование для проведения работ > Электрический стул
Главная > Продукты > Оборудование для выполнения работ > Галлоны
... где категории продуктов и продукты являются записями из базы данных. Некоторые ссылки должны быть определены статически (Домой наверняка).
Я пытаюсь понять, как это сделать, но я уверен, что кто-то уже сделал это с ASP.net MVC.
Ответы
Ответ 1
Есть инструмент для этого на codeplex: http://mvcsitemap.codeplex.com/ [project переместился в github]
Edit:
Существует способ управлять SiteMapProvider из базы данных: http://www.asp.net/Learn/data-access/tutorial-62-cs.aspx
Возможно, вы сможете изменить инструмент mvcsitemap, чтобы использовать его для получения того, что вы хотите.
Ответ 2
Sitemap - это, безусловно, один из способов... альтернативно, вы можете написать его сами! (конечно, если соблюдаются стандартные правила MVC)... Я только что написал один, я подумал, что поделюсь здесь.
@Html.ActionLink("Home", "Index", "Home")
@if(ViewContext.RouteData.Values["controller"].ToString() != "Home") {
@:> @Html.ActionLink(ViewContext.RouteData.Values["controller"].ToString(), "Index", ViewContext.RouteData.Values["controller"].ToString())
}
@if(ViewContext.RouteData.Values["action"].ToString() != "Index"){
@:> @Html.ActionLink(ViewContext.RouteData.Values["action"].ToString(), ViewContext.RouteData.Values["action"].ToString(), ViewContext.RouteData.Values["controller"].ToString())
}
Надеюсь, кто-то найдет это полезным, это именно то, что я искал, когда искал SO для панировочных сухарей MVC.
Ответ 3
ASP.NET 5 (аналогично ASP.NET Core), MVC Core Solution
В ASP.NET Core все еще оптимизировано, так как нам не нужно подкреплять разметку в методе расширения.
В ~/Extesions/HtmlExtensions.cs
:
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace YourProjectNamespace.Extensions
{
public static class HtmlExtensions
{
private static readonly HtmlContentBuilder _emptyBuilder = new HtmlContentBuilder();
public static IHtmlContent BuildBreadcrumbNavigation(this IHtmlHelper helper)
{
if (helper.ViewContext.RouteData.Values["controller"].ToString() == "Home" ||
helper.ViewContext.RouteData.Values["controller"].ToString() == "Account")
{
return _emptyBuilder;
}
string controllerName = helper.ViewContext.RouteData.Values["controller"].ToString();
string actionName = helper.ViewContext.RouteData.Values["action"].ToString();
var breadcrumb = new HtmlContentBuilder()
.AppendHtml("<ol class='breadcrumb'><li>")
.AppendHtml(helper.ActionLink("Home", "Index", "Home"))
.AppendHtml("</li><li>")
.AppendHtml(helper.ActionLink(controllerName.Titleize(),
"Index", controllerName))
.AppendHtml("</li>");
if (helper.ViewContext.RouteData.Values["action"].ToString() != "Index")
{
breadcrumb.AppendHtml("<li>")
.AppendHtml(helper.ActionLink(actionName.Titleize(), actionName, controllerName))
.AppendHtml("</li>");
}
return breadcrumb.AppendHtml("</ol>");
}
}
}
~/Extensions/StringExtensions.cs
остается таким же, как показано ниже (прокрутите вниз до версии MVC5).
В режиме бритвы нам не нужно Html.Raw
, так как Razor позаботится об экранировании при работе с IHtmlContent
:
....
....
<div class="container body-content">
<!-- #region Breadcrumb -->
@Html.BuildBreadcrumbNavigation()
<!-- #endregion -->
@RenderBody()
<hr />
...
...
ASP.NET 4, решение MVC 5
=== ОРИГИНАЛЬНЫЙ/СТАРЫЙ ОТВЕТ НИЖЕ ===
(Расширение на ответе Шона Хэдди выше)
Если вы хотите сделать его принудительным (сохраняя Views clean), вы можете сделать что-то вроде:
В ~/Extesions/HtmlExtensions.cs
:
(совместим с MVC5/bootstrap)
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace YourProjectNamespace.Extensions
{
public static class HtmlExtensions
{
public static string BuildBreadcrumbNavigation(this HtmlHelper helper)
{
// optional condition: I didn't wanted it to show on home and account controller
if (helper.ViewContext.RouteData.Values["controller"].ToString() == "Home" ||
helper.ViewContext.RouteData.Values["controller"].ToString() == "Account")
{
return string.Empty;
}
StringBuilder breadcrumb = new StringBuilder("<ol class='breadcrumb'><li>").Append(helper.ActionLink("Home", "Index", "Home").ToHtmlString()).Append("</li>");
breadcrumb.Append("<li>");
breadcrumb.Append(helper.ActionLink(helper.ViewContext.RouteData.Values["controller"].ToString().Titleize(),
"Index",
helper.ViewContext.RouteData.Values["controller"].ToString()));
breadcrumb.Append("</li>");
if (helper.ViewContext.RouteData.Values["action"].ToString() != "Index")
{
breadcrumb.Append("<li>");
breadcrumb.Append(helper.ActionLink(helper.ViewContext.RouteData.Values["action"].ToString().Titleize(),
helper.ViewContext.RouteData.Values["action"].ToString(),
helper.ViewContext.RouteData.Values["controller"].ToString()));
breadcrumb.Append("</li>");
}
return breadcrumb.Append("</ol>").ToString();
}
}
}
В ~/Extensions/StringExtensions.cs
:
using System.Globalization;
using System.Text.RegularExpressions;
namespace YourProjectNamespace.Extensions
{
public static class StringExtensions
{
public static string Titleize(this string text)
{
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text).ToSentenceCase();
}
public static string ToSentenceCase(this string str)
{
return Regex.Replace(str, "[a-z][A-Z]", m => m.Value[0] + " " + char.ToLower(m.Value[1]));
}
}
}
Затем используйте его как (например, в _Layout.cshtml):
....
....
<div class="container body-content">
<!-- #region Breadcrumb -->
@Html.Raw(Html.BuildBreadcrumbNavigation())
<!-- #endregion -->
@RenderBody()
<hr />
...
...
Ответ 4
Maarten Balliauw MvcSiteMapProvider работал очень хорошо для меня.
Я создал небольшое приложение mvc для тестирования своего провайдера: Тест MvcSiteMapProvider (404)
Ответ 5
Я построил этот пакет nuget для решения этой проблемы для себя:
https://www.nuget.org/packages/MvcBreadCrumbs/
Вы можете внести свой вклад, если у вас есть идеи для этого:
https://github.com/thelarz/MvcBreadCrumbs
Ответ 6
Для тех, кого это интересует, я сделал улучшенную версию HtmlExtension
, которая также рассматривает области, и дополнительно использует Reflection для проверки наличия контроллера по умолчанию внутри области или действия индекса внутри контроллера:
public static class HtmlExtensions
{
public static MvcHtmlString BuildBreadcrumbNavigation(this HtmlHelper helper)
{
string area = (helper.ViewContext.RouteData.DataTokens["area"] ?? "").ToString();
string controller = helper.ViewContext.RouteData.Values["controller"].ToString();
string action = helper.ViewContext.RouteData.Values["action"].ToString();
// add link to homepage by default
StringBuilder breadcrumb = new StringBuilder(@"
<ol class='breadcrumb'>
<li>" + helper.ActionLink("Homepage", "Index", "Home", new { Area = "" }, new { @class="first" }) + @"</li>");
// add link to area if existing
if (area != "")
{
breadcrumb.Append("<li>");
if (ControllerExistsInArea("Default", area)) // by convention, default Area controller should be named Default
{
breadcrumb.Append(helper.ActionLink(area.AddSpaceOnCaseChange(), "Index", "Default", new { Area = area }, new { @class = "" }));
}
else
{
breadcrumb.Append(area.AddSpaceOnCaseChange());
}
breadcrumb.Append("</li>");
}
// add link to controller Index if different action
if ((controller != "Home" && controller != "Default") && action != "Index")
{
if (ActionExistsInController("Index", controller, area))
{
breadcrumb.Append("<li>");
breadcrumb.Append(helper.ActionLink(controller.AddSpaceOnCaseChange(), "Index", controller, new { Area = area }, new { @class = "" }));
breadcrumb.Append("</li>");
}
}
// add link to action
if ((controller != "Home" && controller != "Default") || action != "Index")
{
breadcrumb.Append("<li>");
//breadcrumb.Append(helper.ActionLink((action.ToLower() == "index") ? controller.AddSpaceOnCaseChange() : action.AddSpaceOnCaseChange(), action, controller, new { Area = area }, new { @class = "" }));
breadcrumb.Append((action.ToLower() == "index") ? controller.AddSpaceOnCaseChange() : action.AddSpaceOnCaseChange());
breadcrumb.Append("</li>");
}
return MvcHtmlString.Create(breadcrumb.Append("</ol>").ToString());
}
public static Type GetControllerType(string controller, string area)
{
string currentAssembly = Assembly.GetExecutingAssembly().GetName().Name;
IEnumerable<Type> controllerTypes = Assembly.GetExecutingAssembly().GetTypes().Where(o => typeof(IController).IsAssignableFrom(o));
string typeFullName = String.Format("{0}.Controllers.{1}Controller", currentAssembly, controller);
if (area != "")
{
typeFullName = String.Format("{0}.Areas.{1}.Controllers.{2}Controller", currentAssembly, area, controller);
}
return controllerTypes.Where(o => o.FullName == typeFullName).FirstOrDefault();
}
public static bool ActionExistsInController(string action, string controller, string area)
{
Type controllerType = GetControllerType(controller, area);
return (controllerType != null && new ReflectedControllerDescriptor(controllerType).GetCanonicalActions().Any(x => x.ActionName == action));
}
public static bool ControllerExistsInArea(string controller, string area)
{
Type controllerType = GetControllerType(controller, area);
return (controllerType != null);
}
public static string AddSpaceOnCaseChange(this string text)
{
if (string.IsNullOrWhiteSpace(text))
return "";
StringBuilder newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (int i = 1; i < text.Length; i++)
{
if (char.IsUpper(text[i]) && text[i - 1] != ' ')
newText.Append(' ');
newText.Append(text[i]);
}
return newText.ToString();
}
}
Если может быть определенно может быть улучшено (вероятно, не распространяется на все возможные случаи), но до сих пор это не подводило меня.