ASP.NET Core MVC and EF Core

Constructing REST API using ASP.NET Core MVC 2.2 and EF Core 3.1

I give this example for those who are using the latest ASP.NET Core MVC 2.2 and EF Core 3.1 to set the multi tiers projects web application. How the solution code got simplified with compliable and executable, and commands on Package Manager Console. The base code references chsakell’s Blog from MIT about Building REST APIs using ASP.NET Core and EF Core in 2016.
Here are the code setting in the solution Scheduler: Adding three projects Scheduler.Model, Scheduler.Data and Scheduler.Api.
1. Create the Models and Data Repositories:

Create two projects separately Scheduler.Model and Scheduler.Data by select Class Library (.NET Core).
Create project Scheduler.Api by select ASP.NET Core Web Application (.NET Core).
Let us put the following code here:
IEntityBase.cs — Adding this interface into Scheduler.Model to hold the base interface for the Entities.
public interface IEntityBase
{
int Id { get; set; }
}

Create a folder Entities in the Scheduler.Model, then create a class Schedule.cs in the Entities.
public class Schedule : IEntityBase
{
public Schedule()
{
Attendees = new List<Attendee>();
}
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime TimeStart { get; set; }
public DateTime TimeEnd { get; set; }
public string Location { get; set; }
public ScheduleType Type { get; set; }
public ScheduleStatus Status { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateUpdated { get; set; }
public User Creator { get; set; }
public int CreatorId { get; set; }
public ICollection<Attendee> Attendees { get; set; }
}

Create User entity class in User.cs, and Attendee class in Attendee.cs in folder Entities. Write two enum for ScheduleEnum.cs in Entities.
public enum ScheduleType
{
Work = 1,
Coffee = 2,
Doctor = 3,
Shopping = 4,
Other = 5
}
public enum ScheduleStatus
{
Valid = 1,
Cancelled = 2
}

Install EntityFrameworkCore Nuget Package in Scheduler.Data to set the DbContext class and a reference to the Scheduler.Model project.
Add a folder named Abstract in Scheduler.Model and create the following interfaces:
IEntityBaseRepository.cs
public interface IEntityBaseRepository<T> where T : class, IEntityBase, new()
{
IEnumerable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties);
IEnumerable<T> GetAll();
int Count();
T GetSingle(int id);
T GetSingle(Expression<Func<T, bool>> predicate);
T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties);
IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate);
void Add(T entity);
void Update(T entity);
void Delete(T entity);
void DeleteWhere(Expression<Func<T, bool>> predicate);
void Commit();
}

And three repositories interface in one file IRepositories.cs:
public interface IScheduleRepository:IEntityBaseRepository<Schedule>{ }
public interface IUserRepository:IEntityBaseRepository<User>{ }
public interface IAttendeeRepository:IEntityBaseRepository<Attendee>{ }

Create Repositories folder in Scheduler.Data, then create a EntityBaseRepository class in this folder, and using this base class create ScheduleRepository, UserRepository and AttendeeRepository in three files.

EntityBaseRepository.cs
public class EntityBaseRepository : IEntityBaseRepository
where T : class, IEntityBase, new()
{
private SchedulerContext _context;
#region Properties
public EntityBaseRepository(SchedulerContext context)
{
_context = context;
}
#endregion
public virtual IEnumerable<T> GetAll()
{
return _context.Set<T>().AsEnumerable();
}
public virtual int Count()
{
return _context.Set<T>().Count();
}
public virtual IEnumerable<T> AllIncluding(params
Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query.AsEnumerable();
}
public T GetSingle(int id)
{
return _context.Set<T>().FirstOrDefault(x => x.Id == id);
}
public T GetSingle(Expression<Func<T, bool>> predicate)
{
return _context.Set<T>().FirstOrDefault(predicate);
}
public T GetSingle(Expression<Func<T, bool>> predicate,
params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query.Where(predicate).FirstOrDefault();
}
public virtual IEnumerable<T> FindBy(Expression<Func<T, bool>>
predicate)
{
return _context.Set<T>().Where(predicate);
}
public virtual void Add(T entity)
{
EntityEntry dbEntityEntry = _context.Entry<T>(entity);
_context.Set<T>().Add(entity);
}
public virtual void Update(T entity)
{
EntityEntry dbEntityEntry = _context.Entry<T>(entity);
dbEntityEntry.State = EntityState.Modified;
}
public virtual void Delete(T entity)
{
EntityEntry dbEntityEntry = _context.Entry<T>(entity);
dbEntityEntry.State = EntityState.Deleted;
}
public virtual void DeleteWhere(Expression<Func<T, bool>>
predicate)
{
IEnumerable<T> entities=_context.Set<T>().Where(predicate);
foreach (var entity in entities)
{
_context.Entry<T>(entity).State = EntityState.Deleted;
}
}
public virtual void Commit()
{
_context.SaveChanges();
}
}

