0

I'm building a MVC 5 app that deals with logging time for employees among other things. I already have a complete CRUD controller with views for dealing with employee records and I have a Timesheet controller for entering time for employees.

I would like to add shortcuts to the Timesheets/Index view to get access to the Employees module without going through the Admin/Index view. I would also like to directly access the Employee Edit methods from a subordinate view to Timesheets/Index.

In the spirit of DRY, can I reuse the Employee controller logic and still get back to where I came from or do I need to duplicate the Employee logic to call different views with different action links? (I know I can reuse part of the view code by using partial templates but that doesn't go far enough.)

OK from the responses I did not do a very good job of explaining what I want to do. Here is part of the Employees controller code:

public partial class EmployeesController : Controller
{
    //
    // GET: /Employees/

    public virtual ActionResult Index(int? page)
    {
        const int pageSize = 15;

        var masterDataProxy = MasterDataChannelFactory.OpenChannel();
        var employees = masterDataProxy.GetPagedEmployees((page ?? 0) * pageSize, pageSize);
        masterDataProxy.CloseChannel();

        ViewBag.HasPrevious = employees.HasPrevious;
        ViewBag.HasMore = employees.HasNext;
        ViewBag.CurrentPage = (page ?? 0);

        return View(employees.Entities);
    }

    //
    // GET: Employees/Edit/{id}

    //[Authorize(Roles = "Admin")]
    public virtual ActionResult Edit(int id)
    {
        var masterDataProxy = MasterDataChannelFactory.OpenChannel();
        var employee = masterDataProxy.GetEmployee(id);
        masterDataProxy.CloseChannel();

        return View(employee);
    }

    //
    // POST: Employees/Edit/{id}

    [AcceptVerbs(HttpVerbs.Post), /*Authorize(Roles = "Admin")*/]
    public virtual ActionResult Edit(int id, FormCollection formValues)
    {
        var masterDataProxy = MasterDataChannelFactory.OpenChannel();
        var employee = masterDataProxy.GetEmployee(id);
        masterDataProxy.CloseChannel();

        if (null == employee)
        {
            return View(Views.NotFound);
        }

        try
        {
            UpdateModel(employee, formValues.ToValueProvider());

            var adminProxy = AdminChannelFactory.OpenChannel();
            adminProxy.AddUpdateEmployee(employee);
            adminProxy.CloseChannel();

            return RedirectToAction(Actions.Index());
        }
        catch (Exception ex)
        {
            ModelState.AddModelError("Employee", ex.Message);

            return View(employee);
        }
    }
...
}

Here is part of the Admin index page view:

@{
    ViewBag.Title = "Master Data Admin";
}

<h2>Master Data</h2>

<ul>
    <li>@Html.ActionLink("Accounts", MVC.Account.Actions.Index())</li>
    <li>@Html.ActionLink("Employees", MVC.Employees.Actions.Index())</li>
</ul>

And then I have a Timesheet/Index view where I want to add another ActionLink to Employees. My question is simply how do I write this so I can call into the Employees Controller from either view (Admin/Index or Timesheets/Index), update the Employees on the service, then return back to where I was called from?

It seems this should be a solved problem but I couldn't find anything close to what I want to do. Maybe I need to rephrase the question? I shoudl add I'm a relative newbie to MVC and web programming in general...

Thanks in advance for any help or guidance.

Dave

3
  • Some basic code would really help here. Pseudo-code if need be. Commented Nov 25, 2013 at 21:03
  • Post the Employee Controller logic, maybe the logic should be in a common class? The controllers shouldn't have too much code. Commented Nov 25, 2013 at 21:07
  • Possibly pull the logic out of your controllers and put it into some sort of service class. Then you can call that logic from any controller which needs it and the controller can present it in whatever way makes sense. Commented Nov 25, 2013 at 21:08

3 Answers 3

2

If I understand correctly you want to use the Employee action methods without being tied to Employee views.

I would create a service layer in your application (maybe in a separate project) that deals with Employee logic. Then from your Employee controller and your Timesheets controller you call this EmployeeService service.

This way your Business Logic (EmployeeService) is separated from your Presentation logic (Employee and Timesheets controllers and related views). So your controllers can use logic stored in EmployeeService and still utilize their own views for presentation.

This in fact becomes a 2-tiered design. If your application is complex enough you might even separate your data access logic into separate layer - effectively making your application 3-tiered.

A sample structure might be as follows.

    Presentation Layer Project (PL)
        Controllers/
            EmployeeController
            TimesheetsController
        Views/
            Employee/
            Timesheets/
        ...

    Business Logic Layer Project (BLL)
        ViewModels/
        Services/
        ...

    Data Access Layer Project (DAL)
        Models/
        Repositories/
        ...

(Here PL would reference BLL and BLL would reference DAL project).

This might give more information: https://softwareengineering.stackexchange.com/questions/135724/separating-data-access-in-asp-net-mvc

Sign up to request clarification or add additional context in comments.

8 Comments

Exactly how I do it. Controllers are just a thin glue between business logic and UI. At first it seems natural to put business logic in the controllers, but when you start down this path of trying to share business logic, you end up refactoring it out into another layer. Eventually this becomes so common that you just automatically begin every new controller implementation by creating and/or finding reusable service class to handle that backend stuff.
This is essentially how I do it, but I'm not sure how that answers my question. With this approach I will still need to have separate views for access from different places? Really all I need to figure out is how to get the "return" links to always get me back to where I came from.
If all you want is for your links to get you back to the calling page you'd set that in Shared\_Layout view from ViewBag variable, for instance, that tracks where you are. Use that to build your links. But ...then return back to where I was called from - your controller would not do that for you.
I realize that - thus the question "how can I use the controller logic from multiple places"... Using the ViewBag variables may be the ticket. I probably should have clarified I'm a relative MVC/web newbie, although I've been writing server-client code for more years than I care to count...
I think you are confusing your logic with presentation. In regular MVC your logic is 'glued' with your presentation via a controller. But if you separate your logic into a service layer then it becomes very easy to solve the problem. You'd only need to worry about presentation part. Because you talking about links AND returning back to where you called the action.
|
0

All this stuff is great, but I think what I am asking is so simple it's just assumed I already knew it. I can definitely see the advantages of repositories and business layers and will "do the right thing" and split data access out of the controller. But, that doesn't answer my question.

What I need to do is take the meat of the edit screen and create a partial view. Then I need to create a separate view for each controller call that includes the correct link to get me back to where I started.

View called from Admin/Employee/Edit:

@model HawkTimeModel.Employee

@{
    ViewBag.Title = "Edit Employee";
}

<h2>Edit</h2>

@{ Html.RenderPartial(MVC.Employees.Views._EmployeeForm); }

<p>
    @Html.ActionLink("Back to List", MVC.Employees.Actions.Index())
</p>

View from Timesheet/EditEmployee:

@model HawkTimeModel.Employee

@{
    ViewBag.Title = "Edit Employee";
}

<h2>Edit</h2>

@{ Html.RenderPartial(MVC.Employees.Views._EmployeeForm); }

<p>
    @Html.ActionLink("Back to Timesheet Overview", MVC.Timesheets.Actions.Index())
</p>

That's it, the "missing link". Looking at it now, I don't understand how I overlooked something so simple.

Thanks for the input though, my app will be much better as a result of your feedback!

Dave

Comments

0

My colleague proposed another solution. Modify the Model so it has a variable defining where the view was called from and use that to determine what the "return link" at the bottom of the page takes you. Then the main index view looks like this:

@using WebReporter.Models

@{ Html.RenderPartial(MVC.Employees.Views._IndexPartial); }

@if (Model.HomePage == HomePage.Timesheets)
{
    <p>
        @Html.ActionLink("Back to Timesheet Overview", MVC.TimeSheets.Index())
    </p>
}

... and is called like this:

    public virtual ActionResult Index(HomePage homePage, int? page)
    {
        return View(_masterDataService.GetPagedEmployees(homePage, page));
    }

I had originally built a second copy of the Employee views and controller but it seemed to violate the DRY principle so I'm trying this.

I can't believe this is so difficult to explain. I can't be the only person who ever wanted to reuse an entire Controller + Views from multiple places, so I must be bad at explaining it...

Thanks for the advice though, my code will be better for your input!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.