REST API with .Net Easily
Tags: csharp, architecture, dotnet, api
Published: 2022-06-24 08:01:09

### 1. A short introduction to how to build Web Applications

REST API is a well-known approach **to designing and structuring Web Applications**. REST is not a protocol or framework it is **a set of concepts**. Although **there are other approaches** i.e. it could be used stateful services with one HTTP method verb (SOAP) or use specific full duplex GRPC or WebSockets connection. REST is a very popular approach: it allows us **to use the same methodology for different resources** (which usually maps on persistent objects, entities). As engineers, we try to optimize our work: we attempt not to repeat ourselves (DRY), simplify (KISS) and so on. Therefore, today i, as a part of the [`Wissance`](https://wissance.com) team, would like to share yet another boilerplate to write REST API easily [see our WebAPI Toolkit](https://github.com/Wissance/WebApiToolkit).

### 2. What is REST

REST is a set of concepts that allows us to build a robust API ([for example see this article](https://restfulapi.net/)).
REST operates with the term "resource" which usually has persistence nature i.e. mapped to database table and so on. Let's see if **_we have a user as a resource_**.
In REST we have separate HTTP methods to perform different operations:

* CREATE this operation implements with POST HTTP method to resource i.e. _`POST {base_url}/api/resource`_ (i.e. if {base_url} is http://10.0.0.10/myservice and resource is `user` full uri could look like: **http://10.0.0.10/myservice/api/user**) During creation we are passing user data as DTO in request body and getting back created user with set identifier and other generated on server fields.
* READ this operation impelements with GET HTTP method and it  usually contains 2 independent endpoints to get collection of entity items and to get one by id i.e. for `user` resource:
    - `GET {base_url}/api/user` to get a collection
    - `GET {base_url}/api/user/{id}` to get a single item by {id}
* UPDATE this operation implements with PUT HTTP method (in this article PATCH method don't be considered). During a update we are passing resource as DTO through request body i.e. for user resource via url `PUT {base_url}/api/user/{id}`
* DELETE this operation implements with DELETE HTTP method i.e for user resource via url `DELETE {base_url}/api/user/{id}`

Implementing REST API Service one by one i finally understood what could be made common. We created open source library with this coomon code and it helps us ti significantly reduce amount of any project code. This is our (Wissance LLC) project [WebApiToolkit](https://github.com/Wissance/WebApiToolkit)

### 3. Our approach and concepts

First of all we have following REST application architecture concepts:

1. All resources are storing in Entity classes that have required property Id (`IModelIdentifiable<T>` [interface](https://github.com/Wissance/WebApiToolkit/blob/master/Wissance.WebApiToolkit/Wissance.WebApiToolkit.Data/Entity/IModelIdentifiable.cs) in our repo).
2. Controllers are just `facades` with some preliminary input data processing and validation, all application logic is implementing in appropriate `Manager` classes (Those we could use the same Managers for i.e. `Signalr` too not only for a REST API). `Manager` is an entry point to all complicated operation i.e. integration with other Web services and applications, with persistent storages like `Databases` and `Queues` and many others and so on is carrying on Manager classes. Controllers could only set Http Status codes and other things like setting some HTTP headers.
3. End user interacts with Web API using DTO which could have differences in number of fields in comparison with Entity i.e. we don't like to expose some fields or we do calculation for database fields during processing DTO by Manager.
4. We have Factory classes that allow to get DTO from Entity and vise versa.
5. All DTO classes are isolated from Entities and others application projects into separate project/assembly this allows us to easily distribute data contract as set of DTO as a Nuget package or directly by Add assembly manually to project.

We analyzed all OUR Net Core projects with REST API and [created package with common code](https://github.com/Wissance/WebApiToolkit). If you like our package please ***don't forget to give us a star, it is important for us***.

### 4. Let's see how it works in details
Lets consider that we are having a Weather station REST application. [A full working code example is here] (https://github.com/Wissance/WeatherControl), please don't forget to give a star to a this project too. In this application we have 2 REST resources that associated with 2 database tables:
- weather station (meta info about where measurements were taken)
- measurements (a set of weather control values: temperature, humidity, atmosphere pressure, e.t.c.) with set of sensors and sensors itself. Lets see how our controllers looks like and what could us give the `Wissance.WebToolkit`.

#### 4.1 Controllers

In our Toolkit we have 2 base Controller classes:
* For read-only resources - `BasicReadController`;
* For resources that could be changed or deleted (Full CRUD) - 
  `BasicCrudController`;
In mentioned upper demo project we used `BasicCrudController` as a base class for `Weather station` and `Measurements`:
```csharp
namespace Wissance.WeatherControl.WebApi.Controllers
{
    [ApiController]
    public class StationController : BasicCrudController<StationDto, StationEntity, int>
    {
        public StationController(StationManager manager)
        {
            Manager = manager;  // this is for basic operations
            _manager = manager; // this for extended operations
        }

        private StationManager _manager;
    }
}
```

As a result we have got:
1. Lightweight controllers, less code as it is possible;
2. We don't care about method declaration, routing, status codes processing;
3. Create and Update operations wraps response in `OperationResultDto` which allow to pass message what was wrong;
4. Get resource collection allowes to use pagination
5. We could use different identitifer type (int, string, guid) becuse it passed to controller class as a generic type.

This controller expose 5 endpoints:
* `GET ~/api/station/[?page={page}&size={size}]` to get collection 
  of station where `page` and 
  `size` are optional parameters if they not provided default 
  values is used (page=1, size=25);
* `GET ~/api/station/{id}` to get one station by {id};
* `POST ~/api/station/` to create new station;
* `PUT ~/api/station/{id}` to update station with id = {id};
* `DELETE ~/api/station/{id}` to delete station with id = {id};

#### 4.2 Managers

In our library we have base abstract class `ModelManager` that implements `IModelManager` interface. To work with `BaseCrudController` we should use Manager classes (they should implement IModelManager interface) i.e. StationManager looks like:
```csharp
 public class StationManager : ModelManager<StationEntity, StationDto, int>
    {
        public StationManager(ModelContext modelContext, ILoggerFactory loggerFactory) : base(loggerFactory)
        {
            _modelContext = modelContext;
        }

        public override async Task<OperationResultDto<IList<StationDto>>> GetAsync(int page, int size)
        {
            return await GetAsync<int>(_modelContext.Stations, page, size, null, null, StationFactory.Create);
        }

        public override async Task<OperationResultDto<StationDto>> GetByIdAsync(int id)
        {
            return await GetAsync(_modelContext.Stations, id, StationFactory.Create);
        }

        public override async Task<OperationResultDto<StationDto>> CreateAsync(StationDto data)
        {
            try
            {
                StationEntity entity = StationFactory.Create(data);
                await _modelContext.Stations.AddAsync(entity);
                int result = await _modelContext.SaveChangesAsync();
                if (result >= 0)
                {
                    return new OperationResultDto<StationDto>(true, (int)HttpStatusCode.Created, null, StationFactory.Create(entity));
                }
                return new OperationResultDto<StationDto>(false, (int)HttpStatusCode.InternalServerError, "An unknown error occurred during station creation", null);

            }
            catch (Exception e)
            {
                return new OperationResultDto<StationDto>(false, (int)HttpStatusCode.InternalServerError, $"An error occurred during station creation: {e.Message}", null);
            }
        }

        public override async Task<OperationResultDto<StationDto>> UpdateAsync(int id, StationDto data)
        {
            try
            {
                StationEntity entity = StationFactory.Create(data);
                StationEntity existingEntity = await _modelContext.Stations.FirstOrDefaultAsync(s => s.Id == id);
                if (existingEntity == null)
                {
                    return new OperationResultDto<StationDto>(false, (int)HttpStatusCode.NotFound, $"Station with id: {id} does not exists", null);
                }

                // Copy only name, description and positions, create measurements if necessary from MeasurementsManager
                existingEntity.Name = entity.Name;
                existingEntity.Description = existingEntity.Description;
                existingEntity.Latitude = existingEntity.Latitude;
                existingEntity.Longitude = existingEntity.Longitude;
                int result = await _modelContext.SaveChangesAsync();
                if (result >= 0)
                {
                    return new OperationResultDto<StationDto>(true, (int)HttpStatusCode.OK, null, StationFactory.Create(entity));
                }
                return new OperationResultDto<StationDto>(false, (int)HttpStatusCode.InternalServerError, "An unknown error occurred during station update", null);

            }
            catch (Exception e)
            {
                return new OperationResultDto<StationDto>(false, (int)HttpStatusCode.InternalServerError, $"An error occurred during station update: {e.Message}", null);
            }
            
        }

        public override async Task<OperationResultDto<bool>> DeleteAsync(int id)
        {
            return await DeleteAsync(_modelContext, _modelContext.Stations, id);
        }

        private readonly ModelContext _modelContext;
    }
```

it is not small as Controller class but it still quite small. We also should mention that we have in `ModelManager` abstract classes following additional methods:
* `public async Task<OperationResultDto<TRes>> GetAsync(DbSet<TObj> dbSet, TId id, Func<TObj, TRes> createFunc)`
* ` public async Task<OperationResultDto<IList<TRes>>> GetAsync<TKey>(DbSet<TObj> dbSet, int page, int size, 
                                                      Func<TObj, bool> filter, Func<TObj, TKey> sort,
                                                      Func<TObj, TRes> createFunc)`
* public async Task<OperationResultDto<bool>> DeleteAsync(DbContext context, DbSet<TObj> dbSet, TId id)

Methods that we mentioned above did not used as a IModelContext methods implementations for one particular reason - we cannot implement univesal authorization to perform all sets of operations in the abstract class due to real cases could very complicated, therefore additional methods were created to help us to implement all required operations in any custom manager class like `StationManager`.

ModelManager abstract class additional features:
* collection of objects could be **filtered** using delegate param -  `Func<TObj, bool> filter` of `GetAsync` method;
* collection of objects could be **sorted** using delegate param - Func<TObj, TKey> sorted of `GetAsync` method.

We still have what to do (this is not the final version) and in one future release there will be a possibility to pass filter & sort params to appropriate controller and manager methods.

### 5. Additional Wissance.WebToolkit features

In our example project we did not consider following important things and we are trying to describe them here briefly (if this articles get 50 likes we will make second chapter):

* authentication and authorization;
* additional controller methods that expands controller interface 
  over a just CRUD controller. 

#### 5.1 Authentication and authorization

We could use `IHttpContextAccessor _httpContext` to check whether user has access to resource or not, we could get Claim value like this where `claimTypeId` is a const like Username and so on:
```csharp
private string GetClaim(string claimTypeId)
{
    ClaimsPrincipal principal = _httpContext.HttpContext.User;
    Claim claim = principal.Claims.FirstOrDefault(c => c.Type == claimTypeId);
    if (claim == null)
         return null;
    return claim.Value;
}
```
Don't forget to inject IHttpContextAccessor:
```csharp
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
```

#### 5.2 Additional controller methods

There is nothing complicated about extending controllers, if i.e. we have to search weather station in pseudo code we should add in controller something like this:
```csharp
    [HttpGet]
    [Route("api/[controller]/search")]
    public async Task<IList<StationDto>> SearchInTaxServiceAsync([FromQuery]string query, [FromQuery]int page)
   {
       IList<StationDto> result = await _manager.SearchAsync(query, page);
       if (result == null)
       {
           HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
        }

        return result;
    }
```

### 6. Conclusion
Thank for reading bro, please rise you star on our github projects (`WebApiToolkit` && `WeatherControl`) if you find that they are helpful for you. As a conclusion we also would like to say just a fact: we significantly reduce amount of code and copy-paste code using our `WebApiToolkit`.