Динамическая карта сайта в 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 в качестве источника, который я думаю.