The Family Page–Part 8, Wish list and Angular.js second try

Since last time where I started using Angular.js with Angular resources and services, I have been reading a bit and I decided on a little bit different approach. I want to use WebApi on the backend, and Angular factories and controllers. With my current (limited) knowledge of angular, the extra layer of abstraction using a resource makes things a little more difficult to understand without really giving any benefits. This might change later in the process, but right now this is the way I am going.

What I am showing here is strongly inspired by Dan Wahlin’s post on the subject. Using an angularjs factory to interact with a restful service.

In this post I will

  • Add WebApi to an existing MVC project
  • Use controller and factory in Angular.js to get the data from the WebApi controller.
  • Update the view view and data structure a little bit in order to group wishes by user. (instead of just having a flat list).

Adding WebApi

In order to use WebApi we need to set up WebApi routing, which is similar to, but separate from ASP.Net MVC routing. I add the WebApiConfic.cs file to Auth_Start, which is automatically added if you start out with a WebApi project:

using System.Web.Http;

namespace TheFamilyPage.App_Start
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

 

I the call this from Global.asax in GlobalConfiguration line (also completely standard, if you start out with a WebApi project)

using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

using TheFamilyPage.App_Start;

namespace TheFamilyPage
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }
}

 

I then add WebApi controller for the wishes. In order to get the same functionality as I had before, I have implemented the “Get” Wishes part:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Mvc;

using TheFamilyPage.Models;
using TheFamilyPage.ViewModel;

namespace TheFamilyPage.Controllers
{
  public class WishApiController : ApiController
  {
    private ApplicationDbContext db = new ApplicationDbContext();

    // GET api/
    public async Task Get()
    {
      var tmpWishes = await db.Wishes.ToListAsync();

      var wishesByUsername = tmpWishes.GroupBy(x => x.User.Name);
      var wishes = new List<UserWishesViewModel>();
      foreach (var w in wishesByUsername)
      {
        var userWishes = new UserWishesViewModel();
        userWishes.Wishes = w.Select(x => new WishMViewodel(x)).ToList();
        userWishes.Name = w.Key;
        wishes.Add(userWishes);
      }

      if (wishes == null) throw new HttpResponseException(HttpStatusCode.NotFound);
      return Request.CreateResponse<IEnumerable<UserWishesViewModel>>(HttpStatusCode.OK, wishes); 
      
    }

    // GET api//5
    public string Get(int id)
    {
      return "value";
    }

    // POST api/
    public void Post([FromBody]string value)
    {
    }

    // PUT api//5
    public void Put(int id, [FromBody]string value)
    {
    }

    // DELETE api//5
    public void Delete(int id)
    {
    }
  }

  
}

I have also added a new ViewModel for the wishes in order to group the wishes by user, instead of having on flat list:

using System.Collections.Generic;

using TheFamilyPage.Models;

namespace TheFamilyPage.ViewModel
{
  class UserWishesViewModel
  {
    public string Name { get; set; }
    public List<WishMViewodel> Wishes { get; set; }
  }
  
  public class WishMViewodel
  {
    public WishMViewodel(Wish wish)
    {
      Id = wish.Id;
      Note = wish.Note;
      Shop = wish.Shop;
      TheWish = wish.TheWish;
      Url = wish.Url;
      UserId = wish.User.Id;
    }

    public string UserId { get; set; }
    public string Url { get; set; }
    public string TheWish { get; set; }
    public string Shop { get; set; }
    public string Note { get; set; }
    public int Id { get; set; }
  }
}

 

Setting up Angular factory and controller.

Now it is time to setup up the angular factory and controller. As mentioned before I find this much easier to work with at the moment, instead of using Services and Resources. Feel free to disagree.

The code is pretty straight forward – first the factory, which right now only supports getting the wishes:

var ngWishApp = angular.module("ngWishApp", []);

ngWishApp.factory('dataFactory', [
  '$http', function($http) {
    var urlBase = '/api/WishApi';
    var dataFactory = {};

    dataFactory.getWishes = function() {
      return $http.get(urlBase);
    }

    return dataFactory;
  }
]);

 

and the controller:

ngWishApp
  .controller('wishController', [
    '$scope', 'dataFactory',
    function($scope, dataFactory) {

      $scope.status;
      $scope.wishes;

      getWishes();

      function getWishes() {
        dataFactory.getWishes()
          .success(function(w) {
            $scope.wishes = w;
          })
          .error(function(error) {
            $scope.status = 'Unable to load wish data: ' + error.message;
          });
      }
    }
  ]);

 

