MVC CORE RedirectToAction() not calling action - model-view-controller

I have a RedirectToAction statement which is not finding the named action. It will find the Index method in the BundleNodes controller but not the PostNodes method. Where am I going wrong?
return RedirectToAction("PostNodes", "BundleNodes", new { id = bndl_id });
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> PostNodes(BundleNode bidnid, int id)
{
var result = _context.Nodes.Where(r => r.Name == id).ToList();
foreach(var item in result )
{
Console.WriteLine(item.Id);
}
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}

Consider changing Task<IActionResult> PostNodes(int bidnid, int id)
To : Task<IActionResult> PostNodes(int bidnid, int id)
Or: Task<IActionResult> PostNodes([FromBody]BundleNode bidnid, int id)
When using objects in the action names, you must ensure the model (BundleNode) is passed in the http request or it will not bind to your variable.
Redirect:
return RedirectToAction("PostNodes", "BundleNodes", new { bidnid = 1234, id = bndl_id });
Controller:
[ValidateAntiForgeryToken]
public async Task<IActionResult> PostNodes(int bidnid, int id)
{
var result = _context.Nodes.Where(r => r.Name == id).ToList();
foreach(var item in result )
{
Console.WriteLine(item.Id);
}
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}

Seems like the only reliable way to access a Post method in a Controller is to use a <Form></Form> or at least that's the only way I have been able to make it work. Just make sure to reference the controller and the action method.

Related

Object State validation based on Controller Actions in ASP.NET Core MVC

I'm trying to implement a DRY based validation using IValidatableObject in an ASP.NET Core 3.1 MVC application. I'm using both data annotations to validate "format" rules, and Validate(ValidationContext validationContext) method to implement my business rules.
So far, it's working smoothly, now, I have some extra business rules based on Object State, which will control some kind of workflow of my Objects, and that workflow will depend on the controller action to be executed.
I want to avoid adding business rules in my controller and want to centralize any validation within my Model. I'm wondering if there's any standard way to do it, otherwise I'll implement my own mechanism.
For example:
Trying to Delete MyObject with STATUS_1 should give a validation error...
This is MyObject class:
public class MyObject : IValidatableObject
{
public MyObjectState State { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Value{ get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//My custom model validation here
}
public IEnumerable<ValidationResult> ValidateState(ActionExecutedContext actionContext)
{
var controllerActionDescriptor = actionContext.ActionDescriptor as ControllerActionDescriptor;
if (controllerActionDescriptor.ActionName == "Delete" && State != MyObjectState.STATE_1)
yield return new ValidationResult("You cannot delete if STATE_1");
}
}
These may be the possible MyObject statuses:
public enum MyObjectState
{
STATE_1 = 1,
STATE_2 = 2,
STATE_3 = 3
}
This may be MyObject Controller with Delete Action:
public class MyObjectController: Controller
{
private readonly ApplicationDbContext _context;
public MyObjectController(ApplicationDbContext context)
{
_context = context;
}
public IActionResult Index()
{
return View();
}
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var myObject = await _context.MyObject.FindAsync(id);
var validationStateResult = myObject.ValidateState(null);
//Not sure how to provide the action context here
if (!validationStateResult)
{
return RedirectToAction(nameof(Index)).WithWarning("ALERT: ", validationStateResult.ToString());
}
return View(myObject);
}
}
You can use ajax in view,action return errormessage or MyObject to ajax,and ajax will alert errormessage or redirect to delete Page.In controller,you can get actionName to check.Here is a demo worked:
MyObject ValidateState(I change the ActionName to DeleteCheck,and you can use ActionName to check ):
public IEnumerable<ValidationResult> ValidateState(string ActionName)
{
if (ActionName == "DeleteCheck" && State == MyObjectState.STATE_1)
yield return new ValidationResult("You cannot delete if STATE_1");
}
MyObjectsController(You can get actionName with RouteData.Values["action"]):
public async Task<IActionResult> DeleteCheck(int? id)
{
if (id == null)
{
return NotFound();
}
var myObject = await _context.MyObjects.FindAsync(id);
var action =(string)RouteData.Values["action"];
var validationStateResult = myObject.ValidateState(action);
string messages = string.Join("", myObject.ValidateState(action)
.SelectMany(x => x.ErrorMessage));
TempData["Message"] = messages;
if (messages!=null&&messages != "")
{
return Json(messages);
}
return Json(myObject);
}
public IActionResult Delete(MyObject myObject) {
return View(myObject);
}
View:
<a href="#" onclick="delete1(#item.Id)" >Delete</a>
function delete1(id) {
$.ajax({
method: 'get',
url: "MyObjects/DeleteCheck?Id=" + id,
dataType: "json",
success: function (data) {
if (typeof data === 'object') {
window.location.href = "/MyObjects/Delete?Id=" + data.id + "&&State=" + data.state + "&&Price=" + data.price;
//alert("object");
} else {
alert(data)
}
}
});
}
result:

Make IFormFile function to work with model

I have a function in my controller that takes a file sent by an ajax call and saves it to a specified path. I was wondering how I could make this work with a model instead as I am hoping to send more data than just the image.
ViewModel:
public class PhotoViewModel
{
// Other strings
public IFormFile userimage { get; set; }
}
Controller: Without model (working)
[HttpPost]
public async Task<IActionResult> Post(IFormFile file)
{
var filePath = Path.GetFullPath(#"C:\Users\me\documents\" + file.FileName);
if (file.Length > 0)
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
}
return StatusCode(200);
}
Attempt at Version with Model:
[HttpPost]
public async Task<IActionResult> PhotoData(PhotoViewModel model)
{
var filePath = Path.GetFullPath(#"C:\Users\me\documents\" + model.userimage.FileName);
if (ModelState.IsValid)
{
if (model.userimage.Length > 0)
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await model.userimage.CopyToAsync(stream);
}
}
}
return StatusCode(200);
}
This attempt doesn't get me far. By setting a breakpoint I can see it fails at setting the filePath but I'm not sure how to work with the model. Any help is appreciated.
What is the error message you are getting and what is the client side code you are using to send the API request?
You will have to use a FormData object and fill the FormData object with the required properties.
var formData = new FormData();
formData.append(add PhotoViewModel properties)
formData.append(userimage, <file>);

IN MVC6 return Json(rows, JsonRequestBehavior.AllowGet) ISSUE

IN MVC6 return Json(rows, JsonRequestBehavior.AllowGet); method is changed and not allowing to set JsonrequestBehavior. What is alternative in MVC6
That overload of Json method which takes JsonRequestBehavior does not exist in the aspnet core any more.
You can simply call the Json method with the object data you want to send back.
public IActionResult GetJsonData()
{
var rows = new List<string> { "Item 1","Item 2" };
return Json(rows);
}
Or even
public IList<string> GetJsonData()
{
var rows = new List<string> {"aa", "bb" };
return rows;
}
or using Ok method and having IActionResult as the return type.
public IActionResult GetJsonData()
{
var rows = new List<string> { "aa", "bb" };
return Ok(rows);
}
and let the content negotiator return the data in the requested format(via Accept header). The default format used by ASP.NET Core MVC is JSON. So if you are not explicitly requesting another format(ex :application/xml), you will get json response.
Try this
[HttpGet]
public JsonResult List()
{
var settings = new JsonSerializerSettings();
return Json(rows, settings);
}
Try this
public JsonResult GetJsonData()
{
var data= //your list values
return Json(data);
}
JsonRequestBehavior is deprecated from ASP.net core 1. Just use return Json();

How do I retain navigation elements after ModelState.IsValid fails?

