Tuesday, 26 May 2015

ASP.NET with a REST API: Part 1


Pyrrho's new REST API works well with ASP.NET. This sample was originally implemented using LocalDB/SQL Server and the Entity Framework. Pyrrho has some features that are much easier to use, as this sample shows. 
Visual Studio uses its own IIS, and for this sample the Pyrrho server needs to be running, with its HTTP service and Pyrrho service. It is not a simple matter to copy or deploy ASP.NET applications, and there are several web sites that discuss how to do so. To make it easier to deploy, you could use EmbeddedPyrrho and the real IIS from the start, but then you have to add more configuration steps during development.
We are still using SQL Server for the ASP.NET account control. You may find yourself accessing the DefaultConnection (Haikus) through Visual Studio’s Server Explorer to remove entries from AspNetUsers.
We will not use the Entity Framework. In my arrogant opinion, client side DataSets and data models are really bad for any form of concurrency and transaction management, and in this respect are a serious error in both the Microsoft and Java world. Instead I invite readers to examine direct use of an database Connection as in step 10 below, to Post (step 12) a strongly-typed class whose structure has been obtained from the DBMS. There are Get, Put and Delete too of course, and this sample will show their use. Put is in step 20, and Get and Delete will be in the next part of this posting. Post automatically supplies any missing components for a new primary key. We will use BLOBs for icons and author pictures.
1. Start up VS Express 2013 as administrator, and from the start Page, select New project. Make sure Visual C# and Web is selected. Give the Name as Haikus, and make sure the location is somewhere suitable such as C:\Temp.Click OK.


2. Select MVC, and keep the the other defaults. Click OK
3. After a few minutes, you get an empty project. Try Run. A browser starts up to access your MVC app running on your local machine. This is the default MVC 5 application. It has many features, but don’t touch them just now. Close the browser window.


Build the underlying database

5. Make sure the Pyrrho server OSP.exe is running, we assume on localhost with the default port 5433.

From a command prompt, start up the PyrrhoCmd utility for the database haikus: PyrrhoCmd haikus and enter the commands shown to create the database tables we will use.
The square brackets here help with line wraparound.
[create table author(id char primary key,name char,pic blob)]

[create table verse(id int primary key,ownr char references author,cont char)]
[create table tag(id int primary key,name char)]
[create table rating(vid int references verse,uid char references author,score int)]
[create table tagging(tid int references tag,vid int references verse)]
[create table comment(id int primary key,vid int references verse,aid char references author, cont char)]
[create table likes(vid int references verse,uid char references author)]
6. Still at the SQL prompt, use the command: table “Role$Class”, to get class definitions for our application.



“Your mileage may vary”: the Schema keys may be different for you. They are used in Pyrrho’s REST API to check that the class definition remains valid in the database and role. 
In the command prompt, right click and Select All, then Enter to copy the text to the clipboard, and paste it into a notepad.
We will use each of the class definitions to define classes AUTHOR, VERSE etc. in the next step.

7. Back in Visual Studio, in Solution Explorer, right-click References, Add Reference.. and Browse.. to add OSPLink.dll from the Pyrrho distribution. Click Add.

8. In Solution Explorer, right-click Models select Add/New Item../Visual C#/Code/Class, change the Name to AUTHOR.cs and click Add.

9. In the AUTHOR class, paste in the class definition from AUTHOR that you obtained in step 6.

Similarly add Model classes to your project for VERSE, TAG, RATING, TAGGING, COMMENT and LIKES. Click the Save All icon in Visual Studio.
10. Add another Models class called Connect and edit it to look like this.
using System;

namespace Haikus.Models
{
    using Pyrrho;

    public class Connect
    {
        public static PyrrhoConnect conn;
        internal Connect()
        {
            conn = new PyrrhoConnect("Files=haikus");
            conn.Open();
        }
    }

}
11. Open the Startup.cs file and add a call to make the connection to the database as shown
   public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            new Models.Connect();
            ConfigureAuth(app);
        }
    }

12. Now let’s start wiring this up. Any time that a user registers a new account, we want to record them in the Author table.

First add a Find method to the Author class, two curly braces in from the end of AUTHOR.cs
internal static AUTHOR Find(string us)
{
   var au = Connect.conn.FindOne<AUTHOR>(us);
   if (au != null)
        return au;
   au = new AUTHOR { ID = us };
   Connect.conn.Post(au);
   return au;
}
13. In Solution Explorer, double-click Controllers>AccountController.cs. Use the drop-down boxes to find the Register(RegisterViewModel model) method and add the line AUTHOR.Find(..) shown.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
   if (ModelState.IsValid)
   {     var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
      IdentityResult result = await UserManager.CreateAsync(user, model.Password);
      if (result.Succeeded)
      {
          await SignInAsync(user, isPersistent: false);
// For more information on how to enable account …..
// ...
          AUTHOR.Find(user.UserName);
           return RedirectToAction("Index", "Home");
       }
       else
       {
            AddErrors(result);
       }
    }
  // If we got this far, something failed, redisplay form
   return View(model);
}
14. Before we run the program, in Solution Explorer, left-click the project. In the Properties pane, ensure that WindowsAuthentication is Disabled and AnonymousAuthentication is Enabled.