You will notice that the controller calls getWishes() in order to initialize $scope.wishes.

Settings up the View

To finish our re-implementation heres is the updated view – note that I didn’t have to change the view to make it work, but I wanted to group the wishes by user, so I updated the view a little bit:

@model IEnumerable
"~/js/wish.js"></script>

@{
    ViewBag.Title = "Index";
}

<h2>Wishes</h2>

<div ng-app="ngWishApp">
  <div ng-controller="wishController">
    <div ng-repeat="items in wishes">
      <h2>{{items.Name}}</h2>
      <ul>
        <li ng-repeat="item in items.Wishes">
          {{item.TheWish}}, {{item.Url}}, {{item.Shop}}, {{item.Note}} 
        </li>
      </ul>
    </div>
    <div>{{status}}</div>
  </div>
</div>
<p>

 

A test run shows this:

image

So everything is working nicely.

This time I simply reimplemented what we did last time but I added WebApi and used controller and factory in angular to get the job done.

Next time I will add full CRUD functionality.

Posted in ASP.NET MVC, javascript, WebAPI | Tagged , , , | Leave a comment

The Family Page–Part 8, using Angular.js to show the wish list.

Last time we made the wishlist datamodel, view and controller. Now we will start to use angular.js to show our data.

First I create some data to show – this is done the Seed() method of the migrations Configuration class. I have created a family “Dad”, “Mom”, “Son” ,”Daughter” and given each a few wishes. “Dad” has the role “Admin”, while “Mom” has the role “Editor”. The kids are normal users.

protected override void Seed(TheFamilyPage.Models.ApplicationDbContext context) { var userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(context)); var roleManager = new ApplicationRoleManager(new RoleStore<IdentityRole>(context)); var dad = CreateUser(userManager, roleManager, "dad@thefamilypage.com", "Dad@123456", "Dad", ApplicationDbInitializer.AdminRoleName); var mom = CreateUser(userManager, roleManager, "mom@thefamilypage.com", "Mom@123456", "Mom", ApplicationDbInitializer.EditorRoleName); var son = CreateUser(userManager, roleManager, "son@thefamilypage.com", "Son@123456", "Son", ApplicationDbInitializer.UserRoleName); var daughter = CreateUser(userManager, roleManager, "daughter@thefamilypage.com", "Daughter@123456", "Daughter", ApplicationDbInitializer.UserRoleName); context.Wishes.AddOrUpdate(p => p.TheWish, new Wish { TheWish = "LEGO Star Wars game", Shop = "The Toy Store", Note = "The new one", User = son }); context.Wishes.AddOrUpdate(p => p.TheWish, new Wish { TheWish = "Bike", Shop = "Bikes online", Note = "Must be blue", Url = "http://www.bikesonline.org", User = son }); context.Wishes.AddOrUpdate(p => p.TheWish, new Wish { TheWish = "Bike", Shop = "Bikes online", Note = "The pink one with stars on the saddle", Url = "http://www.bikesonline.org", User = daughter }); context.Wishes.AddOrUpdate(p => p.TheWish, new Wish { TheWish = "Dress", Shop = "Cocktaildresses'R'Us", User = mom }); context.Wishes.AddOrUpdate(p => p.TheWish, new Wish { TheWish = "Xamarin Studio", Shop = "Xamarin", Url = "http://xamarin.com", User = dad }); } static ApplicationUser CreateUser(ApplicationUserManager userManager, ApplicationRoleManager roleManager, string email, string password, string name, string roleName) { var user = userManager.FindByName(email); if (user == null) { user = new ApplicationUser { UserName = email, Email = email, Name = name }; userManager.Create(user, password); userManager.SetLockoutEnabled(user.Id, false); } // Add user admin to Role Admin if not already added var rolesForUser = userManager.GetRoles(user.Id); var role = roleManager.FindByName(roleName); if (role == null) { role = new IdentityRole(roleName); roleManager.Create(role); } if (!rolesForUser.Contains(role.Name)) { userManager.AddToRole(user.Id, role.Name); } return user; }

 

 

 

The current wishlist index view looks like this:

@model IEnumerable<TheFamilyPage.Models.Wish>

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.TheWish)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Url)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Shop)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Note)
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.TheWish)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Url)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Shop)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Note)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
            @Html.ActionLink("Details", "Details", new { id=item.Id }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.Id })
        </td>
    </tr>
}

