DEV Community

Manik
Manik

Posted on

How to Web API .Net Core Basics to Advanced Part 6 Second Controller

Address Controller

As we created a CompanyController in the last article to make things more interesting, let us create an AddressController and AddressTypeController which allows the client to Retrieve, Add, Update, Remove address for a company. In our case let us assume our company may have one address or more than one address and some times clients can request that if we want to send an invoice it should be sent to a different address and for normal correspondence we should use a different address.

*Enough talking lets begin. *

In the image below we have a table structure.

Asp Net Web API

Enough of the theory let us create our Models, Dtos and controllers.
Models/Entity
In our Entity folder I am going to add two files and I am going to name them Address.cs and AddressType.cs to map our data with tables in db.
Address.cs



public class Address
    {
        [Key]
        public int Id { get; set; }
        [Required]
        public Guid GUID { get; set; }
        [Required]
        public Guid CompanyGUID { get; set; }
        [Required]        
        [RegularExpression(@"[a-zA-Z0-9_.-]{2,150}",
             ErrorMessage = "The {0} must be 2 to 150 valid characters which are any digit, any letter and -._@+.")]
        [StringLength(150, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 2)]
        [Display(Name = "HouseNoName")]
        public string HouseNoName { get; set; }
        [Required]
        public string Street { get; set; }      
        public string City { get; set; }
        [Required]
        public string County { get; set; }
        [Required]
        public string PostCode { get; set; }
        public DateTimeOffset Createddate { get; set; }
        public bool IsEnabled { get; set; }
        public bool IsDeleted { get; set; }

        //Forign Key navigation property
        [Required]
        public int CompanyId { get; set; }
        [ForeignKey("CompanyId")]
        public Company Company { get; set; }

        //Forign Key navigation property
        [Required]
        public int AddressTypeId { get; set; }
        [ForeignKey("AddressTypeId")]
        public AddressType AddressType { get; set; }
    }


Enter fullscreen mode Exit fullscreen mode

AddressType.cs



 public class AddressType
    {
        [Key]
        public int Id { get; set; }
        [Required]
        public string AddressTypeName { get; set; }
        public bool IsEnabled { get; set; }
        public bool IsDeleted { get; set; }
    }


Enter fullscreen mode Exit fullscreen mode

Now we can use DB-update command to Update the database schema in our DB as I have shown in my article <<<Part 2 How to Web API Core set up Database Context

Dto
Let us now add two folders (Address-AddressType) in our Dto folder. I am going to use these folders to add our Dto files like we did for company, I am leaving out Dtos Create and Update for you guys.

AddressDto*



public class AddressDto
    {     
        public int Id { get; set; }
        public Guid GUID { get; set; }
        [Required]
        public Guid CompanyGUID { get; set; }
        [Required]       
        [RegularExpression(@"[a-zA-Z0-9_.-]{2,150}",
             ErrorMessage = "The {0} must be 2 to 150 valid characters which are any digit, any letter and -._@+.")]
        [StringLength(150, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 2)]
        [Display(Name = "HouseNoName")]
        public string HouseNoName { get; set; }
        [Required]
        public string Street { get; set; }
        public string City { get; set; }
        public string County { get; set; }
        [Required]
        public string PostCode { get; set; }
        public DateTimeOffset Createddate { get; set; }
        public bool IsEnabled { get; set; }
        public bool IsDeleted { get; set; }

        //Forign Key realtion
        [Required]
        public int CompanyId { get; set; }       
        public CompanyDto Company { get; set; }
        //Forign Key realtion
        [Required]
        public int AddressTypeId { get; set; }       
        public AddressTypeDto AddressType { get; set; }


Enter fullscreen mode Exit fullscreen mode

Controllers
To Add a controller right click the controller folder in solution explorer and select Add->Controller

Asp.Net Web API Controller

and you will end up at Add New Scaffold Item Make sure you select API option, default one is MVC, I am going to go with API Controller - Empty option as that is the best way to learn. But I encourage you to explore other templates when you add more Controllers.

Asp.Net Core Web Api

Next we are going to name our controller, so our first controller is going to be Address as in image below.

Asp.Net Core Web Api

We will end up with the basic template of Web API controller and in the code below I have set up a constructor which expects an object of IAddressService.



sing Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using WebApiSeriesCore5.ServiceResponder;
using WebApiSeriesCore5.Services.Contract;
using WebApiSeriesCore5.Dto.Address;

namespace WebApiSeriesCore5.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AddressController : ControllerBase
    {

        private readonly IAddressService _addrService;

        public AddressController(IAddressService addressService)
        {
            _addrService = addressService;
        }
}



Enter fullscreen mode Exit fullscreen mode

How to Use Get Put Post Delete
To start with, we can get a list of addresses for a company and to do that we can use [HttpGet] in our controller as you can see in the example code below.



/// <summary>
/// Get list of all address for a company
/// </summary>
/// <param name="companyGUID"></param>
/// <returns></returns>
//Get/{Guid}
[HttpGet("[action]/{companyGUID:Guid}", Name = "GetCompanyAddressByGUID")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AddressDto))]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)] //Not found
[ProducesDefaultResponseType]
public async Task<IActionResult> GetCompanyAddress(Guid companyGUID)
{
     if(companyGUID == Guid.Empty) { return BadRequest(); }

     ServiceResponse<List<AddressDto>> listCompanyAddress = await _addrService.GetCompanyAddressAsync(companyGUID);

     if (listCompanyAddress.Data == null)
     {
        return NotFound(listCompanyAddress);
     }

    return Ok(listCompanyAddress);
}


