RedirectToAction on [HttpPost] returning "resource not found" error - entity-framework

I have a fairly simple project with is using ninject with asp mvc 4 and entity framework.
I have added an edit and create ActionResult based on the view with no problems. However the delete ActionResult is not working.
The view is a IEnumrable based on the entity, with a simple ActionLink
#Html.ActionLink("Delete", "Delete_Client", new { item.ClientId })
The controller is also very simple.
[HttpPost]
public ActionResult Delete_Client(int id)
{
Client deleteClient = repository.DeleteClient(id);
if (deleteClient != null)
{
TempData["message"] = string.Format("{0} was deleted.", deleteClient.Name);
}
return RedirectToAction("Admin_Client_List");
}
This interacts with the model through the Iinterface
Client DeleteClient(int id);
and in the Entity framework
public Client DeleteClient(int id)
{
Client dbEntry = context.Clients.Find(id);
if (dbEntry != null)
{
context.Clients.Remove(dbEntry);
context.SaveChanges();
}
return dbEntry;
}
The error is
The resource cannot be found.
This is very confusing because i feel like i am not understanding a very fundamental principle of the framework. As i understand it, that means that there is no corresponding ActionResult for the client controller. But there is. The tutorial is am working through suggested that a delete action should be idempotent and therefore only contain a [HttpPost] .
The Uri looks like this
/Client/Delete_Client?ClientId=12
I thought that maybe it would need to look like this
/Client/Delete_Client/12
However that does not work.
Updated request for Admin_Client_List.cshtml
#model IEnumerable<Project.Domain.Entities.Client>
#{
ViewBag.Title = "Client List";
ViewBag.Icon = "entypo-layout";
ViewBag.ClientActive = "active";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
Create a new Client
<div class="row">
#foreach (var item in Model)
{
<div class="col-sm-3">
<div class="tile-progress tile-blue">
<div class="tile-header">
<a href="#Url.Action("Client_Details", "Client", new { id =item.ClientId})">
<h3>#item.Name <i class="entypo-right-open-big"></i> <span class="badge badge-secondary pull-right">7</span></h3>
</a>
</div>
<div class="tile-progressbar">
<span data-fill="78%" style="width: 78%;"></span>
</div>
<div class="tile-footer">
<h4>
<span class="pct-counter">78</span>% increase
</h4>
<span>#item.Description</span>
</div>
<div class="tile-header">
<a href="#Url.Action("Edit_Client", "Client", new { id = item.ClientId})" type="button" class="btn btn-blue btn-icon icon-left">
<i class="entypo-pencil"></i> Edit
</a>
#*Show Me*#
#Html.ActionLink("Delete", "Delete_Client", new { id = item.ClientId })
#*Delete <i class="entypo-cancel"></i>*#
</div>
</div>
</div>
}
</div>

try with
#Html.ActionLink("Delete", "Delete_Client", null, new { id = item.ClientId })
and you must delete the [HttpPost], this is a GET request

Use the following code:
#Html.ActionLink("Delete", "Delete_Client", new { id = item.ClientId })
[HttpPost]
public ActionResult Delete_Client(int id)
{
Client deleteClient = repository.DeleteClient(id);
if (deleteClient != null)
{
TempData["message"] = string.Format("{0} was deleted.", deleteClient.Name);
}
return RedirectToAction("Admin_Client_List");
}

Change your code
#Html.ActionLink("Delete", "Delete_Client", new { item.ClientId })
to
#Html.ActionLink("Delete", "Delete_Client", new { id = item.ClientId })

Related

How can I bind form fields to a nested model on post?

