0

THIS IS MY MODEL:

public class Employee
{
    public string EmployeeName { get; set; }
    public string Address { get; set; }
    public DateTime DateOfBirth { get; set; }
    public int Salary { get; set; }
}

THIS IS MY VIEW:

<div>
    Employee Detail<br />
    Employee Name : @Model.emp.EmployeeName<br />
    Address : @Model.emp.Address <br />
    Age : @Model.Age
    <span style="color:@Model.SalaryColor">Salary : @Model.emp.Salary</span>
     @if (Model.Salary > 20000)
     {
         <div1>..</div1>
     }
     else
     {
        <div2>..</div2>
     }
</div>

With this if/else, i am inserting business logic in the UI Layer/ View of MVC. But i am clueless how can this be avoided?

I know that this would make UI tightly bound with Business logic which is not good. Any way i can avoid this?

tereško
  • 58,060
  • 25
  • 98
  • 150
Sahil Sharma
  • 3,847
  • 6
  • 48
  • 98
  • In short... As long as it's purely to do with presentation, flow control in a view is fine. – Basic Nov 23 '15 at 10:53
  • Angualr or Knockout are good for this type of thing, you can bind Visible property, so you can show or hide divs bound to a property – Mark Homer Nov 23 '15 at 10:59

2 Answers2

3

The problem isn't the if. Sometimes you have to present things differently based on some aspect of the model. The "problem" is the Salary > 20000 "logic". While the example you've given is probably simple enough that it's not terribly important, ideally that kind of thing doesn't really belong in the view.

The standard way to do this is to make use of a ViewModel. This object exists to decouple the view from the implementation details of your data model, and to encapsulate some presentation-layer logic. This is particularly important if your data model is e.g. an Entity Framework proxy which has a lot of constraints and behaviour that force it into the wrong "shape" for your presentation layer. In this particular instance, your ViewModel could have a property along the lines of bool ShowHighSalarySection - either calculated by the ViewModel, or by some external business code and stored in the ViewModel.

The precise shape of your ViewModel will depend greatly on what particular flavour of MV* you follow, but for example:-

  1. Simple ViewModel with data-model constructor:-
    public class EmployeeViewModel
    {
      private readonly Employee model;

      public EmployeeViewModel(Employee model)
      {
        this.model = model;
      }

      public bool ShowHighSalarySection { get { return this.model.Salary > 20000; } }

      // Some people like to have the ViewModel explicitly only expose
      // the data the view needs.
      public string EmployeeName { get { return this.model.EmployeeName; } }
      public string Address { get { return this.model.Address; } }
      public DateTime DateOfBirth { get { return this.model.DateOfBirth } }
      public int Salary { get { return this.model.Salary; } }

      // Or, if boilerplate freaks you out, and you don't have the tools
      // to automate it, you can even just expose the data model and its
      // properties directly as e.g. Model.Employee.EmployeeName.
      // This gives you some abstraction very cheaply, but obviously
      // still leaves your views somewhat coupled to your data model:-
      public Employee Employee { get { return this.Model; } }
    }

And then in your view:-

if (Model.ShowHighSalarySection) { ...
  1. You can also use a more "dumb" ViewModel along with a mapper such as AutoMapper:-
    public class EmployeeViewModel
    {
      // Note that this ViewModel doesn't even know the data model *exists*!
      public string EmployeeName { get; set; }
      public string Address { get; set; }
      public DateTime DateOfBirth { get; set; }
      public int Salary { get; set; }
      public bool ShowHighSalarySection { get; set; }
    }

In your controller:-

    var query = db.Employees.Select(x => ...);

    // Some people like the "magic" of a mapper, especially when
    // all you're doing is reasonably "dumb" transformations.
    // Some people don't like that there could be somewhat complicated
    // things going on behind the curtain.
    // The huge advantage of this type of code is that you can usually
    // leverage some kind of query projection and have the mapping
    // take place on your database, rather than pulling the entire
    // entity graph into memory.
    var viewModel = this.mapper.Project()
                               .To<EmployeeViewModel>(query)
                               .FirstOrDefault();

    return View(viewModel);
  1. Sometimes business rules are complicated enough you might want some explicit handling code explicitly responsible for applying the business rule:-
    public class GetEmployeeQueryHandler
    {
      private readonly IEmployeeStore store;

      public GetEmployeeQueryHandler(IEmployeeStore store)
      {
        this.store = store;
      }

      public EmployeeViewModel Handle(EmployeeQuery query)
      {
        var employee = store.Get(EmployeeQuery.Identity);
        var viewModel = new EmployeeViewModel()
        {
          // Could leverage some automatic mapping to do the
          // "dumb" part of the mapping if you want tidiness
          // or query projection.
        }

        // This is probably excessive for a calculation as simple
        // as this one, but we've managed to separate ourselves
        // totally from both the database *and* the rendering
        // engine.
        // This lets us e.g. test our important business logic in
        // complete isolation, or reuse it elsewhere.
        viewModel.ShowHighSalarySection = employee.Salary > store.HighSalaryThreshold;
      }
    }

Exactly what goes where will depend on your architecture, your philosophy, and how much boilerplate (or tooling) you're prepared to accept in return for tidiness. The main thing a ViewModel does is it allows you to get business rules out of the view and into somewhere appropriate (safer, more testable, more reusable, and so on).

Iain Galloway
  • 18,669
  • 6
  • 52
  • 73
1

If content of div1 and div2 are more or less similar, the best thing would be to drop if/else altogether, perhaps by have a bit more fields in the model.

However if the two are entirely different, you can have if/else, this is acceptable. But beware of what is and what is not business logic. Consider:

public class Employee
{
    public int Salary { get; set; }
    private const int SALARY_CAP = 20000;
    public bool IsSalaryAboveTheCap { get { return Salary > SALARY_CAP; } }
}

And in the view:

 @if (Model.IsSalaryAboveTheCap)
 {
     <div1>..</div1>
 }
 else
 {
    <div2>..</div2>
 }

The fact that the salary is above the some cap is not business logic per se. The value of the cap however is.

Arguably though view model is not the best place to put this logic in either. Do you have any business logic layer where this could come from?

Andrei
  • 55,890
  • 9
  • 87
  • 108