通过n层层绑定视图表单属性

时间:2018-07-07 22:13:51

标签: c# asp.net-core entity-framework-core

学习了一些有关ASP.NET Core MVC的知识之后,我构建了一个简单的基于CRUD的Web应用程序。尽管很简单,但我现在正在尝试学习如何通过将业务逻辑和控制器外部的数据访问移入其自己的层来制作更小的控制器。问题在于,控制器中的模型绑定不容易传递给其他层。

在将所有业务和数据访问逻辑移出控制器之前,创建实体是可行的。还有一个GetAll()方法(以下未显示),该方法使用单独的层来获取和显示存储在数据库中的所有实体。但是,当尝试创建实体(如下所示)时,会发生以下错误:
InvalidOperationException: Could not create an instance of type 'MyProject.Models.Entity'. Model bound complex types must not be abstract or value types and must have a parameterless constructor.

如何将通过View创建的模型绑定对象传递到Web应用程序的n层中?

Entity.cs

namespace MyProject.Models
{
    public enum EntityType { Type1, Type2 }
    public class Entity
    {
        private readonly ApplicationDbContext context;

        public Entity(ApplicationDbContext _context)
        {
            context = _context;
        }

        public int EntityId { get; set; }
        public string Name { get; set; }
        public DateTime Date1 { get; set; }
        public EntityType Type { get; set; }
    }
}

CreateEntityModel.cs

namespace MyProject.Models
{
    public class CreateEntityModel
    {
        public string Name { get; set; }
        public string Date1 { get; set; }
        public int Type { get; set; }
    }
}

Create.cshtml

@model CreateEntityModel

<h1>Create Entity</h1>
<form asp-controller="entity" asp-action="createentity" method="post">
    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="Date1"></label>
        <input asp-for="Date1" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="Type"></label>
        <select asp-for="Type">
            <option selected>Choose entity type</option>
            <option value="0">Type1</option>
            <option value="1">Type2</option>
        </select>
    </div>
    <button type="submit">Create</button>
</form>

EntityController.cs

namespace MyProject.Controllers
{
    public class EntityController : Controller
    {
        private UserManager<ApplicationUser> userManager;
        private readonly ApplicationDbContext context;
        private EntityService entityService;

        public EntityController(UserManager<ApplicationUser> _userManager,
                ApplicationDbContext _context)
        {
            userManager = _userManager;
            context = _context;
            entityService= new EntityService (_userManager, _context);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> CreateEntity([Bind("Name, Date1, Type")] Entity entity)
        {
            if (ModelState.IsValid)
            {
                await entityService.Create(entity);
                return RedirectToAction(nameof(Index));
            }

            return View("~/Views/MyView");
        }
    }
}

EntityService.cs

namespace MyProject.Business
{
    public class EntityService
    {
        private UserManager<ApplicationUser> userManager;
        private readonly ApplicationDbContext context;
        private EntityRepository entityRepository;

        public EntityService(
            UserManager<ApplicationUser> _userManager,
            ApplicationDbContext _context)
        {
            userManager = _userManager;
            context = _context;
            entityRepository = new EntityRepository(_context);
        }

        public async Task Create(Entity entity)
        {
            await entityRepository.Create(entity);
        }
    }
}

EntityRepository.cs

namespace MyProject.Data
{
    public class EntityRepository
    {
        private readonly ApplicationDbContext context;

        public EntityRepository(ApplicationDbContext _context)
        {
            context = _context;
        }

        public async Task Create(Entity entity)
        {
            context.Add(entity);
            await context.SaveChangesAsync();
        }
    }
}

1 个答案:

答案 0 :(得分:1)

您的代码有两个问题。让我们从发布错误的实体开始。以下代码没有任何意义:

public Entity(ApplicationDbContext _context)
{
    context = _context;
}

为什么实体知道其所属的上下文?那正在颠倒依赖性链。您可以删除该构造函数和字段:

public class Entity
{
    public int EntityId { get; set; }
    public string Name { get; set; }
    public DateTime Date1 { get; set; }
    public EntityType Type { get; set; }
}

现在可以创建实体了,您将面临的第二个问题是您的视图使用CreateEntityModel,但是您的Action需要一个Entity。鉴于模型相同,这不是一个实际的问题,但是您的Actions不应与Entities一起使用,而应与您创建的ViewModel一起使用,所以:

public async Task<IActionResult> CreateEntity([Bind("Name, Date1, Type")] CreateEntityModel entity)

此外,您应该始终牢记,Web应用程序项目是业务逻辑的 client 。在这里,有些人声称服务层(您的EntityService)应该接收ViewModel并对其进行转换,而其他人则认为它应该接收最终的Entity,由您决定要遵循哪个版本。

如果您希望服务接收ViewModel并将其转换:

public class EntityService
{
    ...
    public async Task Create(CreateEntityModel model)
    {
        var entity = new Entity
        {
            X = model.X...
        };
        await entityRepository.Create(entity);
    }
}

如果您希望您的服务直接接收最终实体:

public async Task<IActionResult> CreateEntity([Bind("Name, Date1, Type")] CreateEntityModel model)
{
    if (ModelState.IsValid)
    {
        var entity = new Entity 
        {
            X = model.X...
        };

        await entityService.Create(entity);
        return RedirectToAction(nameof(Index));
    }

    return View("~/Views/MyView");
}

注意:每当您看到类似entityService= new EntityService (_userManager, _context);的代码时,您就没有正确进行依赖注入。 official docs for ASP.NET Core很好地介绍了依赖注入以及如何在ASP.NET Core应用程序中使用它。
另一个说明:除非您有很强的理由(我对此表示怀疑),否则建议您不要使用Repository层,而直接使用Entity Framework Core。您可能需要阅读this answer of mine,以获取有关ASP.NET Core中的工作单元和d存储库模式的一些观点。