Enter fullscreen mode Exit fullscreen mode

Even though I have provided a complete controller in the example below, I would suggest try and add HttpPost, HttPut, HttpPatch and HttpDelete yourself and have fun with them because that it best way to learn when you are enjoying it.



using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
using WebApiSeriesCore5.ServiceResponder;
using WebApiSeriesCore5.Services.Contract;
using WebApiSeriesCore5.Dto.Address;

namespace WebApiSeriesCore5.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AddressController : ControllerBase
    {

        private readonly IAddressService _addrService;

        public AddressController(IAddressService addressService)
        {
            _addrService = addressService;
        }

        /// <summary>
        /// Get list of all address for a company
        /// </summary>
        /// <param name="companyGUID"></param>
        /// <returns></returns>
        //Get/{Guid}
        [HttpGet("[action]/{companyGUID:Guid}", Name = "GetCompanyAddressByGUID")]
        [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AddressDto))]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)] //Not found
        [ProducesDefaultResponseType]
        public async Task<IActionResult> GetCompanyAddress(Guid companyGUID)
        {
            if (companyGUID == Guid.Empty) { return BadRequest(); }

            ServiceResponse<List<AddressDto>> listCompanyAddress = await _addrService.GetCompanyAddressAsync(companyGUID);

            if (listCompanyAddress.Data == null)
            {
                return NotFound(listCompanyAddress);
            }

            return Ok(listCompanyAddress);
        }

        /// <summary>
        /// Get a address by GUID
        /// </summary>
        /// <param name="addressGUID"></param>
        /// <returns></returns>
        //Get/12
        [HttpGet("{addressGUID:Guid}", Name = "GetAddressByGUID")]
        [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AddressDto))]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)] //Not found
        [ProducesDefaultResponseType]
        public async Task<IActionResult> GetAddress(Guid addressGUID)
        {
            if (addressGUID == Guid.Empty) { return BadRequest(); }

            ServiceResponse<AddressDto> _address = await _addrService.GetByGUIDAsync(addressGUID);

            if (_address.Data == null)
            {
                return NotFound(_address);
            }

            return Ok(_address);
        }

        /// <summary>
        /// Create a new address for a company
        /// </summary>
        /// <param name="createAddressDto"></param>
        /// <returns></returns>
        //POST/Address
        [HttpPost]
        [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AddressDto))]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)] //Not found
        [ProducesResponseType(StatusCodes.Status500InternalServerError)]
        public async Task<ActionResult<AddressDto>> CreateAddress([FromBody] CreateAddressDto createAddressDto)
        {

            if (createAddressDto == null)
            {
                return BadRequest(ModelState);
            }

            if (!ModelState.IsValid) { return BadRequest(ModelState); }

            var _newAddress = await _addrService.AddAddressAsync(createAddressDto);

            if (_newAddress.Success == false && _newAddress.Message == "Exist")
            {
                ModelState.AddModelError("", "Address Exist");
                return StatusCode(404, ModelState);
            }

            //only for Demo in production never send response back about internal error.
            if (_newAddress.Success == false && _newAddress.Message == "RepoError")
            {
                ModelState.AddModelError("", $"Some thing went wrong at repository layer when adding Address {createAddressDto}");
                return StatusCode(500, ModelState);
            }

            //only for Demo in production never send response back about internal error.
            if (_newAddress.Success == false && _newAddress.Message == "Error")
            {
                ModelState.AddModelError("", $"Some thing went wrong at service layer when adding Address {createAddressDto}");
                return StatusCode(500, ModelState);
            }

            //Return new address created
            return CreatedAtRoute("GetCompanyAddressByGUID", new { CompanyGUID = _newAddress.Data.CompanyGUID }, _newAddress);

        }

        /// <summary>
        /// Update an existing address
        /// </summary>
        /// <param name="addressGUID"></param>
        /// <param name="updateAddressyDto"></param>
        /// <returns></returns>
        //PUT/Address/{Guid}
        [HttpPut("{addressGUID:Guid}", Name = "UpdateAddress")]
        [ProducesResponseType(StatusCodes.Status204NoContent)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status404NotFound)] //Not found
        [ProducesResponseType(StatusCodes.Status500InternalServerError)]
        public async Task<IActionResult> UpdateAddress(Guid addressGUID, [FromBody] UpdateAddressDto updateAddressyDto)
        {
            if (updateAddressyDto == null || updateAddressyDto.GUID != addressGUID)
            {
                return BadRequest(ModelState);
            }


            var _updateAddress = await _addrService.UpdateAddressAsync(updateAddressyDto);

            if (_updateAddress.Success == false && _updateAddress.Message == "NotFound")
            {
                ModelState.AddModelError("", "Address Not found");
                return StatusCode(404, ModelState);
            }
            //only for Demo in production never send response back about internal error.
            if (_updateAddress.Success == false && _updateAddress.Message == "RepoError")
            {
                ModelState.AddModelError("", $"Some thing went wrong at repository layer when adding Address {updateAddressyDto}");
                return StatusCode(500, ModelState);
            }
            //only for Demo in production never send response back about internal error.
            if (_updateAddress.Success == false && _updateAddress.Message == "Error")
            {
                ModelState.AddModelError("", $"Some thing went wrong at service layer when adding Address {updateAddressyDto}");
                return StatusCode(500, ModelState);
            }


            return Ok(_updateAddress);
        }

        /// <summary>
        /// Mark a address as deleted.
        /// </summary>
        /// <param name="addressGUID"></param>
        /// <returns></returns>
        //DELETE/Address/{Guid} 
        [HttpDelete("{addressGUID:Guid}", Name = "DeleteAddress")]
        [ProducesResponseType(StatusCodes.Status204NoContent)]
        [ProducesResponseType(StatusCodes.Status404NotFound)] //Not found
        [ProducesResponseType(StatusCodes.Status409Conflict)] //Can not be removed 
        [ProducesResponseType(StatusCodes.Status500InternalServerError)]
        public async Task<IActionResult> DeleteAddress(Guid addressGUID)
        {

            var _deleteAddress = await _addrService.SoftDeleteAddressAsync(addressGUID);

            if (_deleteAddress.Success == false && _deleteAddress.Data == "NotFound")
            {
                ModelState.AddModelError("", "Address Not found");
                return StatusCode(404, ModelState);
            }

            //only for Demo in production never send response back about internal error.
            if (_deleteAddress.Success == false && _deleteAddress.Data == "RepoError")
            {
                ModelState.AddModelError("", $"Some thing went wrong at Repository layer when deleting Address");
                return StatusCode(500, ModelState);
            }
            //only for Demo in production never send response back about internal error.
            if (_deleteAddress.Success == false && _deleteAddress.Data == "Error")
            {
                ModelState.AddModelError("", $"Some thing went wrong at service layer when deleting Address");
                return StatusCode(500, ModelState);
            }

            return NoContent();

        }
    }
}



