I am trying to implement both Create and List actions in a single View. I was being suggested to use ViewModel. I am getting the object reference error. Also some good example on how to achieve this will help.
My Model Class
public class Employee
{
public int ID { get; set; }
[Required(ErrorMessage="Please enter name")]
public string Name { get; set; }
}
My View Model Class
public class EmployeeVM
{
public Employee Employee { get; set; }
public List<Employee> Employees { get; set; }
}
My Controller
[HttpPost]
public ActionResult Create(EmployeeVM emp, string Name)
{
if (ModelState.IsValid) //my modelstate is valid even when the value is empty string; it then gives an Object reference not set error
{
emp.Employee.Name = Name;
repository.SaveRole(emp);
return RedirectToAction("Index");
}
else
{
return View(emp);
}
}
My View
#model ERP.Domain.Entity.EmployeeVM
<body>
<div class="jumbotron">
#using (Html.BeginForm("Create", "MyController", FormMethod.Post))
{
#Html.ValidationSummary(true)
<label>
Name</label>
<input id="txtName" type="text" name="Name" class="btn btn-default" />
#Html.ValidationMessageFor(model => model.Employee.Name)
<input type="submit" value="Save" class="btn btn-primary" />
}
</div>
Also I was suggested in StackOverflow to go with ViewModel approach if I want to use both Create and List in the same View? Is it the right approach. Some example may help.
Related
I am very new to this and I am trying to show multiple entries to show and have a single button for each entry to edit them. I thought it would be really easy but For some reason I am not getting the clicked entity. Is there way of getting modified entity without running javascript?
Any help will be appreciated. I couldn't find any example code that does this. There are many examples that returns all the entries but not the single element.
Here is my entity class Resource:
public class Resource
{
[Required]
public string title { get; set; }
[Required]
public int value { get; set; }
[Key]
[Required]
public int id { get; set; }
}
On the controller side I have:
[HttpGet]
public IActionResult Index()
{
return View(resources);
}
[HttpPost]
public IActionResult Index(Resource resource)
{
return View(resource);
}
Here is the View and EditorTemplates
#model learningMVC.Models.Resource[]
#{
ViewData["Title"] = "index";
}
<h1>index</h1>
<fieldset>
#Html.EditorFor(x => x);
</fieldset>
#addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
#model learningMVC.Models.Resource
<div>
First Name:<br />
<form asp-action="Index" asp-controller="Home">
<input asp-for="id" class="form-controller" />
<input asp-for="value" class="form-controller" />
<input asp-for="title" class="form-controller" />
<input type="submit" value="Save" class="btn btn-primary" id="btnSubmit_#Model.id" />
</form>
</div>
In your case, you should use particalview instead of editfor, you can see my demo below.
_ResourcesPartical.cshtml:
#model Resource
<div>
<form method="post" asp-action="Index">
First Name:<br />
<input asp-for="id" class="form-controller" />
<input asp-for="value" class="form-controller" />
<input asp-for="title" class="form-controller" />
<input type="submit" value="save" />
</form>
</div>
Your Index view:
#model List<Resource>
#foreach(var m in Model)
{
#await Html.PartialAsync("_ResourcesPartical", m)
}
Controller:
[HttpGet]
public IActionResult Index()
{
//here you get your data.below is my fake data.
var resources= new List<Resource>
{
new Resource
{
id = 1,
title = "aa",
value = 3
},
new Resource
{
id = 2,
title = "bb",
value = 4
}
};
return View(resources);
}
[HttpPost]
public IActionResult Index(Resource resource)
{
return RedirectToAction("Index");
}
Test result:
I created a search bar like this
<form method="post" asp-action="Search">
<input type="text" name="search" placeholder="Enter here the Name " />
<select name="type" id="type" class="form-control">
<option value="Success">Inactive Reservation</option>
<option value="Approved">Active Reservation</option>
<option value="Pending">Pending Reservation</option>
</select>
<input type="submit" value="Search" />
</form>
and the method in controller:
public async Task<IActionResult> Search(string search,string type)
{
var allRsv = from m in _db.Reservation
select m;
var Rsv = allRsv
.Where(x => x.ClientName.ToLower().Contains(search.ToLower()) &&
x.Status.ToLower().Contains(type.ToLower()));
return View(Rsv);
}
What I want: to send in search page something like 'You searched for #search and type: #type.
return View has no option to do this ,neither return to action ..
Can I do it in a simple way ?
My single idea it is to send query string and then request query in search view
What I want: to send in search page something like 'You searched for #search and type: #type.
You can try to pass data to search page via ViewData etc, like below.
In View Page
<form method="post" asp-action="Search">
<input type="text" name="search" placeholder="Enter here the Name " />
<select name="type" id="type" class="form-control">
<option value="Success">Inactive Reservation</option>
<option value="Approved">Active Reservation</option>
<option value="Pending">Pending Reservation</option>
</select>
<input type="submit" value="Search" />
</form>
<h3>You searched for "#ViewData["search"]" and type: #ViewData["type"].</h3>
In Action Method
public async Task<IActionResult> Search(string search, string type)
{
var allRsv = from m in _db.Reservation
select m;
var Rsv = allRsv
.Where(x => x.ClientName.ToLower().Contains(search.ToLower()) &&
x.Status.ToLower().Contains(type.ToLower()));
ViewData["search"] = search;
ViewData["type"] = type;
return View(Rsv);
}
Test Result
Using ViewData to pass data between controllers and views is fine as long as there are not many pieces of data in between. If you have lots of going, it will make everybody else harder to understand what's going on with ViewData because it's a weak-typed and you have no idea what it contains, what's available to get, etc. And then you have to go back to the controller and see what's being passed. What if there are multiple controllers returning this same view...yucky!
In addition, it's not a good practice to display what you have from your database directly from the controller to the view.
Hence you should use one of the alternatives of ViewData, that is ViewModel, which is strongly-typed!
Fake Entity Models
Since I don't have your database, for this demo, I am setting up two fake entity models that represent the data from your persistence storage.
namespace DL.NetCore.EmptySolution.Web.UI.Models.Reservation
{
public class FakeReservationStatusEntity
{
public string StatusId { get; set; }
public string Status { get; set; }
}
public class FakeReservationEntity
{
public int ReservationId { get; set; }
public int ClientId { get; set; }
public string ClientName { get; set; }
public DateTime StartTimeUtc { get; set; }
public FakeReservationStatusEntity ReservationStatus { get; set; }
public int CreatedByUserId { get; set; }
}
}
There is one-to-many relationship between reservation and reservation status I assumed. And please notice I purposely made it so that it has more properties than the view model!
Viewmodels
They're just POCOs (Plain Old CLR Objects) that serve as data containers to travel between controllers and views.
namespace DL.NetCore.EmptySolution.Web.UI.Models.Reservation
{
public class ReservationFiltersViewModel
{
[Display(Name = "Client name")]
public string ClientNameSearchQuery { get; set; }
[Display(Name = "Reservation type")]
public string ReservationTypeSearchQuery { get; set; }
public IDictionary<string, string> AvailableReservationTypes { get; set; }
}
public class ReservationViewModel
{
public int ReservationId { get; set; }
public string ReservationType { get; set; }
public string ClientName { get; set; }
public DateTime StartTime { get; set; }
}
public class ReservationListViewModel
{
public ReservationFiltersViewModel Filters { get; set; }
public IEnumerable<ReservationViewModel> Reservations { get; set; }
}
}
Controller
namespace DL.NetCore.EmptySolution.Web.UI.Controllers
{
public class ReservationController : Controller
{
public IActionResult Index(string c, string t)
{
var vm = new ReservationListViewModel
{
Filters = new ReservationFiltersViewModel
{
ClientNameSearchQuery = c,
ReservationTypeSearchQuery = t,
// You would normally get the list from your database
AvailableReservationTypes = GetFakeReservationStatusesFromDb()
.ToDictionary(x => x.StatusId, x => x.Status)
},
Reservations = Enumerable.Empty<ReservationViewModel>()
};
// You would normally get the list of reservations from your database
var reservationsFromDb = GetFakeReservationsFromDb();
// Filters
if (!String.IsNullOrWhiteSpace(c))
{
reservationsFromDb = reservationsFromDb
.Where(x => x.ClientName.Contains(c, StringComparison.InvariantCultureIgnoreCase));
}
if (!String.IsNullOrWhiteSpace(t))
{
reservationsFromDb = reservationsFromDb
.Where(x => x.ReservationStatus.StatusId.Contains(t, StringComparison.InvariantCultureIgnoreCase));
}
// See you only want to explore what you want on the view
vm.Reservations = reservationsFromDb
.Select(x => new ReservationViewModel
{
ReservationId = x.ReservationId,
ClientName = x.ClientName,
ReservationType = x.ReservationStatus.Status,
StartTime = x.StartTimeUtc.ToLocalTime()
});
return View(vm);
}
[HttpPost]
public IActionResult Search(ReservationFiltersViewModel filters)
{
return RedirectToAction(nameof(Index),
new { c = filters.ClientNameSearchQuery, t = filters.ReservationTypeSearchQuery });
}
...
}
}
Index View
#model DL.NetCore.EmptySolution.Web.UI.Models.Reservation.ReservationListViewModel
#{
ViewBag.Title = "Reservations";
var selectList = new SelectList(Model.Filters.AvailableReservationTypes, "Key", "Value");
}
<h2>Reservations</h2>
<p class="text-muted">
List of reservations you can manage
</p>
<div class="row">
<div class="col-lg-4">
<div class="card">
<div class="card-body">
<form method="post" asp-area="" asp-controller="reservation" asp-action="search">
<div class="form-group">
<label asp-for="Filters.ClientNameSearchQuery"></label>
<input asp-for="Filters.ClientNameSearchQuery" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Filters.ReservationTypeSearchQuery"></label>
<select asp-for="Filters.ReservationTypeSearchQuery" class="form-control"
asp-items="selectList">
<option value="">- select -</option>
</select>
</div>
<button type="submit" class="btn btn-success">Search</button>
</form>
</div>
</div>
</div>
<div class="col-lg-8">
<!-- This could be better optimized, i.e., only display non-empty search -->
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
You searched for <strong>#Model.Filters.ClientNameSearchQuery</strong>
and <strong>#Model.Filters.ReservationTypeSearchQuery</strong>
</div>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Client name</th>
<th>Start from</th>
<th>Type</th>
</tr>
</thead>
<tbody>
#foreach (var reservation in Model.Reservations)
{
<tr>
<td>#reservation.ReservationId</td>
<td>#reservation.ClientName</td>
<td>#reservation.StartTime.ToShortDateString()</td>
<td>#reservation.ReservationType</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
With ViewModel, there is no magic string flowing around like with ViewData. Everything is strongly typed. And the form only posts back the filter model that contains only what we need to the server.
Screenshots
Source code
My demo project's source code is at https://github.com/davidliang2008/DL.NetCore.EmptySolution. The checkin specifically for this demo is at https://github.com/davidliang2008/DL.NetCore.EmptySolution/commit/32087b989de06e316cf747ad49da6ad4b24b61b8
One of my asp.net core pages isn't passing its model back to the controller. I've investigated similar threads in stackoverflow but none could solve my issue - what's different in this particular case?
In the attached MVC, I fill the input box "Enter here some text", then I click on button "CLICK HERE". The debugger enters correctly the controller but the variable "TEST" returns null.
Any geniuses out there able to help me out?
My Model:
namespace MyProgram.Models._04_ModuleTasker
{
public class TaskerViewModel
{
public SelectList SelectListLocations;
public SelectList SelectListUsers;
public SelectList SelectListFilters;
public string SelectedCompanyLocationDesc;
public string SelectedUserId;
public string SelectedFilter;
}
}
My View:
#model MyProgram.Models._04_ModuleTasker.TaskerViewModel
#{
ViewData["Title"] = "AllTasks";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="container-fluid">
<div class="PageTitle">
Tasker
<hr class="TitleHR" />
</div>
<div style="text-align:center;margin-top:20px" class="text-body">
<form asp-controller="Tasker" asp-action="ShowActiveTasksPOST" method="post" role="form">
<input type="text" asp-for="SelectedUserId" /> Enter here some text <br />
<input type="submit" class="btn btn-success InputShortWidth LoginButton" value="CLICK HERE" />
</form>
</div>
</div>
My Controller:
[HttpPost]
public async Task<IActionResult> ShowActiveTasksPOST(TaskerViewModel taskerViewModel)
{
string TEST = taskerViewModel.SelectedUserId;
return RedirectToAction("Index", "Home");
}
To make the binding works, you need to convert the fields to properties with with setters and getters. E.g.:
public string SelectedCompanyLocationDesc { get; set; }
public string SelectedUserId { get; set; }
public string SelectedFilter { get; set; }
I am trying to add validations on my View. But I am unable to access the property inside #Html.ValidationMessageFor(?)
My View
#model IEnumerable<Entity.Employee>
<div class="jumbotron">
#using (Html.BeginForm("Create", "Employee", FormMethod.Post))
{
<label>Name</label>
<input id="txtName" type="text" name="EmployeeName" class="btn btn-default" />
#Html.ValidationMessageFor(model => model. //not able to get the Name property
<input type="submit" value="Save" class="btn btn-primary" />
}
</div>
Employee Class
[Required(ErrorMessage="Please enter name")]
public string Name { get; set; }
Controller
[HttpPost]
public ActionResult Create(Employee employee, string EmployeeName)
{
if (ModelState.IsValid)
{
employee.Name = EmployeeName;
repository.SaveRole(role);
return RedirectToAction("Index");
}
else
{
return View(employee);
}
}
I am not sure what have I missed or is it because the View is Strongly couple to IEnumerable<Type>
Your view has defined model of type IEnumerable<Entity.Employee>. This stands for multiple employees. You should change your model declaration to
#model Entity.Employee
There has been a lot of questions about this... but somehow I can't get this binding to work and I'm still getting null values in my posted View Model. This is MVC 4.
Here is the Main View Model
public class RoleVM {
[Required]
[Display(Name = "Name of the Role")]
public string Role {get; set;}
public IEnumerable<RolePermission> permissions { get; set; }
}
Here is the RolePermission Class
public class RolePermission {
public int id;
public bool permission_value;
public string name { get; set; }
}
Here is GET Create Method in the controller
public ActionResult Create() {
RoleVM role_vm = new RoleVM();
var allpermissions = from p
in permission_repo.GetPermissions()
select p;
role_vm.permissions = from p
in allpermissions
select new RolePermission
{ name = p.name, id = p.PermissionId, permission_value = false };
return View(role_vm);
}
Here is the Create.cshtml file
#model RoleVM
#using (Html.BeginForm("Create", "Role",
FormMethod.Post, new { #class = "permission_form" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>RoleVM</legend>
<div class="form-item">
#Html.LabelFor(model => model.Role)
#Html.EditorFor(model => model.Role)
#Html.ValidationMessageFor(model => model.Role)
</div>
#Html.EditorFor(model => model.permissions)
<p>
<input class="submit-btn" type="submit" value="Create" />
</p>
</fieldset>
}
Next here is the rolepermissions.cshtml file located in ~/Views/Shared/EditorTemplates
#model RolePermission
<div class="form-item">
#Html.HiddenFor(modelItem => modelItem.id)
#Html.LabelFor(modelItem => modelItem.permission_value, Model.name)
#Html.CheckBoxFor(modelItem => modelItem.permission_value)
</div>
Here is an example of one of the html items that is rendered on page
<div class="form-item">
<input data-val="true" data-val-number="The field Int32 must be a number." data-val-required="The Int32 field is required." id="permissions_2__id" name="permissions[2].id" type="hidden" value="3" />
<label for="permissions_2__permission_value">Role-Edit</label>
<input data-val="true" data-val-required="The Boolean field is required." id="permissions_2__permission_value" name="permissions[2].permission_value"
type="checkbox" value="true" /><input name="permissions[2].permission_value" type="hidden" value="false" />
</div>
Finally here is the Create POST method
[HttpPost]
public ActionResult Create(RoleVM rolevm)
{
//In here rolevm.role is populated based on the textbox input
//However rolevm.permissions is there with the correct
//number of items, but the values all are not binded
// id is null, name is empty, and permission_value is false
// regardless of which checkboxes were checked
return RedirectToAction("Index");
}
Any help on the binding issue with the posted model would be really great.
This was a simple issue, if someone else comes across it maybe this will help them. I didn't have the properties on the class declared as properties with get; set;
public class RolePermission {
public int id { get; set; }
public bool permission_value { get; set; }
public string name { get; set; }
}