diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f0dff49 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [ master, main ] + pull_request: + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET 8 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore + run: dotnet restore SampleWebApp.sln + + - name: Build + run: dotnet build SampleWebApp.sln --configuration Release --no-restore + + - name: Test + run: dotnet test SampleWebApp.sln --configuration Release --no-build --verbosity normal diff --git a/.gitignore b/.gitignore index f863a14..c8dccab 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,8 @@ SampleWebApp/Properties/PublishProfiles/samplemvcwebapp - Web Deploy.pubxml SampleWebApp/Properties/PublishProfiles/samplemvcwebapp.net - WebWiz.pubxml git/.gitattributes git/.gitignore + +# Local SQLite demo database +*.db +*.db-shm +*.db-wal 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..8338e7d 100644 --- a/BizLayer/BizLayer.csproj +++ b/BizLayer/BizLayer.csproj @@ -1,87 +1,16 @@ - - - + + - Debug - AnyCPU - {19592A8F-7C58-4221-8124-D38ACD8F5E31} - Library - Properties + net8.0 + disable + disable BizLayer BizLayer - v4.5.1 - 512 - - 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/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/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..88ac963 100644 --- a/DataLayer/DataClasses/SampleWebAppDb.cs +++ b/DataLayer/DataClasses/SampleWebAppDb.cs @@ -1,134 +1,102 @@ -#region licence -// The MIT License (MIT) -// -// Filename: SampleWebAppDb.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.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; +using Microsoft.EntityFrameworkCore; [assembly: InternalsVisibleTo("Tests")] namespace DataLayer.DataClasses { - - public class SampleWebAppDb : DbContext, IGenericServicesDbContext + /// + /// EF Core replacement for the original EF6 context. It keeps the same public shape + /// (DbSets, SaveChanges override, database-level Tag uniqueness check) so the rest of + /// the application is unchanged. + /// + public class SampleWebAppDb : DbContext, IGenericServicesDbContext, IValidateOnSave { internal const string NameOfConnectionString = "SampleWebAppDb"; + /// + /// Fallback configuration used when the context is created without DI (tests, EF tooling, + /// the parameterless constructor). The web host configures the context through DI instead. + /// + public static Action DefaultOptionsAction { get; set; } + public DbSet Blogs { get; set; } public DbSet Posts { get; set; } public DbSet Tags { get; set; } - public SampleWebAppDb() : base("name=" + NameOfConnectionString) {} + public SampleWebAppDb() + { + } - internal SampleWebAppDb(string connectionString) : base(connectionString) { } + public SampleWebAppDb(DbContextOptions options) : base(options) + { + } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (optionsBuilder.IsConfigured) + return; + + if (DefaultOptionsAction != null) + DefaultOptionsAction(optionsBuilder); + else + optionsBuilder.UseSqlite("Data Source=SampleWebAppDb.db"); + } - /// - /// This has been overridden to handle: - /// a) Updating of modified items (see p194 in DbContext book) - /// - /// public override int SaveChanges() { HandleChangeTracking(); return base.SaveChanges(); } - /// - /// Same for async - /// - /// - public override Task SaveChangesAsync() + public override Task SaveChangesAsync(CancellationToken cancellationToken = default) { HandleChangeTracking(); - return base.SaveChangesAsync(); + return base.SaveChangesAsync(cancellationToken); } /// - /// This does validations that can only be done at the database level + /// Database-level validation. Replaces the EF6 ValidateEntity override: ensures a + /// Tag's Slug is unique (excluding the Tag being edited). /// - /// - /// - /// - protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, - IDictionary items) + public IEnumerable ValidateEntity(object entity) { - - if (entityEntry.Entity is Tag && (entityEntry.State == EntityState.Added || entityEntry.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)) - }); - } - - return base.ValidateEntity(entityEntry, items); + var tagToCheck = entity as Tag; + if (tagToCheck == null) + yield break; + + if (Tags.Any(x => x.TagId != tagToCheck.TagId && x.Slug == tagToCheck.Slug)) + yield return new ValidationResult( + string.Format("The Slug on tag '{0}' must be unique and is already being used.", tagToCheck.Name), + new[] { "Slug" }); } - //-------------------------------------------------- //private helpers /// - /// This handles going through all the entities that have changed and seeing if they need any special handling. + /// Stamps on every added/modified entity that + /// tracks updates. /// private void HandleChangeTracking() { - //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); - //} - - foreach (var entity in ChangeTracker.Entries() - .Where( - e => - e.State == EntityState.Added || e.State == EntityState.Modified)) + 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; + var trackUpdateClass = entry.Entity as TrackUpdate; + if (trackUpdateClass == null) + continue; trackUpdateClass.UpdateTrackingInfo(); } } - } } diff --git a/DataLayer/DataLayer.csproj b/DataLayer/DataLayer.csproj index 01771cf..f6f040f 100644 --- a/DataLayer/DataLayer.csproj +++ b/DataLayer/DataLayer.csproj @@ -1,135 +1,33 @@ - - - + + - Debug - AnyCPU - {264E1878-12DE-4099-B8D7-CC53A73FEA49} - Library - Properties + net8.0 + disable + disable 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 - - - - 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 - - - - - - - - - - - - - - - - - - - - - - - + - - + + DataLayer.Startup.Internal.BlogsContentSimple.xml + + + DataLayer.Startup.Internal.BlogsContextMedium.xml + + - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - + + - - - \ No newline at end of file + + diff --git a/DataLayer/Migrations/20260714171925_InitialCreate.Designer.cs b/DataLayer/Migrations/20260714171925_InitialCreate.Designer.cs new file mode 100644 index 0000000..8195298 --- /dev/null +++ b/DataLayer/Migrations/20260714171925_InitialCreate.Designer.cs @@ -0,0 +1,141 @@ +// +using System; +using DataLayer.DataClasses; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DataLayer.Migrations +{ + [DbContext(typeof(SampleWebAppDb))] + [Migration("20260714171925_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.11"); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Blog", b => + { + b.Property("BlogId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EmailAddress") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("BlogId"); + + b.ToTable("Blogs"); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Post", b => + { + b.Property("PostId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BlogId") + .HasColumnType("INTEGER"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastUpdated") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("PostId"); + + b.HasIndex("BlogId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Tag", b => + { + b.Property("TagId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("TagId"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("PostTag", b => + { + b.Property("PostsPostId") + .HasColumnType("INTEGER"); + + b.Property("TagsTagId") + .HasColumnType("INTEGER"); + + b.HasKey("PostsPostId", "TagsTagId"); + + b.HasIndex("TagsTagId"); + + b.ToTable("PostTag"); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Post", b => + { + b.HasOne("DataLayer.DataClasses.Concrete.Blog", "Blogger") + .WithMany("Posts") + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Blogger"); + }); + + modelBuilder.Entity("PostTag", b => + { + b.HasOne("DataLayer.DataClasses.Concrete.Post", null) + .WithMany() + .HasForeignKey("PostsPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DataLayer.DataClasses.Concrete.Tag", null) + .WithMany() + .HasForeignKey("TagsTagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Blog", b => + { + b.Navigation("Posts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DataLayer/Migrations/20260714171925_InitialCreate.cs b/DataLayer/Migrations/20260714171925_InitialCreate.cs new file mode 100644 index 0000000..e286b01 --- /dev/null +++ b/DataLayer/Migrations/20260714171925_InitialCreate.cs @@ -0,0 +1,115 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DataLayer.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Blogs", + columns: table => new + { + BlogId = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 64, nullable: false), + EmailAddress = table.Column(type: "TEXT", maxLength: 256, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Blogs", x => x.BlogId); + }); + + migrationBuilder.CreateTable( + name: "Tags", + columns: table => new + { + TagId = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Slug = table.Column(type: "TEXT", maxLength: 64, nullable: false), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Tags", x => x.TagId); + }); + + migrationBuilder.CreateTable( + name: "Posts", + columns: table => new + { + PostId = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Content = table.Column(type: "TEXT", nullable: false), + BlogId = table.Column(type: "INTEGER", nullable: false), + LastUpdated = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Posts", x => x.PostId); + table.ForeignKey( + name: "FK_Posts_Blogs_BlogId", + column: x => x.BlogId, + principalTable: "Blogs", + principalColumn: "BlogId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PostTag", + columns: table => new + { + PostsPostId = table.Column(type: "INTEGER", nullable: false), + TagsTagId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PostTag", x => new { x.PostsPostId, x.TagsTagId }); + table.ForeignKey( + name: "FK_PostTag_Posts_PostsPostId", + column: x => x.PostsPostId, + principalTable: "Posts", + principalColumn: "PostId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PostTag_Tags_TagsTagId", + column: x => x.TagsTagId, + principalTable: "Tags", + principalColumn: "TagId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Posts_BlogId", + table: "Posts", + column: "BlogId"); + + migrationBuilder.CreateIndex( + name: "IX_PostTag_TagsTagId", + table: "PostTag", + column: "TagsTagId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PostTag"); + + migrationBuilder.DropTable( + name: "Posts"); + + migrationBuilder.DropTable( + name: "Tags"); + + migrationBuilder.DropTable( + name: "Blogs"); + } + } +} diff --git a/DataLayer/Migrations/SampleWebAppDbModelSnapshot.cs b/DataLayer/Migrations/SampleWebAppDbModelSnapshot.cs new file mode 100644 index 0000000..b593be0 --- /dev/null +++ b/DataLayer/Migrations/SampleWebAppDbModelSnapshot.cs @@ -0,0 +1,138 @@ +// +using System; +using DataLayer.DataClasses; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DataLayer.Migrations +{ + [DbContext(typeof(SampleWebAppDb))] + partial class SampleWebAppDbModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.11"); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Blog", b => + { + b.Property("BlogId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EmailAddress") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("BlogId"); + + b.ToTable("Blogs"); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Post", b => + { + b.Property("PostId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BlogId") + .HasColumnType("INTEGER"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastUpdated") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("PostId"); + + b.HasIndex("BlogId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Tag", b => + { + b.Property("TagId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("TagId"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("PostTag", b => + { + b.Property("PostsPostId") + .HasColumnType("INTEGER"); + + b.Property("TagsTagId") + .HasColumnType("INTEGER"); + + b.HasKey("PostsPostId", "TagsTagId"); + + b.HasIndex("TagsTagId"); + + b.ToTable("PostTag"); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Post", b => + { + b.HasOne("DataLayer.DataClasses.Concrete.Blog", "Blogger") + .WithMany("Posts") + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Blogger"); + }); + + modelBuilder.Entity("PostTag", b => + { + b.HasOne("DataLayer.DataClasses.Concrete.Post", null) + .WithMany() + .HasForeignKey("PostsPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DataLayer.DataClasses.Concrete.Tag", null) + .WithMany() + .HasForeignKey("TagsTagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DataLayer.DataClasses.Concrete.Blog", b => + { + b.Navigation("Posts"); + }); +#pragma warning restore 612, 618 + } + } +} 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..d657658 100644 --- a/DataLayer/Startup/DataLayerInitialise.cs +++ b/DataLayer/Startup/DataLayerInitialise.cs @@ -1,69 +1,42 @@ -#region licence -// The MIT License (MIT) -// -// Filename: DataLayerInitialise.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.Data.Entity; using System.Linq; using DataLayer.DataClasses; using DataLayer.Startup.Internal; using GenericLibsBase; using GenericServices; +using Microsoft.EntityFrameworkCore; namespace DataLayer.Startup { - public enum TestDataSelection { Small = 0, Medium = 1} + public enum TestDataSelection { Small = 0, Medium = 1 } public static class DataLayerInitialise { - private static IGenericLogger _logger; - private static readonly Dictionary XmlBlogsDataFileManifestPath = new Dictionary + private static readonly Dictionary XmlBlogsDataFileManifestPath = + new Dictionary { - {TestDataSelection.Small, "DataLayer.Startup.Internal.BlogsContentSimple.xml"}, - {TestDataSelection.Medium, "DataLayer.Startup.Internal.BlogsContextMedium.xml"} + { TestDataSelection.Small, "DataLayer.Startup.Internal.BlogsContentSimple.xml" }, + { TestDataSelection.Medium, "DataLayer.Startup.Internal.BlogsContextMedium.xml" } }; /// - /// This should be called at Startup + /// Called at startup. Applies EF Core migrations to create/update the schema and, when the + /// database has no blogs yet, seeds the small data set so the demo has content to show. /// - /// 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 InitialiseThis(SampleWebAppDb context, bool canCreateDatabase) { - 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 (!canCreateDatabase) + return; + + context.Database.Migrate(); + + if (!context.Blogs.Any()) + ResetBlogs(context, TestDataSelection.Small); } public static void ResetBlogs(SampleWebAppDb context, TestDataSelection selection) @@ -77,7 +50,8 @@ public static void ResetBlogs(SampleWebAppDb context, TestDataSelection selectio } catch (Exception ex) { - _logger.Critical("Exception when resetting the blogs", ex); + if (_logger != null) + _logger.Critical("Exception when resetting the blogs", ex); throw; } @@ -87,12 +61,11 @@ public static void ResetBlogs(SampleWebAppDb context, TestDataSelection selectio var status = context.SaveChangesWithChecking(); if (!status.IsValid) { - _logger.CriticalFormat("Error when resetting courses data. Error:\n{0}", - string.Join(",", status.Errors)); + if (_logger != null) + _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/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/GenericLibsBase/Core/ISuccessOrErrors.cs b/GenericLibsBase/Core/ISuccessOrErrors.cs new file mode 100644 index 0000000..02955a4 --- /dev/null +++ b/GenericLibsBase/Core/ISuccessOrErrors.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace GenericLibsBase.Core +{ + /// + /// Carries the success/error outcome of an operation, including a list of + /// errors (each possibly associated with member names) + /// and an optional success message. + /// + public interface ISuccessOrErrors + { + /// + /// True if there are no errors. (A status must have been given a success message + /// or had errors added before this is meaningful.) + /// + bool IsValid { get; } + + /// The validation errors, if any. + IReadOnlyList Errors { get; } + + /// Optional non-error warnings. + IReadOnlyList Warnings { get; } + + bool HasWarnings { get; } + + /// The success message set via . + string SuccessMessage { get; } + + /// Adds a global error (no member name). + ISuccessOrErrors AddSingleError(string errorformat, params object[] args); + + /// Adds an error associated with the named property/parameter. + ISuccessOrErrors AddNamedParameterError(string parameterName, string errorformat, params object[] args); + + /// Adds a warning message. + ISuccessOrErrors AddWarning(string warningformat, params object[] args); + + /// Copies the errors and warnings from another status into this one. + ISuccessOrErrors Combine(ISuccessOrErrors objToCopyErrorsFrom); + + /// Sets the success message. Only meaningful when there are no errors. + ISuccessOrErrors SetSuccessMessage(string successformat, params object[] args); + + /// Returns the errors formatted as an HTML unordered list. + string ErrorsAsHtml(); + } +} diff --git a/GenericLibsBase/Core/ISuccessOrErrorsGeneric.cs b/GenericLibsBase/Core/ISuccessOrErrorsGeneric.cs new file mode 100644 index 0000000..82c1f88 --- /dev/null +++ b/GenericLibsBase/Core/ISuccessOrErrorsGeneric.cs @@ -0,0 +1,14 @@ +namespace GenericLibsBase.Core +{ + /// + /// A success/error status that also carries a typed result. + /// + public interface ISuccessOrErrors : ISuccessOrErrors + { + /// The result value (only meaningful when is true). + T Result { get; } + + /// Sets the result and a success message. + ISuccessOrErrors SetSuccessWithResult(T result, string successformat, params object[] args); + } +} diff --git a/GenericLibsBase/Core/SuccessOrErrors.cs b/GenericLibsBase/Core/SuccessOrErrors.cs new file mode 100644 index 0000000..4bc2aa3 --- /dev/null +++ b/GenericLibsBase/Core/SuccessOrErrors.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace GenericLibsBase.Core +{ + /// + /// Default implementation of . + /// + public class SuccessOrErrors : ISuccessOrErrors + { + private readonly List _errors = new List(); + private readonly List _warnings = new List(); + private string _successMessage = string.Empty; + + private static string Format(string format, object[] args) + { + //When there are no args the string is used verbatim, so messages containing + //braces (e.g. validation messages) are never re-parsed by string.Format. + return args == null || args.Length == 0 ? format : string.Format(format, args); + } + + public bool IsValid + { + get { return _errors.Count == 0; } + } + + public IReadOnlyList Errors + { + get { return _errors; } + } + + public IReadOnlyList Warnings + { + get { return _warnings; } + } + + public bool HasWarnings + { + get { return _warnings.Count > 0; } + } + + public string SuccessMessage + { + get { return IsValid ? _successMessage : string.Empty; } + } + + public ISuccessOrErrors AddSingleError(string errorformat, params object[] args) + { + _errors.Add(new ValidationResult(Format(errorformat, args))); + return this; + } + + public ISuccessOrErrors AddNamedParameterError(string parameterName, string errorformat, params object[] args) + { + _errors.Add(new ValidationResult(Format(errorformat, args), new[] { parameterName })); + return this; + } + + public ISuccessOrErrors AddWarning(string warningformat, params object[] args) + { + _warnings.Add("Warning: " + Format(warningformat, args)); + return this; + } + + public ISuccessOrErrors Combine(ISuccessOrErrors objToCopyErrorsFrom) + { + if (objToCopyErrorsFrom == null) return this; + _errors.AddRange(objToCopyErrorsFrom.Errors); + _warnings.AddRange(objToCopyErrorsFrom.Warnings); + return this; + } + + public ISuccessOrErrors SetSuccessMessage(string successformat, params object[] args) + { + _successMessage = Format(successformat, args); + return this; + } + + public string ErrorsAsHtml() + { + if (IsValid) return string.Empty; + var lines = _errors.Select(x => "
  • " + System.Net.WebUtility.HtmlEncode(x.ErrorMessage) + "
  • "); + return "
      " + string.Join(string.Empty, lines) + "
    "; + } + + public override string ToString() + { + return IsValid + ? (string.IsNullOrEmpty(_successMessage) ? "Successful" : _successMessage) + : string.Format("Failed with {0} error(s)", _errors.Count); + } + + /// Creates a successful status with the given message. + public static ISuccessOrErrors Success(string successformat, params object[] args) + { + return new SuccessOrErrors().SetSuccessMessage(successformat, args); + } + + /// Creates a status carrying a single global error. + public static ISuccessOrErrors SingleError(string errorformat, params object[] args) + { + return new SuccessOrErrors().AddSingleError(errorformat, args); + } + } +} diff --git a/GenericLibsBase/Core/SuccessOrErrorsGeneric.cs b/GenericLibsBase/Core/SuccessOrErrorsGeneric.cs new file mode 100644 index 0000000..1de1ee6 --- /dev/null +++ b/GenericLibsBase/Core/SuccessOrErrorsGeneric.cs @@ -0,0 +1,28 @@ +namespace GenericLibsBase.Core +{ + /// + /// Default implementation of . + /// + public class SuccessOrErrors : SuccessOrErrors, ISuccessOrErrors + { + public T Result { get; private set; } + + public ISuccessOrErrors SetSuccessWithResult(T result, string successformat, params object[] args) + { + Result = result; + SetSuccessMessage(successformat, args); + return this; + } + + /// + /// Builds an errored from a non-generic status. + /// The result stays at its default value; errors/warnings are copied over. + /// + public static ISuccessOrErrors ConvertNonResultStatus(ISuccessOrErrors status) + { + var result = new SuccessOrErrors(); + result.Combine(status); + return result; + } + } +} diff --git a/GenericLibsBase/GenericLibsBase.csproj b/GenericLibsBase/GenericLibsBase.csproj new file mode 100644 index 0000000..d3ba5d3 --- /dev/null +++ b/GenericLibsBase/GenericLibsBase.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + disable + disable + GenericLibsBase + GenericLibsBase + + + diff --git a/GenericLibsBase/GenericLibsBaseConfig.cs b/GenericLibsBase/GenericLibsBaseConfig.cs new file mode 100644 index 0000000..51e1e88 --- /dev/null +++ b/GenericLibsBase/GenericLibsBaseConfig.cs @@ -0,0 +1,32 @@ +using System; + +namespace GenericLibsBase +{ + /// + /// Global configuration point for the logging abstraction. The host assigns + /// at startup; callers obtain loggers via . + /// + public static class GenericLibsBaseConfig + { + private static Func _getLoggerMethod = name => new NoLoggingGenericLogger(); + + /// + /// Set this at startup to control how loggers are created. + /// + public static Func SetLoggerMethod + { + private get { return _getLoggerMethod; } + set { _getLoggerMethod = value ?? (name => new NoLoggingGenericLogger()); } + } + + public static IGenericLogger GetLogger(string name) + { + return _getLoggerMethod(name); + } + + public static IGenericLogger GetLogger(Type typeForName) + { + return _getLoggerMethod(typeForName.Name); + } + } +} diff --git a/GenericLibsBase/IGenericLogger.cs b/GenericLibsBase/IGenericLogger.cs new file mode 100644 index 0000000..62148d5 --- /dev/null +++ b/GenericLibsBase/IGenericLogger.cs @@ -0,0 +1,27 @@ +using System; + +namespace GenericLibsBase +{ + /// + /// Logging abstraction used across the layers. Implementations bridge to a + /// concrete logging framework (originally log4net; now Microsoft.Extensions.Logging). + /// + public interface IGenericLogger + { + void Verbose(object message); + void VerboseFormat(string format, params object[] args); + + void Info(object message); + void InfoFormat(string format, params object[] args); + + void Warn(object message); + void WarnFormat(string format, params object[] args); + + void Error(object message); + void ErrorFormat(string format, params object[] args); + + void Critical(object message); + void Critical(object message, Exception ex); + void CriticalFormat(string format, params object[] args); + } +} diff --git a/GenericLibsBase/NoLoggingGenericLogger.cs b/GenericLibsBase/NoLoggingGenericLogger.cs new file mode 100644 index 0000000..4d11067 --- /dev/null +++ b/GenericLibsBase/NoLoggingGenericLogger.cs @@ -0,0 +1,22 @@ +using System; + +namespace GenericLibsBase +{ + /// + /// Default no-op logger used until the host assigns a real logger factory. + /// + public class NoLoggingGenericLogger : IGenericLogger + { + public void Verbose(object message) { } + public void VerboseFormat(string format, params object[] args) { } + public void Info(object message) { } + public void InfoFormat(string format, params object[] args) { } + public void Warn(object message) { } + public void WarnFormat(string format, params object[] args) { } + public void Error(object message) { } + public void ErrorFormat(string format, params object[] args) { } + public void Critical(object message) { } + public void Critical(object message, Exception ex) { } + public void CriticalFormat(string format, params object[] args) { } + } +} diff --git a/GenericServices/Core/EfGenericDto.cs b/GenericServices/Core/EfGenericDto.cs new file mode 100644 index 0000000..cfa3e4a --- /dev/null +++ b/GenericServices/Core/EfGenericDto.cs @@ -0,0 +1,81 @@ +using System; +using GenericLibsBase.Core; + +namespace GenericServices.Core +{ + /// + /// Base class for a synchronous DTO mapped to . + /// Concrete DTOs override the hooks to shape data and add business logic. + /// + public abstract class EfGenericDto : EfGenericDtoBase, ISyncInternalDtoCrud + where TEntity : class + where TDto : EfGenericDto + { + /// + /// Override to fill in any secondary data (dropdown lists etc.) needed by the view. + /// + protected internal virtual void SetupSecondaryData(IGenericServicesDbContext context, TDto dto) + { + } + + /// + /// Builds a new entity from this DTO. Default behaviour maps the DTO onto a new entity. + /// + protected internal virtual ISuccessOrErrors CreateDataFromDto(IGenericServicesDbContext context, TDto source) + { + var entity = System.Activator.CreateInstance(); + EntityFromDto.Copy(source, entity); + return new SuccessOrErrors().SetSuccessWithResult(entity, string.Empty); + } + + /// + /// Copies this DTO onto an existing entity. Default behaviour maps the DTO onto the entity. + /// + protected internal virtual ISuccessOrErrors UpdateDataFromDto(IGenericServicesDbContext context, TDto source, TEntity destination) + { + EntityFromDto.Copy(source, destination); + return SuccessOrErrors.Success(string.Empty); + } + + //-------------------------------------------------- + //non-generic entry points used by the service layer + + Type IInternalDtoCrud.AssociatedEntityType + { + get { return EntityTypeInternal; } + } + + object[] IInternalDtoCrud.GetKeyValues() + { + return GetKeyValuesInternal(); + } + + CrudFunctions IInternalDtoCrud.GetSupportedFunctions() + { + return GetSupportedFunctionsInternal(); + } + + void ISyncInternalDtoCrud.RunSetupSecondaryData(IGenericServicesDbContext context) + { + SetupSecondaryData(context, Self); + } + + ISuccessOrErrors ISyncInternalDtoCrud.RunCreateDataFromDto(IGenericServicesDbContext context) + { + var status = CreateDataFromDto(context, Self); + if (status.IsValid) + context.Set().Add(status.Result); + return status; + } + + ISuccessOrErrors ISyncInternalDtoCrud.RunUpdateDataFromDto(IGenericServicesDbContext context, object destinationEntity) + { + return UpdateDataFromDto(context, Self, (TEntity)destinationEntity); + } + + private TDto Self + { + get { return (TDto)(object)this; } + } + } +} diff --git a/GenericServices/Core/EfGenericDtoAsync.cs b/GenericServices/Core/EfGenericDtoAsync.cs new file mode 100644 index 0000000..55a9c0a --- /dev/null +++ b/GenericServices/Core/EfGenericDtoAsync.cs @@ -0,0 +1,74 @@ +using System; +using System.Threading.Tasks; +using GenericLibsBase.Core; + +namespace GenericServices.Core +{ + /// + /// Base class for an asynchronous DTO mapped to . + /// + public abstract class EfGenericDtoAsync : EfGenericDtoBase, IAsyncInternalDtoCrud + where TEntity : class + where TDto : EfGenericDtoAsync + { + protected internal virtual Task SetupSecondaryDataAsync(IGenericServicesDbContext context, TDto dto) + { + return Task.CompletedTask; + } + + protected internal virtual Task> CreateDataFromDtoAsync(IGenericServicesDbContext context, TDto source) + { + var entity = System.Activator.CreateInstance(); + EntityFromDto.Copy(source, entity); + ISuccessOrErrors status = new SuccessOrErrors().SetSuccessWithResult(entity, string.Empty); + return Task.FromResult(status); + } + + protected internal virtual Task UpdateDataFromDtoAsync(IGenericServicesDbContext context, TDto source, TEntity destination) + { + EntityFromDto.Copy(source, destination); + return Task.FromResult(SuccessOrErrors.Success(string.Empty)); + } + + //-------------------------------------------------- + //non-generic entry points used by the service layer + + Type IInternalDtoCrud.AssociatedEntityType + { + get { return EntityTypeInternal; } + } + + object[] IInternalDtoCrud.GetKeyValues() + { + return GetKeyValuesInternal(); + } + + CrudFunctions IInternalDtoCrud.GetSupportedFunctions() + { + return GetSupportedFunctionsInternal(); + } + + async Task IAsyncInternalDtoCrud.RunSetupSecondaryDataAsync(IGenericServicesDbContext context) + { + await SetupSecondaryDataAsync(context, Self).ConfigureAwait(false); + } + + async Task IAsyncInternalDtoCrud.RunCreateDataFromDtoAsync(IGenericServicesDbContext context) + { + var status = await CreateDataFromDtoAsync(context, Self).ConfigureAwait(false); + if (status.IsValid) + context.Set().Add(status.Result); + return status; + } + + async Task IAsyncInternalDtoCrud.RunUpdateDataFromDtoAsync(IGenericServicesDbContext context, object destinationEntity) + { + return await UpdateDataFromDtoAsync(context, Self, (TEntity)destinationEntity).ConfigureAwait(false); + } + + private TDto Self + { + get { return (TDto)(object)this; } + } + } +} diff --git a/GenericServices/Core/EfGenericDtoBase.cs b/GenericServices/Core/EfGenericDtoBase.cs new file mode 100644 index 0000000..d19c0e9 --- /dev/null +++ b/GenericServices/Core/EfGenericDtoBase.cs @@ -0,0 +1,53 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; + +namespace GenericServices.Core +{ + /// + /// Non-generic root of the DTO hierarchy. Lets non-generic code (the service layer, + /// the mapper configuration) recognise DTOs and read their metadata. + /// + public abstract class EfGenericDtoBase + { + /// + /// Which CRUD functions this DTO supports. Override in the concrete DTO. + /// + protected internal virtual CrudFunctions SupportedFunctions + { + get { return CrudFunctions.None; } + } + + internal CrudFunctions GetSupportedFunctionsInternal() + { + return SupportedFunctions; + } + + /// + /// Reads the values of the DTO properties marked with , + /// used to locate the matching data entity for update. + /// + internal object[] GetKeyValuesInternal() + { + var keyProps = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.IsDefined(typeof(KeyAttribute), true)) + .ToArray(); + return keyProps.Select(p => p.GetValue(this)).ToArray(); + } + } + + /// + /// Generic DTO base carrying the associated entity/DTO types. Shared by the sync and + /// async DTO base classes. + /// + public abstract class EfGenericDtoBase : EfGenericDtoBase + where TEntity : class + where TDto : EfGenericDtoBase + { + internal Type EntityTypeInternal + { + get { return typeof(TEntity); } + } + } +} diff --git a/GenericServices/Core/EntityFromDto.cs b/GenericServices/Core/EntityFromDto.cs new file mode 100644 index 0000000..aa7e42a --- /dev/null +++ b/GenericServices/Core/EntityFromDto.cs @@ -0,0 +1,44 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Reflection; + +namespace GenericServices.Core +{ + /// + /// Copies the writable data from a DTO onto a data entity for create/update. This replaces + /// the AutoMapper "write" map GenericServices used in EF6. Doing the copy by reflection keeps + /// full control over entity navigations: related entities (e.g. the chosen Tags) are assigned + /// by reference so EF Core tracks them, rather than being cloned. + /// Rules: + /// - properties marked [DoNotCopyBackToDatabase] are skipped (data layer owns them), + /// - [Key] properties are skipped (never overwrite the primary key), + /// - only properties that exist and are writable on the entity, with an assignable value, are copied. + /// + internal static class EntityFromDto + { + public static void Copy(object source, object destination) + { + var dtoType = source.GetType(); + var entityType = destination.GetType(); + + foreach (var dtoProp in dtoType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (!dtoProp.CanRead) continue; + if (dtoProp.GetIndexParameters().Length != 0) continue; + if (dtoProp.IsDefined(typeof(DoNotCopyBackToDatabaseAttribute), true)) continue; + if (dtoProp.IsDefined(typeof(KeyAttribute), true)) continue; + + var entityProp = entityType.GetProperty(dtoProp.Name, BindingFlags.Public | BindingFlags.Instance); + if (entityProp == null || !entityProp.CanWrite) continue; + + var value = dtoProp.GetValue(source); + if (value != null && !entityProp.PropertyType.IsInstanceOfType(value)) continue; + if (value == null && entityProp.PropertyType.IsValueType && + Nullable.GetUnderlyingType(entityProp.PropertyType) == null) + continue; + + entityProp.SetValue(destination, value); + } + } + } +} diff --git a/GenericServices/Core/GenericValidation.cs b/GenericServices/Core/GenericValidation.cs new file mode 100644 index 0000000..6a14ef7 --- /dev/null +++ b/GenericServices/Core/GenericValidation.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using GenericLibsBase.Core; +using Microsoft.EntityFrameworkCore; + +namespace GenericServices.Core +{ + /// + /// Reproduces the validation EF6 used to run automatically on SaveChanges: + /// DataAnnotations + on every Added/Modified entity, + /// plus any database-level checks exposed via . + /// + internal static class GenericValidation + { + public static ISuccessOrErrors Validate(DbContext context) + { + var status = new SuccessOrErrors(); + var customValidator = context as IValidateOnSave; + + var changedEntities = context.ChangeTracker.Entries() + .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified) + .ToList(); + + foreach (var entry in changedEntities) + { + var entity = entry.Entity; + + var validationContext = new ValidationContext(entity, serviceProvider: null, items: null); + var results = new List(); + Validator.TryValidateObject(entity, validationContext, results, validateAllProperties: true); + + if (customValidator != null) + { + var extra = customValidator.ValidateEntity(entity); + if (extra != null) + results.AddRange(extra); + } + + foreach (var result in results) + { + var memberName = result.MemberNames == null ? null : result.MemberNames.FirstOrDefault(); + if (string.IsNullOrEmpty(memberName)) + status.AddSingleError(SafeFormat(result.ErrorMessage)); + else + status.AddNamedParameterError(memberName, SafeFormat(result.ErrorMessage)); + } + } + + return status.IsValid ? status.SetSuccessMessage("Successfully validated.") : status; + } + + //Error messages may legitimately contain braces, so avoid string.Format re-parsing them. + private static string SafeFormat(string message) + { + return message ?? string.Empty; + } + } +} diff --git a/GenericServices/Core/IInternalDtoCrud.cs b/GenericServices/Core/IInternalDtoCrud.cs new file mode 100644 index 0000000..ba6b020 --- /dev/null +++ b/GenericServices/Core/IInternalDtoCrud.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; +using GenericLibsBase.Core; + +namespace GenericServices.Core +{ + /// + /// Non-generic entry points the service layer uses to drive a DTO without knowing its + /// concrete generic type. Implemented internally by the DTO base classes. + /// + internal interface IInternalDtoCrud + { + Type AssociatedEntityType { get; } + object[] GetKeyValues(); + CrudFunctions GetSupportedFunctions(); + } + + internal interface ISyncInternalDtoCrud : IInternalDtoCrud + { + void RunSetupSecondaryData(IGenericServicesDbContext context); + ISuccessOrErrors RunCreateDataFromDto(IGenericServicesDbContext context); + ISuccessOrErrors RunUpdateDataFromDto(IGenericServicesDbContext context, object destinationEntity); + } + + internal interface IAsyncInternalDtoCrud : IInternalDtoCrud + { + Task RunSetupSecondaryDataAsync(IGenericServicesDbContext context); + Task RunCreateDataFromDtoAsync(IGenericServicesDbContext context); + Task RunUpdateDataFromDtoAsync(IGenericServicesDbContext context, object destinationEntity); + } +} diff --git a/GenericServices/CrudFunctions.cs b/GenericServices/CrudFunctions.cs new file mode 100644 index 0000000..2f94188 --- /dev/null +++ b/GenericServices/CrudFunctions.cs @@ -0,0 +1,19 @@ +using System; + +namespace GenericServices.Core +{ + /// + /// Flags describing which CRUD operations a DTO supports. + /// + [Flags] + public enum CrudFunctions + { + None = 0, + List = 1, + Detail = 2, + Create = 4, + Update = 8, + Delete = 16, + AllCrud = List | Detail | Create | Update | Delete + } +} diff --git a/GenericServices/DoNotCopyBackToDatabaseAttribute.cs b/GenericServices/DoNotCopyBackToDatabaseAttribute.cs new file mode 100644 index 0000000..2438f51 --- /dev/null +++ b/GenericServices/DoNotCopyBackToDatabaseAttribute.cs @@ -0,0 +1,13 @@ +using System; + +namespace GenericServices +{ + /// + /// Marks a DTO property that must NOT be copied back onto the data entity when + /// creating/updating (e.g. values the data layer maintains itself). + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + public sealed class DoNotCopyBackToDatabaseAttribute : Attribute + { + } +} diff --git a/GenericServices/GenericServices.csproj b/GenericServices/GenericServices.csproj new file mode 100644 index 0000000..69f8a45 --- /dev/null +++ b/GenericServices/GenericServices.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + disable + disable + GenericServices + GenericServices + + + + + + + + + + + + + + + + + + + diff --git a/GenericServices/GenericServicesConfig.cs b/GenericServices/GenericServicesConfig.cs new file mode 100644 index 0000000..4e3fc86 --- /dev/null +++ b/GenericServices/GenericServicesConfig.cs @@ -0,0 +1,68 @@ +using System.Linq; +using System.Reflection; +using AutoMapper; +using GenericServices.Internal; +using GenericServices.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace GenericServices +{ + /// + /// Configuration entry point for the GenericServices EF Core compatibility layer. + /// Builds the AutoMapper configuration from the DTOs and registers the service + /// implementations in the ASP.NET Core DI container. + /// + public static class GenericServicesConfig + { + /// + /// The AutoMapper instance used by the DTO base classes for in-memory mapping. + /// Set by / . + /// + public static IMapper Mapper { get; set; } + + /// + /// Scans the given assemblies for DTOs, builds the AutoMapper configuration and + /// stores it in . + /// + public static IMapper BuildMapper(params Assembly[] dtoAssemblies) + { + var dtoTypes = dtoAssemblies + .SelectMany(a => a.GetTypes()) + .Where(t => !t.IsAbstract && DtoReflection.IsDto(t)) + .ToList(); + + var config = new MapperConfiguration(cfg => DtoMappingConfig.AddMappings(cfg, dtoTypes)); + var mapper = config.CreateMapper(); + Mapper = mapper; + return mapper; + } + + /// + /// Registers the AutoMapper instance and all GenericServices service implementations. + /// The application must separately register its DbContext as + /// (scoped). + /// + public static IServiceCollection AddGenericServices(this IServiceCollection services, params Assembly[] dtoAssemblies) + { + var mapper = BuildMapper(dtoAssemblies); + services.AddSingleton(mapper); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + return services; + } + } +} diff --git a/GenericServices/IGenericServicesDbContext.cs b/GenericServices/IGenericServicesDbContext.cs new file mode 100644 index 0000000..9299a1c --- /dev/null +++ b/GenericServices/IGenericServicesDbContext.cs @@ -0,0 +1,25 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; + +namespace GenericServices +{ + /// + /// Abstraction over the EF Core used by the service layer and DTOs. + /// The signatures deliberately match so a context can implement this + /// interface without any extra code. + /// + public interface IGenericServicesDbContext + { + DbSet Set() where TEntity : class; + + EntityEntry Entry(TEntity entity) where TEntity : class; + + EntityEntry Entry(object entity); + + int SaveChanges(); + + Task SaveChangesAsync(CancellationToken cancellationToken = default); + } +} diff --git a/GenericServices/IValidateOnSave.cs b/GenericServices/IValidateOnSave.cs new file mode 100644 index 0000000..2acf3c7 --- /dev/null +++ b/GenericServices/IValidateOnSave.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace GenericServices +{ + /// + /// Implemented by a DbContext that needs to run extra, database-level validation on + /// entities being added/modified (e.g. cross-row uniqueness checks). This replaces the + /// EF6 DbContext.ValidateEntity override, which EF Core does not provide. + /// + public interface IValidateOnSave + { + /// + /// Returns any database-level validation errors for the given entity, or an empty + /// sequence if it is valid. + /// + IEnumerable ValidateEntity(object entity); + } +} diff --git a/GenericServices/Internal/DtoMappingConfig.cs b/GenericServices/Internal/DtoMappingConfig.cs new file mode 100644 index 0000000..e900c34 --- /dev/null +++ b/GenericServices/Internal/DtoMappingConfig.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using AutoMapper; + +namespace GenericServices.Internal +{ + /// + /// Builds the AutoMapper read maps used to project entities onto DTOs (entity -> DTO), + /// relying on AutoMapper flattening for members like BloggerName / PostsCount. + /// Writing DTOs back onto entities is handled by , not + /// AutoMapper, so no DTO -> entity maps are configured here. + /// + internal static class DtoMappingConfig + { + public static void AddMappings(IMapperConfigurationExpression cfg, IEnumerable dtoTypes) + { + var entityMemberTypes = new HashSet(); + + foreach (var dtoType in dtoTypes) + { + var pair = DtoReflection.GetEntityAndDtoTypes(dtoType); + if (pair == null) continue; + + var entityType = pair.Item1; + BuildReadMap(cfg, entityType, dtoType, entityMemberTypes); + } + + //DTOs sometimes expose the entity type directly (e.g. ICollection Tags). Add a + //self-map for those types that copies only scalar members, so projection works and + //we never recurse through their navigations. + foreach (var entityMemberType in entityMemberTypes) + BuildScalarSelfMap(cfg, entityMemberType); + } + + private static void BuildReadMap(IMapperConfigurationExpression cfg, Type entityType, Type dtoType, + HashSet entityMemberTypes) + { + var map = cfg.CreateMap(entityType, dtoType); + + foreach (var prop in PublicProps(dtoType)) + { + //computed (read-only) properties are evaluated in memory, never projected + if (!prop.CanWrite) + { + map.ForMember(prop.Name, o => o.Ignore()); + continue; + } + + //members with no matching source (e.g. UI dropdown lists) are filled elsewhere + if (!DtoReflection.CanResolveSource(entityType, prop.Name)) + { + map.ForMember(prop.Name, o => o.Ignore()); + continue; + } + + var memberEntityType = EntityElementType(prop.PropertyType); + if (memberEntityType != null) + entityMemberTypes.Add(memberEntityType); + } + } + + private static void BuildScalarSelfMap(IMapperConfigurationExpression cfg, Type type) + { + var map = cfg.CreateMap(type, type); + foreach (var prop in PublicProps(type)) + { + if (DtoReflection.IsEnumerableButNotString(prop.PropertyType) || IsEntityLike(prop.PropertyType)) + map.ForMember(prop.Name, o => o.Ignore()); + } + } + + /// + /// If the property is an entity or a collection of entities, returns that entity type; + /// otherwise null. + /// + private static Type EntityElementType(Type propertyType) + { + if (DtoReflection.IsEnumerableButNotString(propertyType)) + { + var element = GetEnumerableElementType(propertyType); + return element != null && IsEntityLike(element) ? element : null; + } + + return IsEntityLike(propertyType) ? propertyType : null; + } + + private static Type GetEnumerableElementType(Type type) + { + if (type.IsArray) + return type.GetElementType(); + if (type.IsGenericType) + return type.GetGenericArguments().FirstOrDefault(); + var iface = type.GetInterfaces() + .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)); + return iface != null ? iface.GetGenericArguments()[0] : null; + } + + private static bool IsEntityLike(Type type) + { + return type.IsClass + && type != typeof(string) + && (type.Namespace == null || !type.Namespace.StartsWith("System")); + } + + private static IEnumerable PublicProps(Type type) + { + return type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.GetIndexParameters().Length == 0); + } + } +} diff --git a/GenericServices/Internal/DtoReflection.cs b/GenericServices/Internal/DtoReflection.cs new file mode 100644 index 0000000..0a18ae5 --- /dev/null +++ b/GenericServices/Internal/DtoReflection.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using GenericServices.Core; +using Microsoft.EntityFrameworkCore; + +namespace GenericServices.Internal +{ + /// + /// Reflection helpers shared by the mapper configuration and the service layer. + /// + internal static class DtoReflection + { + public static bool IsDto(Type type) + { + return typeof(EfGenericDtoBase).IsAssignableFrom(type); + } + + /// + /// Walks the base chain to find and returns + /// (entityType, dtoType), or null if the type is not a DTO. + /// + public static Tuple GetEntityAndDtoTypes(Type dtoType) + { + var t = dtoType; + while (t != null && t != typeof(object)) + { + if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(EfGenericDtoBase<,>)) + { + var args = t.GetGenericArguments(); + return Tuple.Create(args[0], args[1]); + } + t = t.BaseType; + } + return null; + } + + /// + /// Returns true if a source member on can supply + /// either directly or by AutoMapper flattening + /// (e.g. "BloggerName" -> Blogger.Name, "PostsCount" -> Posts.Count). + /// + public static bool CanResolveSource(Type entityType, string memberName) + { + if (string.IsNullOrEmpty(memberName)) return false; + + if (GetPublicProperty(entityType, memberName) != null) + return true; + + //greedy longest-prefix flattening + for (var i = memberName.Length - 1; i >= 1; i--) + { + var prefix = memberName.Substring(0, i); + var remainder = memberName.Substring(i); + var prop = GetPublicProperty(entityType, prefix); + if (prop == null) continue; + + if (IsEnumerableButNotString(prop.PropertyType)) + { + if (remainder == "Count") + return true; + continue; + } + + if (CanResolveSource(prop.PropertyType, remainder)) + return true; + } + + return false; + } + + private static PropertyInfo GetPublicProperty(Type type, string name) + { + return type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); + } + + public static bool IsEnumerableButNotString(Type type) + { + return type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type); + } + + //-------------------------------------------------- + //runtime access to Set() / Find for a type only known at runtime + + private static readonly MethodInfo SetMethod = typeof(IGenericServicesDbContext) + .GetMethod("Set", Type.EmptyTypes); + + public static IQueryable QueryableForEntity(IGenericServicesDbContext context, Type entityType) + { + var generic = SetMethod.MakeGenericMethod(entityType); + return (IQueryable)generic.Invoke(context, null); + } + + public static object FindEntity(IGenericServicesDbContext context, Type entityType, object[] keys) + { + var set = SetMethod.MakeGenericMethod(entityType).Invoke(context, null); + var find = set.GetType().GetMethod("Find", new[] { typeof(object[]) }); + return find.Invoke(set, new object[] { keys }); + } + + public static async Task FindEntityAsync(IGenericServicesDbContext context, Type entityType, object[] keys) + { + var set = SetMethod.MakeGenericMethod(entityType).Invoke(context, null); + var find = set.GetType().GetMethod("FindAsync", new[] { typeof(object[]) }); + var valueTask = find.Invoke(set, new object[] { keys }); + + // DbSet.FindAsync returns ValueTask; convert to Task via AsTask() + var asTask = valueTask.GetType().GetMethod("AsTask").Invoke(valueTask, null); + await (Task)asTask; + var resultProp = asTask.GetType().GetProperty("Result"); + return resultProp.GetValue(asTask); + } + } +} diff --git a/GenericServices/SaveChangesExtensions.cs b/GenericServices/SaveChangesExtensions.cs new file mode 100644 index 0000000..9f3e74d --- /dev/null +++ b/GenericServices/SaveChangesExtensions.cs @@ -0,0 +1,75 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using GenericLibsBase.Core; +using GenericServices.Core; +using Microsoft.EntityFrameworkCore; + +namespace GenericServices +{ + /// + /// Provides the GenericServices "save with checking" behaviour on top of EF Core: + /// it validates the tracked changes first (as EF6 did automatically) and turns any + /// validation or concurrency failure into an result + /// rather than throwing. + /// + public static class SaveChangesExtensions + { + public static ISuccessOrErrors SaveChangesWithChecking(this IGenericServicesDbContext context) + { + var dbContext = AsDbContext(context); + + var validation = GenericValidation.Validate(dbContext); + if (!validation.IsValid) + return validation; + + try + { + dbContext.SaveChanges(); + } + catch (DbUpdateConcurrencyException ex) + { + return ConcurrencyError(ex); + } + + return SuccessOrErrors.Success("Successfully saved."); + } + + public static async Task SaveChangesWithCheckingAsync( + this IGenericServicesDbContext context, CancellationToken cancellationToken = default) + { + var dbContext = AsDbContext(context); + + var validation = GenericValidation.Validate(dbContext); + if (!validation.IsValid) + return validation; + + try + { + await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + } + catch (DbUpdateConcurrencyException ex) + { + return ConcurrencyError(ex); + } + + return SuccessOrErrors.Success("Successfully saved."); + } + + private static DbContext AsDbContext(IGenericServicesDbContext context) + { + var dbContext = context as DbContext; + if (dbContext == null) + throw new InvalidOperationException( + "SaveChangesWithChecking requires the context to derive from EF Core's DbContext."); + return dbContext; + } + + private static ISuccessOrErrors ConcurrencyError(DbUpdateConcurrencyException ex) + { + return new SuccessOrErrors().AddSingleError( + "Another user changed this data at the same time. Please retry your edit. ({0})", + ex.Message); + } + } +} diff --git a/GenericServices/Services.cs b/GenericServices/Services.cs new file mode 100644 index 0000000..a046a1e --- /dev/null +++ b/GenericServices/Services.cs @@ -0,0 +1,81 @@ +using System.Linq; +using System.Threading.Tasks; +using GenericLibsBase.Core; + +namespace GenericServices +{ + //------------------------------------------------------------------- + // Synchronous service contracts + + public interface IListService + { + IQueryable GetAll() where T : class; + } + + public interface IDetailService + { + ISuccessOrErrors GetDetail(params object[] keys) where T : class; + } + + public interface IUpdateSetupService + { + ISuccessOrErrors GetOriginal(params object[] keys) where T : class; + } + + public interface IUpdateService + { + ISuccessOrErrors Update(T item) where T : class; + T ResetDto(T dto) where T : class; + } + + public interface ICreateSetupService + { + T GetDto() where T : class; + } + + public interface ICreateService + { + ISuccessOrErrors Create(T item) where T : class; + T ResetDto(T dto) where T : class; + } + + public interface IDeleteService + { + ISuccessOrErrors Delete(params object[] keys) where T : class; + } + + //------------------------------------------------------------------- + // Asynchronous service contracts + + public interface IDetailServiceAsync + { + Task> GetDetailAsync(params object[] keys) where T : class; + } + + public interface IUpdateSetupServiceAsync + { + Task> GetOriginalAsync(params object[] keys) where T : class; + } + + public interface IUpdateServiceAsync + { + Task UpdateAsync(T item) where T : class; + Task ResetDtoAsync(T dto) where T : class; + } + + public interface ICreateSetupServiceAsync + { + Task GetDtoAsync() where T : class; + } + + public interface ICreateServiceAsync + { + Task CreateAsync(T item) where T : class; + Task ResetDtoAsync(T dto) where T : class; + } + + public interface IDeleteServiceAsync + { + Task DeleteAsync(params object[] keys) where T : class; + } +} diff --git a/GenericServices/Services/AsyncServices.cs b/GenericServices/Services/AsyncServices.cs new file mode 100644 index 0000000..6f6c432 --- /dev/null +++ b/GenericServices/Services/AsyncServices.cs @@ -0,0 +1,182 @@ +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using GenericLibsBase.Core; +using GenericServices; +using GenericServices.Core; +using GenericServices.Internal; +using Microsoft.EntityFrameworkCore; + +namespace GenericServices.Services +{ + public class DetailServiceAsync : GenericServiceBase, IDetailServiceAsync + { + public DetailServiceAsync(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public Task> GetDetailAsync(params object[] keys) where T : class + { + return AsyncReadHelper.ReadSingleAsync(Context, Mapper, keys); + } + } + + public class UpdateSetupServiceAsync : GenericServiceBase, IUpdateSetupServiceAsync + { + public UpdateSetupServiceAsync(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public Task> GetOriginalAsync(params object[] keys) where T : class + { + return AsyncReadHelper.ReadSingleAsync(Context, Mapper, keys); + } + } + + public class CreateSetupServiceAsync : GenericServiceBase, ICreateSetupServiceAsync + { + public CreateSetupServiceAsync(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public async Task GetDtoAsync() where T : class + { + var dto = System.Activator.CreateInstance(); + var crud = dto as IAsyncInternalDtoCrud; + if (crud != null) + await crud.RunSetupSecondaryDataAsync(Context).ConfigureAwait(false); + return dto; + } + } + + public class CreateServiceAsync : GenericServiceBase, ICreateServiceAsync + { + public CreateServiceAsync(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public async Task CreateAsync(T item) where T : class + { + var crud = item as IAsyncInternalDtoCrud; + if (crud != null) + { + var buildStatus = await crud.RunCreateDataFromDtoAsync(Context).ConfigureAwait(false); + if (!buildStatus.IsValid) + { + await crud.RunSetupSecondaryDataAsync(Context).ConfigureAwait(false); + return buildStatus; + } + + var saveStatus = await Context.SaveChangesWithCheckingAsync().ConfigureAwait(false); + if (saveStatus.IsValid) + saveStatus.SetSuccessMessage("Successfully created {0}.", crud.AssociatedEntityType.Name); + else + await crud.RunSetupSecondaryDataAsync(Context).ConfigureAwait(false); + return saveStatus; + } + + Context.Set().Add(item); + var status = await Context.SaveChangesWithCheckingAsync().ConfigureAwait(false); + if (status.IsValid) + status.SetSuccessMessage("Successfully created {0}.", typeof(T).Name); + return status; + } + + public async Task ResetDtoAsync(T dto) where T : class + { + var crud = dto as IAsyncInternalDtoCrud; + if (crud != null) + await crud.RunSetupSecondaryDataAsync(Context).ConfigureAwait(false); + return dto; + } + } + + public class UpdateServiceAsync : GenericServiceBase, IUpdateServiceAsync + { + public UpdateServiceAsync(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public async Task UpdateAsync(T item) where T : class + { + var crud = item as IAsyncInternalDtoCrud; + if (crud != null) + { + var entity = await DtoReflection.FindEntityAsync(Context, crud.AssociatedEntityType, crud.GetKeyValues()).ConfigureAwait(false); + if (entity == null) + { + await crud.RunSetupSecondaryDataAsync(Context).ConfigureAwait(false); + return new SuccessOrErrors().AddSingleError( + "Could not find the item you requested. Perhaps it was deleted by another user."); + } + + var buildStatus = await crud.RunUpdateDataFromDtoAsync(Context, entity).ConfigureAwait(false); + if (!buildStatus.IsValid) + { + await crud.RunSetupSecondaryDataAsync(Context).ConfigureAwait(false); + return buildStatus; + } + + var saveStatus = await Context.SaveChangesWithCheckingAsync().ConfigureAwait(false); + if (saveStatus.IsValid) + saveStatus.SetSuccessMessage("Successfully updated {0}.", crud.AssociatedEntityType.Name); + else + await crud.RunSetupSecondaryDataAsync(Context).ConfigureAwait(false); + return saveStatus; + } + + Context.Entry(item).State = EntityState.Modified; + var status = await Context.SaveChangesWithCheckingAsync().ConfigureAwait(false); + if (status.IsValid) + status.SetSuccessMessage("Successfully updated {0}.", typeof(T).Name); + return status; + } + + public async Task ResetDtoAsync(T dto) where T : class + { + var crud = dto as IAsyncInternalDtoCrud; + if (crud != null) + await crud.RunSetupSecondaryDataAsync(Context).ConfigureAwait(false); + return dto; + } + } + + public class DeleteServiceAsync : GenericServiceBase, IDeleteServiceAsync + { + public DeleteServiceAsync(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public async Task DeleteAsync(params object[] keys) where T : class + { + var entity = await Context.Set().FindAsync(keys).AsTask().ConfigureAwait(false); + if (entity == null) + return new SuccessOrErrors().AddSingleError( + "Could not find the {0} you requested. Perhaps it was deleted by another user.", typeof(T).Name); + + Context.Set().Remove(entity); + var status = await Context.SaveChangesWithCheckingAsync().ConfigureAwait(false); + if (status.IsValid) + status.SetSuccessMessage("Successfully deleted {0}.", typeof(T).Name); + return status; + } + } + + internal static class AsyncReadHelper + { + public static async Task> ReadSingleAsync(IGenericServicesDbContext context, IMapper mapper, object[] keys) where T : class + { + if (!DtoReflection.IsDto(typeof(T))) + { + var entity = await context.Set().FindAsync(keys).AsTask().ConfigureAwait(false); + if (entity == null) + return (ISuccessOrErrors)new SuccessOrErrors().AddSingleError("Could not find the data entry you requested."); + return new SuccessOrErrors().SetSuccessWithResult(entity, "Success"); + } + + var pair = DtoReflection.GetEntityAndDtoTypes(typeof(T)); + var source = DtoReflection.QueryableForEntity(context, pair.Item1); + var projected = source.ProjectTo(mapper.ConfigurationProvider); + var predicate = GenericServiceBase.BuildKeyPredicate(keys); + var dto = await projected.Where(predicate).SingleOrDefaultAsync().ConfigureAwait(false); + + if (dto == null) + return (ISuccessOrErrors)new SuccessOrErrors().AddSingleError("Could not find the data entry you requested."); + + var crud = dto as IAsyncInternalDtoCrud; + if (crud != null) + await crud.RunSetupSecondaryDataAsync(context).ConfigureAwait(false); + + return new SuccessOrErrors().SetSuccessWithResult(dto, "Success"); + } + } +} diff --git a/GenericServices/Services/GenericServiceBase.cs b/GenericServices/Services/GenericServiceBase.cs new file mode 100644 index 0000000..dedc88e --- /dev/null +++ b/GenericServices/Services/GenericServiceBase.cs @@ -0,0 +1,56 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using AutoMapper; +using GenericServices.Internal; + +namespace GenericServices.Services +{ + /// + /// Shared state and helpers for the concrete service classes. + /// + public abstract class GenericServiceBase + { + protected readonly IGenericServicesDbContext Context; + protected readonly IMapper Mapper; + + protected GenericServiceBase(IGenericServicesDbContext context, IMapper mapper) + { + Context = context; + Mapper = mapper; + } + + protected static bool IsDto(Type type) + { + return DtoReflection.IsDto(type); + } + + /// + /// Builds a predicate matching the DTO's [Key] property/properties against the given key values. + /// + internal static Expression> BuildKeyPredicate(object[] keys) + { + var keyProps = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.IsDefined(typeof(KeyAttribute), true)) + .ToArray(); + + if (keyProps.Length == 0) + throw new InvalidOperationException( + string.Format("The DTO '{0}' has no [Key] property to identify the row.", typeof(T).Name)); + + var param = Expression.Parameter(typeof(T), "x"); + Expression body = null; + for (var i = 0; i < keyProps.Length; i++) + { + var member = Expression.Property(param, keyProps[i]); + var value = Convert.ChangeType(keys[i], keyProps[i].PropertyType); + var equal = Expression.Equal(member, Expression.Constant(value, keyProps[i].PropertyType)); + body = body == null ? equal : Expression.AndAlso(body, equal); + } + + return Expression.Lambda>(body, param); + } + } +} diff --git a/GenericServices/Services/SyncServices.cs b/GenericServices/Services/SyncServices.cs new file mode 100644 index 0000000..12e28ae --- /dev/null +++ b/GenericServices/Services/SyncServices.cs @@ -0,0 +1,195 @@ +using System.Linq; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using GenericLibsBase.Core; +using GenericServices; +using GenericServices.Core; +using GenericServices.Internal; + +namespace GenericServices.Services +{ + public class ListService : GenericServiceBase, IListService + { + public ListService(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public IQueryable GetAll() where T : class + { + if (!IsDto(typeof(T))) + return Context.Set(); + + var pair = DtoReflection.GetEntityAndDtoTypes(typeof(T)); + var source = DtoReflection.QueryableForEntity(Context, pair.Item1); + return source.ProjectTo(Mapper.ConfigurationProvider); + } + } + + public class DetailService : GenericServiceBase, IDetailService + { + public DetailService(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public ISuccessOrErrors GetDetail(params object[] keys) where T : class + { + return SyncReadHelper.ReadSingle(Context, Mapper, keys); + } + } + + public class UpdateSetupService : GenericServiceBase, IUpdateSetupService + { + public UpdateSetupService(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public ISuccessOrErrors GetOriginal(params object[] keys) where T : class + { + return SyncReadHelper.ReadSingle(Context, Mapper, keys); + } + } + + public class CreateSetupService : GenericServiceBase, ICreateSetupService + { + public CreateSetupService(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public T GetDto() where T : class + { + var dto = System.Activator.CreateInstance(); + var crud = dto as ISyncInternalDtoCrud; + if (crud != null) + crud.RunSetupSecondaryData(Context); + return dto; + } + } + + public class CreateService : GenericServiceBase, ICreateService + { + public CreateService(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public ISuccessOrErrors Create(T item) where T : class + { + var crud = item as ISyncInternalDtoCrud; + if (crud != null) + { + var buildStatus = crud.RunCreateDataFromDto(Context); + if (!buildStatus.IsValid) + { + crud.RunSetupSecondaryData(Context); + return buildStatus; + } + + var saveStatus = Context.SaveChangesWithChecking(); + if (saveStatus.IsValid) + saveStatus.SetSuccessMessage("Successfully created {0}.", crud.AssociatedEntityType.Name); + else + crud.RunSetupSecondaryData(Context); + return saveStatus; + } + + Context.Set().Add(item); + var status = Context.SaveChangesWithChecking(); + if (status.IsValid) + status.SetSuccessMessage("Successfully created {0}.", typeof(T).Name); + return status; + } + + public T ResetDto(T dto) where T : class + { + var crud = dto as ISyncInternalDtoCrud; + if (crud != null) + crud.RunSetupSecondaryData(Context); + return dto; + } + } + + public class UpdateService : GenericServiceBase, IUpdateService + { + public UpdateService(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public ISuccessOrErrors Update(T item) where T : class + { + var crud = item as ISyncInternalDtoCrud; + if (crud != null) + { + var entity = DtoReflection.FindEntity(Context, crud.AssociatedEntityType, crud.GetKeyValues()); + if (entity == null) + { + crud.RunSetupSecondaryData(Context); + return new SuccessOrErrors().AddSingleError( + "Could not find the item you requested. Perhaps it was deleted by another user."); + } + + var buildStatus = crud.RunUpdateDataFromDto(Context, entity); + if (!buildStatus.IsValid) + { + crud.RunSetupSecondaryData(Context); + return buildStatus; + } + + var saveStatus = Context.SaveChangesWithChecking(); + if (saveStatus.IsValid) + saveStatus.SetSuccessMessage("Successfully updated {0}.", crud.AssociatedEntityType.Name); + else + crud.RunSetupSecondaryData(Context); + return saveStatus; + } + + Context.Entry(item).State = Microsoft.EntityFrameworkCore.EntityState.Modified; + var status = Context.SaveChangesWithChecking(); + if (status.IsValid) + status.SetSuccessMessage("Successfully updated {0}.", typeof(T).Name); + return status; + } + + public T ResetDto(T dto) where T : class + { + var crud = dto as ISyncInternalDtoCrud; + if (crud != null) + crud.RunSetupSecondaryData(Context); + return dto; + } + } + + public class DeleteService : GenericServiceBase, IDeleteService + { + public DeleteService(IGenericServicesDbContext context, IMapper mapper) : base(context, mapper) { } + + public ISuccessOrErrors Delete(params object[] keys) where T : class + { + var entity = Context.Set().Find(keys); + if (entity == null) + return new SuccessOrErrors().AddSingleError( + "Could not find the {0} you requested. Perhaps it was deleted by another user.", typeof(T).Name); + + Context.Set().Remove(entity); + var status = Context.SaveChangesWithChecking(); + if (status.IsValid) + status.SetSuccessMessage("Successfully deleted {0}.", typeof(T).Name); + return status; + } + } + + internal static class SyncReadHelper + { + public static ISuccessOrErrors ReadSingle(IGenericServicesDbContext context, IMapper mapper, object[] keys) where T : class + { + if (!DtoReflection.IsDto(typeof(T))) + { + var entity = context.Set().Find(keys); + if (entity == null) + return new SuccessOrErrors().AddSingleError("Could not find the data entry you requested.") as ISuccessOrErrors; + return new SuccessOrErrors().SetSuccessWithResult(entity, "Success"); + } + + var pair = DtoReflection.GetEntityAndDtoTypes(typeof(T)); + var source = DtoReflection.QueryableForEntity(context, pair.Item1); + var projected = source.ProjectTo(mapper.ConfigurationProvider); + var predicate = GenericServiceBase.BuildKeyPredicate(keys); + var dto = projected.Where(predicate).SingleOrDefault(); + + if (dto == null) + return new SuccessOrErrors().AddSingleError("Could not find the data entry you requested.") as ISuccessOrErrors; + + var crud = dto as ISyncInternalDtoCrud; + if (crud != null) + crud.RunSetupSecondaryData(context); + + return new SuccessOrErrors().SetSuccessWithResult(dto, "Success"); + } + } +} diff --git a/README.md b/README.md index 21e465e..cc102d7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,51 @@ SampleMvcWebApp =============== +> ### .NET 8 modernization +> +> This repository has been migrated from **ASP.NET MVC 5 / .NET Framework 4.5.1** to +> **cross-platform ASP.NET Core MVC on .NET 8**, while preserving the app's pages, CRUD +> flows, and business rules. The historical description below is retained for context. +> +> **What changed** +> +> | Area | Before | After | +> |------|--------|-------| +> | Projects | old MSBuild + `packages.config` | SDK-style `.csproj`, `net8.0` | +> | Web framework | ASP.NET MVC 5 (`System.Web`, `Global.asax`) | ASP.NET Core MVC (`Program.cs`, middleware) | +> | ORM | Entity Framework 6.1.3 | EF Core 8 (SQLite, cross-platform, with migrations) | +> | DI | Autofac 3.5 | built-in ASP.NET Core DI | +> | Mapping | AutoMapper 4.2 | AutoMapper 14 | +> | Logging | log4net | Microsoft.Extensions.Logging | +> | Config | `web.config` | `appsettings.json` | +> | GenericServices | EF6-only NuGet package | in-repo `GenericServices`/`GenericLibsBase` compatibility port on EF Core 8 | +> | Tests | NUnit 2.6 (.NET 4.5.1) | NUnit 4 (`net8.0`) | +> +> **Prerequisites:** [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0). +> +> **Build, test and run:** +> ```bash +> dotnet build SampleWebApp.sln +> dotnet test SampleWebApp.sln +> dotnet run --project SampleWebApp +> ``` +> On first run the app creates and seeds a local SQLite database (`SampleWebAppDb.db`) via +> EF Core migrations. The connection string and host type live in +> `SampleWebApp/appsettings.json`. +> +> **CI:** `.github/workflows/ci.yml` builds and tests the solution on .NET 8. +> +> **Remaining risks / review notes:** +> - The default database provider is now **SQLite** for cross-platform demo use; the original +> used SQL Server LocalDB. Production SQL Server compatibility is not re-validated. +> - EF Core validation/tracking/cascade behaviour can differ subtly from EF6. +> - AutoMapper is pinned to **14.0.0** (last MIT-licensed line); a single, scoped NuGet audit +> suppression is applied for advisory `GHSA-rvv3-g6hj-g44x`. +> - The legacy test suite (which asserted EF6/Autofac/MVC5 internals that no longer exist) was +> replaced with focused regression tests covering the preserved CRUD/service-layer behaviour. +> +> --- + SampleMvcWebApp is a ASP.NET MVC5 web site designed to show number of useful methods for building enterprise grade web applications using ASP.NET MVC5 and Entity Framework 6. The code for this sample MVC web application, and the associated diff --git a/SampleWebApp.sln b/SampleWebApp.sln index b474189..b517f90 100644 --- a/SampleWebApp.sln +++ b/SampleWebApp.sln @@ -1,63 +1,58 @@  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}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenericLibsBase", "GenericLibsBase\GenericLibsBase.csproj", "{F3FEAB2B-8725-4E8B-83A2-E51C4EC66EF7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataLayer", "DataLayer\DataLayer.csproj", "{264E1878-12DE-4099-B8D7-CC53A73FEA49}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenericServices", "GenericServices\GenericServices.csproj", "{BC14D5B3-3FA5-48E4-9062-6D30929D72F4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceLayer", "ServiceLayer\ServiceLayer.csproj", "{D2813927-0F38-43C3-B47C-AE8F00D50CAE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataLayer", "DataLayer\DataLayer.csproj", "{095E3125-1C36-49B2-98D2-D21ED7225FAB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{6D9E7904-B2AC-49E3-83A7-6B48876F46B9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceLayer", "ServiceLayer\ServiceLayer.csproj", "{5BE6B3D0-A4CA-493D-BA86-377105DD92BC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizLayer", "BizLayer\BizLayer.csproj", "{4538B585-62DE-45E5-A6DD-8BD48505075D}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AF8764F7-FBEE-48AD-AF62-23010DA35D70}" - ProjectSection(SolutionItems) = preProject - Licence.txt = Licence.txt - README.md = README.md - EndProjectSection +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebApp", "SampleWebApp\SampleWebApp.csproj", "{FBD2F5CB-9E02-4E9B-B26D-8BB9E9DC4C63}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{6D9E7904-B2AC-49E3-83A7-6B48876F46B9}" 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(SolutionProperties) = preSolution + HideSolutionNode = FALSE 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 + {F3FEAB2B-8725-4E8B-83A2-E51C4EC66EF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3FEAB2B-8725-4E8B-83A2-E51C4EC66EF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3FEAB2B-8725-4E8B-83A2-E51C4EC66EF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3FEAB2B-8725-4E8B-83A2-E51C4EC66EF7}.Release|Any CPU.Build.0 = Release|Any CPU + {BC14D5B3-3FA5-48E4-9062-6D30929D72F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC14D5B3-3FA5-48E4-9062-6D30929D72F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC14D5B3-3FA5-48E4-9062-6D30929D72F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC14D5B3-3FA5-48E4-9062-6D30929D72F4}.Release|Any CPU.Build.0 = Release|Any CPU + {095E3125-1C36-49B2-98D2-D21ED7225FAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {095E3125-1C36-49B2-98D2-D21ED7225FAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {095E3125-1C36-49B2-98D2-D21ED7225FAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {095E3125-1C36-49B2-98D2-D21ED7225FAB}.Release|Any CPU.Build.0 = Release|Any CPU + {5BE6B3D0-A4CA-493D-BA86-377105DD92BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BE6B3D0-A4CA-493D-BA86-377105DD92BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BE6B3D0-A4CA-493D-BA86-377105DD92BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BE6B3D0-A4CA-493D-BA86-377105DD92BC}.Release|Any CPU.Build.0 = Release|Any CPU + {4538B585-62DE-45E5-A6DD-8BD48505075D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4538B585-62DE-45E5-A6DD-8BD48505075D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4538B585-62DE-45E5-A6DD-8BD48505075D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4538B585-62DE-45E5-A6DD-8BD48505075D}.Release|Any CPU.Build.0 = Release|Any CPU + {FBD2F5CB-9E02-4E9B-B26D-8BB9E9DC4C63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBD2F5CB-9E02-4E9B-B26D-8BB9E9DC4C63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBD2F5CB-9E02-4E9B-B26D-8BB9E9DC4C63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBD2F5CB-9E02-4E9B-B26D-8BB9E9DC4C63}.Release|Any CPU.Build.0 = Release|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 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE EndGlobalSection EndGlobal 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..b62dae3 100644 --- a/SampleWebApp/Controllers/BlogsController.cs +++ b/SampleWebApp/Controllers/BlogsController.cs @@ -25,7 +25,7 @@ // SOFTWARE. #endregion using System.Linq; -using System.Web.Mvc; +using Microsoft.AspNetCore.Mvc; using DataLayer.DataClasses.Concrete; using GenericServices; using SampleWebApp.Infrastructure; diff --git a/SampleWebApp/Controllers/HomeController.cs b/SampleWebApp/Controllers/HomeController.cs index bfef31c..ee76b1c 100644 --- a/SampleWebApp/Controllers/HomeController.cs +++ b/SampleWebApp/Controllers/HomeController.cs @@ -24,7 +24,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #endregion -using System.Web.Mvc; +using Microsoft.AspNetCore.Mvc; using SampleWebApp.Models; namespace SampleWebApp.Controllers @@ -36,6 +36,11 @@ public ActionResult Index() return View(); } + public ActionResult Error() + { + return View(); + } + public ActionResult About() { ViewBag.Message = "Your application description page."; diff --git a/SampleWebApp/Controllers/PostsAsyncController.cs b/SampleWebApp/Controllers/PostsAsyncController.cs index 22e914f..3f3c76f 100644 --- a/SampleWebApp/Controllers/PostsAsyncController.cs +++ b/SampleWebApp/Controllers/PostsAsyncController.cs @@ -25,10 +25,10 @@ // SOFTWARE. #endregion using System.Collections.Generic; -using System.Data.Entity; +using Microsoft.EntityFrameworkCore; using System.Linq; using System.Threading.Tasks; -using System.Web.Mvc; +using Microsoft.AspNetCore.Mvc; using DataLayer.DataClasses; using DataLayer.DataClasses.Concrete; using DataLayer.Startup; @@ -114,7 +114,7 @@ public async Task Delete(int id, IDeleteServiceAsync service) TempData["message"] = response.SuccessMessage; else //else errors, so send back an error message - TempData["errorMessage"] = new MvcHtmlString(response.ErrorsAsHtml()); + TempData["errorMessage"] = response.ErrorsAsHtml(); return RedirectToAction("Index"); } diff --git a/SampleWebApp/Controllers/PostsController.cs b/SampleWebApp/Controllers/PostsController.cs index d5cd076..d51d4eb 100644 --- a/SampleWebApp/Controllers/PostsController.cs +++ b/SampleWebApp/Controllers/PostsController.cs @@ -26,7 +26,7 @@ #endregion using System.Linq; using System.Threading; -using System.Web.Mvc; +using Microsoft.AspNetCore.Mvc; using DataLayer.DataClasses; using DataLayer.DataClasses.Concrete; using DataLayer.Startup; @@ -123,7 +123,7 @@ public ActionResult Delete(int id, IDeleteService service) TempData["message"] = response.SuccessMessage; else //else errors, so send back an error message - TempData["errorMessage"] = new MvcHtmlString(response.ErrorsAsHtml()); + TempData["errorMessage"] = response.ErrorsAsHtml(); return RedirectToAction("Index"); } diff --git a/SampleWebApp/Controllers/TagsAsyncController.cs b/SampleWebApp/Controllers/TagsAsyncController.cs index 6d9e9b9..c8d85dc 100644 --- a/SampleWebApp/Controllers/TagsAsyncController.cs +++ b/SampleWebApp/Controllers/TagsAsyncController.cs @@ -24,9 +24,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #endregion -using System.Data.Entity; +using Microsoft.EntityFrameworkCore; using System.Threading.Tasks; -using System.Web.Mvc; +using Microsoft.AspNetCore.Mvc; using DataLayer.DataClasses.Concrete; using GenericServices; using SampleWebApp.Infrastructure; @@ -110,7 +110,7 @@ public async Task Delete(int id, IDeleteServiceAsync service) TempData["message"] = response.SuccessMessage; else //else errors, so send back an error message - TempData["errorMessage"] = new MvcHtmlString(response.ErrorsAsHtml()); + TempData["errorMessage"] = response.ErrorsAsHtml(); return RedirectToAction("Index"); } diff --git a/SampleWebApp/Controllers/TagsController.cs b/SampleWebApp/Controllers/TagsController.cs index a729d08..236ec69 100644 --- a/SampleWebApp/Controllers/TagsController.cs +++ b/SampleWebApp/Controllers/TagsController.cs @@ -25,7 +25,7 @@ // SOFTWARE. #endregion using System.Linq; -using System.Web.Mvc; +using Microsoft.AspNetCore.Mvc; using DataLayer.DataClasses.Concrete; using GenericServices; using SampleWebApp.Infrastructure; @@ -108,7 +108,7 @@ public ActionResult Delete(int id, IDeleteService service) TempData["message"] = response.SuccessMessage; else //else errors, so send back an error message - TempData["errorMessage"] = new MvcHtmlString(response.ErrorsAsHtml()); + TempData["errorMessage"] = response.ErrorsAsHtml(); return RedirectToAction("Index"); } 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/MelGenericLogger.cs b/SampleWebApp/Infrastructure/MelGenericLogger.cs new file mode 100644 index 0000000..25e3f89 --- /dev/null +++ b/SampleWebApp/Infrastructure/MelGenericLogger.cs @@ -0,0 +1,36 @@ +using System; +using GenericLibsBase; +using Microsoft.Extensions.Logging; + +namespace SampleWebApp.Infrastructure +{ + /// + /// Bridges the GenericServices abstraction onto + /// Microsoft.Extensions.Logging, replacing the original log4net logger. + /// + public class MelGenericLogger : IGenericLogger + { + private readonly ILogger _logger; + + public MelGenericLogger(ILogger logger) + { + _logger = logger; + } + + public void Verbose(object message) => _logger.LogDebug("{Message}", message); + public void VerboseFormat(string format, params object[] args) => _logger.LogDebug(format, args); + + public void Info(object message) => _logger.LogInformation("{Message}", message); + public void InfoFormat(string format, params object[] args) => _logger.LogInformation(format, args); + + public void Warn(object message) => _logger.LogWarning("{Message}", message); + public void WarnFormat(string format, params object[] args) => _logger.LogWarning(format, args); + + public void Error(object message) => _logger.LogError("{Message}", message); + public void ErrorFormat(string format, params object[] args) => _logger.LogError(format, args); + + public void Critical(object message) => _logger.LogCritical("{Message}", message); + public void Critical(object message, Exception ex) => _logger.LogCritical(ex, "{Message}", message); + public void CriticalFormat(string format, params object[] args) => _logger.LogCritical(format, args); + } +} diff --git a/SampleWebApp/Infrastructure/ServiceParameterBindingConvention.cs b/SampleWebApp/Infrastructure/ServiceParameterBindingConvention.cs new file mode 100644 index 0000000..b524c45 --- /dev/null +++ b/SampleWebApp/Infrastructure/ServiceParameterBindingConvention.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace SampleWebApp.Infrastructure +{ + /// + /// The original MVC5 app used a custom model binder (DiModelBinder) so that services could be + /// supplied as action-method parameters, e.g. Index(IListService service). ASP.NET Core + /// does not bind such parameters from DI by default, so this convention marks any action + /// parameter whose type is a registered service as , + /// preserving the original controller signatures. + /// + public class ServiceParameterBindingConvention : IActionModelConvention + { + private readonly IServiceProviderIsService _isService; + + public ServiceParameterBindingConvention(IServiceProviderIsService isService) + { + _isService = isService; + } + + public void Apply(ActionModel action) + { + foreach (var parameter in action.Parameters) + { + if (parameter.BindingInfo?.BindingSource != null) + continue; + + if (_isService.IsService(parameter.ParameterInfo.ParameterType)) + { + parameter.BindingInfo ??= new BindingInfo(); + parameter.BindingInfo.BindingSource = BindingSource.Services; + } + } + } + } + + /// + /// Registers once the DI container is available. + /// + public class ConfigureServiceParameterBinding : IConfigureOptions + { + private readonly IServiceProviderIsService _isService; + + public ConfigureServiceParameterBinding(IServiceProviderIsService isService) + { + _isService = isService; + } + + public void Configure(MvcOptions options) + { + options.Conventions.Add(new ServiceParameterBindingConvention(_isService)); + } + } +} 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..d5981f2 100644 --- a/SampleWebApp/Infrastructure/ValidationHelper.cs +++ b/SampleWebApp/Infrastructure/ValidationHelper.cs @@ -1,36 +1,8 @@ -#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.ModelBinding; +using GenericLibsBase.Core; namespace SampleWebApp.Infrastructure { @@ -39,13 +11,13 @@ 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. + /// This means that errors associated 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) + public static void CopyErrorsToModelState(this ISuccessOrErrors errorHolder, ModelStateDictionary modelState, T displayDto) { if (errorHolder.IsValid) return; @@ -65,72 +37,15 @@ public static void CopyErrorsToModelState(this ISuccessOrErrors errorHolder, /// /// 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) - { - 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())) - { - 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)); - else - 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(); + modelState.AddModelError("", error.ErrorMessage); } - private static IList PropertyNamesInDto ( T objectToCheck) + private static IList PropertyNamesInDto(T objectToCheck) { return objectToCheck.GetType() @@ -138,6 +53,5 @@ private static IList PropertyNamesInDto ( T objectToCheck) .Select(x => x.Name) .ToList(); } - } -} \ No newline at end of file +} diff --git a/SampleWebApp/Infrastructure/WebUiInitialise.cs b/SampleWebApp/Infrastructure/WebUiInitialise.cs index 2bb8154..1b927a4 100644 --- a/SampleWebApp/Infrastructure/WebUiInitialise.cs +++ b/SampleWebApp/Infrastructure/WebUiInitialise.cs @@ -1,110 +1,41 @@ -#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; +using Microsoft.Extensions.Logging; namespace SampleWebApp.Infrastructure { public enum HostTypes { NotSet, LocalHost, WebWiz, Azure }; + /// + /// Holds the host type shown in the UI and wires the GenericServices logger + /// abstraction onto Microsoft.Extensions.Logging. + /// 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 + /// The host the app believes it is running on (shown in the page footer). /// public static HostTypes HostType { get; private set; } /// - /// This should be called at Startup + /// Called at startup. Decodes the configured host type and routes the + /// GenericServices logger through the supplied logger factory. /// - public static void InitialiseThis(HttpApplication application) + public static void InitialiseThis(string hostTypeString, ILoggerFactory loggerFactory) { - 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(); + HostType = DecodeHostType(hostTypeString); - //// Set the dependency resolver for MVC. - var mvcResolver = new AutofacDependencyResolver(container); - DependencyResolver.SetResolver(mvcResolver); + GenericLibsBaseConfig.SetLoggerMethod = name => new MelGenericLogger(loggerFactory.CreateLogger(name)); + GenericLibsBaseConfig.GetLogger("LoggerSetup").Info("We have just assigned a logger."); } private static HostTypes DecodeHostType(string hostTypeString) { - HostTypes hostType ; + 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/Log4Net.xml b/SampleWebApp/Log4Net.xml deleted file mode 100644 index 6feb4c4..0000000 --- a/SampleWebApp/Log4Net.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SampleWebApp/Models/InternalsInfo.cs b/SampleWebApp/Models/InternalsInfo.cs index f9df44e..d9e866c 100644 --- a/SampleWebApp/Models/InternalsInfo.cs +++ b/SampleWebApp/Models/InternalsInfo.cs @@ -53,7 +53,9 @@ public InternalsInfo() WorkerThreads = workerThreads; AvailableThreads = availableThreads; - AvailableMbytes = (int)new PerformanceCounter("Memory", "Available MBytes", true).RawValue; + //PerformanceCounter is Windows-only; use the cross-platform GC memory info instead + var gcInfo = GC.GetGCMemoryInfo(); + AvailableMbytes = (int)(gcInfo.TotalAvailableMemoryBytes / (1024 * 1024)); HeapMemoryUsedKbytes = (int)(GC.GetTotalMemory(true)/1000); } diff --git a/SampleWebApp/Program.cs b/SampleWebApp/Program.cs new file mode 100644 index 0000000..0e5d9e1 --- /dev/null +++ b/SampleWebApp/Program.cs @@ -0,0 +1,59 @@ +using DataLayer.DataClasses; +using DataLayer.Startup; +using GenericServices; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using SampleWebApp.Infrastructure; +using ServiceLayer.PostServices; +using ServiceLayer.Startup; + +var builder = WebApplication.CreateBuilder(args); + +var connectionString = builder.Configuration.GetConnectionString(WebUiInitialise.DatabaseConnectionStringName) + ?? "Data Source=SampleWebAppDb.db"; + +// EF Core context (replaces EF6 + web.config connection string) +builder.Services.AddDbContext(options => options.UseSqlite(connectionString)); +builder.Services.AddScoped(sp => sp.GetRequiredService()); + +// GenericServices compatibility layer + AutoMapper (replaces Autofac registrations) +builder.Services.AddGenericServices(typeof(SimplePostDto).Assembly); + +builder.Services.AddControllersWithViews(); + +// Preserve the MVC5 pattern of supplying services as action-method parameters +builder.Services.AddSingleton, ConfigureServiceParameterBinding>(); + +var app = builder.Build(); + +// Route the GenericServices logger through Microsoft.Extensions.Logging (replaces log4net) +var hostTypeString = app.Configuration["HostTypeString"] ?? nameof(HostTypes.LocalHost); +WebUiInitialise.InitialiseThis(hostTypeString, app.Services.GetRequiredService()); + +// Initialise / migrate / seed the database (replaces the EF6 database initializer) +using (var scope = app.Services.CreateScope()) +{ + var context = scope.ServiceProvider.GetRequiredService(); + ServiceLayerInitialise.InitialiseThis(context, canCreateDatabase: true); +} + +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Home/Error"); +} + +app.UseStaticFiles(); + +app.UseRouting(); + +app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}"); + +app.Run(); diff --git a/SampleWebApp/Project_Readme.html b/SampleWebApp/Project_Readme.html deleted file mode 100644 index cb9e793..0000000 --- a/SampleWebApp/Project_Readme.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - Your ASP.NET application - - - - - - -
    -
    -

    This application consists of:

    -
      -
    • Sample pages showing basic nav between Home, About, and Contact
    • -
    • Theming using Bootstrap
    • -
    • Authentication, if selected, shows how to register and sign in
    • -
    • ASP.NET features managed using NuGet
    • -
    -
    - - - - - -
    -

    Get help

    - -
    -
    - - - \ No newline at end of file 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..f09cf7d 100644 --- a/SampleWebApp/SampleWebApp.csproj +++ b/SampleWebApp/SampleWebApp.csproj @@ -1,485 +1,19 @@ - - - + + - Debug - AnyCPU - - - 2.0 - {CFFEE5E0-3B99-46E0-9A82-2E74621C17C5} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties + net8.0 + disable + disable SampleWebApp SampleWebApp - v4.5.1 - false - true - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - 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/Blogs/Index.cshtml b/SampleWebApp/Views/Blogs/Index.cshtml index 98bb41d..e334799 100644 --- a/SampleWebApp/Views/Blogs/Index.cshtml +++ b/SampleWebApp/Views/Blogs/Index.cshtml @@ -9,7 +9,7 @@ } @if (TempData["errorMessage"] != null) { -
    @TempData["errorMessage"]
    +
    @Html.Raw(TempData["errorMessage"])
    }

    diff --git a/SampleWebApp/Views/Posts/Create.cshtml b/SampleWebApp/Views/Posts/Create.cshtml index ca33689..d847642 100644 --- a/SampleWebApp/Views/Posts/Create.cshtml +++ b/SampleWebApp/Views/Posts/Create.cshtml @@ -39,7 +39,7 @@

    - @Html.Label("Tags", htmlAttributes: new { @class = "control-label col-md-2" }) + @Html.Label("Tags", "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" }) @@ -57,8 +57,9 @@ @Html.ActionLink("Back to List", "Index")

    -@Html.Partial("PostValidation") + @section Scripts { - @Scripts.Render("~/bundles/jqueryval") + + } diff --git a/SampleWebApp/Views/Posts/Edit.cshtml b/SampleWebApp/Views/Posts/Edit.cshtml index 49d666f..6f2d0eb 100644 --- a/SampleWebApp/Views/Posts/Edit.cshtml +++ b/SampleWebApp/Views/Posts/Edit.cshtml @@ -42,7 +42,7 @@
    - @Html.Label("Tags", htmlAttributes: new { @class = "control-label col-md-2" }) + @Html.Label("Tags", "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" }) @@ -61,9 +61,10 @@ @Html.ActionLink("Back to List", "Index")

    -@Html.Partial("PostValidation") + @section Scripts { - @Scripts.Render("~/bundles/jqueryval") + + } diff --git a/SampleWebApp/Views/Posts/Index.cshtml b/SampleWebApp/Views/Posts/Index.cshtml index 197b2f6..e4c389d 100644 --- a/SampleWebApp/Views/Posts/Index.cshtml +++ b/SampleWebApp/Views/Posts/Index.cshtml @@ -12,7 +12,7 @@ } @if (TempData["errorMessage"] != null) { -
    @TempData["errorMessage"]
    +
    @Html.Raw(TempData["errorMessage"])
    }

    diff --git a/SampleWebApp/Views/PostsAsync/Create.cshtml b/SampleWebApp/Views/PostsAsync/Create.cshtml index d22f305..0ae1039 100644 --- a/SampleWebApp/Views/PostsAsync/Create.cshtml +++ b/SampleWebApp/Views/PostsAsync/Create.cshtml @@ -39,7 +39,7 @@

    - @Html.Label("Tags", htmlAttributes: new { @class = "control-label col-md-2" }) + @Html.Label("Tags", "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" }) @@ -57,8 +57,9 @@ @Html.ActionLink("Back to List", "Index")

    -@Html.Partial("PostValidation") + @section Scripts { - @Scripts.Render("~/bundles/jqueryval") + + } diff --git a/SampleWebApp/Views/PostsAsync/Edit.cshtml b/SampleWebApp/Views/PostsAsync/Edit.cshtml index 75b1706..09ec61c 100644 --- a/SampleWebApp/Views/PostsAsync/Edit.cshtml +++ b/SampleWebApp/Views/PostsAsync/Edit.cshtml @@ -41,7 +41,7 @@
    - @Html.Label("Tags", htmlAttributes: new { @class = "control-label col-md-2" }) + @Html.Label("Tags", "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" }) @@ -60,8 +60,9 @@ @Html.ActionLink("Back to List", "Index")

    -@Html.Partial("PostValidation") + @section Scripts { - @Scripts.Render("~/bundles/jqueryval") + + } diff --git a/SampleWebApp/Views/PostsAsync/Index.cshtml b/SampleWebApp/Views/PostsAsync/Index.cshtml index 706400f..a04efa0 100644 --- a/SampleWebApp/Views/PostsAsync/Index.cshtml +++ b/SampleWebApp/Views/PostsAsync/Index.cshtml @@ -12,7 +12,7 @@ } @if (TempData["errorMessage"] != null) { -
    @TempData["errorMessage"]
    +
    @Html.Raw(TempData["errorMessage"])
    }

    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/_Layout.cshtml b/SampleWebApp/Views/Shared/_Layout.cshtml index 345fd91..0f327cc 100644 --- a/SampleWebApp/Views/Shared/_Layout.cshtml +++ b/SampleWebApp/Views/Shared/_Layout.cshtml @@ -5,7 +5,8 @@ @ViewBag.Title - SampleMvcWebApp - @Styles.Render("~/Content/css") + + @@ -54,7 +55,9 @@
    - @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..8bcc251 100644 --- a/SampleWebApp/Views/Tags/Create.cshtml +++ b/SampleWebApp/Views/Tags/Create.cshtml @@ -43,4 +43,4 @@ @Html.ActionLink("Back to List", "Index")
    -@Html.Partial("TagValidation") + diff --git a/SampleWebApp/Views/Tags/Edit.cshtml b/SampleWebApp/Views/Tags/Edit.cshtml index baab41e..e3a01bf 100644 --- a/SampleWebApp/Views/Tags/Edit.cshtml +++ b/SampleWebApp/Views/Tags/Edit.cshtml @@ -44,4 +44,4 @@ @Html.ActionLink("Back to List", "Index")
    -@Html.Partial("TagValidation") + diff --git a/SampleWebApp/Views/Tags/Index.cshtml b/SampleWebApp/Views/Tags/Index.cshtml index 03c2653..a371c20 100644 --- a/SampleWebApp/Views/Tags/Index.cshtml +++ b/SampleWebApp/Views/Tags/Index.cshtml @@ -12,7 +12,7 @@ } @if (TempData["errorMessage"] != null) { -
    @TempData["errorMessage"]
    +
    @Html.Raw(TempData["errorMessage"])
    }

    diff --git a/SampleWebApp/Views/TagsAsync/Create.cshtml b/SampleWebApp/Views/TagsAsync/Create.cshtml index 84783db..0e0ce81 100644 --- a/SampleWebApp/Views/TagsAsync/Create.cshtml +++ b/SampleWebApp/Views/TagsAsync/Create.cshtml @@ -43,4 +43,4 @@ @Html.ActionLink("Back to List", "Index")


    -@Html.Partial("TagValidation") + diff --git a/SampleWebApp/Views/TagsAsync/Edit.cshtml b/SampleWebApp/Views/TagsAsync/Edit.cshtml index 3f41685..51e3e42 100644 --- a/SampleWebApp/Views/TagsAsync/Edit.cshtml +++ b/SampleWebApp/Views/TagsAsync/Edit.cshtml @@ -44,4 +44,4 @@ @Html.ActionLink("Back to List", "Index")
    -@Html.Partial("TagValidation") + diff --git a/SampleWebApp/Views/TagsAsync/Index.cshtml b/SampleWebApp/Views/TagsAsync/Index.cshtml index 92c0337..5188df8 100644 --- a/SampleWebApp/Views/TagsAsync/Index.cshtml +++ b/SampleWebApp/Views/TagsAsync/Index.cshtml @@ -12,7 +12,7 @@ } @if (TempData["errorMessage"] != null) { -
    @TempData["errorMessage"]
    +
    @Html.Raw(TempData["errorMessage"])
    } @Html.ValidationSummary(false, "", new { @class = "text-danger" }) 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..83e313c --- /dev/null +++ b/SampleWebApp/Views/_ViewImports.cshtml @@ -0,0 +1,6 @@ +@using SampleWebApp +@using SampleWebApp.Models +@using ServiceLayer.UiClasses +@using DataLayer.DataClasses.Concrete +@using Microsoft.AspNetCore.Mvc.Rendering +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/SampleWebApp/Web.AzureRelease.config b/SampleWebApp/Web.AzureRelease.config deleted file mode 100644 index 7ea0845..0000000 --- a/SampleWebApp/Web.AzureRelease.config +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - Azure - - - Azure - - - - \ No newline at end of file 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.WebWizRelease.config b/SampleWebApp/Web.WebWizRelease.config deleted file mode 100644 index 451e45b..0000000 --- a/SampleWebApp/Web.WebWizRelease.config +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - WebWiz - - - jonsmith_ - - - - \ No newline at end of file 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..3e1a225 --- /dev/null +++ b/SampleWebApp/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Information" + } + } +} diff --git a/SampleWebApp/appsettings.json b/SampleWebApp/appsettings.json new file mode 100644 index 0000000..c9da4b1 --- /dev/null +++ b/SampleWebApp/appsettings.json @@ -0,0 +1,13 @@ +{ + "ConnectionStrings": { + "SampleWebAppDb": "Data Source=SampleWebAppDb.db" + }, + "HostTypeString": "LocalHost", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} 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/Content/Site.css b/SampleWebApp/wwwroot/Content/Site.css similarity index 100% rename from SampleWebApp/Content/Site.css rename to SampleWebApp/wwwroot/Content/Site.css diff --git a/SampleWebApp/Content/bootstrap-theme.css b/SampleWebApp/wwwroot/Content/bootstrap-theme.css similarity index 100% rename from SampleWebApp/Content/bootstrap-theme.css rename to SampleWebApp/wwwroot/Content/bootstrap-theme.css diff --git a/SampleWebApp/Content/bootstrap-theme.css.map b/SampleWebApp/wwwroot/Content/bootstrap-theme.css.map similarity index 100% rename from SampleWebApp/Content/bootstrap-theme.css.map rename to SampleWebApp/wwwroot/Content/bootstrap-theme.css.map diff --git a/SampleWebApp/Content/bootstrap-theme.min.css b/SampleWebApp/wwwroot/Content/bootstrap-theme.min.css similarity index 100% rename from SampleWebApp/Content/bootstrap-theme.min.css rename to SampleWebApp/wwwroot/Content/bootstrap-theme.min.css diff --git a/SampleWebApp/Content/bootstrap.css b/SampleWebApp/wwwroot/Content/bootstrap.css similarity index 100% rename from SampleWebApp/Content/bootstrap.css rename to SampleWebApp/wwwroot/Content/bootstrap.css diff --git a/SampleWebApp/Content/bootstrap.css.map b/SampleWebApp/wwwroot/Content/bootstrap.css.map similarity index 100% rename from SampleWebApp/Content/bootstrap.css.map rename to SampleWebApp/wwwroot/Content/bootstrap.css.map diff --git a/SampleWebApp/Content/bootstrap.min.css b/SampleWebApp/wwwroot/Content/bootstrap.min.css similarity index 100% rename from SampleWebApp/Content/bootstrap.min.css rename to SampleWebApp/wwwroot/Content/bootstrap.min.css diff --git a/SampleWebApp/Content/img/setup-progress.gif b/SampleWebApp/wwwroot/Content/img/setup-progress.gif similarity index 100% rename from SampleWebApp/Content/img/setup-progress.gif rename to SampleWebApp/wwwroot/Content/img/setup-progress.gif diff --git a/SampleWebApp/Content/img/task-progress.gif b/SampleWebApp/wwwroot/Content/img/task-progress.gif similarity index 100% rename from SampleWebApp/Content/img/task-progress.gif rename to SampleWebApp/wwwroot/Content/img/task-progress.gif diff --git a/SampleWebApp/Content/notify.css b/SampleWebApp/wwwroot/Content/notify.css similarity index 100% rename from SampleWebApp/Content/notify.css rename to SampleWebApp/wwwroot/Content/notify.css diff --git a/SampleWebApp/Content/notify.min.css b/SampleWebApp/wwwroot/Content/notify.min.css similarity index 100% rename from SampleWebApp/Content/notify.min.css rename to SampleWebApp/wwwroot/Content/notify.min.css diff --git a/SampleWebApp/Content/themes/base/images/animated-overlay.gif b/SampleWebApp/wwwroot/Content/themes/base/images/animated-overlay.gif similarity index 100% rename from SampleWebApp/Content/themes/base/images/animated-overlay.gif rename to SampleWebApp/wwwroot/Content/themes/base/images/animated-overlay.gif diff --git a/SampleWebApp/Content/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png diff --git a/SampleWebApp/Content/themes/base/images/ui-bg_flat_75_ffffff_40x100.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_flat_75_ffffff_40x100.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-bg_flat_75_ffffff_40x100.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_flat_75_ffffff_40x100.png diff --git a/SampleWebApp/Content/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png diff --git a/SampleWebApp/Content/themes/base/images/ui-bg_glass_65_ffffff_1x400.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_glass_65_ffffff_1x400.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-bg_glass_65_ffffff_1x400.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_glass_65_ffffff_1x400.png diff --git a/SampleWebApp/Content/themes/base/images/ui-bg_glass_75_dadada_1x400.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_glass_75_dadada_1x400.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-bg_glass_75_dadada_1x400.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_glass_75_dadada_1x400.png diff --git a/SampleWebApp/Content/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png diff --git a/SampleWebApp/Content/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png diff --git a/SampleWebApp/Content/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png diff --git a/SampleWebApp/Content/themes/base/images/ui-icons_222222_256x240.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-icons_222222_256x240.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-icons_222222_256x240.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-icons_222222_256x240.png diff --git a/SampleWebApp/Content/themes/base/images/ui-icons_2e83ff_256x240.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-icons_2e83ff_256x240.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-icons_2e83ff_256x240.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-icons_2e83ff_256x240.png diff --git a/SampleWebApp/Content/themes/base/images/ui-icons_454545_256x240.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-icons_454545_256x240.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-icons_454545_256x240.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-icons_454545_256x240.png diff --git a/SampleWebApp/Content/themes/base/images/ui-icons_888888_256x240.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-icons_888888_256x240.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-icons_888888_256x240.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-icons_888888_256x240.png diff --git a/SampleWebApp/Content/themes/base/images/ui-icons_cd0a0a_256x240.png b/SampleWebApp/wwwroot/Content/themes/base/images/ui-icons_cd0a0a_256x240.png similarity index 100% rename from SampleWebApp/Content/themes/base/images/ui-icons_cd0a0a_256x240.png rename to SampleWebApp/wwwroot/Content/themes/base/images/ui-icons_cd0a0a_256x240.png diff --git a/SampleWebApp/Content/themes/base/jquery-ui.css b/SampleWebApp/wwwroot/Content/themes/base/jquery-ui.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery-ui.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery-ui.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.accordion.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.accordion.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.accordion.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.accordion.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.all.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.all.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.all.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.all.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.autocomplete.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.autocomplete.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.autocomplete.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.autocomplete.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.base.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.base.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.base.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.base.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.button.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.button.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.button.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.button.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.core.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.core.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.core.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.core.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.datepicker.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.datepicker.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.datepicker.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.datepicker.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.dialog.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.dialog.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.dialog.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.dialog.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.menu.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.menu.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.menu.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.menu.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.progressbar.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.progressbar.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.progressbar.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.progressbar.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.resizable.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.resizable.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.resizable.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.resizable.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.selectable.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.selectable.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.selectable.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.selectable.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.slider.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.slider.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.slider.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.slider.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.spinner.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.spinner.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.spinner.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.spinner.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.tabs.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.tabs.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.tabs.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.tabs.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.theme.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.theme.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.theme.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.theme.css diff --git a/SampleWebApp/Content/themes/base/jquery.ui.tooltip.css b/SampleWebApp/wwwroot/Content/themes/base/jquery.ui.tooltip.css similarity index 100% rename from SampleWebApp/Content/themes/base/jquery.ui.tooltip.css rename to SampleWebApp/wwwroot/Content/themes/base/jquery.ui.tooltip.css diff --git a/SampleWebApp/Content/themes/base/minified/images/animated-overlay.gif b/SampleWebApp/wwwroot/Content/themes/base/minified/images/animated-overlay.gif similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/animated-overlay.gif rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/animated-overlay.gif diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-bg_flat_0_aaaaaa_40x100.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_flat_0_aaaaaa_40x100.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-bg_flat_0_aaaaaa_40x100.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_flat_0_aaaaaa_40x100.png diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-bg_flat_75_ffffff_40x100.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_flat_75_ffffff_40x100.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-bg_flat_75_ffffff_40x100.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_flat_75_ffffff_40x100.png diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-bg_glass_55_fbf9ee_1x400.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_glass_55_fbf9ee_1x400.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-bg_glass_55_fbf9ee_1x400.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_glass_55_fbf9ee_1x400.png diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-bg_glass_65_ffffff_1x400.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_glass_65_ffffff_1x400.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-bg_glass_65_ffffff_1x400.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_glass_65_ffffff_1x400.png diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-bg_glass_75_dadada_1x400.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_glass_75_dadada_1x400.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-bg_glass_75_dadada_1x400.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_glass_75_dadada_1x400.png diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-bg_glass_75_e6e6e6_1x400.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_glass_75_e6e6e6_1x400.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-bg_glass_75_e6e6e6_1x400.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_glass_75_e6e6e6_1x400.png diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-bg_glass_95_fef1ec_1x400.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_glass_95_fef1ec_1x400.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-bg_glass_95_fef1ec_1x400.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_glass_95_fef1ec_1x400.png diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_highlight-soft_75_cccccc_1x100.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-bg_highlight-soft_75_cccccc_1x100.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-bg_highlight-soft_75_cccccc_1x100.png diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-icons_222222_256x240.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-icons_222222_256x240.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-icons_222222_256x240.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-icons_222222_256x240.png diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-icons_2e83ff_256x240.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-icons_2e83ff_256x240.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-icons_2e83ff_256x240.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-icons_2e83ff_256x240.png diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-icons_454545_256x240.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-icons_454545_256x240.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-icons_454545_256x240.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-icons_454545_256x240.png diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-icons_888888_256x240.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-icons_888888_256x240.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-icons_888888_256x240.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-icons_888888_256x240.png diff --git a/SampleWebApp/Content/themes/base/minified/images/ui-icons_cd0a0a_256x240.png b/SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-icons_cd0a0a_256x240.png similarity index 100% rename from SampleWebApp/Content/themes/base/minified/images/ui-icons_cd0a0a_256x240.png rename to SampleWebApp/wwwroot/Content/themes/base/minified/images/ui-icons_cd0a0a_256x240.png diff --git a/SampleWebApp/Content/themes/base/minified/jquery-ui.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery-ui.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery-ui.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery-ui.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.accordion.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.accordion.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.accordion.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.accordion.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.autocomplete.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.autocomplete.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.autocomplete.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.autocomplete.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.button.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.button.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.button.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.button.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.core.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.core.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.core.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.core.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.datepicker.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.datepicker.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.datepicker.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.datepicker.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.dialog.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.dialog.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.dialog.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.dialog.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.menu.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.menu.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.menu.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.menu.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.progressbar.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.progressbar.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.progressbar.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.progressbar.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.resizable.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.resizable.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.resizable.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.resizable.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.selectable.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.selectable.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.selectable.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.selectable.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.slider.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.slider.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.slider.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.slider.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.spinner.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.spinner.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.spinner.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.spinner.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.tabs.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.tabs.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.tabs.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.tabs.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.theme.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.theme.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.theme.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.theme.min.css diff --git a/SampleWebApp/Content/themes/base/minified/jquery.ui.tooltip.min.css b/SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.tooltip.min.css similarity index 100% rename from SampleWebApp/Content/themes/base/minified/jquery.ui.tooltip.min.css rename to SampleWebApp/wwwroot/Content/themes/base/minified/jquery.ui.tooltip.min.css diff --git a/SampleWebApp/Scripts/ActionRunnerComms.js b/SampleWebApp/wwwroot/Scripts/ActionRunnerComms.js similarity index 100% rename from SampleWebApp/Scripts/ActionRunnerComms.js rename to SampleWebApp/wwwroot/Scripts/ActionRunnerComms.js diff --git a/SampleWebApp/Scripts/ActionRunnerUi.js b/SampleWebApp/wwwroot/Scripts/ActionRunnerUi.js similarity index 100% rename from SampleWebApp/Scripts/ActionRunnerUi.js rename to SampleWebApp/wwwroot/Scripts/ActionRunnerUi.js diff --git a/SampleWebApp/Scripts/_references.js b/SampleWebApp/wwwroot/Scripts/_references.js similarity index 100% rename from SampleWebApp/Scripts/_references.js rename to SampleWebApp/wwwroot/Scripts/_references.js diff --git a/SampleWebApp/Scripts/bootstrap.js b/SampleWebApp/wwwroot/Scripts/bootstrap.js similarity index 100% rename from SampleWebApp/Scripts/bootstrap.js rename to SampleWebApp/wwwroot/Scripts/bootstrap.js diff --git a/SampleWebApp/Scripts/bootstrap.min.js b/SampleWebApp/wwwroot/Scripts/bootstrap.min.js similarity index 100% rename from SampleWebApp/Scripts/bootstrap.min.js rename to SampleWebApp/wwwroot/Scripts/bootstrap.min.js diff --git a/SampleWebApp/Scripts/jquery-1.10.2.intellisense.js b/SampleWebApp/wwwroot/Scripts/jquery-1.10.2.intellisense.js similarity index 100% rename from SampleWebApp/Scripts/jquery-1.10.2.intellisense.js rename to SampleWebApp/wwwroot/Scripts/jquery-1.10.2.intellisense.js diff --git a/SampleWebApp/Scripts/jquery-1.10.2.js b/SampleWebApp/wwwroot/Scripts/jquery-1.10.2.js similarity index 100% rename from SampleWebApp/Scripts/jquery-1.10.2.js rename to SampleWebApp/wwwroot/Scripts/jquery-1.10.2.js diff --git a/SampleWebApp/Scripts/jquery-1.10.2.min.js b/SampleWebApp/wwwroot/Scripts/jquery-1.10.2.min.js similarity index 100% rename from SampleWebApp/Scripts/jquery-1.10.2.min.js rename to SampleWebApp/wwwroot/Scripts/jquery-1.10.2.min.js diff --git a/SampleWebApp/Scripts/jquery-1.10.2.min.map b/SampleWebApp/wwwroot/Scripts/jquery-1.10.2.min.map similarity index 100% rename from SampleWebApp/Scripts/jquery-1.10.2.min.map rename to SampleWebApp/wwwroot/Scripts/jquery-1.10.2.min.map diff --git a/SampleWebApp/Scripts/jquery-notify.js b/SampleWebApp/wwwroot/Scripts/jquery-notify.js similarity index 100% rename from SampleWebApp/Scripts/jquery-notify.js rename to SampleWebApp/wwwroot/Scripts/jquery-notify.js diff --git a/SampleWebApp/Scripts/jquery-notify.min.js b/SampleWebApp/wwwroot/Scripts/jquery-notify.min.js similarity index 100% rename from SampleWebApp/Scripts/jquery-notify.min.js rename to SampleWebApp/wwwroot/Scripts/jquery-notify.min.js diff --git a/SampleWebApp/Scripts/jquery-ui-1.10.4.js b/SampleWebApp/wwwroot/Scripts/jquery-ui-1.10.4.js similarity index 100% rename from SampleWebApp/Scripts/jquery-ui-1.10.4.js rename to SampleWebApp/wwwroot/Scripts/jquery-ui-1.10.4.js diff --git a/SampleWebApp/Scripts/jquery-ui-1.10.4.min.js b/SampleWebApp/wwwroot/Scripts/jquery-ui-1.10.4.min.js similarity index 100% rename from SampleWebApp/Scripts/jquery-ui-1.10.4.min.js rename to SampleWebApp/wwwroot/Scripts/jquery-ui-1.10.4.min.js diff --git a/SampleWebApp/Scripts/jquery.signalR-2.0.3.js b/SampleWebApp/wwwroot/Scripts/jquery.signalR-2.0.3.js similarity index 100% rename from SampleWebApp/Scripts/jquery.signalR-2.0.3.js rename to SampleWebApp/wwwroot/Scripts/jquery.signalR-2.0.3.js diff --git a/SampleWebApp/Scripts/jquery.signalR-2.0.3.min.js b/SampleWebApp/wwwroot/Scripts/jquery.signalR-2.0.3.min.js similarity index 100% rename from SampleWebApp/Scripts/jquery.signalR-2.0.3.min.js rename to SampleWebApp/wwwroot/Scripts/jquery.signalR-2.0.3.min.js diff --git a/SampleWebApp/Scripts/jquery.validate-vsdoc.js b/SampleWebApp/wwwroot/Scripts/jquery.validate-vsdoc.js similarity index 100% rename from SampleWebApp/Scripts/jquery.validate-vsdoc.js rename to SampleWebApp/wwwroot/Scripts/jquery.validate-vsdoc.js diff --git a/SampleWebApp/Scripts/jquery.validate.js b/SampleWebApp/wwwroot/Scripts/jquery.validate.js similarity index 100% rename from SampleWebApp/Scripts/jquery.validate.js rename to SampleWebApp/wwwroot/Scripts/jquery.validate.js diff --git a/SampleWebApp/Scripts/jquery.validate.min.js b/SampleWebApp/wwwroot/Scripts/jquery.validate.min.js similarity index 100% rename from SampleWebApp/Scripts/jquery.validate.min.js rename to SampleWebApp/wwwroot/Scripts/jquery.validate.min.js diff --git a/SampleWebApp/Scripts/jquery.validate.unobtrusive.js b/SampleWebApp/wwwroot/Scripts/jquery.validate.unobtrusive.js similarity index 100% rename from SampleWebApp/Scripts/jquery.validate.unobtrusive.js rename to SampleWebApp/wwwroot/Scripts/jquery.validate.unobtrusive.js diff --git a/SampleWebApp/Scripts/jquery.validate.unobtrusive.min.js b/SampleWebApp/wwwroot/Scripts/jquery.validate.unobtrusive.min.js similarity index 100% rename from SampleWebApp/Scripts/jquery.validate.unobtrusive.min.js rename to SampleWebApp/wwwroot/Scripts/jquery.validate.unobtrusive.min.js diff --git a/SampleWebApp/Scripts/modernizr-2.6.2.js b/SampleWebApp/wwwroot/Scripts/modernizr-2.6.2.js similarity index 100% rename from SampleWebApp/Scripts/modernizr-2.6.2.js rename to SampleWebApp/wwwroot/Scripts/modernizr-2.6.2.js diff --git a/SampleWebApp/Scripts/npm.js b/SampleWebApp/wwwroot/Scripts/npm.js similarity index 100% rename from SampleWebApp/Scripts/npm.js rename to SampleWebApp/wwwroot/Scripts/npm.js diff --git a/SampleWebApp/Scripts/respond.js b/SampleWebApp/wwwroot/Scripts/respond.js similarity index 100% rename from SampleWebApp/Scripts/respond.js rename to SampleWebApp/wwwroot/Scripts/respond.js diff --git a/SampleWebApp/Scripts/respond.min.js b/SampleWebApp/wwwroot/Scripts/respond.min.js similarity index 100% rename from SampleWebApp/Scripts/respond.min.js rename to SampleWebApp/wwwroot/Scripts/respond.min.js diff --git a/SampleWebApp/favicon.ico b/SampleWebApp/wwwroot/favicon.ico similarity index 100% rename from SampleWebApp/favicon.ico rename to SampleWebApp/wwwroot/favicon.ico diff --git a/SampleWebApp/fonts/glyphicons-halflings-regular.eot b/SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from SampleWebApp/fonts/glyphicons-halflings-regular.eot rename to SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.eot diff --git a/SampleWebApp/fonts/glyphicons-halflings-regular.svg b/SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from SampleWebApp/fonts/glyphicons-halflings-regular.svg rename to SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.svg diff --git a/SampleWebApp/fonts/glyphicons-halflings-regular.ttf b/SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from SampleWebApp/fonts/glyphicons-halflings-regular.ttf rename to SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.ttf diff --git a/SampleWebApp/fonts/glyphicons-halflings-regular.woff b/SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from SampleWebApp/fonts/glyphicons-halflings-regular.woff rename to SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.woff diff --git a/SampleWebApp/fonts/glyphicons-halflings-regular.woff2 b/SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from SampleWebApp/fonts/glyphicons-halflings-regular.woff2 rename to SampleWebApp/wwwroot/fonts/glyphicons-halflings-regular.woff2 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/PostServices/DetailPostDtoAsync.cs b/ServiceLayer/PostServices/DetailPostDtoAsync.cs index dd27b14..d434656 100644 --- a/ServiceLayer/PostServices/DetailPostDtoAsync.cs +++ b/ServiceLayer/PostServices/DetailPostDtoAsync.cs @@ -27,7 +27,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Data.Entity; +using Microsoft.EntityFrameworkCore; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; 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..ec9f173 100644 --- a/ServiceLayer/ServiceLayer.csproj +++ b/ServiceLayer/ServiceLayer.csproj @@ -1,136 +1,17 @@ - - - + + - Debug - AnyCPU - {D2813927-0F38-43C3-B47C-AE8F00D50CAE} - Library - Properties + net8.0 + disable + disable ServiceLayer ServiceLayer - 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 - - - - 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..1a5df1a 100644 --- a/ServiceLayer/Startup/ServiceLayerInitialise.cs +++ b/ServiceLayer/Startup/ServiceLayerInitialise.cs @@ -1,55 +1,23 @@ -#region licence -// The MIT License (MIT) -// -// Filename: ServiceLayerInitialise.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.Text; +using DataLayer.DataClasses; using DataLayer.Startup; - namespace ServiceLayer.Startup { /// - /// This handles the initialisation of this layer and any other layers + /// This handles the initialisation of this layer and any other layers. /// public static class ServiceLayerInitialise { /// - /// This should be called at Startup + /// 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) + /// the application DbContext + /// true if the app may create/migrate the database + public static void InitialiseThis(SampleWebAppDb context, bool canCreateDatabase) { - //Place any tasks that need initialising here - DataLayerInitialise.InitialiseThis(isAzure, canCreateDatabase); - + DataLayerInitialise.InitialiseThis(context, canCreateDatabase); } } } 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/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/DependencyItems/ClassToTestClassWithPrivateCtor.cs b/Tests/DependencyItems/ClassToTestClassWithPrivateCtor.cs deleted file mode 100644 index d4e889e..0000000 --- a/Tests/DependencyItems/ClassToTestClassWithPrivateCtor.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Tests.DependencyItems -{ - internal interface IClassToTestClassWithPrivateCtor - { - } - - internal interface ISetType - { - void SetType(Type resolvedType); - } - - class ClassToTestClassWithPrivateCtor : IClassToTestClassWithPrivateCtor, ISetType - { - private Type _classType; - - public void SetType(Type resolvedType) - { - _classType = resolvedType; - } - } -} diff --git a/Tests/DependencyItems/ClassWithPrivateCtor.cs b/Tests/DependencyItems/ClassWithPrivateCtor.cs deleted file mode 100644 index 5ac6bc3..0000000 --- a/Tests/DependencyItems/ClassWithPrivateCtor.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Tests.DependencyItems -{ - internal interface IClassWithPrivateCtor - { - } - - class ClassWithPrivateCtor : IClassWithPrivateCtor - { - - private ClassWithPrivateCtor() { } - - } -} diff --git a/Tests/DependencyItems/ConstructorParamClass.cs b/Tests/DependencyItems/ConstructorParamClass.cs deleted file mode 100644 index 252b109..0000000 --- a/Tests/DependencyItems/ConstructorParamClass.cs +++ /dev/null @@ -1,44 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: ConstructorParamClass.cs -// Date Created: 2014/05/22 -// -// 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 -namespace Tests.DependencyItems -{ - public interface IConstructorParamClass - { - int MyInt { get; } - } - - public class ConstructorParamClass : IConstructorParamClass - { - - public int MyInt { get; private set; } - - public ConstructorParamClass(int myInt) - { - MyInt = myInt; - } - } -} diff --git a/Tests/DependencyItems/GenericInterface.cs b/Tests/DependencyItems/GenericInterface.cs deleted file mode 100644 index 240e14c..0000000 --- a/Tests/DependencyItems/GenericInterface.cs +++ /dev/null @@ -1,43 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: GenericInterface.cs -// Date Created: 2014/05/22 -// -// 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 -namespace Tests.DependencyItems -{ - public interface IGenericInterface where T : class - { - string GetTypeName(); - } - - public class GenericInterface : IGenericInterface where T : class - { - - public string GetTypeName() - { - return typeof (T).Name; - } - - } -} diff --git a/Tests/DependencyItems/MyDisposableClass.cs b/Tests/DependencyItems/MyDisposableClass.cs deleted file mode 100644 index 2fbce42..0000000 --- a/Tests/DependencyItems/MyDisposableClass.cs +++ /dev/null @@ -1,64 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: MyDisposableClass.cs -// Date Created: 2014/05/22 -// -// 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; - -namespace Tests.DependencyItems -{ - public interface IMyDisposableClass { } - - public class MyDisposableClass : IMyDisposableClass, IDisposable - { - - private readonly Action _disposeWasCalled; - - public MyDisposableClass(Action disposeWasCalled) - { - _disposeWasCalled = disposeWasCalled; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool _disposed; - - protected virtual void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing) - { - _disposeWasCalled(); - } - } - _disposed = true; - } - - } -} diff --git a/Tests/DependencyItems/ServiceTypeClass.cs b/Tests/DependencyItems/ServiceTypeClass.cs deleted file mode 100644 index 7581d98..0000000 --- a/Tests/DependencyItems/ServiceTypeClass.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Tests.DependencyItems -{ - class ServiceTypeClass - { - private Type _resolvedType; - - public ServiceTypeClass(Type resolvedType) - { - this._resolvedType = resolvedType; - } - } -} diff --git a/Tests/DependencyItems/SimpleClass.cs b/Tests/DependencyItems/SimpleClass.cs deleted file mode 100644 index c4a1933..0000000 --- a/Tests/DependencyItems/SimpleClass.cs +++ /dev/null @@ -1,40 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: SimpleClass.cs -// Date Created: 2014/05/22 -// -// 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 -namespace Tests.DependencyItems -{ - public interface ISimpleClass - { - void DoSomething(); - }; - - public class SimpleClass : ISimpleClass - { - public void DoSomething() - { - } - } -} diff --git a/Tests/Docs/Flow of control when running action with progress and cancel.pdf b/Tests/Docs/Flow of control when running action with progress and cancel.pdf deleted file mode 100644 index 32a7d0c..0000000 Binary files a/Tests/Docs/Flow of control when running action with progress and cancel.pdf and /dev/null differ diff --git a/Tests/Docs/Flow of control when running action with progress and cancel.vsd b/Tests/Docs/Flow of control when running action with progress and cancel.vsd deleted file mode 100644 index fdc3cb9..0000000 Binary files a/Tests/Docs/Flow of control when running action with progress and cancel.vsd and /dev/null differ diff --git a/Tests/Helpers/DbSnapShot.cs b/Tests/Helpers/DbSnapShot.cs deleted file mode 100644 index a28d446..0000000 --- a/Tests/Helpers/DbSnapShot.cs +++ /dev/null @@ -1,62 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: DbSnapShot.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.Linq; -using DataLayer.DataClasses; - -namespace Tests.Helpers -{ - public class DbSnapShot - { - - public int NumPosts { get; private set; } - - public int NumPostTagLinks { get; private set; } - - public int NumBlogs { get; private set; } - - public int NumTags { get; private set; } - - public DbSnapShot(SampleWebAppDb db) - { - NumBlogs = db.Blogs.Count(); - NumPostTagLinks = db.Database.SqlQuery("SELECT COUNT(*) FROM dbo.TagPosts").First(); - NumPosts = db.Posts.Count(); - NumTags = db.Tags.Count(); - } - - public void CheckSnapShot(SampleWebAppDb db, int postsChange = 0, int postTagLinkChange = 0, int blogsChange = 0, int tagsChange = 0) - { - var newSnap = new DbSnapShot(db); - - newSnap.NumPosts.ShouldEqual(NumPosts + postsChange, "posts wrong"); - newSnap.NumPostTagLinks.ShouldEqual(NumPostTagLinks + postTagLinkChange, "posttaglinks wrong"); - newSnap.NumBlogs.ShouldEqual(NumBlogs + blogsChange, "blogs wrong"); - newSnap.NumTags.ShouldEqual(NumTags + tagsChange, "tags wrong"); - } - - } -} diff --git a/Tests/Helpers/DummyIDbContextWithValidation.cs b/Tests/Helpers/DummyIDbContextWithValidation.cs deleted file mode 100644 index dd1544c..0000000 --- a/Tests/Helpers/DummyIDbContextWithValidation.cs +++ /dev/null @@ -1,85 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: DummyIDbContextWithValidation.cs -// Date Created: 2014/06/26 -// -// 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.Data.Entity; -using System.Data.Entity.Infrastructure; -using System.Data.Entity.Validation; -using System.Threading.Tasks; -using GenericServices; -using GenericServices.Core; - -namespace Tests.Helpers -{ - public class DummyIDbContextWithValidation : IGenericServicesDbContext - { - - public bool SaveChangesWithValidationCalled { get; private set; } - - - public DbSet Set() where TEntity : class - { - throw new NotImplementedException(); - } - - public DbSet Set(Type entityType) - { - throw new NotImplementedException(); - } - - public int SaveChanges() - { - SaveChangesWithValidationCalled = true; - return 1; - } - - public async Task SaveChangesAsync() - { - SaveChangesWithValidationCalled = true; - return 1; - } - - public IEnumerable GetValidationErrors() - { - throw new NotImplementedException(); - } - - public DbEntityEntry Entry(TEntity entity) where TEntity : class - { - throw new NotImplementedException(); - } - - public DbEntityEntry Entry(object entity) - { - throw new NotImplementedException(); - } - - public void Dispose() - { - } - } -} diff --git a/Tests/Helpers/ExtendAsserts.cs b/Tests/Helpers/ExtendAsserts.cs deleted file mode 100644 index 8e85c05..0000000 --- a/Tests/Helpers/ExtendAsserts.cs +++ /dev/null @@ -1,111 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: ExtendAsserts.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.Collections.Generic; -using System.Linq; -using System.ComponentModel.DataAnnotations; -using NUnit.Framework; - -namespace Tests.Helpers -{ - internal static class ExtendAsserts - { - internal static void ShouldEqual(this string actualValue, string expectedValue, string errorMessage = null) - { - Assert.AreEqual(expectedValue, actualValue, errorMessage); - } - - internal static void ShouldStartWith(this string actualValue, string expectedValue, string errorMessage = null) - { - StringAssert.StartsWith(expectedValue, actualValue, errorMessage); - } - - internal static void ShouldEndWith(this string actualValue, string expectedValue, string errorMessage = null) - { - StringAssert.EndsWith(expectedValue, actualValue, errorMessage); - } - - internal static void ShouldContain(this string actualValue, string expectedValue, string errorMessage = null) - { - StringAssert.Contains(expectedValue, actualValue, errorMessage); - } - - internal static void ShouldNotEqual(this string actualValue, string expectedValue, string errorMessage = null) - { - Assert.True(expectedValue != actualValue, errorMessage); - } - - internal static void ShouldEqualWithTolerance(this float actualValue, double expectedValue, double tolerance, string errorMessage = null) - { - Assert.AreEqual(expectedValue, actualValue, tolerance, errorMessage); - } - - internal static void ShouldEqualWithTolerance(this long actualValue, long expectedValue, int tolerance, string errorMessage = null) - { - Assert.AreEqual(expectedValue, actualValue, tolerance, errorMessage); - } - - internal static void ShouldEqualWithTolerance(this double actualValue, double expectedValue, double tolerance, string errorMessage = null) - { - Assert.AreEqual(expectedValue, actualValue, tolerance, errorMessage); - } - - internal static void ShouldEqualWithTolerance(this int actualValue, int expectedValue, int tolerance, string errorMessage = null) - { - Assert.AreEqual(expectedValue, actualValue, tolerance, errorMessage); - } - - internal static void ShouldEqual( this T actualValue, T expectedValue, string errorMessage = null) - { - Assert.AreEqual(expectedValue, actualValue, errorMessage); - } - - internal static void ShouldEqual(this T actualValue, T expectedValue, IEnumerable errorMessages) - { - Assert.AreEqual(expectedValue, actualValue, string.Join("\n", errorMessages)); - } - - internal static void ShouldEqual(this T actualValue, T expectedValue, IEnumerable validationResults) - { - Assert.AreEqual(expectedValue, actualValue, string.Join("\n", validationResults.Select( x => x.ErrorMessage))); - } - - internal static void ShouldNotEqual(this T actualValue, T unexpectedValue, string errorMessage = null) - { - Assert.AreNotEqual(unexpectedValue, actualValue); - } - - internal static void ShouldNotEqualNull(this T actualValue, string errorMessage = null) where T : class - { - Assert.NotNull( actualValue); - } - - internal static void IsA(this object actualValue, string errorMessage = null) - { - Assert.True(actualValue.GetType() == typeof(T)); - } - } -} diff --git a/Tests/Helpers/JsonHelper.cs b/Tests/Helpers/JsonHelper.cs deleted file mode 100644 index 2bf617f..0000000 --- a/Tests/Helpers/JsonHelper.cs +++ /dev/null @@ -1,129 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: JsonHelper.cs -// Date Created: 2014/05/31 -// -// 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.ComponentModel.DataAnnotations; -using System.Linq.Expressions; -using System.Reflection; -using System.Web.Helpers; -using Newtonsoft.Json; -using NUnit.Framework; - -namespace Tests.Helpers -{ - static class JsonHelper - { - - //public static string SerialiseToJsonUsingJsonNet(this object data) - //{ - // JsonConvert.SerializeObject(data); - // - //} - - //public static string SerialiseToJsonIndentedUsingJsonNet(this object data) - //{ - // return JsonConvert.SerializeObject(data, Formatting.Indented); - //} - - public static string SerialiseToJson(this object data) - { - return Json.Encode(data); - } - - public static string AssertJsonPropertyPresentAndReturnValue - (this string jsonString, - TSource source, Expression> propertyLambda) - { - return jsonString.AssertJsonPropertyInOrOutAndReturnValue(source, propertyLambda, false); - } - - public static void AssertJsonPropertyNotPresent - (this string jsonString, - TSource source, Expression> propertyLambda) - { - jsonString.AssertJsonPropertyInOrOutAndReturnValue(source, propertyLambda, true); - } - - //----------------------------------------------------------------- - - private static string AssertJsonPropertyInOrOutAndReturnValue - (this string jsonString, - TSource source, Expression> propertyLambda, - bool doesNotContain) - { - - var stringToFind = string.Format("\"{0}\":", source.GetPropertyInfo(propertyLambda).Name); - var startIndex = jsonString.IndexOf(stringToFind, StringComparison.InvariantCultureIgnoreCase); - if (doesNotContain) - { - if (startIndex == -1) return null; //all good - Assert.Fail("The property '{0}' should NOT be in the in json string", stringToFind); - } - - //otherwise we expect it to be in - if (startIndex == -1) - Assert.Fail("Looked for '{0}' in json and could not find it", stringToFind); - - //now return value after it - - var closingIndex = jsonString.IndexOf('\n', startIndex + 1); - if (closingIndex == -1) - throw new ValidationException("This only works on indented json, and this doesn't seem to be indented"); - - var result = jsonString.Substring(startIndex + stringToFind.Length, closingIndex - startIndex - stringToFind.Length).Trim(); - return result.EndsWith(",") ? result.Substring(0, result.Length - 1).Trim() : result; - } - - public static PropertyInfo GetPropertyInfo( - this TSource source, - Expression> propertyLambda) - { - Type type = typeof(TSource); - - MemberExpression member = propertyLambda.Body as MemberExpression; - if (member == null) - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a method, not a property.", - propertyLambda.ToString())); - - PropertyInfo propInfo = member.Member as PropertyInfo; - if (propInfo == null) - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a field, not a property.", - propertyLambda.ToString())); - - if (type != propInfo.ReflectedType && - !type.IsSubclassOf(propInfo.ReflectedType)) - throw new ArgumentException(string.Format( - "Expresion '{0}' refers to a property that is not from type {1}.", - propertyLambda.ToString(), - type)); - - return propInfo; - } - - } -} diff --git a/Tests/Helpers/ModelStateTester.cs b/Tests/Helpers/ModelStateTester.cs deleted file mode 100644 index 8f3068b..0000000 --- a/Tests/Helpers/ModelStateTester.cs +++ /dev/null @@ -1,102 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: ModelStateTester.cs -// Date Created: 2014/06/10 -// -// 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.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel.DataAnnotations; -using System.Globalization; -using System.Web.Mvc; - -namespace Tests.Helpers -{ - static class ModelStateTester - { - - public class TestModel : IValidatableObject - { - [MinLength(2)] - [Required] - public string MyString { get; set; } - - [Range(0,100)] - public int MyInt { get; set; } - - public bool CreateValidationError { get; set; } - - public IEnumerable Validate(ValidationContext validationContext) - { - if (CreateValidationError) - yield return new ValidationResult("This is a top level error caused by CreateValidationError being set."); - - if (MyInt == 50) - yield return new ValidationResult("This is a top level error caused by MyInt having value 50."); - } - - public TestModel() - { - } - - public TestModel(string myString, int myInt, bool createValidationError) - { - MyString = myString; - MyInt = myInt; - CreateValidationError = createValidationError; - } - } - - - private class TestController : Controller - { - public ActionResult ValidDateTestModel(TestModel model) - { - // ReSharper disable once Mvc.ViewNotResolved - return View(model); - } - } - - public static ModelStateDictionary ReturnModelState(this TestModel model) - { - var testController = new TestController(); - - var modelBinder = new ModelBindingContext() - { - ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType( - () => model, model.GetType()), - ValueProvider = new NameValueCollectionValueProvider( - new NameValueCollection(), CultureInfo.InvariantCulture) - }; - var binder = new DefaultModelBinder().BindModel( - new ControllerContext(), modelBinder); - testController.ModelState.Clear(); - testController.ModelState.Merge(modelBinder.ModelState); - - var viewResult = (ViewResult) testController.ValidDateTestModel(model); - return viewResult.ViewData.ModelState; - } - - - } -} diff --git a/Tests/Helpers/SimpleTagDto.cs b/Tests/Helpers/SimpleTagDto.cs deleted file mode 100644 index 62d4dc8..0000000 --- a/Tests/Helpers/SimpleTagDto.cs +++ /dev/null @@ -1,56 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: SimpleTagDto.cs -// Date Created: 2014/06/26 -// -// 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.ComponentModel.DataAnnotations; -using DataLayer.DataClasses.Concrete; -using GenericServices.Core; - -namespace Tests.Helpers -{ - class SimpleTagDto : InstrumentedEfGenericDto - { - - [Key] - public int TagId { get; set; } - - [MaxLength(64)] - [Required] - [RegularExpression(@"\w*", ErrorMessage = "The slug must not contain spaces or non-alphanumeric characters.")] - public string Slug { get; set; } - - [MaxLength(128)] - [Required] - public string Name { get; set; } - - //-------------------------------------- - - protected internal override CrudFunctions SupportedFunctions - { - get { return CrudFunctions.AllCrud; } - } - - } -} diff --git a/Tests/Helpers/SimpleTagDtoAsync.cs b/Tests/Helpers/SimpleTagDtoAsync.cs deleted file mode 100644 index 60256b4..0000000 --- a/Tests/Helpers/SimpleTagDtoAsync.cs +++ /dev/null @@ -1,59 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: SimpleTagDtoAsync.cs -// Date Created: 2014/06/26 -// -// 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.ComponentModel.DataAnnotations; -using DataLayer.DataClasses.Concrete; -using GenericServices.Core; - -namespace Tests.Helpers -{ - class SimpleTagDtoAsync : InstrumentedEfGenericDtoAsync - { - - - [Key] - public int TagId { get; set; } - - [MaxLength(64)] - [Required] - [RegularExpression(@"\w*", ErrorMessage = "The slug must not contain spaces or non-alphanumeric characters.")] - public string Slug { get; set; } - - [MaxLength(128)] - [Required] - public string Name { get; set; } - - - //-------------------------------------- - - - protected internal override CrudFunctions SupportedFunctions - { - get { return CrudFunctions.AllCrud; } - } - - } -} diff --git a/Tests/Helpers/TestFileHelpers.cs b/Tests/Helpers/TestFileHelpers.cs deleted file mode 100644 index bb2080b..0000000 --- a/Tests/Helpers/TestFileHelpers.cs +++ /dev/null @@ -1,108 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: TestFileHelpers.cs -// Date Created: 2014/06/27 -// -// 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.IO; - -namespace Tests.Helpers -{ - internal static class TestFileHelpers - { - private const string TestFileDirectoryName = @"\TestData"; - - //------------------------------------------------------------------- - - internal static string GetTestFileFilePath(string searchPattern) - { - string[] fileList = GetTestFileFilesOfGivenName(searchPattern); - - if (fileList.Length != 1) - throw new Exception(string.Format("GetTestFileFilePath: The searchString {0} found {1} file. Either not there or ambiguous", - searchPattern, fileList.Length)); - - return fileList[0]; - } - - internal static string GetTestFileContent(string searchPattern) - { - var filePath = GetTestFileFilePath(searchPattern); - return File.ReadAllText(filePath); - } - - internal static string[] GetTestFileFilesOfGivenName(string searchPattern = "") - { - var directory = GetTestDataFileDirectory(); - if (searchPattern.Contains(@"\")) - { - //Has subdirectory in search pattern, so change directory - directory = Path.Combine(directory, searchPattern.Substring(0, searchPattern.LastIndexOf('\\'))); - searchPattern = searchPattern.Substring(searchPattern.LastIndexOf('\\')+1); - } - - string[] fileList = Directory.GetFiles(directory, searchPattern); - - return fileList; - } - - - //------------------------------------------------------------------------------ - - public static string GetTestDataFileDirectory(string alternateTestDir = TestFileDirectoryName) - { - string pathToManipulate = Environment.CurrentDirectory; - const string debugEnding = @"\bin\debug"; - const string releaseEnding = @"\bin\release"; - - if (pathToManipulate.EndsWith(debugEnding, StringComparison.InvariantCultureIgnoreCase)) - return pathToManipulate.Substring(0, pathToManipulate.Length - debugEnding.Length) + alternateTestDir; - if (pathToManipulate.EndsWith(releaseEnding, StringComparison.InvariantCultureIgnoreCase)) - return pathToManipulate.Substring(0, pathToManipulate.Length - releaseEnding.Length) + alternateTestDir; - - throw new Exception("bad news guys. Not the expected path"); - - } - - public static string GetSolutionDirectory() - { - string pathToManipulate = Environment.CurrentDirectory; - const string debugEnding = @"\bin\debug"; - const string releaseEnding = @"\bin\release"; - - string projectDir = null; - if (pathToManipulate.EndsWith(debugEnding, StringComparison.InvariantCultureIgnoreCase)) - projectDir = pathToManipulate.Substring(0, pathToManipulate.Length - debugEnding.Length); - if (pathToManipulate.EndsWith(releaseEnding, StringComparison.InvariantCultureIgnoreCase)) - projectDir = pathToManipulate.Substring(0, pathToManipulate.Length - releaseEnding.Length); - - if (projectDir == null) - throw new Exception("bad news guys. Not the expected path"); - - return projectDir.Substring(0, projectDir.LastIndexOf("\\", StringComparison.Ordinal)); - - } - - } -} diff --git a/Tests/Helpers/TestServices.cs b/Tests/Helpers/TestServices.cs new file mode 100644 index 0000000..ee7c7ec --- /dev/null +++ b/Tests/Helpers/TestServices.cs @@ -0,0 +1,48 @@ +using System; +using DataLayer.DataClasses; +using DataLayer.Startup; +using GenericServices; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using ServiceLayer.PostServices; + +namespace Tests.Helpers +{ + /// + /// Builds an isolated, seeded application stack (EF Core SQLite in-memory + GenericServices + /// via the built-in DI container) that mirrors how Program.cs wires the running web app. + /// Each instance owns its own database, so tests do not interfere with each other. + /// + public sealed class TestServices : IDisposable + { + private readonly SqliteConnection _connection; + private readonly ServiceProvider _provider; + + public TestServices(TestDataSelection selection = TestDataSelection.Small) + { + _connection = new SqliteConnection("DataSource=:memory:"); + _connection.Open(); + + var services = new ServiceCollection(); + services.AddDbContext(options => options.UseSqlite(_connection)); + services.AddScoped(sp => sp.GetRequiredService()); + services.AddGenericServices(typeof(SimplePostDto).Assembly); + _provider = services.BuildServiceProvider(); + + using var scope = _provider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + context.Database.EnsureCreated(); + DataLayerInitialise.ResetBlogs(context, selection); + } + + /// Creates a new DI scope. Dispose it to release the scoped DbContext. + public IServiceScope CreateScope() => _provider.CreateScope(); + + public void Dispose() + { + _provider.Dispose(); + _connection.Dispose(); + } + } +} 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/Properties/Settings.Designer.cs b/Tests/Properties/Settings.Designer.cs deleted file mode 100644 index f2bf076..0000000 --- a/Tests/Properties/Settings.Designer.cs +++ /dev/null @@ -1,38 +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 Tests.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.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("jonsmith_")] - public string DatabaseLoginPrefix { - get { - return ((string)(this["DatabaseLoginPrefix"])); - } - set { - this["DatabaseLoginPrefix"] = value; - } - } - } -} diff --git a/Tests/Properties/Settings.settings b/Tests/Properties/Settings.settings deleted file mode 100644 index 74f592a..0000000 --- a/Tests/Properties/Settings.settings +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - jonsmith_ - - - \ No newline at end of file diff --git a/Tests/TestData/TestRadio4 schedule 2014-7-3.xml b/Tests/TestData/TestRadio4 schedule 2014-7-3.xml deleted file mode 100644 index 05116c3..0000000 --- a/Tests/TestData/TestRadio4 schedule 2014-7-3.xml +++ /dev/null @@ -1,2375 +0,0 @@ - - - - BBC Radio 4 - - FM - - - - - - p021khy6 - 2014-07-03T00:00:00+01:00 - 2014-07-03T00:30:00+01:00 - 1800 - - b047w7gj - - 03/07/2014 - The latest national and international news from BBC Radio 4. - audio - 1800 - - p01lcbf6 - - - Midnight News - 03/07/2014 - - 2014-07-03T00:00:00+01:00 - - - BBC Radio 4 - - - - b00cs19l - Midnight News - - - p01lcbf6 - - - 2007-02-03T00:00:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021khy7 - 2014-07-03T00:30:00+01:00 - 2014-07-03T00:48:00+01:00 - 1080 - - b047zlbz - 3 - How El Valero Got Its Name - A moving story from pal Rogelio, convinces Chris to compile an anthology of such stories. - audio - 900 - - p021gf7t - - - Book of the Week - Last Days of the Bus Club, How El Valero Got Its Name - - 2014-07-02T09:45:00+01:00 - - - BBC Radio 4 - - - - b0490f3d - Last Days of the Bus Club - - - p021gf7t - - 5 - 2014-06-30T09:45:00+01:00 - - b006qftk - Book of the Week - - - p01lc1rh - - - 2007-01-01T09:45:00Z - - - BBC Radio 4 - - - - - 0 - 0 - - - - p021khy8 - 2014-07-03T00:48:00+01:00 - 2014-07-03T01:00:00+01:00 - 720 - - b047w7gl - - 03/07/2014 - The latest shipping forecast. - audio - 660 - - p01lc1xj - - - Shipping Forecast - 03/07/2014 - - 2014-07-03T00:48:00+01:00 - - - BBC Radio 4 - - - - b006qfvv - Shipping Forecast - - - p01lc1xj - - - 2006-04-05T00:48:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021khy9 - 2014-07-03T01:00:00+01:00 - 2014-07-03T05:20:00+01:00 - 15600 - - b047w7gn - - 03/07/2014 - BBC Radio 4 joins the BBC World Service. - audio - 15600 - - p01lchw0 - - - Selection of BBC World Service Programmes - 03/07/2014 - - 2014-07-03T01:00:00+01:00 - - - BBC World Service - - - - b006s54y - Selection of BBC World Service Programmes - - - p01lchw0 - - - 2007-01-06T01:00:00Z - - - BBC World Service - - - - 0 - 0 - - - - p021khyb - 2014-07-03T05:20:00+01:00 - 2014-07-03T05:30:00+01:00 - 600 - - b047w7gq - - 03/07/2014 - The latest shipping forecast. - audio - 600 - - p01lc1xj - - - Shipping Forecast - 03/07/2014 - - 2014-07-03T05:20:00+01:00 - - - BBC Radio 4 - - - - b006qfvv - Shipping Forecast - - - p01lc1xj - - - 2006-04-05T00:48:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021khyd - 2014-07-03T05:30:00+01:00 - 2014-07-03T05:43:00+01:00 - 780 - - b047w7gs - - 03/07/2014 - News from BBC Radio 4. - audio - 780 - - p01lc375 - - - News Briefing - 03/07/2014 - - 2014-07-03T05:30:00+01:00 - - - BBC Radio 4 - - - - b007rhyn - News Briefing - - - p01lc375 - - - 2007-02-03T05:30:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021khyf - 2014-07-03T05:43:00+01:00 - 2014-07-03T05:45:00+01:00 - 120 - - b0483p66 - - 03/07/2014 - A spiritual comment and prayer to begin the day with Sheikh Michael Mumisa. - audio - 120 - - p01lc206 - - - Prayer for the Day - 03/07/2014 - - 2014-07-03T05:43:00+01:00 - - - BBC Radio 4 - - - - b006qmpj - Prayer for the Day - - - p01lc206 - - - 2007-01-06T05:43:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021khyg - 2014-07-03T05:45:00+01:00 - 2014-07-03T05:58:00+01:00 - 780 - - b048033l - - 03/07/2014 - The latest news about food, farming and the countryside. Presented by Charlotte Smith. - audio - 780 - - p01wrdlh - - - Farming Today - 03/07/2014 - - 2014-07-03T05:45:00+01:00 - - - BBC Radio 4 - - - - b006qj8q - Farming Today - - - p01lcqw7 - - - 2007-01-06T06:35:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021khyh - 2014-07-03T05:58:00+01:00 - 2014-07-03T06:00:00+01:00 - 120 - - b0378sqk - - Stonechat - Michaela Strachan presents the story and sound of the stonechat. - audio - 120 - - p01k03yg - - - Tweet of the Day - Stonechat - - 2013-08-01T05:58:00+01:00 - - - BBC Radio 4 - - - - b01s6xyk - Tweet of the Day - - 265 - 2013-05-06T05:58:00+01:00 - - - BBC Radio 4 - - - - 2013-08-01T06:37:09+01:00 - 1 - 0 - - - Available to listen - - - - - p021khyj - 2014-07-03T06:00:00+01:00 - 2014-07-03T09:00:00+01:00 - 10800 - - b048033n - 21027 - 03/07/2014 - Morning news. Includes Sports Desk; Yesterday in Parliament; Weather; Thought for the Day. - audio - 10800 - - p01lc1m2 - - - Today - 03/07/2014 - - 2014-07-03T06:00:00+01:00 - - - BBC Radio 4 - - - - b006qj9z - Today - - - p01lc1m2 - - - 1966-10-27T07:15:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfnr - 2014-07-03T09:00:00+01:00 - 2014-07-03T09:45:00+01:00 - 2700 - - b048033q - 637 - Mrs Dalloway - Melvyn Bragg and his guests discuss Virginia Woolf's novel Mrs Dalloway. - audio - 2580 - - p021n60h - - - In Our Time - Mrs Dalloway - - 2014-07-03T09:00:00+01:00 - - - BBC Radio 4 - - - - b006qykl - In Our Time - - - p01lc1r8 - - - 1998-10-15T09:02:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfns - 2014-07-03T09:45:00+01:00 - 2014-07-03T10:00:00+01:00 - 900 - - b048033s - 4 - The Rain in Spain: Part 1 - Andalucia is a region racked with droughts, but one winter the downpour was biblical. - audio - 900 - - p021gf7t - - - Book of the Week - Last Days of the Bus Club, The Rain in Spain: Part 1 - - 2014-07-03T09:45:00+01:00 - - - BBC Radio 4 - - - - b0490f3d - Last Days of the Bus Club - - - p021gf7t - - 5 - 2014-06-30T09:45:00+01:00 - - b006qftk - Book of the Week - - - p01lc1rh - - - 2007-01-01T09:45:00Z - - - BBC Radio 4 - - - - - 0 - 0 - - - - p021mfnt - 2014-07-03T10:00:00+01:00 - 2014-07-03T10:45:00+01:00 - 2700 - - b048033v - - 03/07/2014 - Jenni Murray presents the programme that offers a female perspective on the world. - audio - 2700 - - p01slb15 - - - Woman's Hour - 03/07/2014 - - 2014-07-03T10:00:00+01:00 - - - BBC Radio 4 - - - - b007qlvb - Woman's Hour - - - p01svhcp - - - 1948-02-02T14:00:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfnv - 2014-07-03T10:45:00+01:00 - 2014-07-03T11:00:00+01:00 - 900 - - b048033x - 4 - Episode 4 - By Scott Cherry. Ian has suddenly returned, claiming he went to help his mother. - audio - 900 - - p021gkyj - - - 15 Minute Drama - A Small Town Murder, Episode 4 - - 2014-07-03T10:45:00+01:00 - - - BBC Radio 4 - - - - b0490f8r - A Small Town Murder - - - p021gkyj - - 5 - 2014-06-30T10:45:00+01:00 - - b006qy2s - 15 Minute Drama - - - p01ynknj - - - 2007-01-19T19:45:00Z - - - BBC Radio 4 - - - - - 0 - 0 - - - - p021mfnx - 2014-07-03T11:00:00+01:00 - 2014-07-03T11:30:00+01:00 - 1800 - - b048033z - - 03/07/2014 - Reports from writers and journalists around the world. Presented by Kate Adie. - audio - 1800 - - p01lcg42 - - - From Our Own Correspondent - 03/07/2014 - - 2014-07-03T11:00:00+01:00 - - - BBC Radio 4 - - - - b006qjlq - From Our Own Correspondent - - - p01lcg42 - - - 2007-01-06T11:30:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfnz - 2014-07-03T11:30:00+01:00 - 2014-07-03T12:00:00+01:00 - 1800 - - b0480341 - - Terry Wogan - Sir Terry Wogan chooses his favourite words and music, with readings by actor David Jason. - audio - 1680 - - p021n5wh - - - With Great Pleasure - Terry Wogan - - 2014-07-03T11:30:00+01:00 - - - BBC Radio 4 - - - - b006qrx7 - With Great Pleasure - - - p01lchhp - - - 2007-02-01T11:30:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfp0 - 2014-07-03T12:00:00+01:00 - 2014-07-03T12:57:00+01:00 - 3420 - - b0480343 - - 03/07/2014 - Consumer news. - audio - 3360 - - p01lc1qq - - - You and Yours - 03/07/2014 - - 2014-07-03T12:00:00+01:00 - - - BBC Radio 4 - - - - b006qps9 - You and Yours - - - p01lc1qq - - - 2006-04-05T12:00:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfp2 - 2014-07-03T12:57:00+01:00 - 2014-07-03T13:00:00+01:00 - 180 - - b047w7gv - - 03/07/2014 - The latest weather forecast. - audio - 180 - - p01lc36f - - - Weather - 03/07/2014 - - 2014-07-03T12:57:00+01:00 - - - BBC Radio 4 - - - - b007rn05 - Weather - - - p01lc36f - - - 2006-04-05T12:57:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfp3 - 2014-07-03T13:00:00+01:00 - 2014-07-03T13:45:00+01:00 - 2700 - - b0480345 - - 03/07/2014 - National and international news with Shaun Ley. - audio - 2700 - - p01lcjyc - - - World at One - 03/07/2014 - - 2014-07-03T13:00:00+01:00 - - - BBC Radio 4 - - - - b006qptc - World at One - - - p01lcjyc - - - 2007-01-19T13:00:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfp5 - 2014-07-03T13:45:00+01:00 - 2014-07-03T14:00:00+01:00 - 900 - - b0480347 - 4 - Cambridge - Town and gown provide the inspiration, as Simon Preston creates a new dish for Cambridge. - audio - 900 - - p021hxgd - - - The Town Is the Menu - Cambridge - - 2014-07-03T13:45:00+01:00 - - - BBC Radio 4 - - - - b0490hzy - The Town Is the Menu - - - p021hxgd - - 5 - 2014-06-30T13:45:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfp6 - 2014-07-03T14:00:00+01:00 - 2014-07-03T14:15:00+01:00 - 900 - - b047zrl3 - 17395 - 02/07/2014 - Susan's not satisfied. - audio - 780 - - p01t7xy8 - - - The Archers - 02/07/2014 - - 2014-07-02T19:00:00+01:00 - - - BBC Radio 4 - - - - b006qpgr - The Archers - - - p01t7xy8 - - - 2007-01-19T14:00:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfp8 - 2014-07-03T14:15:00+01:00 - 2014-07-03T15:00:00+01:00 - 2700 - - b0480349 - - Blurred - By Frazer Flintham. Old university friends Nikki and Hannah meet up after a ten-year gap. - audio - 2700 - - p021n8mh - - - Afternoon Drama - Blurred - - 2014-07-03T14:15:00+01:00 - - - BBC Radio 4 - - - - b006qrzz - Afternoon Drama - - - p01ynb8q - - - 2002-06-02T15:00:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfp9 - 2014-07-03T15:00:00+01:00 - 2014-07-03T15:27:00+01:00 - 1620 - - b048034c - - Tour de Yorkshire - Helen Mark explores the route of the opening leg of the Tour De France in Yorkshire. - audio - 1500 - - p021nxzk - - - Open Country - Tour de Yorkshire - - 2014-07-03T15:00:00+01:00 - - - BBC Radio 4 - - - - b006qgft - Open Country - - - p01lcfyg - - - 2006-04-06T13:30:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfpc - 2014-07-03T15:27:00+01:00 - 2014-07-03T15:30:00+01:00 - 180 - - b047w8k7 - - Excellent Development - Alison Bell presents an appeal for Excellent Development. - audio - 120 - - p0219rxb - - - Radio 4 Appeal - Excellent Development - - 2014-06-29T07:55:00+01:00 - - - BBC Radio 4 - - - - b006qnc7 - Radio 4 Appeal - - - p01lyjsv - - - 2007-02-01T15:27:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfpd - 2014-07-03T15:30:00+01:00 - 2014-07-03T16:00:00+01:00 - 1800 - - b047wb68 - - 29/06/2014 - News and features from the world of books, presented by Mariella Frostrup. - audio - 1800 - - p021np5m - - - Open Book - 29/06/2014 - - 2014-06-29T16:00:00+01:00 - - - BBC Radio 4 - - - - b006qp6p - Open Book - - - p01lc1zt - - - 2003-02-23T16:00:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfpg - 2014-07-03T16:00:00+01:00 - 2014-07-03T16:30:00+01:00 - 1800 - - b048034f - - 03/07/2014 - Looking at the latest cinema releases, DVDs and films on TV. - audio - 1680 - - p01m630v - - - The Film Programme - 03/07/2014 - - 2014-07-03T16:00:00+01:00 - - - BBC Radio 4 - - - - b006r5jt - The Film Programme - - - p01m630v - - - 2004-06-05T17:30:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfph - 2014-07-03T16:30:00+01:00 - 2014-07-03T16:55:00+01:00 - 1500 - - b048034h - - 03/07/2014 - Adam Rutherford investigates the news in science and science in the news. - audio - 1800 - - p01tbgxt - - - Inside Science - 03/07/2014 - - 2014-07-03T16:30:00+01:00 - - - BBC Radio 4 - - - - b036f7w2 - Inside Science - - - p01tbgxt - - - 2013-07-04T16:30:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfpk - 2014-07-03T16:55:00+01:00 - 2014-07-03T17:00:00+01:00 - 300 - - b048034k - - 3rd July - The funeral of Archduke Franz Ferdinand in Vienna. - audio - 300 - - p01qlg3g - - - 1914: Day by Day - 3rd July - - 2014-07-03T16:55:00+01:00 - - - BBC Radio 4 - - - - b03t8m6h - 1914: Day by Day - - - p01qlg3g - - - 2014-06-27T16:55:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfpm - 2014-07-03T17:00:00+01:00 - 2014-07-03T18:00:00+01:00 - 3600 - - b048034m - - 03/07/2014 - Eddie Mair presents coverage and analysis of the day's news. - audio - 3600 - - p01vf6q0 - - - PM - 03/07/2014 - - 2014-07-03T17:00:00+01:00 - - - BBC Radio 4 - - - - b006qskw - PM - - - p01vf6q0 - - - 2007-01-19T17:00:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfpp - 2014-07-03T18:00:00+01:00 - 2014-07-03T18:30:00+01:00 - 1800 - - b047w7gx - - 03/07/2014 - National and international news from BBC Radio 4. - audio - 1800 - - p01lc357 - - - Six O'Clock News - 03/07/2014 - - 2014-07-03T18:00:00+01:00 - - - BBC Radio 4 - - - - b006qjxt - Six O'Clock News - - - p01lc357 - - - 2007-01-06T18:00:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfpr - 2014-07-03T18:30:00+01:00 - 2014-07-03T19:00:00+01:00 - 1800 - - b01gg7g4 - 4 - Making a Difference - Ed finds himself an unlikely star of local radio. - audio - 1800 - - p01lcmpy - - - Ed Reardon's Week - Series 8, Making a Difference - - 2012-04-24T18:30:00+01:00 - - - BBC Radio 4 - - - - b01fjvry - Series 8 - 8 - - p01lcmpy - - 6 - 2012-04-03T18:30:00+01:00 - - b006r5ck - Ed Reardon's Week - - - p01lcmpy - - - 2006-12-15T11:30:00Z - - - BBC Radio 4 - - - - - 0 - 0 - - - - p021mfpt - 2014-07-03T19:00:00+01:00 - 2014-07-03T19:15:00+01:00 - 900 - - b048034p - 17396 - 03/07/2014 - Elizabeth and Shula have a heart to heart. - audio - 780 - - p01t7xy8 - - - The Archers - 03/07/2014 - - 2014-07-03T19:00:00+01:00 - - - BBC Radio 4 - - - - b006qpgr - The Archers - - - p01t7xy8 - - - 2007-01-19T14:00:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfpw - 2014-07-03T19:15:00+01:00 - 2014-07-03T19:45:00+01:00 - 1800 - - b048034r - - 03/07/2014 - Live daily magazine programme on the worlds of arts, literature, film, media and music. - audio - 1800 - - p01n47p5 - - - Front Row - 03/07/2014 - - 2014-07-03T19:15:00+01:00 - - - BBC Radio 4 - - - - b006qsq5 - Front Row - - - p01n47p5 - - - 1998-12-28T19:15:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfpx - 2014-07-03T19:45:00+01:00 - 2014-07-03T20:00:00+01:00 - 900 - - b048033x - 4 - Episode 4 - By Scott Cherry. Ian has suddenly returned, claiming he went to help his mother. - audio - 900 - - p021gkyj - - - 15 Minute Drama - A Small Town Murder, Episode 4 - - 2014-07-03T10:45:00+01:00 - - - BBC Radio 4 - - - - b0490f8r - A Small Town Murder - - - p021gkyj - - 5 - 2014-06-30T10:45:00+01:00 - - b006qy2s - 15 Minute Drama - - - p01ynknj - - - 2007-01-19T19:45:00Z - - - BBC Radio 4 - - - - - 0 - 0 - - - - p021mfpz - 2014-07-03T20:00:00+01:00 - 2014-07-03T20:30:00+01:00 - 1800 - - b048034t - - 03/07/2014 - Combining original insights into major news stories with topical investigations. - audio - 1680 - - p01lykqh - - - The Report - 03/07/2014 - - 2014-07-03T20:00:00+01:00 - - - BBC Radio 4 - - - - b00jkr1q - The Report - - - p01lykqh - - - 2009-04-02T20:00:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfq1 - 2014-07-03T20:30:00+01:00 - 2014-07-03T21:00:00+01:00 - 1800 - - b048034w - - Mental Health - Evan Davis and guests discuss coping with depression and business success at the same time - audio - 1800 - - p01lcmg7 - - - The Bottom Line - Mental Health - - 2014-07-03T20:30:00+01:00 - - - BBC Radio 4 - - - - b006sz6t - The Bottom Line - - - p01lcmg7 - - - 2007-02-03T17:30:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfq2 - 2014-07-03T21:00:00+01:00 - 2014-07-03T21:30:00+01:00 - 1800 - - b048034h - - 03/07/2014 - Adam Rutherford investigates the news in science and science in the news. - audio - 1800 - - p01tbgxt - - - Inside Science - 03/07/2014 - - 2014-07-03T16:30:00+01:00 - - - BBC Radio 4 - - - - b036f7w2 - Inside Science - - - p01tbgxt - - - 2013-07-04T16:30:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfq4 - 2014-07-03T21:30:00+01:00 - 2014-07-03T21:58:00+01:00 - 1680 - - b048033q - 637 - Mrs Dalloway - Melvyn Bragg and his guests discuss Virginia Woolf's novel Mrs Dalloway. - audio - 2580 - - p021n60h - - - In Our Time - Mrs Dalloway - - 2014-07-03T09:00:00+01:00 - - - BBC Radio 4 - - - - b006qykl - In Our Time - - - p01lc1r8 - - - 1998-10-15T09:02:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfq5 - 2014-07-03T21:58:00+01:00 - 2014-07-03T22:00:00+01:00 - 120 - - b047w7gz - - 03/07/2014 - Weather information. - audio - 120 - - p01lc36f - - - Weather - 03/07/2014 - - 2014-07-03T21:58:00+01:00 - - - BBC Radio 4 - - - - b007rn05 - Weather - - - p01lc36f - - - 2006-04-05T12:57:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfq6 - 2014-07-03T22:00:00+01:00 - 2014-07-03T22:45:00+01:00 - 2700 - - b048034y - - 03/07/2014 - In-depth reporting and analysis from a global perspective. - audio - 2700 - - p01z0ls6 - - - The World Tonight - 03/07/2014 - - 2014-07-03T22:00:00+01:00 - - - BBC Radio 4 - - - - b006qtl3 - The World Tonight - - - p01z0ls6 - - - 2006-04-05T22:00:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfq7 - 2014-07-03T22:45:00+01:00 - 2014-07-03T23:00:00+01:00 - 900 - - b0480350 - 9 - Episode 9 - Eric and Cecil have decided to take matters into their own hands. - audio - 900 - - p02128k8 - - - Book at Bedtime - Remember Me Like This, Episode 9 - - 2014-07-03T22:45:00+01:00 - - - BBC Radio 4 - - - - b048329v - Remember Me Like This - - - p02128k8 - - 10 - 2014-06-23T22:45:00+01:00 - - b006qtlx - Book at Bedtime - - - p01lcmpc - - - 2007-01-19T22:45:00Z - - - BBC Radio 4 - - - - - 0 - 0 - - - - p021mfq8 - 2014-07-03T23:00:00+01:00 - 2014-07-03T23:30:00+01:00 - 1800 - - b0480352 - 3 - Episode 3 - A pair of American tourists visit St Paul's cathedral to confess their sins. - audio - 1800 - - p0217xw4 - - - Trodd en Bratt Say 'Well Done You' - Episode 3 - - 2014-07-03T23:00:00+01:00 - - - BBC Radio 4 - - - - b0480h31 - Trodd en Bratt Say 'Well Done You' - - - p0217xw4 - - 4 - 2014-06-19T23:00:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfq9 - 2014-07-03T23:30:00+01:00 - 2014-07-04T00:00:00+01:00 - 1800 - - b0480354 - - 03/07/2014 - Sean Curran reports from Westminster. - audio - 1800 - - p01lcfy2 - - - Today in Parliament - 03/07/2014 - - 2014-07-03T23:30:00+01:00 - - - BBC Radio 4 - - - - b006qtqd - Today in Parliament - - - p01lcfy2 - - - 2007-01-19T23:30:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfqb - 2014-07-04T00:00:00+01:00 - 2014-07-04T00:30:00+01:00 - 1800 - - b047w7ht - - 04/07/2014 - The latest national and international news from BBC Radio 4. - audio - 1800 - - p01lcbf6 - - - Midnight News - 04/07/2014 - - 2014-07-04T00:00:00+01:00 - - - BBC Radio 4 - - - - b00cs19l - Midnight News - - - p01lcbf6 - - - 2007-02-03T00:00:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfqc - 2014-07-04T00:30:00+01:00 - 2014-07-04T00:48:00+01:00 - 1080 - - b048033s - 4 - The Rain in Spain: Part 1 - Andalucia is a region racked with droughts, but one winter the downpour was biblical. - audio - 900 - - p021gf7t - - - Book of the Week - Last Days of the Bus Club, The Rain in Spain: Part 1 - - 2014-07-03T09:45:00+01:00 - - - BBC Radio 4 - - - - b0490f3d - Last Days of the Bus Club - - - p021gf7t - - 5 - 2014-06-30T09:45:00+01:00 - - b006qftk - Book of the Week - - - p01lc1rh - - - 2007-01-01T09:45:00Z - - - BBC Radio 4 - - - - - 0 - 0 - - - - p021mfqd - 2014-07-04T00:48:00+01:00 - 2014-07-04T01:00:00+01:00 - 720 - - b047w7hw - - 04/07/2014 - The latest shipping forecast. - audio - 660 - - p01lc1xj - - - Shipping Forecast - 04/07/2014 - - 2014-07-04T00:48:00+01:00 - - - BBC Radio 4 - - - - b006qfvv - Shipping Forecast - - - p01lc1xj - - - 2006-04-05T00:48:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfqf - 2014-07-04T01:00:00+01:00 - 2014-07-04T05:20:00+01:00 - 15600 - - b047w7hy - - 04/07/2014 - BBC Radio 4 joins the BBC World Service. - audio - 15600 - - p01lchw0 - - - Selection of BBC World Service Programmes - 04/07/2014 - - 2014-07-04T01:00:00+01:00 - - - BBC World Service - - - - b006s54y - Selection of BBC World Service Programmes - - - p01lchw0 - - - 2007-01-06T01:00:00Z - - - BBC World Service - - - - 0 - 0 - - - - p021mfqg - 2014-07-04T05:20:00+01:00 - 2014-07-04T05:30:00+01:00 - 600 - - b047w7j2 - - 04/07/2014 - The latest shipping forecast. - audio - 600 - - p01lc1xj - - - Shipping Forecast - 04/07/2014 - - 2014-07-04T05:20:00+01:00 - - - BBC Radio 4 - - - - b006qfvv - Shipping Forecast - - - p01lc1xj - - - 2006-04-05T00:48:00+01:00 - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfqh - 2014-07-04T05:30:00+01:00 - 2014-07-04T05:43:00+01:00 - 780 - - b047w7j4 - - 04/07/2014 - News from BBC Radio 4. - audio - 780 - - p01lc375 - - - News Briefing - 04/07/2014 - - 2014-07-04T05:30:00+01:00 - - - BBC Radio 4 - - - - b007rhyn - News Briefing - - - p01lc375 - - - 2007-02-03T05:30:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfqj - 2014-07-04T05:43:00+01:00 - 2014-07-04T05:45:00+01:00 - 120 - - b0483pkw - - 04/07/2014 - A spiritual comment and prayer to begin the day with Sheikh Michael Mumisa. - audio - 120 - - p01lc206 - - - Prayer for the Day - 04/07/2014 - - 2014-07-04T05:43:00+01:00 - - - BBC Radio 4 - - - - b006qmpj - Prayer for the Day - - - p01lc206 - - - 2007-01-06T05:43:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfql - 2014-07-04T05:45:00+01:00 - 2014-07-04T05:58:00+01:00 - 780 - - b04807h6 - - 04/07/2014 - The latest news about food, farming and the countryside. Presented by Charlotte Smith. - audio - 780 - - p01lqyv0 - - - Farming Today - 04/07/2014 - - 2014-07-04T05:45:00+01:00 - - - BBC Radio 4 - - - - b006qj8q - Farming Today - - - p01lcqw7 - - - 2007-01-06T06:35:00Z - - - BBC Radio 4 - - - - 0 - 0 - - - - p021mfqn - 2014-07-04T05:58:00+01:00 - 2014-07-04T06:00:00+01:00 - 120 - - b0378srp - - House Sparrow - Michaela Strachan presents the story and sound of the house sparrow. - audio - 120 - - p01k03ym - - - Tweet of the Day - House Sparrow - - 2013-08-02T05:58:00+01:00 - - - BBC Radio 4 - - - - b01s6xyk - Tweet of the Day - - 265 - 2013-05-06T05:58:00+01:00 - - - BBC Radio 4 - - - - 2013-08-02T06:45:19+01:00 - 1 - 0 - - - Available to listen - - - - - - diff --git a/Tests/TestData/actionRunnerData - default.js b/Tests/TestData/actionRunnerData - default.js deleted file mode 100644 index a708c20..0000000 --- a/Tests/TestData/actionRunnerData - default.js +++ /dev/null @@ -1 +0,0 @@ -var jsonData = { "ActionGuid": "abcd" } \ No newline at end of file diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 5dca393..8a6470a 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,231 +1,26 @@ - - - + + - Debug - AnyCPU - {6D9E7904-B2AC-49E3-83A7-6B48876F46B9} - Library - Properties - Tests - Tests - v4.5.1 - 512 + net8.0 + disable + disable + false + true - - 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 - - - False - ..\packages\log4net.2.0.3\lib\net40-full\log4net.dll - - - False - ..\packages\Microsoft.AspNet.SignalR.Core.2.0.3\lib\net45\Microsoft.AspNet.SignalR.Core.dll - - - False - ..\packages\Microsoft.Owin.2.1.0\lib\net45\Microsoft.Owin.dll - - - False - ..\packages\Microsoft.Owin.Security.2.1.0\lib\net45\Microsoft.Owin.Security.dll - - - True - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - - ..\packages\Mono.Reflection.1.0.0.0\lib\Mono.Reflection.dll - - - ..\packages\Moq.4.2.1408.0717\lib\net40\Moq.dll - - - False - ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - - - ..\packages\NUnit.2.6.3\lib\nunit.framework.dll - - - False - ..\packages\Owin.1.0\lib\net40\Owin.dll - - - - - - - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - True - - - ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll - True - - - ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll - True - - - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll - True - - - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll - True - - - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Settings.settings - - - - - - - - - - - Designer - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - {264e1878-12de-4099-b8d7-cc53a73fea49} - DataLayer - - - {cffee5e0-3b99-46e0-9a82-2e74621c17c5} - SampleWebApp - - - {d2813927-0f38-43c3-b47c-ae8f00d50cae} - ServiceLayer - - + - + + + + + + - - + + + + - - - \ No newline at end of file + + diff --git a/Tests/UnitTests/Group01DataLayer/DataLayerTests.cs b/Tests/UnitTests/Group01DataLayer/DataLayerTests.cs new file mode 100644 index 0000000..a8c3752 --- /dev/null +++ b/Tests/UnitTests/Group01DataLayer/DataLayerTests.cs @@ -0,0 +1,70 @@ +using System.Linq; +using DataLayer.DataClasses; +using DataLayer.DataClasses.Concrete; +using DataLayer.Startup; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Tests.Helpers; + +namespace Tests.UnitTests.Group01DataLayer +{ + [TestFixture] + public class DataLayerTests + { + [Test] + public void SeedSmall_LoadsExpectedBlogsPostsAndTags() + { + using var app = new TestServices(TestDataSelection.Small); + using var scope = app.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + Assert.That(db.Blogs.Count(), Is.EqualTo(2)); + Assert.That(db.Posts.Count(), Is.EqualTo(3)); + Assert.That(db.Tags.Count(), Is.EqualTo(3)); + } + + [Test] + public void Post_ManyToManyTags_AreLoaded() + { + using var app = new TestServices(); + using var scope = app.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + var post = db.Posts.Include(p => p.Tags).Include(p => p.Blogger) + .OrderBy(p => p.PostId).First(); + + Assert.That(post.Blogger, Is.Not.Null); + Assert.That(post.Tags.Count, Is.GreaterThan(0)); + } + + [Test] + public void SaveChanges_SetsLastUpdatedOnPost() + { + using var app = new TestServices(); + using var scope = app.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + var post = db.Posts.OrderBy(p => p.PostId).First(); + var before = post.LastUpdated; + post.Title = "Edited title"; + db.SaveChanges(); + + Assert.That(post.LastUpdated, Is.GreaterThanOrEqualTo(before)); + } + + [Test] + public void ResetBlogs_Medium_LoadsMoreThanSmall() + { + using var small = new TestServices(TestDataSelection.Small); + using var medium = new TestServices(TestDataSelection.Medium); + + using var smallScope = small.CreateScope(); + using var mediumScope = medium.CreateScope(); + var smallDb = smallScope.ServiceProvider.GetRequiredService(); + var mediumDb = mediumScope.ServiceProvider.GetRequiredService(); + + Assert.That(mediumDb.Posts.Count(), Is.GreaterThanOrEqualTo(smallDb.Posts.Count())); + } + } +} diff --git a/Tests/UnitTests/Group01DataLayer/Test10SetupBlogs.cs b/Tests/UnitTests/Group01DataLayer/Test10SetupBlogs.cs deleted file mode 100644 index 7a03ba0..0000000 --- a/Tests/UnitTests/Group01DataLayer/Test10SetupBlogs.cs +++ /dev/null @@ -1,126 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: Test10SetupBlogs.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.Linq; -using DataLayer.DataClasses; -using DataLayer.Startup; -using DataLayer.Startup.Internal; -using NUnit.Framework; -using Tests.Helpers; - -namespace Tests.UnitTests.Group01DataLayer -{ - class Test10SetupBlogs - { - [Test] - public void Check01XmlFileLoadOk() - { - - //SETUP - - //ATTEMPT - var bloggers = LoadDbDataFromXml.FormBlogsWithPosts("DataLayer.Startup.Internal.BlogsContentSimple.xml").ToList(); - - //VERIFY - bloggers.Count().ShouldEqual(2); - bloggers.SelectMany( x => x.Posts).Count().ShouldEqual(3); - bloggers.SelectMany( x => x.Posts.SelectMany( y => y.Tags)).Distinct().Count().ShouldEqual(3); - - } - - [Test] - public void Check02XmlFileLoadBad() - { - - //SETUP - - //ATTEMPT - var ex = Assert.Throws( () => LoadDbDataFromXml.FormBlogsWithPosts("badname.xml")); - - //VERIFY - ex.Message.ShouldStartWith("Could not find the xml file you asked for."); - - } - - [Test] - public void Check10BlogsResetSmallOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - DataLayerInitialise.InitialiseThis(false, true); - - //ATTEMPT - DataLayerInitialise.ResetBlogs(db, TestDataSelection.Small); - - //VERIFY - db.Blogs.Count().ShouldEqual(2); - db.Posts.Count().ShouldEqual(3); - db.Tags.Count().ShouldEqual(3); - } - } - - [Test] - public void Check11BlogsResetMediumOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - DataLayerInitialise.InitialiseThis(false, true); - - //ATTEMPT - DataLayerInitialise.ResetBlogs(db, TestDataSelection.Medium); - - //VERIFY - db.Blogs.Count().ShouldEqual(4); - db.Posts.Count().ShouldEqual(17); - db.Tags.Count().ShouldEqual(8); - } - } - - //--------------------------------------------------------- - - [Test] - public void Check20NullInitialiserOk() - { - Check10BlogsResetSmallOk(); //we call this to ensure the database is setup - using (var db = new SampleWebAppDb()) - { - //SETUP - DataLayerInitialise.InitialiseThis(false, false); //select null initialiser - - //ATTEMPT - DataLayerInitialise.ResetBlogs(db, TestDataSelection.Small); - - //VERIFY - db.Blogs.Count().ShouldEqual(2); - db.Posts.Count().ShouldEqual(3); - db.Tags.Count().ShouldEqual(3); - } - } - } -} diff --git a/Tests/UnitTests/Group01DataLayer/Test13Validation.cs b/Tests/UnitTests/Group01DataLayer/Test13Validation.cs deleted file mode 100644 index 5bba1c2..0000000 --- a/Tests/UnitTests/Group01DataLayer/Test13Validation.cs +++ /dev/null @@ -1,230 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: Test13Validation.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 DataLayer.DataClasses; -using DataLayer.DataClasses.Concrete; -using DataLayer.Startup; -using GenericServices; -using NUnit.Framework; -using Tests.Helpers; - -namespace Tests.UnitTests.Group01DataLayer -{ - class Test13Validation - { - - [TestFixtureSetUp] - public void SetUpFixture() - { - using (var db = new SampleWebAppDb()) - { - DataLayerInitialise.InitialiseThis(false, true); - DataLayerInitialise.ResetBlogs(db, TestDataSelection.Small); - } - } - - [Test] - public void Check01ValidateTagOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var snap = new DbSnapShot(db); - - //ATTEMPT - var dupTag = new Tag { Name = "non-duplicate slug", Slug = Guid.NewGuid().ToString("N") }; - db.Tags.Add(dupTag); - var status = db.SaveChangesWithChecking(); - - //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); - snap.CheckSnapShot(db, 0,0,0,1); - } - } - - [Test] - public void Check02ValidateTagError() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var existingTag = db.Tags.First(); - - //ATTEMPT - var dupTag = new Tag {Name = "duplicate slug", Slug = existingTag.Slug}; - db.Tags.Add(dupTag); - var status = db.SaveChangesWithChecking();; - - //VERIFY - status.IsValid.ShouldEqual(false); - status.Errors.Count.ShouldEqual(1); - status.Errors[0].ErrorMessage.ShouldEqual("The Slug on tag 'duplicate slug' must be unique and is already being used."); - } - } - - [Test] - public void Check10ValidatePostOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var snap = new DbSnapShot(db); - var existingTag = db.Tags.First(); - var existingBlogger = db.Blogs.First(); - - //ATTEMPT - var newPost = new Post() - { - Blogger = existingBlogger, - Title = "Test post", - Content = "Nothing special", - Tags = new[] { existingTag } - }; - db.Posts.Add(newPost); - var status = db.SaveChangesWithChecking();; - - //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); - snap.CheckSnapShot(db,1,1); - } - } - - - [Test] - public void Check15ValidatePostTitleError() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var existingTag = db.Tags.First(); - var existingBlogger = db.Blogs.First(); - - //ATTEMPT - var newPost = new Post() - { - Blogger = existingBlogger, - Title = "Test post!", - Content = "Nothing special", - Tags = new[] { existingTag } - }; - db.Posts.Add(newPost); - var status = db.SaveChangesWithChecking();; - - //VERIFY - status.IsValid.ShouldEqual(false); - status.Errors.Count.ShouldEqual(1); - status.Errors[0].ErrorMessage.ShouldEqual("Sorry, but you can't get too excited and include a ! in the title."); - } - } - - [Test] - public void Check16ValidatePostTitleError() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var existingTag = db.Tags.First(); - var existingBlogger = db.Blogs.First(); - - //ATTEMPT - var newPost = new Post() - { - Blogger = existingBlogger, - Title = "Test post?", - Content = "Nothing special", - Tags = new[] { existingTag } - }; - db.Posts.Add(newPost); - var status = db.SaveChangesWithChecking();; - - //VERIFY - status.IsValid.ShouldEqual(false); - status.Errors.Count.ShouldEqual(1); - status.Errors[0].ErrorMessage.ShouldEqual("Sorry, but you can't ask a question, i.e. the title can't end with '?'."); - } - } - - [Test] - public void Check20ValidatePostContentOneError() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var existingTag = db.Tags.First(); - var existingBlogger = db.Blogs.First(); - - //ATTEMPT - var newPost = new Post() - { - Blogger = existingBlogger, - Title = "Test post", - Content = "Should not end sentence with sheep.", - Tags = new[] { existingTag } - }; - db.Posts.Add(newPost); - var status = db.SaveChangesWithChecking();; - - //VERIFY - status.IsValid.ShouldEqual(false); - status.Errors.Count.ShouldEqual(1); - status.Errors[0].ErrorMessage.ShouldEqual("Sorry. Not allowed to end a sentance with 'sheep'."); - } - } - - - [Test] - public void Check21ValidatePostContentTwoErrors() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var existingTag = db.Tags.First(); - var existingBlogger = db.Blogs.First(); - - //ATTEMPT - var newPost = new Post() - { - Blogger = existingBlogger, - Title = "Test post", - Content = "Should not end sentence with sheep. Nor end sentence with lamb.", - Tags = new[] { existingTag } - }; - db.Posts.Add(newPost); - var status = db.SaveChangesWithChecking();; - - //VERIFY - status.IsValid.ShouldEqual(false); - status.Errors.Count.ShouldEqual(2); - status.Errors[0].ErrorMessage.ShouldEqual("Sorry. Not allowed to end a sentance with 'sheep'."); - status.Errors[1].ErrorMessage.ShouldEqual("Sorry. Not allowed to end a sentance with 'lamb'."); - } - } - } -} - diff --git a/Tests/UnitTests/Group01DataLayer/Test14ReadWriteBlogs.cs b/Tests/UnitTests/Group01DataLayer/Test14ReadWriteBlogs.cs deleted file mode 100644 index 9c70554..0000000 --- a/Tests/UnitTests/Group01DataLayer/Test14ReadWriteBlogs.cs +++ /dev/null @@ -1,331 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: Test14ReadWriteBlogs.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.Data.Entity; -using System.Linq; -using System.Threading; -using DataLayer.DataClasses; -using DataLayer.DataClasses.Concrete; -using DataLayer.Startup; -using GenericServices; -using NUnit.Framework; -using Tests.Helpers; - -namespace Tests.UnitTests.Group01DataLayer -{ - class Test14ReadWriteBlogs - { - - [SetUp] - public void SetUp() - { - using (var db = new SampleWebAppDb()) - { - DataLayerInitialise.InitialiseThis(false, true); - DataLayerInitialise.ResetBlogs(db, TestDataSelection.Small); - } - } - - [Test] - public void Check01ReadBlogsNoPostsOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - - //ATTEMPT - var blogs = db.Blogs.ToList(); - - //VERIFY - blogs.Count.ShouldEqual(2); - blogs.All(x => x.Posts == null).ShouldEqual(true); - } - } - - [Test] - public void Check02ReadBlogsWithPostsOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - - //ATTEMPT - var blogs = db.Blogs.Include(x => x.Posts).ToList(); - - //VERIFY - blogs.Count.ShouldEqual(2); - blogs.All(x => x.Posts != null).ShouldEqual(true); - blogs.All(x => x.Posts.All(y => y.Tags == null)).ShouldEqual(true); - } - } - - [Test] - public void Check03ReadBlogsWithPostTagsOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - - //ATTEMPT - var blogs = db.Blogs.Include(x => x.Posts.Select(y => y.Tags)).ToList(); - - //VERIFY - blogs.Count.ShouldEqual(2); - blogs.All(x => x.Posts != null).ShouldEqual(true); - blogs.All(x => x.Posts.All(y => y.Tags != null)).ShouldEqual(true); - - } - } - - [Test] - public void Check05ReadPostsOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - - //ATTEMPT - var posts = db.Posts.ToList(); - - //VERIFY - posts.Count.ShouldEqual(3); - posts.All(x => x.Blogger != null).ShouldEqual(true); - posts.All(x => x.Tags == null).ShouldEqual(true); - } - } - - - [Test] - public void Check06ReadPostsWithTagsOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - - //ATTEMPT - var posts = db.Posts.Include(x => x.Tags).ToList(); - - //VERIFY - posts.Count.ShouldEqual(3); - posts.All(x => x.Blogger != null).ShouldEqual(true); - posts.All(x => x.Tags != null).ShouldEqual(true); - } - } - - [Test] - public void Check10ReadTAllocatedTagsWithUglySlugOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - - //ATTEMPT - var postsWithuglyTags = db.Posts.Where(x => x.Tags.Any(y => y.Slug == "ugly")).ToList(); - var uglyTagPosts = db.Tags.Include(x => x.Posts).Single(y => y.Slug == "ugly").Posts; - - //VERIFY - postsWithuglyTags.Count.ShouldEqual(2); - uglyTagPosts.Count().ShouldEqual(2); - } - } - - //----------------------------------------------------------------- - //now adding new posts, tags etc. - - [Test] - public void Check20AddPostOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var snap = new DbSnapShot(db); - - //ATTEMPT - var uglyTag = db.Tags.Single(x => x.Slug == "ugly"); - var jonBlogger = db.Blogs.First(); - var newPost = new Post - { - Blogger = jonBlogger, - Content = "a few simple words.", - Title = "A new post", - Tags = new[] { uglyTag } - }; - - db.Posts.Add(newPost); - var status = db.SaveChangesWithChecking(); - - //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); - snap.CheckSnapShot(db, 1, 1); - var uglyPosts = db.Tags.Include(x => x.Posts).Single(y => y.Slug == "ugly").Posts; - uglyPosts.Count.ShouldEqual(3); - } - } - - [Test] - public void Check21CheckUpdateSimpleOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var snap = new DbSnapShot(db); - var newGuid = Guid.NewGuid().ToString(); - - //ATTEMPT - var firstPost = db.Posts.First(); - firstPost.Title = newGuid; - var status = db.SaveChangesWithChecking(); - - //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); - snap.CheckSnapShot(db); - db.Posts.First().Title.ShouldEqual(newGuid); - } - } - - - [Test] - public void Check22CheckUpdateLastUpdatedOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var snap = new DbSnapShot(db); - var firstPost = db.Posts.First(); - var originalDateTime = firstPost.LastUpdated; - Thread.Sleep(400); - - //ATTEMPT - firstPost.Title = Guid.NewGuid().ToString(); - var status = db.SaveChangesWithChecking(); - - //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); - snap.CheckSnapShot(db); - Assert.GreaterOrEqual(db.Posts.First().LastUpdated.Subtract(originalDateTime).Milliseconds, 400); - } - } - - [Test] - public void Check25UpdatePostToAddTagOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var snap = new DbSnapShot(db); - var badTag = db.Tags.Single(x => x.Slug == "bad"); - var firstPost = db.Posts.First(); - - //ATTEMPT - db.Entry(firstPost).Collection(x => x.Tags).Load(); - firstPost.Tags.Add(badTag); - var status = db.SaveChangesWithChecking(); - - //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); - snap.CheckSnapShot(db, 0, 1); - firstPost = db.Blogs.Include(x => x.Posts.Select(y => y.Tags)).First().Posts.First(); - firstPost.Tags.Count.ShouldEqual(3); - } - } - - [Test] - public void Check26ReplaceTagsOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var snap = new DbSnapShot(db); - var firstPost = db.Posts.First(); - var tagsNotInFirstPostTracked = db.Tags.Where(x => x.Posts.All(y => y.PostId != firstPost.PostId)).ToList(); - - //ATTEMPT - - db.Entry(firstPost).Collection(x => x.Tags).Load(); - firstPost.Tags = tagsNotInFirstPostTracked; - var status = db.SaveChangesWithChecking(); - - //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); - snap.CheckSnapShot(db, 0, -1); - firstPost = db.Blogs.Include(x => x.Posts.Select(y => y.Tags)).First().Posts.First(); - firstPost.Tags.Count.ShouldEqual(1); - } - } - - [Test] - public void Check30CheckCreateLastUpdatedOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var snap = new DbSnapShot(db); - var firstPostUntracked = db.Posts.AsNoTracking().First(); - var originalDateTime = firstPostUntracked.LastUpdated; - Thread.Sleep(400); - - //ATTEMPT - firstPostUntracked.Title = Guid.NewGuid().ToString(); - firstPostUntracked.Blogger = db.Blogs.First(); - firstPostUntracked.Tags = db.Tags.Take(2).ToList(); - db.Posts.Add(firstPostUntracked); - var status = db.SaveChangesWithChecking(); - - //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); - snap.CheckSnapShot(db,1,2); - var loadedPost = db.Posts.Single(x => x.PostId == firstPostUntracked.PostId); - Assert.GreaterOrEqual(loadedPost.LastUpdated.Subtract(originalDateTime).Milliseconds, 400); - } - } - - [Test] - public void Check31CheckCreateDataOk() - { - using (var db = new SampleWebAppDb()) - { - //SETUP - var snap = new DbSnapShot(db); - var firstPostUntracked = db.Posts.AsNoTracking().First(); - - //ATTEMPT - firstPostUntracked.Title = Guid.NewGuid().ToString(); - firstPostUntracked.Blogger = db.Blogs.First(); - firstPostUntracked.Tags = db.Tags.Take(2).ToList(); - db.Posts.Add(firstPostUntracked); - var status = db.SaveChangesWithChecking(); - - //VERIFY - status.IsValid.ShouldEqual(true, status.Errors); - snap.CheckSnapShot(db,1,2); - var loadedPost = db.Posts.Include( x => x.Blogger).Include( x => x.Tags).Single(x => x.PostId == firstPostUntracked.PostId); - loadedPost.Blogger.BlogId.ShouldEqual(db.Blogs.First().BlogId); - CollectionAssert.AreEquivalent(db.Tags.Take(2).Select(x => x.TagId), loadedPost.Tags.Select(x => x.TagId)); - } - } - } -} \ No newline at end of file diff --git a/Tests/UnitTests/Group03ServiceLayer/ServiceLayerAsyncTests.cs b/Tests/UnitTests/Group03ServiceLayer/ServiceLayerAsyncTests.cs new file mode 100644 index 0000000..bab5ad5 --- /dev/null +++ b/Tests/UnitTests/Group03ServiceLayer/ServiceLayerAsyncTests.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Threading.Tasks; +using DataLayer.DataClasses; +using GenericServices; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using ServiceLayer.PostServices; +using Tests.Helpers; + +namespace Tests.UnitTests.Group03ServiceLayer +{ + [TestFixture] + public class ServiceLayerAsyncTests + { + [Test] + public async Task DetailAsync_ReturnsPost() + { + using var app = new TestServices(); + using var scope = app.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var detail = scope.ServiceProvider.GetRequiredService(); + + var firstId = db.Posts.OrderBy(p => p.PostId).First().PostId; + var response = await detail.GetDetailAsync(firstId); + + Assert.That(response.IsValid, Is.True, string.Join(";", response.Errors.Select(e => e.ErrorMessage))); + Assert.That(response.Result.PostId, Is.EqualTo(firstId)); + } + + [Test] + public async Task CreateAsync_WithSelectedTags_Succeeds() + { + using var app = new TestServices(); + int createdId; + using (var scope = app.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + var createSetup = scope.ServiceProvider.GetRequiredService(); + var createSvc = scope.ServiceProvider.GetRequiredService(); + + var dto = await createSetup.GetDtoAsync(); + dto.Title = "Async New Post"; + dto.Content = "Async content."; + dto.Bloggers.SelectedValue = db.Blogs.First().BlogId.ToString(); + dto.UserChosenTags.FinalSelection = db.Tags.Take(2).Select(t => t.TagId.ToString()).ToArray(); + + var response = await createSvc.CreateAsync(dto); + Assert.That(response.IsValid, Is.True, string.Join(";", response.Errors.Select(e => e.ErrorMessage))); + } + + using (var scope = app.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + createdId = db.Posts.Count(p => p.Title == "Async New Post"); + Assert.That(createdId, Is.EqualTo(1)); + } + } + } +} diff --git a/Tests/UnitTests/Group03ServiceLayer/ServiceLayerCrudTests.cs b/Tests/UnitTests/Group03ServiceLayer/ServiceLayerCrudTests.cs new file mode 100644 index 0000000..fa68ee3 --- /dev/null +++ b/Tests/UnitTests/Group03ServiceLayer/ServiceLayerCrudTests.cs @@ -0,0 +1,151 @@ +using System.Linq; +using DataLayer.DataClasses; +using DataLayer.DataClasses.Concrete; +using GenericServices; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using ServiceLayer.BlogServices; +using ServiceLayer.PostServices; +using ServiceLayer.TagServices; +using Tests.Helpers; + +namespace Tests.UnitTests.Group03ServiceLayer +{ + [TestFixture] + public class ServiceLayerCrudTests + { + [Test] + public void BlogListDto_FlattensPostsCount() + { + using var app = new TestServices(); + using var scope = app.CreateScope(); + var list = scope.ServiceProvider.GetRequiredService(); + + var blogs = list.GetAll().ToList(); + + Assert.That(blogs.Count, Is.EqualTo(2)); + Assert.That(blogs.Sum(b => b.PostsCount), Is.EqualTo(3)); + } + + [Test] + public void TagListDto_FlattensPostsCount() + { + using var app = new TestServices(); + using var scope = app.CreateScope(); + var list = scope.ServiceProvider.GetRequiredService(); + + var tags = list.GetAll().ToList(); + + Assert.That(tags.Count, Is.EqualTo(3)); + Assert.That(tags.All(t => !string.IsNullOrEmpty(t.Slug))); + } + + [Test] + public void SimplePostDto_FlattensBloggerNameAndProjectsTags() + { + using var app = new TestServices(); + using var scope = app.CreateScope(); + var list = scope.ServiceProvider.GetRequiredService(); + + var posts = list.GetAll().ToList(); + + Assert.That(posts.Count, Is.EqualTo(3)); + Assert.That(posts.All(p => !string.IsNullOrEmpty(p.BloggerName))); + Assert.That(posts.All(p => p.Tags != null)); + } + + [Test] + public void DetailSetup_PopulatesBloggerAndTagPickers() + { + using var app = new TestServices(); + using var scope = app.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var setup = scope.ServiceProvider.GetRequiredService(); + + var firstId = db.Posts.OrderBy(p => p.PostId).First().PostId; + var dto = setup.GetOriginal(firstId).Result; + + Assert.That(dto.Bloggers.KeyValueList, Is.Not.Empty); + Assert.That(dto.UserChosenTags.AllPossibleOptions, Is.Not.Empty); + Assert.That(dto.UserChosenTags.FinalSelection, Is.Not.Empty); + } + + [Test] + public void UpdatePost_ChangesTitleAndReplacesTags() + { + using var app = new TestServices(); + int postId; + using (var scope = app.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + var setupSvc = scope.ServiceProvider.GetRequiredService(); + var updateSvc = scope.ServiceProvider.GetRequiredService(); + + postId = db.Posts.OrderBy(p => p.PostId).First().PostId; + var dto = setupSvc.GetOriginal(postId).Result; + dto.Title = "UPDATED TITLE"; + dto.Bloggers.SelectedValue = dto.BlogId.ToString(); + dto.UserChosenTags.FinalSelection = + new[] { dto.UserChosenTags.AllPossibleOptions.First().Value.ToString() }; + + var response = updateSvc.Update(dto); + Assert.That(response.IsValid, Is.True, string.Join(";", response.Errors.Select(e => e.ErrorMessage))); + Assert.That(response.SuccessMessage, Does.Contain("updated")); + } + + using (var scope = app.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + var post = db.Posts.Include(p => p.Tags).First(p => p.PostId == postId); + Assert.That(post.Title, Is.EqualTo("UPDATED TITLE")); + Assert.That(post.Tags.Count, Is.EqualTo(1)); + } + } + + [Test] + public void CreatePost_WithSelectedTags_Succeeds() + { + using var app = new TestServices(); + using (var scope = app.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + var createSetup = scope.ServiceProvider.GetRequiredService(); + var createSvc = scope.ServiceProvider.GetRequiredService(); + + var dto = createSetup.GetDto(); + dto.Title = "Brand New Post"; + dto.Content = "Some content here."; + dto.Bloggers.SelectedValue = db.Blogs.First().BlogId.ToString(); + dto.UserChosenTags.FinalSelection = db.Tags.Take(2).Select(t => t.TagId.ToString()).ToArray(); + + var response = createSvc.Create(dto); + Assert.That(response.IsValid, Is.True, string.Join(";", response.Errors.Select(e => e.ErrorMessage))); + Assert.That(response.SuccessMessage, Does.Contain("created")); + } + + using (var scope = app.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + var created = db.Posts.Include(p => p.Tags).FirstOrDefault(p => p.Title == "Brand New Post"); + Assert.That(created, Is.Not.Null); + Assert.That(created.Tags.Count, Is.EqualTo(2)); + } + } + + [Test] + public void DeletePost_RemovesIt() + { + using var app = new TestServices(); + using var scope = app.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var deleteSvc = scope.ServiceProvider.GetRequiredService(); + + var postId = db.Posts.OrderBy(p => p.PostId).First().PostId; + var response = deleteSvc.Delete(postId); + + Assert.That(response.IsValid, Is.True, string.Join(";", response.Errors.Select(e => e.ErrorMessage))); + Assert.That(db.Posts.Any(p => p.PostId == postId), Is.False); + } + } +} diff --git a/Tests/UnitTests/Group03ServiceLayer/ServiceLayerValidationTests.cs b/Tests/UnitTests/Group03ServiceLayer/ServiceLayerValidationTests.cs new file mode 100644 index 0000000..3ffb3e3 --- /dev/null +++ b/Tests/UnitTests/Group03ServiceLayer/ServiceLayerValidationTests.cs @@ -0,0 +1,95 @@ +using System.Linq; +using DataLayer.DataClasses; +using DataLayer.DataClasses.Concrete; +using GenericServices; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using ServiceLayer.PostServices; +using Tests.Helpers; + +namespace Tests.UnitTests.Group03ServiceLayer +{ + [TestFixture] + public class ServiceLayerValidationTests + { + [Test] + public void CreatePost_WithNoBlogger_IsRejected() + { + using var app = new TestServices(); + using var scope = app.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var createSetup = scope.ServiceProvider.GetRequiredService(); + var createSvc = scope.ServiceProvider.GetRequiredService(); + + var dto = createSetup.GetDto(); + dto.Title = "No blogger post"; + dto.Content = "content"; + dto.Bloggers.SelectedValue = null; + dto.UserChosenTags.FinalSelection = new[] { db.Tags.First().TagId.ToString() }; + + var response = createSvc.Create(dto); + + Assert.That(response.IsValid, Is.False); + Assert.That(string.Join(";", response.Errors.Select(e => e.ErrorMessage)), + Does.Contain("The blogger was not selected")); + } + + [Test] + public void CreatePost_WithNoTags_IsRejected() + { + using var app = new TestServices(); + using var scope = app.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var createSetup = scope.ServiceProvider.GetRequiredService(); + var createSvc = scope.ServiceProvider.GetRequiredService(); + + var dto = createSetup.GetDto(); + dto.Title = "No tags post"; + dto.Content = "content"; + dto.Bloggers.SelectedValue = db.Blogs.First().BlogId.ToString(); + dto.UserChosenTags.FinalSelection = new string[0]; + + var response = createSvc.Create(dto); + + Assert.That(response.IsValid, Is.False); + Assert.That(string.Join(";", response.Errors.Select(e => e.ErrorMessage)), + Does.Contain("at least one tag")); + } + + [Test] + public void CreatePost_WithExclamationInTitle_IsRejectedByEntityRule() + { + using var app = new TestServices(); + using var scope = app.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var createSetup = scope.ServiceProvider.GetRequiredService(); + var createSvc = scope.ServiceProvider.GetRequiredService(); + + var dto = createSetup.GetDto(); + dto.Title = "Bad!"; + dto.Content = "ok content"; + dto.Bloggers.SelectedValue = db.Blogs.First().BlogId.ToString(); + dto.UserChosenTags.FinalSelection = new[] { db.Tags.First().TagId.ToString() }; + + var response = createSvc.Create(dto); + + Assert.That(response.IsValid, Is.False); + } + + [Test] + public void CreateTag_WithDuplicateSlug_IsRejected() + { + using var app = new TestServices(); + using var scope = app.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var createSvc = scope.ServiceProvider.GetRequiredService(); + + var existingSlug = db.Tags.First().Slug; + var response = createSvc.Create(new Tag { Name = "Some Name", Slug = existingSlug }); + + Assert.That(response.IsValid, Is.False); + Assert.That(string.Join(";", response.Errors.Select(e => e.ErrorMessage)), + Does.Contain("must be unique")); + } + } +} diff --git a/Tests/UnitTests/Group03ServiceLayer/Test10DiSimple.cs b/Tests/UnitTests/Group03ServiceLayer/Test10DiSimple.cs deleted file mode 100644 index b502ff5..0000000 --- a/Tests/UnitTests/Group03ServiceLayer/Test10DiSimple.cs +++ /dev/null @@ -1,296 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: Test10DiSimple.cs -// Date Created: 2014/05/22 -// -// 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.Linq; -using System.Reflection; -using Autofac; -using Autofac.Core; -using NUnit.Framework; -using Tests.DependencyItems; -using Tests.Helpers; - -namespace Tests.UnitTests.Group03ServiceLayer -{ - class Test10DiSimple - { - - [Test] - public void Test01AutoFacSimple() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterType().As(); - var container = builder.Build(); - - //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var instance = lifetimeScope.Resolve(); - Assert.NotNull(instance); - (instance is SimpleClass).ShouldEqual(true); - } - - } - - [Test] - public void Test02AutoFacTransient() - { - //Setup - var builder = new ContainerBuilder(); - builder.RegisterType().As(); - var container = builder.Build(); - - //Attempt - ISimpleClass instance1; - using (var lifetimeScope = container.BeginLifetimeScope()) - instance1 = lifetimeScope.Resolve(); - ISimpleClass instance2; - using (var lifetimeScope = container.BeginLifetimeScope()) - instance2 = lifetimeScope.Resolve(); - - //Verify - Assert.NotNull(instance1); - Assert.NotNull(instance2); - Assert.AreNotSame(instance1, instance2); - - } - - - [Test] - public void Test03AutoFacSingle() - { - //Setup - var builder = new ContainerBuilder(); - builder.RegisterType().As().SingleInstance(); - var container = builder.Build(); - - //Attempt - ISimpleClass instance1; - using (var lifetimeScope = container.BeginLifetimeScope()) - instance1 = lifetimeScope.Resolve(); - ISimpleClass instance2; - using (var lifetimeScope = container.BeginLifetimeScope()) - instance2 = lifetimeScope.Resolve(); - - //Verify - Assert.NotNull(instance1); - Assert.NotNull(instance2); - Assert.AreSame(instance1, instance2); - - } - - [Test] - public void Test04AutoFacLifeTimeScope() - { - //Setup - var builder = new ContainerBuilder(); - builder.RegisterType().As().InstancePerLifetimeScope(); - var container = builder.Build(); - - //Attempt and VERIFY - ISimpleClass scope1Instance1; - ISimpleClass scope1Instance2; - using (var lifetimeScope = container.BeginLifetimeScope()) - { - scope1Instance1 = lifetimeScope.Resolve(); - scope1Instance2 = lifetimeScope.Resolve(); - Assert.NotNull(scope1Instance1); - Assert.NotNull(scope1Instance2); - Assert.AreSame(scope1Instance1, scope1Instance2); - } - - using (var lifetimeScope = container.BeginLifetimeScope()) - { - ISimpleClass scope2Instance1 = lifetimeScope.Resolve(); - Assert.NotNull(scope2Instance1); - Assert.NotNull(scope1Instance1); - Assert.NotNull(scope1Instance2); - Assert.AreNotSame(scope1Instance1, scope2Instance1); - Assert.AreNotSame(scope1Instance1, scope2Instance1); - } - - } - - //----------------------------------------------------------- - //item with constructor param - - [Test] - public void Test05AutoFacConstructor() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterType().As() - .WithParameter("myInt", 42); - var container = builder.Build(); - - //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var instance = lifetimeScope.Resolve(); - Assert.NotNull(instance); - instance.MyInt.ShouldEqual(42); - } - - } - - - //----------------------------------------------------------- - //tests on IDisposable items - - private int _numTimeDisposeCalled; - - [Test] - public void Test15AutoFacDisposeCreate() - { - //Setup - var builder = new ContainerBuilder(); - Action checker = (() => _numTimeDisposeCalled++); - builder.RegisterType().As().WithParameter("disposeWasCalled", checker); - var container = builder.Build(); - - //Attempt - _numTimeDisposeCalled = 0; - var mydisp = container.Resolve(); - - //Verify - Assert.NotNull(mydisp); - _numTimeDisposeCalled.ShouldEqual(0); - - } - - [Test] - public void Test16AutoFacDisposeCalled() - { - //Setup - var builder = new ContainerBuilder(); - Action checker = (() => _numTimeDisposeCalled++); - builder.RegisterType().As().WithParameter("disposeWasCalled", checker); - var container = builder.Build(); - - //Attempt - _numTimeDisposeCalled = 0; - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var mydisp = lifetimeScope.Resolve(); - Assert.NotNull(mydisp); - } - - //Verify - _numTimeDisposeCalled.ShouldEqual(1); - - } - - //-------------------------------------------------------------- - //register generic - - [Test] - public void Test20AutoFacRegisterGeneric() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterGeneric(typeof(GenericInterface<>)).As(typeof(IGenericInterface<>)); - var container = builder.Build(); - - //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var instance = lifetimeScope.Resolve>(); - Assert.NotNull(instance); - (instance is GenericInterface).ShouldEqual(true); - instance.GetTypeName().ShouldEqual(typeof(SimpleClass).Name); - } - - } - - [Test] - public void Test21AutoFacRegisterGenericAfterRegisterAssembly() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterAssemblyTypes(GetType().Assembly).AsImplementedInterfaces(); - builder.RegisterGeneric(typeof(GenericInterface<>)).As(typeof(IGenericInterface<>)); - var container = builder.Build(); - - //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var instance = lifetimeScope.Resolve>(); - Assert.NotNull(instance); - (instance is GenericInterface).ShouldEqual(true); - instance.GetTypeName().ShouldEqual(typeof(SimpleClass).Name); - } - - } - - //--------------------------------------------------------- - //tests on what happens if ctor is private - - [Test] - public void Test30AutoFacRegisterClassWithPrivateCtorBad() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterType().As(); - var container = builder.Build(); - - //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var ex = Assert.Throws(() => lifetimeScope.Resolve()); - ex.Message.ShouldStartWith("No constructors on type"); - } - - } - - [Test] - public void Test31AutoFacTryCtorWithPrivateCtorAsOptionOk() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterType().As(); - builder.RegisterGeneric(typeof (ClassToTestClassWithPrivateCtor<>)) - .As(typeof (IClassToTestClassWithPrivateCtor<>)) - .OnActivating(e => - { - var interfaceToLookup = e.Instance.GetType().GetGenericArguments()[0]; - var resolvedInterface = - e.Context.ComponentRegistry.RegistrationsFor(new TypedService(interfaceToLookup)).SingleOrDefault(); - ((ISetType)e.Instance).SetType(resolvedInterface.Activator.LimitType); - }); - var container = builder.Build(); - - //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var instance = lifetimeScope.Resolve>(); - Assert.NotNull(instance); - (instance is ClassToTestClassWithPrivateCtor).ShouldEqual(true); - } - - } - } -} diff --git a/Tests/UnitTests/Group03ServiceLayer/Test11AutoFacModules.cs b/Tests/UnitTests/Group03ServiceLayer/Test11AutoFacModules.cs deleted file mode 100644 index 3d4ff00..0000000 --- a/Tests/UnitTests/Group03ServiceLayer/Test11AutoFacModules.cs +++ /dev/null @@ -1,167 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: Test11AutoFacModules.cs -// Date Created: 2014/05/22 -// -// 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.Linq; -using Autofac; -using DataLayer.DataClasses; -using DataLayer.DataClasses.Concrete; -using DataLayer.Startup; -using GenericServices; -using GenericServices.Services.Concrete; -using NUnit.Framework; -using SampleWebApp.Infrastructure; -using ServiceLayer.Startup; -using Tests.Helpers; - -namespace Tests.UnitTests.Group03ServiceLayer -{ - [TestFixture] - public class Test11AutoFacModules - { - - [TestFixtureSetUp] - public void FixtureSetUp() - { - using (var db = new SampleWebAppDb()) - { - DataLayerInitialise.InitialiseThis(false, true); - DataLayerInitialise.ResetBlogs(db, TestDataSelection.Small); - } - } - - - //------------------------------------- - //DataLayer - - [Test] - public void CheckSetupDbContextLifetimeScopeItems() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterModule( new DataLayerModule()); - var container = builder.Build(); - - //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var instance1 = lifetimeScope.Resolve(); - var instance2 = lifetimeScope.Resolve(); - Assert.NotNull(instance1); - (instance1 is SampleWebAppDb).ShouldEqual(true); - Assert.AreSame(instance1, instance2); //check that lifetimescope is working - } - } - - - //--------------------------------------------- - //ServiceLayer, which also resolves DataLayer - - [Test] - public void Test10ServiceSetupServiceLayer() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterModule(new ServiceLayerModule()); - var container = builder.Build(); - - //ATTEMPT & VERIFY - CheckExampleServicesResolve(container); - } - - - [Test] - public void Test15SetupServiceLayerDirectGenerics() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterModule(new ServiceLayerModule()); - var container = builder.Build(); - - //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var instance = lifetimeScope.Resolve(); - Assert.NotNull(instance); - (instance is ListService).ShouldEqual(true); - } - } - - [Test] - public void Test16UseServiceLayerDirectGenerics() - { - //SETUP - var builder = new ContainerBuilder(); - builder.RegisterModule(new ServiceLayerModule()); - var container = builder.Build(); - - //ATTEMPT & VERIFY - using (var lifetimeScope = container.BeginLifetimeScope()) - { - var service = lifetimeScope.Resolve(); - var posts = service.GetAll().ToList(); - posts.Count.ShouldEqual(3); - } - } - - //------------------------------------------------------ - //MVC layer - - [Test] - public void Test20ViaMvcSetup() - { - //SETUP - var container = AutofacDi.SetupDependency(); - - //ATTEMPT & VERIFY - CheckExampleServicesResolve(container); - - } - - //------------------------------------------------------- - //private helper - - private static void CheckExampleServicesResolve(IContainer container) - { - using (var lifetimeScope = container.BeginLifetimeScope()) - { - //DataLayer - Data classes - - //DataLayer - repositories - var db1 = lifetimeScope.Resolve(); - var db2 = lifetimeScope.Resolve(); - Assert.NotNull(db1); - Assert.AreSame(db1, db2); //check that lifetimescope is working - - //ServiceLayer - complex - var service1 = lifetimeScope.Resolve(); - var service2 = lifetimeScope.Resolve(); - Assert.NotNull(service1); - Assert.AreNotSame(service1, service2); //check transient - (service1 is ListService).ShouldEqual(true); - } - } - } -} diff --git a/Tests/UnitTests/Group06Mvc/Test02Validation.cs b/Tests/UnitTests/Group06Mvc/Test02Validation.cs deleted file mode 100644 index 7ef1d18..0000000 --- a/Tests/UnitTests/Group06Mvc/Test02Validation.cs +++ /dev/null @@ -1,273 +0,0 @@ -#region licence -// The MIT License (MIT) -// -// Filename: Test02Validation.cs -// Date Created: 2014/06/10 -// -// 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.Linq; -using GenericLibsBase.Core; -using GenericServices.Core; -using NUnit.Framework; -using SampleWebApp.Infrastructure; -using Tests.Helpers; - -namespace Tests.UnitTests.Group06Mvc -{ - class Test02Validation - { - - [Test] - public void Check01TestModelValidate() - { - //SETUP - var model = new ModelStateTester.TestModel("123", 50, true); - - //ATTEMPT - var vErrors = model.Validate(null).ToList(); - - //VERIFY - vErrors.Count.ShouldEqual(2); - vErrors[0].MemberNames.Any().ShouldEqual(false); - vErrors[0].ErrorMessage.ShouldEqual("This is a top level error caused by CreateValidationError being set."); - vErrors[1].MemberNames.Any().ShouldEqual(false); - vErrors[1].ErrorMessage.ShouldEqual("This is a top level error caused by MyInt having value 50."); - } - - - [Test] - public void Check05TestModelStateValidateOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("123", 50, true); - - //ATTEMPT - var modelState = model.ReturnModelState(); - - //VERIFY - modelState.IsValid.ShouldEqual(false); - modelState.Keys.Count.ShouldEqual(1); - modelState.Keys.First().ShouldEqual(""); - modelState[modelState.Keys.First()].Errors.Count.ShouldEqual(2); - modelState[modelState.Keys.First()].Errors[0].ErrorMessage.ShouldEqual("This is a top level error caused by CreateValidationError being set."); - modelState[modelState.Keys.First()].Errors[1].ErrorMessage.ShouldEqual("This is a top level error caused by MyInt having value 50."); - } - - [Test] - public void Check06TestModelStateValidateOneOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("123", 2, true); - - //ATTEMPT - var modelState = model.ReturnModelState(); - - //VERIFY - modelState.IsValid.ShouldEqual(false); - modelState.Keys.Count.ShouldEqual(1); - modelState.Keys.First().ShouldEqual(""); - modelState[modelState.Keys.First()].Errors.Count.ShouldEqual(1); - modelState[modelState.Keys.First()].Errors[0].ErrorMessage.ShouldEqual("This is a top level error caused by CreateValidationError being set."); - } - - [Test] - public void Check06TestModelStateIntAttributeOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("123", -1, false); - - //ATTEMPT - var modelState = model.ReturnModelState(); - - //VERIFY - modelState.IsValid.ShouldEqual(false); - modelState.Keys.Count.ShouldEqual(1); - modelState.Keys.First().ShouldEqual("MyInt"); - modelState[modelState.Keys.First()].Errors.Count.ShouldEqual(1); - modelState[modelState.Keys.First()].Errors[0].ErrorMessage.ShouldEqual("The field MyInt must be between 0 and 100."); - - } - - [Test] - public void Check07TestModelStateStringAttributeOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("", 2, false); - - //ATTEMPT - var modelState = model.ReturnModelState(); - - //VERIFY - modelState.IsValid.ShouldEqual(false); - modelState.Keys.Count.ShouldEqual(1); - modelState.Keys.First().ShouldEqual("MyString"); - CollectionAssert.AreEquivalent(new[] - { - "The field MyString must be a string or array type with a minimum length of '2'.", - "The MyString field is required." - }, - modelState[modelState.Keys.First()].Errors.Select( x => x.ErrorMessage)); - - - } - - [Test] - public void Check08TestModelStateMixedErrorsOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("", -1, true); - - //ATTEMPT - var modelState = model.ReturnModelState(); - - //VERIFY - modelState.IsValid.ShouldEqual(false); - CollectionAssert.AreEquivalent(new[] { "MyInt", "MyString" }, modelState.Keys); //Note: only runs Validate if no attribute errors - modelState["MyInt"].Errors.Count.ShouldEqual(1); - modelState["MyString"].Errors.Count.ShouldEqual(2); - } - - //--------------------------------------------------------------------- - //now use to check ValidationHelper ReturnModelErrorsAsJson - - [Test] - public void Check15TestModelStateValidateOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("123", 50, true); - - //ATTEMPT - var jsonResult = model.ReturnModelState().ReturnModelErrorsAsJson(); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - json.ShouldEqual("{\"errorsDict\":{\"\":{\"errors\":[\"This is a top level error caused by CreateValidationError being set.\",\"This is a top level error caused by MyInt having value 50.\"]}}}"); - } - - [Test] - public void Check16TestModelStateValidateOneOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("123", 2, true); - - //ATTEMPT - var jsonResult = model.ReturnModelState().ReturnModelErrorsAsJson(); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - json.ShouldEqual("{\"errorsDict\":{\"\":{\"errors\":[\"This is a top level error caused by CreateValidationError being set.\"]}}}"); - } - - - [Test] - public void Check16TestModelStateIntAttributeOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("123", -1, false); - - //ATTEMPT - var jsonResult = model.ReturnModelState().ReturnModelErrorsAsJson(); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - json.ShouldEqual("{\"errorsDict\":{\"MyInt\":{\"errors\":[\"The field MyInt must be between 0 and 100.\"]}}}"); - } - - [Test] - public void Check17TestModelStateStringAttributeOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("", 2, false); - - //ATTEMPT - var jsonResult = model.ReturnModelState().ReturnModelErrorsAsJson(); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - const string order1 = "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The field MyString must be a string or array type with a minimum length of '2'.\",\"The MyString field is required.\"]}}}"; - const string order1Json = "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The field MyString must be a string or array type with a minimum length of \\u00272\\u0027.\",\"The MyString field is required.\"]}}}"; - - const string order2 = "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The MyString field is required.\",\"The field MyString must be a string or array type with a minimum length of '2'.\"]}}}"; - const string order2Json = - "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The MyString field is required.\",\"The field MyString must be a string or array type with a minimum length of \\u00272\\u0027.\"]}}}"; - (json == order1Json || json == order2Json).ShouldEqual(true); - - } - - [Test] - public void Check18TestModelStateMixedErrorsOnly() - { - //SETUP - var model = new ModelStateTester.TestModel("", -1, true); - - //ATTEMPT - var jsonResult = model.ReturnModelState().ReturnModelErrorsAsJson(); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - const string order1 = "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The field MyString must be a string or array type with a minimum length of '2'.\",\"The MyString field is required.\"]},"; - const string order1Json = "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The field MyString must be a string or array type with a minimum length of \\u00272\\u0027.\",\"The MyString field is required.\"]},"; - const string order2 = "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The MyString field is required.\",\"The field MyString must be a string or array type with a minimum length of '2'.\"]},"; - const string order2Json = - "{\"errorsDict\":{\"MyString\":{\"errors\":[\"The MyString field is required.\",\"The field MyString must be a string or array type with a minimum length of \\u00272\\u0027.\"]},"; - const string part2 = "\"MyInt\":{\"errors\":[\"The field MyInt must be between 0 and 100.\"]}}}"; - (json == order1Json + part2 || json == order2Json + part2).ShouldEqual(true); - } - - //------------------------------------------------------------------- - //now the ReturnErrorsAsJson - - [Test] - public void Check20StatusToJsonTopLevel() - { - //SETUP - var status = new SuccessOrErrors(); - var dto = new {MyInt = 1}; - - //ATTEMPT - status.AddSingleError("This is a top level error."); - var jsonResult = status.ReturnErrorsAsJson(dto); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - json.ShouldEqual("{\"errorsDict\":{\"\":{\"errors\":[\"This is a top level error.\"]}}}"); - } - - [Test] - public void Check21StatusToJsonProperty() - { - //SETUP - var status = new SuccessOrErrors(); - var dto = new { MyInt = 1 }; - - //ATTEMPT - status.AddNamedParameterError("MyInt", "This is a property level error."); - var jsonResult = status.ReturnErrorsAsJson(dto); - - //VERIFY - var json = jsonResult.Data.SerialiseToJson(); - json.ShouldEqual("{\"errorsDict\":{\"MyInt\":{\"errors\":[\"This is a property level error.\"]}}}"); - } - - } -} 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 diff --git a/global.json b/global.json new file mode 100644 index 0000000..3fea262 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.0", + "rollForward": "latestFeature" + } +}