Controller action is not invoked in asp.net core - entity-framework-core

I read the book 'Pro Entity Framework Core 2 for ASP.NET MVC'. I'm currently at the beginning of chapter 12 and have some problems. I have this controller:
namespace DataApp.Controllers
{
public class HomeController : Controller
{
private IDataRepository repository;
public HomeController(IDataRepository repo)
{
repository = repo;
}
public IActionResult Index()
{
return View(repository.GetAllProducts());
}
public IActionResult Create()
{
ViewBag.CreateMode = true;
return View("Editor", new Product());
}
[HttpPost]
public IActionResult Create(Product product)
{
repository.CreateProduct(product);
return RedirectToAction(nameof(Index));
}
public IActionResult Edit(long id)
{
ViewBag.CreateMode = false;
return View("Editor", repository.GetProduct(id));
}
[HttpPost]
public IActionResult Edit(Product product)
{
repository.UpdateProduct(product);
return RedirectToAction(nameof(Index));
}
[HttpPost]
public IActionResult Delete(long id)
{
repository.DeleteProduct(id);
return RedirectToAction(nameof(Index));
}
}
}
The index view looks like this:
#model IEnumerable<DataApp.Models.Product>
#{
ViewData["Title"] = "Products";
Layout = "_Layout";
}
<table class="table table-sm table-striped">
<thead>
<tr><th>ID</th><th>Name</th><th>Category</th><th>Price</th></tr>
</thead>
<tbody>
#foreach (var p in Model)
{
<tr>
<td>#p.Id</td>
<td>#p.Name</td>
<td>#p.Category</td>
<td>$#p.Price.ToString("F2")</td>
<td>
<form asp-action="Delete" method="post">
<a asp-action="Edit"
class="btn btn-sm btn-warning" asp-route-id="#p.Id">
Edit
</a>
<input type="hidden" name="id" value="#p.Id" />
<button type="submit" class="btn btn-danger btn-sm">
Delete
</button>
</form>
</td>
</tr>
}
</tbody>
</table>
<a asp-action="Create" class="btn btn-primary">Create New Product</a>
If I run the application and click the Edit or Create button, I don't get the Editor view. If I navigate in the browser to /Home/Edit, then the view is shown. What can be the problem?
You can find the complete source code for this chapter here:
Chapter 2
Please note, that I'm at the beginning of the chapter and the files in the source code may contain more, that I currently have, but according to the book, it should work on this stage too.

Related

Returning object details in the View based on the object ID ASP.net core MVC

