1

I am building an asp mvc 5 support ticket helpdesk application. The application will require users to signup to submit a support ticket. Users actions would be to update the ticket by adding notes like a thread.

Agents will manage the ticket and have additional permissions to edit the ticket and create internal as well as public notes and be able to view all tickets.

There is a site admin who can configure the site.

The issue is if I have a controller say called ticket controller, the main actions on the create or edit ticket are similar for both an agent and a user. The differences are that when a user creates a ticket they can only complete certain fields on the form where as an agent can update additional fields.

So what I'm ending up with is a lot of if then else statements in my controllers and views to check user roles inorder to restrict what form fields and actions are displayed to the different types of users. Also my viewmodels are exposing properties to a customer that they shouldn't have permissions to.

So my question is how do you organize this type of set up. Do you create a separate area for Agents and duplicate all the controllers and viewmodels to cater for this type of user.

Or would you just bloat the controllers and views with if then else statements for every different type of user role that is created?

Some advise on what would be best practice for this particular application would be appreciated.

The only benefit of creating an Area for agents is that I could drastically change the look and feel of the Agent portal, have a different url eg. mysite/agentdesk/tickets and it would be easier to maintain but at the expense of duplicating code.

/* Current Controller Logic and view model */ As you can see the in viewmodel exposes the status and priority properties which only an agent can set.

In the create method of the controller, you can see the teneray operator being used to determing if the user has the correct role to set the above properties.

public class TicketViewModel
{

      public int Id { get; set; }

      [Required]
      public string Subject { get; set; }

      [Required]
      public string Description { get; set; }

      // Only an agent can change the status
      [Required]
      public int Status { get; set; }

      // Only an agent can change the priority
      [Required]
      public int Priority { get; set; }


      public IEnumerable<StatusType> StatusTypes { get; set; }
      public IEnumerable<PriorityLevel> PriorityLevels { get; set; }

}

public class TicketController : Controller
{

    [Authorize]
    public ActionResult Create()
    {
        var viewModel = new TicketViewModel
        {
            StatusTypes = _context.StatusTypes.ToList(),
            PriorityLevels = _context.PriorityLevels.ToList()

        };

        return View(viewModel);
    }


    [Authorize]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(TicketViewModel viewModel)
    {
        if (!ModelState.IsValid)
        {
            viewModel.StatusTypes = _context.StatusTypes.ToList(),
            viewModel.PriorityLevels = _context.PriorityLevels.ToList()
            return View(viewModel);
        }

        bool agentRole = HttpContext.Current.User.IsInRole("Agent");

        var ticket = new Ticket
        {
            CreatedBy = User.Identity.GetUserId(),
            Subject = viewModel.Subject,
            Description = viewModel.Description,
            StatusId = agentRole ? viewModel.Status : 1,
            PriorityId = agentRole ? viewModel.Priority : 1
        };

        _context.Tickets.Add(ticket);
        _context.SaveChanges();

        return RedirectToAction("Index", "Tickets");
    }
}
adam78
  • 9,668
  • 24
  • 96
  • 207
  • I would probably introduce the Agent Area, with all controllers decorated with [Role("Agent")] etc.. With regards to duplicated views, if you introduce common shared fields as a partial view, then that would cut this down. With regards to your controllers, they should hold as little business logic as possible, so create a business class named TicketCreator for instance which contains two methods AsAgent and AsUser.. you can then call each one from your different controllers but share as much of the logic within the class as you like – uk2k05 Jun 24 '16 at 09:17
  • Just to add, your business class methods would also return two different types of ViewModel, so that you don't send sensitive data to the user – uk2k05 Jun 24 '16 at 09:20
  • @uk2k05 can you show an example of your business class named TicketCreator solution. I'm trying to figure how I can can factor the viewmodel to accomodate both a customer and agent. – adam78 Jun 26 '16 at 15:49

1 Answers1

0

I think an area would be the best way to go. The simple fact is that the complexity of your code is going to quickly spiral out of control otherwise. It's one thing to just show/hide form fields based on some role, but really, for security, this needs to be protected end-to-end. In other words, you would also need to check inside your action that properties are only being set with values if the user is allowed to access those properties. It's trivial to post things you shouldn't be able to edit, even if no form fields exist to allow that.

To get around the code-duplication issue, just don't duplicate code. That sounds a little trite, but there it is. Similar code in your action can be factored out into a base controller or just some helper class. Similar code in your views can be factored out into partials. Basically, anything that would be the same for both situations should go somewhere where the same code can be used in both situations.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • 1
    if I was to set up an Area specific for Agents I would still end up with almost identical controller methods and view model - see my example above of how the current logic is. How would you refactor the viewmodel and controller methods. An example would be helpful? – adam78 Jun 26 '16 at 15:44
  • Again, if the controller actions are identical, you can put them on a base controller and inherit from that in your area. You don't have to redefine the actions. If they aren't identical, you can factor out the parts that are into helper methods on that base controller, or you can create a helper class that the area's controller can use. For the view model, define just the properties that both share on it, then inherit another class, specific to the agent, from that, where you can define just the properties relevant to agents. – Chris Pratt Jun 27 '16 at 12:45
  • You can even employ generics to sub in the right view model, entities, etc. Basically, you just need to employ the tools that C# already gives you for code-reuse. There's nothing special about a controller/view model. They're just classes like any other, and you can do anything with them you could do with any class in C#. – Chris Pratt Jun 27 '16 at 12:47
  • with regards to views, how can you share partial views from the main shared views directory with the Agent area? If I have a partial view that uses a base view model in my main shared views directory and in my Agent area I want to reuse that same partial but using an agent view model which is inherits from the base view model. Basically in my partial I am using `@model Myapp.ViewModels.TicketViewModel` . When needing to use the same partial in the agent area I need to be using `@model Myapp.AgentArea.ViewModels.AgentTicketViewModel` – adam78 Jul 17 '16 at 17:30
  • see my new post for example of what I mean: http://stackoverflow.com/questions/38424224/asp-mvc-5-reusing-partial-views-in-different-areas – adam78 Jul 17 '16 at 19:10