ScheduleRepository.cs
public class ScheduleRepository : EntityBaseRepository, IScheduleRepository
{
public ScheduleRepository(SchedulerContext context): base(context){ }
}

Create UserRepository class and AttendeeRepository class similar as ScheduleRepository.
Add the SchedulerContext class under the root of the Scheduler.Data project install packages Microsoft.EntityFrameworkCore (3.1.3) and Microsoft.EntityFrameworkCore.Relational (3.1.3) to access the SchdulerDb.
SchedulerContext.cs

public class SchedulerContext : DbContext
{
public DbSet<Schedule> Schedules { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Attendee> Attendees { get; set; }
public SchedulerContext(DbContextOptions options): base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuldr)
{
var fkey = modelBuldr.Model.GetEntityTypes()
.SelectMany(e => e.GetForeignKeys());
foreach (var relationship in fkey)
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
modelBuldr.Entity<Schedule>().ToTable("Schedule");
modelBuldr.Entity<Schedule>().Property(s => s.CreatorId)
.IsRequired();
modelBuldr.Entity<Schedule>().Property(s => s.DateCreated)
.HasDefaultValue(DateTime.Now);
modelBuldr.Entity<Schedule>().Property(s => s.DateUpdated)
.HasDefaultValue(DateTime.Now);
modelBuldr.Entity<Schedule>().Property(s => s.Type)
.HasDefaultValue(ScheduleType.Work);
modelBuldr.Entity<Schedule>().Property(s => s.Status)
.HasDefaultValue(ScheduleStatus.Valid);
modelBuldr.Entity<Schedule>().HasOne(s => s.Creator)
.WithMany(c => c.SchedulesCreated);
modelBuldr.Entity<User>().ToTable("User");
modelBuldr.Entity<User>().Property(u => u.Name)
.HasMaxLength(100).IsRequired();
modelBuldr.Entity<Attendee>().ToTable("Attendee");
modelBuldr.Entity<Attendee>().HasOne(a => a.User)
.WithMany(u => u.SchedulesAttended)
.HasForeignKey(a => a.UserId);
modelBuldr.Entity<Attendee>().HasOne(a => a.Schedule)
.WithMany(s => s.Attendees)
.HasForeignKey(a => a.ScheduleId);
modelBuilder.Entity().HasData(new User { Id = 1, Name = "Chris Sakellarios", Profession = "Developer", Avatar = "avatar_02.png"});
modelBuilder.Entity().HasData(new User { Id = 2, Name = "Charlene Campbell", Profession = "Web Designer", Avatar = "avatar_03.jpg" });
modelBuilder.Entity().HasData(new User { Id = 3, Name = "Mattie Lyons", Profession = "Engineer", Avatar = "avatar_05.png" });
modelBuilder.Entity().HasData(new User { Id = 4, Name = "Kelly Alvarez", Profession = "Network Engineer", Avatar = "avatar_01.png" });
modelBuilder.Entity().HasData(new User { Id = 5, Name = "Charlie Cox", Profession = "Developer", Avatar = "avatar_03.jpg" });
modelBuilder.Entity().HasData(new User { Id = 6, Name = "Megan Fox", Profession = "Hacker", Avatar = "avatar_05.png" });
modelBuilder.Entity().HasData(new Schedule
{
Id = 1,
Title = "Meeting",
Description = "Meeting at work with the boss",
Location = "Korai",
CreatorId = 1,
Status = ScheduleStatus.Valid,
Type = ScheduleType.Work,
TimeStart = DateTime.Now.AddHours(4),
TimeEnd = DateTime.Now.AddHours(6),
Attendees = new List
{
new Attendee() { ScheduleId = 1, UserId = 2 },
new Attendee() { ScheduleId = 1, UserId = 3 },
new Attendee() { ScheduleId = 1, UserId = 4 }
}
},
new Schedule
{
Id = 2,
Title = "Coffee",
Description = "Coffee with folks",
Location = "Athens",
CreatorId = 2,
Status = ScheduleStatus.Valid,
Type = ScheduleType.Coffee,
TimeStart = DateTime.Now.AddHours(3),
TimeEnd = DateTime.Now.AddHours(6),
Attendees = new List
{
new Attendee() { ScheduleId = 2, UserId = 1 },
new Attendee() { ScheduleId = 1, UserId = 3 },
new Attendee() { ScheduleId = 2, UserId = 4 }
}
},
new Schedule
{
Id = 3,
Title = "Shopping day",
Description = "Shopping therapy",
Location = "Attica",
CreatorId = 3,
Status = ScheduleStatus.Valid,
Type = ScheduleType.Shopping,
TimeStart = DateTime.Now.AddHours(3),
TimeEnd = DateTime.Now.AddHours(6),
Attendees = new List
{
new Attendee() { ScheduleId = 3, UserId = 1 },
new Attendee() { ScheduleId = 3, UserId = 4 },
new Attendee() { ScheduleId = 3, UserId = 5 }
}
},
new Schedule
{
Id = 4,
Title = "Family",
Description = "Thanks giving day",
Location = "Home",
CreatorId = 5,
Status = ScheduleStatus.Valid,
Type = ScheduleType.Other,
TimeStart = DateTime.Now.AddHours(3),
TimeEnd = DateTime.Now.AddHours(6),
Attendees = new List
{
new Attendee() { ScheduleId = 4, UserId = 1 },
new Attendee() { ScheduleId = 4, UserId = 2 },
new Attendee() { ScheduleId = 4, UserId = 5 }
}
},
new Schedule
{
Id = 5,
Title = "Friends",
Description = "Friends giving day",
Location = "Home",
CreatorId = 5,
Status = ScheduleStatus.Cancelled,
Type = ScheduleType.Other,
TimeStart = DateTime.Now.AddHours(5),
TimeEnd = DateTime.Now.AddHours(7),
Attendees = new List
{
new Attendee() { ScheduleId = 4, UserId = 1 },
new Attendee() { ScheduleId = 4, UserId = 2 },
new Attendee() { ScheduleId = 4, UserId = 3 },
new Attendee() { ScheduleId = 4, UserId = 4 },
new Attendee() { ScheduleId = 4, UserId = 5 }
}
});
}
}