I'm building online catalog for phones, I have two controller one for phone Catalog and second for phone's details.With catalog everything is fine, now my goal is to see phone details after clicking on the phone's name or photo in the catalog.I think that with phone's id, its easier to solve this task, also I use repository pattern.
This Catalog' s Controller:
public class PhonesCatalog : Controller
{
private readonly IPhoneRepository _repository;
public int PageSize = 6;
public PhonesCatalog(IPhoneRepository repository)
{
_repository = repository;
}
[HttpGet]
public IActionResult Catalog(int productPage = 1)
=> View(new ProductsListViewModel
{
Phones = _repository.Phones
.OrderBy(x => x.PhoneId)
.Skip((productPage -1) * PageSize)
.Take(PageSize),
PagingInfo = new PagingInfo
{
CurrentPage = productPage,
ItemsPrePage = PageSize,
TotalItems = _repository.Phones.Count()
}
});
}
The repository pattern:
public interface IPhoneRepository
{
IQueryable<Phone> Phones { get; }
Phone GetPhoneById(int id);
}
public class EfMobileStoreRepository : IPhoneRepository
{
private readonly MobileStoreCatalogContext _context;
public EfMobileStoreRepository(MobileStoreCatalogContext context)
{
_context = context;
}
public IQueryable<Phone> Phones => _context.Phones;
public Phone GetPhoneById(int id) => _context.Phones
.FirstOrDefault(p => p.PhoneId == id);
}
and here is Phone detail controller and view:
public class PhonesDetails : Controller
{
private readonly IPhoneRepository _repository;
public PhonesDetails(IPhoneRepository repository)
{
_repository = repository;
}
[HttpGet]
public IActionResult Details(int id)
{
return View(_repository.GetPhoneById(id));
}
}
#model Mobile_Store_Catalog_wandio.Models.Phone
<h4>Phone Details</h4>
<div class="row">
<p>#Model.PhoneName</p>
<p>#Model.Manufactor</p>
<p>#Model.OperationSystem</p>
<p>#Model.Processor</p>
<p>#Model.Memory</p>
<p>#Model.ScreenResolution</p>
<p>#Model.Size</p>
<p>#Model.Wight</p>
</div>
Here Catalog View:
#model ProductsListViewModel
<h1>Phone Catalog</h1>
<div class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-md-3 btn-group-vertical text-center">
Filter
Search
</div>
<div class=" row col-md-9">
#foreach (var p in Model.Phones)
{
<div class=" col-md-4 border border-dark">
<a href="#Url.Action("Details", "PhonesDetails")">
<img class="img-fluid" src="/Images/#p.ImageName"/>
</a>
<p class="text-center container">#p.PhoneName</p>
<p class="text-white text-center bg-success">#p.Price.ToString("C2")</p>
</div>
}
</div>
</div>
</div>
</div>
<div class="row">
<div page-model="#Model.PagingInfo" page-action="Catalog" page-classes-enabled="true"
page-class="btn" page-class-normal="btn-outline-dark"
page-class-selected="btn-primary" class="btn-group-toggle m-1 al">
</div>
</div>
and here is the ProductViewList code:
public class ProductsListViewModel
{
public IEnumerable<Phone> Phones { get; set; }
public PagingInfo PagingInfo { get; set; }
}
Problem is that when I'm clicking on the phone image,but it takes only first id of phone and returns its detail,it doesn't matter which image is clicked, but I need to return details by id, not only first id Phone detail.
What I'm doing wrong here, can anyone help me?
You can change your code like this:
<a asp-controller="PhonesDetails" asp-action="Details" asp-route-id="#p.Id">
<img class="img-fluid" src="/Images/#p.ImageName"/></a>

How to update a MVC Form before Submit

So i have 2 Viewbags, they each have a list of values from a database table, the first Viewbag have all possible values of a database column, while the other have only the values corresponding to the selected value in the first Viewbag.
I have the logic for the search.
but i need to have the form update after selecting one value, since they both need to be in the same form, it is not searching for the second value.
OBS:i am only using the controllers, and cshtml views, not razor pages.
Here is a simple demo how to create cascading selectlist in asp.net core mvc:
Model:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SubCategory
{
public int Id { get; set; }
public int CategoryId { get; set; }
public string SubCategoryName { get; set; }
}
View(Index.cshtml):
<div>
<div style="float:left;width:40%">
<form id="form">
<div class="form-group row">
<label>Category Name</label>
<div class="col-12">
<select id="CategoryId" class="custom-select mr-sm-2"
asp-items="#(new SelectList( #ViewBag.Category,"Id","Name"))">
<option value="">Please Select</option>
</select>
</div>
</div>
<div class="form-group row">
<label>SubCategory Name</label>
<div class="col-12">
<select id="SubCategoryId" class="custom-select mr-sm-2"
asp-items="#(new SelectList(string.Empty,"Id","SubCategoryName"))">
<option value="">Please Select</option>
</select>
</div>
</div>
<div>
<input type="button" value="Search" />
</div>
</form>
</div>
</div>
#section Scripts
{
<script>
$(function () {
$('#CategoryId').change(function () {
var data = $("#CategoryId").val();
$.ajax({
url: '/Home/GetSubCategory?CategoryId=' + data,
type: 'Get',
success: function (data) {
var items = "";
$.each(data, function (i, item) {
items += "<option value='" + item.value + "'>" + item.text + "</option>";
});
$('#SubCategoryId').html(items);
}
})
});
})
</script>
}
Controller:
public class HomeController : Controller
{
private readonly MvcProj3Context _context;
public HomeController(MvcProj3Context context)
{
_context = context;
}
public IActionResult Index()
{
ViewBag.Category = _context.Category.ToList();
return View();
}
public JsonResult GetSubCategory(int CategoryId)
{
ViewBag.SubCategory = (from m in _context.SubCategory
where m.CategoryId == CategoryId
select m).ToList();
return Json(new SelectList(ViewBag.SubCategory, "Id", "SubCategoryName"));
}
}
Result:
The problem is, that the .cshtml file is completely rendered before its send to your browser. Therefore you cannot change it with C# code after its sent to the browser.
You could use blazor if you want to do it with c#, or you could do it with javascript.

