<repo_name>EbarriosCode/BackendDotnet_CleanArchitecture<file_sep>/Countries.Infra.Data/Data/DbInitializer.cs
﻿using Countries.Infra.Data.DataContext;
using Countries.Models.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace Countries.Infra.Data.Data
{
    public static class DbInitializer
    {
        public static void Initialize(IServiceProvider serviceProvider)
        {
            using (var _context = new CountriesDbContext(serviceProvider.GetRequiredService<DbContextOptions<CountriesDbContext>>()))
            {
                // Add Countries in DB
                if (_context.Countries.Any())                
                    return;
                
                _context.Countries.AddRange(
                    new Country { Name = "Francia", Alpha_2 = "Fr", Alpha_3 = "Fra", NumericCode = "250" },
                    new Country { Name = "Alemania", Alpha_2 = "De", Alpha_3 = "Deu", NumericCode = "276" },
                    new Country { Name = "Guatemala", Alpha_2 = "Gt", Alpha_3 = "Gtm", NumericCode = "320" }
                 );

                _context.SaveChanges();

                // Add Subdivision in Db
                if (_context.Subdivisions.Any())
                    return;

                _context.Subdivisions.AddRange(
                    // Francia
                    new Subdivision { Name = "French Polynesia", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("Fr")).CountryID },
                    new Subdivision { Name = "Saint Barthélemy", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("Fr")).CountryID },
                    new Subdivision { Name = "Saint Martin", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("Fr")).CountryID },
                    new Subdivision { Name = "Saint Pierre and Miquelon", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("Fr")).CountryID },
                    new Subdivision { Name = "Wallis and Futuna", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("Fr")).CountryID },

                    // Alemania
                    new Subdivision { Name = "Baden-Württemberg", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("De")).CountryID },
                    new Subdivision { Name = "Bavaria (Bayern)", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("De")).CountryID },
                    new Subdivision { Name = "Berlin", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("De")).CountryID },
                    new Subdivision { Name = "Brandenburg", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("De")).CountryID },
                    new Subdivision { Name = "Bremen", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("De")).CountryID },

                    // Guatemala
                    new Subdivision { Name = "Suchitepequez", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("Gt")).CountryID },
                    new Subdivision { Name = "<NAME>", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("Gt")).CountryID },
                    new Subdivision { Name = "Quetzaltenango", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("Gt")).CountryID },
                    new Subdivision { Name = "Huehuetenango", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("Gt")).CountryID },
                    new Subdivision { Name = "Izabal", CountryID = _context.Countries.FirstOrDefault(c => c.Alpha_2.Contains("Gt")).CountryID }
                );

                _context.SaveChanges();
            }
        }
    }
}
<file_sep>/Application/Services/CountriesService.cs
﻿using Application.Helpers;
using Application.Interfaces;
using Countries.Common.Classes;
using Countries.Infra.Data.Repositories.Custom;
using Countries.Models.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Application.Services
{
    public class CountriesService : ICountriesService
    {
        private readonly ICountriesRepository _repository;
        private string includeProperties = string.Empty;
        public CountriesService(ICountriesRepository repository) => this._repository = repository;

        public async Task<PagedList<Country>> GetCountries(CountryParameters parameters)
        {            
            try
            {                
                var result = new List<Country>();

                if (parameters != null)
                {
                    #region Filter by Name or Alpha2 Code and Orderby Numeric Code
                    
                    if (!string.IsNullOrEmpty(parameters.Name) && parameters.OrderByNumericCode)
                        result = this._repository.Get(x => x.Name.Contains(parameters.Name), x => x.OrderBy(y => y.NumericCode), includeProperties) as List<Country>;

                    else if (!string.IsNullOrEmpty(parameters.Name) && !parameters.OrderByNumericCode)
                        result = this._repository.Get(x => x.Name.Contains(parameters.Name), null, includeProperties) as List<Country>;

                    else if (!string.IsNullOrEmpty(parameters.Alpha2) && parameters.OrderByNumericCode)
                        result = this._repository.Get(x => x.Alpha_2.Contains(parameters.Alpha2), x => x.OrderBy(y => y.NumericCode), includeProperties) as List<Country>;

                    else if (!string.IsNullOrEmpty(parameters.Alpha2) && !parameters.OrderByNumericCode)
                        result = this._repository.Get(x => x.Alpha_2.Contains(parameters.Alpha2), null, includeProperties) as List<Country>;

                    else if (!string.IsNullOrEmpty(parameters.Name) && !string.IsNullOrEmpty(parameters.Alpha2) && parameters.OrderByNumericCode)
                        result = this._repository.Get(x => x.Name.Contains(parameters.Name) && x.Alpha_2.Contains(parameters.Alpha2), x => x.OrderBy(y => y.NumericCode), includeProperties) as List<Country>;

                    else if (!string.IsNullOrEmpty(parameters.Name) && !string.IsNullOrEmpty(parameters.Alpha2) && !parameters.OrderByNumericCode)
                        result = this._repository.Get(x => x.Name.Contains(parameters.Name) && x.Alpha_2.Contains(parameters.Alpha2), null, includeProperties) as List<Country>;

                    else
                        result = this._repository.Get(null, null, includeProperties) as List<Country>;
                    #endregion
                }

                return await Task.Run(() => PagedList<Country>.ToPagedList(result, parameters.PageNumber, parameters.PageSize));
            }
            catch (Exception)
            {
                throw;
            }
        }

        public async Task<Country> CreateCountryAsync(Country country)
        {
            Country created = new();

            try
            {
                if (country != null)
                {
                    created = await this._repository.CreateAsync(country);
                }
            }
            catch (Exception)
            {
                throw;
            }

            return created;
        }

        public Country GetCountry(int id)
        {
            Country result = null;
            includeProperties = "Subdivisions";

            try
            {
                List<Country> country = this._repository.Get(x => x.CountryID == id, null, includeProperties) as List<Country>;

                if (country.Count > 0)
                    result = country[0];
            }
            catch (Exception)
            {
                throw;
            }

            return result;
        }

        public async Task<Country> UpdateCountryAsync(Country country)
        {
            Country updated = new();

            try
            {
                if(country != null)
                {
                    updated = await this._repository.UpdateAsync(country, country.CountryID);                    
                }
            }
            catch (Exception)
            {
                throw;
            }

            return updated;
        }

        public async Task<int> DeleteCountryAsync(Country country)
        {
            int deleted = 0;

            try
            {
                if (country != null)
                    deleted = await this._repository.DeleteAsync(country);                
            }
            catch (Exception)
            {
                throw;
            }

            return deleted;
        }
    }
}
<file_sep>/Application/Interfaces/ISubdivisionsService.cs
﻿using Application.Helpers;
using Countries.Common.Classes;
using Countries.Models.Models;
using System.Threading.Tasks;

