Многоязычные URL-адреса с ASP.NET MVC - PullRequest
4 голосов
/ 02 февраля 2011

Я разрабатываю концепции для нового проекта, в котором мне нужно поддерживать многоязычные URL-адреса.В идеале все URL должны быть на родном языке пользователя.Поэтому мы не хотим использовать domain.com / en / contact и domain.com / es / contact , но нам нравится domain.com / contact и domain.com / contactar (для контакта контактная информация - испанский).Внутренне оба должны быть направлены в один и тот же класс ContactController .

Это можно сделать, добавив несколько статических маршрутов в Global.asax.cs для каждого языка, но мы хотели бы сделать это оченьдинамический и хотел бы, чтобы пользователь системы мог изменять перевод URL через систему управления контентом.Поэтому нам нужно какое-то динамическое сопоставление URL-адресов с контроллерами и действиями.

Просматривая исходный код MVC3, я обнаружил, что ProcessRequestInit метод MvcHandler отвечает за определение, какой контроллер создать.Он просто смотрит в RouteData , чтобы получить имя контроллера.Один из способов переопределить маршрутизацию MVC по умолчанию - создать простой маршрут по умолчанию, который использует пользовательский RouteHandler .Этот RouteHandler заставляет MVC использовать мою собственную пользовательскую версию MvcHandler , которая переопределяет метод ProcessRequestInit .Этот переопределенный метод вставляет мой собственный динамически найденный контроллер и действие в RouteData перед обратным вызовом исходного ProcessRequestInit .

Я пробовал это:

Global.asax.cs

routes.Add(
    new Route("{*url}", new MultilingualRouteHandler())
    {
        Defaults = new RouteValueDictionary(new { controller = "Default", action = "Default" })
    }
);

MultilingualRouteHandler.cs

public class MultilingualRouteHandler : IRouteHandler
{

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        return new MultilingualMVCHandler(requestContext);
    }

}

MultilingualMvcHandler.cs

public class MultilingualMVCHandler : MvcHandler
{

    public MultilingualMVCHandler(RequestContext context) : base(context)
    {
    }

    protected override void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
    {

        if (RequestContext.RouteData.Values.ContainsKey("controller"))
        {
            RequestContext.RouteData.Values.Remove("controller");
        }

        if (RequestContext.RouteData.Values.ContainsKey("action"))
        {
            RequestContext.RouteData.Values.Remove("action");
        }

        RequestContext.RouteData.Values.Add("controller", "Product");
        RequestContext.RouteData.Values.Add("action", "Index");

        base.ProcessRequestInit(httpContext, out controller, out factory);

    }

}

В этом обработчике я жестко закодировал контроллер и действие для целей тестирования с некоторыми фиксированными значениями, но это не сложно сделать динамическим.Это работает, но единственная проблема заключается в том, что мне пришлось изменить исходный код ASP.NET MVC3, чтобы заставить его работать.Проблема заключается в том, что метод ProcessRequestInit для MvcHandler является закрытым и поэтому не может быть переопределен.Я изменил исходный код и изменил его на защищенный виртуальный, что позволяет мне переопределять его.

Это все замечательно, но, возможно, не лучшее решение.Обременительно, что мне всегда нужно будет распространять свою собственную версию System.Web.Mvc.dll.Было бы намного лучше, если бы он работал с RTM-версией.

Я упускаю какие-либо другие возможности подключения к ASP.NET MVC, которые позволили бы мне динамически определять контроллер и действие для запуска, в зависимости отURL?Еще один способ, о котором я подумал, - это динамически создать RouteCollection на * Application_Start *, но я думаю, что это усложнит его изменение на лету.

Буду признателен за любые советыкрючки, которые я еще не нашел.

Ответы [ 3 ]

5 голосов
/ 08 июня 2011

Это довольно старое время, просто на всякий случай, если кто-то еще ищет что-то подобное ...

Если я не совсем понимаю, что вы хотите сделать, на самом деле это довольно просто.

Шаг 1: Добавьте новый маршрут к global.ascx.cs, содержащий ссылку на ваш персональный механизм маршрутизации

routes.Add(new MyProject.Routing.ContentRoutingEngine());

Убедитесь, что он находится в нужном месте в списке маршрутов, чтобы другие маршрутыдвигатели могут перехватить данные перед этим, если это необходимо, или продолжить поиск маршрута, если ваш двигатель не обрабатывает определенный маршрут.Я поставил его после игнорирования, но до маршрутов по умолчанию MVC.

Шаг 2. Создайте модуль маршрутизации содержимого, убедившись, что он наследуется от абстрактного класса System.Web.Routing.RouteBase, и переопределяет GetRouteData иМетоды GetVirtualPath, как требуется, например,

public class ContentRoutingEngine : RouteBase
{
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var routeHandler = new MvcRouteHandler();
        var currentRoute = new Route("{controller}/{action}", routeHandler);
        var routeData = new RouteData(currentRoute, routeHandler);

        // set your values dynamically here
        routeData.Values["controller"] = "Home" ;
        // or
        routeData.Values.Add("action", "Index");

        // return the route, or null to have it passed to the next routing engine in the list
        return routeData;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        //implement this to return url's for routes, or null to just pass it on
        return null;
    }
}

, и это должно сделать это.Вы можете изменять маршруты так динамически, как вы хотите в вашем движке, и никаких изменений в источнике MVC не требуется.Пусть стандартный MVC RouteHandler фактически вызывает контроллер.

Постскриптум: Очевидно, что приведенный выше код не является производственным стандартом - он написан для того, чтобы сделать как можно более очевидным, что происходит.

0 голосов
/ 03 февраля 2011

Я думаю, что наиболее элегантным решением было бы использование некоторого фильтра действий в сочетании с пользовательским ActionInvoker.Таким образом, вы можете вызвать действие, к которому применены определенные фильтры.Что-то вроде атрибута ActionName, способного принимать только несколько значений (имен).

Редактировать: Взгляните на ActionMethodSelectorAttribute, возможно, вам даже не нужен пользовательский ActionInvoker.

0 голосов
/ 02 февраля 2011

Если вы разрешаете модификацию URL-адресов через вашу CMS, вам придется сохранить все старые версии URL-адресов, чтобы вы могли перенаправить 301 на новые.

Лучше всего для этого поместить URL-токены, например, "contactar" в БД вместе с соответствующим контроллером.

запросите это и создайте свои маршруты из этого.

создать маршрут, который будет обрабатывать 301 с

...