Is there any way to have Controller, ViewModel and View without Model? It's for an Index only view

What I want to do is a parametrized report, i would love to use SSRS or other fancy tools for this but it's sort of dangerous at this point because i don't really want to mess around with the company server and I dont have much time; Also If it's a tool it should be a free and light tool and i didn't find one by now.
So, my idea is making a simple controller with Index that will return a List to View according to parameters and the View will use that ViewModel as Model then the users can export that list to CSV or PDF, the problem is: MVC is asking for a real db model to complete the scaffolding, how can this be done then?
Controller (I call an stored proc here)
public class ReporteEntregasPresentacionController : Controller
{
private EntregaMedicamentosEntities db = new EntregaMedicamentosEntities();
public ActionResult Index(DateTime DateFrom, DateTime DateTo)
{
ReporteEntregasPresentacionViewModel rep = new ReporteEntregasPresentacionViewModel();
string sqlQuery = "[dbo].[spEntregasPorPresentacion] ({0},{1})";
Object[] parameters = { DateFrom, DateTo };
rep.LstEntregasPresentacionViewModel = db.Database.SqlQuery<ItemEntregasPresentacionViewModel>(sqlQuery, parameters).ToList();
return View(rep);
}
}
ViewModel:
public class ReporteEntregasPresentacionViewModel
{
public int index;
public List<ItemEntregasPresentacionViewModel> LstEntregasPresentacionViewModel;
}
public class ItemEntregasPresentacionViewModel {
public string idProducto { get; set; }
public string Descripcion { get; set; }
public string EntregasTotales { get; set; }
}
I don't have a View now but i should be something like this:
#model EntregaMedicamentos.Models.ReporteEntregasPresentacionViewModel
<link href="~/Content/css/styles.css" rel="stylesheet" />
#{
ViewBag.Title = "ReporteEntregasPresentacion";
}
<h2>ReporteEntregasPresentacion</h2>
#using (Html.BeginForm("Index", "Entrega", FormMethod.Post))
{
<div class="card text-white bg-secondary">
<h5 class="card-header">Search</h5>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="input-group">
#Html.TextBox("DateFrom", ViewBag.currentFilter1 as DateTime?, new { #class = "form-control", placeholder = "Desde fecha", #readonly = "true", type = "datetime" })
#Html.TextBox("DateTo", ViewBag.currentFilter2 as DateTime?, new { #class = "form-control", placeholder = "Hasta fecha", #readonly = "true", type = "datetime" })
<button id="Submit4" type="submit" style='font-size:22px ;color:blue'><i class='fas fa-search'></i></button>
</div>
</div>
</div>
</div>
</div>
}
<br>
<table class="table table-striped ">
<tr class="table-primary">
<th>
Código
</th>
<th>
Producto
</th>
<th>
Entregas Totales
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.idProducto)
</td>
<td>
#Html.DisplayFor(modelItem => item.Descripcion)
</td>
<td>
#Html.DisplayFor(modelItem => item.Valor)
</td>
</tr>
}
</table>
I ended up creating a real table/model and then it worked fine with the viewmodel. Thanks.

Validation Message is not Displayed on View