</table>

Which produces the output:

image

 

This is lists all the wishes but we can not see who wishes for what, and the code is not very pretty.  I will try to make it a little better by using angular.js.

First I change the Index method in the controller so it does not return any data. Angualr will ask for the data in another controller method called GetWishes. GetWishes returns its data as JSON, so it is directly usable by angular:

        public async Task<ActionResult> Index()
        {
            return View();
        }

        [HttpGet]
        public async Task<JsonResult> GetWishes()
        {
          return Json(await db.Wishes.ToListAsync(), JsonRequestBehavior.AllowGet);
        }

Next I need to use angualr.js – I do this by adding these two lines to the _layout.cshtml file:

  <script src="~/Scripts/angular.js"></script>
  <script src="~/Scripts/angular-resource.js"></script>

Then we need to add a javascript file of our own. I have called it wish.js, and I have create a ‘js’ folder for my own javescript files.

Wish.js contains a small module ‘ngWishApp’ with wishController that gets the data from my WishesController.cs:

var ngWishApp = angular.module("ngWishApp", ['ngResource']);

ngWishApp.factory("wishService", function($resource) {
  return {
    getWishes: $resource("/Wishes/GetWishes")
  }
});

ngWishApp.controller("wishController", function($scope, wishService) {
  $scope.wishes = wishService.getWishes.query({}, isArray = true);
});

To really understand the module, factory and reosurce stuff going on here, you can read this very good article at dotnetcurry.

Now we only need to update our WishesIndex view – I remove all the razor view code, and add some simple angualr.js code:

@model IEnumerable<TheFamilyPage.Models.Wish>
<script src="~/js/wish.js"></script>

@{
    ViewBag.Title = "Index";
}

<h2>Wishes</h2>

<div ng-app="ngWishApp">
  <div ng-controller="wishController">
    <ul>
      <li ng-repeat="item in wishes">
        {{item.UserName}}, {{item.TheWish}}, {{item.Url}}, {{item.Shop}}, {{item.Note}}
      </li>
    </ul>
  </div>
</div>
<p>

ng-app in the outer div-tag defines the area of html code where the ngWishApp is active. Further we indicate that we are using the wishController. I then create a list of wishes using the ng-repeat keyword. 

The result looks like this:

image

There is no doubt that this can be more pretty, but right now I am satisfied that I have started to use angualr.js.

Posted in Ikke kategoriseret | Leave a comment

Creating the Family Page – Part 7, Beginning on the Wish List

Now that all the user and role code is done, it is time to add some real functionality. This time we will add the data model for the wish list and create a basic controller and view for it. This means that we will not handle anything that relates to connection specific wishes to specifics users today.

First we add a Wish class to the Models folder

namespace TheFamilyPage.Models
{
  public class Wish
  {
    public int Id { get; set; }
    public virtual ApplicationUser User { get; set; }
    public string TheWish { get; set; }
    public string Url { get; set; }
    public string Shop { get; set; }
    public string Note { get; set; }
  }
}

A wish has the following data:

– A user that has the wish on his/her wish list.
– A wish name “TheWish”
– A URL to be used if it can be found on the internet
– A shop name so people know where to find the wish in the real world
– A note, that can describe the wish in more detail (clothes size, colors, preferences etc.)

Then we need to add the wishes to the ApplicationDbContext class which is defined in the IdentityModels.cs file. I chose to make the class partial and add a new file called AppilcationDbContext.cs with the parts that is relevant to our data model.

namespace TheFamilyPage.Models
{
  public partial class ApplicationDbContext
  {
    public DbSet<Wish> Wishes { get; set; } 
  }
}

In the PackageManager console I write

PM> Add-Migration WishList
Scaffolding migration 'WishList'.
The Designer Code for this migration file includes a snapshot of your current Code First model. This snapshot is used to calculate the changes to your model when you scaffold the next migration. If you make additional changes to your model that you want to include in this migration, then you can re-scaffold it by running 'Add-Migration WishList' again.

And then

PM> Update-Database
Specify the '-Verbose' flag to view the SQL statements being applied to the target database.
Applying explicit migrations: [201404281925350_WishList].
Applying explicit migration: 201404281925350_WishList.
Running Seed method.

 

Now our database is ready for use with the new datastructure.

