Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
450 views
in Technique[技术] by (71.8m points)

asp.net - .NET MVC-4 routing with custom slugs

I'm rewriting a website project with ASP.Net MVC 4 and I find it difficult to setup the right routes. The url structure is not RESTful or following a controller/action pattern - the pages have the following structure of slugs. All slugs are saved in the database.

/country
/country/carmake
/country/carmake/carmodel
/custom-landing-page-slug
/custom-landing-page-slug/subpage

Example:

/italy
/italy/ferrari
/italy/ferrari/360
/history-of-ferrari
/history-of-ferrari/enzo

Since Country, Car Make and Car Model are different models/entities, I would like to have something like a CountriesController, CarMakesController and CarModelsController where I can handle the differing logic and render the appropriate views from. Furthermore, I have the custom landing pages which can have slugs containing one or more slashes.

My first attempt was to have a catch-all PagesController which would look up the slug in the database and call the appropriate controller based on the page type (eg. CarMakesController), which would then perform some logics and render the view. However, I never succeed to "call" the other controller and render the appropriate view - and it didn't feel right.

Can anyone point me in the right direction here? Thanks!

EDIT: To clarify: I do not want a redirect - I want to delegate the request to a different controller for handling logic and rendering a view, depending on the type of content (Country, CarMake etc.).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Since your links looks similar, you can't separate them at the routing level. But here are good news: you can write custom route handler and forget about typical ASP.NET MVC links' parsing.

First of all, let's append RouteHandler to default routing:

routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Default", action = "Index", id = UrlParameter.Optional }
).RouteHandler = new SlugRouteHandler();

This allows you to operate with your URLs in different ways, e.g.:

public class SlugRouteHandler : MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        var url = requestContext.HttpContext.Request.Path.TrimStart('/');

        if (!string.IsNullOrEmpty(url))
        {
            PageItem page = RedirectManager.GetPageByFriendlyUrl(url);
            if (page != null)
            {
                FillRequest(page.ControllerName, 
                    page.ActionName ?? "GetStatic", 
                    page.ID.ToString(), 
                    requestContext);
            }
        }

        return base.GetHttpHandler(requestContext);
    }

    private static void FillRequest(string controller, string action, string id, RequestContext requestContext)
    {
        if (requestContext == null)
        {
            throw new ArgumentNullException("requestContext");
        }

        requestContext.RouteData.Values["controller"] = controller;
        requestContext.RouteData.Values["action"] = action;
        requestContext.RouteData.Values["id"] = id;
    }
}

Some explanations are required here.

First of all, your handler should derive MvcRouteHandler from System.Web.Mvc.

PageItem represents my DB-structure, which contains all the necessary information about slug:

PageItem structure

ContentID is a foreign key to contents table.

GetStatic is default value for action, it was convenient in my case.

RedirectManager is a static class which works with database:

public static class RedirectManager
{
    public static PageItem GetPageByFriendlyUrl(string friendlyUrl)
    {
        PageItem page = null;

        using (var cmd = new SqlCommand())
        {
            cmd.Connection = new SqlConnection(/*YourConnectionString*/);
            cmd.CommandText = "select * from FriendlyUrl where FriendlyUrl = @FriendlyUrl";
            cmd.Parameters.Add("@FriendlyUrl", SqlDbType.NVarChar).Value = friendlyUrl.TrimEnd('/');

            cmd.Connection.Open();
            using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
            {
                if (reader.Read())
                {
                    page = new PageItem
                               {
                                   ID = (int) reader["Id"],
                                   ControllerName = (string) reader["ControllerName"],
                                   ActionName = (string) reader["ActionName"],
                                   FriendlyUrl = (string) reader["FriendlyUrl"],
                               };
                }
            }

            return page;
        }
    }
}

Using this codebase, you can add all the restrictions, exceptions and strange behaviors.

It worked in my case. Hope this helps in yours.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...