Note: I have added mock data in the above SchedulerContext class.

2. Build the API using REST architecture principles
Install following Nuget Packages at Scheduler.Api project.
Microsoft.AspNetCoreMvc (2.2.0)
Microsoft.AspNetCoreMvc.NewtonsoftJson (3.1.3)
Microsoft.EntityFrameworkCore (3.1.3)
Microsoft.EntityFrameworkCore.SqlServer (3.1.3)
Microsoft.EntityFrameworkCore.Tools (3.1.3)
Microsoft.VisualStudio.Web.CodeGeneration.Design (3.1.1)
Add Scheduler.Api project References Scheduler.Model and Scheduler.Data.
Add following connectionstring in to appsettings.json:
“ConnectionStrings”: {
“SchedulerDb”: “Server={servename};Database=SchedulerDb;Trusted_Connection=True;MultipleActiveResultSets=true”
},

Using Newtonsoft.Json.Serialization in Startup.cs:

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.AddDbContext<SchedulerContext>(options =>
options.UseSqlServer(Configuration
.GetConnectionString("SchedulerDb"), b =>
b.MigrationsAssembly("Scheduler.Api")));
// Repositories
services.AddScoped<IScheduleRepository, ScheduleRepository>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IAttendeeRepository, AttendeeRepository>();
services.AddAutoMapper(typeof(Startup));
// Enable Cors
services.AddCors();
// Add MVC services to the services container.
services.AddMvc().AddNewtonsoftJson(options =>
options.SerializerSettings.ContractResolver = new
DefaultContractResolver());
}
// This method gets called by the runtime. Use this method to
// configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment
env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(builder => builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}