I have the following pair of controller methods for Editing.
The initial call is no problem, and correctly displays the model and properties from two of the child/navigation objects (1:1 relationships).
When I try to save, if the model is valid there is no problem.
When it is Invalid though, I get a null reference in my view referencing any of the child/navigation properties - which were correctly there in the original view.
public ActionResult Edit(int id)
{
var reportcustomerlimit = db.ReportCustomerLimits.Single(r => r.Id == id);
return View(reportcustomerlimit);
}
[HttpPost]
public ActionResult Edit(ReportCustomerLimit reportcustomerlimit)
{
if (ModelState.IsValid)
{
db.ReportCustomerLimits.Attach(reportcustomerlimit);
reportcustomerlimit.ReportCustomer.Verified = false;
ReportGenerator.ClearAllReportsZip();
db.ObjectStateManager.ChangeObjectState(reportcustomerlimit, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index", new { id = reportcustomerlimit.CustomerNumber });
}
else
{
//What do I do here?
}
return View(reportcustomerlimit);
}
What am I missing?
(Note: The validation is normally done client side, and stops the form being submitted - but I've turned off javascript to test the server side validation works as well)
Try this code:
[HttpPost]
public ActionResult Edit(ReportCustomerLimit reportcustomerlimit)
{
if (ModelState.IsValid)
{
db.ReportCustomerLimits.Attach(reportcustomerlimit);
reportcustomerlimit.ReportCustomer.Verified = false;
ReportGenerator.ClearAllReportsZip();
db.ObjectStateManager.ChangeObjectState(reportcustomerlimit, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index", new { id = reportcustomerlimit.CustomerNumber });
}
else
{
var reportcustomerlimit = db.ReportCustomerLimits.Single(r => r.Id == id);
return View(reportcustomerlimit);
}
}
Hope it helps.

Returning Multiple partial views from single Controller action?

I need to update Multiple from an Ajax call , I am confused as in how to return these Multiple views from the Controller Action method.
You can only return one value from a function so you can't return multiple partials from one action method.
If you are trying to return two models to one view, create a view model that contains both of the models that you want to send, and make your view's model the new ViewModel.
E.g.
Your view model would look like:
public class ChartAndListViewModel
{
public List<ChartItem> ChartItems {get; set;};
public List<ListItem> ListItems {get; set;};
}
Then your controller action would be:
public ActionResult ChartList()
{
var model = new ChartAndListViewModel();
model.ChartItems = _db.getChartItems();
model.ListItems = _db.getListItems();
return View(model);
}
And finally your view would be:
#model Application.ViewModels.ChartAndListViewModel
<h2>Blah</h2>
#Html.RenderPartial("ChartPartialName", model.ChartItems);
#Html.RenderPartial("ListPartialName", model.ListItems);
There is a very good example here....
http://rhamesconsulting.com/2014/10/27/mvc-updating-multiple-partial-views-from-a-single-ajax-action/
Create a helper method to package up the partial view...
public static string RenderRazorViewToString(ControllerContext controllerContext,
string viewName, object model)
{
controllerContext.Controller.ViewData.Model = model;
using (var stringWriter = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(controllerContext, viewName);
var viewContext = new ViewContext(controllerContext, viewResult.View, controllerContext.Controller.ViewData, controllerContext.Controller.TempData, stringWriter);
viewResult.View.Render(viewContext, stringWriter);
viewResult.ViewEngine.ReleaseView(controllerContext, viewResult.View);
return stringWriter.GetStringBuilder().ToString();
}
}
Create a controller action to bundle the multiple partial views....
[HttpPost]
public JsonResult GetResults(int someExampleInput)
{
MyResultsModel model = CalculateOutputData(someExampleInput);
var totalValuesPartialView = RenderRazorViewToString(this.ControllerContext, "_TotalValues", model.TotalValuesModel);
var summaryValuesPartialView = RenderRazorViewToString(this.ControllerContext, "_SummaryValues", model.SummaryValuesModel);
return Json(new { totalValuesPartialView, summaryValuesPartialView });
}
Each partial view can use its own model if required or can be bundled into the same model as in this example.
Then use an AJAX call to update all the sections in one go:
$('#getResults').on('click', function () {
$.ajax({
type: 'POST',
url: "/MyController/GetResults",
dataType: 'json',
data: {
someExampleInput: 10
},
success: function (result) {
if (result != null) {
$("#totalValuesPartialView").html(result.totalValuesPartialView);
$("#summaryValuesPartialView").html(result.summaryValuesPartialView);
} else {
alert('Error getting data.');
}
},
error: function () {
alert('Error getting data.');
}
});
});
If you want to use this method for a GET request, you need to remove the [HttpPost] decorator and add JsonRequestBehavior.AllowGet to the returned JsonResult:
return Json(new { totalValuesPartialView, summaryValuesPartialView }, JsonRequestBehavior.AllowGet);
Maybe this solution can help you:
http://www.codeproject.com/Tips/712187/Returning-More-Views-in-an-ASP-NET-MVC-Action

Resources