This is not a repeated question am posting this question after trying all solutions.
I want to perform CRUD on a single View so I got this article
CRUD using SIngle View
It works fine but when I keep the text box empty then the Model is Valid returns false which is correct,after debugging it shows Name field is required but I cant see the error on the View.
Even #Html.ValidationSummary(true) is present in Begin Form
and #Html.ValidationMessageFor(model => model.Name)
So keeping the code short have used only one field
Model
public partial class tblClient
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
A class which handle multiple button
public class HttpParamActionAttribute : ActionNameSelectorAttribute
{
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
if (actionName.Equals(methodInfo.Name, StringComparison.InvariantCultureIgnoreCase))
return true;
var request = controllerContext.RequestContext.HttpContext.Request;
return request[methodInfo.Name] != null;
}
}
Controller
public class EmpController : Controller
{
SampleEntities1 db = new SampleEntities1();
//
// GET: /Emp/
public ActionResult Index(int? id)
{
ViewBag.Operation = id;
ViewBag.Name = db.tblClients.ToList();
tblClient objEmp = db.tblClients.Find(id);
return View(objEmp);
}
[HttpPost]
[HttpParamAction]
[ValidateAntiForgeryToken]
public ActionResult Create(tblClient objEmp)
{
if (ModelState.IsValid)
{
db.tblClients.Add(objEmp);
db.SaveChanges();
}
return RedirectToAction("Index");
}
[HttpPost]
[HttpParamAction]
[ValidateAntiForgeryToken]
public ActionResult Update(tblClient objEmp)
{
if (ModelState.IsValid)
{
db.Entry(objEmp).State = EntityState.Modified;
db.SaveChanges();
}
return RedirectToAction("Index", new { id = 0 });
}
public ActionResult Delete(int id)
{
tblClient objEmp = db.tblClients.Find(id);
db.tblClients.Remove(objEmp);
db.SaveChanges();
return RedirectToAction("Index", new { id = 0 });
}
}
View
#using (Html.BeginForm())
{
<fieldset>
<legend><b>Emp Details</b></legend>
<table border="1" cellpadding="10">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
Action
</th>
</tr>
#foreach (var item in (IEnumerable<SingleVIewCrud.Models.tblClient>)ViewBag.Name)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.ActionLink("Edit", "Index", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
</table>
</fieldset>
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
<fieldset>
<legend> <b>Entry Screen</b></legend>
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="form-group">
<p>
<input type="submit" value="Create" name="Create"
style=#((ViewBag.Operation != null && Convert.ToInt32(ViewBag.Operation) > 0) ? "display:none" : "display:block") />
<input type="submit" value="Update" name="Update"
style=#((ViewBag.Operation != null && Convert.ToInt32(ViewBag.Operation) > 0) ? "display:block" : "display:none") />
</p>
</div>
</fieldset>
</div>
}
What is wrong that the validation error message is not displayed.
In both your Create() and Edit() POST methods, if ModelState is invalid, you just redirecting to the Index() view. You need to return the existing view -
if (!ModelState.IsValid()
{
return View(objEmp);
}
// save and redirect
db.tblClients.Add(objEmp);
db.SaveChanges();
return RedirectToAction("Index");
Side note: If you include the jquery.validate.js and jquery.validate.unobtrusive.js scripts, then you will also get client side validation and the POST methods will not even be hit - the validation messages will be displayed and the submit will be cancelled

DefaultModelBinder Implementation does not populate the model - ASP.NET MVC 2

I have a very simple implementation of the DefaultModelBinder, I need it to fire some custom validation.
public class MyViewModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ModelStateDictionary modelState = bindingContext.ModelState;
var model = (MyViewModel)base.BindModel(controllerContext, bindingContext);
var result = ValidationFactory.ForObject<MyViewModel>().Validate(model);
CustomValidation(result, modelState);
return model;
}
}
MyViewModel is a public sealed class.
The model binder is registered in the Global.asax this way:
ModelBinders.Binders.Add(typeof(MyViewModel), new MyViewModelBinder());
The problem is that the model is never populated! But the MVC default model binder (I remove the registration in global.asax) works fine.
This is the view HTML:
<table>
<tr>
<td><label for="Name">Name</label></td>
<td><input id="Name" name="Name" type="text" value="" /></td>
</tr>
<tr>
<td><label for="Code">Code</label></td>
<td><input id="Code" name="Code" type="text" value="" /></td>
</tr>
</table> </div>
Every field matches a property of the model.
From the information you provided I am unable to reproduce the problem. Here's what I did.
View model:
public sealed class MyViewModel
{
public string Name { get; set; }
public string Code { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// at this stage the model is populated perfectly fine
return View();
}
}
Index View:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<% using (Html.BeginForm()) { %>
<table>
<tr>
<td><label for="Name">Name</label></td>
<td><input id="Name" name="Name" type="text" value="" /></td>
</tr>
<tr>
<td><label for="Code">Code</label></td>
<td><input id="Code" name="Code" type="text" value="" /></td>
</tr>
</table>
<input type="submit" value="OK" />
<% } %>
</body>
</html>
Model binder:
public class MyViewModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = (MyViewModel)base.BindModel(controllerContext, bindingContext);
// at this stage the model is populated perfectly fine
return model;
}
}
So now the question is, how does your code differs than mine and what is it in those CustomValidation and Validate methods?