How to create consistent URLs with MVC .NET routing?

768 Views Asked by At

I am running ASP.Net MVC 5 in .NET Framework 4.8. I keep getting 404 error due to inconsistently generated URLs. For example, in my _PageNav.chstml partial that is included at the top of each page I the following to take the user back to the home page: @Url.Action("Index", new { controller = "Home" }). In the navigation bar this resolves to <a class="navbar-brand" href="/" /> and functions properly.

When I use the same @Url.Action("Index", new { controller = "Home" }) on the same page, but later in on a button, it resolves to this: <a id="doneButton" class="btn btn-secondary px-4" href="https://localhost:44337/smrt/">Done</a>

Because of this inconsistency I often have issues where AJAX JavaScript references to the controllers end up with missing controller references such as /create resulting in https://localhost:44337/create instead of https://localhost:44337/home/create or /home/create resulting in https://localhost:44337/home/home/create instead of https://localhost:44337/home/create

I do have also have some limitations because of security restrictions; for example I cannot have any JavaScript on the page itself so I can't write razor code in my .cshtml files that will result in JavaScript. I can only use JavaScript referenced in source files for the page.

2

There are 2 best solutions below

0
On BEST ANSWER

It took a few different ideas, but I was able to solve my problem. I now get consistent URLs in just they way I need them, every time.

First, I used this article How to Build Absolute Action URLs Using the UrlHelper Class, to create the follwoing class with an extension method based on the UrlHelper Class .

using System.Web.Mvc;

namespace MVCPages.Utilities
{
    public static class UrlHelperExtenions
    {
        /// <summary>
        /// Generates a fully qualified URL to an action method by using
        /// the specified action name, controller name and route values.
        /// </summary>
        /// <param name="url">The URL helper.</param>
        /// <param name="actionName">The name of the action method.</param>
        /// <param name="controllerName">The name of the controller.</param>
        /// <param name="routeValues">The route values.</param>
        /// <returns>The absolute URL.</returns>        
        public static string AbsoluteAction(
            this UrlHelper url,
            string actionName,
            string controllerName,
            object routeValues = null
        )
        {
            var httpContext = url.RequestContext.HttpContext;
            string scheme = httpContext.Request.Url.Scheme;

            return url.Action(
                actionName,
                controllerName,
                routeValues,
                scheme
            );
        }
    }
}

This new method dynamically gives me the root of the website no matter where the site is deployed.

I then call the new method extension in the views where I need URL consistency and pass the absolute root URL to the view using the ViewBag.

public ActionResult Index()
{
   string rootUrl = Url.AbsoluteAction("Index", "Smrt");
   ViewBag.RootUrl = rootUrl;
}

Since I am not allowed to use Razor to create JavaScript, I next use Razor to create a hidden HTML div tag that includes the root URL, <div id="rootUrl">https://rooturl.com/</div>

Now, in JavaScript, I use the DOM to retrieve the URL from the HTML tag; then use ad that to my ajax calls to create a complete URL for the AJAX calls.

window.onload = function () { 
   var rootUrl = document.getElementById("rootUrl").innerHTML;
   var ajaxUrl = rootUrl + ajaxCall;
};
1
On

@Url.Action("Index", "Home") should be enough to do the job but I've noticed some issues sometimes where custom routing is at play, when changing levels. The Url.Action routine does not render fully qualified urls and sometimes I found I had to append "../" to navigate to a different controller structure, when one controller's view makes an AJAX call to another folder's view. Also, the default URL structure can throw off relative URL calls because the default implementation is to hide /Index in the URL structure. So the URL:

  • localhost/site (defaults Home/Index)
  • localhost/site/other (defaults the Index)

Sees the two views in a different folder structure, and URL navigation can get thrown off.

That has been my experiences as to why you may see some of the problems you are seeing.