【问题标题】:ASP.NET Mvc 5 return View with seo friendly urlASP.NET Mvc 5 返回带有 seo 友好 url 的视图
【发布时间】:2017-06-03 02:44:18
【问题描述】:

我正在寻找一种基于 Id 返回 View 的方法,但在返回时添加友好的 url 部分。

我知道当我有这些数据可用时,我可以传入 id 和 name,例如通过使用:

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);
}

上面的这段代码没有问题。当我提交一个只有 ID 可用的表单(下拉列表)时会出现问题。

[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 操作重定向到只有一个 ID 的 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
   }

当仅给出 ID 时,如何在控制器操作中返回视图时使我的 url seo 友好? 设置 RouteData.Values。似乎没有将部分添加到 url。

【问题讨论】:

    标签: asp.net asp.net-mvc seo asp.net-mvc-routing


    【解决方案1】:

    重定向之前,您必须在数据库中查找对 SEO 友好的 slug,并将其包含在 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 操作,就无法更改客户端上显示的网址已经太晚了,除非您进行额外的重定向(这当然是疯狂的)。

    【讨论】:

    • 由于逻辑原因,我需要对 Index 操作进行数据库查询,但现在我需要在 SelCat 操作中进行第二个数据库查询。该死的!我认为可以在一个 Action 中完成。
    • 鉴于您通过 id 查询,任何体面的数据库都应该可以通过主键查询。此外,如果您担心性能,可以添加一些缓存层。
    【解决方案2】:

    您可以创建一个自定义 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 }
    );
    

    通过上述注册,您可以使用@Html.ActionLink("A link to a page", "Details", "CustomPage", new { id = "9a19078b-2d7e-4fc6-ae1d-3e76f8be46e5" }, null) 生成基于ID 的URL。这会将您带到名为 CustomPageController 的控制器上的名为 Details 的操作(然后可以返回视图)。

    顺便说一句 - 路由与“返回视图”是一个单独的问题,所以你的问题有点令人困惑。

    【讨论】:

      猜你喜欢
      • 2013-02-14
      • 1970-01-01
      • 2011-04-06
      • 2014-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-18
      相关资源
      最近更新 更多