├── .gitattributes ├── .gitignore ├── README.md ├── Scheduler.API ├── Controllers │ ├── SchedulesController.cs │ └── UsersController.cs ├── Core │ ├── Extensions.cs │ └── PaginationHeader.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Scheduler.API.csproj ├── Startup.cs ├── ViewModels │ ├── Mappings │ │ ├── AutoMapperConfiguration.cs │ │ ├── DomainToViewModelMappingProfile.cs │ │ └── ViewModelToDomainMappingProfile.cs │ ├── ScheduleDetailsViewModel.cs │ ├── ScheduleViewModel.cs │ ├── UserViewModel.cs │ └── Validations │ │ ├── ScheduleViewModelValidator.cs │ │ └── UserViewModelValidator.cs ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ └── images │ ├── avatar_01.png │ ├── avatar_02.png │ ├── avatar_03.jpg │ ├── avatar_04.jpg │ └── avatar_05.png ├── Scheduler.Data ├── Abstract │ ├── IEntityBaseRepository.cs │ └── IRepositories.cs ├── Repositories │ ├── AttendeeRepository.cs │ ├── EntityBaseRepository.cs │ ├── ScheduleRepository.cs │ └── UserRepository.cs ├── Scheduler.Data.csproj ├── SchedulerContext.cs └── SchedulerDbInitializer.cs ├── Scheduler.Model ├── Entities │ ├── Attendee.cs │ ├── Schedule.cs │ └── User.cs ├── IEntityBase.cs ├── ScheduleEnums.cs └── Scheduler.Model.csproj ├── Scheduler.sln ├── appveyor.yml └── licence /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Microsoft Azure ApplicationInsights config file 170 | ApplicationInsights.config 171 | 172 | # Windows Store app package directory 173 | AppPackages/ 174 | BundleArtifacts/ 175 | 176 | # Visual Studio cache files 177 | # files ending in .cache can be ignored 178 | *.[Cc]ache 179 | # but keep track of directories ending in .cache 180 | !*.[Cc]ache/ 181 | 182 | # Others 183 | ClientBin/ 184 | [Ss]tyle[Cc]op.* 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # SQL Server files 206 | *.mdf 207 | *.ldf 208 | 209 | # Business Intelligence projects 210 | *.rdl.data 211 | *.bim.layout 212 | *.bim_*.settings 213 | 214 | # Microsoft Fakes 215 | FakesAssemblies/ 216 | 217 | # GhostDoc plugin setting file 218 | *.GhostDoc.xml 219 | 220 | # Node.js Tools for Visual Studio 221 | .ntvs_analysis.dat 222 | 223 | # Visual Studio 6 build log 224 | *.plg 225 | 226 | # Visual Studio 6 workspace options file 227 | *.opt 228 | 229 | # Visual Studio LightSwitch build output 230 | **/*.HTMLClient/GeneratedArtifacts 231 | **/*.DesktopClient/GeneratedArtifacts 232 | **/*.DesktopClient/ModelManifest.xml 233 | **/*.Server/GeneratedArtifacts 234 | **/*.Server/ModelManifest.xml 235 | _Pvt_Extensions 236 | 237 | # LightSwitch generated files 238 | GeneratedArtifacts/ 239 | ModelManifest.xml 240 | 241 | # Paket dependency manager 242 | .paket/paket.exe 243 | 244 | # FAKE - F# Make 245 | .fake/ 246 | 247 | # Migrations folder 248 | Scheduler.API/Migrations 249 | /Scheduler.API/Properties/PublishProfiles 250 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building REST APIs using ASP.NET Core and Entity Framework Core 2 | [![License](https://img.shields.io/github/license/chsakell/dotnetcore-entityframework-api.svg)](https://github.com/chsakell/dotnetcore-entityframework-api/blob/master/licence) [![Build status](https://ci.appveyor.com/api/projects/status/github/chsakell/dotnetcore-entityframework-api?branch=master&svg=true)](https://ci.appveyor.com/project/chsakell/dotnetcore-entityframework-api/branch/master) 3 | 4 | Blog post
5 | Frameworks - Packages - Patterns - Features used 6 | 16 | dotnet-core-api-14 17 |

Installation Instructions (1)

18 | 21 | 22 |

Installation Instructions (2) - Visual Studio

23 |
    24 |
  1. Open the solution in VS 2017
  2. 25 |
  3. Open Package Manager Console and navigate to Scheduler.API by typing cd path_to_Scheduler.API
  4. 26 |
  5. Modify the connection string in appsettings.json to reflect your database environment
  6. 27 |
  7. run the following commands 28 |
      29 |
    1. Add-Migration Initial
    2. 30 |
    3. Update-Database
    4. 31 |
    32 |
  8. 33 |
  9. Build and run the Scheduler.API project
  10. 34 |
35 | 36 |

Installation Instructions (2) - Without Visual Studio

37 |
    38 |
  1. Clone or download the repository
  2. 39 | 40 |
  3. Open a terminal/cmd
  4. 41 |
  5. Open Scheduler.API folder in your favorite text editor (preferably VS Code). If you get a message Required assets to build and debug are missing from your project. Add them?, click Yes
  6. 42 |
  7. Navigate to Scheduler.Model and run dotnet restore
  8. 43 |
  9. Navigate to Scheduler.Data and run dotnet restore
  10. 44 |
  11. Navigate to Scheduler.API and run dotnet restore
  12. 45 |
  13. If you haven't SQL Server (Linux or MAC) set "InMemoryProvider": true in the appsettings.json file and skip to the last step
  14. 46 |
  15. Modify the connection string in appsettings.json to reflect your database environment
  16. 47 |
  17. While at Scheduler.API run the following commands 48 |
      49 |
    1. Add-Migration Initial
    2. 50 |
    3. Update-Database
    4. 51 |
    52 |
  18. 53 |
  19. While at Scheduler.API run dotnet run
  20. 54 |
55 | 56 | > This project is used as the backend API in this Angular 2 - TypeScript SPA 57 | 58 |

Microsoft Azure Deployment

59 | Learn how to deploy an ASP.NET Core app on Microsoft Azure here. 60 | 61 |

Follow chsakell's Blog

62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 79 | 82 | 83 | 84 |
FacebookTwitter
Microsoft Web Application Development
77 | facebook 78 | 80 | twitter-small 81 |
85 |

License

