Commit 29c03843 authored by boncho vylkov's avatar boncho vylkov

initial commit of the most awesome template

parent a6fadf41
......@@ -26,8 +26,6 @@ bld/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
......@@ -286,3 +284,27 @@ __pycache__/
*.btm.cs
*.odx.cs
*.xsd.cs
# JavaScript Files
**/wwwroot/
**/app/components/**/*.js
**/app/components/**/*.js.map
**/app/directives/**/*.js
**/app/directives/**/*.js.map
**/app/services/**/*.js
**/app/services/**/*.js.map
**/app/domain/**/*.js
**/app/domain/**/*.js.map
**/app/pipes/**/*.js
**/app/pipes/**/*.js.map
**/app/*.js
**/app/*.js.map
!**/app/systemjs.config.js
package-lock.json
namespace MvcCoreTemplate.Data.Common
{
using System;
public interface IDbQueryRunner : IDisposable
{
void RunQuery(string query, params object[] parameters);
}
}
using System;
namespace MvcCoreTemplate.Web.Infrastructure.Models.Base
namespace MvcCoreTemplate.Data.Common.Models
{
public class BaseViewModel<TKey>
{
public TKey Id { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
using System;
public abstract class BaseDeletableModel<TKey> : BaseModel<TKey>, IDeletableEntity
{
public bool IsDeleted { get; set; }
public DateTime? DeletedOn { get; set; }
......
......@@ -3,7 +3,7 @@
using System;
using System.ComponentModel.DataAnnotations;
public abstract class BaseModel<TKey> : IAuditInfo, IDeletableEntity
public abstract class BaseModel<TKey> : IAuditInfo
{
[Key]
public TKey Id { get; set; }
......@@ -11,9 +11,5 @@
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public bool IsDeleted { get; set; }
public DateTime? DeletedOn { get; set; }
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\Rules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004" PrivateAssets="All" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.4.1" />
</ItemGroup>
</Project>
\ No newline at end of file
namespace MvcCoreTemplate.Data.Common.Repositories
{
using System.Linq;
using System.Threading.Tasks;
using MvcCoreTemplate.Data.Common.Models;
......@@ -10,6 +10,10 @@
{
IQueryable<TEntity> AllWithDeleted();
IQueryable<TEntity> AllAsNoTrackingWithDeleted();
Task<TEntity> GetByIdWithDeletedAsync(params object[] id);
void HardDelete(TEntity entity);
void Undelete(TEntity entity);
......
......@@ -9,6 +9,8 @@
{
IQueryable<TEntity> All();
IQueryable<TEntity> AllAsNoTracking();
Task<TEntity> GetByIdAsync(params object[] id);
void Add(TEntity entity);
......
namespace MvcCoreTemplate.Data.Models
// ReSharper disable VirtualMemberCallInConstructor
namespace MvcCoreTemplate.Data.Models
{
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using MvcCoreTemplate.Data.Common.Models;
using System;
using MvcCoreTemplate.Data.Common.Models;
using Microsoft.AspNetCore.Identity;
public class ApplicationRole : IdentityRole, IAuditInfo, IDeletableEntity
{
public ApplicationRole()
......@@ -14,7 +17,7 @@
public ApplicationRole(string name)
: base(name)
{
this.CreatedOn = DateTime.UtcNow;
this.Id = Guid.NewGuid().ToString();
}
public DateTime CreatedOn { get; set; }
......
namespace MvcCoreTemplate.Data.Models
// ReSharper disable VirtualMemberCallInConstructor
namespace MvcCoreTemplate.Data.Models
{
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using MvcCoreTemplate.Data.Common.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using MvcCoreTemplate.Data.Common.Models;
using Microsoft.AspNetCore.Identity;
public class ApplicationUser : IdentityUser, IAuditInfo, IDeletableEntity
{
public ApplicationUser()
{
this.CreatedOn = DateTime.UtcNow;
this.PlacedOrders = new HashSet<PlacedOrder>();
this.Id = Guid.NewGuid().ToString();
this.Roles = new HashSet<IdentityUserRole<string>>();
this.Claims = new HashSet<IdentityUserClaim<string>>();
this.Logins = new HashSet<IdentityUserLogin<string>>();
}
// Audit info
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
// Deletable entity
public bool IsDeleted { get; set; }
public DateTime? DeletedOn { get; set; }
public Cart Cart { get; set; }
public OrderDetails OrderDetails { get; set; }
public virtual ICollection<IdentityUserRole<string>> Roles { get; set; }
public ICollection<PlacedOrder> PlacedOrders { get; set; }
public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\Rules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="1.1.1" />
<AdditionalFiles Include="..\..\stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MvcCoreTemplate.Data.Common\MvcCoreTemplate.Data.Common.csproj" />
</ItemGroup>
</Project>
\ No newline at end of file
namespace MvcCoreTemplate.Data.Models
{
using System.ComponentModel.DataAnnotations;
using MvcCoreTemplate.Data.Common.Models;
public class TodoItem : BaseDeletableModel<int>
{
[Required]
public string Title { get; set; }
public bool IsDone { get; set; }
[Required]
public string AuthorId { get; set; }
public virtual ApplicationUser Author { get; set; }
}
}
namespace MvcCoreTemplate.Data
{
using System;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using MvcCoreTemplate.Data.Common.Models;
using MvcCoreTemplate.Data.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string>
{
private static readonly MethodInfo SetIsDeletedQueryFilterMethod =
typeof(ApplicationDbContext).GetMethod(
nameof(SetIsDeletedQueryFilter),
BindingFlags.NonPublic | BindingFlags.Static);
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; }
public override int SaveChanges() => this.SaveChanges(true);
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
this.ApplyAuditInfoRules();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)) =>
this.SaveChangesAsync(true, cancellationToken);
public override Task<int> SaveChangesAsync(
bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = default(CancellationToken))
{
this.ApplyAuditInfoRules();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
protected override void OnModelCreating(ModelBuilder builder)
{
// Needed for Identity models configuration
base.OnModelCreating(builder);
ConfigureUserIdentityRelations(builder);
EntityIndexesConfiguration.Configure(builder);
var entityTypes = builder.Model.GetEntityTypes().ToList();
// Set global query filter for not deleted entities only
var deletableEntityTypes = entityTypes
.Where(et => et.ClrType != null && typeof(IDeletableEntity).IsAssignableFrom(et.ClrType));
foreach (var deletableEntityType in deletableEntityTypes)
{
var method = SetIsDeletedQueryFilterMethod.MakeGenericMethod(deletableEntityType.ClrType);
method.Invoke(null, new object[] { builder });
}
// Disable cascade delete
var foreignKeys = entityTypes
.SelectMany(e => e.GetForeignKeys().Where(f => f.DeleteBehavior == DeleteBehavior.Cascade));
foreach (var foreignKey in foreignKeys)
{
foreignKey.DeleteBehavior = DeleteBehavior.Restrict;
}
}
private static void ConfigureUserIdentityRelations(ModelBuilder builder)
{
builder.Entity<ApplicationUser>()
.HasMany(e => e.Claims)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Logins)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<ApplicationUser>()
.HasMany(e => e.Roles)
.WithOne()
.HasForeignKey(e => e.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
}
private static void SetIsDeletedQueryFilter<T>(ModelBuilder builder)
where T : class, IDeletableEntity
{
builder.Entity<T>().HasQueryFilter(e => !e.IsDeleted);
}
private void ApplyAuditInfoRules()
{
var changedEntries = this.ChangeTracker
.Entries()
.Where(e =>
e.Entity is IAuditInfo &&
(e.State == EntityState.Added || e.State == EntityState.Modified));
foreach (var entry in changedEntries)
{
var entity = (IAuditInfo)entry.Entity;
if (entry.State == EntityState.Added && entity.CreatedOn == default(DateTime))
{
entity.CreatedOn = DateTime.UtcNow;
}
else
{
entity.ModifiedOn = DateTime.UtcNow;
}
}
}
}
}
namespace MvcCoreTemplate.Data
{
using System.Security.Claims;
using MvcCoreTemplate.Data.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
public class ApplicationRoleStore : RoleStore<
ApplicationRole,
ApplicationDbContext,
string,
IdentityUserRole<string>,
IdentityRoleClaim<string>>
{
public ApplicationRoleStore(ApplicationDbContext context, IdentityErrorDescriber describer = null)
: base(context, describer)
{
}
protected override IdentityRoleClaim<string> CreateRoleClaim(ApplicationRole role, Claim claim) =>
new IdentityRoleClaim<string>
{
RoleId = role.Id,
ClaimType = claim.Type,
ClaimValue = claim.Value
};
}
}
namespace MvcCoreTemplate.Data
{
using System.Security.Claims;
using MvcCoreTemplate.Data.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
public class ApplicationUserStore : UserStore<
ApplicationUser,
ApplicationRole,
ApplicationDbContext,
string,
IdentityUserClaim<string>,
IdentityUserRole<string>,
IdentityUserLogin<string>,
IdentityUserToken<string>,
IdentityRoleClaim<string>>
{
public ApplicationUserStore(ApplicationDbContext context, IdentityErrorDescriber describer = null)
: base(context, describer)
{
}
protected override IdentityUserRole<string> CreateUserRole(ApplicationUser user, ApplicationRole role)
{
return new IdentityUserRole<string> { RoleId = role.Id, UserId = user.Id };
}
protected override IdentityUserClaim<string> CreateUserClaim(ApplicationUser user, Claim claim)
{
var identityUserClaim = new IdentityUserClaim<string> { UserId = user.Id };
identityUserClaim.InitializeFromClaim(claim);
return identityUserClaim;
}
protected override IdentityUserLogin<string> CreateUserLogin(ApplicationUser user, UserLoginInfo login) =>
new IdentityUserLogin<string>
{
UserId = user.Id,
ProviderKey = login.ProviderKey,
LoginProvider = login.LoginProvider,
ProviderDisplayName = login.ProviderDisplayName
};
protected override IdentityUserToken<string> CreateUserToken(
ApplicationUser user,
string loginProvider,
string name,
string value)
{
var token = new IdentityUserToken<string>
{
UserId = user.Id,
LoginProvider = loginProvider,
Name = name,
Value = value
};
return token;
}
}
}
namespace MvcCoreTemplate.Data
{
using System;
using MvcCoreTemplate.Data.Common;
using Microsoft.EntityFrameworkCore;
public class DbQueryRunner : IDbQueryRunner
{
public DbQueryRunner(ApplicationDbContext context)
{
this.Context = context ?? throw new ArgumentNullException(nameof(context));
}
public ApplicationDbContext Context { get; set; }
public void RunQuery(string query, params object[] parameters)
{
this.Context.Database.ExecuteSqlCommand(query, parameters);
}
public void Dispose()
{
this.Context?.Dispose();
}
}
}
namespace MvcCoreTemplate.Data
{
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
builder.UseSqlServer(connectionString);
// Stop client query evaluation
builder.ConfigureWarnings(w => w.Throw(RelationalEventId.QueryClientEvaluationWarning));
return new ApplicationDbContext(builder.Options);
}
}
}
namespace MvcCoreTemplate.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage;
internal static class EfExpressionHelper
{
private static readonly Type StringType = typeof(string);
private static readonly MethodInfo ValueBufferGetValueMethod =
typeof(ValueBuffer).GetRuntimeProperties().Single(p => p.GetIndexParameters().Any()).GetMethod;
private static readonly MethodInfo EfPropertyMethod =
typeof(EF).GetTypeInfo().GetDeclaredMethod(nameof(Property));
public static Expression<Func<TEntity, bool>>