Refactor to better support controller using repository pattern.

Add DTO for read.
This commit is contained in:
Jack Hadrill 2020-09-29 13:23:40 +01:00
parent ba8040a63c
commit 9c8ee7d441
22 changed files with 279 additions and 504 deletions

View File

@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VCinemaApi", "VCinema\VCinemaApi.csproj", "{465DE7C9-B246-4F97-BC9B-7004AE0C7D2E}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VCinemaApi", "VCinema\VCinemaApi.csproj", "{465DE7C9-B246-4F97-BC9B-7004AE0C7D2E}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VCinemaApiTests", "VCinemaApiTests\VCinemaApiTests.csproj", "{EC67DA15-0391-455D-8CCA-F6D12C07B2E0}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -13,5 +15,9 @@ Global
{465DE7C9-B246-4F97-BC9B-7004AE0C7D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU {465DE7C9-B246-4F97-BC9B-7004AE0C7D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{465DE7C9-B246-4F97-BC9B-7004AE0C7D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU {465DE7C9-B246-4F97-BC9B-7004AE0C7D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{465DE7C9-B246-4F97-BC9B-7004AE0C7D2E}.Release|Any CPU.Build.0 = Release|Any CPU {465DE7C9-B246-4F97-BC9B-7004AE0C7D2E}.Release|Any CPU.Build.0 = Release|Any CPU
{EC67DA15-0391-455D-8CCA-F6D12C07B2E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC67DA15-0391-455D-8CCA-F6D12C07B2E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC67DA15-0391-455D-8CCA-F6D12C07B2E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC67DA15-0391-455D-8CCA-F6D12C07B2E0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -1,4 +1,3 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace VCinemaApi.Controllers namespace VCinemaApi.Controllers
@ -6,32 +5,6 @@ namespace VCinemaApi.Controllers
[Route("api/screens")] [Route("api/screens")]
[ApiController] [ApiController]
public class ScreensController : ControllerBase public class ScreensController : ControllerBase
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
[HttpPost]
public void Post([FromBody] string value)
{
}
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
[HttpDelete("{id}")]
public void Delete(int id)
{ {
} }
} }
}

View File

@ -1,37 +0,0 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace VCinemaApi.Controllers
{
[Route("api/watchers")]
[ApiController]
public class WatchersController : ControllerBase
{
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
[HttpPost]
public void Post([FromBody] string value)
{
}
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
using VCinemaApi.Models;
namespace VCinemaApi.Data
{
public interface IScreenRepository
{
public bool SaveChanges();
public IEnumerable<Screen> GetScreens();
public Screen GetScreenById(int id);
public void CreateScreen(Screen screen);
public void UpdateScreen(Screen screen);
public void SetPlayingById(int id, bool playing);
public void SetPositionById(int id, double position);
public bool GetPlayingById(int id);
public double GetPositionById(int id);
public double GetCurrentPositionById(int id);
}
}

View File

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using VCinemaApi.Models;
namespace VCinemaApi.Data
{
public class MockScreenRepository : IScreenRepository
{
private static List<Screen> _screens = new List<Screen>
{
new Screen
{
Id = 0,
Name = "Kirby's Screen",
Source = "https://vcinema.b-cdn.net/Shrek.mp4",
Playing = true,
Position = 1337.0,
LastModified = new DateTime(2020, 03, 11, 17, 30, 00)
},
new Screen
{
Id = 1,
Name = "Jack's Screen",
Source = "https://vcinema.b-cdn.net/The%20Fanatic.mp4",
Playing = true,
Position = 69420.00,
LastModified = new DateTime(2020, 03, 20, 19, 31, 04)
}
};
public void CreateScreen(Screen screen)
{
_screens.Add(screen);
}
public void UpdateScreen(Screen newScreen)
{
var screen = GetScreenById(newScreen.Id);
screen.Name = newScreen.Name;
screen.Source = newScreen.Source;
screen.Playing = newScreen.Playing;
screen.Position = newScreen.Position;
screen.LastModified = DateTime.UtcNow;
}
public IEnumerable<Screen> GetScreens()
{
return _screens;
}
public Screen GetScreenById(int id)
{
return _screens.Find(screen => screen.Id == id);
}
public void SetPlayingById(int id, bool playing)
{
var screen = GetScreenById(id);
screen.Playing = playing;
}
public bool GetPlayingById(int id)
{
var screen = GetScreenById(id);
return screen.Playing;
}
public void SetPositionById(int id, double position)
{
var screen = GetScreenById(id);
screen.Position = position;
}
public double GetPositionById(int id)
{
var screen = GetScreenById(id);
return screen.Position;
}
public double GetCurrentPositionById(int id)
{
var screen = GetScreenById(id);
var elapsed = DateTime.UtcNow - screen.LastModified;
return elapsed.TotalSeconds + screen.Position;
}
public bool SaveChanges()
{
return true;
}
}
}

View File

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using VCinemaApi.Models;
namespace VCinemaApi.Data
{
public class ScreenRepository : IScreenRepository
{
private VCinemaContext _context;
public ScreenRepository(VCinemaContext context)
{
_context = context;
}
public void CreateScreen(Screen screen)
{
_context.Screens.Add(screen);
}
public void UpdateScreen(Screen newScreen)
{
var screen = GetScreenById(newScreen.Id);
screen.Name = newScreen.Name;
screen.Source = newScreen.Source;
screen.Playing = newScreen.Playing;
screen.Position = newScreen.Position;
screen.LastModified = DateTime.UtcNow;
}
public IEnumerable<Screen> GetScreens()
{
return _context.Screens;
}
public Screen GetScreenById(int id)
{
return _context.Screens.Find(id);
}
public void SetPlayingById(int id, bool playing)
{
var screen = GetScreenById(id);
screen.Playing = playing;
}
public bool GetPlayingById(int id)
{
var screen = GetScreenById(id);
return screen.Playing;
}
public void SetPositionById(int id, double position)
{
var screen = GetScreenById(id);
screen.Position = position;
}
public double GetPositionById(int id)
{
var screen = GetScreenById(id);
return screen.Position;
}
public double GetCurrentPositionById(int id)
{
var screen = GetScreenById(id);
var elapsed = DateTime.UtcNow - screen.LastModified;
return elapsed.TotalSeconds + screen.Position;
}
public bool SaveChanges()
{
return (_context.SaveChanges() >= 0);
}
}
}

View File

@ -1,13 +1,16 @@
using Microsoft.EntityFrameworkCore; using System;
namespace VCinemaApi.Models using Microsoft.EntityFrameworkCore;
using VCinemaApi.Models;
namespace VCinemaApi.Data
{ {
public class VCinemaContext : DbContext public class VCinemaContext : DbContext
{ {
public VCinemaContext(DbContextOptions<VCinemaContext> options) : base(options) public VCinemaContext(DbContextOptions<VCinemaContext> options) : base(options)
{ {
} }
public DbSet<Watcher> Watchers { get; set; }
public DbSet<Screen> Screens { get; set; } public DbSet<Screen> Screens { get; set; }
} }
} }

View File

@ -0,0 +1,23 @@
using System;
using VCinemaApi.Models;
namespace VCinemaApi.Dtos
{
public class ScreenReadDto
{
public ScreenReadDto(Screen screen)
{
Id = screen.Id;
Name = screen.Name;
Source = screen.Source;
Playing = screen.Playing;
Position = screen.Position;
}
public readonly int Id;
public readonly string Name;
public readonly string Source;
public readonly bool Playing;
public readonly double Position;
}
}

View File

@ -1,15 +0,0 @@
using VCinemaApi.Models;
namespace VCinemaApi.Hubs
{
public interface IVCinemaHub
{
public Screen CreateScreen(string name, string source);
public void DeleteScreen(int screenId);
public Screen WatchScreen(int screenId);
public void LeaveScreen();
public Watcher SetWatcherName(string name);
public Screen SetPlayState(bool playing);
public Screen SetPlayOffset(int playOffset);
}
}

View File

@ -1,69 +1,9 @@
using System; using Microsoft.AspNetCore.SignalR;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using VCinemaApi.Models;
using VCinemaApi.Repositories;
namespace VCinemaApi.Hubs namespace VCinemaApi.Hubs
{ {
public class VCinemaHub : Hub<IVCinemaHub> public class VCinemaHub : Hub
{ {
private readonly IScreenRepository _screenRepository;
private readonly IWatcherRepository _watcherRepository;
public VCinemaHub(IScreenRepository screenRepository, IWatcherRepository watcherRepository)
{
_screenRepository = screenRepository;
_watcherRepository = watcherRepository;
}
public override async Task OnConnectedAsync()
{
_watcherRepository.AddWatcher(Context.ConnectionId);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
_watcherRepository.DeleteWatcherByConnectionId(Context.ConnectionId);
await base.OnDisconnectedAsync(exception);
}
public Screen CreateScreen(string name, string source)
{
return _screenRepository.AddScreen(name, source);
}
public void DeleteScreen(int screenId)
{
_screenRepository.DeleteScreenById(screenId);
}
public Screen WatchScreen(int screenId)
{
var watcher = _watcherRepository.SetWatcherScreenByConnectionId(Context.ConnectionId, screenId);
return watcher.Screen;
}
public void LeaveScreen()
{
_watcherRepository.UnsetWatcherScreenByConnectionId(Context.ConnectionId);
}
public Watcher SetWatcherName(string name)
{
return _watcherRepository.SetWatcherNameByConnectionId(Context.ConnectionId, name);
}
public Screen SetPlayState(bool playing)
{
throw new NotImplementedException();
}
public Screen SetPlayOffset(int playOffset)
{
throw new NotImplementedException();
}
} }
} }

View File

@ -1,23 +1,32 @@
using System; using System;
using System.Collections.Generic; using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace VCinemaApi.Models namespace VCinemaApi.Models
{ {
public class Screen public class Screen
{ {
[Key] [Key]
public int ScreenId { get; set; } public int Id { get; set; }
[Required] [Required]
[MaxLength(100)]
public string Name { get; set; } public string Name { get; set; }
[Required] [Required]
[MaxLength(250)]
public string Source { get; set; } public string Source { get; set; }
public DateTime PlayStateUpdated { get; set; } [Required]
public bool PlayState { get; set; } [DefaultValue(false)]
public int PlayOffset { get; set; } public bool Playing { get; set; }
public ICollection<Watcher> Watchers { get; set; } [Required]
[DefaultValue(0.0)]
public double Position { get; set; }
[JsonIgnore]
public DateTime LastModified { get; set; }
} }
} }

View File

@ -1,16 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace VCinemaApi.Models
{
public class Watcher
{
[Key]
public int WatcherId { get; set; }
[Required]
public string ConnectionId { get; set; }
public string Name { get; set; }
public Screen Screen { get; set; }
}
}

View File

@ -1,14 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using VCinemaApi.Models;
namespace VCinemaApi.Repositories
{
public interface IScreenRepository
{
IEnumerable<Screen> GetScreens();
Screen GetScreenById(int id);
Screen AddScreen(string name, string source);
void DeleteScreenById(int id);
}
}

View File

@ -1,17 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using VCinemaApi.Models;
namespace VCinemaApi.Repositories
{
public interface IWatcherRepository
{
IEnumerable<Watcher> GetWatchersByScreenId(int screenId);
Watcher GetWatcherByConnectionId(string connectionId);
Watcher AddWatcher(string connectionId);
Watcher SetWatcherNameByConnectionId(string connectionId, string name);
Watcher SetWatcherScreenByConnectionId(string connectionId, int screenId);
Watcher UnsetWatcherScreenByConnectionId(string connectionId);
void DeleteWatcherByConnectionId(string connectionId);
}
}

View File

@ -1,64 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using VCinemaApi.Models;
namespace VCinemaApi.Repositories
{
public class MockScreenRepository : IScreenRepository
{
public IEnumerable<Screen> GetScreens()
{
return new List<Screen>
{
new Screen {
ScreenId = 1,
Name = "Kirby's screen",
Source = "https://vcinema.b-cdn.net/shrek.mp4",
PlayStateUpdated = new DateTime(2020, 9, 21, 21, 02, 57),
PlayState = true,
PlayOffset = 1337
},
new Screen {
ScreenId = 2,
Name = "Sid's screen",
Source = "https://vcinema.b-cdn.net/weeb.mp4",
PlayStateUpdated = new DateTime(2020, 9, 21, 21, 03, 22),
PlayState = true,
PlayOffset = 69
}
};
}
public Screen GetScreenById(int id)
{
return new Screen
{
ScreenId = id,
Name = "Kirby's screen",
Source = "https://vcinema.b-cdn.net/shrek.mp4",
PlayStateUpdated = DateTime.UtcNow,
PlayState = true,
PlayOffset = 1337
};
}
public Screen AddScreen(string name, string source)
{
return new Screen
{
ScreenId = 1,
Name = name,
Source = source,
PlayStateUpdated = DateTime.UtcNow,
PlayState = true,
PlayOffset = 1337
};
}
public void DeleteScreenById(int screenId)
{
}
}
}

View File

@ -1,109 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using VCinemaApi.Models;
namespace VCinemaApi.Repositories
{
public class MockWatcherRepository : IWatcherRepository
{
public IEnumerable<Watcher> GetWatchersByScreenId(int screenId)
{
var screen = new Screen
{
ScreenId = 1,
Name = "Kirby's screen",
Source = "https://vcinema.b-cdn.net/shrek.mp4",
PlayStateUpdated = new DateTime(2020, 9, 21, 21, 02, 57),
PlayState = true,
PlayOffset = 1337
};
return new List<Watcher>
{
new Watcher
{
WatcherId = 1,
ConnectionId = "dJSbEc73n6YjGIhj-SZz1Q",
Name = "Kirby",
Screen = screen
},
new Watcher
{
WatcherId = 2,
ConnectionId = "rNA9Jn7ytYPzQfFJ-j3NBa",
Name = "Jack",
Screen = screen
}
};
}
public Watcher GetWatcherByConnectionId(string connectionId)
{
return new Watcher
{
WatcherId = 1,
ConnectionId = "dJSbEc73n6YjGIhj-SZz1Q",
Name = "Kirby",
Screen = null
};
}
public Watcher AddWatcher(string connectionId)
{
return new Watcher
{
WatcherId = 1,
ConnectionId = connectionId,
Name = "Kirby",
Screen = null
};
}
public Watcher SetWatcherNameByConnectionId(string connectionId, string name)
{
return new Watcher
{
WatcherId = 1,
ConnectionId = connectionId,
Name = "Kirby",
Screen = null
};
}
public Watcher SetWatcherScreenByConnectionId(string connectionId, int screenId)
{
return new Watcher
{
WatcherId = 1,
ConnectionId = connectionId,
Name = "Kirby",
Screen = new Screen
{
ScreenId = screenId,
Name = "Kirby's screen",
Source = "https://vcinema.b-cdn.net/shrek.mp4",
PlayStateUpdated = new DateTime(2020, 9, 21, 21, 02, 57),
PlayState = true,
PlayOffset = 1337
}
};
}
public Watcher UnsetWatcherScreenByConnectionId(string connectionId)
{
return new Watcher
{
WatcherId = 1,
ConnectionId = connectionId,
Name = "Kirby",
Screen = null
};
}
public void DeleteWatcherByConnectionId(string connectionId)
{
}
}
}

View File

@ -1,47 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using VCinemaApi.Models;
namespace VCinemaApi.Repositories
{
public class ScreenRepository : IScreenRepository
{
private readonly VCinemaContext _context;
public ScreenRepository(VCinemaContext context)
{
_context = context;
}
public IEnumerable<Screen> GetScreens()
{
return _context.Screens.ToList();
}
public Screen GetScreenById(int id)
{
return _context.Screens.Find(id);
}
public Screen AddScreen(string name, string source)
{
var screen = new Screen
{
Name = name,
Source = source
};
_context.Screens.Add(screen);
_context.SaveChanges();
return screen;
}
public void DeleteScreenById(int screenId)
{
var screen = GetScreenById(screenId);
_context.Screens.Remove(screen);
_context.SaveChanges();
}
}
}

View File

@ -1,77 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using VCinemaApi.Models;
namespace VCinemaApi.Repositories
{
public class WatcherRepository : IWatcherRepository
{
private readonly VCinemaContext _context;
public WatcherRepository(VCinemaContext context)
{
_context = context;
}
public IEnumerable<Watcher> GetWatchersByScreenId(int screenId)
{
return _context.Watchers.Where(watcher => watcher.Screen.ScreenId == screenId);
}
public Watcher GetWatcherByConnectionId(string connectionId)
{
return _context.Watchers.SingleOrDefault(watcher => watcher.ConnectionId == connectionId);
}
public Watcher AddWatcher(string connectionId)
{
var watcher = new Watcher()
{
ConnectionId = connectionId,
};
_context.Watchers.Add(watcher);
_context.SaveChanges();
return watcher;
}
public Watcher SetWatcherNameByConnectionId(string connectionId, string name)
{
var watcher = GetWatcherByConnectionId(connectionId);
watcher.Name = name;
_context.SaveChanges();
return watcher;
}
public Watcher SetWatcherScreenByConnectionId(string connectionId, int screenId)
{
var watcher = GetWatcherByConnectionId(connectionId);
var screen = _context.Screens.Find(screenId);
watcher.Screen = screen;
_context.SaveChanges();
return watcher;
}
public Watcher UnsetWatcherScreenByConnectionId(string connectionId)
{
var watcher = GetWatcherByConnectionId(connectionId);
watcher.Screen = null;
_context.SaveChanges();
return watcher;
}
public void DeleteWatcherByConnectionId(string connectionId)
{
var watcher = GetWatcherByConnectionId(connectionId);
_context.Watchers.Remove(watcher);
_context.SaveChanges();
}
}
}

View File

@ -4,9 +4,8 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using VCinemaApi.Data;
using VCinemaApi.Hubs; using VCinemaApi.Hubs;
using VCinemaApi.Models;
using VCinemaApi.Repositories;
namespace VCinemaApi namespace VCinemaApi
{ {
@ -22,9 +21,8 @@ namespace VCinemaApi
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddDbContext<VCinemaContext>(options => options.UseInMemoryDatabase("VCinema")); services.AddDbContext<VCinemaContext>(options => options.UseInMemoryDatabase(databaseName: "VCinema"));
services.AddScoped<IScreenRepository, ScreenRepository>(); services.AddScoped<IScreenRepository, ScreenRepository>();
services.AddScoped<IWatcherRepository, WatcherRepository>();
services.AddControllers(); services.AddControllers();
services.AddSignalR(); services.AddSignalR();
} }

View File

@ -11,8 +11,9 @@
<Folder Include="Hubs\" /> <Folder Include="Hubs\" />
<Folder Include="wwwroot\" /> <Folder Include="wwwroot\" />
<Folder Include="wwwroot\js\" /> <Folder Include="wwwroot\js\" />
<Folder Include="Repositories\" /> <Folder Include="Data\" />
<Folder Include="Controllers\" /> <Folder Include="Controllers\" />
<Folder Include="Dtos\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.8" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.8" />

View File

@ -0,0 +1,13 @@
using Xunit;
namespace VCinemaApiTests
{
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}
}

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
</Project>