image

At this time we will add a generic MVC controller for the wish list and also generate a view.Right-click on the controllers folder and select Add / Controller

image

Select ´Controller with View Usinger Entity Framework. In the following dailog choose a name for the controller and the data model to use (Wish):

image

Click Add.

Now a WishesController with all the CRUD actions has been added. Views for these actions have also been added. If we try to run our application we can try to access the wish list, by changing the URL to http://localhost:<port>/wishes – we have to do this manually, since we haven’t added any links to the wish list yet.

image

It is possible to add, edit and remove wishes.

Conclusion

We now have the ability to show, create, edit and delete wishes. So the basics of the wish list is in place.

Next time I want to implement.

  • Anonymous users to be able to view the wish lists
  • Logged in users to be able to edit their own wish lists.
  • Users with the role of editor to be able to edit all wish lists.
  • Links to view wish lists, and links to edit wish lists.
Posted in ASP.NET MVC, C#, Web | Tagged , , , , | Leave a comment

Creating The Family Page – Part 6, Role and User Management

This time we will add role management and subsequently give an administrator the ability to a manage (add/edit/delete) users. It might seem overkill to have roles on such a simple page, that will hold the content of a family of four. The idea is however that the kids can edit their own content, while mom and dad can edit everything, so roles will be useful. Again it might be overkill to implement management systems for that – we know the number of users and their roles are pretty much set in stone, so we could just hardcode everything. It is however pretty straight forward to implement user and role management and it also has an educational value for me.

The code presented here is heavily inspired by an ASP.NET Identity sample project from the team behind ASP.NET Identity. Unfortunately I haven’t been able to find that sample again – but here is a lot of smaller samples to get you started: http://www.asp.net/aspnet/samples/aspnet-identity

So, the agenda of the day:

  • Bootstrap the user and role system – Create an admin role and user (user zero)
  • Create role management CRUD operations in controller and views.
  • Create user management CRUD operations in controller and views.
  • Remove registration from the front page.

Lets get started.

Bootstrap the user and role system

The only persons allowed to use the user and role management systems are administrators. But in order to become an administrator your need to use the role management system. This is kind of a deadlock. To solve this, we will hardcode a first user into the system. In App_Start/IdentityConfig.cs we add the following code:

  public class ApplicationRoleManager : RoleManager
  {
    public ApplicationRoleManager(IRoleStorestring> roleStore)
      : base(roleStore)
    {
    }

    public static ApplicationRoleManager Create(IdentityFactoryOptions options, IOwinContext context)
    {
      var manager = new ApplicationRoleManager(new RoleStore<IdentityRole>(context.Get()));
      return manager;
    }
  }


  public class ApplicaitonDbInitializer : DropCreateDatabaseIfModelChanges
  {
    const string adminRoleName = "Admin";

    protected override void Seed(ApplicationDbContext context)
    {
      InitializeAdminRole(context);
      InitializeFirstUser(context);
      base.Seed(context);
    }

    void InitializeFirstUser(ApplicationDbContext context)
    {
      var userManager = HttpContext.Current.GetOwinContext().GetUserManager();
      var roleManager = HttpContext.Current.GetOwinContext().Get();
      const string name = "admin@admin.com";
      const string password = "Admin@123456";

      var user = userManager.FindByName(name);
      if (user == null)
      {
        user = new ApplicationUser { UserName = name, Email = name };
        userManager.Create(user, password);
        userManager.SetLockoutEnabled(user.Id, false);
      }

      // Add user admin to Role Admin if not already added
      var rolesForUser = userManager.GetRoles(user.Id);
      var role = roleManager.FindByName(adminRoleName);
      if (!rolesForUser.Contains(role.Name))
      {
        userManager.AddToRole(user.Id, role.Name);
      }
    }

    void InitializeAdminRole(ApplicationDbContext context)
    {
      var roleManager = HttpContext.Current.GetOwinContext().Get();

      //Create Role Admin if it does not exist
      var role = roleManager.FindByName(adminRoleName);
      if (role == null)
      {
        role = new IdentityRole(adminRoleName);
        roleManager.Create(role);
      }
    }
  }

 

So this code add the user “admin@admin.com” with the password “Admin@12345” and gives this user the role “Admin”.

Create role management

First we add a simple view model.

  public class RoleViewModel
  {
    public string Id { get; set; }

    [Required(AllowEmptyStrings = false)]
    public string RoleName { get; set; }
  }

 

