ASP.NET Core Tutorial (1) WebApi

Posted by Andy Feng on October 5, 2018

download source code here

Create project via Cli

  1. Install .net core sdk, .net core runtime at https://www.microsoft.com/net/learn/get-started/windows or https://www.microsoft.com/net/download/windows

    in Feb, 2018, .net core sdk is v2.1.4, .net core runtime is v 2.0.5

  2. open console, dotnet new --help to check templates

  3. create webapi project, dotnet new webapi -o myApi

  4. run the app, dotnet run

  5. install entity framework core package. dotnet add Microsoft.EntityFrameworkCore.SqlServer

    other commands:

    dotnet add package: Adds a package reference to the project file, then runs dotnet restore to install the package. dotnet remove package: Removes a package reference from the project file dotnet restore: Restores the dependencies and tools of a project. dotnet nuget locals: Clears or lists local NuGet resources such as the http-request cache, the temporary cache, and the machine-wide global packages folder.

Create project via Visual Studio

we got

If this webapi project references other libaries, make sure to choose the compatible .net framework version

ctrl + f5 to run,

nuget > install Microsoft.EntityFrameworkCore.SqlServer package

Create a simple demo

  1. add models

    Models > TodoItem.cs:

     namespace Models
     {
         public class TodoItem
         {
             public long Id { get; set; }
             public string Name { get; set; }
             public bool IsComplete { get; set; }
         }
     }
    
  2. add data

    Data > TodoContext.cs:

     using Microsoft.EntityFrameworkCore;
     using Models;
    
     namespace TodoApi.Models
     {
         public class TodoContext : DbContext
         {
             public TodoContext(DbContextOptions<TodoContext> options)
                 : base(options)
             {
             }
    	
             public DbSet<TodoItem> TodoItems { get; set; }
    	
         }
     }
    

  3. register db context in dependency injection container. Here we specify an in-memory database is injected into the service container.

    update Startup.cs

     public class Startup
     {       
         public void ConfigureServices(IServiceCollection services)
         {
             services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
             services.AddMvc();
         }
         ...
     }
    
  4. Create a controller: TodoController.cs

     using Microsoft.AspNetCore.Mvc;
     using Models;
     using Data;
     using System.Linq;
    	
     namespace TodoApi.Controllers
     {
         [Route("api/[controller]")]
         public class TodoController : Controller
         {
             private readonly TodoContext _context;
    	
             public TodoController(TodoContext context)
             {
                 _context = context;
    	
                 if (_context.TodoItems.Count() == 0)
                 {
                     _context.TodoItems.Add(new TodoItem { Name = "Item1" });
                     _context.SaveChanges();
                 }
             }
         }
     }
    
  5. Add get endpoints

     [HttpGet]
     public IEnumerable<TodoItem> GetAll()
     {
         return _context.TodoItems.ToList();
     }
    	
     [HttpGet("{id}", Name = "GetTodo")]
     public IActionResult GetById(long id)
     {
         var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
         if (item == null)
         {
             return NotFound();
         }
         return new ObjectResult(item);
     }
    
  6. Add create endpoint

     [HttpPost]
     public IActionResult Create([FromBody] TodoItem item)
     {
         if (item == null)
         {
             return BadRequest();
         }
    	
         _context.TodoItems.Add(item);
         _context.SaveChanges();
    	
         return CreatedAtRoute("GetTodo", new { id = item.Id }, item);
     }
    
  7. Add update endpoint

     [HttpPut("{id}")]
     public IActionResult Update(long id, [FromBody] TodoItem item)
     {
         if (item == null || item.Id != id)
         {
             return BadRequest();
         }
    	
         var todo = _context.TodoItems.FirstOrDefault(t => t.Id == id);
         if (todo == null)
         {
             return NotFound();
         }
    	
         todo.IsComplete = item.IsComplete;
         todo.Name = item.Name;
    	
         _context.TodoItems.Update(todo);
         _context.SaveChanges();
         return new NoContentResult();
     }
    
  8. add delete endpoint

     [HttpDelete("{id}")]
     public IActionResult Delete(long id)
     {
         var todo = _context.TodoItems.FirstOrDefault(t => t.Id == id);
         if (todo == null)
         {
             return NotFound();
         }
    	
         _context.TodoItems.Remove(todo);
         _context.SaveChanges();
         return new NoContentResult();
     }
    