Enter fullscreen mode Exit fullscreen mode

If you have read my previous article How to Web API .Net Core Basics to Advanced Part 4 Service Layer in which we implemented service layer. I have given below a basic example and left a few methods out. I am hoping you guys will love to try out completing the rest of the code for AddressTypeService, AddressService, AddressRepository and AddressTypeRepository.

Interface for AddressTypeService



public interface IAddressTypeService
    {
      /// <summary>
      /// Return all addresstypes which are not marked as deleted and enabled=false
      /// </summary>
      /// <returns>List Of AddressTypeDto</returns>
      Task<ServiceResponse<List<AddressTypeDto>>> GetAddressTypeAsync();
      /// <summary>
      /// Return addresstype.
      /// </summary>
      /// <param name="Id"></param>
      /// <returns>AddressTypeDto</returns>
      Task<ServiceResponse<AddressTypeDto>> GetByIdAsync(int Id);
}



Enter fullscreen mode Exit fullscreen mode

Implimetation of AddressType



public class AddressTypeService : IAddressTypeWrapper
{
   private readonly IAddressTypeRepository _addrTypeRep;
   private readonly IMapper _mapper;

public AddressTypeService(IAddressTypeRepository addressTypeRepository, IMapper mapper)
{
  _addrTypeRep = addressTypeRepository;
  _mapper = mapper;
 }