namespace Application.Interfaces
{
    public interface ISubdivisionsService
    {
        Task<PagedList<Subdivision>> GetSubdivisions(SubdivisionParameter parameters);
        Subdivision GetSubdivision(int id);
        Task<Subdivision> CreateSubdivisionAsync(Subdivision subdivision);
        Task<Subdivision> UpdateSubdivisionAsync(Subdivision subdivision);
        Task<int> DeleteSubdivisionAsync(Subdivision subdivision);
    }
}
<file_sep>/Application/DTO/CountryDTO.cs
﻿using Countries.Models.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Application.DTO
{
    public class CountryDTO : Country
    {
    }
}
<file_sep>/Countries.Infra.Data/Repositories/Custom/CountriesRepository.cs
﻿using Countries.Infra.Data.Repositories.Generic;
using Countries.Models.Models;

namespace Countries.Infra.Data.Repositories.Custom
{
    public interface ICountriesRepository : IBaseRepository<Country>
    {
    }
    public class CountriesRepository : BaseRepository<Country>, ICountriesRepository
    {
        public CountriesRepository(IUnitOfWork _unitOfWork)
            : base(_unitOfWork)
        { }
    }
}
<file_sep>/Countries.Common/Classes/SubdivisionParameter.cs
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Countries.Common.Classes
{
    public class SubdivisionParameter : PagingParameters
    {
    }
}
<file_sep>/IoC/DependencyContainer.cs
﻿using Application.Interfaces;
using Application.Services;
using Countries.Infra.Data.Repositories.Custom;
using Countries.Infra.Data.Repositories.Generic;
using Microsoft.Extensions.DependencyInjection;

