NullReferenceException NullReferenceException - 4 months ago 90
AngularJS Question

AngularJS hash url and MVC routing

I am facing issue in navigating to URL. My default page set to Login and my application URL is

http://localhost:12345/#/
.

This works well but there are two ways the user can login to application



  1. Direct through the application.

  2. Getting username and password trough query string.




When application is logging through Query String the url is like
http://localhost:12345?auth=123654654656564/#/
.

I would like to remove auth value from the URL. I tried to map the routing but it doesn't work.

routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Account", action = "Login"}
);


And also i tried to create one more action result that will return the
view


routes.MapRoute(
name: "ActualDefault",
url: "{controller}/{action}",
defaults: new { controller = "Account", action = "LoginQuery" }
);


Controller:

public ActionResult Login()
{
if (Request.QueryString.Count > 0 && Request.QueryString != null)
{
//validating
return RedirectToAction("LoginQuery", "Account");
}
else
{
return View();
}
}
public ActionResult LoginQuery()
{
return View("Index");
}


The above code removes query string but the URL will be
http://localhost:12345/Account/LoginQuery/#/
.

I just need the URL like
http://localhost:12345/#/
.

Answer

Logging in via Query String

I would be negligent not to point out that this is an extremely bad practice. You should always use HTTP POST when logging into an application and send the user secrets in the body of the post, not the query string.

See

Note that you can also create forms in plain HTML (or via angularjs) to call an MVC action method, or you can make an HTTP POST via JavaScript or some other programming language to do the same thing.

Query string values are completely ignored by MVC routing. But you can make a custom route use query string values.

public class LoginViaQueryStringRoute : RouteBase
{
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var path = httpContext.Request.Path;

        if (!string.IsNullOrEmpty(path))
        {
            // Don't handle URLs that have a path /controller/action
            return null;
        }

        var queryString = httpContext.Request.QueryString;

        if (!queryString.HasKeys())
        {
            // Don't handle the route if there is no query string.
            return null;
        }

        if (!queryString.AllKeys.Contains("username") && !queryString.AllKeys.Contains("password"))
        {
            // Don't handle the case where controller and action are missing.
            return null;
        }

        var routeData = new RouteData(this, new MvcRouteHandler());

        routeData.Values["controller"] = "Account";
        routeData.Values["action"] = "LoginQuery";
        routeData.Values["username"] = queryString["username"];
        routeData.Values["password"] = queryString["password"];

        return routeData;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        return null;
    }
}

Usage

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

        routes.Add(new LoginViaQueryStringRoute());

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

This route will now match http://localhost:12345/?username=foo&password=bar and send it to your LoginQuery action method.

Logging in via http://localhost:12345/#/

It is unclear how you expect this to work. Since everything after the hash tag are generally not sent to the server from the browser, http://localhost:12345/#/ is equivalent to http://localhost:12345/. So, you are effectively saying "I want my home page to be the login page".

In a typical MVC application, you would setup an AuthorizeAttribute on the home page to redirect the user to the login page. After the user logs in, they would be redirected back to the home page (or usually whatever secured page they initially requested).

[Authorize]
public ActionResult Index()
{
    return View();
}

If you want all of your application secured, you can register the AuthorizeAttribute globally and use AllowAnonymousAttribute on your public action methods (such as the login and register pages).

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new AuthorizeAttribute());
        filters.Add(new HandleErrorAttribute());
    }
}

And your login action methods:

[AllowAnonymous]
public ActionResult Login()
{
    //...
}

[AllowAnonymous]
[HttpPost]
public ActionResult Login(LoginModel model)
{
    //...
}

[AllowAnonymous]
public ActionResult LoginQuery(string username, string password)
{
    //...
}

But then, that is a typical MVC-only application.

If you are using Angular to make a SPA, then this could be a very different story. Namely, you would probably switch views on the client side without doing an HTTP 302 redirect to the login form (perhaps it would be a popup - who knows). The point is, without any details of how the client is setup to communicate with MVC, it is not possible to give you any useful advice on setting up MVC for your client beyond how you would typically setup MVC to work in a multi-page application.

NOTE: I can tell you that your routing is misconfigured. The Default and ActualDefault definitions cannot exist in the same route configuration because the first match always wins, therefore the first one will run and the other one will never run. Both of the route URL definitions will match any URL that is 0, 1, or 2 segments in length, so whichever you have first in the route table will match and the other one will be an unreachable execution path.