I am coding a solution where the user will submit a form, posting the values back to my ASP.NET MVC controller. My model is complex and the form fields are contained in a nested object (I'm using CQRS via MediatR). When I submit the form, the values come across as null. How can I get the complex model to recognize the form fields?
Here is my code:
Controller:
[HttpPost]
[Route("edit")]
public async Task<IActionResult> Edit(UpdateApplicationCommand command)
{
await _mediator.Send(command)
.ConfigureAwait(false);
return RedirectToAction("Index");
}
Models:
public class UpdateApplicationCommand : IRequest<Unit>
{
public ApplicationEditGeneralViewModel ApplicationEditGeneralViewModel { get; set; } = null!;
}
public class ApplicationEditGeneralViewModel
{
[Required]
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
}
View:
#model ApplicationEditGeneralViewModel
<form method="post" asp-action="Edit" asp-controller="Applications">
<div class="form-floating mb-3">
#Html.TextBoxFor(m => m.Name, new { #class = "form-control", placeholder = "Application Name"})
<label for="Name">Application Name</label>
</div>
<div class="form-floating mb-3">
#Html.TextBoxFor(m => m.Description, new { #class = "form-control", placeholder = "Application Description"})
<label for="Description">Application Description</label>
</div>
<div class="d-flex flex-row-reverse bd-highlight">
<input type="submit" value="Submit" class="btn btn-primary mt-2" />
</div>
</form>
I've tried to reduce the complex model to its fields, by placing the contents of the ApplicationEditGeneralViewModel directly into the UpdateApplicationCommand class. This worked, but I'd really like to keep the nested structure so that I can reuse the ApplicationEditGeneralViewModel object.
I saw this solution here:
How to bind nested model in partial view
But I'd rather avoid adding the name as a route object (if possible) for every form field. Is there another, more simple way that I can do this?
The first way, you can custom model binding like below:
public class CustomModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var model = new UpdateApplicationCommand()
{
ApplicationEditGeneralViewModel = new ApplicationEditGeneralViewModel()
{
Description = bindingContext.ValueProvider.GetValue("Description").ToString(),
Name = bindingContext.ValueProvider.GetValue("Name").ToString()
}
};
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
Apply the custom model binding like below:
[HttpPost]
public async Task<IActionResult> Edit([ModelBinder(typeof(CustomModelBinder))]UpdateApplicationCommand model)
{
//.....
}
The second way, just change your razor view like below:
#model UpdateApplicationCommand
<form method="post">
<div class="form-floating mb-3">
#Html.TextBoxFor(m => m.ApplicationEditGeneralViewModel.Name, new { #class = "form-control", placeholder = "Application Name"})
<label for="Name">Application Name</label>
</div>
<div class="form-floating mb-3">
#Html.TextBoxFor(m => m.ApplicationEditGeneralViewModel.Description, new { #class = "form-control", placeholder = "Application Description"})
<label for="Description">Application Description</label>
</div>
<div class="d-flex flex-row-reverse bd-highlight">
<input type="submit" value="Submit" class="btn btn-primary mt-2" />
</div>
</form>

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

Cannot add new role in MVC5 Microsoft.AspNet.Identity.EntityFramework.IdentityRole?

I add 3 roles using my MVC application before. Now I cant add new role. When I debug I can see new role Id but the Role name is empty. How can I solve this problem?
I have 3 roles at the moment. User, Admin, Sales. Now I want to add Account role and cannot add.
CONTROLLER
// POST: /Roles/Create
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
context.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
{
Name = collection["RoleName"]
});
context.SaveChanges();
ViewBag.ResultMessage = "Role created successfully !";
return RedirectToAction("Index");
}
catch
{
return View();
}
}
CSHTML
#model Microsoft.AspNet.Identity.EntityFramework.IdentityRole
<div class="container body-content">
#{
ViewBag.Title = "Create";
}
<h2>Create Role</h2>
#Html.ActionLink("List Roles", "Index") | #Html.ActionLink("Manage User Role", "ManageUserRoles")
<hr />
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<p>
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
</p>
<br />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</div>
}
</div>
You should only use viewModels in your view, but as you are using your view and object now, you should adjust your controller the following to use mvc roleManager (much easier):
// POST: /Roles/Create
[HttpPost]
public ActionResult Create(IdentityRole role)
{
try
{
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
roleManager.Create(role)
context.SaveChanges();
ViewBag.ResultMessage = "Role created successfully !";
return RedirectToAction("Index");
}
catch
{
return View();
}
}

Usung ASP.NET MongoDB.Driver and I can't save ContextType

