Asp.Net MVC layout and partial views - c#

let's consider two views that use the same layout composed of:
A left column containing a "body" (which is filled differently by both views)
A right column that displays general information (passed via the model)
Instead of defining the right part twice, I wondered if I could create a PartialView to link directly from the layout page.
The problem is that the partial views implicitely inherit their models from the view that is being rendered. And since each view has its own model, I end up with a model type mismatch in the partial view.
From here I see two solutions:
I could insert the common part of the view model in the ViewBag. Unfortunately this means that each view that uses this layout has to implement this "convention" but nothing warns the developer about it at compile time...
I could use polymorphism to make each view model inherit from the same base class (edit: or interface) that the Partial Views uses. This would work up to a certain extend but would potentially exponentially increase in complexity as soon as I have a second partial view in the same layout.
So here are the questions:
Am I right with the assumptions above?
Do you see any other possibility?
Any return on experience on this?
Thanks a lot,
TB.

Use an Interface and implement it on the two models, this is exactly the kind of thing they're used for.
Here is an example of two different Views using two different Models that both implement an interface. This is subtyping instead of ad-hoc polymorphism.
public class ViewModelOne : IReusableView
{
public string Name { get; set; }
public string Something { get; set; }
public int ANumber { get; set; }
}
public class ViewModelTwo : IReusableView
{
public string Name { get; set; }
public string Thing { get; set; }
public string SomethingElse { get; set; }
public int ANumber2 { get; set; }
}
public interface IReusableView
{
string Name { get; }
}
So we have the really simple partial view here that is 'InnerPartialView':
#model TestIntegration.Models.IReusableView
<div>
#Model.Name
</div>
Which is used in the home and about pages of this example controller:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View(new ViewModelOne() { Name = "hello", Something="sdfsdfs", ANumber = 1 });
}
public ActionResult About()
{
return View(new ViewModelTwo() { Name = "hello 2", SomethingElse = "aaaddd", ANumber2 = 10, Thing="rand" });
}
}
The home view:
#model TestIntegration.Models.ViewModelOne
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit http://asp.net/mvc.
#Html.Partial("InnerPartialView")
</p>
The about view:
#model TestIntegration.Models.ViewModelTwo
#{
ViewBag.Title = "About Us";
}
<h2>About</h2>
<p>
Put content here.
#Html.Partial("InnerPartialView")
</p>

When you render the partial view, you can send it a model:
#Html.RenderPartial(MVC.Partials.Views.Sidebar, Model.SideBarModel);
So you could send down data as part of the parent model that is the model for the partial sidebar.

In partial views, models are of type dynamic so you don't need to know what type they are. However, you just need to make sure the model has the property you need. In other words you can use Model.MyProperty or Model.MyProperty as MyPropertyType when using Html.Partial.

Related

How can I access custom properties of a view (or partial view)?

In ASP .NET WebForms, it was possible to create properties and methods in WebControl pages that were accessible from the host page. Is this possible in MVC?
I'd like to add custom properties to a partial view, render it on the main view and then have the main view access those properties. What are my options?
This problem can have many solutions. F.e. you can use complex main view model, that have a partial view model property:
public struct PartialViewModel
{
public string Foo { get; set; }
public int Bar { get ; set; }
}
. . .
public struct MainViewModel
{
public double Baz { get; set; }
public PartialViewModel Qux { get; set; }
}
Then you can build MainViewModel in an action's code:
public ActionResult SomeAction(int id)
{
var model = ModelRepostiory.GetById(id);
var viewModel = new MainViewModel
{
Baz = model.Baz,
Qux = new PartialViewModel
{
Foo = model.Partial.Foo,
Bar = model.Partial.Bar,
},
};
return View(viewModel);
}
MainView.cshtml
#model MainViewModel
. . .
<div>
<p>Additional info:</p>
#Partial("partial", Model.Qux)
</div>
PartialView.cshtml
#model PartialViewModel
<p>Foo: #Model.Foo</p>
<p>Bar: #Model.Bar</p>
I believe the ViewBag will help you in this
You can set ViewBag properties with
ViewBag.MyProperty = "this is a string";
and then retrieve them in other parts of the view.
This only makes sense in an event based system. The state of a control changes and the container needs to react to those changes.
In ASP.NET MVC the container main view
passes the data to its partials. If a partial needs to change the state of the application it posts the changes to a controller action. The controller action then supplies the changed model to the main view and the the contained partials.

ViewModel vs. InputModel in Razor View