namespace IoC
{
    public static class DependencyContainer
    {
        public static IServiceCollection AddDependency(this IServiceCollection services)
        {
            // Inject the service generic repository
            services.AddScoped<IUnitOfWork, UnitOfWork>();

            // Inject the service JWT
            services.AddTransient<IJWTService, JWTService>();

            // Inject the service Countries            
            services.AddScoped<ICountriesService, CountriesService>();
            services.AddScoped<ICountriesRepository, CountriesRepository>();

            // Inject the service Subdivisions            
            services.AddScoped<ISubdivisionsService, SubdivisionsService>();
            services.AddScoped<ISubdivisionsRepository, SubdivisionsRepository>();

            return services;
        }
    }
}
<file_sep>/Countries.Models/Models/Subdivision.cs
﻿using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Countries.Models.Models
{
    [Table("Subdivison")]
    public class Subdivision
    {
        [Key]
        public int SubdivisonID { get; set; }

        [Required(ErrorMessage = "The field {0} is required")]
        [StringLength(30)]
        public string Name { get; set; }

        public int CountryID { get; set; }
    }
}
<file_sep>/Countries.Common/Resources/Literals.cs
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Countries.Common.Resources
{
    public class Literals
    {
        public static string URI_API_COUNTRIES = "api/Countries";
        public static string URI_API_SUBDIVISIONS = "api/Subdivisions";
    }
}
<file_sep>/Application/Interfaces/IJWTService.cs
﻿using Countries.Common.Classes;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Application.Interfaces
{
    public interface IJWTService
    {
        Task<UserToken> BuildToken(UserInfo userInfo);
        JwtSecurityToken DecodeJwt(string jwt);
    }
}
<file_sep>/Countries.API/Startup.cs
using Countries.Infra.Data.DataContext;
using IoC;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using System;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace Countries.API
{
    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)
        {
            // Add Entity Framework DbContext
            services.AddDbContext<CountriesDbContext>(options => options.UseSqlServer(this.Configuration.GetConnectionString("DefaultConnection")));

            services.AddControllersWithViews();           
            services.AddRazorPages();

            // Indentity Server Configuration
            services.AddIdentity<IdentityUser, IdentityRole>()
                    .AddRoles<IdentityRole>()
                    .AddEntityFrameworkStores<CountriesDbContext>()
                    .AddDefaultTokenProviders();

            // Configuración JWT
            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
                    {
                        options.TokenValidationParameters = new TokenValidationParameters
                        {
                            ValidateIssuer = true,
                            ValidateAudience = true,
                            ValidateLifetime = true,
                            ValidateIssuerSigningKey = true,
                            ValidIssuer = Configuration["JwtIssuer"],
                            ValidAudience = Configuration["JwtAudience"],
                            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSecurityKey"])),
                            ClockSkew = TimeSpan.Zero
                        };
                    });

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo 
                { 
                    Title = "Countries.API", 
                    Version = "v1" ,
                    Contact = new OpenApiContact
                    {
                        Name = "<NAME>",
                        Email = "dev_<EMAIL>",
                        Url = new Uri("https://twitter.com/ebarriosdev"),
                    }                    
                });
            });

            // Init IoC Services
            DependencyContainer.AddDependency(services);

            // EnableCors
            services.AddCors(policy =>
            {
                policy.AddPolicy("CorsPolicy", opt => opt
                 .AllowAnyOrigin()
                 .AllowAnyHeader()
                 .AllowAnyMethod()
                 .WithExposedHeaders("X-Pagination"));
            });
        }

        private async Task CreateRoles(IServiceProvider serviceProvider)
        {
            var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();            
            bool adminRoleExists = await RoleManager.RoleExistsAsync("Admin");

            if (!adminRoleExists)
                await RoleManager.CreateAsync(new IdentityRole("Admin"));            
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider services)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Countries.API v1"));
            }

            JsonConvert.DefaultSettings = () => new JsonSerializerSettings
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            };

            app.UseHttpsRedirection();
            app.UseBlazorFrameworkFiles();
            app.UseStaticFiles();

            app.UseRouting();
            app.UseCors(options => options.AllowAnyOrigin());

            app.UseAuthentication();
            app.UseAuthorization();

            // Create Roles in Database
            CreateRoles(services).Wait();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapControllers();
                endpoints.MapFallbackToFile("index.html");
            });
        }
    }
}
<file_sep>/Countries.SPA/Services/CountriesHttpService.cs
﻿using Application.DTO;
using Application.Helpers;
using Countries.Common.Classes;
using Countries.Common.Resources;
using Countries.SPA.Helpers.Paging;
using Microsoft.AspNetCore.WebUtilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;

namespace Countries.SPA.Services
{
    public interface ICountriesHttpService
    {
        Task<PagingResponse<CountryDTO>> GetCountriesDTOAsync(CountryParameters parameters);
        Task<List<CountryDTO>> GetCountriesDTOAsync();
        Task<CountryDTO> GetByID(int id);
        Task<CountryDTO> Create(CountryDTO model);
        Task<CountryDTO> Update(CountryDTO model);
        Task<int> Delete(int id);
    }

    public class CountriesHttpService : ICountriesHttpService
    {
        private readonly HttpClient _client;        
        public CountriesHttpService(HttpClient client) => this._client = client;

        public async Task<PagingResponse<CountryDTO>> GetCountriesDTOAsync(CountryParameters parameters)
        {
            var queryStringParam = new Dictionary<string, string>
            {
                ["pageNumber"] = parameters.PageNumber.ToString()
            };

            var response = await _client.GetAsync(QueryHelpers.AddQueryString(Literals.URI_API_COUNTRIES, queryStringParam));
            var content = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                throw new ApplicationException(content);
            }
            var pagingResponse = new PagingResponse<CountryDTO>
            {
                Items = JsonSerializer.Deserialize<List<CountryDTO>>(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }),
                MetaData = JsonSerializer.Deserialize<MetaData>(response.Headers.GetValues("X-Pagination").First(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
            };

            return pagingResponse;            
        }