There is a mismatch between appsettings.json which holds the database connection string in Scheduler.Api and the SchedulerDbContext class belongs to the Scheduler.Data.
The above code in ConfigureServices Startup.cs informs EntityFrameworkCore the assembly to be used for migrations.
services.AddDbContext<SchedulerContext>(options =>
options.UseSqlServer(Configuration
.GetConnectionString(“SchedulerDb”), b =>
b.MigrationsAssembly(“Scheduler.Api”)));
We can use migration in solution directory by following command in the Package Manager Console:
pm>add migration initial

3. ViewModel validations and mappings
Create a folder ViewModels in Scheduler.Api, and add classses to this folder: ScheduleViewMode, UserViewModel and ScheduleDetailsViewModel.
ScheduleViewMode.cs
public class ScheduleViewModel : IValidatableObject
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime TimeStart { get; set; }
public DateTime TimeEnd { get; set; }
public string Location { get; set; }
public string Type { get; set; }
public string Status { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateUpdated { get; set; }
public string Creator { get; set; }
public int CreatorId { get; set; }
public int[] Attendees { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext
validationContext)
{
var validator = new ScheduleViewModelValidator();
var result = validator.Validate(this);
return result.Errors.Select(item => new
ValidationResult(item.ErrorMessage, new[]
{ item.PropertyName }));
}
}

UserViewModel.cs
public class UserViewModel : IValidatableObject
{
public int Id { get; set; }
public string Name { get; set; }
public string Avatar { get; set; }
public string Profession { get; set; }
public int SchedulesCreated { get; set; }
public IEnumerable Validate(ValidationContext
validationContext)
{
var validator = new UserViewModelValidator();
var result = validator.Validate(this);
return result.Errors.Select(item => new
ValidationResult(item.ErrorMessage,
new[] { item.PropertyName }));
}
}

Add custom validations FluentValidation in Nuget Package to validate the post ViewModel data. Use following example you can create other ViewModel Validator.
UserViewModelValidator.cs
public class UserViewModelValidator : AbstractValidator<UserViewModel>
{
public UserViewModelValidator()
{
RuleFor(user => user.Name).NotEmpty
.WithMessage("Name cannot be empty");
RuleFor(user => user.Profession)
.NotEmpty().WithMessage("Profession cannot be empty");
RuleFor(user => user.Avatar)
.NotEmpty().WithMessage("Profession cannot be empty");
}
}

Add a new folder named Mappings inside the ViewModels and set the Domain to ViewModel mappings. AutoMapper is a simple library built to solve a deceptively complex problem. It is a convention-based object to object mapper that requires very little configuration. Here we use the AutoMapper to map the entity to model view. To perform all the scanning and dependency injection registration package in ASP.NET Core with services.AddAutoMapper(assembly[]), we need to use AutoMapper.Extensions.Microsoft.DependencyInjection. Let us install Nuget Packages into Scheduler.Api project: AutoMapper (9.0.0) AutoMapper.Extensions.Microsoft.DependencyInjection (7.0.0).

DomainToViewModelMappingProfile.cs
public class DomainToViewModelMappingProfile : Profile
{
public DomainToViewModelMappingProfile()
{
CreateMap<user, userviewmodel="">()
.ForMember(vm => vm.SchedulesCreated,
map => map.MapFrom(u => u.SchedulesCreated.Count()));</user,>
CreateMap<schedule, scheduleviewmodel="">()
.ForMember(vm => vm.Creator,
map => map.MapFrom(s => s.Creator.Name))
.ForMember(vm => vm.Attendees, map =>
map.MapFrom(s => s.Attendees.Select(a =>
a.UserId)));
}
}</schedule,>

