简体   繁体   中英

How to load partial login and registration views with AngularJS and MVC5 (ASP.NET Identity)

I am trying to build a SPA with log-in/register functionality using MVC5, ASP.NET Identity and Angular. I am very new to web development and pretty much every single web technology out there. After a lot of reading on HTML, JavaScript, CSS and Angular I decided a good starting point is the Single Page Application template in Visual Studio. Even though this template already gives me full login and register functionality, it uses Knockout for data binding and view routing. I am required to do this using AngularJS. So far I have managed to call the API methods in the AccountController using the $http Angular service. I log in as follows:

  var config = {
                method: 'POST',
                url: http://localhost:52091/token,
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                data: 'grant_type=password&username=' + username + '&password=' + password,
            };

            var userData = {
                isAuthenticated: false,
                username: '',
                bearerToken: '',
                expirationDate: null,
            };

            function setHttpAuthHeader() {
                $http.defaults.headers.common.Authorization = 'Bearer ' + userData.bearerToken;
            }

            $http(config)
                .success(function (data) {
                    userData.isAuthenticated = true;
                    userData.username = data.userName;
                    userData.bearerToken = data.access_token;
                    userData.expirationDate = new Date(data['.expires']);
                    $http.defaults.headers.common.Authorization = 'Bearer ' + userData.bearerToken;
                    if (typeof successCallback === 'function') {
                        successCallback();
                    }
                })
                .error(function (data) {
                    if (typeof failedCallback === 'function') {
                        if (data.error_description) {
                            failedCallback(data.error_description);
                        } else {
                            failedCallback('Unable to contact server; please, try again later.');
                        }
                    }
                });

First of all, I am not 100% sure why this works and if it is even the correct way of logging in, but it seems to work. Any explanation here would be appreciated, however it is not the main purpose of this question. Secondly, I call the register method as follows:

// Registration
        service.register = function (username, password, confirmPassword, successCallback, failedCallback) {

            var config = {
                method: 'POST',
                url: 'api/account/register',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                data: 'username=' + username + '&password=' + password + '&confirmPassword=' + confirmPassword,
            };

            var userData = {
                isAuthenticated: false,
                username: '',
                bearerToken: '',
                expirationDate: null,
            };

            function setHttpAuthHeader() {
                $http.defaults.headers.common.Authorization = 'Bearer ' + userData.bearerToken;
            }

            $http(config)
                .success(function (data) {
                    userData.isAuthenticated = true;
                    userData.username = data.userName;
                    userData.bearerToken = data.access_token;
                    userData.expirationDate = new Date(data['.expires']);
                    $http.defaults.headers.common.Authorization = 'Bearer ' + userData.bearerToken;
                    if (typeof successCallback === 'function') {
                        successCallback();
                    }
                })
                // Handle errors properly
                .error(function (data) {
                    if (typeof failedCallback === 'function') {
                        if (data.error_description) {
                            failedCallback(data.error_description);
                        } else {
                            failedCallback('Unable to contact server; please, try again later.');
                        }
                    }
                });

        }

This calls into the Register method on the AccountController:

 // POST api/Account/Register
    [AllowAnonymous]
    [Route("Register")]
    public async Task<IHttpActionResult> Register(RegisterBindingModel model)
    {
        if (!ModelState.IsValid) {
            return BadRequest(ModelState);
        }

        IdentityUser user = new IdentityUser {
            UserName = model.UserName
        };

        IdentityResult result = await UserManager.CreateAsync(user, model.Password);
        IHttpActionResult errorResult = GetErrorResult(result);

        if (errorResult != null) {
            return errorResult;
        }

        return Ok();
    }

This all works great. I can register a new user and log in. Now, I would like to configure 2 routes between my Login and Register views so that I can switch between them and load the correct view when necessary ie the Login.cshtml view will have a Register link which, when clicked, should load the Register.cshtml view and vice versa. I tried this by defining a new route as follows in app.js:.

angular.module('Authentication', []);
angular.module('Home', []);

