0

I am rusty in ASP.Net MVC and am struggling with the proper way to resolve a situation I have ran into on a large project that I inherited. The application has a large number of controllers that were originally well defined in their roles. As the application has grown I am running into issues with increasing overlap of functionality between them.

For example, there is now a Dashboard controller that gives a user an overview of key parameters happening throughout the rest of the application. As a result this controller relies on a lot of dependencies in order to populate the view. It also performs a lot of business logic/ calculations on that data also needed for the dashboard. Now we have the new additional requirement to archive these same values into the database at key milestones through the application process. This works great when the dashboard view is in use (it uses ajax and makes frequent calls for updates) - and I can have it store the values as needed when we hit those milestones.

But when the dashboard is not being used - and no calls to the dashboard view / controller are being made we still need to store the data at the milestones. I do not want to duplicate the same logic in every other controller where the various milestones may be hit, but am struggling to abstract it with all the necessary dependencies. What is the best way to handle this?

[Authorize]
public class DashboardController : SdControllerBase
{
    private readonly ICoLabAssembler _assembler;
    private readonly ICoLabDataMapper _colabDataMapper;
    private readonly IClusterAssembler _clusterAssembler;
    private readonly IClusterDataMapper _clusterDataMapper;
    private readonly IStatementDataMapper _statementDataMapper;
    private readonly IStatementAssembler _statementAssembler;
    private readonly IActiveCoLabIdService _activeCoLabIdService;
    private readonly IIsmAlgorithmAdapterService _ismAdapter;
    private readonly IPrioritizationVoteActionDataMapper _prioritizationVoteActionDataMapper;
    private readonly IVoteDataMapper<PrioritizationVote, Participant> _prioritizationVoteDataMapper;
    private readonly IPrincipal _currentUser;
    private readonly SdContext _context;

    private int? _activeColabId;
    protected int ActiveColabId
    {
        get
        {
            _activeColabId = _activeColabId ?? _activeCoLabIdService.GetColabId();
            return _activeColabId.Value;
        }
    }

    public DashboardController(ICoLabAssembler assembler, ICoLabDataMapper dataMapper, 
        IClusterAssembler clusterAssembler, IClusterDataMapper clusterDataMapper,
        IStatementDataMapper statementDataMapper, IStatementAssembler statementAssembler,
        IActiveCoLabIdService activeCoLabIdService, IPrioritizationVoteActionDataMapper prioritizationVoteActionDataMapper, 
        IVoteDataMapper<PrioritizationVote, Participant> prioritizationVoteDataMapper, IPrincipal currentUser, IIsmAlgorithmAdapterService ismAdapter, SdContext context)
    {
        _assembler = assembler;
        _colabDataMapper = dataMapper;
        _clusterAssembler = clusterAssembler;
        _clusterDataMapper = clusterDataMapper;
        _statementDataMapper = statementDataMapper;
        _statementAssembler = statementAssembler;
        _activeCoLabIdService = activeCoLabIdService;
        _prioritizationVoteActionDataMapper = prioritizationVoteActionDataMapper;
        _prioritizationVoteDataMapper = prioritizationVoteDataMapper;
        _currentUser = currentUser;
        _ismAdapter = ismAdapter;
        _context = context;
    }
    
    public async Task<ActionResult> Index()
    {
        var model = await buildDashboardViewModelAsync();   

        return View(model);
    }

    [HttpPost]
    public async Task<JsonResult> getDashboardAllData(int coLabID = 0)
    {
        var model = await buildDashboardViewModelAsync(coLabID);
        return Json(model, JsonRequestBehavior.AllowGet);
    }

    private async Task<CoLabDashboardViewModel> buildDashboardViewModelAsync(int coLabID = 0)
    {
        //Lots of business logic here that uses the dependencies above
        
        return model;
    }
Mark1270287
  • 411
  • 1
  • 4
  • 14
  • Can't you create a service that does the work and inject it wherever needed, i.e. in the `DashboardController`? Maybe I'm not understanding the issue – Aluan Haddad Jan 09 '21 at 21:17
  • Aluan Haddard - Yes that may be exactly what I need to do. In past projects the complexity never progressed past the point where logic in the controller was no longer sufficient. I am new to dependency injection and using a service layer. The googling I have done so far has been helpful - but any good resources on the topic for beginners would be appreciated! – Mark1270287 Jan 10 '21 at 14:25
  • I'll try to find some resources but the basic idea is that your controller itself is actually a service so you are already using service composition. When asp.net creates your controllers it resolved their dependencies recursively. So if one of the many services in the code above requires another service, simply make it a Constructor parameter. I would write an example an but since I don't know which of your services are involved in the `// Lots of business logic` chunk there's not much more to say. – Aluan Haddad Jan 10 '21 at 16:03
  • Thanks. I removed the business logic where that comment was because it is really lengthy. It reports on key values and status of the entire application, so this controller basically works with nearly every model / object in the entire application. The dashboard view makes calls with this controller - and it works as we want. But now we want the application to also store / track these values to the databases at key milestones in the app - and the dashboard view may not be in use so I need nearly every other controller to now do the same logic - causing major overlap in functionality. – Mark1270287 Jan 10 '21 at 16:46
  • Roughly `interface IDashboardService { Task BuildViewModelAsync(int id = 0); }` and implement it like `class DashboardService: IDashboardService { public async Task BuildViewModelAsync(int id = 0) { business logic } public DashboardService(deps) {} }` where deps is whichever of the current `DashboardController` depenencies that are needed by the business logic extracted. Then register it in the same way as your existing services are – Aluan Haddad Jan 10 '21 at 16:56
  • Thanks Aluan - I will see if I can implement this approach. I was pursuing an alternate solution in the mean time that I just posted as an aswer for feedback, although I do not have it working yet either. – Mark1270287 Jan 10 '21 at 17:31

1 Answers1

0

I may be able to make a working solution, if not the most elegant, if I can call the dashboard controller from the various other controllers. Following this answer: How to call another controller Action From a controller in Mvc

Added to my dashboardController:

public async void calculateMilestoneValues(int coLabID = 0)
{
    //Used to calcualte and stored milestone values from other controllers.
    var model = await buildDashboardViewModelAsync(coLabID);
    return;
}

Edit: I was finally able to get a working solution after figuring out that the project was already using Castle Windsor. I still needed to setup some additional components for it to resolve dependancies, but was then able to call the needed fuctionality in the DashboardController from other controllers with:

//Call dashboard controller so it can save milestone data to db if needed
var dashboardController = ServiceLocator.GetContainer().Resolve<DashboardController>();
DashboardController.CalculateMilestoneValues(viewModel.CoLabId);

So this doesn't really abstract the functionality from the controller, but it did provide a way for me to call it without needing to duplicate it into the other controllers.

Mark1270287
  • 411
  • 1
  • 4
  • 14