And a controller for CRUD operations.

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.AspNet.Identity.EntityFramework;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Collections.Generic;

using TheFamilyPage;
using TheFamilyPage.Models;

namespace IdentitySample.Controllers
{
  [Authorize(Roles = "Admin")]
  public class RolesManagerController : Controller
  {
    public RolesManagerController()
    {
    }

    public ApplicationUserManager UserManager
    {
      get
      {
        return HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
      }
    }

    public ApplicationRoleManager RoleManager
    {
      get
      {
        return HttpContext.GetOwinContext().Get<ApplicationRoleManager>();
      }
    }

    //
    // GET: /Roles/
    public ActionResult Index()
    {
      return View(RoleManager.Roles);
    }

    //
    // GET: /Roles/Details/5
    public async Task Details(string id)
    {
      if (id == null)
      {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
      }
      var role = await RoleManager.FindByIdAsync(id);
      // Get the list of Users in this Role
      var users = new List<ApplicationUser>();

      // Get the list of Users in this Role
      foreach (var user in UserManager.Users.ToList())
      {
        if (await UserManager.IsInRoleAsync(user.Id, role.Name))
        {
          users.Add(user);
        }
      }

      ViewBag.Users = users;
      ViewBag.UserCount = users.Count();
      return View(role);
    }

    //
    // GET: /Roles/Create
    public ActionResult Create()
    {
      return View();
    }

    //
    // POST: /Roles/Create
    [HttpPost]
    public async Task Create(RoleViewModel roleViewModel)
    {
      if (ModelState.IsValid)
      {
        var role = new IdentityRole(roleViewModel.RoleName);
        var roleresult = await RoleManager.CreateAsync(role);
        if (!roleresult.Succeeded)
        {
          ModelState.AddModelError("", roleresult.Errors.First());
          return View();
        }
        return RedirectToAction("Index");
      }
      return View();
    }

    //
    // GET: /Roles/Edit/Admin
    public async Task Edit(string id)
    {
      if (id == null)
      {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
      }
      var role = await RoleManager.FindByIdAsync(id);
      if (role == null)
      {
        return HttpNotFound();
      }
      RoleViewModel roleModel = new RoleViewModel { Id = role.Id, RoleName = role.Name };
      return View(roleModel);
    }

    //
    // POST: /Roles/Edit/5
    [HttpPost]

    [ValidateAntiForgeryToken]
    public async Task Edit([Bind(Include = "Name,Id")] RoleViewModel roleModel)
    {
      if (ModelState.IsValid)
      {
        var role = await RoleManager.FindByIdAsync(roleModel.Id);
        role.Name = roleModel.RoleName;
        await RoleManager.UpdateAsync(role);
        return RedirectToAction("Index");
      }
      return View();
    }

    //
    // GET: /Roles/Delete/5
    public async Task Delete(string id)
    {
      if (id == null)
      {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
      }
      var role = await RoleManager.FindByIdAsync(id);
      if (role == null)
      {
        return HttpNotFound();
      }
      return View(role);
    }

    //
    // POST: /Roles/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public async Task DeleteConfirmed(string id, string deleteUser)
    {
      if (ModelState.IsValid)
      {
        if (id == null)
        {
          return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        var role = await RoleManager.FindByIdAsync(id);
        if (role == null)
        {
          return HttpNotFound();
        }
        IdentityResult result;
        if (deleteUser != null)
        {
          result = await RoleManager.DeleteAsync(role);
        }
        else
        {
          result = await RoleManager.DeleteAsync(role);
        }
        if (!result.Succeeded)
        {
          ModelState.AddModelError("", result.Errors.First());
          return View();
        }
        return RedirectToAction("Index");
      }
      return View();
    }
  }
}

And views to match the controller:

Create:

@model TheFamilyPage.Models.RoleViewModel

@{
    ViewBag.Title = "Create";
}

<h2>Create.</h2>


@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Role.</h4>
        <hr />
        @Html.ValidationSummary(true)

        <div class="form-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Name, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Name)
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Edit:

@model TheFamilyPage.Models.RoleViewModel

@{
    ViewBag.Title = "Edit";
}

<h2>Edit.</h2>


@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Roles.</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.Id)

        <div class="form-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Name, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Name)
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Delete:

@model Microsoft.AspNet.Identity.EntityFramework.IdentityRole