Enable cors

  1. Install package Microsoft.AspNetCore.Cors either via nuget in visual studio or command line

  2. Enable cors

    1. way1, enable in middleware. modify Startup.cs

       public void ConfigureServices(IServiceCollection services)
       {
           services.AddCors();
       }
      
    2. way2, enable in controller

Add lazy loading

  1. Install package Microsoft.EntityFrameworkCore.Proxies

  2. Suppose we have a database EmployeeManagement, we use database first to generate model classes:

    Scaffold-DbContext "Server=(local);Database=EmployeeManagement;Integrated Security=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Entities

    A folder Entities will be created with entities and dbcontext in it

  3. Open entity classes, manually set all navigation properties as virtual:

  4. Enable lazy loading, Startup.cs

     ```
     ...
      public void ConfigureServices(IServiceCollection services)
             {
                 // add db optoins
                 var connection = @"Server=(local);Database=EmployeeManagement;Integrated Security=true;";
                 services.AddDbContext<EmployeeManagementContext>(opt =>
                     {
                         opt.UseLazyLoadingProxies().UseSqlServer(connection);
                     }
                 );
                 //services.AddEntityFrameworkProxies();
                 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
             }
     ```
    

Test

  1. run via dotnet run

  2. navigate to http://localhost:5000/api/todo

  3. post a json data

    { “id”: 2, “name”: “Item2”, “isComplete”: true }

Add database first entities

  1. Add nuget libraries:
     Microsoft.EntityFrameworkCore.SqlServer
     EntityFrameworkCore.Tools
     EntityFrameworkCore.SqlServer.Design
    
  2. nuget command console Scaffold-DbContext "Server=(local)\SQLEXPRESS;Database=EFCoreDBFirstDemo;Integrated Security=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Entities

    it will create a Entities folder with all models from database inside it.

Enable cors

  1. Install Microsoft.AspNetCore.Cors package

  2. Add the CORS services

     public void ConfigureServices(IServiceCollection services)
     {
         ...
         services.AddCors();
     } 
    
  3. Enable cors with middleware. Note that the CORS middleware must precede any defined endpoints in your app that you want to support cross-origin requests (ex. before any call to UseMvc).

     public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
     {
         loggerFactory.AddConsole();
    	
         if (env.IsDevelopment())
         {
             app.UseDeveloperExceptionPage();
         }
    	
         // Shows UseCors with CorsPolicyBuilder.
         app.UseCors(builder =>
            builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
    	
         ...
     }
    

Model validation

Attribute validation

  1. modify properties of model

  2. We can define our own validation attribute

     public class TorontoPhoneAttribute : System.ComponentModel.DataAnnotations.ValidationAttribute
     { 
    
         protected override ValidationResult IsValid(object value, ValidationContext validationContext)
         {
             if(!value.ToString().StartsWith("647"))
             {
                 return new ValidationResult("this is not 647-xxx-xxxx");
             }
             return null;
         }
     }
    
  3. In controller, verify ModelStatus.IsValid and return badrequest accordingly

     [HttpPost("")]
     public IActionResult AddCompany([FromBody] TblCompany company)
     {
         if (ModelState.IsValid)
         {
             this.employeeManagementV2Context.TblCompany.Add(company);
             this.employeeManagementV2Context.SaveChanges();
             return Ok();
         }
         return BadRequest(ModelState);
     }
    
  4. POST a request, we will get errors as below

Fluent validation

  1. Install 3rd package
    • asp.net core FluentValidation.AspNetCore
    • asp.net mvc FluentValidation.Mvc5
    • asp.net webapi v2 FluentValidation.WebApi

References

https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api

https://blogs.msdn.microsoft.com/webdev/2017/04/06/jwt-validation-and-authorization-in-asp-net-core/

https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?tabs=visual-studio

https://docs.microsoft.com/en-us/aspnet/core/security/cors

https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.1

https://fluentvalidation.net

https://www.c-sharpcorner.com/article/learn-about-web-api-validation/

https://www.jerriepelser.com/blog/validation-response-aspnet-core-webapi/