Build A New MusicStore Project Using NancyFX And PostgreSQL - Part Two

Introduction

In this article, you will learn how to  complete the album's information and leearn how to use the forms authentication to manage the albums.

Details of Album

First of all, let's begin with the details page. When we click one of the albums, we should enter the details page, such as the top selling albums in the index page. In the last article, we did not give the real link for the albums, just used JavaScript.

  1. <ul id="album-list">  
  2.     @foreach (var album in Model)  
  3.     {  
  4.         <li>  
  5.             <a href="javascript:;">  
  6.                 <img alt="@album.Title" src="@album.AlbumArtUrl" />  
  7.                 <span>@album.Title</span>  
  8.             </a>  
  9.         </li>  
  10.     }  
  11. </ul>   

Now, let's use the real link to replace JavaScript that is same as the MVC MusicStore.

  1. <a href="/store/details/@album.AlbumId">  
  2.           <img alt="@album.Title" src="@album.AlbumArtUrl" />  
  3.           <span>@album.Title</span>  
  4.   </a>   

The link means that we should visit the details page via a link which looks like http://domain.com/store/details/id.

We should add something new to the constructor of StoreModule, so that we can return the valid view of the user.

  1. Get["details/{id:int}"] = _ =>  
  2. {  
  3.     int id = 0;  
  4.     if (int.TryParse(_.id, out id))  
  5.     {  
  6.         string cmd = "public.get_album_details_by_aid";  
  7.         var album = DBHelper.QueryFirstOrDefault<AlbumDetailsViewModel>(cmd, new  
  8.         {  
  9.             aid = id  
  10.         }, nullnull, CommandType.StoredProcedure);  
  11.         if (album != null)  
  12.         {  
  13.             return View["Details", album];  
  14.         }  
  15.     }  
  16.     return View["Shared/Error"];  
  17. };   

Pay attention to the route of the details where we limit the parameter Id that can only be an integer. This is the Route Segment constraints of Nancy. We can use _.id to get its value, then visit the database to find out the first row of the data.

What about the view of the details page?

  1. @inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<NancyMusicStore.ViewModels.AlbumDetailsViewModel>      
  2. @{  
  3.     ViewBag.Title = "Album - " + Model.Title;     
  4. }      
  5. <h2>@Model.Title</h2>      
  6. <p>  
  7.     <img alt="@Model.Title" src="@Model.AlbumArtUrl" />  
  8. </p>      
  9. <div id="album-details">  
  10.     <p>  
  11.         <em>Genre:</em>  
  12.         @Model.GenreName  
  13.     </p>  
  14.     <p>  
  15.         <em>Artist:</em>  
  16.         @Model.ArtistName  
  17.     </p>  
  18.     <p>  
  19.         <em>Price:</em>  
  20.         @String.Format("{0:F}", Model.Price)  
  21.     </p>  
  22.     <p class="button">  
  23.         <a href="/ShoppingCart/AddToCart/@Model.AlbumId">Add to cart</a>          
  24.     </p>  
  25. </div>   

It's very simple. Just show little information of the album. Let's run the Application and click one of the top selling albums in the index page. Then, you can see something like the screenshot, as shown below.


Genres of Albums

In the layout page, we also do not use the real link to take place of JavaScript, so we should replace it at first.

  1. $("#categories").append('<li><a href="/store/browse/">' + res[i].name + '</a></li>');   

We also add something new to the constructor of StoreModule, so that we can browse albums of some genres.

  1. Get["browse/{genre}"] = _ =>  
  2. {  
  3.     string genre = _.genre;  
  4.     ViewBag.Genre = genre;      
  5.     string cmd = "public.get_album_list_by_gname";  
  6.     var albumList = DBHelper.Query<AlbumListViewModel>(cmd, new  
  7.     {  
  8.         gname = genre  
  9.     }, nulltruenull, CommandType.StoredProcedure).ToList();  
  10.     return View["Browse", albumList];  
  11. };   