Run the program, and register a user called fred@abc.com with password Pa$$w0rd.
Click Register and close the browser.

15. When you are returned to the home page, verify that you have a welcome message for your new user. Close the browser.  In the command prompt we had before, do “table author” to see that fred got defined by the code we added.

Author Profule Pictures

16. Let’s show pictures for logged-in users. We’ll add a URL for this in the AccountController. We’ll create a default image to use if the user hasn’t given a picture. In AccountController.cs add using System.Drawing; and using System.IO; Add this code two closing braces before the end of AccountController.cs:
[AllowAnonymous]
public ActionResult Picture(string id)
 {
   var au = AUTHOR.Find(id);
   var b = au.PIC;
   if (b==null)
   {
    var im = new Bitmap(24, 24);
    using (var g = Graphics.FromImage(im))
    {
      g.FillRectangle(Brushes.White, new Rectangle(0, 0, 24, 24));
      g.DrawEllipse(Pens.Black, new Rectangle(6, 6, 12, 12));
      g.DrawLine(Pens.Black, 2, 22, 7, 17);
      g.DrawLine(Pens.Black, 22, 22, 17, 17);
    }
    var ms = new MemoryStream();
    im.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
    b = ms.ToArray();
   }
   return File(b, "image/jpg");
}
17. Now replace the Hello greeting with the user's picture:
@using Microsoft.AspNet.Identity
@if (Request.IsAuthenticated)
{
    using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
    {
        var id = User.Identity.Name;
    @Html.AntiForgeryToken()

    <ul class="nav navbar-nav navbar-right">
        <li>
 <a href="@Url.Action("Index","Manage")"> <img title="@id" alt="@id" src="@("/Account/Picture/'" + id + "'/")" />  @id</a>
        </li>
18. Now let’s start to provide a way for authors to add their profile picture. In Views/Manage/Index.cshtml add the following code somewhere sensible:
<legend>Choose a profile picture:</legend>
@using (Html.BeginForm("Profile", "Account", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    <input type="file" name="file" />
    <input type="submit" value="Upload Picture" />
19.  We need to handle the postback.

Add the following code two closing braces in from the end of AccountController:
// This action handles the form POST and the upload
///

/// A user has just updated their profile picture
///

///
///
[HttpPost]
public new ActionResult Profile(HttpPostedFileBase file)
{
    var del = new System.Drawing.Image.GetThumbnailImageAbort(dummy);
    IntPtr p = new IntPtr();
    // Verify that the user selected a file
    if (file != null && file.ContentLength > 0) 
    {
        var sr = file.InputStream;
        var im = System.Drawing.Image.FromStream(sr);
        if (im.Width>24 || im.Height>24)
            im = im.GetThumbnailImage(24, 24, del, p);
        var ms = new MemoryStream();
            im.Save(ms,System.Drawing.Imaging.ImageFormat.Jpeg);
            Haikus.Models.AUTHOR.SetPicture(User.Identity.Name,ms.ToArray());
     }
     // redirect back to the index action to show the form once again
     return RedirectToAction("../Home/Index");        
 }
 // Kludge for GetThumbnail
 bool dummy()
 {
     return false;
 }20. Finally define the SetPicture method in the AUTHOR.cs class:
internal static void SetPicture(string user, byte[] pic)
{
    var au = Find(user);
    au.PIC = pic;
Connect.conn.Put(au);
}
Next steps - in Part 2: Editing Haikus, Likes and Comments.



ASP.NET with a REST API: Part 2

Part 1 of this exercise is here.
In this second part we demonstrate the use of ASP.NET MVC's strongly-typed views.
1. Let’s write a partial view to display a haiku. Right-click Views/Shared, select Add/View..

Give the name of the View as HaikuPartial, select Empty as the template, and for the model class select VERSE from the dropdown. Make the Data context class blank. Check Create a partial View. Click Add.
2. Edit the HaikuPartial.cshtml as shown. Split is needed here to preserve the lines of the verses.
@model Haikus.Models.VERSE
@{
    var h = Model;
       <p>
       @foreach (var s in h.CONT.Split('\r'))
       {
           <label>@s</label><br />
       }
       </p>
}
 3. Let’s make the Home/Index page show the current list of Haikus (for simplicity we suppose there aren’t too many). Delete all the current content of Views/Home>/Index.cshtml and replace by the code shown.
@{
    ViewBag.Title = "Home Page";
}
<h2>@ViewBag.Title</h2>
@foreach (var h in Haikus.Models.Connect.conn.FindAllVERSE>())
{
       Html.RenderPartial("HaikuPartial", h);   
}
It would be better structure to have a public static method of VERSE of the form VERSE[] All() that returned Connect.conn.FindAll etc.
4.  We won’t see much unless there is at least one Haiku in the database. At the PyrrhoCmd haikus command prompt, give the command shown (or something similar).
[Insert into verse (ownr,cont) values('fred@abc.com','Cherry blossom falls like snow')]

It’s just one line for now – we will fix it later. Embedded newlines inside strings are a pain for command line tools such as PyrrhoCmd. Check this works, and close the browser.
5. Let’s display the author for each haiku.

Change the HaikuPartial.cshtml as follows:
@model Haikus.Models.VERSE
@{
    var h = Model;
    var id = h.OWNR;
    <p>
        @foreach (var s in h.CONT.Split('\r'))
       {
            <label>@s</label><br />
       }
        <img title="@id" alt="@id" src="@("/Account/Picture/'" + id + "'/")" />
    </p>
}
 6. Rebuild Solution. Now check our page works. (The picture is a little slow to appear)




Close the browser
7. Right-Click Views>Home and Add>View..  Give the name as NewHaikuPartial, select the Edit Template, select VERSE from the dropdown, leave the Data context class empty, and check the Create a Partial View checkbox.

Click Add, and edit the new page as follows:
@model Haikus.Models.VERSE

@using (Html.BeginForm("Haiku/","Home"))
{
    @Html.AntiForgeryToken()
   
    <div class="form-horizontal">
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="editor-field">
            @Html.TextAreaFor(model => model.CONT, new { rows = 3, cols = 80 })
            @Html.ValidationMessageFor(model => model.CONT)
        </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")
}
8. Now add this part to Home\Index.cshtml, of course the author must log in first.
   @if (Request.IsAuthenticated)
   {
       <h2>Contribute a Haiku:</h2>
       Html.RenderPartial("NewHaikuPartial");
   }
 9. Let’s add code to the Haiku model to add a Haiku, in VERSE.cs, after the declaration of CONT.
public static void Add(VERSE m)
{
  Connect.conn.Post(m);
}
10.  Finally, we need to deal with the POST of the new Haiku.

Add using Haikus.Models; at the top of HomeController.cs and this code two braces in from the end
///




/// A user has just posted a new Haiku for the collection
/// using the web form in NewHaikuPartial
///

///
///
[HttpPost]
public ActionResult Haiku(VERSE model)
{
   model.CONT = Request["CONT"]; // why is this needed?
   model.OWNR = User.Identity.Name;
   VERSE.Add(model);
   ModelState.Clear();
   return View("Index");
}
11.  Try out our new machinery: add some more authors and haikus.

The list of Authors

12. Next step is to give a list of authors to filter the haikus. Let’s create a section on the right of the page for this.

In Views>Shared>_Layout.cshtml, replace the content of
as shown:

@RenderSection("featured", required: false)
 <section class="content-wrapper main-content clear-fix">
    <section id="main" style="float:left;width:70%">
        @RenderBody()
    </section>
    <section class="right" style="float:right">
        @RenderSection("right",required:false)
     </section>
 </section>
13. Add the following to Views/Home/Index.cshtml (a good place is before the

tag.



You will also need @using Haikus.Models; at the top.
The list has just the distinct author names. We don’t want to use the Authors table in case an Author no longer has any Haikus.
@section right {
<h3>Authors</h3>
@{
   var lt = new List<string>();

   foreach (var v in Connect.conn.FindAll<VERSE>())
   {
      if (!lt.Contains(v.OWNR))
      {
         lt.Add(v.OWNR);
      }
   }
   foreach (var an in lt)
   {
     <img alt="@an" title="@an" src="@("/Account/Picture/'" + an + "'/")" />
   }
 }
}
14.  Let’s make the list of authors clickable, so that we can get to a page that just has haikus by a particular author.

Let’s add a new method to the HomeController for this.
public ActionResult Author(string id)
{
    ViewBag.Author = id;
    return View();
}
15.  And add a View to Views>Home called Author.

Make sure this time the Template is “Empty (without model)”
Also clear the partial view checkbox.
@using Haikus.Models
@{
    ViewBag.Title = "Filtered by Author";
    string id = ViewBag.Author;}
<h2>Haikus contributed by <img title="@id" alt="@id" src="@("/Account/Picture/'" + id + "'/")" /></h2>

@foreach (var h in Connect.conn.FindAll<VERSE>())
{
    if (h.OWNR == id)
    {
        Html.RenderPartial("HaikuPartial", h);
    }
}
16.  And make the entries in the list of Authors into links to show this new view. Change Views/Home/Index.cshtml to contain:
foreach (var an in lt)
{
    <a href="@("/Home/Author/" + an + "/")">
      <img alt="@an" title="@(an + "'s haikus")"
           src="@("/Account/Picture/" + an + "/")" />
    </a>
}
17.  Try this out:
18. Let’s add some icons to our database for adding editing to the site. We’ll cheat and add them as profile pictures for non-authors _Pencil etc.Right click and Save each of these as Picture: Delete, Details, Liked, NotLiked and Pencil.




a.       Register a new user called Joe@abc.com. Add Delete.jpg as Joe’s profile picture.   Have a command prompt alongside your browser window.running PyrrhoCmd haikus.     At the SQL> prompt, give the command

update author set ID ='_Delete' where ID='Joe@abc.com'
b.      Check with select ID from author

Close the browser when you’ve done them all.
19. Let’s begin by allowing an author to edit their own haiku. For convenience let’s add some stuff to VERSE.cs. Add a using clause at the top as shown, and the rest of the code shown to the VERSE class.

You can see the Delete method tidies up relationships when a haiku is deleted. (We could get the database to do this automatically.)
using System.Web.WebPages;
public static void Delete(int id)
{
    var c = Connect.conn;
    foreach (var t in c.FindWith<COMMENT>("VID="+id))
        c.Delete(t);
    foreach (var t in c.FindWith<TAGGING>("VID="+id))
        c.Delete(t);
     c.Delete(c.FindOne<VERSE>(id));
}
public bool owner(WebPageRenderingBase page)
{
   return page.Request.IsAuthenticated &&
      OWNR == page.User.Identity.Name;
}
 20. Add this code inside the Shared>HaikuPartial view in place of the
@if (h.owner(this))
{   
  <a href="@("/Home/EditHaiku/" + h.ID + "/")" >
    <img alt="Pencil" title="Edit"
        src="@("/Account/Picture/'_Pencil'/")" />
  </a>
  <a href="@("/Home/Delete/" + h.ID + "/")" >
    <img alt="Delete" title="Delete"    
        src="@("/Account/Picture/'_Delete'/")" />
  </a>
}
else
{
   <img title="@h.OWNR" alt="@h.OWNR" src="@("/Account/Picture/" + h.OWNR + "/")" />
}
 21. We need to add these methods to the HomeController.
public ActionResult Delete(int id)
{
   VERSE.Delete(id);
   return View("Index");
}

public ActionResult EditHaiku(int id)
{
    var h =  Connect.conn.FindOne<VERSE>(id);
    ViewBag.ContentsLegend = "Update your Haiku";
    return View("EditHaikuPartial",h);
}
22.  In Views>Home add a partial view called EditHaikuPartial,using the scaffolding for Edit and the VERSE model,

And add the TextAreaFor bits shown.
Rowversion is part of Pyrrho Versioned persistence machinery and is needed for Put.
@model Haikus.Models.VERSE

@{
ViewBag.Title = "EditHaiku";
    Model.CONT = Model.CONT.Replace("|", "\r\n");
}

<h2>EditHaiku</h2>

@using (Html.BeginForm("Update/", "Home"))
{
    @Html.AntiForgeryToken()
   
    <div class="form-horizontal">
        <h4>VERSE</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="editor-field">
            @Html.TextAreaFor(model => model.CONT, new { rows = 3, cols = 80 })
            @Html.ValidationMessageFor(model => model.CONT)
        </div>
        @Html.HiddenFor(model => model.ID);
        @Html.HiddenFor(model => model.rowversion);
        <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>
23.  We need a post method for the Save in HomeController.

I actually don’t know why we need to fetch things from Request[] ourselves here.
///



/// A user has just edited a Haiku in the collection
/// using the web form in EditHaikuPartial
///

///
///
[HttpPost]
public ActionResult Update(VERSE model)
{
    model.rowversion = long.Parse(Request["rowversion"]);
    model.ID = long.Parse(Request["ID"]);
    model.CONT = Request["CONT"];
model.OWNR = User.Identity.Name;
VERSE.Update(model);
    ModelState.Clear();
    return View("Index");
} 24. And an Update method in VERSE.cs
public static void Update(VERSE m)
{
     Connect.conn.Put(m);
}
25.  Try out the new machinery.

Haikus should be 2 or 3 lines and 14 or 21 syllables.
Close the browser.




Still to come in Part 3: Implementing Likes and Comments.