How do I retain navigation elements after ModelState.IsValid fails? - asp.net-mvc-3

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.

Related

MVC CORE RedirectToAction() not calling action

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.

Controller that returns JSON format not returning to target page

Why is it that my controller can't manage to go to the target Index page after logging in?
[HttpPost]
[ValidateAntiForgeryToken]
public JsonResult Login(FormCollection fc)
{
var isSuccess = false;
Array errors = null;
more code here...
return Json(new { infoMessage = errors, successMessage = "", Status = isSuccess, gotoUrl = string.Format("/MyAccounts/Index") }, JsonRequestBehavior.AllowGet);
}
The motive is to redirect the page to Index but it returns the Json result document instead.
Add return inside if statement
if(isSuccess == true) {
//return redirect to target page
}
else {
return Json(new { infoMessage = errors, successMessage = "", Status = isSuccess, gotoUrl = string.Format("/MyAccounts/Index") }, JsonRequestBehavior.AllowGet);
}
It won't go to any target page because you specified it to return a json. What you want is to redirect to an action.
return RedirectToAction("Index", "MyAccounts");

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:

Pass ViewModel to JSONresult in controller

My application was working but I was asked to change how message are displayed by using the JSONResponseFactory. But when I try that my ViewModel is empty going back to the JSonResult (which used to be ActionResult). I've been told I may have to serialize the form fields. Here are some present code excerpts:
cshtml:
function SubmitForm() {
$.ajaxSetup({ cache: false });
$.post('#Url.Action("StoreLevelPlanning", "StoreLevelPlanning")', { actionType: "Submit"})
.done(function (data) {
if (data.Success == true) {
CreateInformationMessage(data.Message, 'window.location.href = "#Url.Action("storemanagement", "AccountListing")";');
}
else {
CreateErrorMessage(data.Message);
}
});
}
Controller:
public JsonResult StoreLevelPlanning_Post(string actionType) // actionType is Save or Submit
{
// message to return to the view on success
string successMessage = "";
var model = new VM_StoreLevelPlanning();
TryUpdateModel(model);
try
{
if (ModelState.IsValid)
{
model.buttonPressed = actionType;
repo.UpdateCLPCategoryAndRemark(model);
//Render different message depending on ActionType
if (actionType == "Save")
{
successMessage = "Your plan was saved. You will now be directed to the Listing Screen";
}
else if (actionType == "Submit")
{
successMessage = "Your plan has been submitted. You will now be directed to the Listing Screen.";
}
else
{
//return RedirectToAction("storemanagement", "AccountListing");
// need to revisit to figure out if this can be removed
throw new Exception("Else case happened");
}
}
else
{
if (actionType == "Save")
{
// TODO : change this to throw an error so that the ErrorLog class is utilized
throw new Exception("Your plan was not saved. Please retry.");
}
else
{
// TODO : change this to throw an error so that the ErrorLog class is utilized
throw new Exception("Your plan was not submitted. Please retry.");
}
}
}
catch (Exception e)
{
return Json(JsonResponseFactory.ErrorResponse(e.Message));
}
return Json(JsonResponseFactory.SuccessResponse(successMessage));
}
I'm open to any suggestions since I'm new to MVC. The idea is to put out a successful save message and redirect the user to the Listing page. But he way I changed the code now the view model is empty. It does not use a form collection. The data is in a list in the view model.
Thank you in advance...

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