@{
    ViewBag.Title = "Delete";
}

Delete.

Are you sure you want to delete

this Role? </h3>
<p>Deleting this Role will remove all users from this role. It will notdelete the users.

 

Role.


class="dl-horizontal">
            @Html.DisplayNameFor(model => model.Name)
        
            @Html.DisplayFor(model => model.Name)
        
    <!--dl>
    @using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()

        <div class="form-actions no-color">
            <input type="submit" value="Delete" class="btn btn-default" /> |
            @Html.ActionLink("Back to List", "Index")
        </div>
    }
</div>

Index:

@model IEnumerable<Microsoft.AspNet.Identity.EntityFramework.IdentityRole>

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>

        </th>
    </tr>

    @foreach (var item in Model)
    {
        
            
                @Html.DisplayFor(modelItem => item.Name)
            
            
                @Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
                @Html.ActionLink("Details", "Details", new { id = item.Id }) |
                @Html.ActionLink("Delete", "Delete", new { id = item.Id })
            </td>
        </tr>
    }

</table>

Details:

@model Microsoft.AspNet.Identity.EntityFramework.IdentityRole

@{
    ViewBag.Title = "Details";
}

<h2>Details.</h2>

<div>
    <h4>Roles.</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.Name)
        </dt>
        <dd>
            @Html.DisplayFor(model => model.Name)
        </dd>
    </dl>
</div>
<h4>List of users in this role</h4>
@if (ViewBag.UserCount == 0)
{
    <hr />
    <p>No users found in this role.</p>
}

<table class="table">

    @foreach (var item in ViewBag.Users)
    {
        <tr>
            <td>
                @item.UserName
            </td>
        </tr>
    }
</table>
<p>
    @Html.ActionLink("Edit", "Edit", new { id = Model.Id }) |
    @Html.ActionLink("Back to List", "Index")
</p>



All of the above is taken direktly from the Identity sample application. This is NOT code I can take credit for.

Create user management

User management is completely analogue to role management –  I will not show the code here, but it is available on GitHub.

Remove anonymous registration and enable managament

In order to enable access to the users manager and roles manger, we add links to these pages in the _laout.cshtml file – so add the yellow lines:

@using System.Net.Mime
<!--DOCTYPE html>


    "utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")

