شروع به کار با DNTFrameworkCore - قسمت 7 - ارتقاء به نسخه 4.5.x
نویسنده: سالار ربال
تاریخ: ۱۳۹۸/۱۲/۱۵ ۱:۵۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
public interface ICreationTracking
{
DateTime CreatedDateTime { get; set; }
}
public interface IModificationTracking
{
DateTime? ModifiedDateTime { get; set; }
} public interface IHasRowIntegrity
{
string Hash { get; set; }
}
public interface IHasRowVersion
{
byte[] Version { get; set; }
} public abstract class AggregateRoot<TKey> : Entity<TKey>, IAggregateRoot
where TKey : IEquatable<TKey>
{
private readonly List<IDomainEvent> _events = new List<IDomainEvent>();
public IReadOnlyCollection<IDomainEvent> Events => _events.AsReadOnly();
protected virtual void AddDomainEvent(IDomainEvent newEvent)
{
_events.Add(newEvent);
}
public virtual void ClearEvents()
{
_events.Clear();
}
} public static class EventBusExtensions
{
public static Task TriggerAsync(this IEventBus bus, IEnumerable<IDomainEvent> events)
{
var tasks = events.Select(async domainEvent => await bus.TriggerAsync(domainEvent));
return Task.WhenAll(tasks);
}
public static async Task PublishAsync(this IEventBus bus, IAggregateRoot aggregateRoot)
{
await bus.TriggerAsync(aggregateRoot.Events);
aggregateRoot.ClearEvents();
}
} public interface IKeyValueService : IApplicationService
{
Task SetValueAsync(string key, string value);
Task<Maybe<string>> LoadValueAsync(string key);
Task<bool> IsTamperedAsync(string key);
}
public class KeyValue : Entity, IModificationTracking, ICreationTracking, IHasRowIntegrity
{
public string Key { get; set; }
[Encrypted] public string Value { get; set; }
public string Hash { get; set; }
public DateTime CreatedDateTime { get; set; }
public DateTime? ModifiedDateTime { get; set; }
} public class ExceptionOptions
{
public List<ExceptionMapItem> Mappings { get; } = new List<ExceptionMapItem>();
[Required] public string DbException { get; set; }
[Required] public string DbConcurrencyException { get; set; }
[Required] public string InternalServerIssue { get; set; }
public bool TryFindMapping(DbException dbException, out ExceptionMapItem mapping)
{
mapping = null;
var words = new HashSet<string>(Regex.Split(dbException.ToStringFormat(), @"\W"));
var mappingItem = Mappings.FirstOrDefault(a => a.Keywords.IsProperSubsetOf(words));
if (mappingItem == null)
{
return false;
}
mapping = mappingItem;
return true;
}
} "Exception": {
"Mappings": [
{
"Message": "به دلیل وجود اطلاعات وابسته امکان حذف وجود ندارد",
"Keywords": [
"DELETE",
"REFERENCE"
]
},
{
"Message": "یک تسک با این عنوان قبلا در سیستم ثبت شده است",
"MemberName": "Title",
"Keywords": [
"Task",
"UIX_Task_NormalizedTitle"
]
}
],
"DbException": "امکان ذخیرهسازی اطلاعات وجود ندارد؛ دوباره تلاش نمائید",
"DbConcurrencyException": "اطلاعات توسط کاربری دیگر در شبکه تغییر کرده است",
"InternalServerIssue": "متأسفانه مشکلی در فرآیند انجام درخواست شما پیش آمده است!"
} _session.UserId _session.UserId<long>() _session.UserId<int>() _session.UserId<Guid>()
public static class UserClaimTypes
{
public const string UserName = ClaimTypes.Name;
public const string UserId = ClaimTypes.NameIdentifier;
public const string SerialNumber = ClaimTypes.SerialNumber;
public const string Role = ClaimTypes.Role;
public const string DisplayName = nameof(DisplayName);
public const string BranchId = nameof(BranchId);
public const string BranchName = nameof(BranchName);
public const string IsHeadOffice = nameof(IsHeadOffice);
public const string TenantId = nameof(TenantId);
public const string TenantName = nameof(TenantName);
public const string IsHeadTenant = nameof(IsHeadTenant);
public const string Permission = nameof(Permission);
public const string PackedPermission = nameof(PackedPermission);
public const string ImpersonatorUserId = nameof(ImpersonatorUserId);
public const string ImpersonatorTenantId = nameof(ImpersonatorTenantId);
} /// <summary>
/// Adds a file logger named 'File' to the factory.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
public static ILoggingBuilder AddFile(this ILoggingBuilder builder)
{
builder.Services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
return builder;
}
/// <summary>
/// Adds a file logger named 'File' to the factory.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <param name="configure">Configure an instance of the <see cref="FileLoggerOptions" /> to set logging options</param>
public static ILoggingBuilder AddFile(this ILoggingBuilder builder, Action<FileLoggerOptions> configure)
{
builder.AddFile();
builder.Services.Configure(configure);
return builder;
} public interface ITenantResolutionStrategy
{
string TenantId();
}
public interface ITenantStore
{
Task<Tenant> FindTenantAsync(string tenantId);
} public interface ITenantSession : IScopedDependency
{
/// <summary>
/// Gets current TenantId or null.
/// This TenantId should be the TenantId of the <see cref="IUserSession.UserId" />.
/// It can be null if given <see cref="IUserSession.UserId" /> is a head-tenant user or no user logged in.
/// </summary>
string TenantId { get; }
/// <summary>
/// Gets current TenantName or null.
/// This TenantName should be the TenantName of the <see cref="IUserSession.UserId" />.
/// It can be null if given <see cref="IUserSession.UserId" /> is a head-tenant user or no user logged in.
/// </summary>
string TenantName { get; }
/// <summary>
/// Represents current tenant is head-tenant.
/// </summary>
bool IsHeadTenant { get; }
/// <summary>
/// TenantId of the impersonator.
/// This is filled if a user with <see cref="IUserSession.ImpersonatorUserId" /> performing actions behalf of the
/// <see cref="IUserSession.UserId" />.
/// </summary>
string ImpersonatorTenantId { get; }
} public static class SystemTime
{
public static Func<DateTime> Now = () => DateTime.UtcNow;
public static Func<DateTime, DateTime> Normalize = (dateTime) =>
DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
} public interface IClock : ITransientDependency
{
DateTime Now { get; }
DateTime Normalize(DateTime dateTime);
}
internal sealed class Clock : IClock
{
public DateTime Now => SystemTime.Now();
public DateTime Normalize(DateTime dateTime)
{
return SystemTime.Normalize(dateTime);
}
} public class Result
{
private static readonly Result _ok = new Result(false, string.Empty);
private readonly List<ValidationFailure> _failures;
protected Result(bool failed, string message) : this(failed, message,
Enumerable.Empty<ValidationFailure>())
{
Failed = failed;
Message = message;
}
protected Result(bool failed, string message, IEnumerable<ValidationFailure> failures)
{
Failed = failed;
Message = message;
_failures = failures.ToList();
}
public bool Failed { get; }
public string Message { get; }
public IEnumerable<ValidationFailure> Failures => _failures.AsReadOnly();
[DebuggerStepThrough]
public static Result Ok() => _ok;
[DebuggerStepThrough]
public static Result Fail(string message)
{
return new Result(true, message);
}
//...
} services.AddFramework()
.WithModelValidation()
.WithFluentValidation()
.WithMemoryCache()
.WithSecurityService()
.WithBackgroundTaskQueue()
.WithRandomNumber(); public abstract class TrackableEntity<TKey> : Entity<TKey>, ITrackable where TKey : IEquatable<TKey>
{
[NotMapped] public TrackingState TrackingState { get; set; }
[NotMapped] public ICollection<string> ModifiedProperties { get; set; }
} public static class ConfigurationBuilderExtensions
{
public static IConfigurationBuilder AddEFCore(this IConfigurationBuilder builder,
IServiceProvider provider)
{
return builder.Add(new EFConfigurationSource(provider));
}
} protected DbContextCore(DbContextOptions options, IEnumerable<IHook> hooks) : base(options)
{
_hooks = hooks ?? throw new ArgumentNullException(nameof(hooks));
} public void IgnoreHook(string hookName)
{
_ignoredHookList.Add(hookName);
} internal sealed class RowIntegrityHook : PostActionHook<IHasRowIntegrity>
{
public override string Name => HookNames.RowIntegrity;
public override int Order => int.MaxValue;
public override EntityState HookState => EntityState.Unchanged;
protected override void Hook(IHasRowIntegrity entity, HookEntityMetadata metadata, IUnitOfWork uow)
{
metadata.Entry.Property(EFCore.Hash).CurrentValue = uow.EntityHash(entity);
}
} //DbContextCore : IUnitOfWork
public string EntityHash<TEntity>(TEntity entity) where TEntity : class
{
var row = Entry(entity).ToDictionary(p => p.Metadata.Name != EFCore.Hash &&
!p.Metadata.ValueGenerated.HasFlag(ValueGenerated.OnUpdate) &&
!p.Metadata.IsShadowProperty());
return EntityHash<TEntity>(row);
}
protected virtual string EntityHash<TEntity>(Dictionary<string, object> row) where TEntity : class
{
var json = JsonConvert.SerializeObject(row, Formatting.Indented);
using (var hashAlgorithm = SHA256.Create())
{
var byteValue = Encoding.UTF8.GetBytes(json);
var byteHash = hashAlgorithm.ComputeHash(byteValue);
return Convert.ToBase64String(byteHash);
}
} IsTamperedAsync HasTamperedEntryAsync TamperedEntryListAsync
public const string CreatedDateTime = nameof(ICreationTracking.CreatedDateTime); public const string CreatedByUserId = nameof(CreatedByUserId); public const string CreatedByBrowserName = nameof(CreatedByBrowserName); public const string CreatedByIP = nameof(CreatedByIP); public const string ModifiedDateTime = nameof(IModificationTracking.ModifiedDateTime); public const string ModifiedByUserId = nameof(ModifiedByUserId); public const string ModifiedByBrowserName = nameof(ModifiedByBrowserName); public const string ModifiedByIP = nameof(ModifiedByIP);
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.NormalizeDateTime();
modelBuilder.NormalizeDecimalPrecision(precision: 20, scale: 6);
base.OnModelCreating(modelBuilder);
} MigrateDbContext<TContext>(this IHost host)
services.AddEFCore<ProjectDbContext>()
.WithTrackingHook<long>()
.WithDeletedEntityHook()
.WithRowIntegrityHook()
.WithNumberingHook(options =>
{
options.NumberedEntityMap[typeof(Task)] = new NumberedEntityOption
{
Prefix = "Task",
FieldNames = new[] {nameof(Task.BranchId)}
};
});
services.AddTenancy()
.WithTenantSession()
.WithStore<InMemoryTenantStore>()
.WithResolutionStrategy<HostResolutionStrategy>(); app.UseTenancy();
PM> Install-Package DNTFrameworkCore PM> Install-Package DNTFrameworkCore.EFCore PM> Install-Package DNTFrameworkCore.EFCore.SqlServer PM> Install-Package DNTFrameworkCore.Web PM> Install-Package DNTFrameworkCore.FluentValidation PM> Install-Package DNTFrameworkCore.Web.Tenancy PM> Install-Package DNTFrameworkCore.Licensing
از خصوصیات Branch* برای سناریوهای چند شعبهای میتوان استفاده کرد که در این صورت اگر یکی از شعب به عنوان دفتر مرکزی در نظر گرفته شود باید Claimای با نام IsHeadOffice با مقدار true از زمان ورود به سیستم برای کاربران آن شعبه در نظر گرفته شود.