The view of this HTTP request is very similar with the top selling albums. The difference between them is their number.

  1. @inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<List<NancyMusicStore.ViewModels.AlbumListViewModel>>      
  2.  @{  
  3.      ViewBag.Title = "Browse Albums";      
  4.  }  
  5.  <div class="genre">  
  6.      <h3><em>@ViewBag.Genre</em> Albums</h3>      
  7.      <ul id="album-list">  
  8.          @foreach (var album in Model)  
  9.          {  
  10.              <li>  
  11.                  <a href="/store/details/@album.AlbumId">  
  12.                     <img alt="@album.Title" src="@album.AlbumArtUrl" />  
  13.                     <span>@album.Title</span>  
  14.                  </a>  
  15.              </li>  
  16.          }  
  17.      </ul>  
  18.  </div>   

For example, here is the list view of Disco.


So far we have completed the details page and the list page. What we should do next is see how to manage the albums.

Management of The Albums

In MusicStore Project , the management of the albums is just CRUD. I will show you how to query and create in this article. As far as the update and delete goes, you can find them in the source code on my GitHub page.

Create a StoreManagerModule class in the Modules.

  1. using Nancy;  
  2. using Nancy.ModelBinding;  
  3. using Nancy.Security;  
  4. using NancyMusicStore.Common;  
  5. using NancyMusicStore.Models;  
  6. using NancyMusicStore.ViewModels;  
  7. using System;  
  8. using System.Data;  
  9.   
  10. namespace NancyMusicStore.Modules  
  11. {  
  12.     public class StoreManagerModule : NancyModule  
  13.     {  
  14.         public StoreManagerModule() : base("/storemanager")  
  15.         {  
  16.             Get["/"] = _ =>  
  17.             {  
  18.                 string cmd = "get_all_albums";  
  19.                 var list = DBHelper.Query<AlbumListViewModel>(cmd, nullnulltruenull, CommandType.StoredProcedure);  
  20.                 return View["Index", list];  
  21.             };  
  22.   
  23.             Get["/create"] = _ =>  
  24.             {  
  25.                 return View["Create"];  
  26.             };  
  27.               
  28.             Post["/create"] = _ =>  
  29.             {  
  30.                 var album = this.Bind<Album>();  
  31.   
  32.                 string cmd = "public.add_album";  
  33.                 var res = DBHelper.ExecuteScalar(cmd, new  
  34.                 {  
  35.                     gid = album.GenreId,  
  36.                     aid = album.AlbumId,  
  37.                     t = album.Title,  
  38.                     p = album.Price,  
  39.                     aurl = album.AlbumArtUrl  
  40.                 }, nullnull, CommandType.StoredProcedure);  
  41.   
  42.                 if (Convert.ToInt32(res) > 0)  
  43.                 {  
  44.                     return Response.AsRedirect("/storemanager");  
  45.                 }  
  46.                 ViewBag.Err = "Some Error Occurred";  
  47.                 return View["Create"];  
  48.             };  
  49.   
  50.             Get["/details/{id:int}"] = _ =>{};      
  51.             Get["/edit/{id:int}"] = _ =>{};      
  52.             Post["/edit"] = _ =>{};      
  53.             Get["/delete/{id:int}"] = _ =>{};                  
  54.             Post["/delete/{id:int}"] = _ =>{};      
  55.             Get["/getallgenres"] = _ =>{};  
  56.             Get["/getallartists"] = _ =>{};  
  57.         }  
  58.     }  
  59. }   

When we enter the index page of management, we will get a table of the albums. We query the result and return them in the view.

Here, the index page of the management is given below.


When we post a new album in the create view, the Post["/Create"] will deal with the request. We need to use another feature of Nancy - ModelBinding. The details of the album which we want to create will be bound to the parameter album. Now, we call the storeprocedure to create a new album in PostgreSQL.

Here, the create view is given below.


Now, the management is finished but everybody can update the details of the albums via the Admin link .


It's very unreasonable to think that everybody will be able to update the details of the albums. It should be updated by the user, who has already logged in. In MVC MusicStore, MemberShip is a good way to solve this problem. In NancyFX , Forms authentication is a good way as well.

