2017-01-18 7 views
-1

Я ищу способ вернуть представление, основанное на Id, но добавляя дружественную часть url при возврате.ASP.NET Mvc 5 return View with seo friendly url

Я знаю, что могу передать идентификатор и имя, когда у меня есть эти данные, например. используя:

Url.Action("Index", "Cat", new { id = model.ID, seoname = model.SEO }); 


[AllowAnonymous] 
[Route("Cat/{id?}/{seoname?}")] 
public ActionResult Index(int? id = null, string seoname = null) { 
    // do something with id and create viewmodel 

    // in case I get redirect from the SelCat actionresult: 
    if (id.HasValue and string.IsNullOrEmpty(seoname)) { 
     // look in the database for title by the given id 
     string seofriendlyTitle = ...; 
     RouteData.Values.AddOrSet("seoname", seofriendlyTitle); 
    } 

    return View(viewmodel); 
} 

этот код не является проблемой. Проблема возникает, когда я отправляю форму (dropdownlist), где у меня есть только идентификатор.

[HttpPost] 
[AllowAnonymous] 
[Route("Cat/SelCat/{form?}")] 
public ActionResult SelCat(FormCollection form) 
{ 
    string selectedValues = form["SelectedCat"]; 
    // ... 
    int id = selectedCatID; 
    return RedirectToAction("Index", new { id = id }); 
} 

В случае, если я переориентировать от действия SelCat к действию Index только с идентификатором Я хочу, чтобы искать seofriendly имени и при возвращении зрения. Я надеялся, что URL-адрес имеет дружественный URL-адрес.

// in case I get redirect from the SelCat actionresult: 
    if (id.HasValue and string.IsNullOrEmpty(seoname)) { 
     // look in the database for title by the given id 
     string seofriendlyTitle = ...; 
     RouteData.Values.AddOrSet("seoname", seofriendlyTitle); // <-- does not alter url 
    } 

Как я могу сделать мой URL сео дружественный при возвращении представления в мой контроллер действий, когда только идентификатор дается? Настройка параметров RouteData.Values. похоже, не добавляет часть к URL.

ответ

1

Вы должны смотреть в базу данных для SEO дружественных пробкового перед тем переадресации и включить его в URL, иначе это слишком поздно:

[HttpPost] 
[AllowAnonymous] 
[Route("Cat/SelCat/{form?}")] 
public ActionResult SelCat(FormCollection form) 
{ 
    string selectedValues = form["SelectedCat"]; 
    // ... 
    int id = selectedCatID; 

    // look in the database for title by the given id 
    string seofriendlyTitle = ...; 
    return RedirectToAction("Index", new { id = id, seoname = seofriendlyTitle }); 
} 

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

+0

Из-за логики мне нужно сделать запрос базы данных в отношении действия индекса, но теперь мне нужен второй запрос базы данных в действии SelCat. Черт возьми! Я думал, что это можно сделать в одном действии. – juFo

+0

Учитывая, что вы запрашиваете идентификатор, любая достойная база данных должна делать все возможное, чтобы запрашивать первичный ключ. Кроме того, вы можете добавить некоторый уровень кеширования, если вас беспокоит производительность. –

1

Вы можете создать подклассу RouteBase и загрузить все свои URL-адреса в кеш. Тогда вам понадобится только id (предположительно на основе первичного ключа) для поиска URL-адреса. Обратите внимание, что вы можете сделать id либо Guid (как показано), либо int.

public class PageInfo 
{ 
    // VirtualPath should not have a leading slash 
    // example: events/conventions/mycon 
    public string VirtualPath { get; set; } 
    public Guid Id { get; set; } 
} 

