diff --git a/Events-MVC/Events.MVC/Constants.cs b/Events-MVC/Events.MVC/Constants.cs index 1164f48..20c66d6 100644 --- a/Events-MVC/Events.MVC/Constants.cs +++ b/Events-MVC/Events.MVC/Constants.cs @@ -28,6 +28,8 @@ public static class Constants public const string HeaderActionLabel = "HeaderActionLabel"; public const string HeaderActionTarget = "HeaderActionTarget"; public const string CreatePersonModel = "CreatePersonModel"; + public const string PeopleCountryOptions = "PeopleCountryOptions"; + public const string PeopleCountryFilter = "PeopleCountryFilter"; public const string Prefix = "Prefix"; public const string CanRemoveRows = "CanRemoveRows"; } diff --git a/Events-MVC/Events.MVC/Controllers/CountriesController.cs b/Events-MVC/Events.MVC/Controllers/CountriesController.cs index 210993b..396d166 100644 --- a/Events-MVC/Events.MVC/Controllers/CountriesController.cs +++ b/Events-MVC/Events.MVC/Controllers/CountriesController.cs @@ -22,7 +22,7 @@ public class CountriesController : Controller private readonly ISieveProcessor sieveProcessor; private readonly PagingSettings pagingSettings; - public CountriesController(EventsContext ctx, ISieveProcessor sieveProcessor, IOptions pagingSettings) + public CountriesController(EventsContext ctx, ISieveProcessor sieveProcessor, IOptionsSnapshot pagingSettings) { this.ctx = ctx; this.sieveProcessor = sieveProcessor; diff --git a/Events-MVC/Events.MVC/Controllers/EventsController.cs b/Events-MVC/Events.MVC/Controllers/EventsController.cs index 5206e68..add39b8 100644 --- a/Events-MVC/Events.MVC/Controllers/EventsController.cs +++ b/Events-MVC/Events.MVC/Controllers/EventsController.cs @@ -22,7 +22,7 @@ public class EventsController : Controller private readonly ISieveProcessor sieveProcessor; private readonly PagingSettings pagingSettings; - public EventsController(EventsContext ctx, ISieveProcessor sieveProcessor, IOptions pagingSettings) + public EventsController(EventsContext ctx, ISieveProcessor sieveProcessor, IOptionsSnapshot pagingSettings) { this.ctx = ctx; this.sieveProcessor = sieveProcessor; diff --git a/Events-MVC/Events.MVC/Controllers/PeopleController.cs b/Events-MVC/Events.MVC/Controllers/PeopleController.cs index 3173383..29adaab 100644 --- a/Events-MVC/Events.MVC/Controllers/PeopleController.cs +++ b/Events-MVC/Events.MVC/Controllers/PeopleController.cs @@ -23,7 +23,7 @@ public class PeopleController : Controller private readonly ISieveProcessor sieveProcessor; private readonly PagingSettings pagingSettings; - public PeopleController(EventsContext ctx, ISieveProcessor sieveProcessor, IOptions pagingSettings) + public PeopleController(EventsContext ctx, ISieveProcessor sieveProcessor, IOptionsSnapshot pagingSettings) { this.ctx = ctx; this.sieveProcessor = sieveProcessor; @@ -40,6 +40,7 @@ public class PeopleController : Controller return RedirectToAction("Index", "Countries"); } + await PopulatePeopleCountryFilterViewDataAsync(sieveModel.Filters); var viewModel = await BuildPeopleListAsync(sieveModel); if (Request.Headers.ContainsKey(Constants.HtmxHeaders.Request)) { @@ -155,6 +156,7 @@ public class PeopleController : Controller } }); + await PopulatePeopleCountryFilterViewDataAsync(sieveModel.Filters); var viewModel = await BuildPeopleListAsync(sieveModel); return PartialView("_PeopleList", viewModel); } @@ -248,16 +250,16 @@ public class PeopleController : Controller } }); + await PopulatePeopleCountryFilterViewDataAsync(sieveModel.Filters); var viewModel = await BuildPeopleListAsync(sieveModel); return PartialView("_PeopleList", viewModel); } - private async Task BuildPeopleListAsync(SieveModel sieveModel) + private async Task> BuildPeopleListAsync(SieveModel sieveModel) { sieveModel.SetDefaultPagingAndSorting(pagingSettings.PageSize, "LastNameTranscription"); var normalizedFilters = sieveModel.Filters?.Trim() ?? string.Empty; var nameFilter = SieveModelExtensions.ExtractFilterValue(normalizedFilters, "FullNameTranscription"); - var countryFilter = SieveModelExtensions.ExtractFilterValue(normalizedFilters, "CountryCode", "=="); var baseQuery = ctx.People .Select(p => new PersonViewModel @@ -308,12 +310,7 @@ public class PeopleController : Controller .Apply(sieveModel, baseQuery) .ToListAsync(); - return new PeoplePageViewModel - { - People = new PagedList(people, pagingInfo), - CountryOptions = await GetCountryOptionsAsync(countryFilter), - CountryFilter = countryFilter - }; + return new PagedList(people, pagingInfo); } private async Task> GetCountryOptionsAsync(string? selectedCode = null) @@ -329,4 +326,11 @@ public class PeopleController : Controller .ToListAsync(); } + private async Task PopulatePeopleCountryFilterViewDataAsync(string? filters) + { + var countryFilter = SieveModelExtensions.ExtractFilterValue(filters?.Trim() ?? string.Empty, "CountryCode", "=="); + ViewData[Constants.ViewDataKeys.PeopleCountryOptions] = await GetCountryOptionsAsync(countryFilter); + ViewData[Constants.ViewDataKeys.PeopleCountryFilter] = countryFilter; + } + } diff --git a/Events-MVC/Events.MVC/Controllers/RegistrationsController.cs b/Events-MVC/Events.MVC/Controllers/RegistrationsController.cs index c89977e..ab691e4 100644 --- a/Events-MVC/Events.MVC/Controllers/RegistrationsController.cs +++ b/Events-MVC/Events.MVC/Controllers/RegistrationsController.cs @@ -23,7 +23,7 @@ public class RegistrationsController : Controller private readonly ISieveProcessor sieveProcessor; private readonly PagingSettings pagingSettings; - public RegistrationsController(EventsContext ctx, ISieveProcessor sieveProcessor, IOptions pagingSettings) + public RegistrationsController(EventsContext ctx, ISieveProcessor sieveProcessor, IOptionsSnapshot pagingSettings) { this.ctx = ctx; this.sieveProcessor = sieveProcessor; diff --git a/Events-MVC/Events.MVC/Controllers/SportsController.cs b/Events-MVC/Events.MVC/Controllers/SportsController.cs index 4573732..462218f 100644 --- a/Events-MVC/Events.MVC/Controllers/SportsController.cs +++ b/Events-MVC/Events.MVC/Controllers/SportsController.cs @@ -22,7 +22,7 @@ public class SportsController : Controller private readonly ISieveProcessor sieveProcessor; private readonly PagingSettings pagingSettings; - public SportsController(EventsContext ctx, ISieveProcessor sieveProcessor, IOptions pagingSettings) + public SportsController(EventsContext ctx, ISieveProcessor sieveProcessor, IOptionsSnapshot pagingSettings) { this.ctx = ctx; this.sieveProcessor = sieveProcessor; diff --git a/Events-MVC/Events.MVC/Models/People/PeoplePageViewModel.cs b/Events-MVC/Events.MVC/Models/People/PeoplePageViewModel.cs deleted file mode 100644 index 642c416..0000000 --- a/Events-MVC/Events.MVC/Models/People/PeoplePageViewModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Rendering; - -namespace Events.MVC.Models.People; - -public class PeoplePageViewModel -{ - public PagedList People { get; set; } = new([], new PagingInfo()); - - public List CountryOptions { get; set; } = []; - - public string CountryFilter { get; set; } = string.Empty; -} diff --git a/Events-MVC/Events.MVC/Util/TagHelpers/PagerTagHelper.cs b/Events-MVC/Events.MVC/Util/TagHelpers/PagerTagHelper.cs index a3e79d6..7403e87 100644 --- a/Events-MVC/Events.MVC/Util/TagHelpers/PagerTagHelper.cs +++ b/Events-MVC/Events.MVC/Util/TagHelpers/PagerTagHelper.cs @@ -14,7 +14,7 @@ public class PagerTagHelper : TagHelper private readonly IUrlHelperFactory urlHelperFactory; private readonly PagingSettings pagingSettings; - public PagerTagHelper(IUrlHelperFactory urlHelperFactory, IOptions pagingSettings) + public PagerTagHelper(IUrlHelperFactory urlHelperFactory, IOptionsSnapshot pagingSettings) { this.urlHelperFactory = urlHelperFactory; this.pagingSettings = pagingSettings.Value; diff --git a/Events-MVC/Events.MVC/Views/People/Index.cshtml b/Events-MVC/Events.MVC/Views/People/Index.cshtml index fe5dd21..7f6c4ec 100644 --- a/Events-MVC/Events.MVC/Views/People/Index.cshtml +++ b/Events-MVC/Events.MVC/Views/People/Index.cshtml @@ -1,4 +1,4 @@ -@model Events.MVC.Models.People.PeoplePageViewModel +@model PagedList @{ ViewData[Constants.ViewDataKeys.Title] = "People"; diff --git a/Events-MVC/Events.MVC/Views/People/_PeopleList.cshtml b/Events-MVC/Events.MVC/Views/People/_PeopleList.cshtml index f660f61..b004989 100644 --- a/Events-MVC/Events.MVC/Views/People/_PeopleList.cshtml +++ b/Events-MVC/Events.MVC/Views/People/_PeopleList.cshtml @@ -1,13 +1,18 @@ -@model Events.MVC.Models.People.PeoplePageViewModel +@model PagedList +@{ + var countryFilter = ViewData[Constants.ViewDataKeys.PeopleCountryFilter] as string ?? string.Empty; + var countryOptions = ViewData[Constants.ViewDataKeys.PeopleCountryOptions] as IEnumerable + ?? Enumerable.Empty(); +}
- - - - - + + + + +
- - - + + +

People list

- @(Model.People.PagingInfo.IsFiltered ? $"{Model.People.PagingInfo.FilteredItemsCount} / {Model.People.PagingInfo.TotalItemsCount}" : Model.People.PagingInfo.TotalItemsCount.ToString()) + @(Model.PagingInfo.IsFiltered ? $"{Model.PagingInfo.FilteredItemsCount} / {Model.PagingInfo.TotalItemsCount}" : Model.PagingInfo.TotalItemsCount.ToString())
- - @if (Model.People.PagingInfo.IsFiltered) + @if (Model.PagingInfo.IsFiltered) { @@ -68,41 +73,41 @@ - - ID@(Model.People.PagingInfo.IsSortedBy("Id") ? (Model.People.PagingInfo.IsDescending() ? " ↓" : " ↑") : "") + + ID@(Model.PagingInfo.IsSortedBy("Id") ? (Model.PagingInfo.IsDescending() ? " ↓" : " ↑") : "") - - First name@(Model.People.PagingInfo.IsSortedBy("FirstNameTranscription") ? (Model.People.PagingInfo.IsDescending() ? " ↓" : " ↑") : "") + + First name@(Model.PagingInfo.IsSortedBy("FirstNameTranscription") ? (Model.PagingInfo.IsDescending() ? " ↓" : " ↑") : "") - - Last name@(Model.People.PagingInfo.IsSortedBy("LastNameTranscription") ? (Model.People.PagingInfo.IsDescending() ? " ↓" : " ↑") : "") + + Last name@(Model.PagingInfo.IsSortedBy("LastNameTranscription") ? (Model.PagingInfo.IsDescending() ? " ↓" : " ↑") : "") - - Birth date@(Model.People.PagingInfo.IsSortedBy("BirthDate") ? (Model.People.PagingInfo.IsDescending() ? " v" : " ^") : "") + + Birth date@(Model.PagingInfo.IsSortedBy("BirthDate") ? (Model.PagingInfo.IsDescending() ? " v" : " ^") : "") - - Country@(Model.People.PagingInfo.IsSortedBy("CountryName") ? (Model.People.PagingInfo.IsDescending() ? " ↓" : " ↑") : "") + + Country@(Model.PagingInfo.IsSortedBy("CountryName") ? (Model.PagingInfo.IsDescending() ? " ↓" : " ↑") : "") - - Registrations@(Model.People.PagingInfo.IsSortedBy("RegistrationsCount") ? (Model.People.PagingInfo.IsDescending() ? " ↓" : " ↑") : "") + + Registrations@(Model.PagingInfo.IsSortedBy("RegistrationsCount") ? (Model.PagingInfo.IsDescending() ? " ↓" : " ↑") : "") - @if (Model.People.Data.Count == 0) + @if (Model.Data.Count == 0) { No people to display. @@ -110,7 +115,7 @@ } else { - @foreach (var person in Model.People.Data) + @foreach (var person in Model.Data) { } @@ -121,24 +126,24 @@
- Page @Model.People.PagingInfo.CurrentPage of @Model.People.PagingInfo.TotalPages + Page @Model.PagingInfo.CurrentPage of @Model.PagingInfo.TotalPages - - + +
(result); Assert.Equal("_PeopleList", partial.ViewName); - var model = Assert.IsType(partial.Model); - Assert.Contains(model.People.Data, p => p.FullName == "Ana Kovac" && p.CountryName == "Croatia"); + var model = Assert.IsType>(partial.Model); + Assert.Contains(model.Data, p => p.FullName == "Ana Kovac" && p.CountryName == "Croatia"); Assert.Contains("was added successfully", controller.Response.Headers[Events.MVC.Constants.HtmxHeaders.Trigger].ToString()); } @@ -100,8 +101,8 @@ public class PeopleControllerShould var partial = Assert.IsType(result); Assert.Equal("_PeopleList", partial.ViewName); - var model = Assert.IsType(partial.Model); - var person = Assert.Single(model.People.Data); + var model = Assert.IsType>(partial.Model); + var person = Assert.Single(model.Data); Assert.Equal("Ana Kovac", person.FullName); Assert.Equal("Croatia", person.CountryName); } @@ -178,8 +179,8 @@ public class PeopleControllerShould }); var view = Assert.IsType(result); - var model = Assert.IsType(view.Model); - var people = Assert.Single(model.People.Data); + var model = Assert.IsType>(view.Model); + var people = Assert.Single(model.Data); Assert.Equal(1, people.Id); Assert.Equal("Ђорђе Петровић", people.FullName); } @@ -203,11 +204,12 @@ public class PeopleControllerShould }); var view = Assert.IsType(result); - var model = Assert.IsType(view.Model); - var person = Assert.Single(model.People.Data); + var model = Assert.IsType>(view.Model); + var person = Assert.Single(model.Data); Assert.Equal("Ana Novak", person.FullName); - Assert.Equal("SI", model.CountryFilter); - Assert.Contains(model.CountryOptions, option => option.Value == "SI" && option.Selected); + Assert.Equal("SI", Assert.IsType(view.ViewData[Events.MVC.Constants.ViewDataKeys.PeopleCountryFilter])); + var countryOptions = Assert.IsAssignableFrom>(view.ViewData[Events.MVC.Constants.ViewDataKeys.PeopleCountryOptions]); + Assert.Contains(countryOptions, option => option.Value == "SI" && option.Selected); } [Fact] diff --git a/Events-MVC/Tests/Events.Tests.UnitTests/Controllers/SportsControllerShould.cs b/Events-MVC/Tests/Events.Tests.UnitTests/Controllers/SportsControllerShould.cs index ad5d01c..14a2777 100644 --- a/Events-MVC/Tests/Events.Tests.UnitTests/Controllers/SportsControllerShould.cs +++ b/Events-MVC/Tests/Events.Tests.UnitTests/Controllers/SportsControllerShould.cs @@ -71,7 +71,7 @@ public class SportsControllerShould new Sport { Id = 3, Name = "Cycling" }); await ctx.SaveChangesAsync(); - var optionsMock = new Mock>(); + var optionsMock = new Mock>(); optionsMock .SetupGet(options => options.Value) .Returns(new PagingSettings { PageSize = 2 }); diff --git a/Events-MVC/Tests/Events.Tests.UnitTests/Infrastructure/ControllerTestContext.cs b/Events-MVC/Tests/Events.Tests.UnitTests/Infrastructure/ControllerTestContext.cs index 7c52b23..03c01d4 100644 --- a/Events-MVC/Tests/Events.Tests.UnitTests/Infrastructure/ControllerTestContext.cs +++ b/Events-MVC/Tests/Events.Tests.UnitTests/Infrastructure/ControllerTestContext.cs @@ -24,9 +24,9 @@ internal static class ControllerTestContext return new EventsContext(options); } - public static IOptions CreatePagingOptions(int pageSize = 10) + public static IOptionsSnapshot CreatePagingOptions(int pageSize = 10) { - return Options.Create(new PagingSettings { PageSize = pageSize }); + return new TestOptionsSnapshot(new PagingSettings { PageSize = pageSize }); } public static SieveModel EmptySieveModel() @@ -95,4 +95,12 @@ internal static class ControllerTestContext Name = name }; } + + private sealed class TestOptionsSnapshot(TOptions value) : IOptionsSnapshot + where TOptions : class + { + public TOptions Value => value; + + public TOptions Get(string? name) => value; + } } diff --git a/Events-MVC/Tests/Events.Tests.UnitTests/Infrastructure/ProviderSpecificQueryShould.cs b/Events-MVC/Tests/Events.Tests.UnitTests/Infrastructure/ProviderSpecificQueryShould.cs index d0c4d5c..26f9cbd 100644 --- a/Events-MVC/Tests/Events.Tests.UnitTests/Infrastructure/ProviderSpecificQueryShould.cs +++ b/Events-MVC/Tests/Events.Tests.UnitTests/Infrastructure/ProviderSpecificQueryShould.cs @@ -14,13 +14,13 @@ public class ProviderSpecificQueryShould ctx.People.Add(ControllerTestContext.CreatePerson()); await ctx.SaveChangesAsync(); - var people = await ctx.People - .Where(person => person.FirstName != null && Microsoft.EntityFrameworkCore.EF.Functions.ILike(person.FirstName, "%iv%")) - .ToListAsync(); + var people = await ctx.People + .Where(person => person.FirstName != null && Microsoft.EntityFrameworkCore.EF.Functions.ILike(person.FirstName, "%iv%")) + .ToListAsync(); - Assert.Single(people); - Assert.Equal("Ivan", people[0].FirstName); - } + Assert.Single(people); + Assert.Equal("Ivan", people[0].FirstName); + } [Fact] public async Task ThrowInvalidOperationExceptionWhenUsingILikeWithInMemoryProvider()