I am trying to render a partial view in my Site.Master file but I keep on getting an error (Object reference not set to an instance of an object). If I access the view directly, I get the info that I need, but if I access the Index View from the HomeController, I get an error. The partial view will display the logged in user, department, and role. This is my code.
Controllers Folder
-HomeController.cs
-UsersController.cs
Models
-Repository Folder
- UersRepository.cs
-Repository Interface Folder
- IUsers.cs
-Service Folder
- UsersService.cs
- IUserService.cs
-Validation Folder
- IValidationDictionary
- ModelStateWrapper
The view that I'm trying to partially render is called LoginInfo.ascx and it's located in the Shared Folder.
LoginInfo.cs Code:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Users>>" %>
<%# Import Namespace="TimeAttendanceMVC.Models"%>
<table>
<tr>
<th>
UserName
</th>
<th>
Department
</th>
<th>
UserType
</th>
</tr>
<% foreach (var item in Model) { %>
<tr>
<td>
<%: Html.DisplayFor(modelItem => item.UserName) %>
</td>
<td>
<%: Html.DisplayFor(modelItem => item.Department) %>
</td>
<td>
<%: Html.DisplayFor(modelItem => item.UserType) %>
</td>
</tr>
<% } %>
</table>
Site.Master Code:
<% Html.RenderPartial("LoginInfo"); %>
UserController.cs
public class UsersController : Controller
{
//
private IUsersService _service;
//==============================================================================
//==============================================================================
public UsersController()
{
_service = new UsersService(new ModelStateWrapper(this.ModelState));
}
public UsersController(IUsersService service)
{
_service = service;
}
//==============================================================================
//==============================================================================
// GET: /Employee/
//==============================================================================
//==============================================================================
// GET: /Employee/
public ActionResult Index()
{
var model = _service.Return_UserName_Dept();
return View("LoginInfo", model);
}
}
HomeController.cs
public ActionResult Index()
{
//var model = _service.Return_UserName_Dept();
//return View(model);
return View();
}
With the code above, I get an error. If I un-comment the 2 lines in my HomeController and I pass the model to the View, then it works fine. But my HomeController will need a different model, so how will I pass 2 models to the View?
Any ideas what I'm doing wrong? I'm still learning MVC right now so I'm not that good at this. Any help would be appreciated. Thanks.
Calling RenderPartial renders the view directly, inheriting the parent's model.
You should call RenderAction instead to render a child action so that the action can pass the correct model to the partial view.
Your Controller is able to pass a single Model to the View for rendering. Rather than attempting to have two models, or to call two Action Methods in a request with RenderAction, have you considered creating a parent ViewModel type?
// This model is passed to your view for rendering
public class MyViewModel
{
public string Title { get; set; }
public DateTime Date { get; set; }
// This property is passed as the model to your partial view
public UserInfo UserInfo { get; set; }
}
public ActionResult Index()
{
var model = new MyViewModel(); // _service.GetIndexViewModel();
model.UserInfo = _service.Return_UserName_Dept();;
return View(model);
}
Then in your View, you would do something like:
<% Html.RenderPartial("LoginInfo", Model.UserInfo); %>
Related
I have a view model derived from a base view model. The base VM has two properties, bool IsDisplayable & int MyValue.
I would like to create a display template that would take those two values and either display MyValue or not depending on the value of IsDisplayable.
This pair of values is part of all my view models and I would like to use one Display Template for all my views that use models derived from my base model.
My question is how would I pass this part of each view model to my Display Template?
#Html.DisplayFor(modelItem => item.MyValue,"MyDisplayTemplate")
My Display Template:
#model WhatModelGoesHere
#if (Model.IsDisplayable)
{
<td>
#Html.DisplayFor(model => model.MyValue)
</td>
}
The difficulty is trying to make the display template generic enough to work with any of my view models.
I suggest you create a common base class for other models to inherit.
Put the two fields IsDisplayable and MyValue that you need to display in the public template in the common base class, so that you only need to reference the common base class in the template.
And I suggest you use partial view to implement Display Template.
Common base class:
public class GenricModel
{
public bool IsDisplayable { get; set; }
public int MyValue { get; set; }
}
View Model to inhert common base class:
public class ViewModel1 : GenricModel
{
public int Id { get; set; }
public string Name { get; set; }
}
In the view to show ViewModel1 data, you can use #Html.Partial("_TemplateView", item) to call Display Template, here is an example based on ViewModel1:
#model IEnumerable<WebApplication_core.Models.ViewModel1>
#{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Index</h1>
<table class="table-bordered">
<tr>
<th>
Id
</th>
<th>
Name
</th>
<th>
MyValue
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#item.Id
</td>
<td>
#item.Name
</td>
#Html.Partial("_TemplateView", item)
</tr>
}
</table>
Then, create a partial view named _TemplateView.cshtml in Views folder.
Generally speaking, partial views are created under Views/Shared path in the project, you can refer to this.
#model WebApplication_core.Models.GenricModel
#if (Model.IsDisplayable)
{
<td>
#Html.DisplayFor(model => Model.MyValue)
</td>
}
I have AccountController.cs with below action:
[HttpGet]
[AllowAnonymous]
public IActionResult Register()
{
ViewBag.Registration = GetRegistration();
return View();
}
ViewBag.Registration contains 2 elements and it's ok.
Then I got Registration.cshtml view:
#model Registration <!-- this model I'm using for other form -->
#{
Layout = "_Layout";
}
<!-- some code -->
#await Html.PartialAsync("AllRegistered")
and AllRegistered.cshtml where data from ViewBag.Registration should be displayed:
#model IEnumerable<Registration>
<table>
<tr>
<th>#Html.DisplayNameFor(m => m.Email)</th>
<th>#Html.DisplayNameFor(m => m.City)</th>
</tr>
#if (Model != null && Model.Count() != 0)
{
#foreach (Registration registration in Model)
{
<tr>
<th>#Html.DisplayFor(m => registration.Email)</th>
<th>#Html.DisplayFor(m => registration.City)</th>
</tr>
}
}
</table>
But nothing is generated into view, Model I think is empty.
The PartialAsync method contains an overload which includes the model:
Html.PartialAsync(string partialViewName, TModel model)
You should include the IEnumerable<Registration> (the partial view's model) in that helper.
If GetRegistrations() is returning that IEnumerable, you would define the partial view like this:
#await Html.PartialAsync("AllRegistered", (List<Registration>)ViewBag.Registration)
While Nathan's answer is perfectly correct, it would be more appropriate for this to be a view component. The fact that you want to display all registrations is a view detail that has nothing to do with the purpose of this action. As such, having the action be responsible for retrieving data requires it to have knowledge it doesn't need and shouldn't have.
Instead, add a class like:
public class AllRegistrationsViewComponent : ViewComponent
{
private readonly RegistrationsService _service;
public AllRegistrationsViewComponent(RegistrationService service)
{
_service = service;
}
public async Task<IViewComponentResult> InvokeAsync()
{
// logic behind `GetRegistrations()` here
return View(registrations);
}
}
The reference here to RegistrationsService is just whatever means you're using to retrieve your registrations, to show how to inject that into the component. This might be your context or something else entirely.
Then, create the view Views/Components/AllRegistrations/Default.cshtml with:
#model IEnumerable<Registration>
<table>
<tr>
<th>#Html.DisplayNameFor(m => m.Email)</th>
<th>#Html.DisplayNameFor(m => m.City)</th>
</tr>
#if (Model != null && Model.Count() != 0)
{
#foreach (Registration registration in Model)
{
<tr>
<th>#Html.DisplayFor(m => registration.Email)</th>
<th>#Html.DisplayFor(m => registration.City)</th>
</tr>
}
}
</table>
The AllRegistrations part of the path is based on the name of the view component, without the ViewComponent part, so if you name it differently, adjust here as well.
Finally, in your view:
#await Component.InvokeAsync("AllRegistrations")
Then, your action can just focus on its actual purpose:
[HttpGet]
[AllowAnonymous]
public IActionResult Register()
{
return View();
}
Extension to: How do you handle multiple submit buttons in ASP.NET MVC Framework?
Let us say a view is composed of partial views bound with related models, let's say a student is required to provide multiple contact persons(partial view bound to Person model) and multiple contact numbers(partial view bound to a model) to get registered, sorry for the bad example. Once a contact person or number is added, an action (child postback or whatever) is called which validates the related model (not student model), adds it in a list and returns to the same page for further processing. Once all are added, the parent/master action validates whole student model and processes it.
How to validate the specific model for which an action is being called, add it to the page and return the same student view with added values in response?
This solution uses #2 (Session) since its simpler to code however this demonstrates the principles.
Views
Index View:
#using StackOverflow.Models
<div>
#{ Html.RenderPartial("PersonGrid", Model.Persons, new ViewDataDictionary()); }
#Html.Partial("NewPerson", new Person())
#{ Html.RenderPartial("ContactGrid", Model.Contacts, new ViewDataDictionary()); }
#Html.Partial("NewContact", new Contact())
#using(Html.BeginForm("Validate", "Home", FormMethod.Post))
{
<input type="submit" value="Validate" />
}
</div>
Person Grid
#model IList
<table>
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
</tr>
</thead>
<tbody>
#if (Model != null && Model.Any())
{
foreach (var person in Model)
{
<tr>
<td>#person.FirstName</td>
<td>#person.LastName</td>
</tr>
}
}
else
{
<tr>
<td colspan="2" style="text-align: center">No persons available</td>
</tr>
}
</tbody>
</table>
Contact Grid
#model IList
<table>
<thead>
<tr>
<td>Phone</td>
</tr>
</thead>
<tbody>
#if (Model != null && Model.Any())
{
foreach (var contact in Model)
{
<tr>
<td>#contact.Phone</td>
</tr>
}
}
else
{
<tr>
<td>No contacts available</td>
</tr>
}
</tbody>
</table>
New Person
#model StackOverflow.Models.Person
#using (Html.BeginForm("NewPerson", "Home", FormMethod.Post))
{
<div>
#Html.Hidden("PersonViewState", TempData["PersonViewState"])
#Html.LabelFor(m => m.FirstName)<br />
#Html.TextBoxFor(m => m.FirstName)<br />
<br />
#Html.LabelFor(m => m.LastName)<br />
#Html.TextBoxFor(m => m.LastName)<br />
<br />
<input type="submit" value="Submit" />
</div>
}
New Contact
#model StackOverflow.Models.Contact
#using (Html.BeginForm("NewContact", "Home", FormMethod.Post))
{
<div>
#Html.LabelFor(m => m.Phone)<br />
#Html.TextBoxFor(m => m.Phone)<br />
<br />
<input type="submit" value="Submit" />
</div>
}
Models
public class Person
{
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
}
public class Contact
{
[Display(Name = "Phone")]
public string Phone { get; set; }
}
public class HomeModel
{
public IList<Person> Persons { get; set; }
public IList<Contact> Contacts { get; set; }
}
Helpers
public static class PersistenceMechanism
{
public static IList GetPersons()
{
return (IList<Person>) HttpContext.Current.Session["__Persons"];
}
public static IList GetContacts()
{
return (IList<Contact>) HttpContext.Current.Session["__Contacts"];
}
public static void Update(IList<Person> persons)
{
HttpContext.Current.Session["__Persons"] = persons;
}
public static void Update(IList<Contact> contacts)
{
HttpContext.Current.Session["__Contacts"] = contacts;
}
}
Controller
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new HomeModel
{
Persons = PersistenceMechanism.GetPersons(),
Contacts = PersistenceMechanism.GetContacts()
};
return View(model);
}
[HttpGet]
public ActionResult PersonGrid()
{
var persons = PersistenceMechanism.GetPersons();
return PartialView(persons);
}
[HttpGet]
public ActionResult ContactGrid()
{
var contacts = PersistenceMechanism.GetContacts();
return PartialView(contacts);
}
[HttpPost]
public ActionResult NewPerson(Person model)
{
var persons = PersistenceMechanism.GetPersons() ?? new List<Person>();
persons.Add(model);
PersistenceMechanism.Update(persons);
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult NewContact(Contact model)
{
var contacts = PersistenceMechanism.GetContacts() ?? new List<Contact>();
contacts.Add(model);
PersistenceMechanism.Update(contacts);
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult Validate()
{
var persons = PersistenceMechanism.GetPersons();
var contacts = PersistenceMechanism.GetContacts();
// validate
// ...
return RedirectToAction("Index");
}
}
To repeat the question to ensure that I have the idea of what you are asking.
Your page page is build from two partial views with different models. Both of these partial views contain a form which will on submit build a grid of records on the UI. The main page will have a further validate button which will then validate the entire contents of both grids on postback?
In this situation I will like to have two forms where the submit event is powered by Ajax. Either jQuery / Microsoft Ajax. Both forms will submit to two separate actions which will accept their respective models, Person and Contact. Each submit button will return its respective partial view which will be a grid showing the collection of items that have been submitted so far. The partial view returned will update a specified target (e.g. div) since we are using AJAX.
Of course it will be necessary to remember the previous items that were submitted so far in order reconstruct the grid with the new item added. This will mean some soft of persistent storage is required. Some options available are:
Database
Session
Hidden form field(s) (preferred). It is possible to use the array model binding mechanism to support this or a simple serialized object (or list of objects) in a single hidden field.
Validation now becomes simple as the model are available server side through your persistent storage mechanism on postback.
I have a MVC model oject that contains a collection of object. I want to expose the MVC model as user input fields on a MVC view.
Below is MVC model and domain model
public class BookModel
{
public BookModel(Book book)
{
this.Authors = book.Authors;
}
public List<Author> Authors { get; set; }
}
public class Book
{
public List<Author> Authors = new List<Author>();
}
public class Author
{
public string Name { get; set; }
}
Below is the Action method in controller:
public ActionResult Edit(int id)
{
BookModel model = GetBookModel(id);
return View(model);
}
The problem is that, the generated view (*.cshtml) does NOT have the input fields for the collection authors.
Any idea would be very much appreciated.
The problem is that, the generated view (*.cshtml) does NOT have the
input fields for the collection authors.
The ASP.NET MVC new View wizard does its best. From there its up to you.
So inside Edit.cshtml you could use editor templates:
#model BookModel
#using (Html.BeginForm())
{
<table>
<thead>
<tr>
<th>Name</th>
<th>Text</th>
</tr>
</thead>
<tbody>
#Html.EditorFor(x => x.Authors)
</tbody>
</table>
<input type="submit" value="Edit" />
}
and then define a editor template for an Author (~/Views/Shared/EditorTemplates/Author.cshtml) which will be rendered automatically for each element of your collection:
#model Author
<tr>
<td>#Html.EditorFor(x => x.Name)</td>
<td>#Html.EditorFor(x => x.Text)</td>
</tr>
I have an index view grid that i would like to put a create partial view at the bottom of-- that way a user can create a new item on the same page as viewing all items. However, upon submitting the partial create view, nothing happens to the main index page; it doesn't update with the new item (although its there after a refresh).
Index page:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Paris.Domain.Models.PhoneNumberType>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Index</h2>
<table>
<tr>
<th></th>
<th>
PhoneNumberTypeID
</th>
<th>
Name
</th>
</tr>
<% foreach (var item in Model) { %>
<tr>
<td>
<%: Html.ActionLink("Edit", "Edit", new { id=item.PhoneNumberTypeID}) %> |
<%: Html.ActionLink("Delete", "Delete", new { id=item.PhoneNumberTypeID })%>
</td>
<td>
<%: item.PhoneNumberTypeID %>
</td>
<td>
<%: item.Name %>
</td>
</tr>
<% } %>
</table>
<p>
Create New
</p>
<% Html.RenderAction("Create"); %>
</asp:Content>
And here is the controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Paris.Domain.Access;
namespace Paris.Web.Areas.Domain.Controllers
{
public class PhoneNumberTypesController : Controller
{
//
// GET: /Domain/PhoneNumberTypes/
private IRepository<Paris.Domain.Models.PhoneNumberType> db;
public PhoneNumberTypesController(IRepository<Paris.Domain.Models.PhoneNumberType> context)
{
db=context;
}
public ViewResult Index()
{
return View(db.Get().Select(i=>i));
}
public ViewResult IndexCreate(Paris.Domain.Models.PhoneNumberType num)
{
Create(num);
return View();
}
public ViewResult Edit()
{
return View();
}
[HttpPost]
public ViewResult Edit(Paris.Domain.Models.PhoneNumberType num)
{
try
{
if (ModelState.IsValid)
{
db.Update(num);
return Index();
}
else
return View();
}
catch
{
return View();
}
}
[HttpPost]
public ViewResult Delete(int PhoneNumberTypeID)
{
db.Delete(db.Get().First(i => i.PhoneNumberTypeID == PhoneNumberTypeID));
return Index();
}
[HttpPost]
public ViewResult Create(Paris.Domain.Models.PhoneNumberType num)
{
try
{
if (ModelState.IsValid)
{
db.Create(num);
return View();
}
else
return View();
}
catch
{
return View();
}
}
[HttpGet]
public ViewResult Create()
{
return View();
}
}
}
If i am doing this completely wrong, feel free to point out how my thinking is flawed.
Thanks
In your Create action methods, you are returning just the Create view, not the whole page. This is because the default behaviour of View() is to return a view with the same name as the action method. Therefore, if you want the Post to return the whole page you need to either specify the correct view name, or even better return a redirect to the Index action. eg: return RedirectToAction("Index");
Logically what you are doing is right. However on the submit button of partial create, your entire page should get refreshed. Why is it not happening? Are you using Ajax to create new record?
I guess this is what you are doing:
Posting to create action.
Record is created and then new record set is fetched from data source.
You pass it back to your view (return View("Index", recordset)).
Index view is refreshed with new record set.
If you are doing these steps then it should work.