</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("The Family Page", "Index", "Home", null, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                  @if (Request.IsAuthenticated && User.IsInRole("Admin"))
                  {
                    <li>@Html.ActionLink("Manage Roles", "Index", "RolesManager")</li>
                    <li>@Html.ActionLink("Manage Users", "Index", "UsersManager")</li>
                  }
                <!--ul>
                @Html.Partial("_LoginPartial")
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()

© @DateTime.Now.Year – My ASP.NET MediaTypeNames.Application

    

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>

Then remove the possibility for anybody to register themselves – so remove the yellow line:

using Microsoft.AspNet.Identity
@if (Request.IsAuthenticated)
{
    using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
    {
    @Html.AntiForgeryToken()

    <ul class="nav navbar-nav navbar-right">
        <li>
            @Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Manage", "Account", routeValues: null, htmlAttributes: new { title = "Manage" })
        </li>
        <li><a href="javascript:document.getElementById('logoutForm').submit()">Log off</a></li>
    </ul>
    }
}
else
{
    <ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink("Register", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
        <li>@Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
    </ul>
}

As always the complete code can be found here: https://github.com/iCodeIT/TheFamilyPage

 

Next time

Next time it is time to talk about content – the wish lists.

Posted in ASP.NET identity, C#, HTML5, Web | Tagged , , , , | 6 Comments

Creating The Family Page – Part 5, User Account–Password reset

This will be a somewhat different post than I had anticipated. I turns out that the ASP.NET Identy module in VS2013 Update 1 and below does not support password reset.  In this Stack Overflow question it is stated that the they are working on it, and some workarounds are suggested. Feel free to follow any of these if needed.

I updated the ASP.NET Identity module to version 2.0 beta. This will be in VS2013 update 2.0 which will be released in a moment. I started of by trying out the new identy module in a new MVC project, so I know what to expect.

Afterwards I simply used a diff-tool (I prefer Beyond Compare) and moved the new Identity code into my project.  The new code comes with password reset out of the box, so I really got nothing to write about here.

Also the new code already supports having an email-address. Actually the email address is used as username also.

I have committed the current state of the project to github: https://github.com/iCodeIT/TheFamilyPage

 

Since this comes out of the box, I don’t feel I have to describe it further here.

Next time we will talk about user account-roles.

Posted in Ikke kategoriseret | Leave a comment

Creating The Family Page – Part 4, User Accounts–Cleaning up

This will be a short and a bit boring post – it is about cleaning up. Last time we expanded on the IdentyUser Object. I also told that we are not going to use the external log in code (that is the posibility to log in using Facebook, Twitter, Google etc.) The MVC template contains some code for external log in, even though it is not enabled by default. We are going to remove that code, to give us a more clean code base before we make further modifications to account handling.

In the Views folder we can remove the following files:

  • Account/_ExternalLoginListPartial.cshtml
  • Account/_RemoveAccountPartial.cshtml
  • Account/ExternalLoginConfirmation.cshtml
  • Account/ExternalLoginFailure.cshtml

In Account/Manage.cshtml the following div can be removed:

<section id=“externalLogins”> @Html.Action(“RemoveAccountList”) @Html.Partial(“_ExternalLoginsListPartial”, new { Action = “LinkLogin”, ReturnUrl = ViewBag.ReturnUrl })

 

 

In Account/Login.cshtml the followinf div can be removed:

<div class="col-md-4">
    <section id="socialLoginForm">
        @Html.Partial("_ExternalLoginsListPartial", new { Action = "ExternalLogin", ReturnUrl = ViewBag.ReturnUrl })
    </section>
</div>

 

In the Controllers/AccountController.cs file there are a lot methods that is used for handling the connection between social logins and a user account. These methods can be removed (listed in the other they are found the code):

  • Disassociate
  • ExternalLogin
  • ExternalLoginCallback
  • LinkLogin
  • LinkLoginCallback
  • ExternalLoginConfirmation
  • ExternalLoginFailure
  • RemoveAccountList

Remove the field “XsrfKey” that is used by the class “ChallengeResult”. Then remove that class also.

In the Manage method remove this line:

: message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."

In the method SignInAsync remove this line:

AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);

Lastly we need to look at Models/AccountViewModel.cs – Here we can remove “all” classes related to social log ins:

Remove the ExternalLoginConfirmationViewModel class.

The last thing to do is to look at App_Start/Startup.Auth.cs. Remove all the comments regarding third party login providers. And remove this line:

app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

 

Conclusion

If you have done everything right you should be able to build and run the web page without errors.

Next time we will look at how to handle forgotten passwords.

Posted in C#, HTML5, Web | Tagged , , | Leave a comment

Creating the Family Page–Part 3, User Accounts–expanding the IdentyUser

The next couple of posts we will talk about user accounts. This time we will look at expanding the information stored on the user account.

First we will take a look at how user account data is stored. As mentioned earlier, the standard setup uses localdb (this simply means that the database is kept in a simple file, that can be uploaded to any web hotel if so desired).

The database file does not exist until you put something in it. To do so, run the application and register a user with username and password. Then stop the application. In the solution explorer, click on App_Data, and then click on the icon “Show All Files”.

image

Double-click the database file to open the file in the server explorer.

image

As can be seen, I have created a user with the username ‘Michael’ and the password is not stored in clear text. This is the only one of the tables, that are used in the standard setup. The other tables are used if you want to assign individual roles to the users, or you want to let the user be able to log in with an external authentication provider (like Google, Twiiter or Facebook).

For this simple page we will not need other means to log in. We will need roles, and that is the subject of one of the next posts.

We will need to expand the user data a little. Besides user name and password, we will also need real name and e-mail address. This is data that is reasonable to store along with the login information. If you also want to store more data about the user (like address, homepage, spouse or whatever) you should do it in another table and handle that someplace else.

The work for today:

  • Enable Entity Framework code first migrations
  • Expand the IdentityUser
  • Update the views to utilize the extra information.

Enable Entity Framework code first migrations

First lets talk about architecture: The template Microsoft supplies with Visual Studio 2013 is meant to help with a quick start on your project. There is no separate DAL project. If you want to, you can create a new project and install Entity Framework in that project instead and move the data files from the main project. (To install EntityFramework in another project run Install-Package EntityFramework -ProjectName from the PackageManager console)

For this simple web page we will keep it all in one project.

In order to enable migrations enter “Enable-Migrations” in the PackageManager console:

image

This results in some new files in the solution explorer:

image

This will enable EntityFramework to create a new database with the same tables that we just looked at in the start of this post.

Under models we have the file IdentityModels.cs with the following content:

using Microsoft.AspNet.Identity.EntityFramework;

namespace TheFamilyPage.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
    }

    public class ApplicationDbContext : IdentityDbContext
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
        }
    }
}

 

