Web Api
Diferencias con .NET Core
Caracteríaticas principales
Cuando usar .NET Core
Cuando no usar .NET Core
Performance
Demo 1
dotnet new console --output demo1
dotnet run --project demo1
Demo 2.
Creación de un proyecto .NET Core con linea de comandos
Demo 2.
Creación de un proyecto .NET Core con linea de comandos
Se trata de cualquier interfaz que utilice el protocolo HTTP para intercambiar información
Características
Single Responsibility Principle
El principio de Responsabilidad Única viene a decirnos que un objeto debe de realizar un solo propósito
¿Cuando sabemos que estamos violando el Principio de Responsabilidad Única?
Open / Closed Principle
Nuestros objetos deben de estar abiertos a ser extendidos y no a ser modificados. Nuevos requisitos o funcionalidad debe implicar la creación de nuevo código, no la alteración del ya existente
¿Como detectamos que estamos violando el Principio Open / Closed?
Si cada vez que añadimos un nuevo requisito, se ven afectadas a modificación las mismas clases
Ejemplo viola el Principio Open / Closed
public enum TipoVehiculo
{
Coche,
Moto
}
public class Vehiculo()
{
public TipoVehiculo Type { get; set; }
public Draw()
{
switch (Type)
{
case Coche:
DrawCoche();
break;
case Moto:
DrawMoto();
break;
}
}
}
Ejemplo que cumple el Principio Open / Closed
public abstract class Vehiculo()
{
public virtual void Draw() { }
}
public class Coche : Vehiculo
{
public override void Draw()
{
DrawCoche();
}
}
public class Moto : Vehiculo
{
public override void Draw()
{
DrawMoto();
}
}
Liskov Substitution Principle
El principio de sustitución de Liskov viene a decir que si extendemos una clase, nuestro código debe de funcionar de igual forma con la clase hija que con su padre.
Este principio nos ayudará a utilizar la herencia de forma correcta.
¿Cómo podemos identificar que estamos violando el Principio de Sustitución de Liskov?
Interface Segregation Principle
Cuando creamos interfaces que definan los contratos de nuestras clases, todas las clases que las implementen, deberán de agregar comportamientos a todos los métodos del contrato
¿Cuando sabemos que estamos violando el principio de segregación de interfaces?
Si al implementar nuestra interfaz en una clase, vemos que alguno de los métodos a implementar no tienen sentido en nuestra clase. Como solución, debemos dividir la interfaz inicial a varias interfaces más específicas
Ejemplo que viola el principio de segregación de interfaces
public interface IProduct
{
int GetRecommendedAge();
}
public class DVD : IProduct
{
public int GetRecommendedAge()
{
}
}
public class CD : IProduct
{
public int GetRecommendedAge()
{
// No tiene sentido este método
}
}
Ejemplo que cumple el principio de segregación de interfaces
public interface IProduct
{
int GetRecommendedAge();
}
public interface IAgeRecommender
{
int GetRecommendedAge();
}
public class DVD : IProduct, IAgeRecommender
{
public int GetRecommendedAge()
{
}
}
public class CD : IProduct
{
}
Dependency Inversion
Debemos abstraer a través de interfaces la implementación concreta de nuestros servicios, de forma, que el núcleo de nuestro código no dependa de las implementaciones, sino únicamente de contratos
¿Cómo saber que estamos violando el principio de inversión de dependencias?
Cualquier instancia de una clase compleja dentro de otra, hace que violemos este principio. La mejor manera de darse cuenta es cuando escribimos tests. Cuando no podemos probar una clase con facilidad, ya que depende de otra clase, es un claro ejemplo de violación del principio de inversión de dependencias
Ejemplo que no cumple el principio Dependency Inversion
public class CestaCompra
{
public class CheckOut(Producto producto)
{
var baseDeDatos = new BaseDeDatos();
baseDeDatos.Guardar(producto);
var pago = new CreditCard();
pago.Pagar(producto);
}
}
public class CreditCard
{
public void Pagar(Producto producto)
{
}
}
public class BaseDeDatos()
{
public class Guardar(Producto producto)
{
}
}
Ejemplo que cumple el principio Dependency Inversion
// El núcleo de nuestra app es independiente de las implementaciones de los contratos
public class CestaCompra
{
private readonly IDataAcces _dataAccess;
private readonly IPaymentMethod _paymentMethod;
public CestaCompra(IDataAccess dataAccess, IPaymentMethod payment)
{
_dataAccess = dataAccess;
_paymentMethod = paymentMethod;
}
public class CheckOut(Producto producto)
{
_dataAccess.Guardar(producto);
_paymentMethod.Pagar(producto);
}
}
public interface IPaymentMethod
{
void Pagar(Producto producto);
}
public interface IDataAcces
{
void Guardar(Producto producto);
}
La única desventaja es que al principio es difícil de adoptar sobre todo por la gran cantidad de comandos y operaciones que puedes hacer
Es recomendable utilizar un cliente de Git para la gestión del código. Veamos algunas recomendaciones:
Instalar un cliente de Git y realizar un pequeño ejemplo que revise los comandos básicos que hemos utilizado
Framework basado en la arquitectura MVC para compilar aplicaciones web y Web APIs
Model-View-Controller
ASP.NET Core MVC
Vamos a crear un proyecto nuevo de ASP.NET Core Web Api y vamos a subirlo a un repositorio de Git
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Se trata del WebHost (servidor) multi-plataforma que ofrece ASP.NET Core para desplegar nuestro site
Podemos utilizar Krestel por si solo o configurar un reverse proxy
Ventajas de utilizar un Reverse Proxy Server
Esta clase es en la que definimos el pipeline de las peticiones Http y la configuración de los servicios de nuestra app
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)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
Método ConfigureServices
Método ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
Método Configure
Se trata del método que define como vamos a tratar los requests Http que reciba nuestra app.
Middlewares: se tratan de requests delegates concatenados que manipulan nuestro Http Request. Podemos crear nuestros propios Middlewares si fuera necesario
Ejemplo de una clase con dependencias
public class MiDependencia
{
public MiDependencia()
{
}
public Task WriteMessage(string message)
{
Console.WriteLine(
$"MiDependencia.WriteMessage called. Message: {message}");
return Task.FromResult(0);
}
}
public class Modelo : PageModel
{
MiDependencia _dependency = new MiDependencia();
public async Task OnGetAsync()
{
await _dependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
Ejemplo de una clase aplicando dependency injection
public interface IMyDependency
{
Task WriteMessage(string message);
}
public class Modelo : PageModel
{
private readonly IMiDependencia _dependency;
public Modelo(IMiDependencia dependency)
{
_dependency = dependency;
}
public async Task OnGetAsync()
{
await _dependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
Registro de Servicios en la clase Startup -> ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMiDependencia, MiDependencia>();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
}
IServiceCollection es nuestro contenedor de dependencias
Ciclo de vida de nuestros servicios
Responsable de mapear la URI de entrada en un request y procesarla adecuadamente. Este es el Routing por defecto configurado:
{controller=Home}/{action=Index}/{id?}
Tenemos 2 formas de gestionar errores a nivel global en una app ASP.NET Core webapi
ExceptionHandler
app.UseExceptionHandler(options =>
{
options.Run(async httpContext =>
{
httpContext.Response.ContentType = "application/json";
httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var ex = httpContext.Features.Get<IExceptionHandlerFeature>();
await httpContext.Response.WriteAsync(
JsonConvert.SerializeObject(new ErrorModel()
{
StatusCode = httpContext.Response.StatusCode,
Message = ex.Error.Message
}));
});
});
Custom ExceptionHandler Middleware
1. Crear la clase que acepte por constructor un RequestDelegate e implemente el método Invoke o InvokeAsync
2. Registrar el nuevo Middleware en el método Configure de la clase Startup
Custom ExceptionHandler Middleware
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _requestDelegate;
private readonly ILogger _logger;
public ErrorHandlerMiddleware(RequestDelegate requestDelegate, ILogger<ErrorHandlerMiddleware> logger)
{
_requestDelegate = requestDelegate;
_logger = logger;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _requestDelegate(httpContext);
}
catch (Exception e)
{
_logger.LogError($"Something went wrong: {e}");
httpContext.Response.ContentType = "application/json";
httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await httpContext.Response.WriteAsync(
JsonConvert.SerializeObject(new ErrorModel()
{
StatusCode = httpContext.Response.StatusCode,
Message = e.Message
}));
}
}
}
Los tipos de versionado que ofrece ASP.NET Core son los siguientes:
Setup
Instalar el nuget package Microsoft.AspNetCore.Mvc.Versioning
public void ConfigureServices( IServiceCollection services )
{
services.AddMvc();
services.AddApiVersioning();
// remaining other stuff omitted for brevity
}
services.AddApiVersioning(
o =>
{
o.AssumeDefaultVersionWhenUnspecified = true );
o.DefaultApiVersion = new ApiVersion( new DateTime( 2016, 7, 1 ) );
} );
QueryString versioning
[ApiVersion( "2.0" )]
[Route( "api/helloworld" )]
public class HelloWorld2Controller : Controller {
[HttpGet]
public string Get() => "Hello world!";
}
api/helloworld?api-version=2.0
URL Path versioning
[ApiVersion( "1" )]
[Route( "api/v{version:apiVersion}/[controller]" )]
public class HelloWorldController : Controller {
public string Get() => "Hello world!";
}
[ApiVersion( "2" )]
[ApiVersion( "3" )]
[Route( "api/v{version:apiVersion}/helloworld" )]
public class HelloWorld2Controller : Controller {
[HttpGet]
public string Get() => "Hello world v2!";
[HttpGet, MapToApiVersion( "3.0" )]
public string GetV3() => "Hello world v3!";
}
api/v1/helloworld
api/v2/helloworld
api/v3/helloworld
Header versioning
public void ConfigureServices( IServiceCollection services )
{
services.AddMvc();
services.AddApiVersioning(o =>
o.ApiVersionReader = new HeaderApiVersionReader("api-version"));
}
Separar Versiones de Controladores por namespace
namespace My.Services.V1
{
[ApiVersion( "1.0" )]
public class HelloWorldController : Controller
{
public string Get() => "Hello world v1.0!";
}
}
namespace My.Services.V2
{
[ApiVersion( "2.0" )]
public class HelloWorldController : Controller
{
public string Get() => "Hello world v2.0!";
}
}
Se trata de un patrón para recuperar Settings en nuestro web app y poder utilizarlos a través de Dependency Injection en nuestros servicios
Es decir, crear clases Settings que almacenan constantes a utilizar por nuestro proyecto
public class MyOptions
{
public MyOptions()
{
// Set default value.
Option1 = "value1_from_ctor";
}
public string Option1 { get; set; }
public int Option2 { get; set; } = 5;
}
// Example #1: Basic options
// Register the Configuration instance which MyOptions binds against.
services.Configure<MyOptions>(Configuration);
{
"option1": "this is a test",
"option2" : 500,
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
private readonly MyOptions _options;
public UnaClaseCualquiera(IOptions<MyOptions> optionsAccessor)
{
_options = optionsAccessor.Value;
}
public class AppSettings
{
public string Secret { get; set; }
}
// Settings
var appSettingsConfig = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsConfig);
{
"AppSettings": {
"Secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
Ejemplo de como establecer la variable de entorno ASPNETCORE_ENVIRONMENT en Azure
Para poder habilitar los archivos appsettings.json por variable de entorno tendremos que indicarlo en la clase Startup a nuestra interfaz IConfiguration:
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
Lo único que queda ahora entonces es crear un archivo por entorno de la siguiente forma:
{
"AppSettings": {
"Secret": "PRODUCTION SETTINGS"
},
"Logging": {
"LogLevel": {
"Default": "Error",
"System": "Information",
"Microsoft": "Information"
}
}
}
appsettings.Production.json
{
"Logging": {
"LogLevel": {
"Default": "Error",
"System": "Information",
"Microsoft": "Information"
}
}
}
appsettings.Development.json
{
"AppSettings": {
"Secret": "DEFAULT SETTINGS"
},
"Logging": {
"LogLevel": {
"Default": "Error",
"System": "Information",
"Microsoft": "Information"
}
}
}
appsettings.json
SETUP
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
app.UseAuthentication();
var key = Encoding.ASCII.GetBytes(appSettingsConfig.Get<AppSettings>().Secret);
services.AddJwtBearer(options => {
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
// authentication successful so generate jwt token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);
Conceptos básicos
SETUP
Definiendo nuestro DbContext
public class KriboDbContext : DbContext
{
public KriboDbContext(DbContextOptions<KriboDbContext> options) : base(options)
{
}
// Tables
public DbSet<User> Users { get; set; }
}
Registro de nuestro DbContext en ConfigureServices
// Database connection
services.AddDbContext<KriboDbContext>(options =>
options.UseSqlite(dbSettingsConfig.Get<DbSettings>().ConnectionString)
);
private readonly AppSettings _appSettings;
private readonly KriboDbContext _dbContext;
public UserService(IOptions<AppSettings> appSettings, KriboDbContext dbContext)
{
_appSettings = appSettings.Value;
_dbContext = dbContext;
}
Las migraciones las realizaremos por lineas de comandos. Veamos los comandos de los que disponemos:
Insertar datos iniciales en nuestras tablas
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().HasData(
new User
{
Id = 1,
FirstName = "",
LastName = "",
Password ="test",
Username = "test",
Token = ""
},
new User
{
Id = 2,
FirstName = "",
LastName = "",
Password = "test2",
Username = "test2",
Token = ""
}
);
}
DEMO
El objetivo de este patrón de diseño es ocultar los detalles de como se acceden a los datos de nuestra base de datos, también evitamos lógica de acceso a datos a lo largo de nuestra aplicación
Si partimos de la siguiente interfaz:
public interface IEmployeeRepository
{
Task<Employee> Get(Guid? id);
Task Save(Employee employee);
Task Delete(Employee employee);
Task Update(Employee employee);
Task<IEnumerable<Employee>> FindAll();
}
Y su implementación:
public class EmployeeRepository : IEmployeeRepository
{
private EmployeeContext _employeeContext;
public EmployeeRepository()
{
_employeeContext = new EmployeeContext();
}
public async Task<Employee> Get(Guid? id)
{
return await _employeeContext.Employees.FirstOrDefaultAsync(x => x.Id == id);
}
public async Task Save(Employee employee)
{
_employeeContext.Employees.Add(employee);
await _employeeContext.SaveChangesAsync();
}
public async Task Delete(Employee employee)
{
_employeeContext.Employees.Remove(employee);
await _employeeContext.SaveChangesAsync();
}
public async Task Update(Employee employee)
{
_employeeContext.Employees.Update(employee);
await _employeeContext.SaveChangesAsync();
}
public async Task<IEnumerable<Employee>> FindAll()
{
return await _employeeContext.Employees.ToListAsync();
}
}
Y lo que quedaría sería registrar el repositorio en nuestra inyección de dependencias. De la siguiente forma:
services.AddScoped<IEmployeeRepository, EmployeeRepository>();
De esta forma ya estamos desacoplando como se accede a la base de datos, pero tendríamos que estar registrando un repositorio por entidad de base de datos. Podemos crear un Repositorio genérico para cualquier entidad a partir de la siguiente intefaz:
public interface IGenericRepository<T> where T: class, IEntity
{
Task<T> Get(Guid? id);
Task Save(T employee);
Task Delete(T employee);
Task Update(T employee);
Task<IEnumerable<T>> FindAll();
}
public interface IEntity
{
Guid Id { get; set; }
}
La implementación de IGenericRepository<T> sería de la siguiente forma:
public class GenericRepository<T> : IGenericRepository<T> where T: class, IEntity
{
private DbContext _dbContext;
public GenericRepository(DbContext dbContext)
{
_dbContext = dbContext;
}
public async Task Delete(T employee)
{
_dbContext.Set<T>().Remove(employee);
await _dbContext.SaveChangesAsync();
}
public async Task<IEnumerable<T>> FindAll()
{
return await _dbContext.Set<T>().ToListAsync();
}
public async Task<T> Get(Guid? id)
{
return await _dbContext.Set<T>().FirstOrDefaultAsync(x => x.Id == id);
}
public async Task Save(T employee)
{
_dbContext.Set<T>().Add(employee);
await _dbContext.SaveChangesAsync();
}
public async Task Update(T employee)
{
_dbContext.Set<T>().Update(employee);
await _dbContext.SaveChangesAsync();
}
}
El Repository Pattern tiene el problema que no podemos realizar acumular transacciones y después actualizar la base de datos, sino que cada vez que llamamos por ejemplo a Delete<T>, siempre estamos ejecutando _dbContext.SaveChangesAsync()
El patrón Unit of Work nos ayuda a poder ejecutar sentencias de base de datos de forma transaccional y después enviar los cambios acumulados a la base de datos. Como siempre partimos de una interfaz:
public interface IUnitOfWork
{
IGenericRepository<Blog> BlogRepository { get; }
IGenericRepository<Post> PostRepository { get; }
void Save();
}
La implementación de la interfaz sería de la siguiente forma:
public class UnitOfWork : IUnitOfWork
{
private readonly BloggingContext _bloggingContext;
private IGenericRepository<Blog> _blogRepository;
private IGenericRepository<Post> _postRepository;
public UnitOfWork(BloggingContext bloggingContext)
{
_bloggingContext = bloggingContext;
}
public IGenericRepository<Blog> BlogRepository
{
get
{
return _blogRepository = _blogRepository ?? new GenericRepository<Blog>(_bloggingContext);
}
}
public IGenericRepository<Post> PostRepository
{
get
{
return _postRepository = _postRepository ?? new GenericRepository<Post>(_bloggingContext);
}
}
public void Save()
{
_bloggingContext.SaveChanges();
}
}
El registro en la inyección de dependencias seria:
Y la utilización del Unit of Work en nuestro código quedaría:
var connection = @"Server=(localdb)\mssqllocaldb;Database=EFGetStarted.AspNetCore.NewDb;Trusted_Connection=True;";
services.AddDbContext<BloggingContext>(options => options.UseSqlServer(connection));
services.AddScoped<IUnitOfWork, UnitOfWork>();
_unitOfWork.BlogRepository.Create(new Blog()
{
Url = "https://dotnetthoughts.net"
});
_unitOfWork.PostRepository.Create(new Post()
{
Title = "Hello World",
Content = "This is a sample blog content",
BlogId = 1
});
_unitOfWork.Save();
Un Unit Test es la forma de comprobar el correcto funcionamiento de una unidad de código. Esto sirve para asegurar que cada unidad funcione correctamente y eficientemente por separado.
En el momento que probemos la integración de los distintos módulos de nuestro sistema, estaríamos hablado de Tests de Integración.
Ventajas
Mocks
// Creamos el Mock sobre nuestra interfaz
var genericUserRepository = new Mock<IGenericRepository<User>>();
// Después definimos el comportamiento sobre el método de la interfaz que vamos a utilizar
genericUserRepository
.Setup(x => x.FirstOrDefault(It.IsAny<Expression<Func<User, bool>>>()))
.Returns(Task.FromResult<User>(null));
// La forma de utilizar nuestra interfaz "mock"
var userService = new UserService(new genericUserRepository.Object);
SETUP
<Project Sdk="Microsoft.NET.Sdk.Web">
Implementación
Implementación
public class ValuesControllerTest : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly WebApplicationFactory<Startup> _factory;
private readonly HttpClient _client;
private User user = null;
public ValuesControllerTest(WebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = _factory
.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices((IServiceCollection services) =>
{
services.AddScoped<IUnitOfWork>(sp => new UnitOfWorkMock(new GenericUserRepositoryMock()));
});
})
.CreateClient();
}
[Fact]
public async Task get_values_unauthorized()
{
var response = await _client.GetAsync("/api/values");
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Fact]
public async Task get_values_ok()
{
if (user == null)
user = await _client.LogInUser(new User { Username = "test", Password = "test" });
_client.DefaultRequestHeaders.Add("Authorization", $"Bearer {user.Token}");
var response = await _client.GetAsync("/api/values");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
Implementación
Podríamos crear nuestro propio WebApplicationFactory
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
public CustomWebApplicationFactory()
{
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices((webHostBuilder, services) =>
{
var configuration = GetConfiguration(webHostBuilder.HostingEnvironment);
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Settings
var appSettingsConfig = configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsConfig);
var dbSettingsConfig = configuration.GetSection("DbSettings");
services.Configure<DbSettings>(dbSettingsConfig);
// Authentication Jwt
services.AddJwtAuthentication(appSettingsConfig.Get<AppSettings>().Secret,
false);
services.AddTransient<IValuesService, ValuesService>();
services.AddTransient<IUserService, UserService>();
services.AddScoped<IUnitOfWork, UnitOfWorkMock>();
services.AddTransient<IJwtTokenService, JwtTokenService>();
});
}
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}