stackface
stackface

Reputation: 25

MVC Two controllers One View

I am trying to have a details view in an existing controller that joins two controllers and passes into the view, but everything i have tried so far has not worked. I have 4 data models and what i would like is to use 2 of them Company and Staff. So when i select details on a specific Company it will return all Staff associated to that Company in the same view.

HRDataModel class

  public partial class HRDataModel : DbContext
{
    public HRDataModel()
        : base("name=HRDataModel")
    {
    }
    public virtual DbSet<Company> Companies{ get; set; }
    public virtual DbSet<Attribs> Attribs{ get; set; }
    public virtual DbSet<Staff> Staffs { get; set; }
    ....

Company Data Model

[Table("Company")]
public partial class Company
{
    public Company()
    {
        Staffs = new HashSet<Staff>();
    }
    public virtual ICollection<Staff> Staffs { get; set; }
    public int companyId{ get; set; }

    [Required]
    [StringLength(10)]
    public string companyName{ get; set; }
    .....

Staff Data Model

public partial class Staff
{
    public Staff()
    {
        Skills = new HashSet<Skill>();
    }
    public virtual Company Company{ get; set; }

    public virtual ICollection<Skill> Skills { get; set; }

    public int staffId { get; set; }
    .........

And i am trying to get my Details method in CompanyController to show details of all active Companies in the db and also all Staff attached to that Company

[Route("Company/Staff/1}")]
public ActionResult Details(int id)
{
    Company co = db.Companies.Find(id); 
    ...How to implement????           
    return View(bu);
}

If anyone can point me in the right direction that would be great. I have tried and tried but cannot get anything to work?

Upvotes: 1

Views: 10053

Answers (4)

Georg Patscheider
Georg Patscheider

Reputation: 9463

You can also compose a view by calling controller actions that return partial views.

The advantage is that you can reuse the partial views and their actions, e.g. to show the company details with the same layout on different pages. This increases consistency and maintainability.

The drawback is that you loose flexibility: if a certain page requires a different layout, you should create a new view. Performance might also be lower because you hit the backend with many small operations instead of a single big one.

The combined viewmodel to show both company and staff details only needs to know how to access the required data:

public class CompanyAndStaffDetailsViewModel {
    public long CompanyId { get; set; }
    public long StaffId { get; set; }
}

The following action renders the combined view:

public ActionResult Details(long companyId, long staffId) {
    var viewModel = new CompanyAndStaffDetailsViewModel {
        CompanyId = companyId,
        StaffId = staffId
    };
    return View("Details", viewModel);
}

The "Details" View composes the usecase by calling actions to render partial views:

@model CompanyAndStaffDetailsViewModel 
@Html.Action("CompanyInfoPartial", "Company", new { companyId = Model.CompanyId })
@Html.Action("StaffInfoPartial", "Staff", new { staffId = Model.StaffId })

The "Company" controller provides a reusable action to render company details:

 public ActionResult CompanyInfoPartial(long companyId) {
       Company co = db.Companies.Find(companyId);
       var model = Mapper.Map<CompanyViewModel>(co); // map persistable entity to ViewModel
       return PartialView("_CompanyInfoPartial", model);
 }

Then the parital View _CompanyInfoParital.cshtml only has to deal with the Company Info stored in the CompanyViewModel and knows nothing about Staff:

@model CompanyViewModel
@Html.DisplayFor(m => m.CompanyName)
// etc ...

The idea for the StaffInfoPartial action is the same as for CompanyInfoPartial.

Upvotes: 1

user5524649
user5524649

Reputation:

Hi @stackface you dont pass two controllers to get both views for that what you do is create one View Model which is essentially a container for multiple models and pass that into the view from the controller.

E.g. Model 1, Model2, ModelN all are needed so you have a class and in that class it has properties consisting of Model1, Model2 and Model3 so that way you pass in your class which has all the needed models.

E.g.

public class Company{...}

public class Staff{...}

public class ViewModel{
public Company Company {get;set;}
public List<Staff> Staff{get;set;}
}

controller:
{
var viewModel = new ViewModel();
viewModel.Company = db.Companies.FirstOrDefault(x => x.id == companyId);
viewModel.Staff = db.Staff.Where(x => x.CompanyId == campanyId).ToList() //or whatever your properties are called.

Return View(viewModel);
}

Update your view to take type ViewModel.

Upvotes: 1

Shyju
Shyju

Reputation: 218702

You need to pass a data structure which has the company info and staff details to your view. You may pass your existing Comapny entity class to do this. But the problem is, It makes your razor view tightly coupled to your Entity which was generated by the ORM. What if you switch your Data access layer to something else tomorrow. So this solution is not great.

So you should use a view model ( A simple POCO class) which has properties which you need to render in the view. Then read your entity from db in your action method, map it to a vie wmodel instance and send it.

Create a view model like this.

public class CompanyInfo
{
  public int Id {set;get;}
  public string Name {set;get;}
  public List<StaffItem> Staffs {set;get;}
  public CompanyInfo()
  {
     this.Staffs = new List<StaffItem>();
  }
}
public class StaffItem
{
  public int Id {set;get;}
  public string Name {set;get;}
}

In your action method read the Company entity and map it to the view model

public ActionResult Details(int id)
{
    var vm = new ComapnyInfo();
    var company = db.Companies
             .Include(r => c.Staffs)
             .FirstOrDefault(x => x.companyId==id); 

    if(co!=null)
    {
       //Map the property values now
       vm.Name = co.companyName;
       vm.Id = co.companyId;
       if(co.Staffs!=null)
       {
         vm.Staffs = co.Staffs.Select(f=> new StaffItem {
                              Id=f.staffId,
                              Name = f.staffName}).ToList();
       }
    }       
    return View(vm);
}

Now your view should be bound to the CompanyInfo view model

@model YourNamespace.CompanyInfo
<h2>@Model.Name</h2>
<h3>Staffs</h3>
@foreach(var staff in ModelStaffs)
{
  <p>@staff.Name</p>
}

If you do not like the manual mapping, You may consider using a mapping libarary like Automapper.

Upvotes: 1

Danny
Danny

Reputation: 624

Since Company includes Staff you can use the include method to include related entities.

var company = db.Companies.Include(c => c.Staffs).FirstOrDefault(x => x.id == companyId);
return View(company);

And in your view:

@foreach(var staff in Model.Staffs) { ... }

Upvotes: 1

Related Questions