AutoMapperConfiguration.cs
public class AutoMapperConfiguration
{
public static void Configure()
{
(new MapperConfiguration(x => {
x.AddProfile<DomainToViewModelMappingProfile>();
})).CreateMapper();
}
}

Add a new folder Core at the root of the API application and create a helper class for supporting pagination in our SPA.
PaginationHeader.cs
public class PaginationHeader
{
public int CurrentPage { get; set; }
public int ItemsPerPage { get; set; }
public int TotalItems { get; set; }
public int TotalPages { get; set; }
public PaginationHeader(int currentPage, int itemsPerPage,
int totalItems, int totalPages)
{
this.CurrentPage = currentPage;
this.ItemsPerPage = itemsPerPage;
this.TotalItems = totalItems;
this.TotalPages = totalPages;
}
}

Using encapsulate pagination information and customer error message for server return error. If the client wants to retrieve the 5 schedules of the second page, the request must have a “Pagination” header equal to “2,5”. All the required information the client needs to build a pagination bar will be contained inside a corresponding response header. To server return error using the global exception handler. Here create an Extensions class inside the Core folder.

Extensions.cs
public static void AddPagination(this HttpResponse response, int currentPage, int itemsPerPage, int totalItems, int totalPages)
{
var paginationHeader = new PaginationHeader(currentPage,
itemsPerPage, totalItems, totalPages);
response.Headers.Add("Pagination", Newtonsoft.Json.JsonConvert
.SerializeObject(paginationHeader));
// CORS
response.Headers
.Add("access-control-expose-headers", "Pagination");
}
public static void AddApplicationError(this HttpResponse response,
string message)
{
response.Headers.Add("Application-Error", message);
// CORS
response.Headers
.Add("access-control-expose-headers",
"Application-Error");
}

