利用控制器和视图中的继承

Hoo*_*ini 13 oop asp.net-mvc inheritance repository unit-of-work

我刚才在codereview.stackexchange.com上发布了这篇评论 ...我觉得它可能更适合stackoverflow,因为它更像是一个问题,而不是代码审查.

它需要一些解释,请耐心等待.


我正在ASP.NET MVC中开发一个电子商务网站.用户可以在网站上发布不同类型的广告.

我使用继承来定义我的广告类型,这个问题是利用层次结构来删除控制器和视图中重复的代码.

我有不同的广告类型:SimpleAd,CarRealEstateRental.

每个广告都源自AdBase,其具有所有常见属性:

public abstract class AdBase
{
    public long AdBaseId { get; set; }
    public bool IsActive { get; set; }
    public long UserId { get; set; }
    public string Title { get; set; }
    public short AdDurationInDays { get; set; }
    public string PhotosFolder { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

现在,其他广告来自此基类:

public class SimpleAd : AdBase
{
    public decimal Price { get; set; }
}

public class Car : AdBase
{
    public decimal Price { get; set; }
    public string Make { get; set; }
}

public class RealEstateRental : AdBase
{
    public decimal WeeklyRent { get; set; }
    public DateTime AvailableFrom { get; set; }
    public short NoOfBedrooms { get; set; }
    public short NoOfBathrooms { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我正在使用Entity Framework与数据库进行交互,我使用的是工作单元和存储库模式:

我有一个通用的AdBaseRepository,包含所有常见的广告方法:

public abstract class AdBaseRepository<TEntity> where TEntity : AdBase
{
    protected readonly ApplicationDbContext Context;

    public AdBaseRepository(ApplicationDbContext context)
    {
       Context = context; 
    }

    public TEntity Get(long adBaseId)
    {
        return Context.AdBase.OfType<TEntity>()
                  .Where(r => r.IsActive == true && r.AdBaseId == adBaseId)
                  .FirstOrDefault();
    }

    // more common methods here...
}
Run Code Online (Sandbox Code Playgroud)

其他广告存储库继承自上述类:

public class SimpleAdRepository : AdBaseRepository<SimpleAd>
{
    public SimpleAdRepository(ApplicationDbContext context) : base(context)
    {
    }
}

public class CarRepository : AdBaseRepository<Car>
{
    public CarRepository(ApplicationDbContext context) : base(context)
    {
    }

    // methods which apply only to car here...
}
Run Code Online (Sandbox Code Playgroud)

这是我的工作单位:

public class UnitOfWork
{
    protected readonly ApplicationDbContext Context;

    public UnitOfWork(ApplicationDbContext context)
    {
        Context = context;
        SimpleAd = new SimpleAdRepository(Context);
        RealEstateRental = new RealEstateRentalRepository(Context);
        Car = new CarRepository(Context);
    }

    public SimpleAdRepository SimpleAd { get; private set; }
    public RealEstateRentalRepository RealEstateRental { get; private set; }
    public CarRepository Car { get; private set; }

    public int SaveChanges()
    {
        return Context.SaveChanges();
    }

    public void Dispose()
    {
        Context.Dispose();
    }
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,我对所有内容感到满意......但问题是我不知道如何在控制器和视图中利用这种继承层次结构.

目前,我有3个控制器:SimpleAdController,CarControllerRealEstateRentalController:

public class SimpleAdController : ControllerBase
{
    private UnitOfWork _unitOfWork;

    public SimpleAdController(UnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    [HttpGet]
    // display specific ad
    public ActionResult Display(long id)
    {
        SimpleAd simpleAd = _unitOfWork.SimpleAd.Get(id);
        /* 
         * I have not included my ViewModel Classes in this question to keep
         * it small, but the ViewModels follow the same inheritance pattern
         */
        var simpleAdDetailsViewModel = Mapper.Map<SimpleAdDetailsViewModel>(simpleAd);
        return View(simpleAdDetailsViewModel);
    }
}
Run Code Online (Sandbox Code Playgroud)

CarController并且RealEstateRentalController具有相同的Display方法,除了广告的类型不同(例如CarController我有):

    public ActionResult Display(long id)
    {
        Car car = _unitOfWork.Car.Get(id);
        var carViewModel = Mapper.Map<CarViewModel>(car);
        return View(car);
    }
Run Code Online (Sandbox Code Playgroud)

我想要实现的是创建一个AdBaseController将所有常用方法放入其中,如下所示:

public class AdBaseController : ControllerBase
{
    private UnitOfWork _unitOfWork;

    public AdBaseController(UnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    // Display for generic ad type
    [HttpGet]
    public ActionResult Display(long id)
    {
        // SimpleAd simpleAd = _unitOfWork.SimpleAd.Get(id);
        /* 
         * I need to replace the above line with a generic ad type... 
         * something like: _unitOfWork<TAd>.GenericAdRepository.Get(id)
         */

        // var simpleAdDetailsViewModel = Mapper.Map<SimpleAdDetailsViewModel>(simpleAd);
        // return View(simpleAdDetailsViewModel);
        /* 
         * similarly I have to replace the above 2 lines with a generic type
         */
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我执行上述操作,那么我的广告控制器可以继承它,我不需要在每一个中重复相同的显示方法......但是我需要制作我的UnitOfWork通用...或者有2个UoW(通用的和非通用的)...我不确定这是不是一个好主意?有任何建议AdBaseController吗?


同样地,我在我的视图中重复了很多代码.例如,这是显示SimpleAdView:

<div class="row">
    <div class="col-l">
        @*this partial view shows Ad photos and is common code for all ad types*@
        @Html.Partial("DisplayAd/_Photos", Model)
    </div>
    <div class="col-r">
        <div class="form-row">
            @*Common in all ads*@
            <h5>@Model.Title</h5>
        </div>

        @*showing ad specific fields here*@
        <div class="form-row">
            <h5 class="price">$@Model.Price</h5>
        </div>

        @*Ad heading is common among all ad types*@
        @Html.Partial("DisplayAd/_AdBaseHeading", Model)
    </div>
</div>
@*Ad Description is common among all ad types*@
@Html.Partial("DisplayAd/_Description", Model)
Run Code Online (Sandbox Code Playgroud)

这是我的展示CarView:

<div class="row">
    <div class="col-l">
        @*Common in all ads*@
        @Html.Partial("DisplayAd/_Photos", Model)
    </div>
    <div class="col-r">
        <div class="form-row">
            @*Common in all ads*@
            <h5>@Model.Title</h5>
        </div>

       @*Price and Make are specific to Car*@ 
        <div class="form-row">
            <h5 class="price">$@Model.Price</h5>
        </div>
        <div class="form-row">
            <h5 class="make">@Model.Make</h5>
        </div>

        @*Common in all ads*@ 
        @Html.Partial("DisplayAd/_AdBaseHeading", Model)
    </div>
</div>
@*Common in all ads*@
@Html.Partial("DisplayAd/_Description", Model)
Run Code Online (Sandbox Code Playgroud)

同样,我觉得我在每个视图中重复了很多代码.我试图通过将它们放在常见的部分视图中来减少重复代码的数量.我不确定是否有更好的方法来做到这一点?

Dmi*_*lov 4

从技术上讲是可能的。对于类似的实体,您可以引入枚举并使用它来指示您在 中处理的实体类型controller。您可以创建通用视图来处理类似的广告(但当然,您需要根据模型广告类型显示/隐藏相应的 UI 元素)。controller这是说明这个想法的伪代码:

using System.Threading.Tasks;
using AutoMapper;
using MyNamespace.Data;
using Microsoft.AspNetCore.Mvc;
using MyNamespace.ViewModels;

namespace MyNamespace
{
    public enum AdType
    {
        [Description("Simple Ad")]
        SimpleAd = 0,

        [Description("Car")]
        Car = 1,

        [Description("Real Estate Rental")]
        RealEstateRental = 2
    }

    public class AdController : Controller
    {
        private readonly ApplicationDbContext _context;
        private readonly IMapper _mapper;

        public AdController(
            ApplicationDbContext context,
            IMapper mapper)
        {
            _context = context;
            _mapper = mapper;
        }

        [HttpGet("Ad/{type}")]
        public IActionResult Index(AdType? type = AdType.SimpleAd)
        {
            switch (type)
            {
                case AdType.RealEstateRental:
                    return RedirectToAction("RealEstateRental");
                case AdType.Car:
                    return RedirectToAction("Car");
                case AdType.SimpleAd:
                default:
                    return RedirectToAction("SimpleAd");
            }
        }

        [HttpGet("Ad/Car")]
        public IActionResult Car()
        {
            return View("Index", AdType.Car);
        }

        [HttpGet("Ad/RealEstateRental")]
        public IActionResult RealEstateRental()
        {
            return View("Index", AdType.RealEstateRental);
        }

        [HttpGet("Ad/SimpleAd")]
        public IActionResult SimpleAd()
        {
            return View("Index", AdType.SimpleAd);
        }

        [HttpGet("Ad/List/{type}")]
        public async Task<IActionResult> List(AdType type)
        {
            // var list = ... switch to retrieve list of ads via switch and generic data access methods 
            return list;
        }

        [HttpGet("Ad/{type}/Details/{id}")]
        public async Task<IActionResult> Details(AdType type, int id)
        {
            var ad = // ... switch by type to retrieve list of ads via switch and generic data access methods
            if (ad == null) return NotFound($"Ad not found.");

            // for instance - configure mappings via Automapper from DB entity to model views
            var model = _mapper.Map<AdViewModel>(ad);

            // Note: view will have to detect the exact ad instance type and show/hide corresponding UI fields
            return View(model);
        }

        [HttpGet("Ad/{type}/Add/")]
        public IActionResult Add(AdType type)
        {
            var ad = // ... switch by type to validate/add new entity  

            return View(_mapper.Map<AdEditModel>(ad));
        }

        [HttpPost("Ad/{type}/Add/")]
        public async Task<IActionResult> Add(AdEditModel model)
        {
            // detect ad type and save 
            return View(model);
        }

        [HttpGet("Ad/{type}/Edit/{id}")]
        public async Task<IActionResult> Edit(AdType type, int id)
        {
            // similar to Add
            return View(model);
        }

        [HttpPost("Ad/{type}/Edit/{id}")]
        public async Task<IActionResult> Edit(AdEditModel model)
        {
            // similar to Add
            return View(model);
        }

        // And so on
    }
}
Run Code Online (Sandbox Code Playgroud)

但我应该指出,UI 相关代码中的继承最终会导致更多的问题而不是好处。代码变得更加复杂,难以维护并保持干净。Views因此,将所有的和分开更有意义Controllers,即使它们的代码彼此非常接近。您可以开始优化 DI 服务(也称为business logic)或类似层下方的“重复代码”使用。

UI层面的问题repeated code应该通过提取组件(又名,,,controls)来解决。控制器继承是可能的,但会使代码更难维护。partial viewsview components