2013-12-18 3 views
2

Я создаю корзину ASP.NET MVC/Entity Framework, чтобы лучше познакомиться с технологией. Одна из функций, которые я хотел иметь, - это URL-адреса, основанные на уникальных пули вместо того, чтобы вставлять идентификаторы сущностей в URL-адрес. Некоторые примеры:Mvc Custom Route and Breadcrumb Стратегии?

  • /
  • /информация
  • /информация/о-нас
  • /информация/контакт-нас
  • /Мужская одежда-
  • /мужской, одежда/mens- рубашки
  • /мужской, одежда/мужские футболки/тест-тенниска

Пуль являются уникальными для всех типов контента, но, например, тест-тенниска может появиться в нескольких категориях:

  • /мужской, одежда/мужские футболки/тест-Tshirt
  • /мужские, одежды/оформлении/test-tshirt

Я создал собственный маршрут, который принимает последний пучок в пути и использует его для поиска текущей страницы.

Это хорошо работает, и у меня есть хорошая гибкость при реализации пользовательской логики и шаблонов отображения (типы содержимого имеет свойство «View», а так я могу динамически установить вид в контроллере).

Однако, я немного спотыкаюсь о внедрении панировочных сухарей. Быстрым и грязным способом было бы использовать путь от URL-адреса, делать запрос для каждого пула в пути и игнорировать, действительно ли страница является дочерним элементом категории. Другим решением было бы использовать что-то вроде MvcSiteMapProvider и создать дерево XML, поскольку содержимое будет добавлено на бэкэнд ... Я не уверен, насколько хорошо эта конкретная реализация будет работать, потому что она, похоже, довольно сосредоточена на стандарте {controller}/{action}/{id}.

Какие другие типы реализаций вы использовали или видели?

ответ

1

MvcSiteMapProvider v4 также работает с URL-адресами, устанавливая свойство Url, а не используя {controller}/{action}/{id}. Это именно тот сценарий, который я использую для (управляемых базой данных URL/пользовательских маршрутов RoutBase), и он отлично работает. Тем не менее, вы должны реализовать обратный URL-поиск в своем маршруте, иначе ваше разрешение URL-адреса не будет работать.

public class ProductRoute 
    : RouteBase, IRouteWithArea 
{ 
    private readonly string area; 
    private readonly IApplicationContext appContext; 
    private readonly IRouteUrlProductListFactory routeUrlProductListFactory; 
    private readonly IRouteUtilities routeUtilities; 

    public ProductRoute(
     string area, 
     IApplicationContext appContext, 
     IRouteUrlProductListFactory routeUrlProductListFactory, 
     IRouteUtilities routeUtilities 
     ) 
    { 
     if (appContext == null) { throw new ArgumentNullException("appContext"); } 
     if (routeUrlProductListFactory == null) { throw new ArgumentNullException("routeUrlProductListFactory"); } 
     if (routeUtilities == null) { throw new ArgumentNullException("routeUtilities"); } 

     this.area = area; 
     this.appContext = appContext; 
     this.routeUrlProductListFactory = routeUrlProductListFactory; 
     this.routeUtilities = routeUtilities; 
    } 

    public override RouteData GetRouteData(HttpContextBase httpContext) 
    { 
     RouteData result = null; 
     var tenant = this.appContext.CurrentTenant; 

     if (tenant.TenantType.ToString().Equals(this.area, StringComparison.OrdinalIgnoreCase)) 
     { 
      var localeId = this.appContext.CurrentLocaleId; 

      // Get all of the pages 
      var path = httpContext.Request.Path; 
      var pathLength = path.Length; 

      var page = this.routeUrlProductListFactory 
       .GetRouteUrlProductList(tenant.Id) 
       .Where(x => x.UrlPath.Length.Equals(pathLength)) 
       .Where(x => x.UrlPath.Equals(path)) 
       .FirstOrDefault(); 

      if (page != null) 
      { 
       result = this.routeUtilities.CreateRouteData(this); 

       this.routeUtilities.AddQueryStringParametersToRouteData(result, httpContext); 

       result.Values["controller"] = "Product"; 
       result.Values["action"] = "Details"; 
       result.Values["localeId"] = localeId; 
       result.DataTokens["area"] = this.area; 

       // TODO: May need a compound key here (ProductXTenantLocaleID and 
       // CategoryId) to allow product to be hosted on pages that are not 
       // below categories. 
       result.Values["id"] = page.CategoryXProductId; 
      } 
     } 

     return result; 
    } 

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) 
    { 
     VirtualPathData result = null; 

     if (requestContext.RouteData.IsAreaMatch(this.area)) 
     { 
      var tenant = this.appContext.CurrentTenant; 

      // Get all of the pages 
      var pages = this.routeUrlProductListFactory.GetRouteUrlProductList(tenant.Id); 
      IRouteUrlProductInfo page = null; 

      if (this.TryFindMatch(pages, values, out page)) 
      { 
       if (!string.IsNullOrEmpty(page.VirtualPath)) 
       { 
        result = this.routeUtilities.CreateVirtualPathData(this, page.VirtualPath); 
        result.DataTokens["area"] = tenant.TenantType.ToString(); 
       } 
      } 
     } 

     return result; 
    } 

    private bool TryFindMatch(IEnumerable<IRouteUrlProductInfo> pages, RouteValueDictionary values, out IRouteUrlProductInfo page) 
    { 
     page = null; 
     Guid categoryXProductId = Guid.Empty; 
     var localeId = (int?)values["localeId"]; 

     if (localeId == null) 
     { 
      return false; 
     } 

     if (!Guid.TryParse(Convert.ToString(values["id"]), out categoryXProductId)) 
     { 
      return false; 
     } 

     var controller = Convert.ToString(values["controller"]); 
     var action = Convert.ToString(values["action"]); 

     if (action == "Details" && controller == "Product") 
     { 
      page = pages 
       .Where(x => x.CategoryXProductId.Equals(categoryXProductId)) 
       .Where(x => x.LocaleId.Equals(localeId)) 
       .FirstOrDefault(); 
      if (page != null) 
      { 
       return true; 
      } 
     } 

     return false; 
    } 

    #region IRouteWithArea Members 

    public string Area 
    { 
     get { return this.area; } 
    } 

    #endregion 
} 

