Write your First API with ASP.NET Core and Entity Framework Core

In this post I will quickly describe how to create a simple API in ASP.NET Core that supports a set of CRUD operations on a database.  Before we get started, make sure to download and install ASP.NET Core on your machine.  You can find the installer here.  Let’s get started.

Start Visual Studio and create new project.  Pick ASP.NET project under .NET Core category.  On the next screen just pick Empty for now.  This will make it very simple for us to navigate the new project instead of trying to separate our code from generated code.

SNAGHTML9a890fd

 

SNAGHTML9a94ebf

Now we need to add necessary NuGet packages.  You can use NuGet package manager window or just edit package.json file directly. 

{ "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.0.0", "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0", "Microsoft.EntityFrameworkCore.Design": "1.0.0-preview2-final", "Microsoft.Extensions.Configuration.Json": "1.0.0" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final", "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final" },

Here is what my file looks like.  I added ASP.NET Core MVC in order to write APIs.  I added Entity Framework for SQL Server and added design assembly to support developer experience.  I also added the latest version of tools under tools area.  Finally, I am going to use JSON configuration files, so I added support for those.  So, the last 4 references under dependencies and the last one under tools.  The reset was generated by empty project template.

Let’s get a controller in place first and make sure we can run our app.  I am going to add a new folder called “Controllers” then I am going to add a new class called PersonsController. I am going to add it using New Item menu and picking Web Api Controller class.  This will give us a bit of a head start.

using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; // For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 namespace WebApplication45.Controllers { [Route("api/[controller]")] public class PersonsController : Controller { // GET: api/values [HttpGet] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } // GET api/values/5 [HttpGet("{id}")] public string Get(int id) { return "value"; } // POST api/values [HttpPost] public void Post([FromBody]string value) { } // PUT api/values/5 [HttpPut("{id}")] public void Put(int id, [FromBody]string value) { } // DELETE api/values/5 [HttpDelete("{id}")] public void Delete(int id) { } } }

A few things are different from Web Api 2.  The route is setup based on naming conventions.  If you use word controller, the route becomes class name without word controller.  In my case that would mean “api/persons”.

Now we need to enable MVC, which is now responsible for both Web Api ad MVC, uniting the two technologies.  Open up Startup.cs and enable MVC

public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); } }

We added MVC to the list of available services, basically injecting it into inversion of control container, responsible for dependency injection,  that comes out of the box with ASP.NET Core.

Let’s build the solution and test it.  Switch to Visual Studio command prompt.  Then navigate the folder where your project (NOT solution) is located.  Type the following to build and run the project.

dotent run

You should something in the console that states that your web server is up and running on port 5000.  Now we can just start the browser and navigate to http://localhost:5000/api/persons  You should see”value1 and valued 2” based on the code we saw.

image

Yeah, we are moving forward.

Let’s add configuration file.  We will add it under project folder for now.  Use new item template called ASP.NET Configuration file,  This will create appsettings.json file.  It comes with a sample connection string.  We will go ahead and change it to point to our local machine’s SQL Server instance.

{ "ConnectionStrings": { "DefaultConnection": "Server=.;Database=MyFirstApi;Trusted_Connection=True;MultipleActiveResultSets=true" } }

Of course, we need to wire up configuration.  We will add constructor to our Startup.cs, inject the environment and build the configuration object.

public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder(); var config = builder .AddJsonFile("appsettings.json", optional: false) .AddEnvironmentVariables(); Configuration = config.Build(); } public IConfigurationRoot Configuration { get; private set; }

You can easily read the code.  We create new configuration builder.  We build an object from two sources: our new configuration file and environmental variables.  Feel free to re-run dotnet run to ensure nothing is broken.

Now we are going to add new folder called data and add a new class, called Person.

namespace WebApplication45.Data { public class Person { public int Id { get; set; } public string Name { get; set; } } }

Very simple.  Now we need to add DbContext and configure our entity.  This code is similar to EF 6.

using Microsoft.EntityFrameworkCore; namespace WebApplication45.Data { public class PersonContext: DbContext { public DbSet<Person> Persons { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); var personModel = modelBuilder.Entity<Person>(); personModel.HasKey(p => p.Id); personModel.Property(p => p.Name).IsRequired().HasMaxLength(40); } } }

