Use of View Components with forms - forms

I am trying to learn the correct usage of View Components in ASP.NET Core MVC, so I have the following example: The idea is to render a view with Movie details, and inside of it, the ReviewViewComponent holding a 10-star movie rating widget.
Inside of the View Component's View is a form with radio buttons. The form action name is passed to the View Component (either Create or Edit, depending on if the user already gave a rating). Depending on the received action name, the form inside the View Component will either call the Create or the Edit method inside of the ReviewsController.
This all works until I reach the return call inside of ReviewsController. I would like to be able to return the View Component there and simply render the return result inside the div with id="now-showing-details-rating-div" in Details using AJAX. This works with Partial Views (code commented out in the ReviewsController's Edit method), but it seems it doesn't work with View Components (it just renders the View Component's View as a completely new View, even though I call the AJAX on the form the same way I would if it were in a Partial View).
Am I actually misusing the View Component concept here? In the sense of rendering a portion of a View after form submit, is it actually better to just use Partial Views?
Ajax snippet
$.ajax({
type: "POST",
url: requestUrl,
data: form.serialize(),
success: function (data) {
$("#" + divZaRezultat).html(data);
}
});
ViewModels:
public class MovieDetailsVM
{
public string Title { get; set; }
public int Id { get; set; }
public int Year { get; set; }
public string Actors { get; set; }
public string Country { get; set; }
public string Directors { get; set; }
public int Duration { get; set; }
public string VideoLink { get; set; }
public string AverageRating { get; set; }
public string NumberOfReviews { get; set; }
public ReviewIndexVM CurrentUserReview { get; set; }
}
public class ReviewIndexVM
{
public int ReviewId { get; set; }
public int Rating { get; set; }
public MasterModel User { get; set; }
public MasterModel Movie { get; set; }
}
ViewComponent
public class ReviewViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(string methodName, ReviewIndexVM review)
{
ViewBag.Method = methodName;
return View(review);
}
}
ViewComponent Default View
#model Cinema.DTO.ViewModels.Reviews.ReviewIndexVM
#{
ViewData["Title"] = "Default";
}
<form asp-controller="Reviews" asp-action="#ViewBag.Method">
<input asp-for="ReviewId" hidden />
<input asp-for="Movie.Id" hidden />
<input asp-for="User.Id" hidden />
<div class="rating form-group">
#for (int i = 10; i > 0; i--)
{
<input asp-for="Rating" type="radio" value="#i" id="#($"rating-star-{i}")" onclick="this.form.submit();" class="form-control rating-star"><label class="rating-star-label" for="#($"rating-star-{i}")"></label>
}
</div>
</form>
Views:
#model Cinema.DTO.ViewModels.Movies.MovieDetailsVM
#using Microsoft.AspNetCore.Identity
#using Cinema.Domain.Entities.Identity
#inject SignInManager<ApplicationUser> SignInManager
#inject UserManager<ApplicationUser> UserManager
#{
ViewData["Title"] = "Details";
Layout = "~/Views/Shared/_Layout.cshtml";
bool first = true;
DateTime currentDate = DateTime.Now;
}
<section>
<div class="container">
<div class="content-wrap">
<div class="row">
<h1 class="h2">#Html.DisplayFor(model => model.Title)</h1>
</div>
<div class="row">
<div class="col-md-4">
<img id="movie-poster" class="pull-left" src="~/img/movie-poster.png" />
</div>
<div class="col-md-8">
<ul class="list-unstyled movie-info">
<li>
<span>#Html.DisplayNameFor(model => model.Title)</span>
#Html.DisplayFor(model => model.Title)
</li>
<li>
<span>#Html.DisplayNameFor(model => model.Year)</span>
#Html.DisplayFor(model => model.Year)
</li>
<li>
<span>#Html.DisplayNameFor(model => model.Actors)</span>
#Html.DisplayFor(model => model.Actors)
</li>
<li>
<span>#Html.DisplayNameFor(model => model.Country)</span>
#Html.DisplayFor(model => model.Country)
</li>
<li>
<span>#Html.DisplayNameFor(model => model.Directors)</span>
#Html.DisplayFor(model => model.Directors)
</li>
<li>
<span>#Html.DisplayNameFor(model => model.Duration)</span>
#Html.DisplayFor(model => model.Duration)
</li>
#*<li>
<span>#Html.DisplayNameFor(model => model.GenreMovies)</span>
#Html.DisplayFor(model => model.GenreMovies)
</li>*#
<li>
<span>#Html.DisplayNameFor(model => model.VideoLink)</span>
#Html.DisplayFor(model => model.VideoLink)
</li>
</ul>
Average rating <span class="badge">#Model.AverageRating</span>
<hr />
<div asp-authorize asp-roles="#Roles.User">
Your rating:
<div id="now-showing-details-rating-div">
#if (#Model.CurrentUserReview.ReviewId == 0)
{
#await Component.InvokeAsync("Review", new { methodName = "Create", review = #Model.CurrentUserReview })
}
else
{
#await Component.InvokeAsync("Review", new { methodName = "Edit", review = #Model.CurrentUserReview })
}
</div>
</div>
</div>
</div>
</div>
</section>
#section Scripts {
$(document).ready(function () {
$('.rating-star-label').mouseover(function () {
$('.rating-star').prop('checked', false);
});
});
</script>
}
ReviewsController
[HttpGet]
[Authorize(Roles = Roles.User)]
public async Task<IActionResult> Edit(int reviewId)
{
Review review = await _unit.Reviews.GetAsync(reviewId);
var authorizationResult = await _authorizationService.AuthorizeAsync(User, review, OperationRequirements.Update);
if (authorizationResult.Succeeded)
{
ReviewUpdateVM model = review.ToUpdateVM();
return PartialView(model);
}
else if (User.Identity.IsAuthenticated)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}
}
[Authorize(Roles = Roles.User)]
public async Task<IActionResult> Edit(ReviewIndexVM model)
{
Review review = model.Create();
var authorizationResult = await _authorizationService.AuthorizeAsync(User, review, OperationRequirements.Update);
if (authorizationResult.Succeeded)
{
await _unit.Reviews.UpdateAsync(review, model.ReviewId);
await _unit.SaveAsync();
return ViewComponent("Review");
//return Redirect("/Reviews/Details?reviewId=" + review.Id);
}
else if (User.Identity.IsAuthenticated)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}
}
}
else if (User.Identity.IsAuthenticated)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}
}