As I found on 10 Good practices for asp.net mvc webapplications it is a good practice to split mvc models in ViewModel (Models representing the view) and InputModels (Representing data entered by user).
The ViewModel gets a property of Type InputModel. The InputModel carries the data that can be edited by the user.
public class EmployeeInputModel{
public string Name {get;set;}
public Id? DepartmentId{get;set;}
}
public class EmployeeCreateViewModel{
public IList<Department> Departments{get;set;}
public EmployeeInputModel EmployeeModel{ get;set;}
}
The Controller-methods look like:
public class EmployeeController : Controller{
public ActionResult Create(){
var viewModel = new EmployeeCreateViewModel(DepartmentService.GetAll(), new EmployeeInputModel());
return View(viewModel);
}
public ActionResult Create(EmployeeInputModel createModel){
try{
EmployeeService.Create(...);
return RedirectToAction("Index");
} catch (Exception ex){
var viewModel = new EmployeeCreateViewModel(DepartmentService.GetAll(), createModel);
return View(viewModel)
}
}
}
The View looks like:
#model EmployeeCreateViewModel
#Html.EditorFor(m => m.EmployeeModel)
The Editor Partial is just like:
#model EmployeeInputModel
#Html.TextBoxFor(m => m.Name)
#Html.DropDownFor(m => m.Department, ???)
This worked great for me, unless I came to the point of DropDownLists (in the sample see departments). Because the EditorTemplate doesnt know the ViewModel but just the InputModel.
I dont want to put the department list into the input model, because it is not the supposed place to be for this list (I would have to bind them). It has to be in the viewmodel. The properties of the input model should also not be in the ViewModel.
Does someone have any idea how to achieve a separation of viewmodel and input model in one view?
You would have to create a model abstraction of your departments. Something like this:
public class EmployeeInputModel{
public string Name {get;set;}
public List<DepartmentInputModel> Departments{get;set;}
}
public class DepartmentInputModel{
public int Id;
public string Name;
}
Then you can display just the name of the department in the drop down list. The value will then be the id of the department.
You can have a look at this SO question for a example.
You can use the ViewBag yo pass the list of departments to your partial.
Or use a partial view that accepts the view model as its model

How to combine viewmodels?

I have two Viewmodels, a view and two partial views and a controller method for submit.
Lets name them, BigViewModel, SmallViewModel, Page.cshtml, _Options.cshtml, _Submit.cshtml and Submit(BigViewModel) method in controller.
SmallViewModel is included in BigViewModel as one of the properties.
like,
public class BigViewModel
{
public bool Confirm { get; set; }
public SmallViewModel s { get; set; }
}
Now, Page.cshtml has a division where _Options.cshtml resides where user can input few options like sorting, searching etc. This _Options.cshtml uses a viewmodel called SmallViewModel. When you click on Submit button which is on _Options.cshtml partial view, it opens a dialog box that is _Submit.cshtml which asks for confirmation and submit button. This confirmation information is stored in BigViewModel. Now, when I hot submit it goes to Submit(BigViewModel) method in the controller.
My problem is how to include SmallViewModel data into BigViewModel from _Submit.cshtml partial view page. Because, when I go to the Submit(BigViewModel) method, I only see Confirm - True/False. But smallViewModel is null.
Hope, question is clear enough. I didn't know how else to ask. Please help.
You can simply create an interface that all models with options will derive from. Here is a sample :
public class BigViewModel : IModelOptions
{
public bool Confirm { get; set; }
public SmallViewModel SmallView { get; set; }
}
public class SmallViewModel
{
public string Stuff{ get; set; }
}
public interface IModelOptions
{
SmallViewModel SmallView { get; set; }
}
this way if you ever need more models with options you can simple do "NewModel : IModelOptions". Now your _options view would look something like
#model MvcApplication1.Models.IModelOptions
#Html.TextAreaFor(x => x.SmallView.Stuff)
Note that now your partial view does not need to be aware of what model is being passed in. So long as it derives from IModelOptions then it knows it will have a SmallView. Our _submit would be something like
Are you sure? If so I will use fancy javascript to allow you to submit!
<input type="submit"/>
and then we tie that all together in our main view (Index , Page, what have you)
#model MvcApplication1.Models.BigViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.Confirm)
{
Html.RenderPartial("_Options");
Html.RenderPartial("_Submit");
}
}
Note that here we take in a BigViewModel. That should work fine as our BigViewModel houses Options and derives from IModelOptions.
Our controller would simply be
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(BigViewModel model)
{
var smallVIewModelInfo = model.SmallView.Stuff;
var bigViewModelConfirm = model.Confirm;
return View();
}
}
and now you should be getting data from both small and big view model.

Accessing model properties in Razor-4 view