We need to configure entity framework during start up.  We just need to add two services: entity framework itself and the db context.

public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddEntityFramework() .AddDbContext<PersonContext>(config => { config.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]); }); }

We now see how we are pulling out connection string from our configuration object.  Not that we configured this, let’s create aour first migration – it will create our database and the table.  We need to go back to the command line and type

dotnet ef migrations add “Initial”

We will see the error as follows

No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext

 

This is is new in entity framework 7.  Let’s do what message tells us to do – add new constructor to our Db Context

public class PersonContext : DbContext { public PersonContext(DbContextOptions<PersonContext> options) : base(options) { }

Let’s rerun our migration command again.  We get new error

An error occurred while calling method ‘ConfigureServices’ on startup class ‘WebApplication45.Startup’. Consider using IDbContextFactory to override the initialization of the DbContext at design-time. Error: The configuration file ‘appsettings.json’ was not found and is not optional.

This time it is a folder issue.  Our configuration is failing to find app settings file when run from command line.  Let’s be more precise in specifying the file location.

public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder(); var config = builder .AddJsonFile(Path.Combine(env.ContentRootPath, "appsettings.json"), optional: false) .AddEnvironmentVariables(); Configuration = config.Build(); }

We specifying the application root.  We could also use web root if we wanted to move the file to wwwroot folder.  This time migration was created.

For now let’s plug in migration into our application startup code, in Configure method

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); using (var context = app.ApplicationServices.GetService<PersonContext>()) { context.Database.Migrate(); } }

We could also migrate externally to our app using a utility app, but let’s keep this simple for now.  Type dotnet run at the command prompt to run the app again and see if the database is created.

Let’s add more code to persons controller.  Let’s start by updating get all methods.  First of all, we need to inject our context.  It is an injectable in ASP.NET Core.

public class PersonsController : Controller { private readonly PersonContext _personContext; public PersonsController(PersonContext personContext) { _personContext = personContext; }

Now we can use it to return all contacts.  We are going to stick to asynchronous API, so our changed get all method looks as follows.

[HttpGet] [ProducesResponseType(typeof(IEnumerable<Person>), (int)HttpStatusCode.OK)] public async Task<IActionResult> Get() { return Ok(await _personContext.Persons.OrderBy(p => p.Name).ToListAsync()); }

I am using new filter/attribute to describe the result.  I am using helper Ok method to return action result.  IActionResult is used instead of HttpResponseResponce Message in previous versions of API.  Inside await I am using ToListAsync method to get the data.  Now, the next method to get a single person is going to look familiar.

[HttpGet("{id}")] [ProducesResponseType(typeof(Person), (int)HttpStatusCode.OK)] public async Task<IActionResult> Get(int id) { var result = await _personContext.Persons.SingleOrDefaultAsync(p => p.Id == id); if (result != null) { return Ok(result); } return NotFound(); }

There is no Find method in EF Core yet, so we are using single or default.  We are returning 404 if we cannot find the entity.  We can test this now by browsing a few rows into our database, then hitting http://localhost:5000/api/persons to get all people or http://localhost:5000/api/persons/1 to get one person.

Let’s writing remaining CRUD operations.

[HttpPost] [ProducesResponseType(typeof(Person), (int)HttpStatusCode.OK)] public async Task<IActionResult> Post([FromBody]Person person) { _personContext.Add(person); await _personContext.SaveChangesAsync(); return Created("api/persons/" + person.Id, person); } [HttpPut("{id}")] public async Task<IActionResult> Put(int id, [FromBody]Person person) { if (id != person.Id) { return BadRequest("Id is incorrect"); } _personContext.Entry(person).State = EntityState.Modified; await _personContext.SaveChangesAsync(); return NoContent(); } [HttpDelete("{id}")] public async Task<IActionResult> Delete(int id) { _personContext.Entry(new Person { Id = id }).State = EntityState.Deleted; await _personContext.SaveChangesAsync(); return Ok(); }

Now we can use Fiddler of Postman to test our operations interactively.  We are following the basic rules for status codes and return values for our CRUD operations, sticking to REST and HTTP guidelines.

Thanks and enjoy.  You can download this sample project here.