Here is a working demo:
Details.cshtml:
#model MovieDetailsVM
#{
ViewData["Title"] = "Details";
Layout = "~/Views/Shared/_Layout.cshtml";
bool first = true;
DateTime currentDate = DateTime.Now;
}
<section>
<div class="container">
<div class="content-wrap">
<div class="row">
<h1 class="h2">#Html.DisplayFor(model => model.Title)</h1>
</div>
<div class="row">
<div class="col-md-8">
<ul class="list-unstyled movie-info">
<li>
<span>#Html.DisplayNameFor(model => model.Title)</span>
#Html.DisplayFor(model => model.Title)
</li>
<li>
<span>#Html.DisplayNameFor(model => model.Year)</span>
#Html.DisplayFor(model => model.Year)
</li>
<li>
<span>#Html.DisplayNameFor(model => model.Actors)</span>
#Html.DisplayFor(model => model.Actors)
</li>
<li>
<span>#Html.DisplayNameFor(model => model.Country)</span>
#Html.DisplayFor(model => model.Country)
</li>
<li>
<span>#Html.DisplayNameFor(model => model.Directors)</span>
#Html.DisplayFor(model => model.Directors)
</li>
<li>
<span>#Html.DisplayNameFor(model => model.Duration)</span>
#Html.DisplayFor(model => model.Duration)
</li>
#*<li>
<span>#Html.DisplayNameFor(model => model.GenreMovies)</span>
#Html.DisplayFor(model => model.GenreMovies)
</li>*#
<li>
<span>#Html.DisplayNameFor(model => model.VideoLink)</span>
#Html.DisplayFor(model => model.VideoLink)
</li>
</ul>
Average rating <span class="badge">#Model.AverageRating</span>
<hr />
<div>
Your rating:
<div id="now-showing-details-rating-div">
#if (#Model.CurrentUserReview == null)
{
#await Component.InvokeAsync("Review", new { methodName = "Create", review = #Model.CurrentUserReview })
}
else
{
#await Component.InvokeAsync("Review", new { methodName = "Edit", review = #Model.CurrentUserReview })
}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
Ajax in Details.cshtml:
#section Scripts {
<script>
$(document).ready(function () {
$('.rating-star-label').mouseover(function () {
$('.rating-star').prop('checked', false);
});
});
function Update() {
$.ajax({
type: "POST",
url: "/Reviews/Edit/#Model.CurrentUserReview.ReviewId",
data: $("form").serialize(),
success: function (data) {
$("#now-showing-details-rating-div").html(data);
}
});
}
</script>
}
Components/Review/Default.cshtml:
#model ReviewIndexVM
#{
ViewData["Title"] = "Default";
}
<form asp-controller="Reviews" asp-action="#ViewBag.Method">
<input asp-for="ReviewId" hidden />
<input asp-for="Movie.Id" hidden />
<input asp-for="User.Id" hidden />
<div class="rating form-group">
#for (int i = 10; i > 0; i--)
{
<input asp-for="Rating" type="radio" value="#i" id="#($"rating-star-{i}")" onclick="Update();" class="form-control rating-star"><label class="rating-star-label" for="#($"rating-star-{i}")"></label>
}
</div>
</form>
Controller:
public class ReviewsController : Controller
{
private readonly Component2_2Context _context;
public ReviewsController(Component2_2Context context)
{
_context = context;
}
// GET: Reviews/Details/5
public async Task<IActionResult> Details(int? id)
{
var reviewIndexVM = await _context.MovieDetailsVM
.Include(m => m.CurrentUserReview)
.FirstOrDefaultAsync(m => m.Id == id);
return View(reviewIndexVM);
}
// GET: Reviews/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var reviewIndexVM = await _context.ReviewIndexVM.FindAsync(id);
if (reviewIndexVM == null)
{
return NotFound();
}
return View(reviewIndexVM);
}
// POST: Reviews/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ReviewId,Rating")] ReviewIndexVM reviewIndexVM)
{
if (id != reviewIndexVM.ReviewId)
{
return NotFound();
}
if (ModelState.IsValid)
{
_context.Update(reviewIndexVM);
await _context.SaveChangesAsync();
return ViewComponent("Review");
}
return new ChallengeResult();
}
ViewComponent:
public class ReviewViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(string methodName, ReviewIndexVM review)
{
ViewBag.Method = methodName;
return View(review);
}
}
Result:

Related

null reference when clicking submit on a form that is filled in with mvc C#

I have a form I created in a view which is supposed to submit to two tables using a view model. When I fill in the fields and click submit, I am getting a null reference exception on the vendormodel and refvendormodel after filling everything in on the form and am unsure of the cause. Any help is greatly appreciated.
My Controller method:
[HttpPost]
[Route("Contract/AddVendor")]
[Route("Contract/AddVendor/{addVendor}")]
public IActionResult AddVendorForm(AddVendorVM addVendor)
{
AddVendorVM model = new AddVendorVM();
if(ModelState.IsValid)
{
model.vendormodel.VENDOR_ID = addVendor.vendormodel.VENDOR_ID;
model.refvendormodel.VENDOR_ID = addVendor.refvendormodel.VENDOR_ID;
model.vendormodel.VENDOR_NAME = addVendor.vendormodel.VENDOR_NAME;
model.refvendormodel.ADDRESS = addVendor.refvendormodel.ADDRESS;
model.refvendormodel.CITY = addVendor.refvendormodel.CITY;
model.refvendormodel.STATE = addVendor.refvendormodel.STATE;
model.refvendormodel.ZIP = addVendor.refvendormodel.ZIP;
model.refvendormodel.FIRST_NAME = addVendor.refvendormodel.FIRST_NAME;
model.refvendormodel.LAST_NAME = addVendor.refvendormodel.LAST_NAME;
model.refvendormodel.PHONE = addVendor.refvendormodel.PHONE;
_context.VENDORs.Add(model.vendortmodel);
_context.REF_VENDORs.Add(model.refvendormodel);
_context.SaveChangesAsync();
return RedirectToAction("Edit");
}
return View(model);
}
[HttpGet]
[Route("Contract/AddVendor")]
public IActionResult AddRespondentForm()
{
AddVendorVM model = new AddVendorVM();
return View(model);
}
My View:
<div class="col-md-4">
#using (
Html.BeginForm("AddVendor", "Contract", FormMethod.Post, new { id = "Form1"}))
{
#Html.HiddenFor(m => Model.vendormodel.VENDOR_ID, new { id = "VendorId" })
#Html.HiddenFor(m => Model.refvendormodel.VENDOR_ID, new { id = "VendorId" })
<label class="control-label">Vendor Name:</label>
#Html.TextBoxFor(r => r.vendormodel.VENDOR_NAME)
<label class="control-label">Address:</label>
#Html.TextBoxFor(r => r.refvendormodel.ADDRESS)
<label class="control-label">City:</label>
#Html.TextBoxFor(r => r.refvendormodel.CITY)
<label class="control-label">State:</label>
#Html.TextBoxFor(r => r.refvendormodel.STATE)
<label class="control-label">Zip Code:</label>
#Html.TextBoxFor(r => r.refvendormodel.ZIP)
<label class="control-label">First Name:</label>
#Html.TextBoxFor(r => r.refvendormodel.FIRST_NAME)
<label class="control-label">Last Name:</label>
#Html.TextBoxFor(r => r.refvendormodel.LAST_NAME)
<label class="control-label">Phone Number:</label>
#Html.TextBoxFor(r => r.refvendormodel.PHONE)
<br />
<input type="submit" value="Add Vendor" />
}
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
My model:
public class AddVendorVM
{
public VENDOR vendormodel { get; set; }
public REF_VENDOR refvendormodel { get; set; }
}
You need to change your code like below:
public IActionResult AddVendorForm(AddVendorVM addVendor)
{
ModelState.Remove("vendormodel.VENDOR_ID");
ModelState.Remove("refvendormodel.VENDOR_ID");
if (ModelState.IsValid)
{
VENDOR model1 = addVendor.vendormodel;
REF_VENDOR model2 = addVendor.refvendormodel;
_context.VENDORs.Add(model1);
_context.REF_VENDORs.Add(model2);
_context.SaveChanges();
return RedirectToAction("Edit");
}
return View();
}
The code in your view are meaningless, you can delete them:
#Html.HiddenFor(m => Model.vendormodel.VENDOR_ID, new { id = "VendorId" })
#Html.HiddenFor(m => Model.refvendormodel.VENDOR_ID, new { id = "VendorId" })
Please notice that if your VENDOR_ID is a primary key of model VENDOR,REF_VENDOR.It will be a required column and automatic increase,so if the VENDOR_ID is 0,the validation will never be true,so you need remove the vendormodel.VENDOR_ID modelstate,then the modelstate will be true.
And you need to notice that don't use SaveChangesAsync in a synchronous method.That will not work.

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.

