Being a popular e-commerce platform, it is very common for store owners to want to migrate to nopCommerce for its powerful features.
One common problem developers face is to migrate old, existing links (from some other e-commerce platforms) to new links (nopCommerce). Especially for old sites which are already getting tons of traffics and are having very good SEO rankings, it is very important to make sure the old URLs are 301-redirected to new nopCommerce URLs.
If it's static URLs (e.g. those for content pages), you can already use IIS URL Rewrite to do the migration manually. But the problem arises when it comes to product and category urls. Because the URLs are dynamic (and there are tons of those URLs), it is usually impractical to manually add URL Rewrite entries one-by-one.
Take the following URLs for example:
- http://oldsite.com/product-slug-one-p1234.htm
- http://oldsite.com/product-slug-two-p4567.htm
- http://oldsite.com/category-slug-one-c12.htm
- http://oldsite.com/category-slug-two-c34.htm
If you look at the URLs, they are pretty dynamic. Product URLs follow a pattern of {product-slug}-p{product-id}.htm. Category links also follow a similar pattern.
And to make the problem worse (which happens very often), old product IDs (and category IDs) do not correspond to new product IDs. That means in old system, product ID 1234 is product ID 4321 in new system.
So the question now is - how can we make sure old URLs, such as http://oldsite.com/product-slug-one-p1234.htm, will be 301-redirected to new nopCommerce URLs, such as http://nopcommercesite.com/product-one-new-slug?
Step 1 - IIS Url Rewrite
We still need to use IIS Url Rewrite to do part of the job. We want to catch all URLs that has the pattern of {product-slug}-p{product-id}.htm and redirect them to one of our MVC Actions (that we'll write in next section).
The URL Rewrite entries can be entered in web.config very easily:
<rewrite>
<rules>
<rule name="Old Product Urls" stopProcessing="true">
<match url="(.*)p(\d+).htm$" />
<action type="Redirect" url="old-product-url/{R:2}" redirectType="Permanent" />
</rule>
<rule name="Old Category URLs" stopProcessing="true">
<match url="(.*)c(\d+).htm$" />
<action type="Redirect" url="old-category-url/{R:2}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
TIPS: You can use Regex Hero to test your regex patterns.
What the above code does is to redirect all URLs that has the pattern of {product-slug}-p{product-id}.htm to old-product-url/{product-id}, the latter being an MVC Action we'll write.
Step 2 - Map old Ids to new Ids
Once you've captured old product URLs, you pass the old product IDs to C# code to do the actual mapping.
The old-id-to-new-id mapping is stored in a simple .txt file (placed inside App_Data), which has entries that look like:
1234 4321
2234 4221
/* ... and so on ... */
It is basically a tab-delimited file, with first row being the old IDs and the second row being the new IDs.
And below is the MVC Action (for product URLs) we've mentioned:
public ActionResult OldProductUrl2(int oldId)
{
var cachedUrlMappings = _cacheManager.Get("OldProductUrl",
() =>
{
StreamReader reader = new StreamReader(new FileStream(
HostingEnvironment.MapPath("~/app_data/old-products.txt"), FileMode.Open));
var urlMappings = new Dictionary<int, int>();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var tokens = line.Split(new char[] { '\t' }, StringSplitOptions.RemoveEmptyEntries);
int key, val;
if (tokens.Length == 2)
{
if (int.TryParse(tokens[0].Trim(), out key) &&
int.TryParse(tokens[1].Trim(), out val))
{
urlMappings.Add(key, val);
}
}
}
reader.Close();
return urlMappings;
});
int newId;
if (cachedUrlMappings.ContainsKey(oldId))
newId = cachedUrlMappings[oldId];
var product = _productService.GetProductById(newId);
if (product != null)
return RedirectToRoutePermanent("Product", new { SeName = product.GetSeName() });
var logger = EngineContext.Current.Resolve();
logger.Information(string.Format("Product Old URL Not Found - {0}", oldId));
return RedirectToRoutePermanent("HomePage");
}
Basically what the code needs to do is, given the old product IDs, find out the new IDs and do the final redirection to actual nopCommerce product page.
Note that we've made use of CacheManager of nopCommerce to cache the entries into IDictionary<int, int>. Remember that IO (Input Output) is very resource intensive, so we want to do as little as IO as possible. So we want to read the text file once, and use the cached results repeteadly for subsequent requests (until cache is cleared).
One last thing we need to do is to make sure MVC Routing works nicely for our new MVC Action (to be added in Presentation\Nop.Web\Infrastructure\RouteProvider.cs):
routes.MapRoute("OldProductUrl",
"old-product-url/{oldId}",
new { controller = "Catalog", action = "OldProductUrl" },
new[] { "Nop.Web.Controllers" });
Conclusion - Adapt to your own URLs
Note that the URL patterns I've shown is just one of the many patterns you'll see in real life e-commerce stores. Most probably you are required to migrate URLs of different format. So what you need to do is to adapt the IIS URL Rewrite regex to capture your URLs, and send the relevant info into your MVC Action to do further processing.
Good luck migrating!