From the course: Using MongoDB with ASP.NET MVC
Demo: Displaying Rental Images
^In case you have Pluralsight
At this point in the course we had attached the images to a Rental document, next we use some razor and a GetImage Action to display an image on a AttachImage.cshtml view. I believe all of that works, the image is getting attached to the document in the database.
Q: When we save the image to the database, why is the ContentType not getting added to the fs.files collection (GridFS) in the database?
NOTE: I believe the code inside the controller that is culprit is at or around:
//------------------------------------------
var options = new MongoGridFSCreateOptions
{
Id = imageId,
ContentType = file.ContentType
};
//------------------------------------------
Proof the image got stored using GridFS
Proof ContentType Didn't get saved
AttachImage.cshtml
#model RealEstate.Rentals.Rental
#{ ViewBag.Title = "AttachImage"; }
<h4>Attach Rental Image</h4>
#using (Html.BeginForm(null, null, FormMethod.Post, new {enctype =
"multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<div class="form-group">
<label class="control-label col-md-2">Decription</label>
<div class="col-md-10">
#Model.Description
</div>
</div>
<div class="form-group">
#Html.Label("Image", new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="file" name="file" id="file" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Attach" class="btn btn-default" />
</div>
</div>
</div>
}
#if (Model.HasImage())
//Show Image
{
<img src="#Url.Action("GetImage", new { id = Model.ImageId })" />
}
AttachImage Page in Browser
RentalsController.cs
namespace RealEstate.Controllers
{
public class RentalsController : Controller
{
public readonly RealEstateContext Context = new RealEstateContext();
public ActionResult Post()
{
return View();
}
[HttpPost]
public ActionResult Post(PostRental postRental)
{
var rental = new Rental(postRental);
Context.Rentals.Insert(rental);
return RedirectToAction("Index");
}
public ActionResult AttachImage(string id)
{
var rental = GetRental(id);
return View(rental);
}
[HttpPost]
public ActionResult AttachImage(string id, HttpPostedFileBase file)
{
var rental = GetRental(id);
if (rental.HasImage())
{
DeleteImage(rental);
}
StoreImage(file, rental);
return RedirectToAction("Index");
}
public ActionResult GetImage(string id)
{
var image = Context.Database.GridFS
.FindOneById(new ObjectId(id));
if (image == null)
{
return HttpNotFound();
}
return File(image.OpenRead(), image.ContentType);
}
private Rental GetRental(string id)
{
var rental = Context.Rentals.FindOneById(new ObjectId(id));
return rental;
}
private void DeleteImage(Rental rental)
{
//Access GrdFS & Delete By ID / Pass ImageID converted to ObjectId
Context.Database.GridFS.DeleteById(new ObjectId(rental.ImageId));
rental.ImageId = null;
Context.Rentals.Save(rental);
}
private void StoreImage(HttpPostedFileBase file, Rental rental)
{
var imageId = ObjectId.GenerateNewId();
rental.ImageId = imageId.ToString();
Context.Rentals.Save(rental);
var options = new MongoGridFSCreateOptions
{
Id = imageId,
ContentType = file.ContentType
};
Context.Database.GridFS.Upload(file.InputStream, file.FileName);
}
I don't know what else to do check, everything not only looks right from my perspective, but it's to the tee (As far as I can tell) from the Course Instruction..
pass the MongoGridFSCreateOptions options to the call to Upload as the last argument:
Context.Database.GridFS.Upload(file.InputStream, file.FileName, options);
Thankfully that's an easy enough fix :)

mvc4 ef5 return points to folder not controller

I have 3 sequential date fields: Start, End, Certify. When the user indicates he wants to update a date in a given row, I actionlink to the controller for that table, Task, where I have added code to determine which field is null and then direct to a view customized to that field. My concept was that the return would go to the scaffold generated edit where the data would be saved. So 3 views with a common return.
I'm getting a 404 error. Since I know the name is present, it must be unavailable.
The scaffold generated post code:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Task task)
{
if (ModelState.IsValid)
{
db.Entry(task).State = EntityState.Modified;
db.SaveChanges();
}
And my selection code:
public ActionResult EditBegin(int? id)
{
Task ViewModel = db.Tasks.Find(id);
{
if (ViewModel.SStart == null)
{
ViewModel.TaskID = id.Value;
ViewModel.SStart = DateTime.Now;
return View("EditStart", ViewModel);
}
else if (ViewModel.SEnd == null)
{
ViewModel.TaskID = id.Value;
ViewModel.SEnd = DateTime.Now;
return View("EditEnd", ViewModel);
}
else if (ViewModel.SCert == null)
{
ViewModel.TaskID = id.Value;
ViewModel.SCert = DateTime.Now;
return View("EditCert", ViewModel);
}
return View("EditCert", ViewModel); //solves not all paths have return error
}
}
And in the EditEnd view the EditorFor and Actionlink.
#Html.EditorFor(model => model.SEnd) // preloaded with NOW
#Html.ActionLink("Save End Date", "Edit", "Task" ) //is the TaskID passed back as part of this?
So in the EditEnd view, press the "Save end date" button and I get the 404 error. I've tested the Task edit function to confirm "the resource is available" and it works fine.
I've discovered the path is to a folder .../task/edit not the controller.
How do I get it to reference the controller. Removing the quotes doesn't help.
Thanks
My entire view is:
#model MVCBSV.Models.Task
#{
ViewBag.Title = "Scanedit";
}
<h2>Add Start Date</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
Html.BeginForm("Edit", "Task", FormMethod.Post);
<fieldset>
<legend>Task</legend>
#Html.HiddenFor(model => model.TaskID)
<div class="editor-label">
Step Name
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.StepName);
</div>
<div class="editor-label">
#Html.LabelFor(model => model.SStart)
</div>
<div class="editor-field">
#Html.EditorFor( model => model.SStart)
#Html.ValidationMessageFor(model => model)
</div>
#* <p>
<input type="submit" value="Save" />
</p>*#
</fieldset>
}
<div>
#Html.ActionLink("Save Start Date", "Edit", "Task" )
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
You edit method is decorated as an HTTP Post method. An anchor tag will provide a link which your browser uses to make an HTTP GET request. You can change from attribute to HttpGet and this will work.
However, it's better practice to actually POST a form to your save method.