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.
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; }
}
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; }
}
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; }
Controllers
To Add a controller right click the controller folder in solution explorer and select Add->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.
Next we are going to name our controller, so our first controller is going to be Address as in image below.
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;
}
}
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);
}
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();
}
}
}
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);
}
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;
}
}
}
Repo AddressType Interface
public interface IAddressTypeRepository
{
Task<ICollection<Entities.AddressType>> GetAddressesType();
Task<Entities.AddressType> GetAddressTypeByIDAsync(int addressTypeId);
}
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);
}
}
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);
}
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);
}
}
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" });
});
}
Lets us try out HttpGet for our address api
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.
Lets us try out HttpPost for our address api
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
}
and api response after adding an address for a company is below.
and if we call HttpGet for address with company GUID we can now retrive address record for a company.
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
}
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)
Tip: Add the language name after the three opening backticks of a code block to obtain syntax highlighting. Your cases would be
c#
andjson
. Examples:@webjose Thank you Jose, I Appreciate it I will update all my bogs.