        public async Task<List<CountryDTO>> GetCountriesDTOAsync()
        {
            var result = await this._client.GetFromJsonAsync<List<CountryDTO>>(Literals.URI_API_COUNTRIES);
            return await Task.FromResult(result);
        }

        public async Task<CountryDTO> GetByID(int id)
        {
            var response = await this._client.GetFromJsonAsync<CountryDTO>($"{Literals.URI_API_COUNTRIES}/{id}");
            return response;
        }

        public async Task<CountryDTO> Create(CountryDTO model)
        {
            // Insertar en el api
            var response = await this._client.PostAsJsonAsync(Literals.URI_API_COUNTRIES, model);

            if (response.StatusCode == HttpStatusCode.Created)
            {
                model = await response.Content.ReadFromJsonAsync<CountryDTO>();                
                return model;
            }
            else
            {
                string str = await response.Content.ReadAsStringAsync();
                throw new Exception(str);
            }
        }

        public async Task<CountryDTO> Update(CountryDTO model)
        {            
            var response = await _client.PutAsJsonAsync(Literals.URI_API_COUNTRIES, model);

            if (response.StatusCode == HttpStatusCode.OK)
            {
                model = await response.Content.ReadFromJsonAsync<CountryDTO>();               
                return model;
            }
            else
            {
                string str = await response.Content.ReadAsStringAsync();
                throw new Exception(str);
            }
        }

        public async Task<int> Delete(int id)
        {
            // Borrar del Api
            var response = await _client.DeleteAsync($"{Literals.URI_API_COUNTRIES}/{id}");

            if (response.StatusCode == HttpStatusCode.NoContent)    
                return 1;
            else
            {
                string str = await response.Content.ReadAsStringAsync();
                throw new Exception(str);
            }
        }
    }
}
<file_sep>/Countries.Common/Classes/CountryParameters.cs
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Countries.Common.Classes
{
    public class CountryParameters : PagingParameters
    {        
        public string Name { get; set; }
        public string Alpha2 { get; set; }
        public bool OrderByNumericCode { get; set; }
    }
}
<file_sep>/Countries.Models/Models/Country.cs
﻿using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;

namespace Countries.Models.Models
{
    [Table("Country")]
    public class Country
    {
        [Key]
        public int CountryID { get; set; }

        [Required(ErrorMessage = "The field {0} is required")]
        [StringLength(30)]
        public string Name { get; set; }

        [StringLength(2)]
        public string Alpha_2 { get; set; }

        [StringLength(3)]
        public string Alpha_3 { get; set; }

        [StringLength(3)]
        public string NumericCode { get; set; }
        public ICollection<Subdivision> Subdivisions { get; set; }
    }
}
<file_sep>/Countries.Infra.Data/DataContext/CountriesDbContext.cs
﻿using Countries.Models.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace Countries.Infra.Data.DataContext
{
    public class CountriesDbContext : IdentityDbContext
    {
        public CountriesDbContext(DbContextOptions<CountriesDbContext> options)
            : base(options) { }

        public DbSet<Country> Countries { get; set; }
        public DbSet<Subdivision> Subdivisions { get; set; }
    }
}
<file_sep>/Countries.SPA/Services/SubdivisionsHttpService.cs
﻿using Application.DTO;
using Application.Helpers;
using Countries.Common.Classes;
using Countries.Common.Resources;
using Countries.SPA.Helpers.Paging;
using Microsoft.AspNetCore.WebUtilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;

namespace Countries.SPA.Services
{
    public interface ISubdivisionsHttpService
    {
        Task<PagingResponse<SubdivisionDTO>> GetSubdivisionsDTOAsync(SubdivisionParameter parameters);
        Task<SubdivisionDTO> GetByID(int id);
        Task<SubdivisionDTO> Create(SubdivisionDTO model);
        Task<SubdivisionDTO> Update(SubdivisionDTO model);
        Task<int> Delete(int id);
    }
    public class SubdivisionsHttpService : ISubdivisionsHttpService
    {
        private readonly HttpClient _client;
        public SubdivisionsHttpService(HttpClient client) => this._client = client;

        public async Task<PagingResponse<SubdivisionDTO>> GetSubdivisionsDTOAsync(SubdivisionParameter parameters)
        {
            var queryStringParam = new Dictionary<string, string>
            {
                ["pageNumber"] = parameters.PageNumber.ToString()
            };

            var response = await _client.GetAsync(QueryHelpers.AddQueryString(Literals.URI_API_SUBDIVISIONS, queryStringParam));
            var content = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                throw new ApplicationException(content);
            }
            var pagingResponse = new PagingResponse<SubdivisionDTO>
            {
                Items = JsonSerializer.Deserialize<List<SubdivisionDTO>>(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }),
                MetaData = JsonSerializer.Deserialize<MetaData>(response.Headers.GetValues("X-Pagination").First(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
            };

            return pagingResponse;
        }

