taking advantage of inheritance in Controllers and Views

前端 未结 3 421
清歌不尽
清歌不尽 2021-01-04 06:10

I had posted this review on codereview.stackexchange.com a while ago... I feel it may be more suitable for stackoverflow, since it is more of a question than a code review.

相关标签:
3条回答
  • 2021-01-04 06:56

    More abstraction -> more abstraction leaks.

    I have complete solution how to generate controllers from EF model definition using exression trees

    Check this, how the controller's code looks after all "duplicated code" is removed:

    https://github.com/DashboardCode/Routines/blob/master/AdminkaV1/Injected.AspCore.MvcApp/Controllers/UsersController.cs

    or this ("Roles" can be created when "Users" was imported from AD )

    https://github.com/DashboardCode/Routines/blob/master/AdminkaV1/Injected.AspCore.MvcApp/Controllers/RolesController.cs

    Those blocks on start configures full controller with a lot of features (e.g. rowversion support, sql server constraints error parsers and etc., one-to many, many-to-many, unhandled expceptions support)

    static ControllerMeta<User, int> meta = new ControllerMeta<User, int>(
                // how to find entity by "id"      
                findByIdExpression: id => e => e.UserId == id,
                // how to extract "id" from http responce      
                keyConverter: Converters.TryParseInt,
                // configure EF includes for Index page
                indexIncludes: chain => chain
                           .IncludeAll(e => e.UserPrivilegeMap)
                // ... and so on, try to read it
    

    But those definitions actually is a kind of new Internal DSL. In fact you are asking "how to write new DSL that defines controllers/pages in bigger bricks". Answer is - it is easy, but there is a reason why people stick to general purpose languages. It is because it is "general".

    P.S. One detail: if you want that "full controller" could be contracted/configured at run time, therefore you are forced to parse http requests by youself - and ignore MS parameters binding model - it is because BindAttribute - important binding modificator - can't be "set up" run time simple way. For many people - even when they loose "int id" in parameters list - is too high price. Even if refusing of MS parameters binding is very logical: why would you need to keep MS parameters binding magic when you are going to configure whole controller magically?

    0 讨论(0)
  • 2021-01-04 06:59

    Forgive me if I've misunderstood but provided you added a genric UOW it seems to me you could do something like this: I don't see why it would be bad to do this

    public class AdBaseController : ControllerBase
    {
        private IUnitOfWork _unitOfWork;
    
        public AdBaseController(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }
    
        public ActionResult GetDisplayAction<TAd, TViewModel>(long id)
        {
            SimpleAd simpleAd = _unitOfWork<TAd>.GenericAdRepository.Get(id)
            var viewModel = Mapper.Map<TViewModel>(simpleAd);         
            return View(viewModel);
        }
    }
    
    public class SimpleAdController : ControllerBase
    {    
        public SimpleAdController(IUnitOfWork unitOfWork) : base(unitOfWork)
        {
        }
    
        [HttpGet]
        public ActionResult Display(long id)
        {
            return GetDisplayAction<AdType, ViewModelType>();
        }
    }
    
    0 讨论(0)
  • 2021-01-04 07:02

    Technically it is possible. For similar entities you can introduce enumeration and use it to indicate what entity type you deal with in controller. You can create generic view to handle similar ads (but of course you will need to show/hide corresponding UI elements depending on the model ad type). this is the pseudo code for controller to illustrate the idea:

    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
        }
    }
    

    But I should note, that inheritance in UI related code eventually results into more problems than benefits. The code becomes more complex to maintain and keep it clean. So it makes more sense to keep all your Views and Controllers separate, even if they have the code very close to each other. You could start optimiziong the "repeated code" usage below the your DI services (aka business logic) or similar layer.

    The repeated code problem for UI level should be solved via extracting components (aka controls, partial views, view components). Controller inheritance is possible but make code harder to maintain.

    0 讨论(0)
提交回复
热议问题