4. Create the API Controllers for SPA Functionalities
Create a folder Controllers in the API root directory. then create the following classes in that folder with the requirement.
SchedulesController.cs
[Route("api/[controller]")]
public class SchedulesController : Controller
{
private IScheduleRepository _scheduleRepository;
private IAttendeeRepository _attendeeRepository;
private IUserRepository _userRepository;
int page = 1;
int pageSize = 4;
private readonly IMapper _mapper;
// Assign the object in the constructor for dependency injection
public SchedulesController(IScheduleRepository
scheduleRepository,
IAttendeeRepository attendeeRepository,
IUserRepository userRepository)
{
_scheduleRepository = scheduleRepository;
_attendeeRepository = attendeeRepository;
_userRepository = userRepository;
}
public IActionResult Get()
{
var pagination = Request.Headers["Pagination"];
if (!string.IsNullOrEmpty(pagination))
{
string[] vals = pagination.ToString().Split(',');
int.TryParse(vals[0], out page);
int.TryParse(vals[1], out pageSize);
}
int currentPage = page;
int currentPageSize = pageSize;
var totalSchedules = _scheduleRepository.Count();
var totalPages = (int)Math.Ceiling((double)totalSchedules /
pageSize);
IEnumerable _schedules = _scheduleRepository
.AllIncluding(s => s.Creator, s => s.Attendees)
.OrderBy(s => s.Id)
.Skip((currentPage - 1) * currentPageSize)
.Take(currentPageSize)
.ToList();
Response.AddPagination(page, pageSize, totalSchedules,
totalPages);
IEnumerable _schedulesVM =
_mapper.Map<ienumerable,
IEnumerable>(_schedules);</ienumerable
return new OkObjectResult(_schedulesVM);
}
[HttpGet("{id}", Name = "GetSchedule")]
public IActionResult Get(int id)
{
Schedule _schedule = _scheduleRepository
.GetSingle(s => s.Id == id, s => s.Creator, s =>
s.Attendees);
if (_schedule != null)
{
ScheduleViewModel _scheduleVM = _mapper.Map<schedule, scheduleviewmodel="">(_schedule);
return new OkObjectResult(_scheduleVM);
}
else
{
return NotFound();
}
}
[HttpGet("{id}/details", Name = "GetScheduleDetails")]
public IActionResult GetScheduleDetails(int id)
{
Schedule _schedule = _scheduleRepository
.GetSingle(s => s.Id == id, s => s.Creator, s =>
s.Attendees);
if (_schedule != null)
{
ScheduleDetailsViewModel _scheduleDetailsVM =
_mapper.Map<schedule, scheduledetailsviewmodel="">(_schedule);</schedule,>
foreach (var attendee in _schedule.Attendees)
{
User _userDb =
_userRepository.GetSingle(attendee.UserId);
_scheduleDetailsVM.Attendees
.Add(_mapper.Map<user, userviewmodel="">(_userDb));
}</user,>
return new OkObjectResult(_scheduleDetailsVM);
}
else
{
return NotFound();
}
}
[HttpPost]
public IActionResult Create([FromBody]ScheduleViewModel schedule)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Schedule _newSchedule = _mapper.Map<scheduleviewmodel, schedule="">(schedule);
_newSchedule.DateCreated = DateTime.Now;
_scheduleRepository.Add(_newSchedule);
_scheduleRepository.Commit();</scheduleviewmodel,>
foreach (var userId in schedule.Attendees)
{
_newSchedule.Attendees.Add(new Attendee { UserId =
userId });
}
_scheduleRepository.Commit();
schedule = _mapper.Map<schedule, scheduleviewmodel="">(_newSchedule);
CreatedAtRouteResult result = CreatedAtRoute("GetSchedule",
new { controller = "Schedules",
id = schedule.Id }, schedule);
return result;
}
[HttpPut("{id}")]
public IActionResult Put(int id, [FromBody]ScheduleViewModel
schedule)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Schedule _scheduleDb = _scheduleRepository.GetSingle(id);
if (_scheduleDb == null)
{
return NotFound();
}
else
{
_scheduleDb.Title = schedule.Title;
_scheduleDb.Location = schedule.Location;
_scheduleDb.Description = schedule.Description;
_scheduleDb.Status = (ScheduleStatus)Enum.
Parse(typeof(ScheduleStatus), schedule.Status);
_scheduleDb.Type = (ScheduleType)Enum.
Parse(typeof(ScheduleType), schedule.Type);
_scheduleDb.TimeStart = schedule.TimeStart;
_scheduleDb.TimeEnd = schedule.TimeEnd;
// Remove current attendees
_attendeeRepository.DeleteWhere(a =>
a.ScheduleId == id);
foreach (var userId in schedule.Attendees)
{
_scheduleDb.Attendees
.Add(new Attendee { ScheduleId = id,
UserId = userId });
}
_scheduleRepository.Commit();
}
schedule = _mapper.Map<schedule, scheduleviewmodel="">(_scheduleDb);
return new NoContentResult();
}
[HttpDelete("{id}", Name = "RemoveSchedule")]
public IActionResult Delete(int id)
{
Schedule _scheduleDb = _scheduleRepository.GetSingle(id);
if (_scheduleDb == null)
{
return new NotFoundResult();
}
else
{
_attendeeRepository.DeleteWhere(a => a.ScheduleId == id);
_scheduleRepository.Delete(_scheduleDb);
_scheduleRepository.Commit();
return new NoContentResult();
}
}
[HttpDelete("{id}/removeattendee/{attendee}")]
public IActionResult Delete(int id, int attendee)
{
Schedule _scheduleDb = _scheduleRepository.GetSingle(id);
if (_scheduleDb == null)
{
return new NotFoundResult();
}
else
{
_attendeeRepository.DeleteWhere(a => a.ScheduleId == id &&
a.UserId == attendee);
_attendeeRepository.Commit();
return new NoContentResult();
}
}
}</schedule,></schedule,></schedule,>