        public async Task<SubdivisionDTO> GetByID(int id)
        {
            var response = await this._client.GetFromJsonAsync<SubdivisionDTO>($"{Literals.URI_API_SUBDIVISIONS}/{id}");
            return response;
        }

        public async Task<SubdivisionDTO> Create(SubdivisionDTO model)
        {
            // Insertar en el api
            var response = await this._client.PostAsJsonAsync(Literals.URI_API_SUBDIVISIONS, model);

            if (response.StatusCode == HttpStatusCode.Created)
            {
                model = await response.Content.ReadFromJsonAsync<SubdivisionDTO>();
                return model;
            }
            else
            {
                string str = await response.Content.ReadAsStringAsync();
                throw new Exception(str);
            }
        }

        public async Task<SubdivisionDTO> Update(SubdivisionDTO model)
        {
            var response = await _client.PutAsJsonAsync(Literals.URI_API_SUBDIVISIONS, model);

            if (response.StatusCode == HttpStatusCode.OK)
            {
                model = await response.Content.ReadFromJsonAsync<SubdivisionDTO>();
                return model;
            }
            else
            {
                string str = await response.Content.ReadAsStringAsync();
                throw new Exception(str);
            }
        }

        public async Task<int> Delete(int id)
        {
            // Borrar del Api
            var response = await _client.DeleteAsync($"{Literals.URI_API_SUBDIVISIONS}/{id}");

            if (response.StatusCode == HttpStatusCode.NoContent)
                return 1;
            else
            {
                string str = await response.Content.ReadAsStringAsync();
                throw new Exception(str);
            }
        }
    }
}<file_sep>/Countries.UnitTests/CountriesControllerTests.cs
using Application.Interfaces;
using Countries.API.Controllers;
using Countries.Models.Models;
using Microsoft.AspNetCore.Mvc;
using Moq;
using System.Threading.Tasks;
using Xunit;

namespace Countries.UnitTests
{
    public class CountriesControllerTests
    {
        [Fact]
        public async Task Create_ReturnsBadRequest_GivenInvalidModel()
        {
            // Arrange
            var mockRepo = new Mock<ICountriesService>();

            var controller = new CountriesController(mockRepo.Object);
            controller.ModelState.AddModelError("error", "some error");

            // Act            
            var result = await controller.PostCountry(country: null);

            // Assert
            Assert.IsType<BadRequestObjectResult>(result.Result);
        }
        
        [Fact]
        public async Task GetCountry_IsOfType_ActionResult_Country()
        {
            // Arrange
            var mockRepo = new Mock<ICountriesService>();
            var controller = new CountriesController(mockRepo.Object);            

            // Act
            var result = await controller.GetCountry(45);

            // Assert
            Assert.IsType<ActionResult<Country>>(result);            
        }       
    }
}
<file_sep>/Countries.API/Controllers/SubdivisionsController.cs
﻿using Application.Interfaces;
using Countries.Common.Classes;
using Countries.Models.Models;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;