86 | Code released under the MIT license. 87 | -------------------------------------------------------------------------------- /Scheduler.API/Controllers/SchedulesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Scheduler.Data.Abstract; 6 | using Scheduler.Model; 7 | using Scheduler.API.ViewModels; 8 | using AutoMapper; 9 | using Scheduler.API.Core; 10 | 11 | // For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 12 | 13 | namespace Scheduler.API.Controllers 14 | { 15 | [Route("api/[controller]")] 16 | public class SchedulesController : Controller 17 | { 18 | private readonly IScheduleRepository _scheduleRepository; 19 | private readonly IAttendeeRepository _attendeeRepository; 20 | private readonly IUserRepository _userRepository; 21 | int page = 1; 22 | int pageSize = 4; 23 | public SchedulesController(IScheduleRepository scheduleRepository, 24 | IAttendeeRepository attendeeRepository, 25 | IUserRepository userRepository) 26 | { 27 | _scheduleRepository = scheduleRepository; 28 | _attendeeRepository = attendeeRepository; 29 | _userRepository = userRepository; 30 | } 31 | 32 | public IActionResult Get() 33 | { 34 | var pagination = Request.Headers["Pagination"]; 35 | 36 | if (!string.IsNullOrEmpty(pagination)) 37 | { 38 | string[] vals = pagination.ToString().Split(','); 39 | int.TryParse(vals[0], out page); 40 | int.TryParse(vals[1], out pageSize); 41 | } 42 | 43 | int currentPage = page; 44 | int currentPageSize = pageSize; 45 | var totalSchedules = _scheduleRepository.Count(); 46 | var totalPages = (int)Math.Ceiling((double)totalSchedules / pageSize); 47 | 48 | IEnumerable _schedules = _scheduleRepository 49 | .AllIncluding(s => s.Creator, s => s.Attendees) 50 | .OrderBy(s => s.Id) 51 | .Skip((currentPage - 1) * currentPageSize) 52 | .Take(currentPageSize) 53 | .ToList(); 54 | 55 | Response.AddPagination(page, pageSize, totalSchedules, totalPages); 56 | 57 | IEnumerable _schedulesVM = Mapper.Map, IEnumerable>(_schedules); 58 | 59 | return new OkObjectResult(_schedulesVM); 60 | } 61 | 62 | [HttpGet("{id}", Name = "GetSchedule")] 63 | public IActionResult Get(int id) 64 | { 65 | Schedule _schedule = _scheduleRepository 66 | .GetSingle(s => s.Id == id, s => s.Creator, s => s.Attendees); 67 | 68 | if (_schedule != null) 69 | { 70 | ScheduleViewModel _scheduleVM = Mapper.Map(_schedule); 71 | return new OkObjectResult(_scheduleVM); 72 | } 73 | else 74 | { 75 | return NotFound(); 76 | } 77 | } 78 | 79 | [HttpGet("{id}/details", Name = "GetScheduleDetails")] 80 | public IActionResult GetScheduleDetails(int id) 81 | { 82 | Schedule _schedule = _scheduleRepository 83 | .GetSingle(s => s.Id == id, s => s.Creator, s => s.Attendees); 84 | 85 | if (_schedule != null) 86 | { 87 | 88 | 89 | ScheduleDetailsViewModel _scheduleDetailsVM = Mapper.Map(_schedule); 90 | 91 | foreach (var attendee in _schedule.Attendees) 92 | { 93 | User _userDb = _userRepository.GetSingle(attendee.UserId); 94 | _scheduleDetailsVM.Attendees.Add(Mapper.Map(_userDb)); 95 | } 96 | 97 | 98 | return new OkObjectResult(_scheduleDetailsVM); 99 | } 100 | else 101 | { 102 | return NotFound(); 103 | } 104 | } 105 | 106 | [HttpPost] 107 | public IActionResult Create([FromBody]ScheduleViewModel schedule) 108 | { 109 | if (!ModelState.IsValid) 110 | { 111 | return BadRequest(ModelState); 112 | } 113 | 114 | Schedule _newSchedule = Mapper.Map(schedule); 115 | _newSchedule.DateCreated = DateTime.Now; 116 | 117 | _scheduleRepository.Add(_newSchedule); 118 | _scheduleRepository.Commit(); 119 | 120 | foreach (var userId in schedule.Attendees) 121 | { 122 | _newSchedule.Attendees.Add(new Attendee { UserId = userId }); 123 | } 124 | _scheduleRepository.Commit(); 125 | 126 | schedule = Mapper.Map(_newSchedule); 127 | 128 | CreatedAtRouteResult result = CreatedAtRoute("GetSchedule", new { controller = "Schedules", id = schedule.Id }, schedule); 129 | return result; 130 | } 131 | 132 | [HttpPut("{id}")] 133 | public IActionResult Put(int id, [FromBody]ScheduleViewModel schedule) 134 | { 135 | if (!ModelState.IsValid) 136 | { 137 | return BadRequest(ModelState); 138 | } 139 | 140 | Schedule _scheduleDb = _scheduleRepository.GetSingle(id); 141 | 142 | if (_scheduleDb == null) 143 | { 144 | return NotFound(); 145 | } 146 | else 147 | { 148 | _scheduleDb.Title = schedule.Title; 149 | _scheduleDb.Location = schedule.Location; 150 | _scheduleDb.Description = schedule.Description; 151 | _scheduleDb.Status = (ScheduleStatus)Enum.Parse(typeof(ScheduleStatus), schedule.Status); 152 | _scheduleDb.Type = (ScheduleType)Enum.Parse(typeof(ScheduleType), schedule.Type); 153 | _scheduleDb.TimeStart = schedule.TimeStart; 154 | _scheduleDb.TimeEnd = schedule.TimeEnd; 155 | 156 | // Remove current attendees 157 | _attendeeRepository.DeleteWhere(a => a.ScheduleId == id); 158 | 159 | foreach (var userId in schedule.Attendees) 160 | { 161 | _scheduleDb.Attendees.Add(new Attendee { ScheduleId = id, UserId = userId }); 162 | } 163 | 164 | _scheduleRepository.Commit(); 165 | } 166 | 167 | schedule = Mapper.Map(_scheduleDb); 168 | 169 | return new NoContentResult(); 170 | } 171 | 172 | [HttpDelete("{id}", Name = "RemoveSchedule")] 173 | public IActionResult Delete(int id) 174 | { 175 | Schedule _scheduleDb = _scheduleRepository.GetSingle(id); 176 | 177 | if (_scheduleDb == null) 178 | { 179 | return new NotFoundResult(); 180 | } 181 | else 182 | { 183 | _attendeeRepository.DeleteWhere(a => a.ScheduleId == id); 184 | _scheduleRepository.Delete(_scheduleDb); 185 | 186 | _scheduleRepository.Commit(); 187 | 188 | return new NoContentResult(); 189 | } 190 | } 191 | 192 | [HttpDelete("{id}/removeattendee/{attendee}")] 193 | public IActionResult Delete(int id, int attendee) 194 | { 195 | Schedule _scheduleDb = _scheduleRepository.GetSingle(id); 196 | 197 | if (_scheduleDb == null) 198 | { 199 | return new NotFoundResult(); 200 | } 201 | else 202 | { 203 | _attendeeRepository.DeleteWhere(a => a.ScheduleId == id && a.UserId == attendee); 204 | 205 | _attendeeRepository.Commit(); 206 | 207 | return new NoContentResult(); 208 | } 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /Scheduler.API/Controllers/UsersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Scheduler.Data.Abstract; 6 | using Scheduler.Model; 7 | using Scheduler.API.ViewModels; 8 | using AutoMapper; 9 | using Scheduler.API.Core; 10 | 11 | // For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 12 | 13 | namespace Scheduler.API.Controllers 14 | { 15 | [Route("api/[controller]")] 16 | public class UsersController : Controller 17 | { 18 | private readonly IUserRepository _userRepository; 19 | private readonly IScheduleRepository _scheduleRepository; 20 | private readonly IAttendeeRepository _attendeeRepository; 21 | 22 | int page = 1; 23 | int pageSize = 10; 24 | public UsersController(IUserRepository userRepository, 25 | IScheduleRepository scheduleRepository, 26 | IAttendeeRepository attendeeRepository) 27 | { 28 | _userRepository = userRepository; 29 | _scheduleRepository = scheduleRepository; 30 | _attendeeRepository = attendeeRepository; 31 | } 32 | 33 | public IActionResult Get() 34 | { 35 | var pagination = Request.Headers["Pagination"]; 36 | 37 | if (!string.IsNullOrEmpty(pagination)) 38 | { 39 | string[] vals = pagination.ToString().Split(','); 40 | int.TryParse(vals[0], out page); 41 | int.TryParse(vals[1], out pageSize); 42 | } 43 | 44 | int currentPage = page; 45 | int currentPageSize = pageSize; 46 | var totalUsers = _userRepository.Count(); 47 | var totalPages = (int)Math.Ceiling((double)totalUsers / pageSize); 48 | 49 | IEnumerable _users = _userRepository 50 | .AllIncluding(u => u.SchedulesCreated) 51 | .OrderBy(u => u.Id) 52 | .Skip((currentPage - 1) * currentPageSize) 53 | .Take(currentPageSize) 54 | .ToList(); 55 | 56 | IEnumerable _usersVM = Mapper.Map, IEnumerable>(_users); 57 | 58 | Response.AddPagination(page, pageSize, totalUsers, totalPages); 59 | 60 | return new OkObjectResult(_usersVM); 61 | } 62 | 63 | [HttpGet("{id}", Name = "GetUser")] 64 | public IActionResult Get(int id) 65 | { 66 | User _user = _userRepository.GetSingle(u => u.Id == id, u => u.SchedulesCreated); 67 | 68 | if (_user != null) 69 | { 70 | UserViewModel _userVM = Mapper.Map(_user); 71 | return new OkObjectResult(_userVM); 72 | } 73 | else 74 | { 75 | return NotFound(); 76 | } 77 | } 78 | 79 | [HttpGet("{id}/schedules", Name = "GetUserSchedules")] 80 | public IActionResult GetSchedules(int id) 81 | { 82 | IEnumerable _userSchedules = _scheduleRepository.FindBy(s => s.CreatorId == id); 83 | 84 | if (_userSchedules != null) 85 | { 86 | IEnumerable _userSchedulesVM = Mapper.Map, IEnumerable>(_userSchedules); 87 | return new OkObjectResult(_userSchedulesVM); 88 | } 89 | else 90 | { 91 | return NotFound(); 92 | } 93 | } 94 | 95 | [HttpPost] 96 | public IActionResult Create([FromBody]UserViewModel user) 97 | { 98 | 99 | if (!ModelState.IsValid) 100 | { 101 | return BadRequest(ModelState); 102 | } 103 | 104 | User _newUser = new User { Name = user.Name, Profession = user.Profession, Avatar = user.Avatar }; 105 | 106 | _userRepository.Add(_newUser); 107 | _userRepository.Commit(); 108 | 109 | user = Mapper.Map(_newUser); 110 | 111 | CreatedAtRouteResult result = CreatedAtRoute("GetUser", new { controller = "Users", id = user.Id }, user); 112 | return result; 113 | } 114 | 115 | [HttpPut("{id}")] 116 | public IActionResult Put(int id, [FromBody]UserViewModel user) 117 | { 118 | if (!ModelState.IsValid) 119 | { 120 | return BadRequest(ModelState); 121 | } 122 | 123 | User _userDb = _userRepository.GetSingle(id); 124 | 125 | if (_userDb == null) 126 | { 127 | return NotFound(); 128 | } 129 | else 130 | { 131 | _userDb.Name = user.Name; 132 | _userDb.Profession = user.Profession; 133 | _userDb.Avatar = user.Avatar; 134 | _userRepository.Commit(); 135 | } 136 | 137 | user = Mapper.Map(_userDb); 138 | 139 | return new NoContentResult(); 140 | } 141 | 142 | [HttpDelete("{id}")] 143 | public IActionResult Delete(int id) 144 | { 145 | User _userDb = _userRepository.GetSingle(id); 146 | 147 | if (_userDb == null) 148 | { 149 | return new NotFoundResult(); 150 | } 151 | else 152 | { 153 | IEnumerable _attendees = _attendeeRepository.FindBy(a => a.UserId == id); 154 | IEnumerable _schedules = _scheduleRepository.FindBy(s => s.CreatorId == id); 155 | 156 | foreach (var attendee in _attendees) 157 | { 158 | _attendeeRepository.Delete(attendee); 159 | } 160 | 161 | foreach (var schedule in _schedules) 162 | { 163 | _attendeeRepository.DeleteWhere(a => a.ScheduleId == schedule.Id); 164 | _scheduleRepository.Delete(schedule); 165 | } 166 | 167 | _userRepository.Delete(_userDb); 168 | 169 | _userRepository.Commit(); 170 | 171 | return new NoContentResult(); 172 | } 173 | } 174 | 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /Scheduler.API/Core/Extensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace Scheduler.API.Core 4 | { 5 | public static class Extensions 6 | { 7 | /// 8 | /// Extension method to add pagination info to Response headers 9 | /// 10 | /// 11 | /// 12 | /// 13 | /// 14 | /// 15 | public static void AddPagination(this HttpResponse response, int currentPage, int itemsPerPage, int totalItems, int totalPages) 16 | { 17 | var paginationHeader = new PaginationHeader(currentPage, itemsPerPage, totalItems, totalPages); 18 | 19 | response.Headers.Add("Pagination", 20 | Newtonsoft.Json.JsonConvert.SerializeObject(paginationHeader)); 21 | // CORS 22 | response.Headers.Add("access-control-expose-headers", "Pagination"); 23 | } 24 | 25 | public static void AddApplicationError(this HttpResponse response, string message) 26 | { 27 | response.Headers.Add("Application-Error", message); 28 | // CORS 29 | response.Headers.Add("access-control-expose-headers", "Application-Error"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Scheduler.API/Core/PaginationHeader.cs: -------------------------------------------------------------------------------- 1 | namespace Scheduler.API.Core 2 | { 3 | public class PaginationHeader 4 | { 5 | public int CurrentPage { get; set; } 6 | public int ItemsPerPage { get; set; } 7 | public int TotalItems { get; set; } 8 | public int TotalPages { get; set; } 9 | 10 | public PaginationHeader(int currentPage, int itemsPerPage, int totalItems, int totalPages) 11 | { 12 | this.CurrentPage = currentPage; 13 | this.ItemsPerPage = itemsPerPage; 14 | this.TotalItems = totalItems; 15 | this.TotalPages = totalPages; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Scheduler.API/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Scheduler.API 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Scheduler.API/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51020", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Scheduler.API": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Scheduler.API/Scheduler.API.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | InProcess 6 | scheduler-api-ce345b64-19cf-4972-b34f-d16f2e7976ed 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Scheduler.API/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Diagnostics; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.EntityFrameworkCore; 11 | using Microsoft.Extensions.Configuration; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using Newtonsoft.Json.Serialization; 14 | using Scheduler.API.Core; 15 | using Scheduler.API.ViewModels.Mappings; 16 | using Scheduler.Data; 17 | using Scheduler.Data.Abstract; 18 | using Scheduler.Data.Repositories; 19 | 20 | namespace Scheduler.API 21 | { 22 | public class Startup 23 | { 24 | private static string _applicationPath = string.Empty; 25 | string sqlConnectionString = string.Empty; 26 | bool useInMemoryProvider = false; 27 | public IConfigurationRoot Configuration { get; } 28 | 29 | public Startup(IHostingEnvironment env) 30 | { 31 | _applicationPath = env.WebRootPath; 32 | // Setup configuration sources. 33 | 34 | var builder = new ConfigurationBuilder() 35 | .SetBasePath(env.ContentRootPath) 36 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 37 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 38 | .AddEnvironmentVariables(); 39 | 40 | if (env.IsDevelopment()) 41 | { 42 | // This reads the configuration keys from the secret store. 43 | // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709 44 | builder.AddUserSecrets(); 45 | } 46 | 47 | Configuration = builder.Build(); 48 | } 49 | 50 | // This method gets called by the runtime. Use this method to add services to the container. 51 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 52 | public void ConfigureServices(IServiceCollection services) 53 | { 54 | string sqlConnectionString = Configuration.GetConnectionString("DefaultConnection"); 55 | try 56 | { 57 | useInMemoryProvider = bool.Parse(Configuration["AppSettings:InMemoryProvider"]); 58 | } 59 | catch { } 60 | 61 | services.AddDbContext(options => { 62 | switch (useInMemoryProvider) 63 | { 64 | case true: 65 | options.UseInMemoryDatabase(); 66 | break; 67 | default: 68 | options.UseSqlServer(sqlConnectionString, 69 | b => b.MigrationsAssembly("Scheduler.API")); 70 | break; 71 | } 72 | }); 73 | 74 | 75 | // Repositories 76 | services.AddScoped(); 77 | services.AddScoped(); 78 | services.AddScoped(); 79 | 80 | // Automapper Configuration 81 | AutoMapperConfiguration.Configure(); 82 | 83 | // Enable Cors 84 | services.AddCors(); 85 | 86 | // Add MVC services to the services container. 87 | services.AddMvc() 88 | .AddJsonOptions(opts => 89 | { 90 | // Force Camel Case to JSON 91 | opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 92 | }); 93 | } 94 | 95 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 96 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 97 | { 98 | app.UseStaticFiles(); 99 | // Add MVC to the request pipeline. 100 | app.UseCors(builder => 101 | builder.AllowAnyOrigin() 102 | .AllowAnyHeader() 103 | .AllowAnyMethod()); 104 | 105 | app.UseExceptionHandler( 106 | builder => 107 | { 108 | builder.Run( 109 | async context => 110 | { 111 | context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; 112 | context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); 113 | 114 | var error = context.Features.Get(); 115 | if (error != null) 116 | { 117 | context.Response.AddApplicationError(error.Error.Message); 118 | await context.Response.WriteAsync(error.Error.Message).ConfigureAwait(false); 119 | } 120 | }); 121 | }); 122 | 123 | app.UseMvc(routes => 124 | { 125 | routes.MapRoute( 126 | name: "default", 127 | template: "{controller=Home}/{action=Index}/{id?}"); 128 | 129 | // Uncomment the following line to add a route for porting Web API 2 controllers. 130 | //routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}"); 131 | }); 132 | 133 | SchedulerDbInitializer.Initialize(app.ApplicationServices); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Scheduler.API/ViewModels/Mappings/AutoMapperConfiguration.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | namespace Scheduler.API.ViewModels.Mappings 4 | { 5 | public class AutoMapperConfiguration 6 | { 7 | public static void Configure() 8 | { 9 | Mapper.Initialize(x => 10 | { 11 | x.AddProfile(); 12 | x.AddProfile(); 13 | }); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Scheduler.API/ViewModels/Mappings/DomainToViewModelMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Scheduler.Model; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace Scheduler.API.ViewModels.Mappings 8 | { 9 | public class DomainToViewModelMappingProfile : Profile 10 | { 11 | public DomainToViewModelMappingProfile() 12 | { 13 | CreateMap() 14 | .ForMember(vm => vm.Creator, 15 | map => map.MapFrom(s => s.Creator.Name)) 16 | .ForMember(vm => vm.Attendees, map => 17 | map.MapFrom(s => s.Attendees.Select(a => a.UserId))); 18 | 19 | CreateMap() 20 | .ForMember(vm => vm.Creator, 21 | map => map.MapFrom(s => s.Creator.Name)) 22 | .ForMember(vm => vm.Attendees, map => 23 | map.MapFrom(src => new List())) 24 | .ForMember(vm => vm.Status, map => 25 | map.MapFrom(s => ((ScheduleStatus)s.Status).ToString())) 26 | .ForMember(vm => vm.Type, map => 27 | map.MapFrom(s => ((ScheduleType)s.Type).ToString())) 28 | .ForMember(vm => vm.Statuses, map => 29 | map.MapFrom(src => Enum.GetNames(typeof(ScheduleStatus)).ToArray())) 30 | .ForMember(vm => vm.Types, map => 31 | map.MapFrom(src => Enum.GetNames(typeof(ScheduleType)).ToArray())); 32 | 33 | CreateMap() 34 | .ForMember(vm => vm.SchedulesCreated, 35 | map => map.MapFrom(u => u.SchedulesCreated.Count())); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Scheduler.API/ViewModels/Mappings/ViewModelToDomainMappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Scheduler.Model; 3 | using System.Collections.Generic; 4 | 5 | namespace Scheduler.API.ViewModels.Mappings 6 | { 7 | public class ViewModelToDomainMappingProfile : Profile 8 | { 9 | public ViewModelToDomainMappingProfile() 10 | { 11 | CreateMap() 12 | .ForMember(s => s.Creator, map => map.MapFrom(src => default(User))) 13 | .ForMember(s => s.Attendees, map => map.MapFrom(src => new List())); 14 | 15 | CreateMap(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Scheduler.API/ViewModels/ScheduleDetailsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Scheduler.API.ViewModels 5 | { 6 | public class ScheduleDetailsViewModel 7 | { 8 | public int Id { get; set; } 9 | public string Title { get; set; } 10 | public string Description { get; set; } 11 | public DateTime TimeStart { get; set; } 12 | public DateTime TimeEnd { get; set; } 13 | public string Location { get; set; } 14 | public string Type { get; set; } 15 | public string Status { get; set; } 16 | public DateTime DateCreated { get; set; } 17 | public DateTime DateUpdated { get; set; } 18 | public string Creator { get; set; } 19 | public int CreatorId { get; set; } 20 | public ICollection Attendees { get; set; } 21 | // Lookups 22 | public string[] Statuses { get; set; } 23 | public string[] Types { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Scheduler.API/ViewModels/ScheduleViewModel.cs: -------------------------------------------------------------------------------- 1 | using Scheduler.API.ViewModels.Validations; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Linq; 6 | 7 | namespace Scheduler.API.ViewModels 8 | { 9 | public class ScheduleViewModel : IValidatableObject 10 | { 11 | public int Id { get; set; } 12 | public string Title { get; set; } 13 | public string Description { get; set; } 14 | public DateTime TimeStart { get; set; } 15 | public DateTime TimeEnd { get; set; } 16 | public string Location { get; set; } 17 | public string Type { get; set; } 18 | public string Status { get; set; } 19 | public DateTime DateCreated { get; set; } 20 | public DateTime DateUpdated { get; set; } 21 | public string Creator { get; set; } 22 | public int CreatorId { get; set; } 23 | public int[] Attendees { get; set; } 24 | 25 | public IEnumerable Validate(ValidationContext validationContext) 26 | { 27 | var validator = new ScheduleViewModelValidator(); 28 | var result = validator.Validate(this); 29 | return result.Errors.Select(item => new ValidationResult(item.ErrorMessage, new[] { item.PropertyName })); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Scheduler.API/ViewModels/UserViewModel.cs: -------------------------------------------------------------------------------- 1 | using Scheduler.API.ViewModels.Validations; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | 6 | namespace Scheduler.API.ViewModels 7 | { 8 | public class UserViewModel : IValidatableObject 9 | { 10 | public int Id { get; set; } 11 | public string Name { get; set; } 12 | public string Avatar { get; set; } 13 | public string Profession { get; set; } 14 | public int SchedulesCreated { get; set; } 15 | 16 | public IEnumerable Validate(ValidationContext validationContext) 17 | { 18 | var validator = new UserViewModelValidator(); 19 | var result = validator.Validate(this); 20 | return result.Errors.Select(item => new ValidationResult(item.ErrorMessage, new[] { item.PropertyName })); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scheduler.API/ViewModels/Validations/ScheduleViewModelValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using System; 3 | 4 | namespace Scheduler.API.ViewModels.Validations 5 | { 6 | public class ScheduleViewModelValidator : AbstractValidator 7 | { 8 | public ScheduleViewModelValidator() 9 | { 10 | RuleFor(s => s.TimeEnd).Must((start, end) => 11 | { 12 | return DateTimeIsGreater(start.TimeStart, end); 13 | }).WithMessage("Schedule's End time must be greater than Start time"); 14 | } 15 | 16 | private bool DateTimeIsGreater(DateTime start, DateTime end) 17 | { 18 | return end > start; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Scheduler.API/ViewModels/Validations/UserViewModelValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace Scheduler.API.ViewModels.Validations 4 | { 5 | public class UserViewModelValidator : AbstractValidator 6 | { 7 | public UserViewModelValidator() 8 | { 9 | RuleFor(user => user.Name).NotEmpty().WithMessage("Name cannot be empty"); 10 | RuleFor(user => user.Profession).NotEmpty().WithMessage("Profession cannot be empty"); 11 | RuleFor(user => user.Avatar).NotEmpty().WithMessage("Profession cannot be empty"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Scheduler.API/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Scheduler.API/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*", 8 | "ConnectionStrings": { 9 | "DefaultConnection": "Server=.;Database=SchedulerDb;Trusted_Connection=True;MultipleActiveResultSets=true" 10 | }, 11 | "AppSettings": { 12 | "InMemoryProvider": "false" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Scheduler.API/wwwroot/images/avatar_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/dotnetcore-entityframework-api/e1b38cc9fe6269a7da260e8a24b5fcc681dc88b9/Scheduler.API/wwwroot/images/avatar_01.png -------------------------------------------------------------------------------- /Scheduler.API/wwwroot/images/avatar_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/dotnetcore-entityframework-api/e1b38cc9fe6269a7da260e8a24b5fcc681dc88b9/Scheduler.API/wwwroot/images/avatar_02.png -------------------------------------------------------------------------------- /Scheduler.API/wwwroot/images/avatar_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/dotnetcore-entityframework-api/e1b38cc9fe6269a7da260e8a24b5fcc681dc88b9/Scheduler.API/wwwroot/images/avatar_03.jpg -------------------------------------------------------------------------------- /Scheduler.API/wwwroot/images/avatar_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/dotnetcore-entityframework-api/e1b38cc9fe6269a7da260e8a24b5fcc681dc88b9/Scheduler.API/wwwroot/images/avatar_04.jpg -------------------------------------------------------------------------------- /Scheduler.API/wwwroot/images/avatar_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/dotnetcore-entityframework-api/e1b38cc9fe6269a7da260e8a24b5fcc681dc88b9/Scheduler.API/wwwroot/images/avatar_05.png -------------------------------------------------------------------------------- /Scheduler.Data/Abstract/IEntityBaseRepository.cs: -------------------------------------------------------------------------------- 1 | using Scheduler.Model; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using System.Linq.Expressions; 7 | 8 | namespace Scheduler.Data.Abstract 9 | { 10 | public interface IEntityBaseRepository where T : class, IEntityBase, new() 11 | { 12 | IEnumerable AllIncluding(params Expression>[] includeProperties); 13 | IEnumerable GetAll(); 14 | int Count(); 15 | T GetSingle(int id); 16 | T GetSingle(Expression> predicate); 17 | T GetSingle(Expression> predicate, params Expression>[] includeProperties); 18 | IEnumerable FindBy(Expression> predicate); 19 | void Add(T entity); 20 | void Update(T entity); 21 | void Delete(T entity); 22 | void DeleteWhere(Expression> predicate); 23 | void Commit(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Scheduler.Data/Abstract/IRepositories.cs: -------------------------------------------------------------------------------- 1 | using Scheduler.Model; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Scheduler.Data.Abstract 8 | { 9 | public interface IScheduleRepository : IEntityBaseRepository { } 10 | 11 | public interface IUserRepository : IEntityBaseRepository { } 12 | 13 | public interface IAttendeeRepository : IEntityBaseRepository { } 14 | } 15 | -------------------------------------------------------------------------------- /Scheduler.Data/Repositories/AttendeeRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Scheduler.Model; 6 | using Scheduler.Data; 7 | using Scheduler.Data.Repositories; 8 | using Scheduler.Data.Abstract; 9 | 10 | namespace Scheduler.Data.Repositories 11 | { 12 | public class AttendeeRepository : EntityBaseRepository, IAttendeeRepository 13 | { 14 | public AttendeeRepository(SchedulerContext context) 15 | : base(context) 16 | { } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Scheduler.Data/Repositories/EntityBaseRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.ChangeTracking; 3 | using Scheduler.Data.Abstract; 4 | using Scheduler.Model; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Linq.Expressions; 9 | using System.Threading.Tasks; 10 | 11 | namespace Scheduler.Data.Repositories 12 | { 13 | public class EntityBaseRepository : IEntityBaseRepository 14 | where T : class, IEntityBase, new() 15 | { 16 | 17 | private SchedulerContext _context; 18 | 19 | #region Properties 20 | public EntityBaseRepository(SchedulerContext context) 21 | { 22 | _context = context; 23 | } 24 | #endregion 25 | public virtual IEnumerable GetAll() 26 | { 27 | return _context.Set().AsEnumerable(); 28 | } 29 | 30 | public virtual int Count() 31 | { 32 | return _context.Set().Count(); 33 | } 34 | public virtual IEnumerable AllIncluding(params Expression>[] includeProperties) 35 | { 36 | IQueryable query = _context.Set(); 37 | foreach (var includeProperty in includeProperties) 38 | { 39 | query = query.Include(includeProperty); 40 | } 41 | return query.AsEnumerable(); 42 | } 43 | 44 | public T GetSingle(int id) 45 | { 46 | return _context.Set().FirstOrDefault(x => x.Id == id); 47 | } 48 | 49 | public T GetSingle(Expression> predicate) 50 | { 51 | return _context.Set().FirstOrDefault(predicate); 52 | } 53 | 54 | public T GetSingle(Expression> predicate, params Expression>[] includeProperties) 55 | { 56 | IQueryable query = _context.Set(); 57 | foreach (var includeProperty in includeProperties) 58 | { 59 | query = query.Include(includeProperty); 60 | } 61 | 62 | return query.Where(predicate).FirstOrDefault(); 63 | } 64 | 65 | public virtual IEnumerable FindBy(Expression> predicate) 66 | { 67 | return _context.Set().Where(predicate); 68 | } 69 | 70 | public virtual void Add(T entity) 71 | { 72 | EntityEntry dbEntityEntry = _context.Entry(entity); 73 | _context.Set().Add(entity); 74 | } 75 | 76 | public virtual void Update(T entity) 77 | { 78 | EntityEntry dbEntityEntry = _context.Entry(entity); 79 | dbEntityEntry.State = EntityState.Modified; 80 | } 81 | public virtual void Delete(T entity) 82 | { 83 | EntityEntry dbEntityEntry = _context.Entry(entity); 84 | dbEntityEntry.State = EntityState.Deleted; 85 | } 86 | 87 | public virtual void DeleteWhere(Expression> predicate) 88 | { 89 | IEnumerable entities = _context.Set().Where(predicate); 90 | 91 | foreach(var entity in entities) 92 | { 93 | _context.Entry(entity).State = EntityState.Deleted; 94 | } 95 | } 96 | 97 | public virtual void Commit() 98 | { 99 | _context.SaveChanges(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Scheduler.Data/Repositories/ScheduleRepository.cs: -------------------------------------------------------------------------------- 1 | using Scheduler.Data.Abstract; 2 | using Scheduler.Model; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Scheduler.Data.Repositories 9 | { 10 | public class ScheduleRepository : EntityBaseRepository, IScheduleRepository 11 | { 12 | public ScheduleRepository(SchedulerContext context) 13 | : base(context) 14 | { } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Scheduler.Data/Repositories/UserRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Scheduler.Model; 6 | using Scheduler.Data; 7 | using Scheduler.Data.Repositories; 8 | using Scheduler.Data.Abstract; 9 | 10 | namespace Scheduler.Data.Repositories 11 | { 12 | public class UserRepository : EntityBaseRepository, IUserRepository 13 | { 14 | public UserRepository(SchedulerContext context) 15 | : base(context) 16 | { } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Scheduler.Data/Scheduler.Data.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Scheduler.Data/SchedulerContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.EntityFrameworkCore; 6 | using Scheduler.Model; 7 | using Microsoft.EntityFrameworkCore.Metadata; 8 | 9 | namespace Scheduler.Data 10 | { 11 | public class SchedulerContext : DbContext 12 | { 13 | public DbSet Schedules { get; set; } 14 | public DbSet Users { get; set; } 15 | public DbSet Attendees { get; set; } 16 | 17 | public SchedulerContext(DbContextOptions options) : base(options) { } 18 | 19 | protected override void OnModelCreating(ModelBuilder modelBuilder) 20 | { 21 | foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys())) 22 | { 23 | relationship.DeleteBehavior = DeleteBehavior.Restrict; 24 | } 25 | 26 | 27 | modelBuilder.Entity() 28 | .ToTable("Schedule"); 29 | 30 | modelBuilder.Entity() 31 | .Property(s => s.CreatorId) 32 | .IsRequired(); 33 | 34 | modelBuilder.Entity() 35 | .Property(s => s.DateCreated) 36 | .HasDefaultValue(DateTime.Now); 37 | 38 | modelBuilder.Entity() 39 | .Property(s => s.DateUpdated) 40 | .HasDefaultValue(DateTime.Now); 41 | 42 | modelBuilder.Entity() 43 | .Property(s => s.Type) 44 | .HasDefaultValue(ScheduleType.Work); 45 | 46 | modelBuilder.Entity() 47 | .Property(s => s.Status) 48 | .HasDefaultValue(ScheduleStatus.Valid); 49 | 50 | modelBuilder.Entity() 51 | .HasOne(s => s.Creator) 52 | .WithMany(c => c.SchedulesCreated); 53 | 54 | modelBuilder.Entity() 55 | .ToTable("User"); 56 | 57 | modelBuilder.Entity() 58 | .Property(u => u.Name) 59 | .HasMaxLength(100) 60 | .IsRequired(); 61 | 62 | modelBuilder.Entity() 63 | .ToTable("Attendee"); 64 | 65 | modelBuilder.Entity() 66 | .HasOne(a => a.User) 67 | .WithMany(u => u.SchedulesAttended) 68 | .HasForeignKey(a => a.UserId); 69 | 70 | modelBuilder.Entity() 71 | .HasOne(a => a.Schedule) 72 | .WithMany(s => s.Attendees) 73 | .HasForeignKey(a => a.ScheduleId); 74 | 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Scheduler.Data/SchedulerDbInitializer.cs: -------------------------------------------------------------------------------- 1 | using Scheduler.Model; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | namespace Scheduler.Data 9 | { 10 | public class SchedulerDbInitializer 11 | { 12 | private static SchedulerContext context; 13 | public static void Initialize(IServiceProvider serviceProvider) 14 | { 15 | using (var serviceScope = serviceProvider.CreateScope()) 16 | { 17 | var context = serviceScope.ServiceProvider.GetService(); 18 | InitializeSchedules(context); 19 | } 20 | } 21 | 22 | private static void InitializeSchedules(SchedulerContext context) 23 | { 24 | if(!context.Users.Any()) 25 | { 26 | User user_01 = new User { Name = "Chris Sakellarios", Profession = "Developer", Avatar = "avatar_02.png" }; 27 | 28 | User user_02 = new User { Name = "Charlene Campbell", Profession = "Web Designer", Avatar = "avatar_03.jpg" }; 29 | 30 | User user_03 = new User { Name = "Mattie Lyons", Profession = "Engineer", Avatar = "avatar_05.png" }; 31 | 32 | User user_04 = new User { Name = "Kelly Alvarez", Profession = "Network Engineer", Avatar = "avatar_01.png" }; 33 | 34 | User user_05 = new User { Name = "Charlie Cox", Profession = "Developer", Avatar = "avatar_03.jpg" }; 35 | 36 | User user_06 = new User { Name = "Megan Fox", Profession = "Hacker", Avatar = "avatar_05.png" }; 37 | 38 | context.Users.Add(user_01); context.Users.Add(user_02); 39 | context.Users.Add(user_03); context.Users.Add(user_04); 40 | context.Users.Add(user_05); context.Users.Add(user_06); 41 | 42 | context.SaveChanges(); 43 | } 44 | 45 | if(!context.Schedules.Any()) 46 | { 47 | Schedule schedule_01 = new Schedule 48 | { 49 | Title = "Meeting", 50 | Description = "Meeting at work with the boss", 51 | Location = "Korai", 52 | CreatorId = 1, 53 | Status = ScheduleStatus.Valid, 54 | Type = ScheduleType.Work, 55 | TimeStart = DateTime.Now.AddHours(4), 56 | TimeEnd = DateTime.Now.AddHours(6), 57 | Attendees = new List 58 | { 59 | new Attendee() { ScheduleId = 1, UserId = 2 }, 60 | new Attendee() { ScheduleId = 1, UserId = 3 }, 61 | new Attendee() { ScheduleId = 1, UserId = 4 } 62 | } 63 | }; 64 | 65 | Schedule schedule_02 = new Schedule 66 | { 67 | Title = "Coffee", 68 | Description = "Coffee with folks", 69 | Location = "Athens", 70 | CreatorId = 2, 71 | Status = ScheduleStatus.Valid, 72 | Type = ScheduleType.Coffee, 73 | TimeStart = DateTime.Now.AddHours(3), 74 | TimeEnd = DateTime.Now.AddHours(6), 75 | Attendees = new List 76 | { 77 | new Attendee() { ScheduleId = 2, UserId = 1 }, 78 | new Attendee() { ScheduleId = 1, UserId = 3 }, 79 | new Attendee() { ScheduleId = 2, UserId = 4 } 80 | } 81 | }; 82 | 83 | Schedule schedule_03 = new Schedule 84 | { 85 | Title = "Shopping day", 86 | Description = "Shopping therapy", 87 | Location = "Attica", 88 | CreatorId = 3, 89 | Status = ScheduleStatus.Valid, 90 | Type = ScheduleType.Shopping, 91 | TimeStart = DateTime.Now.AddHours(3), 92 | TimeEnd = DateTime.Now.AddHours(6), 93 | Attendees = new List 94 | { 95 | new Attendee() { ScheduleId = 3, UserId = 1 }, 96 | new Attendee() { ScheduleId = 3, UserId = 4 }, 97 | new Attendee() { ScheduleId = 3, UserId = 5 } 98 | } 99 | }; 100 | 101 | Schedule schedule_04 = new Schedule 102 | { 103 | Title = "Family", 104 | Description = "Thanks giving day", 105 | Location = "Home", 106 | CreatorId = 5, 107 | Status = ScheduleStatus.Valid, 108 | Type = ScheduleType.Other, 109 | TimeStart = DateTime.Now.AddHours(3), 110 | TimeEnd = DateTime.Now.AddHours(6), 111 | Attendees = new List 112 | { 113 | new Attendee() { ScheduleId = 4, UserId = 1 }, 114 | new Attendee() { ScheduleId = 4, UserId = 2 }, 115 | new Attendee() { ScheduleId = 4, UserId = 5 } 116 | } 117 | }; 118 | 119 | Schedule schedule_05 = new Schedule 120 | { 121 | Title = "Friends", 122 | Description = "Friends giving day", 123 | Location = "Home", 124 | CreatorId = 5, 125 | Status = ScheduleStatus.Cancelled, 126 | Type = ScheduleType.Other, 127 | TimeStart = DateTime.Now.AddHours(5), 128 | TimeEnd = DateTime.Now.AddHours(7), 129 | Attendees = new List 130 | { 131 | new Attendee() { ScheduleId = 4, UserId = 1 }, 132 | new Attendee() { ScheduleId = 4, UserId = 2 }, 133 | new Attendee() { ScheduleId = 4, UserId = 3 }, 134 | new Attendee() { ScheduleId = 4, UserId = 4 }, 135 | new Attendee() { ScheduleId = 4, UserId = 5 } 136 | } 137 | }; 138 | 139 | Schedule schedule_06 = new Schedule 140 | { 141 | Title = "Meeting with the boss and collegues", 142 | Description = "Discuss project planning", 143 | Location = "Office", 144 | CreatorId = 3, 145 | Status = ScheduleStatus.Cancelled, 146 | Type = ScheduleType.Other, 147 | TimeStart = DateTime.Now.AddHours(22), 148 | TimeEnd = DateTime.Now.AddHours(30), 149 | Attendees = new List 150 | { 151 | new Attendee() { ScheduleId = 4, UserId = 1 }, 152 | new Attendee() { ScheduleId = 4, UserId = 2 }, 153 | new Attendee() { ScheduleId = 4, UserId = 5 } 154 | } 155 | }; 156 | 157 | Schedule schedule_07 = new Schedule 158 | { 159 | Title = "Scenario presentation", 160 | Description = "Discuss new movie's scenario", 161 | Location = "My special place", 162 | CreatorId = 6, 163 | Status = ScheduleStatus.Cancelled, 164 | Type = ScheduleType.Other, 165 | TimeStart = DateTime.Now.AddHours(11), 166 | TimeEnd = DateTime.Now.AddHours(13), 167 | Attendees = new List 168 | { 169 | new Attendee() { ScheduleId = 4, UserId = 4 }, 170 | new Attendee() { ScheduleId = 4, UserId = 2 }, 171 | new Attendee() { ScheduleId = 4, UserId = 3 } 172 | } 173 | }; 174 | 175 | context.Schedules.Add(schedule_01); context.Schedules.Add(schedule_02); 176 | context.Schedules.Add(schedule_03); context.Schedules.Add(schedule_04); 177 | context.Schedules.Add(schedule_05); context.Schedules.Add(schedule_06); 178 | } 179 | 180 | context.SaveChanges(); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /Scheduler.Model/Entities/Attendee.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Scheduler.Model 7 | { 8 | public class Attendee : IEntityBase 9 | { 10 | public int Id { get; set; } 11 | public int UserId { get; set; } 12 | public User User { get; set; } 13 | 14 | public int ScheduleId { get; set; } 15 | public Schedule Schedule { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Scheduler.Model/Entities/Schedule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Scheduler.Model 7 | { 8 | public class Schedule : IEntityBase 9 | { 10 | public Schedule() 11 | { 12 | Attendees = new List(); 13 | } 14 | 15 | public int Id { get; set; } 16 | public string Title { get; set; } 17 | public string Description { get; set; } 18 | public DateTime TimeStart { get; set; } 19 | public DateTime TimeEnd { get; set; } 20 | public string Location { get; set; } 21 | public ScheduleType Type { get; set; } 22 | 23 | public ScheduleStatus Status { get; set; } 24 | public DateTime DateCreated { get; set; } 25 | public DateTime DateUpdated { get; set; } 26 | public User Creator { get; set; } 27 | public int CreatorId { get; set; } 28 | public ICollection Attendees { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Scheduler.Model/Entities/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Scheduler.Model 7 | { 8 | public class User : IEntityBase 9 | { 10 | public User() 11 | { 12 | SchedulesCreated = new List(); 13 | SchedulesAttended = new List(); 14 | } 15 | public int Id { get; set; } 16 | public string Name { get; set; } 17 | public string Avatar { get; set; } 18 | public string Profession { get; set; } 19 | public ICollection SchedulesCreated { get; set; } 20 | public ICollection SchedulesAttended { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Scheduler.Model/IEntityBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Scheduler.Model 7 | { 8 | public interface IEntityBase 9 | { 10 | int Id { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Scheduler.Model/ScheduleEnums.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Scheduler.Model 7 | { 8 | public enum ScheduleType 9 | { 10 | Work = 1, 11 | Coffee = 2, 12 | Doctor = 3, 13 | Shopping = 4, 14 | Other = 5 15 | } 16 | 17 | public enum ScheduleStatus 18 | { 19 | Valid = 1, 20 | Cancelled = 2 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Scheduler.Model/Scheduler.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Scheduler.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.7 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scheduler.Model", "Scheduler.Model\Scheduler.Model.csproj", "{686E4421-7945-451D-83BA-CFB8DEFECCD1}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scheduler.Data", "Scheduler.Data\Scheduler.Data.csproj", "{746C7C73-46A3-4EF7-93B9-D6C619EEABFB}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scheduler.API", "Scheduler.API\Scheduler.API.csproj", "{C7008E92-843D-485C-82D8-059E10D9BD2B}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {686E4421-7945-451D-83BA-CFB8DEFECCD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {686E4421-7945-451D-83BA-CFB8DEFECCD1}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {686E4421-7945-451D-83BA-CFB8DEFECCD1}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {686E4421-7945-451D-83BA-CFB8DEFECCD1}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {746C7C73-46A3-4EF7-93B9-D6C619EEABFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {746C7C73-46A3-4EF7-93B9-D6C619EEABFB}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {746C7C73-46A3-4EF7-93B9-D6C619EEABFB}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {746C7C73-46A3-4EF7-93B9-D6C619EEABFB}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {C7008E92-843D-485C-82D8-059E10D9BD2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {C7008E92-843D-485C-82D8-059E10D9BD2B}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {C7008E92-843D-485C-82D8-059E10D9BD2B}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {C7008E92-843D-485C-82D8-059E10D9BD2B}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {0EEB9AEC-E9FD-4498-9A23-6DA7909C53D4} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | - 2 | branches: 3 | only: 4 | - master 5 | image: Visual Studio 2017 6 | # Test against the latest version of this Node.js version 7 | environment: 8 | DOTNET_CLI_TELEMETRY_OPTOUT: true 9 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 10 | 11 | version: 1.0.{build} 12 | configuration: Release 13 | platform: Any CPU 14 | skip_branch_with_pr: false 15 | before_build: 16 | - cmd: dotnet restore 17 | -------------------------------------------------------------------------------- /licence: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Christos Sakellarios 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | --------------------------------------------------------------------------------