 /// <summary>
 /// return all addresstypes which are not marked as deleted and enabled.
 /// </summary>
 /// <returns>AddressTypeDto</returns>
 public async Task<ServiceResponse<List<AddressTypeDto>>> GetAddressTypeAsync()
 {

     ServiceResponse<List<AddressTypeDto>> _response = new();
     var AddressTypeList = await _addrTypeRep.GetAddressesType();

     var AddressTypeListDto = new List<AddressTypeDto>();

     foreach (var item in AddressTypeList)
     {
        AddressTypeListDto.Add(_mapper.Map<AddressTypeDto>(item));
     }

       _response.Success = true;
       _response.Message = "ok";
       _response.Data = AddressTypeListDto;
       return _response;

 }

 /// <summary>
 /// return addresstype.
 /// </summary>
 /// <param name="Id"></param>
 /// <returns>AddressTypeDto</returns>
 public async Task<ServiceResponse<AddressTypeDto>> GetByIdAsync(int Id)
 {
   if (Id <= 0) throw new ArgumentOutOfRangeException("id");

   ServiceResponse<AddressTypeDto> _response = new();

   var _address = await _addrTypeRep.GetAddressTypeByIDAsync(Id);

   if (_address == null)
   {
      _response.Success = true;
      _response.Message = "NotFound";
      _response.Data = null;
      return _response;
   }

   var _addressTypeDto = _mapper.Map<AddressTypeDto>(_address);

   _response.Success = true;
   _response.Message = "ok";
   _response.Data = _addressTypeDto;

   return _response;

}

}
}


Enter fullscreen mode Exit fullscreen mode

Repo AddressType Interface



public interface IAddressTypeRepository
{
Task<ICollection<Entities.AddressType>> GetAddressesType();
Task<Entities.AddressType> GetAddressTypeByIDAsync(int addressTypeId);
}


Enter fullscreen mode Exit fullscreen mode

Repo AddressType



public class AdressTypeRepository : IAddressTypeRepository
    {
        private readonly DataContext dataContext;

        public AdressTypeRepository(DataContext dataContext)
        {
            this.dataContext = dataContext; 
       }
 public async Task<ICollection<Entities.AddressType>> GetAddressesType()
        {
            return await dataContext.AddressTypes.Where(AT => AT.IsDeleted == false && AT.IsEnabled == true).ToListAsync();
        }

 public async Task<Entities.AddressType> GetAddressTypeByIDAsync(int addressTypeId)
        {
            return await dataContext.AddressTypes.SingleOrDefaultAsync(AT => AT.Id == addressTypeId);

        }


}


Enter fullscreen mode Exit fullscreen mode

Address Repo Interface



public interface IAddressRespository
    {   
        /// </summary>
        /// <param name="companyGUID"></param>
        /// <returns>Entites.Address</returns>
        Task<ICollection<Entities.Address>> GetCompanyAddresses(Guid companyGUID);    
        /// </summary>
        /// <param name="addressGUID"></param>
        /// <returns>Entites.Address</returns>
        Task<Entities.Address> GetAddressByGUID(Guid addressGUID);
    }


Enter fullscreen mode Exit fullscreen mode

Address Repo Implimentation



public class AddressRepository : IAddressRespository
    {
        private readonly DataContext _DbContext;

        public AddressRepository(DataContext dataContext)
        {
            _DbContext = dataContext;
        }


        public async Task<ICollection<Entities.Address>> GetCompanyAddresses(Guid companyGUID)
        {           
            return  await _DbContext.Addresses.Include(c => c.Company).Include(t=>t.AddressType).Where(a => a.CompanyGUID == companyGUID && a.IsDeleted==false).ToListAsync();

        }       

        public async Task<Entities.Address> GetAddressByGUID(Guid addressGUID)
        {          
            return await _DbContext.Addresses.Include(c => c.Company).Include(t => t.AddressType).SingleOrDefaultAsync(a => a.GUID == addressGUID && a.IsDeleted == false);
        }

        public async Task<Entities.Address> GetAddressByID(int addressId)
        {           
            return await _DbContext.Addresses.Include(c => c.Company).Include(t => t.AddressType).SingleOrDefaultAsync(a => a.Id == addressId && a.IsDeleted == false);
        }

    }


