Marten & ASP.NET Core - Postgresql noSQL storage in ASP.NET Core

What is Marten and why do I care?

Marten (no relation) is an open source .NET driver for Postgresql that focuses on Postgres’ JSON-based document storage capabilities. Think of it like an alternative to MongoDB or RavenDB for your .NET applications.

Getting started

This article is going to walk you through the first steps of getting an ASP.NET Core project up and running with Marten, as well as basic querying. This should work on all operating systems.

The first thing you’ll want to do is start a new ASP.NET Core project. Remember, in the new .NET Core world an ASP.NET Core project is just a regular old .NET Core console app that hosts its own web server and uses ASP.NET libraries. This guide is the canonical reference for getting started here. You’ll end up with three files in your project that look like this.

using Microsoft.AspNetCore.Hosting;

namespace Sample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }
    }
}
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

namespace Sample
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.Run(context =>
            {
                return context.Response.WriteAsync("Hello from ASP.NET Core!");
            });
        }
    }
}
{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true
  },
  "dependencies": {},
  "frameworks": {
    "netcoreapp1.0": {
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.0"
        },
        "Microsoft.AspNetCore.Server.Kestrel": "1.0.0"
      },
      "imports": "dnxcore50"
    }
  }
}

Add a model and a controller

Let’s quickly add a model - this will be the object that maps to our table in Postgresql. Create a new Model directory and add a BlogPost model.

using System;
using System.Collections.Generic;

namespace Sample.Model
{
    public class BlogPost
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
        public IEnumerable<string> Tags { get; set; }
        public Dates ModifiedDates { get; set; }

        public class Dates 
        {
            public DateTime CreatedDate { get; set; }
            public DateTime LastEditedDate { get; set; }
        }
    }
}

And we’ll need a controller which manages this. Create a new Controllers folder and create a file called BlogPostController.cs.

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Sample.Model;

namespace Sample.Controllers
{
    [Route("/posts")]
    public class BlogPostController
    {
        [HttpGet]
        public IEnumerable<BlogPost> Get()
        {
            // TODO get all blog posts from DB
            return new BlogPost[0];
        }

        [HttpGet("{id}")]
        public BlogPost Get(int id) 
        {
            // TODO get the specified blog post from DB
            return null;
        } 

        [HttpPost]
        public BlogPost Create([FromBody]BlogPost post)
        {
            // TODO create a blog post in the DB
            return null;
        }
    }
}

For this step you’ll also need to add a depedency on Microsoft.AspNetCore.Mvc to project.json - while we’re here, add dependencies on Kestrel (the built-in web server for aspnetcore development) and Marten:

{
  "version": "1.0.0-*",
  "buildOptions": {
    "debugType": "portable",
    "emitEntryPoint": true
  },
  "dependencies": {
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
    "Microsoft.AspNetCore.Mvc": "1.0.0",
    "Marten": "0.9.12.563"
  },
  "frameworks": {
// ...etc

Finally before using this controller you’ll need to setup the ASP.NET MVC framework. Edit your Startup.cs file:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Marten;
using Microsoft.AspNetCore.Hosting;

namespace Sample
{
    public class Startup
    {
        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }

        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();
        }
    }
}

At this stage it’s a good idea to fire up the web server and make sure everything is working as expected. Go to your command line terminal and run ‘dotnet run’. It should tell you that the server is running on localhost on some port (default is 5000). Open up a browser, Postman or your HTTP client of choice and make sure that when you hit http://localhost:5000/posts an empty array is returned. If not, something has gone wrong.

Configure dependency injection

The next step is to get the Marten DocumentStore object injected into our controllers so we can use it. Open up your Startup.cs file and edit the ConfigureServices method so that it looks like below. ConfigureServices is the method that the ASP.NET Core framework will call to set up your dependency injection framework - a simple DI container is included with ASP.NET core, so that’s what we’re going to use.

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();

    // Marten document store
    services.AddScoped<IDocumentStore>(provider => 
        DocumentStore.For("Server=127.0.0.1;Port=5432;Database=my-database;User Id=admin;Password=admin;"));
}

This line tells the dependency injection framework that we want to be able to ask for instances of IDocumentStore, and when we ask for them you should call that DocumentStore.For() method to get one. Additionally because we use the AddScoped method, this object will exist for the lifetime of a single request. You can read more about object lifetimes in ASP.NET Core here.