We can see that the ApplicationUser has an empty inheritance from the IdentityUser, which just gives us a user with username and password.

The IdentityUser is implemented like this:

 

namespace Microsoft.AspNet.Identity.EntityFramework
{
  public class IdentityUser : IUser
  {
    public virtual string Id { get; set; }

    public virtual string UserName { get; set; }

    public virtual string PasswordHash { get; set; }

    public virtual string SecurityStamp { get; set; }

    public virtual ICollection<IdentityUserRole> Roles { get; private set; }

    public virtual ICollection<IdentityUserClaim> Claims { get; private set; }

    public virtual ICollection<IdentityUserLogin> Logins { get; private set; }

    public IdentityUser()
    {
      this.Id = Guid.NewGuid().ToString();
      this.Claims = (ICollection<IdentityUserClaim>) new List<IdentityUserClaim>();
      this.Roles = (ICollection<IdentityUserRole>) new List<IdentityUserRole>();
      this.Logins = (ICollection<IdentityUserLogin>) new List<IdentityUserLogin>();
    }

    public IdentityUser(string userName)
      : this()
    {
      this.UserName = userName;
    }
  }
}

 

Pretty simple – now we want to expand on that.

Expand the IdentityUser

In order to expand the IdentityUser we add some extra fields to the ApplicationUser class.

    public class ApplicationUser : IdentityUser
    {
      public string Name { get; set; }
      public string Email { get; set; }
    }

 

Now our application user also has a Name and an Email. now we need to add those fields to the database also. In the PackageManger Console write “Add-Migration UserData”

This creates some new migration code:

image

The file contains code to update the database, but also to roll back the database if needed:

namespace TheFamilyPage.Migrations
{
    using System;
    using System.Data.Entity.Migrations;
    
    public partial class UserData : DbMigration
    {
        public override void Up()
        {
            AddColumn("dbo.AspNetUsers", "Name", c => c.String());
            AddColumn("dbo.AspNetUsers", "Email", c => c.String());
        }
        
        public override void Down()
        {
            DropColumn("dbo.AspNetUsers", "Email");
            DropColumn("dbo.AspNetUsers", "Name");
        }
    }
}

In order to do the actual update of the database, type “Update-Database” in the PackageManager console.

Looking in the AspNetUsers table in the database we can se that the new fields now exist in the database:

image

Next we need to use the new data:

Update the views

First we need to update the Register AccountViewModel with our new data:

public class RegisterViewModel
  {
    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }
  }

 

We also need to change the AccountControllers Register action:

[HttpPost] 
[AllowAnonymous] 
[ValidateAntiForgeryToken] 
public async Task Register(RegisterViewModel model) 
{ 
  if (ModelState.IsValid) 
  { 
    var user = new ApplicationUser() 
    { 
      UserName = model.UserName,
      Name = model.Name, Email = model.Email
    }; 
    var result = await UserManager.CreateAsync(user, model.Password); 
    if (result.Succeeded) 
    { 
      await SignInAsync(user, isPersistent: false); 
      return RedirectToAction("Index", "Home"); 
    } 
    else 
    { 
      AddErrors(result); 
    } 
  } 
  // If we got this far, something failed, redisplay form 
  return View(model); 
}

 

We also add Name and Email to the RegisterView

@using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    <hr />
    @Html.ValidationSummary()
    <div class="form-group">
        @Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.UserName, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Name, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>
}

Now I try to run the page and register a new user. When I look at the data table I can see  that the name and e-mail is stored for my new user.

image

I can also se that my first registered user doesn’t have name or e-mail set. I have no way to remedy that yet.

Conclusion

We now have the ability to register a user with name and e-mail, and store the result in the database. We still want to add a couple of things to our account handling:

  • We want to be able to change our name and e-mail.
  • We want to clean up the views and controllers, so we don’t have code we don’t need.
  • We need to handle that users might forget their password
  • We need to add Roles, for an administrator, an editor (that also can edit other users wish-lists) and a normal user, who can only edit his/her own data.

So that is the plan for the next couple of posts.

Posted in C#, HTML5, Web | Tagged , , , | Leave a comment