From 8656313f75aa3824c2937a9b3c985a9e9b45b377 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:40:04 +0000 Subject: [PATCH 1/2] Migrate SampleMvcWebApp from .NET Framework 4.5.1 (ASP.NET MVC5) to .NET 8 - Convert all 4 projects to SDK-style .csproj targeting net8.0 - Migrate EF6 to EF Core 8 with SQLite provider - Replace Autofac DI with Microsoft.Extensions.DependencyInjection - Replace Global.asax with Program.cs (minimal hosting model) - Replace Web.config with appsettings.json - Update Razor views with ASP.NET Core tag helpers and conventions - Refactor GenericServices DTOs to plain DTOs - Update all controllers to ASP.NET Core MVC with constructor injection - Remove obsolete files (packages.config, AssemblyInfo.cs, App_Start, etc.) Co-Authored-By: Achal Channarasappa --- BizLayer/App.config | 17 - BizLayer/BizLayer.csproj | 90 +--- BizLayer/Properties/AssemblyInfo.cs | 62 --- BizLayer/Startup/BizLayerInitialise.cs | 6 +- BizLayer/Startup/BizLayerModule.cs | 46 -- BizLayer/packages.config | 7 - DataLayer/App.config | 25 - DataLayer/DataClasses/Concrete/Blog.cs | 11 +- .../Concrete/Helpers/TrackUpdate.cs | 6 +- DataLayer/DataClasses/Concrete/Post.cs | 39 +- DataLayer/DataClasses/Concrete/Tag.cs | 8 +- DataLayer/DataClasses/EfConfiguration.cs | 48 -- DataLayer/DataClasses/SampleWebAppDb.cs | 145 +++--- DataLayer/DataLayer.csproj | 138 +---- DataLayer/Properties/AssemblyInfo.cs | 62 --- DataLayer/Startup/DataLayerInitialise.cs | 82 ++- DataLayer/Startup/DataLayerModule.cs | 50 -- .../DataLayerServiceCollectionExtensions.cs | 17 + .../Startup/Internal/LoadDbDataFromXml.cs | 81 ++- DataLayer/packages.config | 11 - SampleWebApp.sln | 33 +- SampleWebApp/App_Start/BundleConfig.cs | 97 ---- SampleWebApp/App_Start/FilterConfig.cs | 39 -- SampleWebApp/App_Start/RouteConfig.cs | 49 -- SampleWebApp/Controllers/BlogsController.cs | 94 ++-- SampleWebApp/Controllers/HomeController.cs | 21 +- .../Controllers/PostsAsyncController.cs | 208 +++++--- SampleWebApp/Controllers/PostsController.cs | 222 +++++--- .../Controllers/TagsAsyncController.cs | 108 ++-- SampleWebApp/Controllers/TagsController.cs | 108 ++-- SampleWebApp/Global.asax | 1 - SampleWebApp/Global.asax.cs | 51 -- SampleWebApp/Infrastructure/AutofacDi.cs | 57 -- SampleWebApp/Infrastructure/DiModelBinder.cs | 41 -- SampleWebApp/Infrastructure/JsonNetResult.cs | 75 --- .../Infrastructure/Log4NetGenericLogger.cs | 99 ---- .../Infrastructure/TraceGenericLogger.cs | 107 ---- .../Infrastructure/ValidationHelper.cs | 126 +---- .../Infrastructure/WebUiInitialise.cs | 110 ---- SampleWebApp/Models/InternalsInfo.cs | 38 +- SampleWebApp/Program.cs | 42 ++ SampleWebApp/Properties/AssemblyInfo.cs | 61 --- SampleWebApp/Properties/Settings.Designer.cs | 44 -- SampleWebApp/Properties/Settings.settings | 12 - SampleWebApp/SampleWebApp.csproj | 491 +----------------- SampleWebApp/Views/Home/Internals.cshtml | 12 +- SampleWebApp/Views/Posts/Create.cshtml | 59 ++- SampleWebApp/Views/Posts/Edit.cshtml | 60 ++- SampleWebApp/Views/PostsAsync/Create.cshtml | 59 ++- SampleWebApp/Views/PostsAsync/Edit.cshtml | 58 ++- .../EditorTemplates/DropDownListType.cshtml | 8 - .../MultiSelectListType.cshtml | 3 - SampleWebApp/Views/Shared/Error.cshtml | 3 - .../Views/Shared/PostValidation.cshtml | 115 +--- .../Views/Shared/TagValidation.cshtml | 89 +--- SampleWebApp/Views/Shared/_Layout.cshtml | 31 +- SampleWebApp/Views/Tags/Create.cshtml | 4 +- SampleWebApp/Views/Tags/Edit.cshtml | 4 +- SampleWebApp/Views/TagsAsync/Create.cshtml | 4 +- SampleWebApp/Views/TagsAsync/Edit.cshtml | 4 +- SampleWebApp/Views/Web.config | 35 -- SampleWebApp/Views/_ViewImports.cshtml | 7 + SampleWebApp/Views/_ViewStart.cshtml | 4 +- SampleWebApp/Web.Debug.config | 30 -- SampleWebApp/Web.Release.config | 31 -- SampleWebApp/Web.config | 110 ---- SampleWebApp/appsettings.Development.json | 9 + SampleWebApp/appsettings.json | 13 + SampleWebApp/packages.config | 47 -- SampleWebApp/wwwroot/css/site.css | 23 + ServiceLayer/App.config | 29 -- ServiceLayer/BlogServices/BlogListDto.cs | 21 +- ServiceLayer/PostServices/DetailPostDto.cs | 146 +----- .../PostServices/DetailPostDtoAsync.cs | 152 +----- ServiceLayer/PostServices/SimplePostDto.cs | 30 +- .../PostServices/SimplePostDtoAsync.cs | 23 +- ServiceLayer/Properties/AssemblyInfo.cs | 62 --- ServiceLayer/ServiceLayer.csproj | 139 +---- .../Startup/ServiceLayerInitialise.cs | 26 +- ServiceLayer/Startup/ServiceLayerModule.cs | 57 -- ...ServiceLayerServiceCollectionExtensions.cs | 16 + ServiceLayer/TagServices/TagListDto.cs | 21 +- ServiceLayer/packages.config | 12 - Tests/App.config | 76 --- Tests/Properties/AssemblyInfo.cs | 62 --- Tests/packages.config | 23 - 86 files changed, 1043 insertions(+), 3959 deletions(-) delete mode 100644 BizLayer/App.config delete mode 100644 BizLayer/Properties/AssemblyInfo.cs delete mode 100644 BizLayer/Startup/BizLayerModule.cs delete mode 100644 BizLayer/packages.config delete mode 100644 DataLayer/App.config delete mode 100644 DataLayer/DataClasses/EfConfiguration.cs delete mode 100644 DataLayer/Properties/AssemblyInfo.cs delete mode 100644 DataLayer/Startup/DataLayerModule.cs create mode 100644 DataLayer/Startup/DataLayerServiceCollectionExtensions.cs delete mode 100644 DataLayer/packages.config delete mode 100644 SampleWebApp/App_Start/BundleConfig.cs delete mode 100644 SampleWebApp/App_Start/FilterConfig.cs delete mode 100644 SampleWebApp/App_Start/RouteConfig.cs delete mode 100644 SampleWebApp/Global.asax delete mode 100644 SampleWebApp/Global.asax.cs delete mode 100644 SampleWebApp/Infrastructure/AutofacDi.cs delete mode 100644 SampleWebApp/Infrastructure/DiModelBinder.cs delete mode 100644 SampleWebApp/Infrastructure/JsonNetResult.cs delete mode 100644 SampleWebApp/Infrastructure/Log4NetGenericLogger.cs delete mode 100644 SampleWebApp/Infrastructure/TraceGenericLogger.cs delete mode 100644 SampleWebApp/Infrastructure/WebUiInitialise.cs create mode 100644 SampleWebApp/Program.cs delete mode 100644 SampleWebApp/Properties/AssemblyInfo.cs delete mode 100644 SampleWebApp/Properties/Settings.Designer.cs delete mode 100644 SampleWebApp/Properties/Settings.settings delete mode 100644 SampleWebApp/Views/Shared/EditorTemplates/DropDownListType.cshtml delete mode 100644 SampleWebApp/Views/Shared/EditorTemplates/MultiSelectListType.cshtml delete mode 100644 SampleWebApp/Views/Web.config create mode 100644 SampleWebApp/Views/_ViewImports.cshtml delete mode 100644 SampleWebApp/Web.Debug.config delete mode 100644 SampleWebApp/Web.Release.config delete mode 100644 SampleWebApp/Web.config create mode 100644 SampleWebApp/appsettings.Development.json create mode 100644 SampleWebApp/appsettings.json delete mode 100644 SampleWebApp/packages.config create mode 100644 SampleWebApp/wwwroot/css/site.css delete mode 100644 ServiceLayer/App.config delete mode 100644 ServiceLayer/Properties/AssemblyInfo.cs delete mode 100644 ServiceLayer/Startup/ServiceLayerModule.cs create mode 100644 ServiceLayer/Startup/ServiceLayerServiceCollectionExtensions.cs delete mode 100644 ServiceLayer/packages.config delete mode 100644 Tests/App.config delete mode 100644 Tests/Properties/AssemblyInfo.cs delete mode 100644 Tests/packages.config diff --git a/BizLayer/App.config b/BizLayer/App.config deleted file mode 100644 index 13ecece..0000000 --- a/BizLayer/App.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - -
- - - - - - - - - - - - \ No newline at end of file diff --git a/BizLayer/BizLayer.csproj b/BizLayer/BizLayer.csproj index aa58ad0..1f0730c 100644 --- a/BizLayer/BizLayer.csproj +++ b/BizLayer/BizLayer.csproj @@ -1,87 +1,15 @@ - - - + + - Debug - AnyCPU - {19592A8F-7C58-4221-8124-D38ACD8F5E31} - Library - Properties + net8.0 BizLayer BizLayer - v4.5.1 - 512 + disable + disable - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Autofac.3.5.0\lib\net40\Autofac.dll - - - ..\packages\AutoMapper.3.2.1\lib\net40\AutoMapper.dll - - - ..\packages\AutoMapper.3.2.1\lib\net40\AutoMapper.Net4.dll - - - False - ..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.dll - - - ..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.SqlServer.dll - - - False - ..\packages\GenericServices.1.0.0-beta4-003\lib\GenericServices.dll - - - - - - - - - - - - - - - - - - - + - - {264e1878-12de-4099-b8d7-cc53a73fea49} - DataLayer - + - - - - - \ No newline at end of file + + diff --git a/BizLayer/Properties/AssemblyInfo.cs b/BizLayer/Properties/AssemblyInfo.cs deleted file mode 100644 index f93aad5..0000000 --- a/BizLayer/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,62 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: AssemblyInfo.cs -// Date Created: 2014/07/11 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("BizLayer")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("BizLayer")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("3ea2faa1-4bb3-4d8e-8d35-f8038d375643")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/BizLayer/Startup/BizLayerInitialise.cs b/BizLayer/Startup/BizLayerInitialise.cs index b86e38e..04d56cc 100644 --- a/BizLayer/Startup/BizLayerInitialise.cs +++ b/BizLayer/Startup/BizLayerInitialise.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: BizLayerInitialise.cs @@ -24,7 +24,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #endregion -using DataLayer.Startup; namespace BizLayer.Startup { @@ -33,14 +32,11 @@ namespace BizLayer.Startup /// public static class BizLayerInitialise { - /// /// This should be called at Startup /// public static void InitialiseThis() { - - } } } diff --git a/BizLayer/Startup/BizLayerModule.cs b/BizLayer/Startup/BizLayerModule.cs deleted file mode 100644 index e924a90..0000000 --- a/BizLayer/Startup/BizLayerModule.cs +++ /dev/null @@ -1,46 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: BizLayerModule.cs -// Date Created: 2014/07/11 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using Autofac; - -namespace BizLayer.Startup -{ - public class BizLayerModule : Module - { - - /// - /// This registers all items in service layer and below - /// - /// - protected override void Load(ContainerBuilder builder) - { - //--------------------------- - //Register service layer: autowire all - builder.RegisterAssemblyTypes(GetType().Assembly).AsImplementedInterfaces(); - } - - } -} diff --git a/BizLayer/packages.config b/BizLayer/packages.config deleted file mode 100644 index 4a74205..0000000 --- a/BizLayer/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/DataLayer/App.config b/DataLayer/App.config deleted file mode 100644 index 5b4002b..0000000 --- a/DataLayer/App.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - -
- - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/DataLayer/DataClasses/Concrete/Blog.cs b/DataLayer/DataClasses/Concrete/Blog.cs index 0bf2f9a..00b6f4a 100644 --- a/DataLayer/DataClasses/Concrete/Blog.cs +++ b/DataLayer/DataClasses/Concrete/Blog.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: Blog.cs @@ -32,6 +32,7 @@ namespace DataLayer.DataClasses.Concrete public class Blog { public int BlogId { get; set; } + [MinLength(2)] [MaxLength(64)] [Required] @@ -43,11 +44,5 @@ public class Blog public string EmailAddress { get; set; } public ICollection Posts { get; set; } - - public override string ToString() - { - return string.Format("BlogId: {0}, Name: {1}, EmailAddress: {2}, NumPosts: {3}", - BlogId, Name, EmailAddress, Posts == null ? "null" : Posts.Count.ToString()); - } - } + } } diff --git a/DataLayer/DataClasses/Concrete/Helpers/TrackUpdate.cs b/DataLayer/DataClasses/Concrete/Helpers/TrackUpdate.cs index 34564e6..5b86518 100644 --- a/DataLayer/DataClasses/Concrete/Helpers/TrackUpdate.cs +++ b/DataLayer/DataClasses/Concrete/Helpers/TrackUpdate.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: TrackUpdate.cs @@ -30,7 +30,7 @@ namespace DataLayer.DataClasses.Concrete.Helpers { public abstract class TrackUpdate { - public DateTime LastUpdated { get; protected set; } + public DateTime LastUpdated { get; set; } internal void UpdateTrackingInfo() { @@ -39,7 +39,7 @@ internal void UpdateTrackingInfo() protected TrackUpdate() { - UpdateTrackingInfo(); //on creation then set date + UpdateTrackingInfo(); } } } diff --git a/DataLayer/DataClasses/Concrete/Post.cs b/DataLayer/DataClasses/Concrete/Post.cs index d6db0d7..8c0a5dd 100644 --- a/DataLayer/DataClasses/Concrete/Post.cs +++ b/DataLayer/DataClasses/Concrete/Post.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: Post.cs @@ -24,14 +24,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #endregion +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using DataLayer.DataClasses.Concrete.Helpers; namespace DataLayer.DataClasses.Concrete { - public class Post : TrackUpdate, IValidatableObject { public int PostId { get; set; } @@ -44,37 +43,19 @@ public class Post : TrackUpdate, IValidatableObject public string Content { get; set; } public int BlogId { get; set; } - public virtual Blog Blogger { get; set; } + public Blog Blogger { get; set; } public ICollection Tags { get; set; } - public override string ToString() - { - return string.Format("PostId: {0}, Title: {1}, BlogId: {2}, Blogger: {3}, AllocatedTags: {4}", - PostId, Title, BlogId, Blogger == null ? "null" : Blogger.Name, Tags == null ? "null" : Tags.Count().ToString()); - } - + /// + /// This allows any validation to be placed in the data class. + /// public IEnumerable Validate(ValidationContext validationContext) { - //Note that Tags may be null if the read din't include them, in which case we can't check them - if (Tags != null && !Tags.Any()) - yield return new ValidationResult("The post must have at least one Tag.", new[] { "AllocatedTags" }); - - if (Title.Contains("!")) - yield return new ValidationResult( "Sorry, but you can't get too excited and include a ! in the title.", new [] { "Title"}); - if (Title.EndsWith("?")) - yield return new ValidationResult("Sorry, but you can't ask a question, i.e. the title can't end with '?'.", new[] { "Title" }); - - //These produce top-level errors, i.e. not assocuiated with a property (used to test non-property error reporting) - if (Content.Contains(" sheep.")) - yield return new ValidationResult("Sorry. Not allowed to end a sentance with 'sheep'."); - if (Content.Contains(" lamb.")) - yield return new ValidationResult("Sorry. Not allowed to end a sentance with 'lamb'."); - if (Content.Contains(" cow.")) - yield return new ValidationResult("Sorry. Not allowed to end a sentance with 'cow'."); - if (Content.Contains(" calf.")) - yield return new ValidationResult("Sorry. Not allowed to end a sentance with 'calf'."); - + //Put any validation rules in here + if (Title != null && Title.EndsWith("!")) + yield return new ValidationResult("Sorry, but you can't have a title that ends with '!'.", + new[] { "Title" }); } } } diff --git a/DataLayer/DataClasses/Concrete/Tag.cs b/DataLayer/DataClasses/Concrete/Tag.cs index 5e8ec9a..f469567 100644 --- a/DataLayer/DataClasses/Concrete/Tag.cs +++ b/DataLayer/DataClasses/Concrete/Tag.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: Tag.cs @@ -31,7 +31,6 @@ namespace DataLayer.DataClasses.Concrete { public class Tag { - [UIHint("HiddenInput")] public int TagId { get; set; } [MaxLength(64)] @@ -44,10 +43,5 @@ public class Tag public string Name { get; set; } public ICollection Posts { get; set; } - - public override string ToString() - { - return string.Format("TagId: {0}, Name: {1}, Slug: {2}", TagId, Name, Slug); - } } } diff --git a/DataLayer/DataClasses/EfConfiguration.cs b/DataLayer/DataClasses/EfConfiguration.cs deleted file mode 100644 index 8c3cfe7..0000000 --- a/DataLayer/DataClasses/EfConfiguration.cs +++ /dev/null @@ -1,48 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: EfConfiguration.cs -// Date Created: 2014/08/14 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion - -using System.Data.Entity; -using System.Data.Entity.SqlServer; - -namespace DataLayer.DataClasses -{ - public class EfConfiguration : DbConfiguration - { - /// - /// This flag should be set to true if we are working with an Azure database. - /// It should be set before EF uses the configuration, i.e. beofre the first access - /// - public static bool IsAzure { get; internal set; } - - public EfConfiguration() - { - if (IsAzure) - SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy()); - } - - } -} diff --git a/DataLayer/DataClasses/SampleWebAppDb.cs b/DataLayer/DataClasses/SampleWebAppDb.cs index 0a86cf3..05ace22 100644 --- a/DataLayer/DataClasses/SampleWebAppDb.cs +++ b/DataLayer/DataClasses/SampleWebAppDb.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: SampleWebAppDb.cs @@ -24,111 +24,112 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #endregion +using System; using System.Collections.Generic; -using System.Data.Entity; -using System.Data.Entity.Infrastructure; -using System.Data.Entity.Validation; +using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using DataLayer.DataClasses.Concrete; using DataLayer.DataClasses.Concrete.Helpers; -using GenericServices; - -[assembly: InternalsVisibleTo("Tests")] +using Microsoft.EntityFrameworkCore; namespace DataLayer.DataClasses { - - public class SampleWebAppDb : DbContext, IGenericServicesDbContext + public class SampleWebAppDb : DbContext { - internal const string NameOfConnectionString = "SampleWebAppDb"; - public DbSet Blogs { get; set; } public DbSet Posts { get; set; } public DbSet Tags { get; set; } - public SampleWebAppDb() : base("name=" + NameOfConnectionString) {} - - internal SampleWebAppDb(string connectionString) : base(connectionString) { } + public SampleWebAppDb(DbContextOptions options) : base(options) { } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // Configure the many-to-many relationship between Post and Tag + modelBuilder.Entity() + .HasMany(p => p.Tags) + .WithMany(t => t.Posts); + + modelBuilder.Entity() + .HasOne(p => p.Blogger) + .WithMany(b => b.Posts) + .HasForeignKey(p => p.BlogId); + + // Configure Tag slug uniqueness + modelBuilder.Entity() + .HasIndex(t => t.Slug) + .IsUnique(); + } - /// - /// This has been overridden to handle: - /// a) Updating of modified items (see p194 in DbContext book) - /// - /// public override int SaveChanges() { HandleChangeTracking(); + var validationErrors = GetValidationErrors(); + if (validationErrors.Any()) + { + throw new ValidationException(string.Join("; ", validationErrors)); + } return base.SaveChanges(); } - /// - /// Same for async - /// - /// - public override Task SaveChangesAsync() + public override Task SaveChangesAsync(CancellationToken cancellationToken = default) { HandleChangeTracking(); - return base.SaveChangesAsync(); + var validationErrors = GetValidationErrors(); + if (validationErrors.Any()) + { + throw new ValidationException(string.Join("; ", validationErrors)); + } + return base.SaveChangesAsync(cancellationToken); } - /// - /// This does validations that can only be done at the database level - /// - /// - /// - /// - protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, - IDictionary items) + private void HandleChangeTracking() { - - if (entityEntry.Entity is Tag && (entityEntry.State == EntityState.Added || entityEntry.State == EntityState.Modified)) + foreach (var entity in ChangeTracker.Entries() + .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified)) { - var tagToCheck = ((Tag)entityEntry.Entity); - - //check for uniqueness of Tag's Slug property (note: because we may alter a Tag we need to exclude check against itself) - if (Tags.Any(x => x.TagId != tagToCheck.TagId && x.Slug == tagToCheck.Slug)) - return new DbEntityValidationResult(entityEntry, - new List - { - new DbValidationError( "Slug", - string.Format( "The Slug on tag '{0}' must be unique and is already being used.", tagToCheck.Name)) - }); + var trackUpdateEntity = entity.Entity as TrackUpdate; + if (trackUpdateEntity != null) + trackUpdateEntity.UpdateTrackingInfo(); } - - return base.ValidateEntity(entityEntry, items); } - - //-------------------------------------------------- - //private helpers - - /// - /// This handles going through all the entities that have changed and seeing if they need any special handling. - /// - private void HandleChangeTracking() + private List GetValidationErrors() { - //Debug.WriteLine("----------------------------------------------"); - //foreach (var entity in ChangeTracker.Entries() - //.Where( - // e => - // e.State == EntityState.Added || e.State == EntityState.Modified)) - //{ - // Debug.WriteLine("Entry {0}, state {1}", entity.Entity, entity.State); - //} + var errors = new List(); - foreach (var entity in ChangeTracker.Entries() - .Where( - e => - e.State == EntityState.Added || e.State == EntityState.Modified)) + // Validate Tag slug uniqueness + foreach (var entry in ChangeTracker.Entries() + .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified)) { - var trackUpdateClass = entity.Entity as TrackUpdate; - if (trackUpdateClass == null) return; - trackUpdateClass.UpdateTrackingInfo(); + var tag = entry.Entity; + var existingTag = Tags.AsNoTracking() + .FirstOrDefault(t => t.TagId != tag.TagId && t.Slug == tag.Slug); + if (existingTag != null) + { + errors.Add(string.Format("The Slug on tag '{0}' must be unique and is already being used.", tag.Name)); + } + } + + // Run IValidatableObject validation + foreach (var entry in ChangeTracker.Entries() + .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified)) + { + if (entry.Entity is IValidatableObject validatable) + { + var context = new ValidationContext(entry.Entity); + var results = new List(); + if (!Validator.TryValidateObject(entry.Entity, context, results, true)) + { + errors.AddRange(results.Select(r => r.ErrorMessage)); + } + } } - } + return errors; + } } } diff --git a/DataLayer/DataLayer.csproj b/DataLayer/DataLayer.csproj index 01771cf..72c176a 100644 --- a/DataLayer/DataLayer.csproj +++ b/DataLayer/DataLayer.csproj @@ -1,135 +1,23 @@ - - - + + - Debug - AnyCPU - {264E1878-12DE-4099-B8D7-CC53A73FEA49} - Library - Properties + net8.0 DataLayer DataLayer - v4.5.1 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - bin\ReleaseAzure\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\AzureRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\WebWizRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset + disable + disable + - - False - ..\packages\Autofac.3.5.0\lib\net40\Autofac.dll - - - ..\packages\AutoMapper.4.2.1\lib\net45\AutoMapper.dll - True - - - ..\packages\DelegateDecompiler.0.18.0\lib\net40-Client\DelegateDecompiler.dll - True - - - ..\packages\DelegateDecompiler.EntityFramework.0.18.0\lib\net45\DelegateDecompiler.EntityFramework.dll - True - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - True - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - True - - - ..\packages\GenericLibsBase.1.0.1\lib\GenericLibsBase.dll - True - - - ..\packages\GenericServices.1.0.9\lib\GenericServices.dll - True - - - ..\packages\Mono.Reflection.1.0.0.0\lib\Mono.Reflection.dll - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - \ No newline at end of file + + diff --git a/DataLayer/Properties/AssemblyInfo.cs b/DataLayer/Properties/AssemblyInfo.cs deleted file mode 100644 index 5cbbe5f..0000000 --- a/DataLayer/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,62 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: AssemblyInfo.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("DataLayer")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("DataLayer")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f78cbd3c-264d-4fda-8d1a-3679faf55c2c")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DataLayer/Startup/DataLayerInitialise.cs b/DataLayer/Startup/DataLayerInitialise.cs index e7c2df5..485939a 100644 --- a/DataLayer/Startup/DataLayerInitialise.cs +++ b/DataLayer/Startup/DataLayerInitialise.cs @@ -1,8 +1,8 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: DataLayerInitialise.cs -// Date Created: 2014/05/20 +// Date Created: 2014/06/09 // // Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) // @@ -25,74 +25,66 @@ // SOFTWARE. #endregion using System; -using System.Collections.Generic; -using System.Data.Entity; using System.Linq; using DataLayer.DataClasses; using DataLayer.Startup.Internal; -using GenericLibsBase; -using GenericServices; +using Microsoft.Extensions.Logging; namespace DataLayer.Startup { - public enum TestDataSelection { Small = 0, Medium = 1} + public enum TestDataSelection + { + Small, + Medium + } public static class DataLayerInitialise { + private static ILogger _logger; - private static IGenericLogger _logger; - - private static readonly Dictionary XmlBlogsDataFileManifestPath = new Dictionary - { - {TestDataSelection.Small, "DataLayer.Startup.Internal.BlogsContentSimple.xml"}, - {TestDataSelection.Medium, "DataLayer.Startup.Internal.BlogsContextMedium.xml"} - }; + public static void InitialiseThis(ILoggerFactory loggerFactory = null) + { + if (loggerFactory != null) + _logger = loggerFactory.CreateLogger("DataLayerInitialise"); + } /// - /// This should be called at Startup + /// Seeds the database with initial data if the database is empty, + /// or resets the data if called explicitly. /// - /// true if running on azure (used for configuring retry policy and BuildSqlConnectionString UserId) - /// true if the database provider allows the app to drop/create a database - public static void InitialiseThis(bool isAzure, bool canCreateDatabase) + public static void SeedDatabase(SampleWebAppDb context, TestDataSelection selection = TestDataSelection.Small) { - EfConfiguration.IsAzure = isAzure; - _logger = GenericLibsBaseConfig.GetLogger("DataLayerInitialise"); - - //Initialiser for the database. Only used when first access is made - if (canCreateDatabase) - Database.SetInitializer(new CreateDatabaseIfNotExists()); - else - //This initializer will not try to change the database - Database.SetInitializer(new NullDatabaseInitializer()); + if (context.Blogs.Any()) return; + ResetBlogs(context, selection); } - public static void ResetBlogs(SampleWebAppDb context, TestDataSelection selection) + /// + /// This resets the blogs content by deleting all existing data and reloading from the XML file. + /// + public static void ResetBlogs(SampleWebAppDb context, TestDataSelection selection = TestDataSelection.Small) { try { - context.Posts.ToList().ForEach(x => context.Posts.Remove(x)); - context.Tags.ToList().ForEach(x => context.Tags.Remove(x)); - context.Blogs.ToList().ForEach(x => context.Blogs.Remove(x)); + context.Posts.RemoveRange(context.Posts); + context.Tags.RemoveRange(context.Tags); + context.Blogs.RemoveRange(context.Blogs); context.SaveChanges(); + + var filepath = selection == TestDataSelection.Small + ? "DataLayer.Startup.Internal.BlogsContentSimple.xml" + : "DataLayer.Startup.Internal.BlogsContextMedium.xml"; + + var blogs = LoadDbDataFromXml.FormBlogsWithPosts(filepath); + context.Blogs.AddRange(blogs); + context.SaveChanges(); + + _logger?.LogInformation("Successfully reset blogs data with {Selection} dataset.", selection); } catch (Exception ex) { - _logger.Critical("Exception when resetting the blogs", ex); + _logger?.LogError(ex, "Error resetting blogs data."); throw; } - - var bloggers = LoadDbDataFromXml.FormBlogsWithPosts(XmlBlogsDataFileManifestPath[selection]); - - context.Blogs.AddRange(bloggers); - var status = context.SaveChangesWithChecking(); - if (!status.IsValid) - { - _logger.CriticalFormat("Error when resetting courses data. Error:\n{0}", - string.Join(",", status.Errors)); - throw new FormatException("problem writing to database. See log."); - } } - } - } diff --git a/DataLayer/Startup/DataLayerModule.cs b/DataLayer/Startup/DataLayerModule.cs deleted file mode 100644 index b2a6ecf..0000000 --- a/DataLayer/Startup/DataLayerModule.cs +++ /dev/null @@ -1,50 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: DataLayerModule.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.Runtime.CompilerServices; -using Autofac; -using DataLayer.DataClasses; -using GenericServices; - -[assembly: InternalsVisibleTo("Tests")] - -namespace DataLayer.Startup -{ - public class DataLayerModule : Module - { - - protected override void Load(ContainerBuilder builder) - { - - //Autowire the classes with interfaces - builder.RegisterAssemblyTypes(GetType().Assembly).AsImplementedInterfaces(); - - //set Entity Framework context to instance per lifetime scope. - //This is important as we get one context per lifetime, so all db classes are tracked together. - builder.RegisterType().As().As().InstancePerLifetimeScope(); - } - } -} diff --git a/DataLayer/Startup/DataLayerServiceCollectionExtensions.cs b/DataLayer/Startup/DataLayerServiceCollectionExtensions.cs new file mode 100644 index 0000000..5aa36f6 --- /dev/null +++ b/DataLayer/Startup/DataLayerServiceCollectionExtensions.cs @@ -0,0 +1,17 @@ +using DataLayer.DataClasses; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace DataLayer.Startup +{ + public static class DataLayerServiceCollectionExtensions + { + public static IServiceCollection AddDataLayer(this IServiceCollection services, string connectionString) + { + services.AddDbContext(options => + options.UseSqlite(connectionString)); + + return services; + } + } +} diff --git a/DataLayer/Startup/Internal/LoadDbDataFromXml.cs b/DataLayer/Startup/Internal/LoadDbDataFromXml.cs index 0dd6299..b7706d8 100644 --- a/DataLayer/Startup/Internal/LoadDbDataFromXml.cs +++ b/DataLayer/Startup/Internal/LoadDbDataFromXml.cs @@ -1,8 +1,8 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: LoadDbDataFromXml.cs -// Date Created: 2014/05/22 +// Date Created: 2014/06/09 // // Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) // @@ -26,26 +26,15 @@ #endregion using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; using System.Xml.Linq; using DataLayer.DataClasses.Concrete; -[assembly: InternalsVisibleTo("Tests")] - namespace DataLayer.Startup.Internal { - internal static class LoadDbDataFromXml { - - /// - /// Loads the Blogs, Posts and Tags from Xml file - /// - /// - /// public static IEnumerable FormBlogsWithPosts(string filepathWithinAssembly) { var assemblyHoldingFile = Assembly.GetAssembly(typeof(LoadDbDataFromXml)); @@ -62,49 +51,43 @@ public static IEnumerable FormBlogsWithPosts(string filepathWithinAssembly } } - private static IEnumerable DecodeBlogs(XElement element, Dictionary tagsDict) + //--------------------------------------------------- + //private helpers + + private static Dictionary DecodeTags(XElement element) { - var result = new Collection(); - foreach (var blogXml in element.Elements("Blog")) - { - var newBlogger = new Blog() + return element.Elements("Tag").ToDictionary( + el => el.Attribute("Slug").Value, + el => new Tag { - Name = blogXml.Element("Name").Value, - EmailAddress = blogXml.Element("Email").Value, - Posts = new Collection() - }; + Slug = el.Attribute("Slug").Value, + Name = el.Attribute("Name").Value + }); + } - foreach (var postXml in blogXml.Element("Posts").Elements("Post")) + private static IEnumerable DecodeBlogs(XElement element, Dictionary tagsDict) + { + return element.Elements("Blog").Select( + blogXml => new Blog { - var content = postXml.Element("Content").Value; - var trimmedContent = string.Join("\n", content.Split('\n').Select(x => x.Trim())); - var newPost = new Post() - { - Blogger = newBlogger, - Title = postXml.Element("Title").Value, - Content = trimmedContent, - Tags = postXml.Element("TagSlugs").Value.Split(',').Select(x => tagsDict[x.Trim()]).ToList() - }; - newBlogger.Posts.Add(newPost ); - } - result.Add( newBlogger); - } - return result; - + Name = blogXml.Attribute("Name").Value, + EmailAddress = blogXml.Attribute("Email").Value, + Posts = DecodePosts(blogXml.Element("Posts"), tagsDict).ToList() + }); } - private static Dictionary DecodeTags(XElement element) + private static IEnumerable DecodePosts(XElement element, Dictionary tagsDict) { - var result = new Dictionary(); - foreach (var newTag in element.Elements("Tag").Select(tagXml => new Tag() - { - Name = tagXml.Element("Name").Value, - Slug = tagXml.Element("Slug").Value - })) - { - result[newTag.Slug] = newTag; - } - return result; + return element.Elements("Post").Select( + postXml => new Post + { + Title = postXml.Attribute("Title").Value, + Content = postXml.Value, + Tags = postXml.Attribute("TagSlugs").Value.Split(',') + .Select(x => x.Trim()) + .Where(x => !string.IsNullOrEmpty(x)) + .Select(x => tagsDict[x]).ToList() + }); } } } diff --git a/DataLayer/packages.config b/DataLayer/packages.config deleted file mode 100644 index 79bc6a6..0000000 --- a/DataLayer/packages.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/SampleWebApp.sln b/SampleWebApp.sln index b474189..a076d80 100644 --- a/SampleWebApp.sln +++ b/SampleWebApp.sln @@ -1,7 +1,7 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApp", "SampleWebApp\SampleWebApp.csproj", "{CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}" EndProject @@ -9,7 +9,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataLayer", "DataLayer\Data EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceLayer", "ServiceLayer\ServiceLayer.csproj", "{D2813927-0F38-43C3-B47C-AE8F00D50CAE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{6D9E7904-B2AC-49E3-83A7-6B48876F46B9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizLayer", "BizLayer\BizLayer.csproj", "{19592A8F-7C58-4221-8124-D38ACD8F5E31}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AF8764F7-FBEE-48AD-AF62-23010DA35D70}" ProjectSection(SolutionItems) = preProject @@ -19,43 +19,26 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - AzureRelease|Any CPU = AzureRelease|Any CPU Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU - WebWizRelease|Any CPU = WebWizRelease|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.AzureRelease|Any CPU.ActiveCfg = AzureRelease|Any CPU - {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.AzureRelease|Any CPU.Build.0 = AzureRelease|Any CPU {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.Release|Any CPU.Build.0 = Release|Any CPU - {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.WebWizRelease|Any CPU.ActiveCfg = WebWizRelease|Any CPU - {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5}.WebWizRelease|Any CPU.Build.0 = WebWizRelease|Any CPU - {264E1878-12DE-4099-B8D7-CC53A73FEA49}.AzureRelease|Any CPU.ActiveCfg = AzureRelease|Any CPU - {264E1878-12DE-4099-B8D7-CC53A73FEA49}.AzureRelease|Any CPU.Build.0 = AzureRelease|Any CPU {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Debug|Any CPU.Build.0 = Debug|Any CPU {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Release|Any CPU.ActiveCfg = Release|Any CPU {264E1878-12DE-4099-B8D7-CC53A73FEA49}.Release|Any CPU.Build.0 = Release|Any CPU - {264E1878-12DE-4099-B8D7-CC53A73FEA49}.WebWizRelease|Any CPU.ActiveCfg = WebWizRelease|Any CPU - {264E1878-12DE-4099-B8D7-CC53A73FEA49}.WebWizRelease|Any CPU.Build.0 = WebWizRelease|Any CPU - {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.AzureRelease|Any CPU.ActiveCfg = AzureRelease|Any CPU - {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.AzureRelease|Any CPU.Build.0 = AzureRelease|Any CPU {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.Release|Any CPU.Build.0 = Release|Any CPU - {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.WebWizRelease|Any CPU.ActiveCfg = WebWizRelease|Any CPU - {D2813927-0F38-43C3-B47C-AE8F00D50CAE}.WebWizRelease|Any CPU.Build.0 = WebWizRelease|Any CPU - {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.AzureRelease|Any CPU.ActiveCfg = AzureRelease|Any CPU - {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.AzureRelease|Any CPU.Build.0 = AzureRelease|Any CPU - {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.Release|Any CPU.Build.0 = Release|Any CPU - {6D9E7904-B2AC-49E3-83A7-6B48876F46B9}.WebWizRelease|Any CPU.ActiveCfg = WebWizRelease|Any CPU + {19592A8F-7C58-4221-8124-D38ACD8F5E31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19592A8F-7C58-4221-8124-D38ACD8F5E31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19592A8F-7C58-4221-8124-D38ACD8F5E31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19592A8F-7C58-4221-8124-D38ACD8F5E31}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SampleWebApp/App_Start/BundleConfig.cs b/SampleWebApp/App_Start/BundleConfig.cs deleted file mode 100644 index ab2ec9f..0000000 --- a/SampleWebApp/App_Start/BundleConfig.cs +++ /dev/null @@ -1,97 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: BundleConfig.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion - -using System.Web.Optimization; - -namespace SampleWebApp -{ - public class BundleConfig - { - // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862 - public static void RegisterBundles(BundleCollection bundles) - { - - bundles.Add(new ScriptBundle("~/bundles/javascript").Include( - "~/Scripts/jquery-{version}.js", - "~/Scripts/bootstrap.js", - "~/Scripts/respond.js")); - - bundles.Add(new StyleBundle("~/Content/css").Include( - "~/Content/bootstrap.css", - //"~/Content/notify.css", - not used any more - "~/Content/site.css")); - - bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( - "~/Scripts/jquery.validate*")); - - //------------------------------------------------------------------ - //the bundles below were once used but not any more. Left as I might need them for the code that has been moved out to another library - - //Combined with bootstrap.js into one javascript bundle - //bundles.Add(new ScriptBundle("~/bundles/jquery").Include( - // "~/Scripts/jquery-{version}.js")); - // - //bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( - // "~/Scripts/bootstrap.js", - // "~/Scripts/respond.js")); - - //Not used anymore - //bundles.Add(new ScriptBundle("~/bundles/ActionRunner").Include( - // "~/Scripts/jquery-notify.js", - // "~/Scripts/jquery-ui-{version}.js", - // "~/Scripts/jquery.signalR-{version}.js", - // "~/Scripts/ActionRunnerComms.js", - // "~/Scripts/ActionRunnerUi.js")); - - //Not used - //// Use the development version of Modernizr to develop with and learn from. Then, when you're - //// ready for production, use the build tool at http://modernizr.com to pick only the tests you need. - //bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( - // "~/Scripts/modernizr-*")); - - //Not used any more - ////Commented out the parts that are not used from the jQuery UI - //bundles.Add(new StyleBundle("~/Content/themes/base/css").Include( - // "~/Content/themes/base/jquery.ui.core.css", - // "~/Content/themes/base/jquery.ui.resizable.css", - // "~/Content/themes/base/jquery.ui.selectable.css", - // //"~/Content/themes/base/jquery.ui.accordion.css", - // //"~/Content/themes/base/jquery.ui.autocomplete.css", - // //"~/Content/themes/base/jquery.ui.button.css", - // "~/Content/themes/base/jquery.ui.dialog.css", - // //"~/Content/themes/base/jquery.ui.slider.css", - // //"~/Content/themes/base/jquery.ui.tabs.css", - // //"~/Content/themes/base/jquery.ui.datepicker.css", - // "~/Content/themes/base/jquery.ui.progressbar.css", - // "~/Content/themes/base/jquery.ui.theme.css")); - - // Set EnableOptimizations to false for debugging. For more information, - // visit http://go.microsoft.com/fwlink/?LinkId=301862 - //BundleTable.EnableOptimizations = true; - } - } -} diff --git a/SampleWebApp/App_Start/FilterConfig.cs b/SampleWebApp/App_Start/FilterConfig.cs deleted file mode 100644 index 3325902..0000000 --- a/SampleWebApp/App_Start/FilterConfig.cs +++ /dev/null @@ -1,39 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: FilterConfig.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.Web; -using System.Web.Mvc; - -namespace SampleWebApp -{ - public class FilterConfig - { - public static void RegisterGlobalFilters(GlobalFilterCollection filters) - { - filters.Add(new HandleErrorAttribute()); - } - } -} diff --git a/SampleWebApp/App_Start/RouteConfig.cs b/SampleWebApp/App_Start/RouteConfig.cs deleted file mode 100644 index 5666db8..0000000 --- a/SampleWebApp/App_Start/RouteConfig.cs +++ /dev/null @@ -1,49 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: RouteConfig.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; - -namespace SampleWebApp -{ - public class RouteConfig - { - public static void RegisterRoutes(RouteCollection routes) - { - routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); - - routes.MapRoute( - name: "Default", - url: "{controller}/{action}/{id}", - defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } - ); - } - } -} diff --git a/SampleWebApp/Controllers/BlogsController.cs b/SampleWebApp/Controllers/BlogsController.cs index 0027062..82626d0 100644 --- a/SampleWebApp/Controllers/BlogsController.cs +++ b/SampleWebApp/Controllers/BlogsController.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: BlogsController.cs @@ -25,88 +25,90 @@ // SOFTWARE. #endregion using System.Linq; -using System.Web.Mvc; +using DataLayer.DataClasses; using DataLayer.DataClasses.Concrete; -using GenericServices; -using SampleWebApp.Infrastructure; +using Microsoft.AspNetCore.Mvc; using ServiceLayer.BlogServices; namespace SampleWebApp.Controllers { - /// - /// This is an example of a Controller using GenericServices database commands directly to the data class (other that List, which needs a DTO) - /// In this case we are using normal, non-async commands + /// This is an example of a Controller using EF Core database commands directly to the data class. + /// In this case we are using normal, non-async commands. /// public class BlogsController : Controller { - - public ActionResult Index(IListService service) + private readonly SampleWebAppDb _db; + + public BlogsController(SampleWebAppDb db) { - return View(service.GetAll().ToList()); + _db = db; } - public ActionResult Edit(int id, IUpdateSetupService service) + public IActionResult Index() { - return View(service.GetOriginal(id).Result); + var list = _db.Blogs.Select(b => new BlogListDto + { + BlogId = b.BlogId, + Name = b.Name, + EmailAddress = b.EmailAddress, + PostsCount = b.Posts.Count + }).ToList(); + return View(list); + } + + public IActionResult Edit(int id) + { + var blog = _db.Blogs.Find(id); + if (blog == null) return NotFound(); + return View(blog); } [HttpPost] [ValidateAntiForgeryToken] - public ActionResult Edit(Blog blog, IUpdateService service) + public IActionResult Edit(Blog blog) { if (!ModelState.IsValid) - //model errors so return immediately return View(blog); - var response = service.Update(blog); - if (response.IsValid) - { - TempData["message"] = response.SuccessMessage; - return RedirectToAction("Index"); - } + var existingBlog = _db.Blogs.Find(blog.BlogId); + if (existingBlog == null) return NotFound(); - //else errors, so copy the errors over to the ModelState and return to view - response.CopyErrorsToModelState(ModelState, blog); - return View(blog); + existingBlog.Name = blog.Name; + existingBlog.EmailAddress = blog.EmailAddress; + _db.SaveChanges(); + TempData["message"] = "Successfully updated Blog."; + return RedirectToAction("Index"); } - public ActionResult Create() + public IActionResult Create() { return View(new Blog()); } [HttpPost] [ValidateAntiForgeryToken] - public ActionResult Create(Blog blog, ICreateService service) + public IActionResult Create(Blog blog) { if (!ModelState.IsValid) - //model errors so return immediately return View(blog); - var response = service.Create(blog); - if (response.IsValid) - { - TempData["message"] = response.SuccessMessage; - return RedirectToAction("Index"); - } - - //else errors, so copy the errors over to the ModelState and return to view - response.CopyErrorsToModelState(ModelState, blog); - return View(blog); + _db.Blogs.Add(blog); + _db.SaveChanges(); + TempData["message"] = "Successfully created Blog."; + return RedirectToAction("Index"); } - public ActionResult Delete(int id, IDeleteService service) + public IActionResult Delete(int id) { - - var response = service.Delete(id); - if (response.IsValid) - TempData["message"] = response.SuccessMessage; - //else it throws a concurrecy error, which shows the default error page. - + var blog = _db.Blogs.Find(id); + if (blog != null) + { + _db.Blogs.Remove(blog); + _db.SaveChanges(); + TempData["message"] = "Successfully deleted Blog."; + } return RedirectToAction("Index"); } - - } -} \ No newline at end of file +} diff --git a/SampleWebApp/Controllers/HomeController.cs b/SampleWebApp/Controllers/HomeController.cs index bfef31c..fb2aba5 100644 --- a/SampleWebApp/Controllers/HomeController.cs +++ b/SampleWebApp/Controllers/HomeController.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: HomeController.cs @@ -24,38 +24,31 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #endregion -using System.Web.Mvc; -using SampleWebApp.Models; +using Microsoft.AspNetCore.Mvc; namespace SampleWebApp.Controllers { public class HomeController : Controller { - public ActionResult Index() + public IActionResult Index() { return View(); } - public ActionResult About() + public IActionResult About() { ViewBag.Message = "Your application description page."; - return View(); } - public ActionResult Contact() + public IActionResult Contact() { return View(); } - public ActionResult Internals() - { - return View(new InternalsInfo()); - } - - public ActionResult CodeView() + public IActionResult CodeView() { return View(); } } -} \ No newline at end of file +} diff --git a/SampleWebApp/Controllers/PostsAsyncController.cs b/SampleWebApp/Controllers/PostsAsyncController.cs index 22e914f..46afe54 100644 --- a/SampleWebApp/Controllers/PostsAsyncController.cs +++ b/SampleWebApp/Controllers/PostsAsyncController.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: PostsAsyncController.cs @@ -25,132 +25,212 @@ // SOFTWARE. #endregion using System.Collections.Generic; -using System.Data.Entity; using System.Linq; using System.Threading.Tasks; -using System.Web.Mvc; using DataLayer.DataClasses; using DataLayer.DataClasses.Concrete; using DataLayer.Startup; -using GenericServices; -using SampleWebApp.Infrastructure; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using ServiceLayer.PostServices; +using ServiceLayer.UiClasses; namespace SampleWebApp.Controllers { + /// + /// This is an example of a Controller using EF Core database commands with a DTO. + /// In this case we are using async commands. + /// public class PostsAsyncController : Controller { - /// - /// This is an example of a Controller using GenericServices database commands with a DTO. - /// In this case we are using async commands - /// - public async Task Index(IListService service) + private readonly SampleWebAppDb _db; + + public PostsAsyncController(SampleWebAppDb db) { - return View(await service.GetAll().ToListAsync()); + _db = db; } - public async Task Details(int id, IDetailServiceAsync service) + public async Task Index() { - return View((await service.GetDetailAsync(id)).Result); + var list = await _db.Posts + .Include(p => p.Blogger) + .Include(p => p.Tags) + .Select(p => new SimplePostDtoAsync + { + PostId = p.PostId, + BloggerName = p.Blogger.Name, + Title = p.Title, + LastUpdated = p.LastUpdated, + TagNamesList = p.Tags.Select(t => t.Name).ToList() + }).ToListAsync(); + + return View(list); } + public async Task Details(int id) + { + var post = await _db.Posts + .Include(p => p.Blogger) + .Include(p => p.Tags) + .FirstOrDefaultAsync(p => p.PostId == id); + if (post == null) return NotFound(); + + var dto = MapToDetailDto(post); + return View(dto); + } - public async Task Edit(int id, IUpdateSetupServiceAsync service) + public async Task Edit(int id) { - return View((await service.GetOriginalAsync(id)).Result); + var post = await _db.Posts + .Include(p => p.Blogger) + .Include(p => p.Tags) + .FirstOrDefaultAsync(p => p.PostId == id); + if (post == null) return NotFound(); + + var dto = MapToDetailDto(post); + await SetupDropDownsAndMultiSelectAsync(dto); + return View(dto); } [HttpPost] [ValidateAntiForgeryToken] - public async Task Edit(DetailPostDtoAsync dto, IUpdateServiceAsync service) + public async Task Edit(DetailPostDtoAsync dto) { if (!ModelState.IsValid) - //model errors so return immediately - return View(await service.ResetDtoAsync(dto)); - - var response = await service.UpdateAsync(dto); - if (response.IsValid) { - TempData["message"] = response.SuccessMessage; - return RedirectToAction("Index"); + await SetupDropDownsAndMultiSelectAsync(dto); + return View(dto); } - //else errors, so copy the errors over to the ModelState and return to view - response.CopyErrorsToModelState(ModelState, dto); - return View(dto); + var post = await _db.Posts + .Include(p => p.Tags) + .FirstOrDefaultAsync(p => p.PostId == dto.PostId); + if (post == null) return NotFound(); + + post.Title = dto.Title; + post.Content = dto.Content; + post.BlogId = GetBlogIdFromDropDown(dto.Bloggers); + + var selectedTagIds = dto.UserChosenTags.GetFinalSelectionAsInts(); + post.Tags = await _db.Tags.Where(t => selectedTagIds.Contains(t.TagId)).ToListAsync(); + + await _db.SaveChangesAsync(); + TempData["message"] = "Successfully updated Post."; + return RedirectToAction("Index"); } - public async Task Create(ICreateSetupServiceAsync setupService) + public async Task Create() { - var dto = await setupService.GetDtoAsync(); + var dto = new DetailPostDtoAsync(); + await SetupDropDownsAndMultiSelectAsync(dto); return View(dto); } [HttpPost] [ValidateAntiForgeryToken] - public async Task Create(DetailPostDtoAsync dto, ICreateServiceAsync service) + public async Task Create(DetailPostDtoAsync dto) { if (!ModelState.IsValid) - //model errors so return immediately - return View(await service.ResetDtoAsync(dto)); - - var response = await service.CreateAsync(dto); - if (response.IsValid) { - TempData["message"] = response.SuccessMessage; - return RedirectToAction("Index"); + await SetupDropDownsAndMultiSelectAsync(dto); + return View(dto); } - //else errors, so copy the errors over to the ModelState and return to view - response.CopyErrorsToModelState(ModelState, dto); - return View(dto); - } + var post = new Post + { + Title = dto.Title, + Content = dto.Content, + BlogId = GetBlogIdFromDropDown(dto.Bloggers) + }; - public async Task Delete(int id, IDeleteServiceAsync service) - { + var selectedTagIds = dto.UserChosenTags.GetFinalSelectionAsInts(); + post.Tags = await _db.Tags.Where(t => selectedTagIds.Contains(t.TagId)).ToListAsync(); - var response = await service.DeleteAsync(id); - if (response.IsValid) - TempData["message"] = response.SuccessMessage; - else - //else errors, so send back an error message - TempData["errorMessage"] = new MvcHtmlString(response.ErrorsAsHtml()); - + _db.Posts.Add(post); + await _db.SaveChangesAsync(); + TempData["message"] = "Successfully created Post."; return RedirectToAction("Index"); } - //----------------------------------------------------- - //Code used in https://www.simple-talk.com/dotnet/.net-framework/the-.net-4.5-asyncawait-commands-in-promise-and-practice/ - - public async Task NumPosts(SampleWebAppDb db) + public async Task Delete(int id) { - return View((object)await GetNumPostsAsync(db)); + var post = await _db.Posts.FindAsync(id); + if (post != null) + { + _db.Posts.Remove(post); + await _db.SaveChangesAsync(); + TempData["message"] = "Successfully deleted Post."; + } + else + { + TempData["errorMessage"] = "Could not find the Post to delete."; + } + return RedirectToAction("Index"); } - private static async Task GetNumPostsAsync(SampleWebAppDb db) + public async Task NumPosts() { - var numPosts = await db.Posts.CountAsync(); - return string.Format("The total number of Posts is {0}", numPosts); + var numPosts = await _db.Posts.CountAsync(); + return View((object)string.Format("The total number of Posts is {0}", numPosts)); } - //-------------------------------------------- - - public ActionResult CodeView() + public IActionResult CodeView() { return View(); } - public async Task Delay() + public async Task Delay() { await Task.Delay(500); return View(500); } - public ActionResult Reset(SampleWebAppDb db) + public IActionResult Reset() { - DataLayerInitialise.ResetBlogs(db, TestDataSelection.Medium); + DataLayerInitialise.ResetBlogs(_db, TestDataSelection.Medium); TempData["message"] = "Successfully reset the blogs data"; return RedirectToAction("Index"); } + + //--------------------------------------------------- + //private helpers + + private DetailPostDtoAsync MapToDetailDto(Post post) + { + return new DetailPostDtoAsync + { + PostId = post.PostId, + Title = post.Title, + Content = post.Content, + BloggerName = post.Blogger?.Name, + BlogId = post.BlogId, + LastUpdated = post.LastUpdated, + Tags = post.Tags?.ToList() ?? new List() + }; + } + + private async Task SetupDropDownsAndMultiSelectAsync(DetailPostDtoAsync dto) + { + var bloggers = await _db.Blogs.ToListAsync(); + dto.Bloggers.SetupDropDownListContent( + bloggers.Select(x => new KeyValuePair(x.Name, x.BlogId.ToString("D"))), + "--- choose blogger ---"); + if (dto.PostId != 0) + dto.Bloggers.SetSelectedValue(dto.BlogId.ToString("D")); + + var preselectedTags = dto.Tags != null && dto.Tags.Any() + ? dto.Tags.Select(x => new KeyValuePair(x.Name, x.TagId)).ToList() + : new List>(); + var allTags = await _db.Tags.ToListAsync(); + dto.UserChosenTags.SetupMultiSelectList( + allTags.Select(x => new KeyValuePair(x.Name, x.TagId)), + preselectedTags); + } + + private int GetBlogIdFromDropDown(DropDownListType bloggers) + { + var blogId = bloggers.SelectedValueAsInt; + return blogId ?? 0; + } } -} \ No newline at end of file +} diff --git a/SampleWebApp/Controllers/PostsController.cs b/SampleWebApp/Controllers/PostsController.cs index d5cd076..cb00fdc 100644 --- a/SampleWebApp/Controllers/PostsController.cs +++ b/SampleWebApp/Controllers/PostsController.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: PostsController.cs @@ -24,143 +24,221 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #endregion +using System.Collections.Generic; using System.Linq; using System.Threading; -using System.Web.Mvc; using DataLayer.DataClasses; using DataLayer.DataClasses.Concrete; using DataLayer.Startup; -using GenericServices; -using SampleWebApp.Infrastructure; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using ServiceLayer.PostServices; - +using ServiceLayer.UiClasses; namespace SampleWebApp.Controllers { /// - /// This is an example of a Controller using GenericServices database commands with a DTO. - /// In this case we are using normal, non-async commands + /// This is an example of a Controller using EF Core database commands with a DTO. + /// In this case we are using normal, non-async commands. /// public class PostsController : Controller { - /// - /// Note that is Index is different in that it has an optional id to filter the list on. - /// - /// - /// - /// - public ActionResult Index(int? id, IListService service) + private readonly SampleWebAppDb _db; + + public PostsController(SampleWebAppDb db) + { + _db = db; + } + + public IActionResult Index(int? id) { - var filtered = id != null && id != 0; - var query = filtered ? service.GetAll().Where(x => x.BlogId == id) : service.GetAll(); - if (filtered) + var query = _db.Posts + .Include(p => p.Blogger) + .Include(p => p.Tags) + .AsQueryable(); + + if (id != null && id != 0) + { + query = query.Where(x => x.BlogId == id); TempData["message"] = "Filtered list"; + } - return View(query.ToList()); + var list = query.Select(p => new SimplePostDto + { + PostId = p.PostId, + BlogId = p.BlogId, + BloggerName = p.Blogger.Name, + Title = p.Title, + LastUpdated = p.LastUpdated, + TagNamesList = p.Tags.Select(t => t.Name).ToList() + }).ToList(); + + return View(list); } - public ActionResult Details(int id, IDetailService service) + public IActionResult Details(int id) { - return View(service.GetDetail(id).Result); + var post = _db.Posts + .Include(p => p.Blogger) + .Include(p => p.Tags) + .FirstOrDefault(p => p.PostId == id); + if (post == null) return NotFound(); + + var dto = MapToDetailDto(post); + return View(dto); } - public ActionResult Edit(int id, IUpdateSetupService service) + public IActionResult Edit(int id) { - return View(service.GetOriginal(id).Result); + var post = _db.Posts + .Include(p => p.Blogger) + .Include(p => p.Tags) + .FirstOrDefault(p => p.PostId == id); + if (post == null) return NotFound(); + + var dto = MapToDetailDto(post); + SetupDropDownsAndMultiSelect(dto); + return View(dto); } [HttpPost] [ValidateAntiForgeryToken] - public ActionResult Edit(DetailPostDto dto, IUpdateService service) + public IActionResult Edit(DetailPostDto dto) { if (!ModelState.IsValid) - //model errors so return immediately - return View(service.ResetDto(dto)); - - var response = service.Update(dto); - if (response.IsValid) { - TempData["message"] = response.SuccessMessage; - return RedirectToAction("Index"); + SetupDropDownsAndMultiSelect(dto); + return View(dto); } - //else errors, so copy the errors over to the ModelState and return to view - response.CopyErrorsToModelState(ModelState, dto); - return View(dto); + var post = _db.Posts + .Include(p => p.Tags) + .FirstOrDefault(p => p.PostId == dto.PostId); + if (post == null) return NotFound(); + + post.Title = dto.Title; + post.Content = dto.Content; + post.BlogId = GetBlogIdFromDropDown(dto.Bloggers); + + // Update tags + var selectedTagIds = dto.UserChosenTags.GetFinalSelectionAsInts(); + post.Tags = _db.Tags.Where(t => selectedTagIds.Contains(t.TagId)).ToList(); + + _db.SaveChanges(); + TempData["message"] = "Successfully updated Post."; + return RedirectToAction("Index"); } - public ActionResult Create(ICreateSetupService setupService) + public IActionResult Create() { - var dto = setupService.GetDto(); + var dto = new DetailPostDto(); + SetupDropDownsAndMultiSelect(dto); return View(dto); } [HttpPost] [ValidateAntiForgeryToken] - public ActionResult Create(DetailPostDto dto, ICreateService service) + public IActionResult Create(DetailPostDto dto) { if (!ModelState.IsValid) - //model errors so return immediately - return View(service.ResetDto(dto)); - - var response = service.Create(dto); - if (response.IsValid) { - TempData["message"] = response.SuccessMessage; - return RedirectToAction("Index"); + SetupDropDownsAndMultiSelect(dto); + return View(dto); } - //else errors, so copy the errors over to the ModelState and return to view - response.CopyErrorsToModelState(ModelState, dto); - return View(dto); - } + var post = new Post + { + Title = dto.Title, + Content = dto.Content, + BlogId = GetBlogIdFromDropDown(dto.Bloggers) + }; - public ActionResult Delete(int id, IDeleteService service) - { + var selectedTagIds = dto.UserChosenTags.GetFinalSelectionAsInts(); + post.Tags = _db.Tags.Where(t => selectedTagIds.Contains(t.TagId)).ToList(); - var response = service.Delete(id); - if (response.IsValid) - TempData["message"] = response.SuccessMessage; - else - //else errors, so send back an error message - TempData["errorMessage"] = new MvcHtmlString(response.ErrorsAsHtml()); - + _db.Posts.Add(post); + _db.SaveChanges(); + TempData["message"] = "Successfully created Post."; return RedirectToAction("Index"); } - //----------------------------------------------------- - //Code used in https://www.simple-talk.com/dotnet/.net-framework/the-.net-4.5-asyncawait-commands-in-promise-and-practice/ - - public ActionResult NumPosts(SampleWebAppDb db) + public IActionResult Delete(int id) { - //The cast to object is to stop the View using the string as a view name - return View((object)GetNumPosts(db)); + var post = _db.Posts.Find(id); + if (post != null) + { + _db.Posts.Remove(post); + _db.SaveChanges(); + TempData["message"] = "Successfully deleted Post."; + } + else + { + TempData["errorMessage"] = "Could not find the Post to delete."; + } + return RedirectToAction("Index"); } - public static string GetNumPosts(SampleWebAppDb db) + public IActionResult NumPosts() { - var numPosts = db.Posts.Count(); - return string.Format("The total number of Posts is {0}", numPosts); + var numPosts = _db.Posts.Count(); + return View((object)string.Format("The total number of Posts is {0}", numPosts)); } - //-------------------------------------------- - - public ActionResult CodeView() + public IActionResult CodeView() { return View(); } - public ActionResult Delay() + public IActionResult Delay() { Thread.Sleep(500); return View(500); } - public ActionResult Reset(SampleWebAppDb db) + public IActionResult Reset() { - DataLayerInitialise.ResetBlogs(db, TestDataSelection.Medium); + DataLayerInitialise.ResetBlogs(_db, TestDataSelection.Medium); TempData["message"] = "Successfully reset the blogs data"; return RedirectToAction("Index"); } + + //--------------------------------------------------- + //private helpers + + private DetailPostDto MapToDetailDto(Post post) + { + return new DetailPostDto + { + PostId = post.PostId, + Title = post.Title, + Content = post.Content, + BloggerName = post.Blogger?.Name, + BlogId = post.BlogId, + LastUpdated = post.LastUpdated, + Tags = post.Tags?.ToList() ?? new List() + }; + } + + private void SetupDropDownsAndMultiSelect(DetailPostDto dto) + { + dto.Bloggers.SetupDropDownListContent( + _db.Blogs.ToList().Select(x => new KeyValuePair(x.Name, x.BlogId.ToString("D"))), + "--- choose blogger ---"); + if (dto.PostId != 0) + dto.Bloggers.SetSelectedValue(dto.BlogId.ToString("D")); + + var preselectedTags = dto.Tags != null && dto.Tags.Any() + ? dto.Tags.Select(x => new KeyValuePair(x.Name, x.TagId)).ToList() + : new List>(); + dto.UserChosenTags.SetupMultiSelectList( + _db.Tags.ToList().Select(x => new KeyValuePair(x.Name, x.TagId)), + preselectedTags); + } + + private int GetBlogIdFromDropDown(DropDownListType bloggers) + { + var blogId = bloggers.SelectedValueAsInt; + return blogId ?? 0; + } } -} \ No newline at end of file +} diff --git a/SampleWebApp/Controllers/TagsAsyncController.cs b/SampleWebApp/Controllers/TagsAsyncController.cs index 6d9e9b9..a78fabf 100644 --- a/SampleWebApp/Controllers/TagsAsyncController.cs +++ b/SampleWebApp/Controllers/TagsAsyncController.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: TagsAsyncController.cs @@ -24,103 +24,109 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #endregion -using System.Data.Entity; +using System.Linq; using System.Threading.Tasks; -using System.Web.Mvc; +using DataLayer.DataClasses; using DataLayer.DataClasses.Concrete; -using GenericServices; -using SampleWebApp.Infrastructure; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using ServiceLayer.TagServices; namespace SampleWebApp.Controllers { /// - /// This is an example of a Controller using GenericServices database commands directly to the data class (other that List, which needs a DTO) - /// In this case we are using async commands + /// This is an example of a Controller using EF Core database commands directly to the data class. + /// In this case we are using async commands. /// public class TagsAsyncController : Controller { - // GET: TagsAsync - public async Task Index(IListService service) + private readonly SampleWebAppDb _db; + + public TagsAsyncController(SampleWebAppDb db) { - return View(await service.GetAll().ToListAsync()); + _db = db; } - public async Task Details(int id, IDetailServiceAsync service) + public async Task Index() { - return View((await service.GetDetailAsync(id)).Result); + var list = await _db.Tags.Select(t => new TagListDto + { + TagId = t.TagId, + Name = t.Name, + Slug = t.Slug, + PostsCount = t.Posts.Count + }).ToListAsync(); + return View(list); } + public async Task Details(int id) + { + var tag = await _db.Tags.FindAsync(id); + if (tag == null) return NotFound(); + return View(tag); + } - public async Task Edit(int id, IUpdateSetupServiceAsync service) + public async Task Edit(int id) { - return View((await service.GetOriginalAsync(id)).Result); + var tag = await _db.Tags.FindAsync(id); + if (tag == null) return NotFound(); + return View(tag); } [HttpPost] [ValidateAntiForgeryToken] - public async Task Edit(Tag tag, IUpdateServiceAsync service) + public async Task Edit(Tag tag) { if (!ModelState.IsValid) - //model errors so return immediately return View(tag); - var response = await service.UpdateAsync(tag); - if (response.IsValid) - { - TempData["message"] = response.SuccessMessage; - return RedirectToAction("Index"); - } + var existingTag = await _db.Tags.FindAsync(tag.TagId); + if (existingTag == null) return NotFound(); - //else errors, so copy the errors over to the ModelState and return to view - response.CopyErrorsToModelState(ModelState, tag); - return View(tag); + existingTag.Name = tag.Name; + existingTag.Slug = tag.Slug; + await _db.SaveChangesAsync(); + TempData["message"] = "Successfully updated Tag."; + return RedirectToAction("Index"); } - public ActionResult Create() + public IActionResult Create() { return View(new Tag()); } [HttpPost] [ValidateAntiForgeryToken] - public async Task Create(Tag tag, ICreateServiceAsync service) + public async Task Create(Tag tag) { if (!ModelState.IsValid) - //model errors so return immediately return View(tag); - var response = await service.CreateAsync(tag); - if (response.IsValid) - { - TempData["message"] = response.SuccessMessage; - return RedirectToAction("Index"); - } - - //else errors, so copy the errors over to the ModelState and return to view - response.CopyErrorsToModelState(ModelState, tag); - return View(tag); + _db.Tags.Add(tag); + await _db.SaveChangesAsync(); + TempData["message"] = "Successfully created Tag."; + return RedirectToAction("Index"); } - public async Task Delete(int id, IDeleteServiceAsync service) + public async Task Delete(int id) { - - var response = await service.DeleteAsync(id); - if (response.IsValid) - TempData["message"] = response.SuccessMessage; + var tag = await _db.Tags.FindAsync(id); + if (tag != null) + { + _db.Tags.Remove(tag); + await _db.SaveChangesAsync(); + TempData["message"] = "Successfully deleted Tag."; + } else - //else errors, so send back an error message - TempData["errorMessage"] = new MvcHtmlString(response.ErrorsAsHtml()); - + { + TempData["errorMessage"] = "Could not find the Tag to delete."; + } return RedirectToAction("Index"); } - //-------------------------------------------- - - public ActionResult CodeView() + public IActionResult CodeView() { return View(); } - } -} \ No newline at end of file +} diff --git a/SampleWebApp/Controllers/TagsController.cs b/SampleWebApp/Controllers/TagsController.cs index a729d08..9879140 100644 --- a/SampleWebApp/Controllers/TagsController.cs +++ b/SampleWebApp/Controllers/TagsController.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: TagsController.cs @@ -25,100 +25,106 @@ // SOFTWARE. #endregion using System.Linq; -using System.Web.Mvc; +using DataLayer.DataClasses; using DataLayer.DataClasses.Concrete; -using GenericServices; -using SampleWebApp.Infrastructure; +using Microsoft.AspNetCore.Mvc; using ServiceLayer.TagServices; namespace SampleWebApp.Controllers { + /// + /// This is an example of a Controller using EF Core database commands directly to the data class. + /// In this case we are using normal, non-async commands. + /// public class TagsController : Controller { - /// - /// This is an example of a Controller using GenericServices database commands directly to the data class (other that List, which needs a DTO) - /// In this case we are using normal, non-async commands - /// - public ActionResult Index(IListService service) + private readonly SampleWebAppDb _db; + + public TagsController(SampleWebAppDb db) { - return View(service.GetAll().ToList()); + _db = db; } - public ActionResult Details(int id, IDetailService service) + public IActionResult Index() { - return View(service.GetDetail(id).Result); + var list = _db.Tags.Select(t => new TagListDto + { + TagId = t.TagId, + Name = t.Name, + Slug = t.Slug, + PostsCount = t.Posts.Count + }).ToList(); + return View(list); } + public IActionResult Details(int id) + { + var tag = _db.Tags.Find(id); + if (tag == null) return NotFound(); + return View(tag); + } - public ActionResult Edit(int id, IUpdateSetupService service) + public IActionResult Edit(int id) { - return View(service.GetOriginal(id).Result); + var tag = _db.Tags.Find(id); + if (tag == null) return NotFound(); + return View(tag); } [HttpPost] [ValidateAntiForgeryToken] - public ActionResult Edit(Tag tag, IUpdateService service) + public IActionResult Edit(Tag tag) { if (!ModelState.IsValid) - //model errors so return immediately return View(tag); - var response = service.Update(tag); - if (response.IsValid) - { - TempData["message"] = response.SuccessMessage; - return RedirectToAction("Index"); - } + var existingTag = _db.Tags.Find(tag.TagId); + if (existingTag == null) return NotFound(); - //else errors, so copy the errors over to the ModelState and return to view - response.CopyErrorsToModelState(ModelState, tag); - return View(tag); + existingTag.Name = tag.Name; + existingTag.Slug = tag.Slug; + _db.SaveChanges(); + TempData["message"] = "Successfully updated Tag."; + return RedirectToAction("Index"); } - public ActionResult Create() + public IActionResult Create() { return View(new Tag()); } [HttpPost] [ValidateAntiForgeryToken] - public ActionResult Create(Tag tag, ICreateService service) + public IActionResult Create(Tag tag) { if (!ModelState.IsValid) - //model errors so return immediately return View(tag); - var response = service.Create(tag); - if (response.IsValid) - { - TempData["message"] = response.SuccessMessage; - return RedirectToAction("Index"); - } - - //else errors, so copy the errors over to the ModelState and return to view - response.CopyErrorsToModelState(ModelState, tag); - return View(tag); + _db.Tags.Add(tag); + _db.SaveChanges(); + TempData["message"] = "Successfully created Tag."; + return RedirectToAction("Index"); } - public ActionResult Delete(int id, IDeleteService service) + public IActionResult Delete(int id) { - - var response = service.Delete(id); - if (response.IsValid) - TempData["message"] = response.SuccessMessage; + var tag = _db.Tags.Find(id); + if (tag != null) + { + _db.Tags.Remove(tag); + _db.SaveChanges(); + TempData["message"] = "Successfully deleted Tag."; + } else - //else errors, so send back an error message - TempData["errorMessage"] = new MvcHtmlString(response.ErrorsAsHtml()); - + { + TempData["errorMessage"] = "Could not find the Tag to delete."; + } return RedirectToAction("Index"); } - //-------------------------------------------- - - public ActionResult CodeView() + public IActionResult CodeView() { return View(); } - } -} \ No newline at end of file +} diff --git a/SampleWebApp/Global.asax b/SampleWebApp/Global.asax deleted file mode 100644 index c209429..0000000 --- a/SampleWebApp/Global.asax +++ /dev/null @@ -1 +0,0 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="SampleWebApp.MvcApplication" Language="C#" %> diff --git a/SampleWebApp/Global.asax.cs b/SampleWebApp/Global.asax.cs deleted file mode 100644 index 610d5ce..0000000 --- a/SampleWebApp/Global.asax.cs +++ /dev/null @@ -1,51 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: Global.asax.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.Web.Mvc; -using System.Web.Optimization; -using System.Web.Routing; -using SampleWebApp.Infrastructure; - -namespace SampleWebApp -{ - public class MvcApplication : System.Web.HttpApplication - { - protected void Application_Start() - { - AreaRegistration.RegisterAllAreas(); - FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); - RouteConfig.RegisterRoutes(RouteTable.Routes); - BundleConfig.RegisterBundles(BundleTable.Bundles); - - //This allows interfaces etc to be provided as parameters to action methods - ModelBinders.Binders.DefaultBinder = new DiModelBinder(); - - //Now call the method to initialise anything that is required before startup (which includes setting up DI) - WebUiInitialise.InitialiseThis(this); - - } - } -} diff --git a/SampleWebApp/Infrastructure/AutofacDi.cs b/SampleWebApp/Infrastructure/AutofacDi.cs deleted file mode 100644 index b2645e7..0000000 --- a/SampleWebApp/Infrastructure/AutofacDi.cs +++ /dev/null @@ -1,57 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: AutofacDi.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.Runtime.CompilerServices; -using Autofac; -using ServiceLayer.Startup; - -[assembly: InternalsVisibleTo("Tests")] - -namespace SampleWebApp.Infrastructure -{ - internal static class AutofacDi - { - - private static IContainer _container; - - internal static IContainer SetupDependency() - { - - var builder = new ContainerBuilder(); - Load(builder); - - _container = builder.Build(); - - return _container; - } - - private static void Load(ContainerBuilder builder) - { - //register the service layer, which then registers all other dependencies in the rest of the system - builder.RegisterModule(new ServiceLayerModule()); - } - } -} \ No newline at end of file diff --git a/SampleWebApp/Infrastructure/DiModelBinder.cs b/SampleWebApp/Infrastructure/DiModelBinder.cs deleted file mode 100644 index e8115ba..0000000 --- a/SampleWebApp/Infrastructure/DiModelBinder.cs +++ /dev/null @@ -1,41 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: DiModelBinder.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System; -using System.Web.Mvc; - -namespace SampleWebApp.Infrastructure -{ - public class DiModelBinder : DefaultModelBinder - { - protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) - { - return modelType.IsInterface - ? DependencyResolver.Current.GetService(modelType) - : base.CreateModel(controllerContext, bindingContext, modelType); - } - } -} \ No newline at end of file diff --git a/SampleWebApp/Infrastructure/JsonNetResult.cs b/SampleWebApp/Infrastructure/JsonNetResult.cs deleted file mode 100644 index f429bba..0000000 --- a/SampleWebApp/Infrastructure/JsonNetResult.cs +++ /dev/null @@ -1,75 +0,0 @@ -#region License -// Copyright (c) 2007 James Newton-King -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -#endregion - -using System; -using System.Text; -using System.Web; -using System.Web.Mvc; -using Newtonsoft.Json; - -//see blog http://james.newtonking.com/archive/2008/10/16/asp-net-mvc-and-json-net.aspx - -namespace SampleWebApp.Infrastructure -{ - public class JsonNetResult : ActionResult - { - public Encoding ContentEncoding { get; set; } - public string ContentType { get; set; } - public object Data { get; set; } - - public JsonSerializerSettings SerializerSettings { get; set; } - public Formatting Formatting { get; set; } - - public JsonNetResult() - { - SerializerSettings = new JsonSerializerSettings(); - } - - public override void ExecuteResult(ControllerContext context) - { - if (context == null) - throw new ArgumentNullException("context"); - - HttpResponseBase response = context.HttpContext.Response; - - response.ContentType = !string.IsNullOrEmpty(ContentType) - ? ContentType - : "application/json"; - - if (ContentEncoding != null) - response.ContentEncoding = ContentEncoding; - - if (Data != null) - { - JsonTextWriter writer = new JsonTextWriter(response.Output) { Formatting = Formatting }; - - JsonSerializer serializer = JsonSerializer.Create(SerializerSettings); - serializer.Serialize(writer, Data); - - writer.Flush(); - } - } - } -} \ No newline at end of file diff --git a/SampleWebApp/Infrastructure/Log4NetGenericLogger.cs b/SampleWebApp/Infrastructure/Log4NetGenericLogger.cs deleted file mode 100644 index 7b71457..0000000 --- a/SampleWebApp/Infrastructure/Log4NetGenericLogger.cs +++ /dev/null @@ -1,99 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: Log4NetGenericLogger.cs -// Date Created: 2014/07/03 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System; -using GenericLibsBase; - -namespace SampleWebApp.Infrastructure -{ - - public class Log4NetGenericLogger : IGenericLogger - { - - private readonly log4net.ILog _logger; - - public Log4NetGenericLogger(string name) - { - _logger = log4net.LogManager.GetLogger(name); - } - - public void Verbose(object message) - { - _logger.Debug(message); - } - - public void VerboseFormat(string format, params object[] args) - { - _logger.DebugFormat(format, args); - } - - public void Info(object message) - { - _logger.Info(message); - } - - public void InfoFormat(string format, params object[] args) - { - _logger.InfoFormat(format, args); - } - - public void Warn(object message) - { - _logger.Warn(message); - } - - public void WarnFormat(string format, params object[] args) - { - _logger.WarnFormat(format, args); - } - - public void Error(object message) - { - _logger.Error(message); - } - - public void ErrorFormat(string format, params object[] args) - { - _logger.ErrorFormat(format, args); - } - - public void Critical(object message) - { - _logger.Fatal(message); - } - - public void Critical(object message, Exception ex) - { - _logger.Fatal(message, ex); - } - - public void CriticalFormat(string format, params object[] args) - { - _logger.FatalFormat(format, args); - } - - } -} diff --git a/SampleWebApp/Infrastructure/TraceGenericLogger.cs b/SampleWebApp/Infrastructure/TraceGenericLogger.cs deleted file mode 100644 index 31584ad..0000000 --- a/SampleWebApp/Infrastructure/TraceGenericLogger.cs +++ /dev/null @@ -1,107 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: TraceGenericLogger.cs -// Date Created: 2014/07/07 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System; -using System.Diagnostics; -using GenericLibsBase; - -namespace SampleWebApp.Infrastructure -{ - - public class TraceGenericLogger : IGenericLogger - { - - private readonly string _loggerName; - - /// - /// Controls whether Verbose is written to Trace. - /// Defaults to excluding Verbose messages - /// - public bool IncludeVerbose { get; set; } - - public TraceGenericLogger(string name) - { - _loggerName = name; - } - - public void Verbose(object message) - { - if (IncludeVerbose) - Trace.TraceInformation("{0}: {1}", _loggerName, message); - } - - public void VerboseFormat(string format, params object[] args) - { - Verbose(string.Format(format, args)); - } - - public void Info(object message) - { - Trace.TraceInformation("{0}: {1}", _loggerName, message); - } - - public void InfoFormat(string format, params object[] args) - { - Info(string.Format(format, args)); - } - - public void Warn(object message) - { - Trace.TraceWarning("{0}: {1}", _loggerName, message); - } - - public void WarnFormat(string format, params object[] args) - { - Warn(string.Format(format, args)); - } - - public void Error(object message) - { - Trace.TraceError("{0}: {1}", _loggerName, message); - } - - public void ErrorFormat(string format, params object[] args) - { - Error(string.Format(format, args)); - } - - public void Critical(object message) - { - Trace.TraceError("{0}: {1}", _loggerName, message); - } - - public void Critical(object message, Exception ex) - { - Trace.TraceError("{0}, expection {1}: {2}\n{3}", _loggerName, ex.GetType().Name, message, ex.StackTrace); - } - - public void CriticalFormat(string format, params object[] args) - { - Critical(string.Format(format, args)); - } - - } -} diff --git a/SampleWebApp/Infrastructure/ValidationHelper.cs b/SampleWebApp/Infrastructure/ValidationHelper.cs index ab6bf7d..3aa8dba 100644 --- a/SampleWebApp/Infrastructure/ValidationHelper.cs +++ b/SampleWebApp/Infrastructure/ValidationHelper.cs @@ -1,143 +1,33 @@ -#region licence -// The MIT License (MIT) -// -// Filename: ValidationHelper.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Web.Mvc; -using GenericLibsBase; -using GenericServices; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; namespace SampleWebApp.Infrastructure { public static class ValidationHelper { - /// - /// This transfers error messages from the DtoValidation methods to the MVC modelState error dictionary. - /// It looks for errors that have member names corresponding to the properties in the displayDto. - /// This means that errors assciated with a field on display will show next to the name. - /// Other errors will be shown in the ValidationSummary - /// - /// The interface that holds the errors - /// The MVC modelState to add errors to - /// This is the Dto that will be used to display the error messages - public static void CopyErrorsToModelState(this ISuccessOrErrors errorHolder, ModelStateDictionary modelState, T displayDto) - { - if (errorHolder.IsValid) return; - - var namesThatWeShouldInclude = PropertyNamesInDto(displayDto); - foreach (var error in errorHolder.Errors) - { - if (!error.MemberNames.Any()) - modelState.AddModelError("", error.ErrorMessage); - else - foreach (var errorKeyName in error.MemberNames) - modelState.AddModelError( - (namesThatWeShouldInclude.Any(x => x == errorKeyName) ? errorKeyName : ""), - error.ErrorMessage); - } - } - - /// - /// This copies errors for general display where we are not returning to a page with the fields on them - /// - /// - /// - public static void CopyErrorsToModelState(this ISuccessOrErrors errorHolder, ModelStateDictionary modelState) - { - if (errorHolder.IsValid) return; - - foreach (var error in errorHolder.Errors) - modelState.AddModelError("", error.ErrorMessage); - } - - - /// - /// This returns the ModelState errors as a json array containing objects with the PropertyName and the first error message. - /// Must only be called if there are model errors. - /// - /// - /// It returns a JsonNetResult with one parameter called errors which contains key value pairs. - /// The key is the name of the property which had the error, or is empty string if global error. - /// The value is an array of error strings for that property key - public static JsonResult ReturnModelErrorsAsJson(this ModelStateDictionary modelState) + public static Microsoft.AspNetCore.Mvc.JsonResult ReturnModelErrorsAsJson(this ModelStateDictionary modelState) { if (modelState.IsValid) throw new ArgumentException("You should only call this if there are model errors to return."); var dict = new Dictionary(); var emptyNameErrors = new List(); - foreach (var propertyError in modelState.Where(x => x.Value.Errors.Any())) + foreach (var propertyError in modelState.Where(x => x.Value!.Errors.Any())) { if (string.IsNullOrEmpty(propertyError.Key)) - //The modelState doesn't seem to combine empty named items so we do it for it - emptyNameErrors.AddRange(propertyError.Value.Errors.Select(x => x.ErrorMessage)); + emptyNameErrors.AddRange(propertyError.Value!.Errors.Select(x => x.ErrorMessage)); else - dict[propertyError.Key] = new { errors = propertyError.Value.Errors.Select(x => x.ErrorMessage) }; + dict[propertyError.Key] = new { errors = propertyError.Value!.Errors.Select(x => x.ErrorMessage) }; } if (emptyNameErrors.Any()) dict[string.Empty] = new { errors = emptyNameErrors }; - var result = new JsonResult { Data = new { errorsDict = dict } }; - - return result; - } - - /// - /// This returns and errorsDict with any errors in ISuccessOrErrors transferred - /// It looks for errors that have member names corresponding to the properties in the displayDto. - /// This means that errors assciated with a field on display will show next to the name. - /// Other errors will be shown in the ValidationSummary - /// Should only be called if there is an error - /// - /// The interface that holds the errors - /// Dto that the error messages came from - /// It returns a JsonNetResult with one parameter called errors which contains key value pairs. - /// The key is the name of the property which had the error, or is empty string if global error. - /// The value is an array of error strings for that property key - public static JsonResult ReturnErrorsAsJson(this ISuccessOrErrors errorHolder, T displayDto) - { - if (errorHolder.IsValid) - throw new ArgumentException("You should only call ReturnErrorsAsJson when there are errors in the status", "errorHolder"); - - var modelState = new ModelStateDictionary(); - errorHolder.CopyErrorsToModelState(modelState, displayDto); - return modelState.ReturnModelErrorsAsJson(); + return new Microsoft.AspNetCore.Mvc.JsonResult(new { errorsDict = dict }); } - - private static IList PropertyNamesInDto ( T objectToCheck) - { - return - objectToCheck.GetType() - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(x => x.Name) - .ToList(); - } - } -} \ No newline at end of file +} diff --git a/SampleWebApp/Infrastructure/WebUiInitialise.cs b/SampleWebApp/Infrastructure/WebUiInitialise.cs deleted file mode 100644 index 2bb8154..0000000 --- a/SampleWebApp/Infrastructure/WebUiInitialise.cs +++ /dev/null @@ -1,110 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: WebUiInitialise.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System; -using System.Web; -using System.Web.Mvc; -using Autofac.Integration.Mvc; -using GenericLibsBase; -using SampleWebApp.Properties; -using ServiceLayer.Startup; - -namespace SampleWebApp.Infrastructure -{ - public enum HostTypes { NotSet, LocalHost, WebWiz, Azure }; - - public static class WebUiInitialise - { - private const bool ResetIndentityDatabase = false; //set this to true to reset content of Identity database - - //Note: This is also used when running locally - private const string WebWizLog4NetRelPath = "~/Log4Net.xml"; - - public const string DatabaseConnectionStringName = "SampleWebAppDb"; - - /// - /// This provides the host we - /// - public static HostTypes HostType { get; private set; } - - /// - /// This should be called at Startup - /// - public static void InitialiseThis(HttpApplication application) - { - HostType = DecodeHostType(Settings.Default.HostTypeString); - //WebWiz does not allow drop/create database - var canDropCreateDatabase = HostType != HostTypes.WebWiz; - - SetupLogging(application, HostType); - - //This runs the ServiceLayer initialise, whoes job it is to initialise any of the lower layers - //NOTE: This MUST to come before the setup of the DI because it relies on the configInfo being set up - ServiceLayerInitialise.InitialiseThis(HostType == HostTypes.Azure, canDropCreateDatabase); - - //This sets up the Autofac container for all levels in the program - var container = AutofacDi.SetupDependency(); - - //// Set the dependency resolver for MVC. - var mvcResolver = new AutofacDependencyResolver(container); - DependencyResolver.SetResolver(mvcResolver); - } - - private static HostTypes DecodeHostType(string hostTypeString) - { - HostTypes hostType ; - Enum.TryParse(hostTypeString, true, out hostType); - return hostType; - } - - private static void SetupLogging(HttpApplication application, HostTypes hostType) - { - - switch (hostType) - { - case HostTypes.NotSet: - //we do not set up the logging - break; - case HostTypes.LocalHost: - //LocalHost uses WebWiz setup for log4Net - case HostTypes.WebWiz: - //We set up the log4net settings from a local file and then assign the logger to GenericServices.GenericLoggerFactory - var log4NetPath = application.Server.MapPath(WebWizLog4NetRelPath); - log4net.Config.XmlConfigurator.ConfigureAndWatch(new System.IO.FileInfo(log4NetPath)); - GenericLibsBaseConfig.SetLoggerMethod = name => new Log4NetGenericLogger(name); - break; - case HostTypes.Azure: - //we use the TraceGenericLogger when in Azure - GenericLibsBaseConfig.SetLoggerMethod = name => new TraceGenericLogger(name); - break; - default: - throw new ArgumentOutOfRangeException("hostType"); - } - - GenericLibsBaseConfig.GetLogger("LoggerSetup").Info("We have just assegned a logger."); - } - } -} \ No newline at end of file diff --git a/SampleWebApp/Models/InternalsInfo.cs b/SampleWebApp/Models/InternalsInfo.cs index f9df44e..71dffb3 100644 --- a/SampleWebApp/Models/InternalsInfo.cs +++ b/SampleWebApp/Models/InternalsInfo.cs @@ -1,45 +1,15 @@ -#region licence -// The MIT License (MIT) -// -// Filename: InternalsInfo.cs -// Date Created: 2014/07/14 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion using System; -using System.Diagnostics; using System.Threading; namespace SampleWebApp.Models { public class InternalsInfo { - public int WorkerThreads { get; private set; } public int AvailableThreads { get; private set; } - public int AvailableMbytes { get; private set; } - - public int HeapMemoryUsedKbytes { get; private set; } + public long HeapMemoryUsedKbytes { get; private set; } public InternalsInfo() { @@ -53,9 +23,7 @@ public InternalsInfo() WorkerThreads = workerThreads; AvailableThreads = availableThreads; - AvailableMbytes = (int)new PerformanceCounter("Memory", "Available MBytes", true).RawValue; - - HeapMemoryUsedKbytes = (int)(GC.GetTotalMemory(true)/1000); + HeapMemoryUsedKbytes = GC.GetTotalMemory(true) / 1000; } } -} \ No newline at end of file +} diff --git a/SampleWebApp/Program.cs b/SampleWebApp/Program.cs new file mode 100644 index 0000000..4039dbf --- /dev/null +++ b/SampleWebApp/Program.cs @@ -0,0 +1,42 @@ +using DataLayer.DataClasses; +using DataLayer.Startup; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container +builder.Services.AddControllersWithViews(); + +// Configure EF Core with SQLite +var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") + ?? "Data Source=SampleWebApp.db"; +builder.Services.AddDbContext(options => + options.UseSqlite(connectionString)); + +var app = builder.Build(); + +// Ensure database is created and seeded +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureCreated(); + DataLayerInitialise.SeedDatabase(db); +} + +// Configure the HTTP request pipeline +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Home/Error"); + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); +app.UseRouting(); +app.UseAuthorization(); + +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + +app.Run(); diff --git a/SampleWebApp/Properties/AssemblyInfo.cs b/SampleWebApp/Properties/AssemblyInfo.cs deleted file mode 100644 index 5605e92..0000000 --- a/SampleWebApp/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,61 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: AssemblyInfo.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SampleWebApp")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("SampleWebApp")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("fa016029-8cb0-4ef3-a0e0-c94ac71c1177")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SampleWebApp/Properties/Settings.Designer.cs b/SampleWebApp/Properties/Settings.Designer.cs deleted file mode 100644 index 3781ad7..0000000 --- a/SampleWebApp/Properties/Settings.Designer.cs +++ /dev/null @@ -1,44 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.18444 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace SampleWebApp.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("LocalHost")] - public string HostTypeString { - get { - return ((string)(this["HostTypeString"])); - } - } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("jonsmith_")] - public string DatabaseLoginPrefix { - get { - return ((string)(this["DatabaseLoginPrefix"])); - } - } - } -} diff --git a/SampleWebApp/Properties/Settings.settings b/SampleWebApp/Properties/Settings.settings deleted file mode 100644 index cd30221..0000000 --- a/SampleWebApp/Properties/Settings.settings +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - LocalHost - - - jonsmith_ - - - \ No newline at end of file diff --git a/SampleWebApp/SampleWebApp.csproj b/SampleWebApp/SampleWebApp.csproj index eca4d77..277b6a9 100644 --- a/SampleWebApp/SampleWebApp.csproj +++ b/SampleWebApp/SampleWebApp.csproj @@ -1,485 +1,22 @@ - - - + + - Debug - AnyCPU - - - 2.0 - {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties + net8.0 SampleWebApp SampleWebApp - v4.5.1 - false - true - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\ - TRACE - prompt - 4 + enable + disable + - - False - ..\packages\Autofac.3.5.0\lib\net40\Autofac.dll - - - False - ..\packages\Autofac.Mvc5.3.3.1\lib\net45\Autofac.Integration.Mvc.dll - - - ..\packages\AutoMapper.4.2.1\lib\net45\AutoMapper.dll - True - - - ..\packages\DelegateDecompiler.0.18.0\lib\net40-Client\DelegateDecompiler.dll - True - - - ..\packages\DelegateDecompiler.EntityFramework.0.18.0\lib\net45\DelegateDecompiler.EntityFramework.dll - True - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - True - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - True - - - ..\packages\GenericLibsBase.1.0.1\lib\GenericLibsBase.dll - True - - - ..\packages\GenericServices.1.0.9\lib\GenericServices.dll - True - - - ..\packages\log4net.2.0.3\lib\net40-full\log4net.dll - - - False - ..\packages\Microsoft.AspNet.Identity.Core.2.1.0\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - False - ..\packages\Microsoft.AspNet.Identity.EntityFramework.2.1.0\lib\net45\Microsoft.AspNet.Identity.EntityFramework.dll - - - False - ..\packages\Microsoft.AspNet.Identity.Owin.2.1.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll - - - ..\packages\Microsoft.AspNet.SignalR.Core.2.0.3\lib\net45\Microsoft.AspNet.SignalR.Core.dll - - - ..\packages\Microsoft.AspNet.SignalR.SystemWeb.2.0.3\lib\net45\Microsoft.AspNet.SignalR.SystemWeb.dll - True - - - - False - ..\packages\Microsoft.Owin.3.0.0\lib\net45\Microsoft.Owin.dll - - - False - ..\packages\Microsoft.Owin.Security.3.0.0\lib\net45\Microsoft.Owin.Security.dll - - - False - ..\packages\Microsoft.Owin.Security.Cookies.3.0.0\lib\net45\Microsoft.Owin.Security.Cookies.dll - - - False - ..\packages\Microsoft.Owin.Security.Facebook.3.0.0\lib\net45\Microsoft.Owin.Security.Facebook.dll - - - False - ..\packages\Microsoft.Owin.Security.Google.3.0.0\lib\net45\Microsoft.Owin.Security.Google.dll - - - False - ..\packages\Microsoft.Owin.Security.MicrosoftAccount.3.0.0\lib\net45\Microsoft.Owin.Security.MicrosoftAccount.dll - - - False - ..\packages\Microsoft.Owin.Security.OAuth.3.0.0\lib\net45\Microsoft.Owin.Security.OAuth.dll - - - ..\packages\Mono.Reflection.1.0.0.0\lib\Mono.Reflection.dll - - - False - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - False - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - - - False - ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll - - - False - ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll - - - False - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll - - - False - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll - - - False - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - - - - - - - - - - - True - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - - - - - - ..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll - - - True - ..\packages\WebGrease.1.5.2\lib\WebGrease.dll - - - True - ..\packages\Antlr.3.4.1.9004\lib\Antlr3.Runtime.dll - + + + - - ..\packages\Owin.1.0\lib\net40\Owin.dll - - - ..\packages\Microsoft.Owin.Host.SystemWeb.2.1.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\packages\Microsoft.Owin.Security.Twitter.2.1.0\lib\net45\Microsoft.Owin.Security.Twitter.dll - + + + - - - - - - - - - - - - Global.asax - - - - - - - - - - - True - True - Settings.settings - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - Web.config - - - Web.config - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Web.config - - - Web.config - - - - - - - - - - - - - {264e1878-12de-4099-b8d7-cc53a73fea49} - DataLayer - - - {d2813927-0f38-43c3-b47c-ae8f00d50cae} - ServiceLayer - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - bin\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - - - - - - - - - True - True - 50623 - / - http://localhost:50623/ - False - False - - - False - - - - - - \ No newline at end of file + + diff --git a/SampleWebApp/Views/Home/Internals.cshtml b/SampleWebApp/Views/Home/Internals.cshtml index fcf3365..62f7ab8 100644 --- a/SampleWebApp/Views/Home/Internals.cshtml +++ b/SampleWebApp/Views/Home/Internals.cshtml @@ -1,4 +1,4 @@ -@model SampleWebApp.Models.InternalsInfo +@model SampleWebApp.Models.InternalsInfo

InternalsInfo

@@ -20,14 +20,6 @@ @Html.DisplayFor(model => model.AvailableThreads) -
- @Html.DisplayNameFor(model => model.AvailableMbytes) -
- -
- @Html.DisplayFor(model => model.AvailableMbytes) -
-
@Html.DisplayNameFor(model => model.HeapMemoryUsedKbytes)
@@ -38,5 +30,5 @@

- @Html.ActionLink("Back to List", "Index") + Back to List

diff --git a/SampleWebApp/Views/Posts/Create.cshtml b/SampleWebApp/Views/Posts/Create.cshtml index ca33689..22e5e44 100644 --- a/SampleWebApp/Views/Posts/Create.cshtml +++ b/SampleWebApp/Views/Posts/Create.cshtml @@ -1,4 +1,4 @@ -@model ServiceLayer.PostServices.DetailPostDto +@model ServiceLayer.PostServices.DetailPostDto @{ ViewBag.Title = "Create"; @@ -6,59 +6,70 @@

Create

- -@using (Html.BeginForm()) -{ +
@Html.AntiForgeryToken()

Post


- @Html.ValidationSummary(true, "", new { @class = "text-danger" }) +
+
- @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" }) + +
- @Html.LabelFor(model => model.Content, htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.Content, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.Content, "", new { @class = "text-danger" }) + +
+
- @Html.LabelFor(model => model.Bloggers, htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.Bloggers, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.Bloggers, "", new { @class = "text-danger" }) + @if (Model.Bloggers?.KeyValueList != null) + { + + }
- @Html.Label("Tags", htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.UserChosenTags, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.UserChosenTags, "", new { @class = "text-danger" }) + @if (Model.UserChosenTags?.AllPossibleOptions != null) + { + + }
+
-} +
- @Html.ActionLink("Back to List", "Index") + Back to List

-@Html.Partial("PostValidation") - -@section Scripts { - @Scripts.Render("~/bundles/jqueryval") -} +@await Html.PartialAsync("PostValidation") diff --git a/SampleWebApp/Views/Posts/Edit.cshtml b/SampleWebApp/Views/Posts/Edit.cshtml index 49d666f..f2de66f 100644 --- a/SampleWebApp/Views/Posts/Edit.cshtml +++ b/SampleWebApp/Views/Posts/Edit.cshtml @@ -1,4 +1,4 @@ -@model ServiceLayer.PostServices.DetailPostDto +@model ServiceLayer.PostServices.DetailPostDto @{ ViewBag.Title = "Edit"; @@ -6,46 +6,59 @@

Edit

- -@using (Html.BeginForm()) -{ +
@Html.AntiForgeryToken()

DetailPostDto


- @Html.ValidationSummary(true, "", new { @class = "text-danger" }) - @Html.HiddenFor(model => model.PostId) +
+
- @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" }) + +
- @Html.LabelFor(model => model.Content, htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.Content, new { htmlAttributes = new { @class = "form-control", style = "max-width: 100%" } }) - @Html.ValidationMessageFor(model => model.Content, "", new { @class = "text-danger" }) + +
- @Html.LabelFor(model => model.Bloggers, htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.Bloggers, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.Bloggers, "", new { @class = "text-danger" }) + @if (Model.Bloggers?.KeyValueList != null) + { + + }
- @Html.Label("Tags", htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.UserChosenTags, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.UserChosenTags, "", new { @class = "text-danger" }) + @if (Model.UserChosenTags?.AllPossibleOptions != null) + { + + }
@@ -55,15 +68,10 @@
-} +
- @Html.ActionLink("Back to List", "Index") + Back to List

-@Html.Partial("PostValidation") - - -@section Scripts { - @Scripts.Render("~/bundles/jqueryval") -} +@await Html.PartialAsync("PostValidation") diff --git a/SampleWebApp/Views/PostsAsync/Create.cshtml b/SampleWebApp/Views/PostsAsync/Create.cshtml index d22f305..eebd6c6 100644 --- a/SampleWebApp/Views/PostsAsync/Create.cshtml +++ b/SampleWebApp/Views/PostsAsync/Create.cshtml @@ -1,4 +1,4 @@ -@model ServiceLayer.PostServices.DetailPostDtoAsync +@model ServiceLayer.PostServices.DetailPostDtoAsync @{ ViewBag.Title = "Create"; @@ -6,59 +6,70 @@

Create (Async)

- -@using (Html.BeginForm()) -{ +
@Html.AntiForgeryToken()

Post


- @Html.ValidationSummary(true, "", new { @class = "text-danger" }) +
+
- @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" }) + +
- @Html.LabelFor(model => model.Content, htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.Content, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.Content, "", new { @class = "text-danger" }) + +
+
- @Html.LabelFor(model => model.Bloggers, htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.Bloggers, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.Bloggers, "", new { @class = "text-danger" }) + @if (Model.Bloggers?.KeyValueList != null) + { + + }
- @Html.Label("Tags", htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.UserChosenTags, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.UserChosenTags, "", new { @class = "text-danger" }) + @if (Model.UserChosenTags?.AllPossibleOptions != null) + { + + }
+
-} +
- @Html.ActionLink("Back to List", "Index") + Back to List

-@Html.Partial("PostValidation") - -@section Scripts { - @Scripts.Render("~/bundles/jqueryval") -} +@await Html.PartialAsync("PostValidation") diff --git a/SampleWebApp/Views/PostsAsync/Edit.cshtml b/SampleWebApp/Views/PostsAsync/Edit.cshtml index 75b1706..c35e028 100644 --- a/SampleWebApp/Views/PostsAsync/Edit.cshtml +++ b/SampleWebApp/Views/PostsAsync/Edit.cshtml @@ -1,4 +1,4 @@ -@model ServiceLayer.PostServices.DetailPostDtoAsync +@model ServiceLayer.PostServices.DetailPostDtoAsync @{ ViewBag.Title = "Edit"; @@ -6,45 +6,59 @@

Edit (Async)

-@using (Html.BeginForm()) -{ +
@Html.AntiForgeryToken()

DetailPostDto


- @Html.ValidationSummary(true, "", new { @class = "text-danger" }) - @Html.HiddenFor(model => model.PostId) +
+
- @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" }) + +
- @Html.LabelFor(model => model.Content, htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.Content, new { htmlAttributes = new { @class = "form-control", style = "max-width: 100%" } }) - @Html.ValidationMessageFor(model => model.Content, "", new { @class = "text-danger" }) + +
- @Html.LabelFor(model => model.Bloggers, htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.Bloggers, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.Bloggers, "", new { @class = "text-danger" }) + @if (Model.Bloggers?.KeyValueList != null) + { + + }
- @Html.Label("Tags", htmlAttributes: new { @class = "control-label col-md-2" }) +
- @Html.EditorFor(model => model.UserChosenTags, new { htmlAttributes = new { @class = "form-control" } }) - @Html.ValidationMessageFor(model => model.UserChosenTags, "", new { @class = "text-danger" }) + @if (Model.UserChosenTags?.AllPossibleOptions != null) + { + + }
@@ -54,14 +68,10 @@
-} +
- @Html.ActionLink("Back to List", "Index") + Back to List

-@Html.Partial("PostValidation") - -@section Scripts { - @Scripts.Render("~/bundles/jqueryval") -} +@await Html.PartialAsync("PostValidation") diff --git a/SampleWebApp/Views/Shared/EditorTemplates/DropDownListType.cshtml b/SampleWebApp/Views/Shared/EditorTemplates/DropDownListType.cshtml deleted file mode 100644 index e7d6ea2..0000000 --- a/SampleWebApp/Views/Shared/EditorTemplates/DropDownListType.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@model ServiceLayer.UiClasses.DropDownListType - -@Html.DropDownListFor( x => x.SelectedValue, Model.KeyValueList.Select( x => new SelectListItem - { - Selected = Model.SelectedValue != null && Model.KeyValueList[0].Key == x.Key, - Text = x.Key, - Value = x.Value - })) \ No newline at end of file diff --git a/SampleWebApp/Views/Shared/EditorTemplates/MultiSelectListType.cshtml b/SampleWebApp/Views/Shared/EditorTemplates/MultiSelectListType.cshtml deleted file mode 100644 index b926efd..0000000 --- a/SampleWebApp/Views/Shared/EditorTemplates/MultiSelectListType.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@model ServiceLayer.UiClasses.MultiSelectListType - -@Html.ListBoxFor(m => m.FinalSelection, new MultiSelectList(Model.AllPossibleOptions, "Value", "Key", Model.InitialSelection )) \ No newline at end of file diff --git a/SampleWebApp/Views/Shared/Error.cshtml b/SampleWebApp/Views/Shared/Error.cshtml index be55b17..61bf998 100644 --- a/SampleWebApp/Views/Shared/Error.cshtml +++ b/SampleWebApp/Views/Shared/Error.cshtml @@ -1,9 +1,6 @@ -@model System.Web.Mvc.HandleErrorInfo - @{ ViewBag.Title = "Error"; }

Error.

An error occurred while processing your request.

- diff --git a/SampleWebApp/Views/Shared/PostValidation.cshtml b/SampleWebApp/Views/Shared/PostValidation.cshtml index 0344aa3..3cf69d3 100644 --- a/SampleWebApp/Views/Shared/PostValidation.cshtml +++ b/SampleWebApp/Views/Shared/PostValidation.cshtml @@ -1,96 +1,19 @@ -
-

Post Validation rules

-

- To help with testing the Post data class, i.e. the one that is linked to the database, has extra rules over the DetailPostDto. - This means that more tests are done when the database is updated, which are then caught by SaveChanges and sent back as errors. -

- -
Rules in both DetailPostDto and Post
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PropertyValidation ruleWhere shown?Where Checked*
TitleMust have between 2 and 128 characters in itBy propertyBrowser,MVC,EF
TitleMust not contain HTML symbols, e.g. <, > ExceptionMVC
ContentCannot be emptyBy propertyBrowser,MVC,EF
ContentMust not contain HTML symbols, e.g. <, > ExceptionMVC
Blog (Author)Must have a blogger assigned to the postBy propertyDTO,EF
TagsMust have at least one tag assigned to the PostBy propertyDTO,EF
- -
Rules only in Post
-

Note that these strange rules have been added to allow testing of different error conditions

- - - - - - - - - - - - - - - - - - - - - - - - - -
PropertyValidation ruleWhere shown?Where Checked*
TitleMust not include an exclamation mark, i.e '!'By propertyEF
TitleMust not end with a question mark, i.e '?'By propertyEF
Content - A sentence must not end with the following words:
- ' sheep.', ' lamb.', ' cow.' or ' calf.' -
At topEF
- -

- Note*: The 'Where Checked' terms are -

-
    -
  • Browser: in browser using javascript validation library
  • -
  • MVC: by statement if (!ModelState.IsValid) in Controller actions
  • -
  • EF: by Entity Framework validation phase
  • -
  • DTO: By the code inside the DetailPostDto class
  • -
-

The test for HTML symbols is done by MVC and throws an exception, which in the release version goes to the default error page.

-
+

Post Validations

+

To test the validations try the following:

+
    +
  • + Title: Must be 2 to 128 characters. +
  • +
  • + Content: Required. +
  • +
  • + Title with trailing '!': The Post entity has a validation rule that does not allow Titles ending with '!'. +
  • +
  • + Blogger: A blogger must be selected. +
  • +
  • + Tags: At least one tag must be selected. +
  • +
diff --git a/SampleWebApp/Views/Shared/TagValidation.cshtml b/SampleWebApp/Views/Shared/TagValidation.cshtml index 8eb991c..28b9f8b 100644 --- a/SampleWebApp/Views/Shared/TagValidation.cshtml +++ b/SampleWebApp/Views/Shared/TagValidation.cshtml @@ -1,76 +1,13 @@ -
-

Tag Validation rules

-

- The Tag data class, i.e. the one that is linked to the database, a number of validation rules, - including one that is checked inside the EF SaveChanges. -

- -
Validation rules in Tag class, and inside EF SaveChanges
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PropertyValidation ruleWhere shown?Where Checked*
NameMust not be emptyBy propertyBrowser,MVC,EF
NameMust not be longer than 128 charactersBy propertyBrowser,MVC,EF
NameMust not contain HTML symbols, e.g. <, > ExceptionMVC
SlugMust not be emptyBy propertyBrowser,MVC,EF
SlugMust not be longer than 128 charactersBy propertyBrowser,MVC,EF
SlugMust not be emptyBy propertyBrowser,MVC,EF
SlugMust not contain spaces or non-alphanumeric charactersBy propertyMVC,EF
SlugMust be unique, i.e. must not exist in any other tag entry in the databaseBy propertySaveChanges
- -

- Note*: The 'Where Checked' terms are -

-
    -
  • Browser: in browser using javascript validation library
  • -
  • MVC: by statement if (!ModelState.IsValid) in Controller actions
  • -
  • EF: by Entity Framework validation phase
  • -
  • SaveChanges: Special validaton code added to EF's SaveChanges
  • -
-

The test for HTML symbols is done by MVC and throws an exception, which in the release version goes to the default error page.

-
\ No newline at end of file +

Tag Validations

+

To test the validations try the following:

+
    +
  • + Name: Required, max 128 characters. +
  • +
  • + Slug: Required, max 64 characters, must be alphanumeric only (no spaces or special characters). +
  • +
  • + Duplicate Slug: Each tag must have a unique slug. +
  • +
diff --git a/SampleWebApp/Views/Shared/_Layout.cshtml b/SampleWebApp/Views/Shared/_Layout.cshtml index 345fd91..9cfc644 100644 --- a/SampleWebApp/Views/Shared/_Layout.cshtml +++ b/SampleWebApp/Views/Shared/_Layout.cshtml @@ -1,12 +1,11 @@ -@using SampleWebApp.Infrastructure @ViewBag.Title - SampleMvcWebApp - @Styles.Render("~/Content/css") - + +
@RenderBody()
-
+
An open source project under the MIT licence, created by Jon Smith. - Hosted on @WebUiInitialise.HostType
- @Scripts.Render("~/bundles/javascript") - @RenderSection("scripts", required: false) + + + @await RenderSectionAsync("Scripts", required: false) diff --git a/SampleWebApp/Views/Tags/Create.cshtml b/SampleWebApp/Views/Tags/Create.cshtml index 216fd78..9fd1dac 100644 --- a/SampleWebApp/Views/Tags/Create.cshtml +++ b/SampleWebApp/Views/Tags/Create.cshtml @@ -1,4 +1,4 @@ -@model DataLayer.DataClasses.Concrete.Tag +@model DataLayer.DataClasses.Concrete.Tag @{ ViewBag.Title = "Create"; @@ -43,4 +43,4 @@ @Html.ActionLink("Back to List", "Index")
-@Html.Partial("TagValidation") +@await Html.PartialAsync("TagValidation") diff --git a/SampleWebApp/Views/Tags/Edit.cshtml b/SampleWebApp/Views/Tags/Edit.cshtml index baab41e..26a41f9 100644 --- a/SampleWebApp/Views/Tags/Edit.cshtml +++ b/SampleWebApp/Views/Tags/Edit.cshtml @@ -1,4 +1,4 @@ -@model DataLayer.DataClasses.Concrete.Tag +@model DataLayer.DataClasses.Concrete.Tag @{ ViewBag.Title = "Edit"; @@ -44,4 +44,4 @@ @Html.ActionLink("Back to List", "Index")
-@Html.Partial("TagValidation") +@await Html.PartialAsync("TagValidation") diff --git a/SampleWebApp/Views/TagsAsync/Create.cshtml b/SampleWebApp/Views/TagsAsync/Create.cshtml index 84783db..2ee56b8 100644 --- a/SampleWebApp/Views/TagsAsync/Create.cshtml +++ b/SampleWebApp/Views/TagsAsync/Create.cshtml @@ -1,4 +1,4 @@ -@model DataLayer.DataClasses.Concrete.Tag +@model DataLayer.DataClasses.Concrete.Tag @{ ViewBag.Title = "Create"; @@ -43,4 +43,4 @@ @Html.ActionLink("Back to List", "Index")
-@Html.Partial("TagValidation") +@await Html.PartialAsync("TagValidation") diff --git a/SampleWebApp/Views/TagsAsync/Edit.cshtml b/SampleWebApp/Views/TagsAsync/Edit.cshtml index 3f41685..e8c04bd 100644 --- a/SampleWebApp/Views/TagsAsync/Edit.cshtml +++ b/SampleWebApp/Views/TagsAsync/Edit.cshtml @@ -1,4 +1,4 @@ -@model DataLayer.DataClasses.Concrete.Tag +@model DataLayer.DataClasses.Concrete.Tag @{ ViewBag.Title = "Edit"; @@ -44,4 +44,4 @@ @Html.ActionLink("Back to List", "Index")
-@Html.Partial("TagValidation") +@await Html.PartialAsync("TagValidation") diff --git a/SampleWebApp/Views/Web.config b/SampleWebApp/Views/Web.config deleted file mode 100644 index ba26898..0000000 --- a/SampleWebApp/Views/Web.config +++ /dev/null @@ -1,35 +0,0 @@ - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SampleWebApp/Views/_ViewImports.cshtml b/SampleWebApp/Views/_ViewImports.cshtml new file mode 100644 index 0000000..a1023a1 --- /dev/null +++ b/SampleWebApp/Views/_ViewImports.cshtml @@ -0,0 +1,7 @@ +@using SampleWebApp +@using DataLayer.DataClasses.Concrete +@using ServiceLayer.PostServices +@using ServiceLayer.BlogServices +@using ServiceLayer.TagServices +@using ServiceLayer.UiClasses +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/SampleWebApp/Views/_ViewStart.cshtml b/SampleWebApp/Views/_ViewStart.cshtml index 2de6241..820a2f6 100644 --- a/SampleWebApp/Views/_ViewStart.cshtml +++ b/SampleWebApp/Views/_ViewStart.cshtml @@ -1,3 +1,3 @@ -@{ - Layout = "~/Views/Shared/_Layout.cshtml"; +@{ + Layout = "_Layout"; } diff --git a/SampleWebApp/Web.Debug.config b/SampleWebApp/Web.Debug.config deleted file mode 100644 index 680849f..0000000 --- a/SampleWebApp/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - diff --git a/SampleWebApp/Web.Release.config b/SampleWebApp/Web.Release.config deleted file mode 100644 index 943c9c0..0000000 --- a/SampleWebApp/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - diff --git a/SampleWebApp/Web.config b/SampleWebApp/Web.config deleted file mode 100644 index 02309cc..0000000 --- a/SampleWebApp/Web.config +++ /dev/null @@ -1,110 +0,0 @@ - - - - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LocalHost - - - jonsmith_ - - - - \ No newline at end of file diff --git a/SampleWebApp/appsettings.Development.json b/SampleWebApp/appsettings.Development.json new file mode 100644 index 0000000..ce2b527 --- /dev/null +++ b/SampleWebApp/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Information" + } + } +} diff --git a/SampleWebApp/appsettings.json b/SampleWebApp/appsettings.json new file mode 100644 index 0000000..e964d99 --- /dev/null +++ b/SampleWebApp/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Data Source=SampleWebApp.db" + } +} diff --git a/SampleWebApp/packages.config b/SampleWebApp/packages.config deleted file mode 100644 index bc278c0..0000000 --- a/SampleWebApp/packages.config +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SampleWebApp/wwwroot/css/site.css b/SampleWebApp/wwwroot/css/site.css new file mode 100644 index 0000000..d66ce7a --- /dev/null +++ b/SampleWebApp/wwwroot/css/site.css @@ -0,0 +1,23 @@ +body { + padding-top: 50px; + padding-bottom: 20px; +} + +/* Set padding to keep content from hitting the edges */ +.body-content { + padding-left: 15px; + padding-right: 15px; +} + +/* Override the default bootstrap behavior where horizontal description lists + will truncate terms that are too long to fit in the left column */ +.dl-horizontal dt { + white-space: normal; +} + +/* Set width on the form input elements since they're 100% wide by default */ +input, +select, +textarea { + max-width: 280px; +} diff --git a/ServiceLayer/App.config b/ServiceLayer/App.config deleted file mode 100644 index dac3ee5..0000000 --- a/ServiceLayer/App.config +++ /dev/null @@ -1,29 +0,0 @@ - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ServiceLayer/BlogServices/BlogListDto.cs b/ServiceLayer/BlogServices/BlogListDto.cs index c50f8e8..09f51fa 100644 --- a/ServiceLayer/BlogServices/BlogListDto.cs +++ b/ServiceLayer/BlogServices/BlogListDto.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: BlogListDto.cs @@ -24,19 +24,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #endregion - using System.ComponentModel.DataAnnotations; -using System.Runtime.CompilerServices; -using DataLayer.DataClasses.Concrete; -using GenericServices.Core; - -[assembly: InternalsVisibleTo("Tests")] namespace ServiceLayer.BlogServices { - public class BlogListDto : EfGenericDto + public class BlogListDto { - [UIHint("HiddenInput")] [Key] public int BlogId { get; set; } @@ -46,14 +39,6 @@ public class BlogListDto : EfGenericDto [EmailAddress] public string EmailAddress { get; set; } - public int PostsCount { get; set; } //Uses AutoMapper Aggregate - - //---------------------------------------------- - //overridden properties or methods - - protected override CrudFunctions SupportedFunctions - { - get { return CrudFunctions.List; } - } + public int PostsCount { get; set; } } } diff --git a/ServiceLayer/PostServices/DetailPostDto.cs b/ServiceLayer/PostServices/DetailPostDto.cs index 2cdc60d..a7f3a0e 100644 --- a/ServiceLayer/PostServices/DetailPostDto.cs +++ b/ServiceLayer/PostServices/DetailPostDto.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: DetailPostDto.cs @@ -28,22 +28,13 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Runtime.CompilerServices; -using DataLayer.DataClasses; using DataLayer.DataClasses.Concrete; -using GenericLibsBase; -using GenericLibsBase.Core; -using GenericServices; -using GenericServices.Core; using ServiceLayer.UiClasses; -[assembly: InternalsVisibleTo("Tests")] - namespace ServiceLayer.PostServices { - public class DetailPostDto : EfGenericDto + public class DetailPostDto { - [UIHint("HiddenInput")] [Key] public int PostId { get; set; } @@ -55,28 +46,17 @@ public class DetailPostDto : EfGenericDto [Required] public string Content { get; set; } - //AutoMapper understands that this means Blogger.Name and fills it with the Name of the Blogger public string BloggerName { get; set; } - //------------------------------------------- - //properties that cannot be set directly (The data layer looks after them) - [ScaffoldColumn(false)] - [DoNotCopyBackToDatabase] public DateTime LastUpdated { get; set; } - //------------------------------------------ - //these two items are altered by the SetupRestOfDto method based on the user's selection - [UIHint("HiddenInput")] public int BlogId { get; set; } [ScaffoldColumn(false)] public ICollection Tags { get; set; } - //------------------------------------------- - //now the various lists for user interaction - /// /// This allows a single blogger to be chosen from the list /// @@ -84,136 +64,18 @@ public class DetailPostDto : EfGenericDto public MultiSelectListType UserChosenTags { get; set; } - //------------------------------------------- - //calculated properties to help display - //(Note: SampleMvcWebApp was written before calculated properties using [Computed] was added to GenericServices. - //see https://github.com/JonPSmith/GenericServices/wiki/Calculated-properties for a better way of doing this - /// /// When it was last updated in DateTime format /// public DateTime LastUpdatedUtc { get { return DateTime.SpecifyKind(LastUpdated, DateTimeKind.Utc); } } - public string TagNames { get { return string.Join(", ", Tags.Select(x => x.Name)); } } + public string TagNames { get { return Tags != null ? string.Join(", ", Tags.Select(x => x.Name)) : ""; } } - //ctor public DetailPostDto() { + Tags = new List(); Bloggers = new DropDownListType(); UserChosenTags = new MultiSelectListType(); } - - //---------------------------------------------- - //overridden methods - - protected override CrudFunctions SupportedFunctions - { - get { return CrudFunctions.AllCrud; } - } - - /// - /// This sets up the dropdownlist for the possible bloggers and the MultiSelectList of tags - /// - /// - /// - protected override void SetupSecondaryData(IGenericServicesDbContext context, DetailPostDto dto) - { - - dto.Bloggers.SetupDropDownListContent( - context.Set() - .ToList() - .Select(x => new KeyValuePair(x.Name, x.BlogId.ToString("D"))), - "--- choose blogger ---"); - if (dto.PostId != 0) - //there is an entry, so set the selected value to that - dto.Bloggers.SetSelectedValue(dto.BlogId.ToString("D")); - - var preselectedTags = dto.PostId == 0 - ? new List>() //Create, so no tags selected yet - : Tags - .Select(x => new { Key = x.Name, Value = x.TagId }) - .ToList() - .Select(x => new KeyValuePair(x.Key, x.Value)) - .ToList(); - dto.UserChosenTags.SetupMultiSelectList( - context.Set().ToList().Select(x => new KeyValuePair(x.Name, x.TagId)), preselectedTags); - } - - protected override ISuccessOrErrors CreateDataFromDto(IGenericServicesDbContext context, DetailPostDto source) - { - var status = SetupRestOfDto(context); - - return status.IsValid - ? base.CreateDataFromDto(context, this) - : SuccessOrErrors.ConvertNonResultStatus(status); - } - - protected override ISuccessOrErrors UpdateDataFromDto(IGenericServicesDbContext context, DetailPostDto source, Post destination) - { - var status = SetupRestOfDto(context, destination); - - return status.IsValid - ? base.UpdateDataFromDto(context, this, destination) - : status; - } - - //--------------------------------------------------- - //private helpers - - private ISuccessOrErrors SetupRestOfDto(IGenericServicesDbContext context, Post post = null) - { - - var db = context as SampleWebAppDb; - if (db == null) - throw new NullReferenceException("The IDbContextWithValidation must be linked to TemplateWebAppDb."); - - var status = SuccessOrErrors.Success("OK if no errors set"); - - //now we sort out the blogger - var errMsg = SetBloggerIdFromDropDownList(db); - if (errMsg != null) - status.AddNamedParameterError("Bloggers", errMsg); - - //now we sort out the tags - errMsg = ChangeTagsBasedOnMultiSelectList(db, post); - if (errMsg != null) - status.AddNamedParameterError("UserChosenTags", errMsg); - - return status; - } - - private string SetBloggerIdFromDropDownList(SampleWebAppDb db) - { - - var blogId = Bloggers.SelectedValueAsInt; - if (blogId == null) - return "The blogger was not selected. You must do that before the post can be saved."; - - var blogger = db.Blogs.Find((int)blogId); - if (blogger == null) - return "Could not find the blogger you selected. Did another user delete it?"; - - BlogId = (int)blogId; //will be copied over to database entity by AutoMapper - return null; - } - - private string ChangeTagsBasedOnMultiSelectList(SampleWebAppDb db, Post post = null) - { - var requiredTagIds = UserChosenTags.GetFinalSelectionAsInts(); - if (!requiredTagIds.Any()) - return "You must select at least one tag for the post."; - - if (requiredTagIds.Any(x => db.Tags.Find(x) == null)) - return "Could not find one of the tags. Did another user delete it?"; - - if (post != null) - //This is an update so we need to load the tags - db.Entry(post).Collection(p => p.Tags).Load(); - - var newTagsForPost = db.Tags.Where(x => requiredTagIds.Contains(x.TagId)).ToList(); - Tags = newTagsForPost; //will be copied over to database entity by AutoMapper - - return null; - } } } diff --git a/ServiceLayer/PostServices/DetailPostDtoAsync.cs b/ServiceLayer/PostServices/DetailPostDtoAsync.cs index dd27b14..fc55c4f 100644 --- a/ServiceLayer/PostServices/DetailPostDtoAsync.cs +++ b/ServiceLayer/PostServices/DetailPostDtoAsync.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: DetailPostDtoAsync.cs @@ -27,25 +27,14 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Data.Entity; using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using DataLayer.DataClasses; using DataLayer.DataClasses.Concrete; -using GenericLibsBase; -using GenericLibsBase.Core; -using GenericServices; -using GenericServices.Core; using ServiceLayer.UiClasses; -[assembly: InternalsVisibleTo("Tests")] - namespace ServiceLayer.PostServices { - public class DetailPostDtoAsync : EfGenericDtoAsync + public class DetailPostDtoAsync { - [UIHint("HiddenInput")] [Key] public int PostId { get; set; } @@ -57,27 +46,16 @@ public class DetailPostDtoAsync : EfGenericDtoAsync [Required] public string Content { get; set; } - //AutoMapper understands that this means Blogger.Name and fills it with the Name of the Blogger public string BloggerName { get; set; } - //------------------------------------------- - //properties that cannot be set directly (The data layer looks after them) - [ScaffoldColumn(false)] - [DoNotCopyBackToDatabase] public DateTime LastUpdated { get; set; } - //------------------------------------------ - //these two items are altered by the SetupRestOfDto method based on the user's selection - [UIHint("HiddenInput")] public int BlogId { get; set; } [ScaffoldColumn(false)] - public ICollection Tags { get; set; } //this must be copied back - - //------------------------------------------- - //now the various lists for user interaction + public ICollection Tags { get; set; } /// /// This allows a single blogger to be chosen from the list @@ -86,138 +64,18 @@ public class DetailPostDtoAsync : EfGenericDtoAsync public MultiSelectListType UserChosenTags { get; set; } - //------------------------------------------- - //calculated properties to help display - //(Note: SampleMvcWebApp was written before calculated properties using [Computed] was added to GenericServices. - //see https://github.com/JonPSmith/GenericServices/wiki/Calculated-properties for a better way of doing this - /// /// When it was last updated in DateTime format /// public DateTime LastUpdatedUtc { get { return DateTime.SpecifyKind(LastUpdated, DateTimeKind.Utc); } } - public string TagNames { get { return string.Join(", ", Tags.Select(x => x.Name)); } } + public string TagNames { get { return Tags != null ? string.Join(", ", Tags.Select(x => x.Name)) : ""; } } - //ctor public DetailPostDtoAsync() { + Tags = new List(); Bloggers = new DropDownListType(); UserChosenTags = new MultiSelectListType(); } - - - //---------------------------------------------- - //overridden methods - - protected override CrudFunctions SupportedFunctions - { - get { return CrudFunctions.AllCrud; } - } - - /// - /// This sets up the dropdownlist for the possible bloggers and the MultiSelectList of tags - /// - /// - /// - protected override async Task SetupSecondaryDataAsync(IGenericServicesDbContext context, DetailPostDtoAsync dto) - { - - var bloggers = await context.Set().ToListAsync(); - - dto.Bloggers.SetupDropDownListContent(bloggers.Select(x => new KeyValuePair(x.Name, x.BlogId.ToString("D"))), - "--- choose blogger ---"); - if (dto.PostId != 0) - //there is an entry, so set the selected value to that - dto.Bloggers.SetSelectedValue(dto.BlogId.ToString("D")); - - var preselectedTags = dto.PostId == 0 - ? new List>() //Create, so no tags selected yet - : Tags - .Select(x => new { Key = x.Name, Value = x.TagId }) - .ToList() - .Select(x => new KeyValuePair(x.Key, x.Value)) - .ToList(); - dto.UserChosenTags.SetupMultiSelectList( - context.Set().ToList().Select(x => new KeyValuePair(x.Name, x.TagId)), preselectedTags); - } - - - protected override async Task> CreateDataFromDtoAsync(IGenericServicesDbContext context, DetailPostDtoAsync source) - { - var status = await SetupRestOfDto(context); - - return status.IsValid - ? await base.CreateDataFromDtoAsync(context, this) - : SuccessOrErrors.ConvertNonResultStatus(status); - } - - protected override async Task UpdateDataFromDtoAsync(IGenericServicesDbContext context, DetailPostDtoAsync source, Post destination) - { - var status = await SetupRestOfDto(context, destination); - - return status.IsValid - ? await base.UpdateDataFromDtoAsync(context, this, destination) - : status; - } - - private async Task SetupRestOfDto(IGenericServicesDbContext context, Post post = null) - { - - var db = context as SampleWebAppDb; - if (db == null) - throw new NullReferenceException("The IDbContextWithValidation must be linked to TemplateWebAppDb."); - - var status = SuccessOrErrors.Success("OK if no errors set"); - - //now we sort out the blogger - var errMsg = await SetupBloggerIdFromDropDownList(db, post); - if (errMsg != null) - status.AddNamedParameterError("Bloggers", errMsg); - - //now we sort out the tags - errMsg = await ChangeTagsBasedOnMultiSelectList(db, post); - if (errMsg != null) - status.AddNamedParameterError("UserChosenTags", errMsg); - - return status; - } - - - //--------------------------------------------------- - //private helpers - - private async Task SetupBloggerIdFromDropDownList(SampleWebAppDb db, Post post) - { - - var blogId = Bloggers.SelectedValueAsInt; - if (blogId == null) - return "The blogger was not selected. You must do that before the post can be saved."; - - var blogger = await db.Blogs.FindAsync((int)blogId); - if (blogger == null) - return "Could not find the blogger you selected. Did another user delete it?"; - - BlogId = (int)blogId; - return null; - } - - private async Task ChangeTagsBasedOnMultiSelectList(SampleWebAppDb db, Post post) - { - var requiredTagIds = UserChosenTags.GetFinalSelectionAsInts(); - if (!requiredTagIds.Any()) - return "You must select at least one tag for the post."; - - if (requiredTagIds.Any(x => db.Tags.Find(x) == null)) - return "Could not find one of the tags. Did another user delete it?"; - - if (post != null) - //This is an update so we need to load the tags - db.Entry(post).Collection(p => p.Tags).Load(); - - var newTagsForPost = await db.Tags.Where(x => requiredTagIds.Contains(x.TagId)).ToListAsync(); - Tags = newTagsForPost; //will be copied over by copyDtoToData - - return null; - } } } diff --git a/ServiceLayer/PostServices/SimplePostDto.cs b/ServiceLayer/PostServices/SimplePostDto.cs index 3a664bc..60a4bd0 100644 --- a/ServiceLayer/PostServices/SimplePostDto.cs +++ b/ServiceLayer/PostServices/SimplePostDto.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: SimplePostDto.cs @@ -28,49 +28,33 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Runtime.CompilerServices; -using DataLayer.DataClasses.Concrete; -using GenericServices; -using GenericServices.Core; - -[assembly: InternalsVisibleTo("Tests")] namespace ServiceLayer.PostServices { - public class SimplePostDto : EfGenericDto + public class SimplePostDto { - [UIHint("HiddenInput")] [Key] public int PostId { get; set; } [UIHint("HiddenInput")] - public int BlogId { get; set; } + public int BlogId { get; set; } - public string BloggerName { get; set; } + public string BloggerName { get; set; } [MinLength(2), MaxLength(128)] public string Title { get; set; } - [ScaffoldColumn(false)] - public ICollection Tags { get; set; } + public List TagNamesList { get; set; } = new List(); [ScaffoldColumn(false)] - public DateTime LastUpdated { get; set; } + public DateTime LastUpdated { get; set; } /// /// When it was last updated in DateTime format /// public DateTime LastUpdatedUtc { get { return DateTime.SpecifyKind(LastUpdated, DateTimeKind.Utc); } } - public string TagNames { get { return string.Join(", ", Tags.Select(x => x.Name)); } } - - //---------------------------------------------- - //overridden properties or methods - - protected override CrudFunctions SupportedFunctions - { - get { return CrudFunctions.List; } - } + public string TagNames { get { return string.Join(", ", TagNamesList); } } } } diff --git a/ServiceLayer/PostServices/SimplePostDtoAsync.cs b/ServiceLayer/PostServices/SimplePostDtoAsync.cs index f19d434..4495142 100644 --- a/ServiceLayer/PostServices/SimplePostDtoAsync.cs +++ b/ServiceLayer/PostServices/SimplePostDtoAsync.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: SimplePostDtoAsync.cs @@ -28,17 +28,11 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Runtime.CompilerServices; -using DataLayer.DataClasses.Concrete; -using GenericServices.Core; - -[assembly: InternalsVisibleTo("Tests")] namespace ServiceLayer.PostServices { - public class SimplePostDtoAsync : EfGenericDtoAsync + public class SimplePostDtoAsync { - [UIHint("HiddenInput")] [Key] public int PostId { get; set; } @@ -48,8 +42,7 @@ public class SimplePostDtoAsync : EfGenericDtoAsync [MinLength(2), MaxLength(128)] public string Title { get; set; } - [ScaffoldColumn(false)] - public ICollection Tags { get; set; } + public List TagNamesList { get; set; } = new List(); [ScaffoldColumn(false)] public DateTime LastUpdated { get; set; } @@ -59,14 +52,6 @@ public class SimplePostDtoAsync : EfGenericDtoAsync /// public DateTime LastUpdatedUtc { get { return DateTime.SpecifyKind(LastUpdated, DateTimeKind.Utc); } } - public string TagNames { get { return string.Join(", ", Tags.Select(x => x.Name)); } } - - //---------------------------------------------- - //overridden properties or methods - - protected override CrudFunctions SupportedFunctions - { - get { return CrudFunctions.List; } - } + public string TagNames { get { return string.Join(", ", TagNamesList); } } } } diff --git a/ServiceLayer/Properties/AssemblyInfo.cs b/ServiceLayer/Properties/AssemblyInfo.cs deleted file mode 100644 index 2793a90..0000000 --- a/ServiceLayer/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,62 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: AssemblyInfo.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceLayer")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ServiceLayer")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("35097f07-8bb1-4da4-ab04-7b4ac4894e18")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ServiceLayer/ServiceLayer.csproj b/ServiceLayer/ServiceLayer.csproj index 09244c2..a00358a 100644 --- a/ServiceLayer/ServiceLayer.csproj +++ b/ServiceLayer/ServiceLayer.csproj @@ -1,136 +1,19 @@ - - - + + - Debug - AnyCPU - {D2813927-0F38-43C3-B47C-AE8F00D50CAE} - Library - Properties + net8.0 ServiceLayer ServiceLayer - v4.5.1 - 512 + disable + disable - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - bin\ReleaseAzure\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\AzureRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\WebWizRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - False - ..\packages\Autofac.3.5.0\lib\net40\Autofac.dll - - - ..\packages\AutoMapper.4.2.1\lib\net45\AutoMapper.dll - True - - - ..\packages\DelegateDecompiler.0.18.0\lib\net40-Client\DelegateDecompiler.dll - True - - - ..\packages\DelegateDecompiler.EntityFramework.0.18.0\lib\net45\DelegateDecompiler.EntityFramework.dll - True - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - True - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - True - - - ..\packages\GenericLibsBase.1.0.1\lib\GenericLibsBase.dll - True - - - ..\packages\GenericServices.1.0.9\lib\GenericServices.dll - True - - - ..\packages\Mono.Reflection.1.0.0.0\lib\Mono.Reflection.dll - - - - - - - - - - - - - - - - - - - - - - - + - - + - + - - {264e1878-12de-4099-b8d7-cc53a73fea49} - DataLayer - + - - - \ No newline at end of file + + diff --git a/ServiceLayer/Startup/ServiceLayerInitialise.cs b/ServiceLayer/Startup/ServiceLayerInitialise.cs index 5fe79ef..58fd605 100644 --- a/ServiceLayer/Startup/ServiceLayerInitialise.cs +++ b/ServiceLayer/Startup/ServiceLayerInitialise.cs @@ -1,8 +1,8 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: ServiceLayerInitialise.cs -// Date Created: 2014/05/20 +// Date Created: 2014/06/09 // // Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) // @@ -24,32 +24,16 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #endregion -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using DataLayer.Startup; - +using Microsoft.Extensions.Logging; namespace ServiceLayer.Startup { - /// - /// This handles the initialisation of this layer and any other layers - /// public static class ServiceLayerInitialise { - /// - /// This should be called at Startup - /// - /// true if working with azure database - /// true if the database provider allows the app to drop/create a database - public static void InitialiseThis(bool isAzure, bool canCreateDatabase) + public static void InitialiseThis(ILoggerFactory loggerFactory = null) { - - //Place any tasks that need initialising here - - DataLayerInitialise.InitialiseThis(isAzure, canCreateDatabase); - + DataLayerInitialise.InitialiseThis(loggerFactory); } } } diff --git a/ServiceLayer/Startup/ServiceLayerModule.cs b/ServiceLayer/Startup/ServiceLayerModule.cs deleted file mode 100644 index 39b40ef..0000000 --- a/ServiceLayer/Startup/ServiceLayerModule.cs +++ /dev/null @@ -1,57 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: ServiceLayerModule.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion - -using Autofac; -using DataLayer.Startup; -using GenericServices; - -namespace ServiceLayer.Startup -{ - public class ServiceLayerModule : Module - { - - /// - /// This registers all items in service layer and below - /// - /// - protected override void Load(ContainerBuilder builder) - { - - //Now register the DataLayer - builder.RegisterModule(new DataLayerModule()); - - //--------------------------- - //Register service layer: autowire all - builder.RegisterAssemblyTypes(GetType().Assembly).AsImplementedInterfaces(); - - //and register the GenericServices assembly - builder.RegisterAssemblyTypes(typeof(IListService).Assembly).AsImplementedInterfaces(); - - } - - } -} diff --git a/ServiceLayer/Startup/ServiceLayerServiceCollectionExtensions.cs b/ServiceLayer/Startup/ServiceLayerServiceCollectionExtensions.cs new file mode 100644 index 0000000..1640754 --- /dev/null +++ b/ServiceLayer/Startup/ServiceLayerServiceCollectionExtensions.cs @@ -0,0 +1,16 @@ +using DataLayer.Startup; +using Microsoft.Extensions.DependencyInjection; + +namespace ServiceLayer.Startup +{ + public static class ServiceLayerServiceCollectionExtensions + { + public static IServiceCollection AddServiceLayer(this IServiceCollection services, string connectionString) + { + // Register data layer services + services.AddDataLayer(connectionString); + + return services; + } + } +} diff --git a/ServiceLayer/TagServices/TagListDto.cs b/ServiceLayer/TagServices/TagListDto.cs index efd0496..5d12491 100644 --- a/ServiceLayer/TagServices/TagListDto.cs +++ b/ServiceLayer/TagServices/TagListDto.cs @@ -1,4 +1,4 @@ -#region licence +#region licence // The MIT License (MIT) // // Filename: TagListDto.cs @@ -24,19 +24,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #endregion - using System.ComponentModel.DataAnnotations; -using System.Runtime.CompilerServices; -using DataLayer.DataClasses.Concrete; -using GenericServices.Core; - -[assembly: InternalsVisibleTo("Tests")] namespace ServiceLayer.TagServices { - public class TagListDto : EfGenericDto + public class TagListDto { - [UIHint("HiddenInput")] [Key] public int TagId { get; set; } @@ -45,14 +38,6 @@ public class TagListDto : EfGenericDto public string Slug { get; set; } - public int PostsCount { get; set; } //uses AutoMapper Aggregate - - //---------------------------------------------- - //overridden properties or methods - - protected override CrudFunctions SupportedFunctions - { - get { return CrudFunctions.List; } - } + public int PostsCount { get; set; } } } diff --git a/ServiceLayer/packages.config b/ServiceLayer/packages.config deleted file mode 100644 index 9c06c52..0000000 --- a/ServiceLayer/packages.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/Tests/App.config b/Tests/App.config deleted file mode 100644 index 875486b..0000000 --- a/Tests/App.config +++ /dev/null @@ -1,76 +0,0 @@ - - - - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jonsmith_ - - - - \ No newline at end of file diff --git a/Tests/Properties/AssemblyInfo.cs b/Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index b722a8d..0000000 --- a/Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,62 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: AssemblyInfo.cs -// Date Created: 2014/05/20 -// -// Copyright (c) 2014 Jon Smith (www.selectiveanalytics.com & www.thereformedprogrammer.net) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -#endregion -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Tests")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("edff866f-292e-46db-9cdf-74c70d23322d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/packages.config b/Tests/packages.config deleted file mode 100644 index 1638b50..0000000 --- a/Tests/packages.config +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 159eee23102233e09a583ed4c27837cceeaa9069 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:44:16 +0000 Subject: [PATCH 2/2] Fix XML parsing in LoadDbDataFromXml: use Element() instead of Attribute() The XML seed data files use child elements (, , etc.) not attributes. This caused a NullReferenceException at startup during database seeding. Co-Authored-By: Achal Channarasappa --- .../Startup/Internal/LoadDbDataFromXml.cs | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/DataLayer/Startup/Internal/LoadDbDataFromXml.cs b/DataLayer/Startup/Internal/LoadDbDataFromXml.cs index b7706d8..49d97e7 100644 --- a/DataLayer/Startup/Internal/LoadDbDataFromXml.cs +++ b/DataLayer/Startup/Internal/LoadDbDataFromXml.cs @@ -57,11 +57,11 @@ public static IEnumerable FormBlogsWithPosts(string filepathWithinAssembly private static Dictionary DecodeTags(XElement element) { return element.Elements("Tag").ToDictionary( - el => el.Attribute("Slug").Value, + el => el.Element("Slug").Value, el => new Tag { - Slug = el.Attribute("Slug").Value, - Name = el.Attribute("Name").Value + Slug = el.Element("Slug").Value, + Name = el.Element("Name").Value }); } @@ -70,8 +70,8 @@ private static IEnumerable DecodeBlogs(XElement element, Dictionary new Blog { - Name = blogXml.Attribute("Name").Value, - EmailAddress = blogXml.Attribute("Email").Value, + Name = blogXml.Element("Name").Value, + EmailAddress = blogXml.Element("Email").Value, Posts = DecodePosts(blogXml.Element("Posts"), tagsDict).ToList() }); } @@ -79,14 +79,19 @@ private static IEnumerable DecodeBlogs(XElement element, Dictionary DecodePosts(XElement element, Dictionary tagsDict) { return element.Elements("Post").Select( - postXml => new Post + postXml => { - Title = postXml.Attribute("Title").Value, - Content = postXml.Value, - Tags = postXml.Attribute("TagSlugs").Value.Split(',') - .Select(x => x.Trim()) - .Where(x => !string.IsNullOrEmpty(x)) - .Select(x => tagsDict[x]).ToList() + var content = postXml.Element("Content").Value; + var trimmedContent = string.Join("\n", content.Split('\n').Select(x => x.Trim())); + return new Post + { + Title = postXml.Element("Title").Value, + Content = trimmedContent, + Tags = postXml.Element("TagSlugs").Value.Split(',') + .Select(x => x.Trim()) + .Where(x => !string.IsNullOrEmpty(x)) + .Select(x => tagsDict[x]).ToList() + }; }); } }