Using Cloud Datastore with .NET

This part of the .NET Bookshelf tutorial shows how to create, read, update, and delete structured data in Google Cloud Datastore.

This page is part of a multi-page tutorial. To start from the beginning and see instructions for setting up, go to .NET Bookshelf App.

Configuring settings

  1. In the getting-started-dotnet\aspnet\2-structured-data directory, double click 2-structured-data.sln to open the sample app in Visual Studio.

  2. In the Solution Explorer pane, click Web.config.

  3. In Web.config:

    • Set GoogleCloudSamples:ProjectId to your project ID.

    • Set GoogleCloudSamples:BookStore to datastore.

  4. Save and close Web.config.

Running the app on your local machine

In Visual Studio, press F5 to run the project.

Now you can browse the app's web pages and add, edit, and delete books.

Deploying the app to Compute Engine

  1. In Visual Studio, in the Solution Explorer pane, right click 2-structured-data, and choose Publish. Publish application

  2. In the Publish Web dialog, select Custom as your publish target.

  3. In the New Custom Profile dialog, for the profile name, enter bookshelf-profile. Click OK.

  4. Fill out your profile:

    • For Server, enter the external IP address of your Compute Engine instance.

    • For Site name, enter Default Web Site.

    • For User name, enter the username of the Windows user account you created on your Compute Engine instance.

    • For Password, enter the password of the Windows user account you created on your Compute Engine instance.

    • For Destination URL, enter this URL:

      http://[EXTERNAL_IP_ADDRESS_OF_YOUR_COMPUTE_ENGINE_INSTANCE]

  5. Click Validate Connection to ensure that the properties are correct.

  6. Because the Microsoft IIS installation in your deployment uses a self-signed certificate by default, you'll see a Certificate Error during the validation process. Check the box to Save this certificate for future sessions of Visual Studio, and click Accept to accept the certificate.

  7. If your configuration is valid, click Settings. Click File Publish Options, and check Remove additional files at destination. This is important for later steps when you publish new web sites to the same Compute Engine instance.

  8. Click Publish to deploy the sample web application. After publishing completes, Visual Studio opens the application in your default web browser:

Application structure

This diagram shows the app's components and how they fit together. It follows the classic ASP.NET MVC pattern. An IBookStore interface lives between the BooksController and DatastoreBookStore so that we can switch to storing book data in Google Cloud SQL without changing any code.

Bookshelf application structure

Understanding the code

This section walks you through the application code and explains how it works.

The data model

The Book class keeps information about one book. It contains some additional fields that are used in later tutorials.

    [Bind(Include = "Title, Author, PublishedDate, Description")]
    public class Book
    {
        [Key]
        public long Id { get; set; }

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

        public string Author { get; set; }

        [Display(Name = "Date Published")]
        [DataType(DataType.Date)]
        public DateTime? PublishedDate { get; set; }

        public string ImageUrl { get; set; }

        [DataType(DataType.MultilineText)]
        public string Description { get; set; }

        public string CreatedById { get; set; }
    }

Handling user submissions with forms

The add/edit HTML form allows users to add and edit book submissions within the app.

Image of add/edit Form

The HTML form is created using Razor, templates. This Razor template specifies that the form include text input fields for Title, Author, Date Published and Description:

<form action="/Books/@Model.FormAction/@Model.Book.Id" method="post" id="book-form" enctype="multipart/form-data">
    @Html.AntiForgeryToken()
    <div class="form-group">
        @Html.LabelFor(model => model.Book.Title)
        @Html.EditorFor(model => model.Book.Title, new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(model => model.Book.Title, "", new { @class = "text-danger" })
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Book.Author)
        @Html.EditorFor(model => model.Book.Author, new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(model => model.Book.Author, "", new { @class = "text-danger" })
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Book.PublishedDate)
        @Html.EditorFor(model => model.Book.PublishedDate, new { htmlAttributes = new { @class = "form-control", @type = "text" } })
        @Html.ValidationMessageFor(model => model.Book.PublishedDate, "", new { @class = "text-danger" })
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Book.Description)
        @Html.EditorFor(model => model.Book.Description, new { htmlAttributes = new { @class = "form-control", @type = "text" } })
        @Html.ValidationMessageFor(model => model.Book.Description, "", new { @class = "text-danger" })
    </div>

    <button type="submit" class="btn btn-success">Save</button>
</form>

Handling form submissions

When a user clicks Add Book, the BooksController.Create() method displays the form. After the user fills in the form clicks Save, the BooksController.Create() method receives the form's contents and sends them to the Cloud SQL Database via the IBookStore::Create() method. Note that the Create method is annotated with HttpPost.

        // GET: Books/Create
        public ActionResult Create()
        {
            return ViewForm("Create", "Create");
        }

        // POST: Books/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Book book)
        {
            if (ModelState.IsValid)
            {
                _store.Create(book);
                return RedirectToAction("Details", new { id = book.Id });
            }
            return ViewForm("Create", "Create", book);
        }

The DatastoreBookStore class calls the Cloud Datastore API to perform queries and to perform Create, Read, Update and Delete (CRUD) operations.

Here is the Create method:

public void Create(Book book)
{
    var entity = book.ToEntity();
    entity.Key = _db.CreateKeyFactory("Book").CreateIncompleteKey();
    var keys = _db.Insert(new[] { entity });
    book.Id = keys.First().Path.First().Id;
}

First, Create calls CreateIncompleteKey which tells Cloud Datastore to create a new key for the new entity. Then, it calls Insert to insert the new book into Cloud Datastore. Finally, it updates the book's ID with the new key created by Cloud Datastore. The IDs are not created in sequential order.

The DatastoreBookStoreExtensionMethods class has helper methods to make packing a Book into Cloud Datastore easier. The ToEntity helper method converts a Book to a Datastore entity.

public static Entity ToEntity(this Book book) => new Entity()
{
    Key = book.Id.ToKey(),
    ["Title"] = book.Title,
    ["Author"] = book.Author,
    ["PublishedDate"] = book.PublishedDate?.ToUniversalTime(),
    ["ImageUrl"] = book.ImageUrl,
    ["Description"] = book.Description,
    ["CreateById"] = book.CreatedById
};

See all the helper methods on GitHub.

Listing books

After users have added books, clicking the Books link navigates to the /Books page, which lists all the books currently stored in Cloud Datastore. The List method does the work of listing all the books by using data retrieved from Cloud Datastore.

public BookList List(int pageSize, string nextPageToken)
{
    var query = new Query("Book") { Limit = pageSize };
    if (!string.IsNullOrWhiteSpace(nextPageToken))
        query.StartCursor = ByteString.FromBase64(nextPageToken);
    var results = _db.RunQuery(query);
    return new BookList()
    {
        Books = results.Entities.Select(entity => entity.ToBook()),
        NextPageToken = results.Entities.Count == query.Limit ?
            results.EndCursor.ToBase64() : null
    };
}

The List method creates and runs a Query to find all entities of kind Book. It uses Cloud Datastore's query cursors to implement paging.

Send feedback about...