Add a new appSetting in web.config to indicate the logon url.

  1. <add key="logonUrl" value="~/account/logon" />   

Configuring the Forms authentication in CustomerBootstrapper class is given below.

  1. protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)  
  2. {  
  3.     base.RequestStartup(container, pipelines, context);  
  4.     //form authentication  
  5.     var formsAuthConfiguration = new FormsAuthenticationConfiguration  
  6.     {  
  7.         RedirectUrl = ConfigHelper.GetAppSettingByKey("logonUrl"),  
  8.         UserMapper = container.Resolve<IUserMapper>(),  
  9.     };  
  10.     FormsAuthentication.Enable(pipelines, formsAuthConfiguration);  
  11. }  
  12.   
  13. protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)  
  14. {  
  15.     base.ConfigureRequestContainer(container, context);  
  16.     container.Register<IUserMapper, UserMapper>();  
  17. }   

We create an instance of formauthenticationconfiguration,  whichindicates the redirected URL and the mapper of the user first. Now, we use the FormsAuthentication.Enable to enable the authentication.

Customer UserMapper is very important for the authentication. The implementation of the UserMapper demonstrates, as shown below.

  1. internal class UserMapper : IUserMapper  
  2. {  
  3.     public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)  
  4.     {  
  5.         string cmd = "public.get_user_by_userid";  
  6.         var user = DBHelper.QueryFirstOrDefault<SysUser>(cmd, new  
  7.         {  
  8.             uid = identifier.ToString()  
  9.         }, nullnull, CommandType.StoredProcedure);      
  10.         return user == null  
  11.             ? null  
  12.             : new UserIdentity  
  13.             {  
  14.                 UserName = user.SysUserName,  
  15.                 Claims = new[] { "SystemUser" }  
  16.             };             
  17.     }  
  18. }   

We have completed the configuration of forms authentication and we must add the code given below in the constructor of StoreManagerModule class.

  1. this.RequiresAuthentication();   

It is a little different with the Authorize Attribute, when we use ASP.NET MVC.

Now, it will ask us to login.


We can see the returnUrl parameter in the URL. It will help us to redirect after someone logs in.

Create a new module class named AccountModule to deal with logon and registration.

  1. Get["/logon"] = _ =>  
  2. {  
  3.     var returnUrl = this.Request.Query["returnUrl"];  
  4.     ViewBag.returnUrl = returnUrl;  
  5.     return View["LogOn"];  
  6. };  
  7.   
  8. Post["/logon"] = _ =>  
  9. {  
  10.     var logonModel = this.Bind<LogOnModel>();  
  11.   
  12.     string cmd = "public.get_user_by_name_and_password";  
  13.     var user = DBHelper.QueryFirstOrDefault<SysUser>(cmd, new  
  14.     {  
  15.         uname = logonModel.SysUserName,  
  16.         upwd = logonModel.SysUserPassword  
  17.     }, nullnull, CommandType.StoredProcedure);  
  18.   
  19.     if (user == null)  
  20.     {  
  21.         return View["LogOn"];  
  22.     }  
  23.     else  
  24.     {  
  25.         MigrateShoppingCart(user.SysUserName);  
  26.   
  27.         var redirectUrl = string.IsNullOrWhiteSpace(logonModel.ReturnUrl) ? "/" : logonModel.ReturnUrl;  
  28.         return this.LoginAndRedirect(Guid.Parse(user.SysUserId), fallbackRedirectUrl: redirectUrl);  
  29.     }  
  30. };   

I stored the returnUrl in the ViewBag, when we open the login page. When someone posts a login request, the returnurl will be posted as a part of LogOnModel class and we use modelbinding again.

The details of LogOnModel are given below.

  1. public class LogOnModel  
  2. {  
  3.     public string SysUserName { get; set; }  
  4.     public string SysUserPassword { get; set; }  
  5.     public string ReturnUrl { get; set; }          
  6. }   

What Next?

In my next article, I will go to complete the shopping cart.