Asp Net Core - Entity Framework - ViewBag - Related Data - CRUD

I am creating a simple Location and Country relationship and i have been able to link the to tables and i am able to pull the related data, the issue is that when i try post the form the database does not update with the ID of the country.
When monitoring the POST action via Firefox Developer tools i can see the ID of the country is being posted.
LocationsController
public class LocationsController : Controller
{
private readonly DataContext _context;
public LocationsController(DataContext context)
{
_context = context;
}
// DropDown: Populate the Dropdown lists
private void PopulateCountryDropDownList(object selectedCountry = null)
{
var countriesQuery = from c in _context.Countries
orderby c.CountryID
select c;
ViewBag.CountryID = new SelectList(countriesQuery.AsNoTracking(), "CountryID", "Title");
}
// GET: Locations/Create
public IActionResult Create()
{
PopulateCountryDropDownList();
return View();
}
// POST: Locations/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Location location)
{
if (ModelState.IsValid)
{
_context.Add(location);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(location);
}
}
Location
using System.ComponentModel.DataAnnotations;
namespace RaceCaribbean.Models
{
public class Location
{
public int LocationID { get; set; }
public string Title { get; set; }
public Country Country { get; set; }
}
}
Country
using System.ComponentModel.DataAnnotations;
namespace RaceCaribbean.Models
{
public class Country
{
public int CountryID { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Code { get; set; }
}
}
Create.cshtml
#model RaceCaribbean.Models.Location
#{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<form asp-action="Create">
<div class="form-horizontal">
<h4>Location</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Country" class="col-md-2 control-label"></label>
<div class="col-md-10">
<select asp-for="Country" class="form-control" asp-items="ViewBag.CountryID">
<option selected="selected" value="">-- Select Country --</option>
</select>
<span asp-validation-for="Country" class="text-danger" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

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