ASP.NET Mvc 5 return 使用 seo 友好查看 url
ASP.NET Mvc 5 return View with seo friendly url
我正在寻找一种方法来 return 基于 Id 的视图,但是在 returning.
时添加友好的 url 部分
我知道当我有可用的数据时,我可以传入 ID 和名称,例如通过使用:
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 操作,我想在 returning 视图时搜索 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 的情况下 return 在我的控制器操作中查看视图时,如何使我的 url 搜索引擎优化友好?
正在设置 RouteData.Values。似乎没有将部分添加到 url.
在 重定向之前,您必须在数据库中查找 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
操作,就无法更改客户端上显示的 url 已经太晚了,除非您进行额外的重定向(这当然会疯了)。
您可以创建自定义 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
的控制器(然后可以 return 视图)上执行名为 Details
的操作。
BTW - Routing is a separate concern from "returning a view", so your question is a bit confusing.
我正在寻找一种方法来 return 基于 Id 的视图,但是在 returning.
时添加友好的 url 部分我知道当我有可用的数据时,我可以传入 ID 和名称,例如通过使用:
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 操作,我想在 returning 视图时搜索 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 的情况下 return 在我的控制器操作中查看视图时,如何使我的 url 搜索引擎优化友好? 正在设置 RouteData.Values。似乎没有将部分添加到 url.
在 重定向之前,您必须在数据库中查找 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
操作,就无法更改客户端上显示的 url 已经太晚了,除非您进行额外的重定向(这当然会疯了)。
您可以创建自定义 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
的控制器(然后可以 return 视图)上执行名为 Details
的操作。
BTW - Routing is a separate concern from "returning a view", so your question is a bit confusing.