{"id":13429622,"url":"https://github.com/rabbal/DNTFrameworkCore","last_synced_at":"2025-03-16T03:32:00.022Z","repository":{"id":46761171,"uuid":"169787814","full_name":"rabbal/DNTFrameworkCore","owner":"rabbal","description":"Lightweight and Extensible Infrastructure for Building Web Applications - Web Application Framework","archived":false,"fork":false,"pushed_at":"2023-11-18T05:53:26.000Z","size":4054,"stargazers_count":314,"open_issues_count":2,"forks_count":76,"subscribers_count":27,"default_branch":"master","last_synced_at":"2024-05-29T01:34:11.548Z","etag":null,"topics":["aop","aspnet-core","cqrs","crud","design-patterns","dotnet-core","efcore","event-driven","framework","integration-testing","master-detail","multitenancy","solid","transaction","unit-testing","validation"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rabbal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":["https://coffeebede.ir/rabbal"]}},"created_at":"2019-02-08T19:30:07.000Z","updated_at":"2024-05-12T08:16:51.000Z","dependencies_parsed_at":"2022-09-18T06:55:21.851Z","dependency_job_id":"9aeae20d-07d4-446f-ad42-2d9074d4518a","html_url":"https://github.com/rabbal/DNTFrameworkCore","commit_stats":{"total_commits":218,"total_committers":3,"mean_commits":72.66666666666667,"dds":0.04587155963302747,"last_synced_commit":"7fda8a3c58a0a97ace151f5eac3085a22e04b8fb"},"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rabbal%2FDNTFrameworkCore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rabbal%2FDNTFrameworkCore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rabbal%2FDNTFrameworkCore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rabbal%2FDNTFrameworkCore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rabbal","download_url":"https://codeload.github.com/rabbal/DNTFrameworkCore/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243822309,"owners_count":20353496,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aop","aspnet-core","cqrs","crud","design-patterns","dotnet-core","efcore","event-driven","framework","integration-testing","master-detail","multitenancy","solid","transaction","unit-testing","validation"],"created_at":"2024-07-31T02:00:42.610Z","updated_at":"2025-03-16T03:31:59.636Z","avatar_url":"https://github.com/rabbal.png","language":"C#","readme":"\u003cimg alt=\"logo\" src=\"docs/logo.png\" height=\"64\"/\u003e\n\n\n[![.NET](https://github.com/rabbal/DNTFrameworkCore/actions/workflows/dotnet.yml/badge.svg)](https://github.com/rabbal/DNTFrameworkCore/actions/workflows/dotnet.yml)\n![Nuget](https://img.shields.io/nuget/v/DNTFrameworkCore)\n[![build aspnet-core-api template](https://github.com/rabbal/DNTFrameworkCore/actions/workflows/aspnet-core-api-template.yml/badge.svg)](https://github.com/rabbal/DNTFrameworkCore/actions/workflows/aspnet-core-api-template.yml)\n![Nuget](https://img.shields.io/nuget/v/DNTFrameworkCoreTemplateAPI?label=aspnet-core-api-template)\n### What is DNTFrameworkCore?\n\n`DNTFrameworkCore` is a Lightweight and \nExtensible Infrastructure for Building High-Quality Web Applications Based on ASP.NET Core and has the following goals:\n* Common structures in various applications like Cross-Cutting Concerns, etc\n* Follow DRY principle to focus on main business logic\n* Reduce the development time\n* Less bug and stop bug propagation \n* Reduce the training time of the new developer with low knowledge about OOP and OOD\n\nCRUD-based Thinking\n----\n\nApplication Service\n```c#\npublic interface IBlogService : IEntityService\u003cint, BlogModel\u003e\n{\n}\n\npublic class BlogService : EntityService\u003cBlog, int, BlogModel\u003e, IBlogService\n{\n    private readonly IMapper _mapper;\n\n    public BlogService(\n        IDbContext dbContext,\n        IEventBus bus,\n        IMapper mapper) : base(dbContext, bus)\n    {\n        _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));\n    }\n\n    public override Task\u003cIPagedResult\u003cBlogModel\u003e\u003e FetchPagedListAsync(FilteredPagedRequest request,\n        CancellationToken cancellationToken = default)\n    {\n        return EntitySet.AsNoTracking()\n            .Select(b =\u003e new BlogModel\n            {\n                Id = b.Id,\n                Version = b.Version,\n                Url = b.Url,\n                Title = b.Title\n            }).ToPagedListAsync(request, cancellationToken);\n    }\n\n    protected override void MapToEntity(BlogModel model, Blog blog)\n    {\n        _mapper.Map(model, blog);\n    }\n\n    protected override BlogModel MapToModel(Blog blog)\n    {\n        return _mapper.Map\u003cBlogModel\u003e(blog);\n    }\n}\n ``` \n \nASP.NET Core WebAPI\n```c#\n[Route(\"api/[controller]\")]\npublic class BlogsController : EntityController\u003cIBlogService, int, BlogModel\u003e\n{\n    public BlogsController(IBlogService service) : base(service)\n    {\n    }\n\n    protected override string CreatePermissionName =\u003e PermissionNames.Blogs_Create;\n    protected override string EditPermissionName =\u003e PermissionNames.Blogs_Edit;\n    protected override string ViewPermissionName =\u003e PermissionNames.Blogs_View;\n    protected override string DeletePermissionName =\u003e PermissionNames.Blogs_Delete;\n}\n ```\n \n ASP.NET Core MVC\n ```c#\npublic class BlogsController : EntityController\u003cIBlogService, int, BlogModel\u003e\n{\n    public BlogsController(IBlogService service) : base(service)\n    {\n    }\n\n    protected override string CreatePermissionName =\u003e PermissionNames.Blogs_Create;\n    protected override string EditPermissionName =\u003e PermissionNames.Blogs_Edit;\n    protected override string ViewPermissionName =\u003e PermissionNames.Blogs_View;\n    protected override string DeletePermissionName =\u003e PermissionNames.Blogs_Delete;\n    protected override string ViewName =\u003e \"_BlogPartial\";\n}\n ```\n \n _BlogPartial.cshtml\n ```razor\n@inherits EntityFormRazorPage\u003cBlogModel\u003e\n@{\n    Layout = \"_EntityFormLayout\";\n    EntityName = \"Blog\";\n    DeletePermission = PermissionNames.Blogs_Delete;\n    CreatePermission = PermissionNames.Blogs_Create;\n    EditPermission = PermissionNames.Blogs_Edit;\n    EntityDisplayName = \"Blog\";\n}\n\n\u003cdiv class=\"form-group row\"\u003e\n    \u003cdiv class=\"col col-md-8\"\u003e\n        \u003clabel asp-for=\"Title\" class=\"col-form-label text-md-left\"\u003e\u003c/label\u003e\n        \u003cinput asp-for=\"Title\" autocomplete=\"off\" class=\"form-control\"/\u003e\n        \u003cspan asp-validation-for=\"Title\" class=\"text-danger\"\u003e\u003c/span\u003e\n    \u003c/div\u003e\n\u003c/div\u003e\n\u003cdiv class=\"form-group row\"\u003e\n    \u003cdiv class=\"col\"\u003e\n        \u003clabel asp-for=\"Url\" class=\"col-form-label text-md-left\"\u003e\u003c/label\u003e\n        \u003cinput asp-for=\"Url\" class=\"form-control\" type=\"url\"/\u003e\n        \u003cspan asp-validation-for=\"Url\" class=\"text-danger\"\u003e\u003c/span\u003e\n    \u003c/div\u003e\n\u003c/div\u003e\n\n```\n ![Role Modal MVC](https://github.com/rabbal/DNTFrameworkCore/blob/master/docs/role-modal-edit.JPG)\n## Installation\n\nTo create your first project based on DNTFrameworkCore you can install the following packages:\n```\nPM\u003e Install-Package DNTFrameworkCore\nPM\u003e Install-Package DNTFrameworkCore.EFCore\nPM\u003e Install-Package DNTFrameworkCore.EFCore.SqlServer\nPM\u003e Install-Package DNTFrameworkCore.Web\nPM\u003e Install-Package DNTFrameworkCore.Web.Tenancy\nPM\u003e Install-Package DNTFrameworkCore.Web.EFCore\nPM\u003e Install-Package DNTFrameworkCore.Licensing\nPM\u003e Install-Package DNTFrameworkCore.FluentValidation\n\n```\n\nOR\n\n1- Run the following command to install boilerplate project template based on ASP.NET Core Web API and DNTFrameworkCore:\n\n```dotnet new --install DNTFrameworkCoreTemplateAPI::*‌‌```\n\n2- Create new project with installed template:\n\n```dotnet new dntcore-api```\n\nNow you have a solution like below that contains complete identity management feature includes user, role, and dynamic permission management and also integrated with persistent JWT authentication mechanism:\n\n![Solution Structure](https://github.com/rabbal/DNTFrameworkCore/blob/master/docs/dnt-solution.jpg)\n\nFor more info about templates, you can watch [DNTFrameworkCoreTemplate repository](https://github.com/rabbal/DNTFrameworkCoreTemplate)\n\n## Features\n\n* Application Input Validation\n* Transaction Management\n* Eventing\n* EntityGraph Tracking (Master-Detail)\n* Numbering\n* Functional Programming Error Handling\n* Permission Authorization\n* EntityService\n* EntityController (API and MVC)\n* DbLogger Provider based on EFCore\n* ProtectionKey EFCore Store\n* Hooks\n* SoftDelete\n* Tenancy\n* Tracking mechanism (ICreationTracking, IModificationTracking)\n* FluentValidation Integration\n* BackgroundTaskQueue\n* RowIntegrity\n* StartupTask mechanism\n* CQRS (coming soon)\n* EntityHistory (coming soon)\n\n## Usage\n[DNTFrameworkCore.TestAPI Complete ASP.NET Core Web API](https://github.com/rabbal/DNTFrameworkCore/tree/master/test/DNTFrameworkCore.TestAPI)\n\n**Create Entity**\n```c#\npublic class Task : Entity\u003cint\u003e, INumberedEntity, IHasRowVersion, IHasRowIntegrity, ICreationTracking, IModificationTracking\n{\n    public const int MaxTitleLength = 256;\n    public const int MaxDescriptionLength = 1024;\n\n    public string Title { get; set; }\n    public string NormalizedTitle { get; set; }\n    public string Number { get; set; }\n    public string Description { get; set; }\n    public TaskState State { get; set; } = TaskState.Todo;\n    public byte[] Version { get; set; }\n}\n```\n\n**Implement ProjectDbContext that inherited from DbContextCore**\n```c#\npublic class ProjectDbContext : DbContextCore\n{\n    public ProjectDbContext(DbContextOptions\u003cProjectDbContext\u003e options, IEnumerable\u003cIHook\u003e hooks) : base(options, hooks)\n    {\n    }\n\n    protected override void OnModelCreating(ModelBuilder modelBuilder)\n    {               \n        modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());\n\n        modelBuilder.AddJsonFields();\n        modelBuilder.AddTrackingFields\u003clong\u003e();\n        modelBuilder.AddIsDeletedField();\n        modelBuilder.AddRowVersionField();\n        modelBuilder.AddRowIntegrityField();\n            \n        modelBuilder.NormalizeDateTime();\n        modelBuilder.NormalizeDecimalPrecision();\n            \n        base.OnModelCreating(modelBuilder);\n    }\n}\n```\n**Create Model/DTO**\n```c#\n[LocalizationResource(Name = \"SharedResource\", Location = \"DNTFrameworkCore.TestAPI\")]\npublic class TaskModel : MasterModel\u003cint\u003e, IValidatableObject\n{\n    public string Title { get; set; }\n\n    [MaxLength(50, ErrorMessage = \"Validation from DataAnnotations\")]\n    public string Number { get; set; }\n\n    public string Description { get; set; }\n    public TaskState State { get; set; } = TaskState.Todo;\n\n    public IEnumerable\u003cValidationResult\u003e Validate(ValidationContext validationContext)\n    {\n        if (Title == \"IValidatableObject\")\n        {\n            yield return new ValidationResult(\"Validation from IValidatableObject\");\n        }\n    }\n}\n```\n\nNote: Based on validation infrastructure, you can validate Model/DTO with various approaches, using by DataAnnotation ValidateAttribute, Implementing IValidatableObject, or Implement IModelValidator\u003cT\u003e that exist in DNTFrameworkCore package.\n\n```c#\npublic class TaskValidator : ModelValidator\u003cTaskModel\u003e\n{\n    public override IEnumerable\u003cModelValidationResult\u003e Validate(TaskModel model)\n    {\n        if (!Enum.IsDefined(typeof(TaskState), model.State))\n        {\n            yield return new ModelValidationResult(nameof(TaskModel.State), \"Validation from IModelValidator\");\n        }\n    }\n}\n```\n\nAlso in most cases, one Model/DTO can be enough for your requirements about Create/Edit/View an entity. However, you can create ReadModel like below:\n```c#\npublic class TaskReadModel : ReadModel\u003cint\u003e\n{\n    public string Title { get; set; }\n    public string Number { get; set; }\n    public TaskState State { get; set; } = TaskState.Todo;\n}\n```\n\n**Implement Service**\n \n```c#\npublic interface ITaskService : IEntityService\u003cint, TaskReadModel, TaskModel, TaskFilteredPagedRequest\u003e\n{\n}\n\npublic class TaskService : EntityService\u003cTask, int, TaskReadModel, TaskModel, TaskFilteredPagedRequest\u003e,\n    ITaskService\n{\n    private readonly IMapper _mapper;\n    public TaskService(IDbContext dbContext, IEventBus bus, IMapper mapper) :base(dbContext, bus)\n    {\n        _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper);\n    }\n\n    public override Task\u003cIPagedResult\u003cTaskReadModel\u003e\u003e FetchPagedListAsync(TaskFilteredPagedRequest request,\n        CancellationToken cancellationToken = default)\n    {\n        return EntitySet.AsNoTracking()\n            .WhereIf(model.State.HasValue, t =\u003e t.State == model.State)\n            .Select(t =\u003e new TaskReadModel\n            {\n                Id = t.Id,\n                Title = t.Title,\n                State = t.State,\n                Number = t.Number\n            }).ToPagedListAsync(request, cancellationToken);\n    }\n\n    protected override void MapToEntity(TaskModel model, Task task)\n    {\n        _mapper.Map(model, task);\n    }\n\n    protected override TaskModel MapToModel(Task task)\n    {\n        return _mapper.Map\u003cTaskModel\u003e(task);\n    }\n}\n```\n\nIn DNTFrameworkCore.EFCore [there is no dependency to AutoMapper](https://cezarypiatek.github.io/post/why-i-dont-use-automapper/) or other mapper libraries, then you can do mapping between Entity and Model manually by implementing MapToModel and MapToEntity abstract methods.\n\n**Implement API Controller**\n```c#\n[Route(\"api/[controller]\")]\npublic class\n    TasksController : EntityController\u003cITaskService, int, TaskReadModel, TaskModel, TaskFilteredPagedRequest\u003e\n{\n    public TasksController(ITaskService service) : base(service)\n    {\n    }\n\n    protected override string CreatePermissionName =\u003e PermissionNames.Tasks_Create;\n    protected override string EditPermissionName =\u003e PermissionNames.Tasks_Edit;\n    protected override string ViewPermissionName =\u003e PermissionNames.Tasks_View;\n    protected override string DeletePermissionName =\u003e PermissionNames.Tasks_Delete;\n}\n```\n\n```c#\n\n[Route(\"api/[controller]\")]\npublic class BlogsController : EntityController\u003cIBlogService, int, BlogModel\u003e\n{\n    public BlogsController(IBlogService service) : base(service)\n    {\n    }\n\n    protected override string CreatePermissionName =\u003e PermissionNames.Blogs_Create;\n    protected override string EditPermissionName =\u003e PermissionNames.Blogs_Edit;\n    protected override string ViewPermissionName =\u003e PermissionNames.Blogs_View;\n    protected override string DeletePermissionName =\u003e PermissionNames.Blogs_Delete;\n}\n\n```\n\nTask-based Thinking\n---------------\nRich Domain Model\n```c#\npublic class PriceType : Entity\u003clong\u003e, IAggregateRoot\n{\n    private PriceType(Title title)\n    {\n        Title = title;\n    }\n    \n    public PriceType(Title title, IPriceTypePolicy policy)\n    {\n        if (title == null) throw new ArgumentNullException(nameof(title));\n        if (policy == null) throw new ArgumentNullException(nameof(policy));\n\n        Title = title;\n\n        if (!policy.IsUnique(this)) ThrowDomainException(\"PriceType Title Should Be Unique\");\n\n        AddDomainEvent(new PriceTypeCreatedDomainEvent(this));\n    }\n\n    public Title Title { get; private set; }\n\n    // public static Result\u003cPriceType\u003e New(Title title, IPriceTypePolicy policy)\n    // {\n    //     if (title == null) throw new ArgumentNullException(nameof(title));\n    //     if (policy == null) throw new ArgumentNullException(nameof(policy));\n    //\n    //     var priceType = new PriceType(title);\n    //     if (!policy.IsUnique(priceType)) return Fail\u003cPriceType\u003e(\"PriceType Title Should Be Unique\");\n    //\n    //     priceType.AddDomainEvent(new PriceTypeCreatedDomainEvent(priceType));\n    //\n    //     return Ok(priceType);\n    // }\n}\n```\nValueObject\n```c#\npublic class Title : ValueObject\n{\n    private Title()\n    {\n    }\n\n    public Title(string value)\n    {\n        value ??= string.Empty;\n\n        switch (value.Length)\n        {\n            case 0:\n                ThrowDomainException(\"title should not be empty\");\n                break;\n            case \u003e 100:\n                ThrowDomainException(\"title is too long\");\n                break;\n        }\n    }\n\n    public string Value { get; private set; }\n\n    protected override IEnumerable\u003cobject\u003e EqualityValues\n    {\n        get { yield return Value; }\n    }\n\n    // public static Result\u003cTitle\u003e New(string value)\n    // {\n    //     value ??= string.Empty;\n    //\n    //     if (value.Length == 0) return Fail\u003cTitle\u003e(\"title should not be empty\");\n    //\n    //     return value.Length \u003e 100 ? Fail\u003cTitle\u003e(\"title is too long\") : Ok(new Title { Value = value });\n    // }\n\n    public static implicit operator string(Title title)\n    {\n        return title.Value;\n    }\n\n    public static explicit operator Title(string title)\n    {\n        return new(title);\n    }\n}\n```\nDomainEvent\n\n```c#\npublic sealed class PriceTypeCreatedDomainEvent : DomainEvent\n{\n    public PriceTypeCreatedDomainEvent(PriceType priceType)\n    {\n        PriceType = priceType ?? throw new ArgumentNullException(nameof(priceType));\n    }\n\n    public PriceType PriceType { get; }\n}\n```\n\nCQRS (Command)\n```c#\npublic sealed class CreatePriceTypeCommand : ICommand\n{\n    public string Title { get; }\n    [JsonConstructor]\n    public CreatePriceTypeCommand(string title) =\u003e Title = title;\n}\n```\nCQRS (CommandHandler)\n```c#\n    public class PriceTypeCommandHandlers : ICommandHandler\u003cRemovePriceTypeCommand, Result\u003e,\n       ICommandHandler\u003cCreatePriceTypeCommand, Result\u003e\n    {\n        private readonly IUnitOfWork _uow;\n        private readonly IPriceTypeRepository _repository;\n        private readonly IPriceTypePolicy _policy;\n        private readonly IEventBus _bus;\n\n        public PriceTypeCommandHandlers(\n            IUnitOfWork uow,\n            IPriceTypeRepository repository,\n            IPriceTypePolicy policy,\n            IEventBus bus)\n        {\n            _uow = uow ?? throw new ArgumentNullException(nameof(uow));\n            _repository = repository ?? throw new ArgumentNullException(nameof(repository));\n            _policy = policy ?? throw new ArgumentNullException(nameof(policy));\n            _bus = bus ?? throw new ArgumentNullException(nameof(bus));\n        }\n\n        public async Task\u003cResult\u003e Handle(RemovePriceTypeCommand command, CancellationToken cancellationToken)\n        {\n            var priceType = await _repository.FindAsync(command.PriceTypeId, cancellationToken);\n            if (priceType is null) return Result.Fail($\"PriceType with id:{command.PriceTypeId} not found\");\n            \n            //Alternative: _repository.Remove(priceType);\n            _uow.Set\u003cPriceType\u003e().Remove(priceType);\n            \n            await _uow.SaveChanges(cancellationToken);\n            await _bus.DispatchDomainEvents(priceType, cancellationToken);\n            \n            return Result.Ok();\n        }\n\n        public async Task\u003cResult\u003e Handle(CreatePriceTypeCommand command, CancellationToken cancellationToken)\n        {\n            var title = new Title(command.Title);\n\n            var priceType = new PriceType(title, _policy);\n\n            //Alternative: _repository.Add(priceType);\n            _uow.Set\u003cPriceType\u003e().Add(priceType);\n            \n            await _uow.SaveChanges(cancellationToken);\n            await _bus.DispatchDomainEvents(priceType, cancellationToken);\n\n            return Result.None;\n        }\n    }\n```\n\n## TODO\n- Write unit tests for packages\n- Use nullable reference types\n- Use HTTP problem details \n- Implement IntegrationEvent mechanism\n- Dispatching DomainEvents before/after save changes\n- Complete CQRS and MediatR related behaviors\n- Publish DNTFrameworkCore.Cqrs.* packages to nuget.org\n- Provide sample codes for multi-tenant scenarios\n- Provide sample codes for using Cqrs packages and Rich Domain Model\n- Complete FilterExpression class with custom DSL support for filtering mechanism\n- Complete DNTFrameworkCore.NHibernate packages\n- Implement a mechanism to provide correlationId in distributed systems\n\n\n\n\n## ASP.NET Boilerplate\n[DNTFrameworkCore (old versions) vs ABP Framework](https://medium.com/@rabbal/dntframeworkcore-vs-abp-framework-b48f5b7f8a24)\n\nA small part of this project like the following sections are taken from [ABP](https://github.com/aspnetboilerplate/aspnetboilerplate)\n- Validation with refactoring to support functional programming error handling mechanism\n","funding_links":["https://coffeebede.ir/rabbal"],"categories":["Frameworks, Libraries and Tools","框架, 库和工具"],"sub_categories":["Application Frameworks","应用程序框架"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frabbal%2FDNTFrameworkCore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frabbal%2FDNTFrameworkCore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frabbal%2FDNTFrameworkCore/lists"}