So what we want to do is attach our selves to the MovingContent event, get a hold of the old URL and the content GUID, store that data, and in our custom 404 controller check for it so that we might perform a redirect (otherwise we display our normal 404 page).
using EPiServer.Core;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.ServiceLocation;
using EPiServer.Web.Routing;
namespace SEO.UrlKeeper
{
[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class InitializationModule : IInitializableModule
{
private static bool _initialized;
public void Initialize(InitializationEngine context)
{
if (_initialized)
{
return;
}
var contentEvents = ServiceLocator.Current.GetInstance();
contentEvents.MovingContent += contentEvents_MovingContent;
_initialized = true;
}
private void contentEvents_MovingContent(object sender, EPiServer.ContentEventArgs e)
{
var urlResolver = ServiceLocator.Current.GetInstance();
var repository = ServiceLocator.Current.GetInstance();
var item = new UrlKeeperItem
{
ContentGuid = e.Content.ContentGuid,
OldPath = urlResolver.GetUrl(e.ContentLink)
};
repository.Save(item);
}
public void Preload(string[] parameters) { }
public void Uninitialize(InitializationEngine context)
{
var contentEvents = ServiceLocator.Current.GetInstance();
contentEvents.MovingContent -= contentEvents_MovingContent;
}
}
}
To persist the data we use EPiServer's Dynamic Data Store. The repository is quite simple.
using System;
using System.Linq;
using EPiServer.Data;
using EPiServer.Data.Dynamic;
using EPiServer.ServiceLocation;
namespace SEO.UrlKeeper
{
[ServiceConfiguration(typeof(IUrlKeeperRepository))]
public class UrlKeeperRepository : IUrlKeeperRepository
{
private static DynamicDataStore Store
{
get
{
return typeof(UrlKeeperItem).GetOrCreateStore();
}
}
public UrlKeeperItem GetByOldPath(string path)
{
return this.GetAll().FirstOrDefault(item => item.OldPath == path);
}
private UrlKeeperItem GetByContentGuid(Guid contentGuid)
{
return this.GetAll().FirstOrDefault(item => item.ContentGuid == contentGuid);
}
private void Delete(Identity id)
{
Store.Delete(id);
}
public IQueryable GetAll()
{
return Store.Items();
}
public Identity Save(UrlKeeperItem item)
{
if (item == null || string.IsNullOrEmpty(item.OldPath))
{
return null;
}
// check if the old path already exists
var oldPathItem = this.GetByOldPath(item.OldPath);
// check if contentGuid is already there
var existingItem = this.GetByContentGuid(item.ContentGuid);
if (oldPathItem != null)
{
// since we don't want more than one item with the same old path.
this.Delete(oldPathItem.Id);
}
if (existingItem != null)
{
// let's replace the existing item with our new on (we only want one per contentGuid)
item.Id = existingItem.Id;
}
item.Modified = DateTime.UtcNow;
return Store.Save(item);
}
}
}
using System.Linq;
using EPiServer.Data;
namespace SEO.UrlKeeper
{
public interface IUrlKeeperRepository
{
IQueryable GetAll();
Identity Save(UrlKeeperItem item);
UrlKeeperItem GetByOldPath(string path);
}
}
using System;
using EPiServer.Data;
using EPiServer.Data.Dynamic;
namespace SEO.UrlKeeper
{
[EPiServerDataStore(AutomaticallyCreateStore = true, AutomaticallyRemapStore = true)]
public class UrlKeeperItem
{
public Identity Id { get; set; }
[EPiServerDataIndex]
public Guid ContentGuid { get; set; }
[EPiServerDataIndex]
public string OldPath { get; set; }
public DateTime Modified { get; set; }
}
}
Our 404 controller is just as simple.
using System.Web.Mvc;
using EPiServer.Web;
using EPiServer.Web.Routing;
using SeoPlayground.SEO.UrlKeeper;
namespace SeoPlayground.Controllers
{
public class NotFoundController : Controller
{
private readonly IUrlKeeperRepository _urlKeeperRepository;
private readonly UrlResolver _urlResolver;
public NotFoundController(IUrlKeeperRepository urlKeeperRepository, UrlResolver urlResolver)
{
this._urlKeeperRepository = urlKeeperRepository;
this._urlResolver = urlResolver;
}
public ActionResult Index()
{
var item = this._urlKeeperRepository.GetByOldPath(Request.RawUrl);
if (item != null)
{
try
{
var redirectUrl = this._urlResolver.GetUrl(PermanentLinkUtility.FindContentReference(item.ContentGuid));
if (redirectUrl != null)
{
Response.RedirectPermanent(redirectUrl, true);
}
}
catch
{
}
}
Response.TrySkipIisCustomErrors = true;
Response.StatusCode = 404;
return View();
}
}
}
Last step is just updating web.config to use our custom 404 controller.
<system.webServer> <httpErrors errorMode="Custom">
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" prefixLanguageFilePath="" path="/NotFound" responseMode="ExecuteURL" />
</httpErrors>
</system.webServer>
Next time an editor moves a page, Search Crawlers and people who use the old URL will automatically be redirected to the new URL instead of getting a 404 error back.
Kontakt oss her