angular.module('BasicHttpAuthExample', [
    'Authentication',
    'Home',
    'ngRoute',
    'ngCookies'
])

.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {

    $routeProvider
        .when('/register', {
            controller: 'RegisterController',
            templateUrl: 'Home/Register',
        })
    .when('/login', {
        controller: 'LoginController',
        templateUrl: 'Home/Login',
    });
}])

This works, IF I have a HomeController with 2 ActionResult methods. One for Register and one for Login. Only, this is not the way the project seems to be set up when I created a new Single Page Application. All it gave me was an empty HomeController. The Register method exists in the Account API controller and there isn't even a Login method anywhere. The _Register and _Login views are both partial views. This causes complete confusion for me as I am still used to each ActionResult method within a controller having a corresponding view linked to it.

Does anyone know how I can go about loading a partial view with Angular?

Not sure if you have figured it out yet but there is a way to load partial views via angular. One caveat for the below answer. It uses Ui-router (not ngRoute) for routing in angular. Note: Please ask questions if you want more explanation about below.

Create a controller that serves only partial views AND add a route for it in the route config.

Below code goes in routeconfig.cs (before the default route)

//Partial view route
routes.MapRoute(
            name: "PartialViews",
            url: "PartialView/{action}/{id}",
            defaults: new { controller = "PartialView", action = "GetView", id = UrlParameter.Optional }
            );
//Default route
 routes.MapRoute(
         name: "AllElse",
         url: "{*url}",
         defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
       );

This is the controller code which will serve your partial views (login, register etc).

    public class PartialViewController : Controller
    {
        [AllowAnonymous]
        public ActionResult GetView(string View)
       {
           return PartialView(View);
       }
    }

Below is how it can be called from angular (using ui-router, which is a routing library for angular and can be used instead of ngRoute).

    $stateProvider
    .state('app', {
        url: '/app',
        templateUrl: 'home/app',
        controller: 'AppController'
    })
    .state('login', {
        url: '/login',
        templateUrl: 'partialview/getview?view=login',
        controller: 'LoginController'
    })
    .state('register', {
        url: '/register',
        templateUrl: 'partialview/getview?view=register',
        controller: 'RegisterController'
    });

Home controller looks like below:

public class HomeController : Controller
{

    public ActionResult Index()
    {
        ViewBag.Title = "Home Page";

        return View();
    }

    [Authorize]
    public ActionResult App()
    {
        return PartialView();
    }
}

So what does the Index view (home/index.cshtml) look like? See below:

<div ui-view>   </div>

Thanks so much roshankar. This works perfectly. I just thought I'd mention that I'm not using UI-Router and this method still works. I am setting up my routes as follows using $routeProvider instead of $stateProvider :

$routeProvider
            .when('/', {
                controller: 'LoginController',
                templateUrl: 'partials/getview?view=login',
            })
            .when('/login', {
                controller: 'LoginController',
                templateUrl: 'partials/getview?view=login',
            })
            .when('/register', {
                controller: 'RegisterController',
                templateUrl: 'partials/getview?view=register',
            })
            .when('/orders', {
                controller: 'HomeController',
                templateUrl: 'Home/Orders',
            })
            .otherwise({ redirectTo: '/login' });

This is the method I am using now to configure my routes. However, in case anyone is interested in how I got it working before roshankar posted this answer, here is how I managed to get it working with just html files. This post by Taiseer Joudeh takes most of the credit for helping me figure it out.

Using the html approach, configuring my routes looked like this:

$routeProvider
            .when('/', {
                controller: 'LoginController',
                templateUrl: '/views/login.html',
            })
            .when('/login', {
                controller: 'LoginController',
                templateUrl: '/views/login.html',
            })
            .when('/register', {
                controller: 'RegisterController',
                templateUrl: '/views/register.html',
            })
            .when('/orders', {
                controller: 'HomeController',
                templateUrl: '/views/orders.html',
            })
            .otherwise({ redirectTo: '/login' });

This works perfectly well if you are not required to use MVC.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM