简体   繁体   English

ASP.NET Mvc 5返回带有seo友好URL的视图

[英]ASP.NET Mvc 5 return View with seo friendly url

I'm looking for a way to return a View based on an Id, but adding a friendly url part when returning. 我正在寻找一种基于ID返回View的方法,但是在返回时添加了一个友好的url部分。

I know I can pass in the id and name when I have this data available, eg by using: 我知道当我可以使用此数据时可以传递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);
}

this code above is not a problem. 上面的这段代码不是问题。 The problem occurs when I submit a form (dropdownlist) where I only have the Id available. 当我提交仅具有可用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 });
} 

In case I redirect from the SelCat action to the Index action with only an ID I want to search the seofriendly name and when returning the view. 如果我仅使用一个ID从SelCat操作重定向到Index操作,我想搜索友好名称并在返回视图时进行搜索。 I was hoping that the url had the friendly url part. 我希望该网址具有友好的网址部分。

   // 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
   }

How can I make my url seo friendly when returning a View in my Controller Action when only an ID is given? 当仅给出ID时,如何在Controller Action中返回View时如何使url seo友好? Setting the RouteData.Values. 设置RouteData.Values。 does not seem to add the part to the url. 似乎没有将该部分添加到url中。

You will have to look in the database for the SEO friendly slug before redirecting and include it in the url, otherwise it's too late: 重定向之前,您必须在数据库中查找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 });
}

Once you have reached the Index action it is already too late to be able to alter the url that is shown on the client, unless you make an additional redirect (which of course would be insane). 一旦执行了“ Index操作,就已经太晚了,无法更改客户端上显示的url,除非您进行其他重定向(这当然是疯狂的)。

You can create a custom RouteBase subclass and load all of your URLs into a cache. 您可以创建一个自定义RouteBase子类,并将所有URL加载到缓存中。 Then all you need is the id (presumably based on a primary key) to lookup the URL. 然后,您需要的只是id (大概基于主键)来查找URL。 Note that you can make the id either Guid (as shown) or int . 请注意,您可以将idGuid (如图所示)或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;
    }
}

You can register the route with MVC like this. 您可以这样向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 }
);

With the above registration, you can generate a URL based off of an id by using @Html.ActionLink("A link to a page", "Details", "CustomPage", new { id = "9a19078b-2d7e-4fc6-ae1d-3e76f8be46e5" }, null) . 通过上述注册,您可以使用@Html.ActionLink("A link to a page", "Details", "CustomPage", new { id = "9a19078b-2d7e-4fc6-ae1d-3e76f8be46e5" }, null) This will take you to an action named Details on a controller named CustomPageController (which can then return a view). 这将带您在名为CustomPageController的控制器上执行名为Details的操作(然后可以返回视图)。

BTW - Routing is a separate concern from "returning a view", so your question is a bit confusing. 顺便说一句-路由与“返回视图”是一个单独的问题,因此您的问题有点令人困惑。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM