More

    Creating Controller Classes as API endpoint (Chapter 8)

    After built the services and factories, we will go to the final step, creating the API endpoint controller.

    Create the Controller

    In .NET Core, it is very easy to create the Controller class, the declaration should like this:

    using Microsoft.AspNetCore.Mvc;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using AutoMapper;
    using Microsoft.Extensions.Options;
    using Microsoft.AspNetCore.Authorization;
    using System.Text.Json;
    using BlueSky.Core.Configuration;
    using BlueSky.Service.Models.Client;
    using BlueSky.Core;
    using BlueSky.Service.Clients;
    using BlueSky.Api.Controllers;
    using BlueSky.Service.Factories;
    
    namespace BlueSky.Api.Controllers
    {
        [Route("api/clients")]
        [ApiController]
        public class ClientController : BaseController
        {
            #region Properties
    
            private readonly IClientService _clientService;
            private readonly IClientFactory _clientFactory;
            private readonly IMapper _mapper;
            private readonly AppSettings _appSettings;
    
            #endregion
    
            #region Ctor
    
            public ClientController(
                       IClientService clientService,
                       IClientFactory clientFactory,
                       IMapper mapper,
                       IOptions<AppSettings> appSettings)
            {
                _clientService = clientService;
                _clientFactory = clientFactory;
                _mapper = mapper;
                _appSettings = appSettings.Value;
            }
    
            #endregion
    
            #region Methods
            ..........
            #endregion
        }
    }

    First of all, use should declare the endpoint route path and inherit the BaseController class

    [Route("api/clients")]
    [ApiController]
    public class ClientController : BaseController
    {

    For this route, frontend should called this controller by “https://mysite.com/api/clients/…..”

    Then we write the first API function

            /// <summary>
            /// 更新當前客戶資料, 如客戶可使用 Cookie, 則不用指明 ClientGuid
            /// Update current client information record
            /// </summary> 
            /// <remarks>
            /// 
            /// Authorize : Bearer Token + Cookie / Admin to update other client
            /// 
            /// Route : api/client/update/{ClientGuid}
            /// 
            /// HTTP Method : POST
            ///     
            /// </remarks>
    
            /// <param name="model">Client Update Model</param>
            /// <param name="ClientGuid">Client Guid Identifier</parm>
            /// <response code="200">Ok Success</response>
            /// <response code="400">Error:304    Client record not found or UserGuid not correct. 客戶資料找不到, 可能是 UserGid 不正確</response>
            /// <response code="401">Unauthorized</response>
            [Authorize]
            [HttpPost("update/{ClientGuid}")]
            public async Task<IActionResult> UpdateClient([FromBody]ClientUpdateRequest model, Guid ClientGuid)
            {
                // Rewritten by Sammy Cheng, 2022-05-11
                var response = new GenericResponseModel<ClientModel>();
                // accept token from request body or cookie
                ClientGuid = getUserGuidCookie(ClientGuid);
    
                var client = await _clientService.UpdateClientFromModelAsync(model, ClientGuid);
                response.Data = _clientFactory.PrepareClientResponseModel(response.Data, client);
                return Ok(response);
            }

    (A) Swagger Comments

    For this Framework, we have integrated the Swagger documentation generator. So, the remark comments will directly generated as http document so that it is easy to work by frontend developers. Also, it reduces our workload on writting the development document. If you have added the comments as above, you will see if you run the project.

    For more information about using Swagger, please find in Swagger training materials.

    (B) API Endpoint

    [Authorize]
    [HttpPost("update/{ClientGuid}")]
    public async Task<IActionResult> UpdateClient([FromBody]ClientUpdateRequest model, Guid ClientGuid)

    For defining the endpoint name, just simply [HttpGet(“apipath&name”/”parameters)]. For http method, there are four method avaliable

    [HttpPost(” “)], [HttpGet(” “)], [HttpPut(” “)], [HttpDelete(” “)]

    But however, avoid using Put and Delete because it will conflict with server WebDAV service.

    Usually, we use Post method when user send data update request and collect data in JSon class or parameters. We use Get method when frontend retrieve data for display with parameters. For some frontend engine limitation, it may restricted than Get method can only send parameters by query but not support by JSon body.

    (C) Passing Parameters

    (1) Pass parameters by inline parameter in route: you can declare the parameter at the end of the endpoint name inside { } backet like this, the parameters will be pass to the function with the same name:

    [HttpGet("info/{ClientGuid}")]
    public async Task<IActionResult> GetClientInfo(Guid ClientGuid)

    (2) Pass parameters by query parameters : you can declare to accept parameters by using query like this.

    [HttpGet("list")]
    public async Task<IActionResult> GetClientList([FromQuery] ClientSearchCommand command)

    Even ClientSearchCommand is a Json class, add [FromQuery] tag to the parameters and you can use it as query parameters. To call this fucntion, it will be:

    https://ccoms.com/client/list?Name=ABCD&PageNumber=1&PageSize=10

    (2) Pass parameters by Json, normally using with Post by adding [FromBody]

    [Authorize]
    [HttpPost("update/{ClientGuid}")]
    public async Task<IActionResult> UpdateClient([FromBody]ClientUpdateRequest model, Guid ClientGuid)

    It is example, a ClientUpdateRequest Model structured Json is required to pass to the API function. You may use PostMan to pass the Json as follow screen:

    For .net core, it API controller will check the correctly of the JSon class automatically. Therefore, if the class is not matched, the API system will reject directly by Bad Request to frontend.

    The example also shows that mixed parameters with Json and inline parameters.

    (D) Authorization

    At the start of the declaration, we added a tag [Authorize]. This is the Framework permission filtering features, which let us to add simple security filtering for this function. There is some options here:

    [AllowAnonymous] – Allow any one to access the function without authenicate

    [Authorize] – Allow only authenicated users

    [Authorize(Role.Admin)] – Allow only authenicated users with Admin Role

    [Authorize(Role.Admin, Role.Operator)] – Allow only authenicated users with Opearator and Admin Role

    Currently, system defined four roles : User, ServiceProvider, Operator, Admin

    (E) Return Data Model

    By using the Framework, we use a universal response <IActionResult> for return model, which is a free format response. But the Framework has defined 2 common response models base class:

    (1) GenericResponseModel – Use it when you needed to return with a Model or a ListModel. The result Json will show as follow:

    {
        "Data": {
            "Email": "sammy@cpu88.com   ",
            "EmailToRevalidate": null,
            "CheckUsernameAvailabilityEnabled": false,
            "AllowUsersToChangeUsernames": false,
            "UsernamesEnabled": false,
            "Username": "sammy@cpu88.com",
            "GenderEnabled": true,
            "Gender": "M",
            "FirstName": "satest",
            "LastName": "32",
            "DateOfBirthEnabled": true,
            "DateOfBirthDay": null,
            "DateOfBirthMonth": null,
            "DateOfBirthYear": null,
            "DateOfBirthRequired": false,
            "CompanyEnabled": true,
            "CompanyRequired": false,
            "Company": null,
            "StreetAddressEnabled": false,
            "StreetAddressRequired": false,
            "StreetAddress": null,
            "StreetAddress2Enabled": false,
            "StreetAddress2Required": false,
            "StreetAddress2": null,
            "ZipPostalCodeEnabled": false,
            "ZipPostalCodeRequired": false,
            "ZipPostalCode": null,
            "CityEnabled": false,
            "CityRequired": false,
            "City": null,
            "CountyEnabled": false,
            "CountyRequired": false,
            "County": null,
            "CountryEnabled": false,
            "CountryRequired": false,
            "CountryId": 0,
            "AvailableCountries": [],
            "StateProvinceEnabled": false,
            "StateProvinceRequired": false,
            "StateProvinceId": 0,
            "AvailableStates": [],
            "PhoneEnabled": false,
            "PhoneRequired": false,
            "Phone": null,
            "FaxEnabled": false,
            "FaxRequired": false,
            "Fax": null,
            "NewsletterEnabled": true,
            "Newsletter": false,
            "SignatureEnabled": false,
            "Signature": null,
            "TimeZoneId": null,
            "AllowCustomersToSetTimeZone": true,
            "VatNumber": null,
            "VatNumberStatusNote": "Unknown",
            "DisplayVatNumber": false,
            "AssociatedExternalAuthRecords": [],
            "NumberOfExternalAuthenticationProviders": 0,
            "AllowCustomersToRemoveAssociations": true,
            "CustomerAttributes": [],
            "GdprConsents": [],
            "CustomProperties": {}
        },
        "Message": null,
        "ErrorList": []
    }

    Where { Data: } can contain any data model class. { Message: } and { ErrorList: } can used to show additional message or capture the errors.

    (2) SimpleResponseModel Use it when you do not needed to return a model class, just return string message or update count.

    public async Task<IActionResult> CreateClientFormUser(Guid UserGuid)
    {
        // Rewritten by Sammy Cheng, 2022-05-11
        // accept token from request body or cookie
        UserGuid = getUserGuidCookie(UserGuid);
        checkAdmin(UserGuid);
        var response = new SimpleResponseModel();
        await _clientService.CreateClientFromUser(UserGuid);
        response.SetSuccess(1, 0, "created from User");
        return Ok(response);
    }

    To result will return as

    1 record(s) created from user.

    You can define you own response model but remember that standardized response format can increase the development performance and enhance the code quality.

    Overview : Developer Guide for BlueSky .NETCORE API Framework

    Previous : Creating and Registering Factory, Using of AutoMapper Service (Ch.7)

    Next : Example of full API code and Error/Exception Handling (Ch.9)

    (c) 2022, BlueSky Information Technology (Int’l) Co. Ltd, All Rights Reserved.

    Recent Articles

    spot_img

    Related Stories

    Stay on op - Ge the daily news in your inbox