Маршрутизация .NET MVC-4 с пользовательскими пулями

Я переписываю проект веб-сайта с помощью ASP.Net MVC 4, и мне сложно настроить правильные маршруты. Структура URL не RESTful или не соответствует шаблону контроллера/действия - страницы имеют следующую структуру пули. Все пули сохраняются в базе данных.

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

Пример:

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

Так как Country, Car Make и Car Model - это разные модели/сущности, я хотел бы иметь что-то вроде CountryController, CarMakesController и CarModelsController, где я могу обрабатывать различную логику и отображать соответствующие представления. Кроме того, у меня есть настраиваемые целевые страницы, в которых могут быть пули, содержащие одну или несколько слэшей.

Моя первая попытка состояла в том, чтобы иметь все PagesController, которые будут искать пул в базе данных и вызывать соответствующий контроллер на основе типа страницы (например, CarMakesController), который затем выполняет некоторые логики и сделать вид. Тем не менее, мне никогда не удастся "вызвать" другой контроллер и сделать соответствующий вид - и он не чувствовал себя хорошо.

Может ли кто-нибудь указать мне в правильном направлении? Спасибо!

EDIT: Чтобы уточнить: я не хочу перенаправления - я хочу делегировать запрос другому контроллеру для обработки логики и визуализации представления в зависимости от типа содержимого (Country, CarMake и т.д.)..

Ответ 1

Так как ваши ссылки похожи, вы не можете их разделить на уровне маршрутизации. Но вот хорошая новость: вы можете написать собственный обработчик маршрута и забыть о типичном анализе ссылок ASP.NET MVC.

Прежде всего, добавьте RouteHandler в стандартную маршрутизацию:

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

Это позволяет вам работать с вашими URL-адресами по-разному, например:

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

Здесь требуются некоторые объяснения.

Прежде всего, ваш обработчик должен получить MvcRouteHandler из System.Web.Mvc.

PageItem представляет мою DB-структуру, которая содержит всю необходимую информацию о slug:

PageItem structure

ContentID является внешним ключом к таблице содержимого.

GetStatic - значение по умолчанию для действия, это было удобно в моем случае.

RedirectManager - это статический класс, который работает с базой данных:

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

Используя эту кодовую базу, вы можете добавить все ограничения, исключения и странное поведение.

Это сработало в моем случае. Надеюсь, это поможет вам.