Search code examples
asp.net-mvcasp.net-mvc-5asp.net-core-3.1

How to show seo friendly url in mvc core 3.1?


I am working on a mvc core 3.1 application. Seo requirements are to show product name with main site instead of complete url.

My original url is

www.abc.com/Fashion/ProductDetail?productId=5088&productName=AliviaBlack&brandId=3

Requirement are

www.abc.com/alivia-black 

I have tried following by using attribute routing

endpoints.MapControllerRoute(
           name: "SpecificRoute",
           pattern: "/{productName}",
           defaults: new { controller = "Fashion", action = "ProductDetail", id= "{productId}" });

In view page

 <a asp-route-productId="@product.ProductId"
                       asp-route-productName="@Common.FilterURL(product.ProductName)"
                       asp-route-brandId="@product.FashionBrand.FashionBrandId" asp-route="SpecificRoute">

Result is

www.abc.com/alivia-black?productId=5223&brandId=4

How to remove question mark and parameters after it.


Solution

  • First off, URIs need to be resilient. You say your current requirement is to have URIs like this:

    www.abc.com/alivia-black
    

    i.e.:

    {host}/{productName}
    

    That's a very bad URI template because:

    • It does not uniquely identify the product (as you could have multiple products with the same name).
    • It will break existing links from external websites if you ever rename a product or replace a product with the same name. And this happens a lot in any product database.
    • Because you're putting the {productName} in the "root" of your URI structure it means it's much harder to handle anything else besides viewing products (e.g. how would you have a /contact-us page? What if you had a product that was named contact-us?)

    I stress that is is very important to include an immutable key to the entity being requested (in this case, your productId value) in the URI and use that as a primary-reference, so the productName can be ignored when handling an incoming HTTP request. This is how StackOverflow's and Amazon's URIs work (you can trim off the text after a StackOverflow's question-id and it will still work: e.g.

    https://stackoverflow.com/questions/69748993/how-to-show-seo-friendly-url-in-mvc-core-3-1
    
    https://stackoverflow.com/questions/69748993
    

    I strongly recommend you read this article on the W3.org's website all about designing good URIs, as well as other guidance from that group's homepage.


    I suggest you use a much better URI template, such as this one:

    {host}/products/{productId}/{productName}
    

    Which will give you a URI like this:

    abc.com/products/5088/alivablack
    

    Handling such a link in ASP.NET MVC (and ASP.NET Core) is trivial, just set the route-template on your controller action:

    [Route( "/products/{productId:int}/{productName?}" )]
    public async Task<IActionResult> ShowProduct( Int32 productId, String? productName = null )
    {
        // Use `productId` to lookup the product.
        // Disregard `productName` unless you want to use it as a fallback to search your database if `productId` doesn't work.
    }
    

    As for generating URIs, as I recommend against using TagHelpers (e.g. <a asp-route-) because they don't give you sufficient control over how the URI is rendered, instead you can define a UrlHelper extension method (ensure you @import the namespace into your .cshtml pages (or add it to ViewStart):

    public static class MyUrls
    {
        public static String ShowProduct( this IUrlHelper u, Int32 productId, String productName )
        {
            const String ACTION_NAME = nameof(ProductsController.ShowProduct);
            const String CONTROLLER_NAME = "Products"; // Unfortunately we can't use `nameof` here due to the "Controller" suffix in the type-name.
    
            String url = u.Action( action: ACTION_NAME, controller: CONTROLLER_NAME, values: new { productId = productId, productName = productName } );
            return url;
        }
    }
    

    Then you can use it like so:

    <a href="@Urls.ShowProduct( 5088, "aliviablack" )">View AliviaBlack</a>
    

    You can also make ShowProduct accept one of your Product objects directly and then pass the values on to the other overload (defined above) which accepts scalars:

        public static String ShowProduct( this IUrlHelper u, Product product )
        {
            String url = ShowProduct( u, productId: product.ProductId, productName: product.ProductName );
            return url;
        }
    

    Then you can use it like so (assuming product is in-scope):

    <a href="@Urls.ShowProduct( product )">@product.ProductName</a>