0

My _Layout uses the BaseViewModel to render the user's name in the navbar which i want to keep consistent through the application. My HomeController has an action on it called Login that passes a UserViewModel to the Dashboard view upon successful login. UserViewModel derives form BaseViewModel and is only used in the Dashboard view right now.

My question is how do I make this BaseViewModel which will be used by the _Layout page be available throughout the views of the application. Do I have to keep making a call to my service (Database) to fetch this data each time a page loads? because the data that the BaseViewModel needs is only fetched in the Login action of the HomeController so the page breaks if i navigate to another view, and I get this error below

The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[InventoryManager.Web.Models.ProductViewModel]', but this dictionary requires a model item of type 'InventoryManager.Web.Models.BaseViewModel'.

BaseViewModel.cs

public class BaseViewModel
{
    public string FirstName { get; set; }

    public string LastName { get; set; }
}

UserViewModel

public class UserViewModel : BaseViewModel
{
    public Guid UserId { get; set; }

    public string Username { get; set; }

    public string Password { get; set; }
}

BaseController

public class BaseController : Controller
{
    //
    // GET: /Base/

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        var model = filterContext.Controller.ViewData.Model as BaseViewModel;
    }
}

HomeController

    public ActionResult Login(LoginViewModel model)
    {
        if (!ModelState.IsValid)
        {
            return ReturnLoginViewOnError(CustomErrorMessages.LOGIN_CREDENTIALS_NOT_PROVIDED);
        }

        var userService = new UserServiceClient();
        var user = userService.GetUser(model.Username, model.Password);

        if (null == user)
        {
            return ReturnLoginViewOnError(CustomErrorMessages.INVALID_USER);
        }

        var userViewModel = Mapper.Map<UserContract, UserViewModel>(user);

        return RedirectToAction("Index", "Dashboard", userViewModel);
    }
floormind
  • 1,868
  • 5
  • 31
  • 85
  • Is your Dashboard view using `model IList`? – kayess Nov 28 '15 at 13:46
  • no my Dashboard is using UserViewModel. My products view uses List . but i still want that BaseViewModel to exist in the product view because I want to display the user's first and last name in the navbar @kayess – floormind Nov 28 '15 at 14:21
  • I (and others) have already [answered](http://stackoverflow.com/questions/33702441/how-to-pass-the-model-from-the-razor-page-to-its-master-layout-page/33707755#33707755) a similar question. Take a look at it, could be appropriate in your solution. – kayess Nov 28 '15 at 14:44
  • I have added a general view model , that you can use for every view, see AppViewModel class – Julius Depulla Nov 28 '15 at 16:38
  • I am not sure weather it is good practice or not once user logged in add base model to session and you can retrieve it when required – Venkata Krishna Reddy Nov 28 '15 at 17:03

2 Answers2

1

You can keep your UserViewModel and BaseViewModel and use composition to send a compatible type to your View and avoid the error as show below. This approach uses what is referred to as Object Composition

See below, Create AppViewModel class

public class AppViewModel
{
  public UserViewModel UserViewModel { get; set; }
  public List<ProductViewModel> ProductViewModel { get; set; }
}

// Login Action Method or any action method

Populate AppViewModel to send to the view

public class HomeController {
    //Action method
      public ActionResult Login(LoginViewModel model)
      {
        //Do stuff and populate AppViewModel

         UserViewModel userViewModel = new UserViewModel {Username = "username", Password ="secret", FirstName = "John", LastName = "Doe"};
         AppViewModelmodel model = new AppViewModel{UserViewModel = userViewModel };
         return RedirectToAction("Index", "Dashboard", model);
      }
}

// ProductController

public class ProductController
{
  public ActionResult Products()
  {
    ProductViewModel productViewModel = new ProductViewModel { /*Initialize properties here*/};
    AppViewModel model = AppViewModel { ProductViewModel  = new List<ProductViewModel>{ new ProductViewModel =  productViewModel }};
    return View(model);
  }
}

// Dashboard View

// Do include your model namespace
@model AppViewModel 
<div>
   <p> FirstName : @Model.UserViewModel.FirstName</p>
</div>

// Products View

// Do include your model namespace
@model AppViewModel 
<div>
   //You get the idea
   <p> Product Name: @Model.ProductViewModel.Select( x => x.ProductName).       </p>
</div>
Julius Depulla
  • 1,493
  • 1
  • 12
  • 27
  • I have modified the code to use AppViewModel.cs. You will be able to use one view model with this design but will require you change your design to use one view model across your views that need it. – Julius Depulla Nov 28 '15 at 17:15
1

What I usally do, is that I create an LayoutController. With this controller, I render all the persistent information which is used on the layout pages.

public class LayoutController : Controller
{
    private readonly IProvideData _provider;

    public LayoutController(IProvideData provider)
    {
        _provider = provider;
    }

    [ChildActionOnly]
    public ActionResult AccountInformation()
    {
        var model = _provider.GetUserStuff();
        return PartialView("_AccountInformation", model);
    }
}

The ChildActionOnly attribute ensures that an action method can be called only as a child method from within a view. On my _Layout.cshtml I can render this action with:

@{ Html.RenderAction("AccountInformation", "Layout"); }

Which renders the _AccountInformation partial view which can look like:

@model MyApplication.ViewModels.UserInformation
Username: @Model.UserName
janhartmann
  • 14,713
  • 15
  • 82
  • 138