public class CustomPageRoute 
    : RouteBase 
{ 
    private object synclock = new object(); 

    public override RouteData GetRouteData(HttpContextBase httpContext) 
    { 
     RouteData result = null; 

     // Trim the leading slash 
     var path = httpContext.Request.Path.Substring(1); 

     // Get the page that matches. 
     var page = GetPageList(httpContext) 
      .Where(x => x.VirtualPath.Equals(path)) 
      .FirstOrDefault(); 

     if (page != null) 
     { 
      result = new RouteData(this, new MvcRouteHandler()); 

      // Optional - make query string values into route values. 
      this.AddQueryStringParametersToRouteData(result, httpContext); 

      // TODO: You might want to use the page object (from the database) to 
      // get both the controller and action, and possibly even an area. 
      // Alternatively, you could create a route for each table and hard-code 
      // this information. 
      result.Values["controller"] = "CustomPage"; 
      result.Values["action"] = "Details"; 

      // This will be the primary key of the database row. 
      // It might be an integer or a GUID. 
      result.Values["id"] = page.Id; 
     } 

     // IMPORTANT: Always return null if there is no match. 
     // This tells .NET routing to check the next route that is registered. 
     return result; 
    } 

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

     PageInfo page = null; 

     // Get all of the pages from the cache. 
     var pages = GetPageList(requestContext.HttpContext); 

     if (TryFindMatch(pages, values, out page)) 
     { 
      if (!string.IsNullOrEmpty(page.VirtualPath)) 
      { 
       result = new VirtualPathData(this, page.VirtualPath); 
      } 
     } 

     // IMPORTANT: Always return null if there is no match. 
     // This tells .NET routing to check the next route that is registered. 
     return result; 
    } 

    private bool TryFindMatch(IEnumerable<PageInfo> pages, RouteValueDictionary values, out PageInfo page) 
    { 
     page = null; 
     Guid id = Guid.Empty; 

     // This example uses a GUID for an id. If it cannot be parsed, 
     // we just skip it. 
     if (!Guid.TryParse(Convert.ToString(values["id"]), out id)) 
     { 
      return false; 
     } 

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

     // The logic here should be the inverse of the logic in 
     // GetRouteData(). So, we match the same controller, action, and id. 
     // If we had additional route values there, we would take them all 
     // into consideration during this step. 
     if (action == "Details" && controller == "CustomPage") 
     { 
      page = pages 
       .Where(x => x.Id.Equals(id)) 
       .FirstOrDefault(); 
      if (page != null) 
      { 
       return true; 
      } 
     } 
     return false; 
    } 

    private 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]; 
      } 
     } 
    } 

    private IEnumerable<PageInfo> GetPageList(HttpContextBase httpContext) 
    { 
     string key = "__CustomPageList"; 
     var pages = httpContext.Cache[key]; 
     if (pages == null) 
     { 
      lock(synclock) 
      { 
       pages = httpContext.Cache[key]; 
       if (pages == null) 
       { 
        // TODO: Retrieve the list of PageInfo objects from the database here. 
        pages = new List<PageInfo>() 
        { 
         new PageInfo() 
         { 
          Id = new Guid("cfea37e8-657a-43ff-b73c-5df191bad7c9"), 
          VirtualPath = "somecategory/somesubcategory/content1" 
         }, 
         new PageInfo() 
         { 
          Id = new Guid("9a19078b-2d7e-4fc6-ae1d-3e76f8be46e5"), 
          VirtualPath = "somecategory/somesubcategory/content2" 
         }, 
         new PageInfo() 
         { 
          Id = new Guid("31d4ea88-aff3-452d-b1c0-fa5e139dcce5"), 
          VirtualPath = "somecategory/somesubcategory/content3" 
         } 
        }; 

        httpContext.Cache.Insert(
         key: key, 
         value: pages, 
         dependencies: null, 
         absoluteExpiration: System.Web.Caching.Cache.NoAbsoluteExpiration, 
         slidingExpiration: TimeSpan.FromMinutes(15), 
         priority: System.Web.Caching.CacheItemPriority.NotRemovable, 
         onRemoveCallback: null); 
       } 
      } 
     } 

     return (IEnumerable<PageInfo>)pages; 
    } 
} 

Вы можете зарегистрировать маршрут с помощью MVC следующим образом.

routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 

// Case sensitive lowercase URLs are faster. 
// If you want to use case insensitive URLs, you need to 
// adjust the matching code in the `Equals` method of the CustomPageRoute. 
routes.LowercaseUrls = true; 

routes.Add(
    name: "CustomPage", 
    item: new CustomPageRoute()); 

routes.MapRoute(
    name: "Default", 
    url: "{controller}/{action}/{id}", 
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 
); 

С выше регистрацией, вы можете генерировать URL на основе офф идентификатора с помощью @Html.ActionLink("A link to a page", "Details", "CustomPage", new { id = "9a19078b-2d7e-4fc6-ae1d-3e76f8be46e5" }, null). Это приведет к действию с именем Details на контроллере с именем CustomPageController (который затем может вернуть представление).

BTW - Маршрутизация является отдельной заботой о «возвращении вида», поэтому ваш вопрос немного запутан.