public class RouteUtilities 
    : IRouteUtilities 
{ 
    #region IRouteUtilities Members 

    public void AddQueryStringParametersToRouteData(RouteData routeData, HttpContextBase httpContext) 
    { 
     var queryString = httpContext.Request.QueryString; 
     if (queryString.Keys.Count > 0) 
     { 
      foreach (var key in queryString.AllKeys) 
      { 
       routeData.Values[key] = queryString[key]; 
      } 
     } 
    } 

    public RouteData CreateRouteData(RouteBase route) 
    { 
     return new RouteData(route, new MvcRouteHandler()); 
    } 

    public VirtualPathData CreateVirtualPathData(RouteBase route, string virtualPath) 
    { 
     return new VirtualPathData(route, virtualPath); 
    } 

    #endregion 
} 

Я использую кэширование, чтобы загрузить все URL-адреса в структуру данных (мое окончательное приложение будет, вероятно, использовать кэширование файлов), поэтому база данных не ударил для каждого URL поиска.

MvcSiteMapProvider также настроен на использование multiple paths to a single page путем создания нескольких узлов на странице (по одному для каждого уникального URL). Вы можете исправить аспект SEO использования нескольких URL-адресов для одного и того же контента, реализовав канонический тег, используя свойства CanonicalUrl или CanonicalKey. См. this article для полного примера.

Вы также можете управлять узлами MvcSiteMapProvider из базы данных, внедряя IDynamicNodeProvider или ISiteMapNodeProvider.

Обратите внимание, что соответствие URL в MvcSiteMapProvider чувствительно к регистру. Было бы лучше, если бы вы гарантировали, что ваши входящие URL-адреса всегда имеют нижний регистр, выполнив 301 переадресацию.

+0

Информативный, спасибо! Необходимо изучить свойство URL, не видел его в примерах. Быстрый вопрос, не связанный с моим оригиналом ... в чем причина проверки длины пути перед проверкой равенства пути в вашей коллекции маршрутов? – Sam

+0

Спасибо, сделал быструю проверку, и параметр «url» работает отлично. Найден xsd для узлов карты сайта, и у него есть тонны дополнительной функциональности. Мне не нужно настраивать сборку для моего приложения (ролевой доступ, httpMethod, видимость, канонические, мета-роботы и т. Д.). Хорошая вещь. – Sam

+0

Я проверяю длину как повышение производительности. Целочисленное сравнение выполняется намного быстрее, чем сравнение двух массивов байтов (строк), поэтому при медленном сравнении строк нет смысла, если длина не равна. Имейте в виду, что это сравнение должно быть выполнено для каждого входящего запроса, поэтому оно должно быть как можно быстрее. ПРИМЕЧАНИЕ. Я просто проверил источник .NET через Reflector, и Microsoft уже позаботилась о сравнении длины строки сначала в методе Equals, поэтому, я думаю, я могу взять эту строку. – NightOwl888