namespace Countries.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class SubdivisionsController : ControllerBase
    {
        private readonly ISubdivisionsService _service;

        public SubdivisionsController(ISubdivisionsService service) => this._service = service;

        // GET: api/Subdivisions
        [HttpGet]
        public async Task<IEnumerable<Subdivision>> GetCountries([FromQuery] SubdivisionParameter parameters)
        {
            if (parameters == null)
                return null;

            var subdivisions = await this._service.GetSubdivisions(parameters);
            Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(subdivisions.MetaData));

            return subdivisions;
        }

        // GET: api/Subdivisions/5
        [HttpGet("{SubdivisionID}")]
        public async Task<ActionResult<Subdivision>> GetSubdivision(int SubdivisionID)
        {
            if (SubdivisionID <= 0)
                return BadRequest("Invalid ID");

            var subdivision = await Task.Run(() => this._service.GetSubdivision(SubdivisionID));

            if (subdivision == null)
                return NotFound();

            return subdivision;
        }

        // POST: api/Subdivisions
        [HttpPost]
        public async Task<ActionResult<Subdivision>> PostSubdivision([FromBody] Subdivision subdivision)
        {
            if (subdivision == null)
                return BadRequest("Error when trying to create the country");

            await this._service.CreateSubdivisionAsync(subdivision);

            return StatusCode((int)HttpStatusCode.Created, subdivision);
        }

        // PUT: api/Subdivisions        
        [HttpPut]
        public async Task<IActionResult> PutSubdivision([FromBody] Subdivision subdivision)
        {
            if (subdivision == null)
                return BadRequest();

            var updated = await this._service.UpdateSubdivisionAsync(subdivision);
            return Ok(updated);
        }

        // DELETE: api/Subdivisions/5
        [HttpDelete("{SubdivisionID}")]
        public async Task<IActionResult> DeleteSubdivision(int SubdivisionID)
        {
            if (SubdivisionID <= 0)
                return BadRequest("Invalid ID");

            var subdivision = this._service.GetSubdivision(SubdivisionID);

            if (subdivision == null)
                return NotFound();

            await this._service.DeleteSubdivisionAsync(subdivision);
            return NoContent();
        }
    }
}<file_sep>/Countries.Infra.Data/Repositories/Generic/BaseRepository.cs
﻿using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace Countries.Infra.Data.Repositories.Generic
{
    public interface IBaseRepository<T> where T : class
    {
        IEnumerable<T> Get(Expression<Func<T, bool>> whereCondition = null,
                           Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
                           string includeProperties = "");
        Task<T> CreateAsync(T entity);
        Task<T> UpdateAsync(T entity, object key);
        Task<int> DeleteAsync(T entity);
    }
    public abstract class BaseRepository<T> : IBaseRepository<T> where T : class
    {
        private readonly IUnitOfWork _unitOfWork;

        public BaseRepository(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }

        public IEnumerable<T> Get(Expression<Func<T, bool>> whereCondition = null,
                                  Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
                                  string includeProperties = "")
        {
            IQueryable<T> query = _unitOfWork.Context.Set<T>();

            if (whereCondition != null)
            {
                query = query.Where(whereCondition);
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            if (orderBy != null)
            {
                return orderBy(query).ToList();
            }
            else
            {
                return query.ToList();
            }
        }

        public virtual async Task<T> CreateAsync(T model)
        {
            _unitOfWork.Context.Set<T>().Add(model);
            await _unitOfWork.Context.SaveChangesAsync();

            return model;
        }

        public virtual async Task<T> UpdateAsync(T model, object key)
        {
            if (model == null)
                return null;

            T exist = await _unitOfWork.Context.Set<T>().FindAsync(key);

            if (exist != null)
            {
                _unitOfWork.Context.Entry(exist).CurrentValues.SetValues(model);
                await _unitOfWork.Context.SaveChangesAsync();
            }

            return exist;
        }

        public virtual async Task<int> DeleteAsync(T model)
        {
            _unitOfWork.Context.Set<T>().Remove(model);
            return await _unitOfWork.Context.SaveChangesAsync();
        }
    }
}
<file_sep>/Application/Services/SubdivisionsService.cs
﻿using Application.Helpers;
using Application.Interfaces;
using Countries.Common.Classes;
using Countries.Infra.Data.Repositories.Custom;
using Countries.Models.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Application.Services
{
    public class SubdivisionsService : ISubdivisionsService
    {
        private readonly ISubdivisionsRepository _repository;        
        public SubdivisionsService(ISubdivisionsRepository repository) => this._repository = repository;

        public async Task<PagedList<Subdivision>> GetSubdivisions(SubdivisionParameter parameters)
        {
            try
            {
                var result = new List<Subdivision>();

                if (parameters != null)
                {

                    result = this._repository.Get(null, null, string.Empty) as List<Subdivision>;
                }

                return await Task.Run(() => PagedList<Subdivision>.ToPagedList(result, parameters.PageNumber, parameters.PageSize));
            }
            catch (Exception)
            {
                throw;
            }
        }

        public async Task<Subdivision> CreateSubdivisionAsync(Subdivision subdivision)
        {
            Subdivision created = new();

            try
            {
                if (subdivision != null)
                {
                    created = await this._repository.CreateAsync(subdivision);
                }
            }
            catch (Exception)
            {
                throw;
            }

            return created;
        }

        public Subdivision GetSubdivision(int id)
        {
            Subdivision result = null;
           
            try
            {
                List<Subdivision> subdivision = this._repository.Get(x => x.SubdivisonID == id, null, string.Empty) as List<Subdivision>;

                if (subdivision.Count > 0)
                    result = subdivision[0];
            }
            catch (Exception)
            {
                throw;
            }

            return result;
        }

        public async Task<Subdivision> UpdateSubdivisionAsync(Subdivision subdivision)
        {
            Subdivision updated = new();

            try
            {
                if (subdivision != null)
                {
                    updated = await this._repository.UpdateAsync(subdivision, subdivision.SubdivisonID);
                }
            }
            catch (Exception)
            {
                throw;
            }

            return updated;
        }

        public async Task<int> DeleteSubdivisionAsync(Subdivision subdivision)
        {
            int deleted = 0;

            try
            {
                if (subdivision != null)
                    deleted = await this._repository.DeleteAsync(subdivision);
            }
            catch (Exception)
            {
                throw;
            }

            return deleted;
        }
    }
}
<file_sep>/Countries.Infra.Data/Migrations/20210307181132_Countries_and_Subdivisions_Models.cs
﻿using Microsoft.EntityFrameworkCore.Migrations;

namespace Countries.Infra.Data.Migrations
{
    public partial class Countries_and_Subdivisions_Models : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Country",
                columns: table => new
                {
                    CountryID = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Name = table.Column<string>(type: "nvarchar(30)", maxLength: 30, nullable: false),
                    Alpha_2 = table.Column<string>(type: "nvarchar(2)", maxLength: 2, nullable: true),
                    Alpha_3 = table.Column<string>(type: "nvarchar(3)", maxLength: 3, nullable: true),
                    NumericCode = table.Column<string>(type: "nvarchar(3)", maxLength: 3, nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Country", x => x.CountryID);
                });

            migrationBuilder.CreateTable(
                name: "Subdivison",
                columns: table => new
                {
                    SubdivisonID = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Name = table.Column<string>(type: "nvarchar(30)", maxLength: 30, nullable: false),
                    CountryID = table.Column<int>(type: "int", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Subdivison", x => x.SubdivisonID);
                    table.ForeignKey(
                        name: "FK_Subdivison_Country_CountryID",
                        column: x => x.CountryID,
                        principalTable: "Country",
                        principalColumn: "CountryID",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateIndex(
                name: "IX_Subdivison_CountryID",
                table: "Subdivison",
                column: "CountryID");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Subdivison");

            migrationBuilder.DropTable(
                name: "Country");
        }
    }
}
<file_sep>/Countries.SPA/Middleware/IoC.cs
﻿using Countries.SPA.Services;
using Countries.SPA.StateProvider;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace Countries.SPA.Middleware
{
    public static class IoC
    {
        public static WebAssemblyHostBuilder AddDependency(this WebAssemblyHostBuilder builder)
        {
            // Configuración del AuthorizationStateProvider
            builder.Services.AddScoped<JWTAuthStateProvider>();
            builder.Services.AddScoped<AuthenticationStateProvider, JWTAuthStateProvider>(provider => provider.GetRequiredService<JWTAuthStateProvider>());
            builder.Services.AddScoped<ILoginService, JWTAuthStateProvider>(provider => provider.GetRequiredService<JWTAuthStateProvider>());

            // Inject CountryService
            builder.Services.AddScoped<ICountriesHttpService, CountriesHttpService>();
            builder.Services.AddScoped<ISubdivisionsHttpService, SubdivisionsHttpService>();

            return builder;
        }
    }
}
<file_sep>/Application/Services/JWTService.cs
﻿using Application.Interfaces;
using Countries.Common.Classes;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace Application.Services
{
    public class JWTService : IJWTService
    {
        private readonly UserManager<IdentityUser> _userManager;
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly IConfiguration _configuration;

        public JWTService(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, IConfiguration configuration)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _configuration = configuration;
        }
        public async Task<UserToken> BuildToken(UserInfo userInfo)
        {
            var user = await _signInManager.UserManager.FindByEmailAsync(userInfo.Email);
            var roles = await _signInManager.UserManager.GetRolesAsync(user);

            var claims = new List<Claim>();

            claims.Add(new Claim(JwtRegisteredClaimNames.UniqueName, userInfo.Email));
            claims.Add(new Claim(ClaimTypes.Name, user.Email));
            claims.Add(new Claim(JwtRegisteredClaimNames.NameId, user.Id));

            foreach (var role in roles)
            {
                claims.Add(new Claim(ClaimTypes.Role, role));
            }

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtSecurityKey"]));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            // Tiempo de expiración del token. En nuestro caso lo hacemos de una hora.
            var expiration = DateTime.UtcNow.AddYears(1);

            JwtSecurityToken token = new JwtSecurityToken(
               _configuration["JwtIssuer"],
               _configuration["JwtAudience"],
               claims: claims,
               expires: expiration,
               signingCredentials: creds);

            return new UserToken()
            {
                Token = new JwtSecurityTokenHandler().WriteToken(token),
                Expiration = expiration
            };
        }

        public JwtSecurityToken DecodeJwt(string jwt)
        {
            var key = Encoding.ASCII.GetBytes(this._configuration.GetSection("JwtSecurityKey").Value);
            var handler = new JwtSecurityTokenHandler();

            var validations = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false
            };

            var claims = handler.ValidateToken(jwt, validations, out var tokenSecure);
            return (JwtSecurityToken)tokenSecure;
        }
    }
}
<file_sep>/Application/Interfaces/ICountriesService.cs
﻿using Application.Helpers;
using Countries.Common.Classes;
using Countries.Models.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Application.Interfaces
{
    public interface ICountriesService
    {
        Task<PagedList<Country>> GetCountries(CountryParameters parameters);
        Country GetCountry(int id);
        Task<Country> CreateCountryAsync(Country country);
        Task<Country> UpdateCountryAsync(Country country);
        Task<int> DeleteCountryAsync(Country country);
    }
}
<file_sep>/Countries.Infra.Data/Repositories/Generic/UnitOfWork.cs
﻿using Countries.Infra.Data.DataContext;
using System;

namespace Countries.Infra.Data.Repositories.Generic
{
    public interface IUnitOfWork : IDisposable
    {
        CountriesDbContext Context { get; }
        void Commit();
    }
    public class UnitOfWork : IUnitOfWork
    {
        public CountriesDbContext Context { get; }

        public UnitOfWork(CountriesDbContext context)
        {
            Context = context;
        }

        public void Commit()
        {
            Context.SaveChanges();
        }

        public void Dispose()
        {
            Context.Dispose();
        }
    }
}
<file_sep>/Countries.Infra.Data/Repositories/Custom/SubdivisionsRepository.cs
﻿using Countries.Infra.Data.Repositories.Generic;
using Countries.Models.Models;

namespace Countries.Infra.Data.Repositories.Custom
{
    public interface ISubdivisionsRepository : IBaseRepository<Subdivision>
    {
    }
    public class SubdivisionsRepository : BaseRepository<Subdivision>, ISubdivisionsRepository
    {
        public SubdivisionsRepository(IUnitOfWork _unitOfWork)
            : base(_unitOfWork)
        { }
    }
}
<file_sep>/Application/DTO/SubdivisionDTO.cs
﻿using Countries.Models.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Application.DTO
{
    public class SubdivisionDTO : Subdivision
    {
    }
}
<file_sep>/Countries.API/Controllers/CountriesController.cs
﻿using Application.Helpers;
using Application.Interfaces;
using Countries.Common.Classes;
using Countries.Models.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

namespace Countries.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class CountriesController : ControllerBase
    {
        private readonly ICountriesService _service;

        public CountriesController(ICountriesService service) => this._service = service;

        // GET: api/Countries
        [HttpGet]
        public async Task<IEnumerable<Country>> GetCountries([FromQuery] CountryParameters parameters)
        {
            if (parameters == null)
                return null;

            var countries = await this._service.GetCountries(parameters);
            Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(countries.MetaData));

            return countries;
        }

        // GET: api/Countries/5
        [HttpGet("{CountryID}")]
        public async Task<ActionResult<Country>> GetCountry(int CountryID)
        {
            if (CountryID <= 0)
                return BadRequest("Invalid ID");

            var country = await Task.Run(() => this._service.GetCountry(CountryID));

            if (country == null)
                return NotFound();            

            return country;
        }

        // POST: api/Countries
        [HttpPost]
        public async Task<ActionResult<Country>> PostCountry([FromBody] Country country)
        {
            if (country == null)
                return BadRequest("Error when trying to create the country");

            await this._service.CreateCountryAsync(country);

            return StatusCode((int)HttpStatusCode.Created, country);
        }

        // PUT: api/Countries        
        [HttpPut]
        public async Task<IActionResult> PutCountry([FromBody] Country country)
        {
            if (country == null)
                return BadRequest();

            var updated = await this._service.UpdateCountryAsync(country);
            return Ok(updated);
        }

        // DELETE: api/Countries/5
        [HttpDelete("{CountryID}")]
        public async Task<IActionResult> DeleteCountry(int CountryID)
        {
            if (CountryID <= 0)
                return BadRequest("Invalid ID");

            var country = this._service.GetCountry(CountryID);

            if (country == null)
                return NotFound();

            await this._service.DeleteCountryAsync(country);
            return NoContent();
        }
    }
}
<file_sep>/Countries.API/Controllers/AccountsController.cs
﻿using Application.Interfaces;
using Countries.Common.Classes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Countries.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AccountsController : ControllerBase
    {
        private readonly UserManager<IdentityUser> _userManager;
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly IConfiguration _configuration;
        private readonly IJWTService _jwtService;

        public AccountsController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, IConfiguration configuration, IJWTService jwtService)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _configuration = configuration;
            _jwtService = jwtService;
        }

        [HttpPost]
        public async Task<ActionResult<UserToken>> Create([FromBody] UserInfo model)
        {
            var user = new IdentityUser { UserName = model.Email, Email = model.Email };
            var result = await _userManager.CreateAsync(user, model.Password);
            
            if (result.Succeeded)
            {
                // All new users are created with the Admin role
                await _userManager.AddToRoleAsync(user, "Admin");
                
                return Ok("Usuario Creado");
            }
            else
            {
                StringBuilder errors = new();
                foreach (var item in result.Errors)
                    errors.AppendLine($"{item.Description}");

                return BadRequest(errors.ToString());
            }
        }

        [HttpPost("Login")]
        public async Task<ActionResult<UserToken>> Login([FromBody] UserInfo userInfo)
        {
            var result = await _signInManager.PasswordSignInAsync(userInfo.Email, userInfo.Password, isPersistent: false, lockoutOnFailure: false);

            if (result.Succeeded)
            {
                return await this._jwtService.BuildToken(userInfo);
            }
            else
            {
                //ModelState.AddModelError("Error", "Intento de inicio de sesión no válido.");
                return BadRequest("Intento de inicio de sesión no válido.");
            }
        }
    }
}