I have the following EF generated data model:
public partial class PrinterMapping
{
public string MTPrinterID { get; set; }
public string NTPrinterID { get; set; }
public string Active { get; set; }
}
I then have the following view model:
public class PrinterViewModel
{
public PrinterMapping PrinterMapping;
public Exceptions Exceptions;
public IEnumerable<PrinterMapping> Printers;
}
In my Index Action in HomeController I am passing my view model to the Index view.
private eFormsEntities db = new eFormsEntities();
public ActionResult Index()
{
PrinterViewModel printerModel = new PrinterViewModel();
printerModel.Printers = from pt in db.PrinterMapping select pt;
return View(printerModel);
}
My Index view is calling a partial view in the following manner towards the end (probably wrong):
#Html.Partial("~/Views/Home/GridView.cshtml")
My GridView.cshtml looks like:
#model AccessPrinterMapping.Models.PrinterViewModel
<h2> This is Where the Grid Will Show</h2>
#{
new WebGrid(#model.Printers, "");
}
#grid.GetHtml()
I learned about the WebGrid method from http://msdn.microsoft.com/en-us/magazine/hh288075.aspx.
My WebGrid line isn't happy at all since it doesn't recognize #model within that line.
How do I access the Printers in the view model that I passed in? Is this even possible?
Thanks very much to you all.
Theres two issues with your code.
First, you should explicitly pass your model in like this:
#Html.Partial("~/Views/Home/GridView.cshtml", Model) #* explicitly pass the model in *#
Then, because you are already in a code block in your partial view.. you don't need the # symbol.. and Model has an uppercase M.
new WebGrid(Model.Printers, "");
#model is a directive for your views/partial views. Think of it as a "configuration" command. Model is an actual property. It is the object that is passed into the view.. and is of the type you specified with the #model directive.
#{
new WebGrid(Model.Printers, "");
}
and also you have to pass your model into partial view in
#Html.Partial("~/Views/Home/GridView.cshtml")
in second parameter. I guess this call should be
#Html.Partial("~/Views/Home/GridView.cshtml", Model)

Render multiple strongly typed partial views inside a view?

I have this Index.cshtml class:
#model ProOptInteractive.ViewModels.InvoicePageViewModel
#{
ViewBag.Title = "Index";
}
<div>#Html.Partial("ListServices", Model.Services)</div>
<div>#Html.Partial("ListProducts", Model.Products)</div>
<div>#Html.Partial("Invoice", Model.InvoiceViewModel)</div>
And this is my InvoicePageViewModel:
namespace ProOptInteractive.ViewModels
{
public class InvoicePageViewModel
{
public InvoiceViewModel InvoiceViewModel { get; set; }
public IEnumerable<Service> Services { get; set; }
public IEnumerable<Product> Products { get; set; }
}
}
The issue is that whenever I load this view, I get an error saying that I have passed the wrong model to the dictionary, and it doesn't specify which one of the partial views is causing the problem. Each partial has a different model type, one IEnumerable called Product, another IEnumerable called Service, and the Invoice partial view having a viewmodel called InvoiceViewModel.
Can anybody explain how to go about making this work? I'm a bit noob at Razor and MVC by the way.
UPDATE
I get this error message:
The model item passed into the dictionary is of type 'ProOptInteractive.ViewModels.InvoiceViewModel', but this dictionary requires a model item of type 'ProOptInteractive.ViewModels.InvoicePageViewModel'.
The error is because you have set your Invoice partial view to have a model of type InvoicePageViewModel, yet you are passing it a model of type InvoiceViewModel.
Either update your InvoiceViewModel property to be of type InvoicePageViewModel, or change the Invoice view to use a model of type InvoiceViewModel.
Error is in line <div>#Html.Partial("Invoice", Model.InvoiceViewModel)</div>
Your view Invoice is accepting model of type
InvoicePageViewModel and you are passing InvoiceViewModel
Change your code to <div>#Html.Partial("Invoice", Model)</div>
or modify your Invoice view to accept InvoiceViewModel as
#model ProOptInteractive.ViewModels.InvoiceViewModel
Invoice.cshtml likely starts with:
#model ProOptInteractive.ViewModels.InvoicePageViewModel
replace it with:
#model ProOptInteractive.ViewModels.InvoiceViewModel
You can pass model to your partial view like this:
#Html.Partial("Invoice", Model.InvoiceViewModel)
or something similar.
Model for main view:
class InvoicePageViewModel {
...
public InvoiceViewModel InvoiceViewModel { get; set; }
public IEnumerable<Service> Services { get; set; }
public IEnumerable<Product> Products { get; set; }
...
}
Then update your partial view to accept view model like this:
#model InvoiceViewModel
...
I discovered the error. It was lying in the controller method. When I called the Index view (which corresponds to the ActionResult Index method) I was returning the viewmodel InvoiceViewModel to the Index page, even though it was strongly typed to InvoicePageViewModel. I changed it to this, which works:
public ActionResult Index()
{
var invoice = InvoiceLogic.GetInvoice(this.HttpContext);
// Set up our ViewModel
var pageViewModel = new InvoicePageViewModel
{
Products = proent.Products.ToList(),
Services = proent.Services.ToList(),
InvoiceViewModel = new InvoiceViewModel
{
InvoiceItems = invoice.GetInvoiceItems(),
Clients = proent.Clients.ToList(),
InvoiceTotal = invoice.GetTotal()
}
};
// Return the view
return View(pageViewModel);
}
Thanks to everyone for all the help and suggestions.

Categories