Открытая коллекция знаний

OpenU.Ru

Поисковая оптимизация сайтов, работающих на .NET

Редиректы с папок Home/Index на корневую в приложениях MVC на ASP.NET Framework

Сайты, разработанные на паттерне MVC в ASP.NET Framework имеют особенность отдавать одинаковое содержимое с HTTP-кодом 200 по разным url, если вызывается действие по-умолчанию контроллера (Index) или сам контроллер по-умолчанию (home). Например, действие Index контроллера HomeController сработает и сформирует одинаковую страницу представления Index.cshtml при обращении сразу как минимум, по трём url-адресам: example.com, example.com/Home и example.com/Home/Index. Index контроллера AboutController — по двум: example.com/About и example.com/About/Index. Это очень плохо в плане поисковой оптимизации, т.к. создаются страницы-дубли, и нужно чтобы либо страница отвечала двухсотым кодом только по одному адресу, а с остальных либо отдавала http ошибку 404 (ресурс не найден), либо 301 (ресурс перемещён навсегда). Выберем второй вариант и будем перенаправлять 301-м редиректом со страниц Index или Home на корневую папку. Существует несколько методов, но во-первых следует перевести все url в нижний регистр, чтобы Index в адресной строке превратился в index.

1. Использование дополнительного параметра в таблице маршрутизации.

Переопределим метод RegisterRoutes класса RouteConfig следующим образом:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        name: "IndexRedirect",
        url: "{controller}/Index",
        defaults: new { controller = "Home", action = "Index", NeedRedirectFromIndex = true }
    );

    routes.MapRoute(
        name: "HomeRedirect",
        url: "Home/",
        defaults: new { controller = "Home", action = "Index", NeedRedirectFromIndex = true }
    );

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );

Тут мы указываем, что если url заканчивается на Index или Home, то в контроллер также передаётся параметр NeedRedirectFromIndex. Далее, контроллер должен проверить этот параметр и в случае его наличия вызвать переадресацию на самого себя с пустым параметром. Для этого перепишем метод Index контроллера:

public ActionResult Index(bool? NeedRedirectFromIndex)
{
    //редирект с папок Home/Index
    if (NeedRedirectFromIndex.HasValue && NeedRedirectFromIndex.Value)
        return RedirectToActionPermanent("Index", new { NeedRedirectFromIndex = null as object });
    return View();
}

Теперь при обращении к папке home/index будет перенаправление в корневую папку сайта. У этого подхода, как минимум, 2 недостатка. Во-первых, нужно так или иначе модифицировать все методы Index всех контроллеров, для которых мы желаем производить перенаправление. Во-вторых, при построении ссылок, например, с помощью выражения ActionLink нужно добавлять пустое значение RedirectToActionPermanent в качестве параметра:

@Html.ActionLink("Главная", "Index", "Home", new { RedirectToActionPermanent = null as object }, null)

Иначе адреса будут формироваться в виде controller/index, а с них уже будет происходить перенаправление, что тоже не есть хорошо не только для поисковой оптимизации, но и для нагрузки на сервер

2. Переадресация в методе Application_BeginRequest класса MvcApplication

Оставляем маршруты и контроллеры в изначальном виде (по умолчанию) и опишем метод Application_BeginRequest класса MvcApplication примерно следующим образом (файл global.asax):

protected void Application_BeginRequest(object sender, EventArgs e)
{
    //Не редиректим на нижний регистр метод "post" или images/css/js
    bool isGet = HttpContext.Current.Request.RequestType.ToLowerInvariant().Contains("get");
    if (isGet && HttpContext.Current.Request.Url.AbsolutePath.Contains(".") == false)
    {
        bool NeedRedirect = false;
        string lowercaseURL = (Request.Url.Scheme + "://" + HttpContext.Current.Request.Url.Authority + HttpContext.Current.Request.Url.AbsolutePath);
        if (Regex.IsMatch(lowercaseURL, @"[A-Z]"))
        {
            //не меняем регистр параметров
            lowercaseURL = lowercaseURL.ToLower() + HttpContext.Current.Request.Url.Query;
            NeedRedirect = true;
        }

        //редирект с /index на уровень вверх...
        if (Regex.IsMatch(lowercaseURL, @"/index[/]?$"))
        {
            lowercaseURL = Regex.Replace(lowercaseURL, "/index[/]?$", RouteTable.Routes.AppendTrailingSlash ? "/" : ""); 
            NeedRedirect = true;
        }

        //редирект с /home на уровень вверх...
        if (Regex.IsMatch(lowercaseURL, @"/home[/]?$"))
        {
            lowercaseURL = Regex.Replace(lowercaseURL, "/home[/]?$", RouteTable.Routes.AppendTrailingSlash ? "/" : "");
            NeedRedirect = true;
        }

        if (NeedRedirect)
        {
            Response.Clear();
            Response.Status = "301 Moved Permanently";
            Response.AddHeader("Location", lowercaseURL);
            Response.End();
        }
    }
} 

В листинге выше уже сразу дополнительно описано преобразование url в нижний регистр и предусмотрена переадресация на url со слэшем на конце или без, в зависимости от выбранной политики с помощью признака свойства RouteTable.Routes.AppendTrailingSlash. Недостатком подхода остается необходимость контроля построения url. Например, для маршрута по умолчанию:

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

в случае, если в качестве id будет передано значение «Index» или «home», то сработает перадресация и параметр никогда не будет передан в контроллер. Понятно, что в параметре Id таких значений, скорее всего, передано не будет, но если в качестве параметра выступает строковое значение (например ЧПУ-url вида {controller}/{action}/{id}/{url}) — то вполне вероятно. В качестве крайнего решения можно предопределить имя метода действия по-умолчанию на какое-нибудь уникальное название (комбинацию символов), которое никогда не будет передано в качестве параметра. Либо доработать условия переадресации метода Application_BeginRequest на соблюдение уровней вложенностей адресов и, возможно, наложения дополнительных условий проверки url-ов в случае сложной адресации, когда в зависимости от условия вхождения строки в url уровень вложенности может быть различным.

3. Редирект с помощью URL Rewrite

Чтобы переадресовать с папки Home сайта в папку home/index, нужно в файле Web.config в секции <configuration> — <system.webServer> — <rewrite> — <rules> добавить правило:

<rule name="Redirect HomeIndex" stopProcessing="true">
  <match url="home[/]?$" />
  <action type="Redirect" url="home/index" redirectType="Permanent" />
</rule>

Аналогично можно сделать и другие переадресации, если есть возможность правильно написать регулярные выражения и условия.