dev-notes/.NET/ASP.NET/REST API.md
2021-01-31 11:05:37 +01:00

336 lines
7.8 KiB
Markdown

# ASP .NET REST API
## Startup class
- Called by `Program.cs`
```cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace <Namespace>
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(); // controllers w/o views
//or
sevices.AddControllersWithViews(); // MVC Controllers
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
```
## DB Context (EF to access DB)
NuGet Packages to install:
- `Microsoft.EntityFrameworkCore`
- `Microsoft.EntityFrameworkCore.Tools`
- `Microsoft.EntityFrameworkCore.Design` *or* `Microsoft.EntityFrameworkCore.<db_provider>.Design`
- `Microsoft.EntityFrameworkCore.<db_provider>`
In `AppDbContext.cs`:
```cs
using <Namespace>.Model;
using Microsoft.EntityFrameworkCore;
namespace <Namespace>.Repo
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<ProjectContext> options) : base(options)
{
}
//DBSet<TEntity> represents the collection of all entities in the context, or that can be queried from the database, of a given type
public DbSet<Entity> entities { get; set; }
}
}
```
In `appsettings.json`:
```json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"CommanderConnection" : "Server=<server>;Database=<database>;UID=<user>;Pwd=<password>"
}
}
```
In `Startup.cs`:
```cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// SqlServer is the db used in this example
services.AddDbContext<CommanderContext>(option => option.UseSqlServer(Configuration.GetConnectionString("CommanderConnection")));
services.AddControllers();
}
```
### Migrations
- Mirroring of model in the DB.
- Will create & update DB Schema if necessary
In Packge Manager Shell:
```ps1
add-migrations <migration_name>
update-database # use the migrations to modify the db
```
## Repository
In `IEntityRepo`:
```cs
using <Namespace>.Model;
using System.Collections.Generic;
namespace <Namespace>.Repository
{
public interface IEntityRepo
{
IEnumerable<Entity> SelectAll();
Entity SelectOneById(int id);
...
}
}
```
In `EntityRepo`:
```cs
using <Namespace>.Model;
using System.Collections.Generic;
namespace <Namespace>.Repo
{
public class EntityRepo : IEntityRepo
{
private readonly AppDbContext _context;
public EntityRepo(AppDbContext context)
{
_context = context;
}
public IEnumerable<Entity> SelectAll()
{
return _context.Entities.ToList(); // linq query (ToList()) becomes sql query
}
public Entity SelectOneById(int id)
{
return _context.Entities.FirstOrDefault(p => p.Id == id);
}
...
}
}
```
## Data Transfer Objects (DTOs)
A **DTO** is an object that defines how the data will be sent and recieved over the network (usually as JSON).
Without a DTO the JSON response (or reqest) could contain irrilevant, wrong or unformatted data.
Moreover, by decoupling the JSON response from the actual data model, it's possible to change the latter without breaking the API.
DTOs must be mapped to the internal methods.
Required NuGet Packages:
- AutoMapper.Extensions.Microsoft.DependencyInjection
In `StartUp.cs`:
```cs
using AutoMapper;
// ...
public void ConfigureServices(IServiceCollection services)
{
// other services
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); // set automapper service
}
```
In `Entity<CrudOperation>DTO.cs`:
```cs
namespace <Namespace>.DTOs
{
// define the data to be serialized in JSON (can differ from model)
public class EntityCrudOpDTO // e.g: EntityReadDTO, ...
{
// only properties to be serialized
}
}
```
In `EntitiesProfile.cs`:
```cs
using AutoMapper;
using <Namespace>.DTOs;
using <Namespace>.Model;
namespace <Namespace>.Profiles
{
public class EntitiesProfile : Profile
{
public EntitiesProfile()
{
CreateMap<Entity, EntityCrudOpDTO>(); // map entity to it's DTO
}
}
}
```
## Controller (No View)
Uses [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) to recieve a suitable implementation of `IEntityRepo`,
### Service Lifetimes
- `AddSignleton`: same for every request
- `AddScoped`: created once per client
- `Transient`: new instance created every time
In `Startup.cs`:
```cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddScoped<IEntityRepo, EntityRepo>(); // map the interface to its implementation, needed for dependency injection
}
```
### Request Mappings
```cs
using <App>.Model;
using <App>.Repo;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace <App>.Controllers
{
[Route("api/endpoint")]
[ApiController]
public class EntitiesController : ControllerBase // MVC controller w/o view
{
private readonly ICommandRepo _repo;
private readonly IMapper _mapper; // AutoMapper class
public EntitiesController(IEntityRepo repository, IMapper mapper) // injection og the dependency
{
_repo = repository;
_mapper = mapper
}
[HttpGet] // GET api/endpoint
public ActionResult<IEnumerable<EntityCrudOpDTO>> SelectAllEntities()
{
var results = _repo.SelectAll();
return Ok(_mapper.Map<EntityCrudOpDTO>(results)); // return an action result OK (200) with the results
}
// default binding source: [FromRoute]
[HttpGet("{id}")] // GET api/endpoint/{id}
public ActionResult<EntityCrudOpDTO> SelectOneEntityById(int id)
{
var result = _repo.SelectOneById(id);
if(result != null)
{
return Ok(_mapper.Map<EntityCrudOp>(result)); // transfrom entity to it's DTO
}
return NotFound(); // ActionResult NOT FOUND (404)
}
}
}
```
## Controller (With View)
```cs
using <App>.Model;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace <App>.Controllers
{
[Route("api/endpoint")]
[ApiController]
public class EntitiesController : Controller
{
private readonly AppDbContext _db;
public EntitiesController(AppDbContext db)
{
_db = db;
}
[HttpGet]
public IActionResult SelectAll()
{
return Json(new { data = _db.Entities.ToList() }); // json view
}
}
}
```