UsersController.cs
[Microsoft.AspNetCore.Components.Route("api/[controller]")]
public class UsersController : Controller
{
private IUserRepository _userRepository;
private IScheduleRepository _scheduleRepository;
private IAttendeeRepository _attendeeRepository;
private readonly IMapper _mapper;
int page = 1;
int pageSize = 10;
public UsersController(IUserRepository userRepository,
IScheduleRepository scheduleRepository,
IAttendeeRepository attendeeRepository)
{
_userRepository = userRepository;
_scheduleRepository = scheduleRepository;
_attendeeRepository = attendeeRepository;
}
public IActionResult Get()
{
var pagination = Request.Headers["Pagination"];
if (!string.IsNullOrEmpty(pagination))
{
string[] vals = pagination.ToString().Split(',');
int.TryParse(vals[0], out page);
int.TryParse(vals[1], out pageSize);
}
int currentPage = page;
int currentPageSize = pageSize;
var totalUsers = _userRepository.Count();
var totalPages = (int)Math.Ceiling((double)totalUsers /
pageSize);
IEnumerable _users = _userRepository
.AllIncluding(u => u.SchedulesCreated)
.OrderBy(u => u.Id)
.Skip((currentPage - 1) * currentPageSize)
.Take(currentPageSize)
.ToList();
IEnumerable _usersVM =
_mapper.Map<ienumerable,
IEnumerable>(_users);</ienumerable
Response.AddPagination(page, pageSize, totalUsers, totalPages);
return new OkObjectResult(_usersVM);
}
[HttpGet("{id}", Name = "GetUser")]
public IActionResult Get(int id)
{
User _user = _userRepository.GetSingle(u => u.Id == id,
u => u.SchedulesCreated);
if (_user != null)
{
UserViewModel _userVM = _mapper.Map<user, userviewmodel="">(_user);
return new OkObjectResult(_userVM);
}
else
{
return NotFound();
}
}
[HttpGet("{id}/schedules", Name = "GetUserSchedules")]
public IActionResult GetSchedules(int id)
{
IEnumerable _userSchedules =
_scheduleRepository.FindBy(s => s.CreatorId == id);
if (_userSchedules != null)
{
IEnumerable _userSchedulesVM =
_mapper.Map<ienumerable,
IEnumerable>(_userSchedules);
return new OkObjectResult(_userSchedulesVM);
}
else
{
return NotFound();
}
}</ienumerable
[HttpPost]
public IActionResult Create([FromBody]UserViewModel user)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
User _newUser = new User { Name = user.Name, Profession =
user.Profession, Avatar = user.Avatar };
_userRepository.Add(_newUser);
_userRepository.Commit();
user = _mapper.Map<user, userviewmodel="">(_newUser);</user,>
CreatedAtRouteResult result = CreatedAtRoute("GetUser",
new {controller = "Users", id = user.Id }, user);
return result;
}
[HttpPut("{id}")]
public IActionResult Put(int id, [FromBody]UserViewModel user)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
User _userDb = _userRepository.GetSingle(id);
if (_userDb == null)
{
return NotFound();
}
else
{
_userDb.Name = user.Name;
_userDb.Profession = user.Profession;
_userDb.Avatar = user.Avatar;
_userRepository.Commit();
}
user = _mapper.Map<user, userviewmodel="">(_userDb);
return new NoContentResult();
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
User _userDb = _userRepository.GetSingle(id);
if (_userDb == null)
{
return new NotFoundResult();
}
else
{
IEnumerable _attendees =
_attendeeRepository.FindBy(a => a.UserId == id);
IEnumerable _schedules =
_scheduleRepository.FindBy(s => s.CreatorId == id);
foreach (var attendee in _attendees)
{
_attendeeRepository.Delete(attendee);
}
foreach (var schedule in _schedules)
{
_attendeeRepository.DeleteWhere(a => a.ScheduleId ==
schedule.Id);
_scheduleRepository.Delete(schedule);
}
_userRepository.Delete(_userDb);
_userRepository.Commit();
return new NoContentResult();
}
}
}</user,></user,>

5. Generating the Database from Code Using Migrations
Let us compile the solution. As mentioned above we can use migration in solution directory by following command in the Package Manager Console:
pm>add migration initial

pm>update-database

You can check the database SchedulerDb is created with tables Schedule, Attendee and User and mock up data inside of them. Also the Migrations folder is created in the API root folder. The inside Migrations folder, the initial file is created.

6. Run the API
We run the Users and Schedules on localhost and here is the result. Also we can use Postman to test the API. You can go my GitHub to see the code sample.