Obviously you’ll need to change your connection string to point to your own Postgresql instance. You can find more information about Postgresql connection strings here.

Querying in the controller

Now we should be able to just add an IDocumentStore to the controller’s constructor and the framework will supply it to us, ready to use. Let’s give it a crack. First add a constructor to the controller which takes the IDocumenStore and stores it in an instance variable.

private readonly IDocumentStore _documentStore;

public BlogPostController(IDocumentStore documentStore)
{
    _documentStore = documentStore;
}

And let’s flesh out the methods we defined earlier.

[HttpGet]
public IEnumerable<BlogPost> Get()
{
    using (var session = _documentStore.QuerySession())
    {
        return session.Query<BlogPost>();
    }
}

[HttpGet("{id}")]
public BlogPost Get(int id) 
{
    using (var session = _documentStore.QuerySession())
    {
        return session
            .Query<BlogPost>()
            .Where(post => post.Id == id)
            .FirstOrDefault();
        
    }
} 

[HttpPost]
public BlogPost Create([FromBody]BlogPost post)
{
    using (var session = _documentStore.LightweightSession())
    {
        session.Store(post);
        session.SaveChanges();
        return post;
    }
}

Let’s dig into what we’re doing here. The IDocumentStore can supply sessions which we use to query the database.

You may notice we’re using two different methods to get the DB session - QuerySession() and LightweightSession(). The query session is a read-only session, optimised for read scenarios. LightweightSession is a read-write session which requires you to manage object change tracking yourself.

The other alternatives are OpenSession() which supplies an implementation of IDocumentSession which is backed by an identity map - basically an in-memory cache of any data that has been retrieved. And finally there’s DirtyTrackedSession() which is a session which will track changes you make to any retrieved entities by storing the original document in memory. I don’t recommend using this unless you really need to - other than the performance penalty, in my experience it’s simpler to be explicit with your changes.

Once we have the session we can query it by calling Query<T> where T is the document type we’re trying to look up. The object returned by Query() is a Linq IQueryable so you can call most common Linq extension methods on it (although you should be cautious with how you query a document database - see more query information here).

Test it out

To test this, fire up your app an insert a document by issuing a POST request to http://localhost:5000/posts that looks something like:

{
    title:         "My new blog post",
    body:          "<h2>a blog post</h2><p>this is my new blog post</p>",
    tags:          ["marten", "blog"],
    modifiedDates: {
        createdDate:    "08/09/2015 21:00",
        LastEditedDate: "08/09/2015 22:50"
    }
}

And you’ll receive a response:

{
  "id": 1001,
  "title": "My new blog post",
  "body": "<h2>a blog post</h2><p>this is my new blog post</p>",
  "tags": [
    "marten",
    "blog"
  ],
  "modifiedDates": {
    "createdDate": "2015-08-09T21:00:00",
    "lastEditedDate": "2015-08-09T22:50:00"
  }
}

Interestingly you’ll find that Marten has automatically assigned a value to the Id field on our object. When you use an integer or long, Marten will use an auto-incrementing algorithm, and it will generate GUIDs if your Id field is a GUID. See here for more info on Marten object identity.

You can now point your web browser at http://localhost:5000/posts to see a listing of every document you have inserted, or http://localhost:5000/posts/{id} to find the document with that ID.

Wrapping up

The most interesting thing is how Marten is using Postgres to store your data. If you open your database instance you’ll find a table that’s defined something like

CREATE TABLE public.mt_doc_blogpost
(
  id integer NOT NULL,
  data jsonb NOT NULL,
  mt_last_modified timestamp with time zone DEFAULT transaction_timestamp(),
  mt_version uuid NOT NULL DEFAULT (md5(((random())::text || (clock_timestamp())::text)))::uuid,
  mt_dotnet_type character varying,
  CONSTRAINT pk_mt_doc_blogpost PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE public.mt_doc_blogpost
  OWNER TO admin;

The interesting bit is “data jsonb NOT NULL” which is the column that stores all of the data. The json document stored in there looks just like the one that was returned after your POST request. The jsonb data type in Postgresql is much more than just a JSON string - there’s real potential to do some cool stuff with this data type, and that’s why Marten is so interesting.

Read more about Marten here, and check out the code. You can find the full source code for this guide at my Bitbucket.