Friday, April 13, 2012

Viewing And Creating Bands

I'm back. Now for my first controllers and views...
I created a controller called HomeController and filled in the "Index" action as follows:


        public ActionResult Index()
        {
            var session = MvcApplication.documentStore.OpenSession();


            var bands = (from b in session.Query<Models.Band>()
                         orderby b.Name
                         select b).ToList();


            return View(bands);
        }


The "session" variable should look familiar. The LINQ sorts all the Band objects (documents) by band name. You would normally use the Query method to search multiple documents when using Raven. 

Here is the Razor code for that action's view:


@model List<MvcRavenCDApp1.Models.Band>


@{
    ViewBag.Title = "Index";
}


<h2>Index</h2>
<p>Click on a band name to see that band's CDs on file...</p>
<ul>
@foreach (var band in Model)
{
    <li><a href="../Band/Index/@band.Id">@band.Name (@(band.HasDescription ? band.Description : "no descr.")) </a></li>
}
</ul>


<p>@Html.ActionLink("Add a band", "../Band/Add")</p>


Here's what I saw when I first ran the program...


As you can see, there was no data, as I hadn't added any bands. Now you can see why I use booleans like "HasDescription". In order to add bands, I had to create an input form. The first step was to create BandController. Then, I created the constructor and "Add" action as follows:

        private IDocumentSession session;

        public BandController()
        {
            session = MvcApplication.documentStore.OpenSession();
        }
...

        [HttpGet]
        public ActionResult Add()
        {
            return View();
        }

        [HttpPost]
        public RedirectResult Add(Models.Band band)
        {
            band.Albums = new List<Models.Album>();
            if (String.IsNullOrWhiteSpace(band.Description))
            {
                band.HasDescription = false;
            }
            else
            {
                band.HasDescription = true;
            }
            session.Store(band);
            session.SaveChanges();
            return Redirect("/Home/Index");
        }

That view's code was the following:

@model MvcRavenCDApp1.Models.Band

@{
    ViewBag.Title = "Add";
}

<h2>Add A Band</h2>
@using (Html.BeginForm())
{
    <p>Band/Artist Name: @Html.TextBoxFor(x => x.Name, new { @required = "true" })</p>
    <p>Genre/Description (optional): @Html.TextBoxFor(x => x.Description)</p>
    <p><input type="submit" name="Submit" value="Add Band" /></p>
}



Note that my code makes use of the HTML 5 "required" attribute. Once I added a couple bands, the home page looked like:


A Band JSON document looked like:

If you look back, you can see that my code works around the band description being optional.  You'll notice that the value of the "Albums" key is an empty array. We will be entering albums in the next post. 





Wednesday, April 11, 2012

The Model

The next step was to create the domain model. The basic structure was simple: Band  > Album > Track. I decided on a bottom-up approach. The Track class contained the following at first:


 public class Track
    {
        public int TrackNumber { get; set; }
        public string Name { get; set; }


        public static int CalculateTrackNumber(string  bandID, string albumID)
        {
            // finish
            throw new NotImplementedException();
        }
    }


I created that function as a stub because it relied on classes than didn't exist yet. The next class to create was the Album class:


public class Album
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public int Year { get; set; }
        public int CDNumber { get; set; }
        public List<Track> Tracks { get; set; }

        public static string GenerateID(string bandID)
        {
            throw new NotImplementedException();
        }
    }

The top layer of the model was the following:



    public class Band
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public List<Album> Albums { get; set; }
        public bool HasDescription { get; set; }
    }

As you can see, the Band class had no GenerateID function. That was because Raven was going to create the document IDs for me. Now I could fill in the stubs, first for the Album class...


       public static string GenerateID(string bandID)
        {
            // throw new NotImplementedException();
            var session = MvcApplication.documentStore.OpenSession();
            var band = session.Load<Band>(bandID);


            return "albums-" + (band.Albums.Count + 1).ToString();
        }


The session gave the method access to the database's documents. The next line retrieved the Band document whose ID matched bandID. The other method body was similar...



        public static int CalculateTrackNumber(string  bandID, string albumID)
        {
            // finish
            // throw new NotImplementedException();
            var session = MvcApplication.documentStore.OpenSession();
            var band = session.Load<Band>(bandID);


            var album = (from a in band.Albums
                         where a.Id == albumID
                         select a).First();


            return (album.Tracks.Count + 1); 
        }


Now hopefully you can see the method to my madness (pardon the pun). A Band object contains a List of albums, and each album contains a list of tracks. The next post will start the real fun... the controllers.









Tuesday, April 10, 2012

More Housekeeping

Welcome back. Before I started creating domain classes, I set up document storage for my application. That meant going back to global.asax.cs and making more changes. 


The first thing to do was adding a couple namespaces to the "using" section, namely Raven.Client and  Raven.Client.Embedded. Next came creating a class variable, like the following...

public static IDocumentStore documentStore;

The last step was to instantiate documentStore and initialize it in Application_Start...


 documentStore = new EmbeddableDocumentStore { ConnectionStringName = "RavenDB", UseEmbeddedHttpServer = true };
            documentStore.Conventions.IdentityPartsSeparator = "-"; // so Raven IDs are web-friendly
            documentStore.Initialize();


The boolean member of the constructor tells C# to enable Raven Studio, the Silverlight database administration system. IdentityPartsSeparator had to be set because Raven puts a "/" character in generated document IDs by default. You basically need two things to access Raven in code, namely an initialized document store and an "opened" IDocumentSession (more on the latter later).

In the next session, we'll set up the Model.



The Preliminaries

This is Ross Albertson, and welcome to my blog. This series will discuss how I created an ASP.NET MVC 3 application using the RavenDB NoSQL database manager.

Well, for starters, I created an MVC 3 project in Visual Studio. I selected using HTML 5 markup and the Razor view engine. Before writing any code, I used the NuGet package manager to install the embedded version of Raven, using the command

install-package ravendb-embedded

That gave me Embedded Raven 1.0.888. When you try this, you might get a different version. The next step was to copy Raven.Studio.xap to the project directory. This enables the Silverlight part of the Raven server to run on the browser.

I also had to create a directory for the data. So, I made a folder called "Database" in the "Content" folder. I also had to direct Raven to that folder. To do that, I commented out the connection string entry in web.config that NuGet generated, and replaced it with

<add name="RavenDB" connectionString="DataDir=~\Content\Database"/>


The last bit of configuring I did was to change the default URL routing in global.asax.cs to



 routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{band_id}/{album_id}", // URL with parameters
                new { controller = "Home", action = "Index", band_id = UrlParameter.Optional, album_id = UrlParameter.Optional } // Parameter defaults
            );


That change enabled me to pass 2 IDs to my controllers. You'll see the reasoning behind that later. We'll be returning to that code file in the next installment.