Динамическая карта сайта в ASP.NET MVC
Я пытаюсь создать автоматическую карту сайта ActionResult, которая выводит действительный файл sitemap.xml. Фактическое создание файла не является проблемой, но я не могу понять, как заполнить список URL-адресов в системе. Вот код, который у меня есть до сих пор:
public ContentResult Sitemap()
{
XNamespace xmlns = "http://www.sitemaps.org/schemas/sitemap/0.9";
XElement root = new XElement(xmlns + "urlset");
//some kind of foreach here to get the loc variable for all URLs in the site
//for each URL in the collection, add it to the root element as here
//root.Add(
// new XElement("url",
// new XElement("loc", "http://google.com"),
// new XElement("changefreq", "daily")));
using (MemoryStream ms = new MemoryStream())
{
using (StreamWriter writer = new StreamWriter(ms, Encoding.UTF8))
{
root.Save(writer);
}
return Content(Encoding.UTF8.GetString(ms.ToArray()), "text/xml", Encoding.UTF8);
}
}
Например, предположим, что у меня есть два контроллера, и каждый контроллер имеет два действия, связанные с ними:
HelpController
AboutController
Я не могу понять, как получить список URL, например:
Ответы
Ответ 1
Я взглянул на подход Maarten Balliauw на один комментарий, но, похоже, это слишком сложно для того, что я пытаюсь сделать.
Я взломал временное решение. Я просто передаю имя контроллера и действия для создания URL-адреса. Чтобы создать URL-адрес, я использую следующий код:
List<string> urlList = new List<string>();
urlList.Add(GetUrl(new { controller = "Help", action = "Edit" }));
urlList.Add(GetUrl(new { controller = "Help", action = "Create" }));
urlList.Add(GetUrl(new { controller = "About", action = "Company" }));
urlList.Add(GetUrl(new { controller = "About", action = "Management" }));
где GetUrl выглядит следующим образом:
protected string GetUrl(object routeValues)
{
RouteValueDictionary values = new RouteValueDictionary(routeValues);
RequestContext context = new RequestContext(HttpContext, RouteData);
string url = RouteTable.Routes.GetVirtualPath(context, values).VirtualPath;
return new Uri(Request.Url, url).AbsoluteUri;
}
Кажется, теперь это трюк, хотя мне нравится идея применения actionfilter к определенным действиям, которые автоматически сжимаются.
Ответ 2
Я отправил ответ do-it-yourself
ниже. Но вот пакет, который делает это из коробки для сайтов MVC:
http://mvcsitemap.codeplex.com/ (< - старый сайт, но с обширной документацией!)
https://github.com/maartenba/MvcSiteMapProvider/wiki (< - перемещен на новый сайт, не хватает какой-либо документации, а не как активный)
Обратите внимание, что он выполняет множество вещей:
- Automagically регистрируется в маршрутах Mvc для ответа на запросы SEO/sitemap.xml (даже при отсутствии физического файла для /sitemap.xml). Это полностью совместимо со всеми поисковыми роботами, которые я нашел, а также перекатывается, когда он доходит до 10 000 и т.д.
- Поставляется с набором частичных представлений для использования встроенной навигации BreadCrumb! Мы используем это довольно широко, хотя динамическая часть данных немного громоздка, она работает.
- Поставляется с набором частичных представлений для управления Menu также.
- Почитает биты безопасности [Authorize] ваших контроллеров и действия.
Все перечисленные выше пункты управляются из одного файла XML mvc.sitemap, который вы редактируете и настраиваете. Я использовал это в целом ряде проектов, чтобы сделать 2 или 3 из вышеперечисленных пунктов. Все это настраивается в 1 месте и динамически генерируется, действительно приятно.
Хотя я нахожу способность создавать динамические поставщики данных немного громоздкими (и грубо нарушает любой тип IoC, который вы хотите сделать), он действительно выполняет свою работу и хорошо масштабируется, как только вы обходите их кеширование и используете свои собственные.
Ответ 3
Как следует из упоминания, вы хотите задуматься о пространстве имен вашей модели (ов) и получить все классы, которые реализуют IController. После того, как у вас есть коллекция, вы хотите задуматься о том, какие члены (методы) возвращают тип ActionResult.
Возможно, вы можете создать свой собственный атрибут [SitemapAttribute], который позволяет выборочно определять, какие методы индексировать в файле Sitemap (т.е. Index(), но не Edit()). Да, мне нравится эта идея контроля над тем, какие методы (URL) записываются.
Это отличный вопрос, потому что я просто думал об этом. +1!
// Controller abstract implements IController
public class HelpController : Controller
{
public HelpController()
{
}
[Sitemap]
public ActionResult Index()
{
// does get written to the file, cause of [Sitemap]
}
public ActionResult Create()
{
// does not get mapped to the file
}
public ActionResult Edit()
{
// does not get mapped to the file
}
[Sitemap]
public ActionResult ViewArticle()
{
// would get indexed.
}
}
Для того, чтобы сделать отражение, вот хорошая статья MSDN, чтобы вы познакомились с размышлением:
http://msdn.microsoft.com/en-us/library/ms172331.aspx
Хороший вопрос!
Ответ 4
Определите ActionFilterAttribute
как это, чтобы применить любой метод Action, который является фактической страницей, которую вы хотите перечислить в вашей карте сайта: -
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class MVCUrlAttribute : ActionFilterAttribute
{
public string Url { get; private set; }
public MVCUrlAttribute(string url)
{
this.Url = url;
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
// Put this 'canonical url' into the model (which feeds the view)
// to help search engines with issues of duplicate content
filterContext.Controller.ViewData["CanonicalUrl"] = url;
base.OnResultExecuting(filterContext);
}
}
Теперь добавьте что-то подобное в свой код запуска глобального приложения или используйте его в коде генерации sitemap.xml: -
// Find all the MVC Routes
Log.Debug("*** FINDING ALL MVC ROUTES MARKED FOR INCLUSION IN SITEMAP");
var allControllers = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsSubclassOf(typeof(Controller)));
Log.DebugFormat("Found {0} controllers", allControllers.Count());
foreach (var controllerType in allControllers)
{
var allPublicMethodsOnController = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
Log.DebugFormat("Found {0} public methods on {1}", allPublicMethodsOnController.Count(), controllerType.Name);
foreach (var publicMethod in allPublicMethodsOnController)
{
var mvcurlattr = publicMethod.GetCustomAttributes(true).OfType<MVCUrlAttribute>().FirstOrDefault();
if (mvcurlattr != null)
{
string url = mvcurlattr.Url;
Log.Debug("Found " + controllerType.Name + "." + publicMethod.Name + " <-- " + url);
Global.SiteMapUrls.Add(url); //<-- your code here using url
}
}
}
Вы можете расширить класс атрибутов, возможно, также включить частоту подсказки обновления.
Ответ 5
Итак, получение контроллеров и действий кажется мне относительно тривиальной частью. Жесткая часть может получить все возможные значения параметров, которые вы можете показать в URL-адресах вашей карты сайта. Если у вас есть шаблон URL, например {controller}/{action}/{id}
, то вы не сможете определить через отражение то, что означает значение id
, или возможные значения. Лучшее, что вы можете сделать, это определить тип системы.
Что пришло мне в голову, так как я смотрел на это, так это то, что карта сайта - это просто другое представление данных вашего сайта. Поэтому одна случайная мысль, которую я имел, заключалась в том, что если вы наследуете базовый контроллер в своем приложении, и у вас есть метод на этом базовом контроллере, который должен быть реализован, например:
abstract ActionResult SiteMapSnippet();
Затем вы можете создать SiteMapController
, который вызывает каждый из других контроллеров в решении и запрашивает их для своего фрагмента, а затем отображает их все вместе в одном конечном представлении. Сорт составного контроллера, хотя это еще не концепция, которая была добавлена в эту структуру еще.
Ответ 6
Вы пробовали что-то вроде этого:
http://blog.maartenballiauw.be/post/2008/08/29/Building-an-ASPNET-MVC-sitemap-provider-with-security-trimming.aspx
После повторного чтения вашего вопроса, я вижу, что вы хотите что-то немного отличающееся от того, что я представил. Я думаю, вам придется отражать все известные контроллеры и их действия для динамического создания файла Sitemap.
Было бы гораздо проще использовать базу данных или файл Sitemap в качестве источника, который я думаю.
Ответ 7
Взгляните на код, стоящий за Филом Хааком "Отладчик маршрута":
http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx