0

After learning a bit about ASP.NET Core MVC, I have built a simple CRUD based web application. Although it is simple, I am now trying to learn how to make smaller controllers by moving business logic, and data access outside of the controller into their own layers. The problem is that the model binding in the controller is not easily passed to other layers.

Before moving all of the business and data access logic outside of the controller, the creation of the Entity worked. There is also a GetAll() method (not shown below), that uses the separate layers to fetch and display all entities storied in the database. But when trying to create an entity (seen below), the following error occurs:
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.

How can a model binded object created from a View be passed into n-tier layers of a web application?

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();
        }
    }
}
crayden
  • 2,130
  • 6
  • 36
  • 65

1 Answers1

1

Your code has two problems. Let's start with the entity which is the error you posted about. The following code does not make any sense:

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

Why would an Entity know the context to which it belongs? That's inverting the dependency chain. You can remove that constructor and the field:

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

The second problem that you will face now that the entity can be created, is that your View works with a CreateEntityModel but your Action expects an Entity. This is not an actual problem given that the models are identical, but your Actions should not work with Entities but rather with the ViewModel that you created, so:

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

Further, you should always keep in mind that the web application project is a client of your business logic. Here, some people claim that the service layer (your EntityService) should receive the ViewModel and transform it, while other people think it should receive the final Entity, up to you to decide which version to follow.

If you want the service to receive ViewModels and convert them:

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

If you want your Service to receive the final Entity directly:

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");
}

Note: whenever you see code like entityService= new EntityService (_userManager, _context);, you are not doing Dependency Injection correctly. The official docs for ASP.NET Core provide a great introduction into both Dependency Injection and how to use it in an ASP.NET Core application.
Another note: unless you have strong reasons, which I doubt, I would suggest you to forget about the Repository layer and use Entity Framework Core directly. You may want to read this answer of mine for some viewpoints regarding Unit of Work andd Repository patterns in ASP.NET Core.

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120