Enter fullscreen mode Exit fullscreen mode

Hold on now don't get too excited
Yes we are almost done, but before we can fire up our project and view our API endpoints we need two more steps. First we need to setup DI DI-dependency-injection for our service layer in startup.cs file and have it ready to be injected when it is required.

Startup.cs



  // This method gets called by the runtime. Use this method to 
//add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddHealthChecks();

            services.AddDbContext<DataContext>(options => options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection")));
            //Add Automapper
            services.AddAutoMapper(typeof(Startup));
            //inject Data Access Layer - Repository
            services.AddScoped<ICompanyRepository, CompanyRepository>();
            services.AddScoped<IAddressRespository, AddressRepository>();
            services.AddScoped<IAddressTypeRepository, AdressTypeRepository>();
            //inject Service layer
            services.AddScoped<ICompanyService, CompanyService>();
            services.AddScoped<IAddressService, AddressService>();
            services.AddScoped<IAddressTypeService, AddressTypeService>();

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApiSeriesCore5", Version = "v1" });
            });
        } 



Enter fullscreen mode Exit fullscreen mode

Lets us try out HttpGet for our address api

Image description

We can see our Address Api endpoint expect a GUID of a company. Because we do not have any records in Address table we should get "not found" response as in example below.

Image description

Lets us try out HttpPost for our address api

Image description

To add company address we need to provide CompanyGUID and AddressTypeId as in json object below



{
  "companyGUID": "4ad95916-904e-44f5-b22b-9c6bed4ce3ce",
  "houseNoName": "23",
  "street": "Willow gradens",
  "city": "Lockewell",
  "county": "Estone",
  "postCode": "ET12 5RM",
  "createddate": "2022-10-16T10:57:31.339Z",
  "isEnabled": true,
  "isDeleted": false,
  "companyId": 1,
  "addressTypeId": 1
}



Enter fullscreen mode Exit fullscreen mode

and api response after adding an address for a company is below.

Image description

and if we call HttpGet for address with company GUID we can now retrive address record for a company.

Image description

and json returned by HttpGet



{
  "data": [
    {
      "id": 1,
      "guid": "f0df8134-e42e-45e7-a986-00116a818861",
      "companyGUID": "4ad95916-904e-44f5-b22b-9c6bed4ce3ce",
      "houseNoName": "23",
      "street": "Willow gradens",
      "city": "Lockewell",
      "county": "Estone",
      "postCode": "ET12 5RM",
      "createddate": "2022-10-16T11:14:36.7777365+00:00",
      "isEnabled": true,
      "isDeleted": false,
      "companyId": 1,
      "company": {
        "id": 1,
        "guid": "4ad95916-904e-44f5-b22b-9c6bed4ce3ce",
        "companyName": "Company One",
        "createdDate": "2022-10-16T10:33:24.2222357+00:00",
        "isEnabled": true,
        "isDeleted": false
      },
      "addressTypeId": 1,
      "addressType": {
        "id": 1,
        "addressTypeName": "Office",
        "isEnabled": true,
        "isDeleted": false
      }
    }
  ],
  "success": true,
  "message": "ok",
  "error": null,
  "errorMessages": null
}



Enter fullscreen mode Exit fullscreen mode

Part 7 API versioning Coming Soon
<<<Part 5 How to Web API .Net Core Controller And SwaggerUI
<<<Part 4 How to Web API Core set up Service Layer
<<<Part 3 How to Web API Core set up Data Access Layer
<<<Part 2 How to Web API Core set up Database Context
<<<Part 1 How to set up Web API core project

Top comments (2)

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

Tip: Add the language name after the three opening backticks of a code block to obtain syntax highlighting. Your cases would be c# and json. Examples:

namespace Test;

public class MyClass
{
    public string Name { get; private set; }
    public MyClass(string name)
    {
        Name = name;
    }
}
Enter fullscreen mode Exit fullscreen mode
{
    "logging": "on",
    "hosts": "*"
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
scorpio69 profile image
Manik • Edited

@webjose Thank you Jose, I Appreciate it I will update all my bogs.