My problem with the following is how do I send the ModelStateErrors to the action Employee, when I go through the catch part in DeleteEmployee
public ActionResult Employee(int ID, string Name)
{
EmployeeListModel model = new EmployeeListModel (ID, projectName);
return View(model);
}
public ActionResult DeleteEmployee(Employee emp)
{
try
{
emp.Delete();
return RedirectToAction("Employee", new { ID = emp.ID, Name = emp.Name });
}
catch (Exception e)
{
EmployeeListModel model = new EmployeeListModel (emp.ID, emp.Name);
ModelState.AddModelError("Error", e.Message);
return RedirectToAction("Employee", model);
}
}
With return View("Employee", model); I a still not able to send the ID and Name as parameter.
Use TempData to persist the ModelState across multiple controller actions.
MvcContrib has an action filter that does this. Jeremy Skinner wrote the code and a blog post about it at http://www.jeremyskinner.co.uk/2008/10/18/storing-modelstate-in-tempdata-with-aspnet-mvc/. The link there for the source is broken, so I posted the code below.
ModelStateToTempDataAttribute Source Code
/// <summary>
/// When a RedirectToRouteResult is returned from an action, anything in the ViewData.ModelState dictionary will be copied into TempData.
/// When a ViewResultBase is returned from an action, any ModelState entries that were previously copied to TempData will be copied back to the ModelState dictionary.
/// </summary>
public class ModelStateToTempDataAttribute : ActionFilterAttribute
{
public const string TempDataKey = "__MvcContrib_ValidationFailures__";
/// <summary>
/// When a RedirectToRouteResult is returned from an action, anything in the ViewData.ModelState dictionary will be copied into TempData.
/// When a ViewResultBase is returned from an action, any ModelState entries that were previously copied to TempData will be copied back to the ModelState dictionary.
/// </summary>
/// <param name="filterContext"></param>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var modelState = filterContext.Controller.ViewData.ModelState;
var controller = filterContext.Controller;
if(filterContext.Result is ViewResultBase)
{
//If there are failures in tempdata, copy them to the modelstate
CopyTempDataToModelState(controller.ViewData.ModelState, controller.TempData);
return;
}
//If we're redirecting and there are errors, put them in tempdata instead (so they can later be copied back to modelstate)
if((filterContext.Result is RedirectToRouteResult || filterContext.Result is RedirectResult) && !modelState.IsValid)
{
CopyModelStateToTempData(controller.ViewData.ModelState, controller.TempData);
}
}
private void CopyTempDataToModelState(ModelStateDictionary modelState, TempDataDictionary tempData)
{
if(!tempData.ContainsKey(TempDataKey)) return;
var fromTempData = tempData[TempDataKey] as ModelStateDictionary;
if(fromTempData == null) return;
foreach(var pair in fromTempData)
{
if (modelState.ContainsKey(pair.Key))
{
modelState[pair.Key].Value = pair.Value.Value;
foreach(var error in pair.Value.Errors)
{
modelState[pair.Key].Errors.Add(error);
}
}
else
{
modelState.Add(pair.Key, pair.Value);
}
}
}
private static void CopyModelStateToTempData(ModelStateDictionary modelState, TempDataDictionary tempData)
{
tempData[TempDataKey] = modelState;
}
}
Here are some similar posts
ASP.NET MVC - How to Preserve ModelState Errors Across RedirectToAction?
How can I maintain ModelState with RedirectToAction?
From the view, the model state dictionary is a part of the ViewDataDictionary, so try accessing it through viewdata, at least in MVC 3 (believe that way in older versions as well). You don't need to pass it through the model, but instead access it this way.
However, i don't know if model state errors are retained if you do a redirect; you may want to return the response directly instead:
return View("Employee", model);
HTH.
The values for ID and Name are being sent wrapped up in the EmployeeListModel you pass to the "View" ActionResult:
Model.ID
Model.Name
Note, #Brian is correct in that you'll need to use the "View" ActionResult rather than "RedirectToAction" ActionResult otherwise the modelstate errors would be lost. The alternative is to store your modelstate in TempData which is really a special wrapper around the session object. You would need to use something like the "RedirectToAction" ActionResult if you need to ensure your url's are updated properly ...
Store the ModelState in TempData:
TempData["ModelState"] = ModelState;
Then merge the ModelState using an action filter for example:
protected override void OnActionExecuted(ActionExecutedContext context)
{
filterContext.Controller.ViewData.ModelState.Merge((ModelStateDictionary)TempData["ModelState"]);
}
Hope this helps.
Try below code
//as a best practise always use Viewmodel e.g. employeeViewMode and strongly type your view to viewmodel
public class EmployeviewModel
{
public int id;
public string name;
public string errormessage;
}
public ActionResult Employee(int ID)
{
EmployeeListModel model = new EmployeeListModel(ID, projectName);
EmployeviewModel vm = new EmployeviewModel();
vm.id = model.id;
vm.name = model.name;
if (TempData.Keys.Count() > 0)
{
vm.errormessage = TempData["errormessage"];
}
return View(vm);
}
public ActionResult DeleteEmployee(Employee emp)
{
try
{
emp.Delete();
return RedirectToAction("Employee", new { ID = emp.ID, Name = emp.Name });
}
catch (Exception e)
{
//use TempData to store error message
TempData["ErrorMessage"] = e.message;
return RedirectToAction("Employee", emp.ID);
}
}
Related
I want to know, there is any technique so we can pass Model as a parameter in RedirectToAction
For Example:
public class Student{
public int Id{get;set;}
public string Name{get;set;}
}
Controller
public class StudentController : Controller
{
public ActionResult FillStudent()
{
return View();
}
[HttpPost]
public ActionResult FillStudent(Student student1)
{
return RedirectToAction("GetStudent","Student",new{student=student1});
}
public ActionResult GetStudent(Student student)
{
return View();
}
}
My Question - Can I pass student model in RedirectToAction?
Using TempData
Represents a set of data that persists only from one request to the
next
[HttpPost]
public ActionResult FillStudent(Student student1)
{
TempData["student"]= new Student();
return RedirectToAction("GetStudent","Student");
}
[HttpGet]
public ActionResult GetStudent(Student passedStd)
{
Student std=(Student)TempData["student"];
return View();
}
Alternative way
Pass the data using Query string
return RedirectToAction("GetStudent","Student", new {Name="John", Class="clsz"});
This will generate a GET Request like Student/GetStudent?Name=John & Class=clsz
Ensure the method you want to redirect to is decorated with [HttpGet] as
the above RedirectToAction will issue GET Request with http status
code 302 Found (common way of performing url redirect)
Just call the action no need for redirect to action or the new keyword for model.
[HttpPost]
public ActionResult FillStudent(Student student1)
{
return GetStudent(student1); //this will also work
}
public ActionResult GetStudent(Student student)
{
return View(student);
}
Yes you can pass the model that you have shown using
return RedirectToAction("GetStudent", "Student", student1 );
assuming student1 is an instance of Student
which will generate the following url (assuming your using the default routes and the value of student1 are ID=4 and Name="Amit")
.../Student/GetStudent/4?Name=Amit
Internally the RedirectToAction() method builds a RouteValueDictionary by using the .ToString() value of each property in the model. However, binding will only work if all the properties in the model are simple properties and it fails if any properties are complex objects or collections because the method does not use recursion. If for example, Student contained a property List<string> Subjects, then that property would result in a query string value of
....&Subjects=System.Collections.Generic.List'1[System.String]
and binding would fail and that property would be null
[HttpPost]
public async Task<ActionResult> Capture(string imageData)
{
if (imageData.Length > 0)
{
var imageBytes = Convert.FromBase64String(imageData);
using (var stream = new MemoryStream(imageBytes))
{
var result = (JsonResult)await IdentifyFace(stream);
var serializer = new JavaScriptSerializer();
var faceRecon = serializer.Deserialize<FaceIdentity>(serializer.Serialize(result.Data));
if (faceRecon.Success) return RedirectToAction("Index", "Auth", new { param = serializer.Serialize(result.Data) });
}
}
return Json(new { success = false, responseText = "Der opstod en fejl - Intet billede, manglede data." }, JsonRequestBehavior.AllowGet);
}
// GET: Auth
[HttpGet]
public ActionResult Index(string param)
{
var serializer = new JavaScriptSerializer();
var faceRecon = serializer.Deserialize<FaceIdentity>(param);
return View(faceRecon);
}
[NonAction]
private ActionResult CRUD(someModel entity)
{
try
{
//you business logic here
return View(entity);
}
catch (Exception exp)
{
ModelState.AddModelError("", exp.InnerException.Message);
Response.StatusCode = 350;
return someerrohandilingactionresult(entity, actionType);
}
//Retrun appropriate message or redirect to proper action
return RedirectToAction("Index");
}
i did find something like this, helps get rid of hardcoded tempdata tags
public class AccountController : Controller
{
[HttpGet]
public ActionResult Index(IndexPresentationModel model)
{
return View(model);
}
[HttpPost]
public ActionResult Save(SaveUpdateModel model)
{
// save the information
var presentationModel = new IndexPresentationModel();
presentationModel.Message = model.Message;
return this.RedirectToAction(c => c.Index(presentationModel));
}
}
I want to know, there is any technique so we can pass Model as a parameter in RedirectToAction
For Example:
public class Student{
public int Id{get;set;}
public string Name{get;set;}
}
Controller
public class StudentController : Controller
{
public ActionResult FillStudent()
{
return View();
}
[HttpPost]
public ActionResult FillStudent(Student student1)
{
return RedirectToAction("GetStudent","Student",new{student=student1});
}
public ActionResult GetStudent(Student student)
{
return View();
}
}
My Question - Can I pass student model in RedirectToAction?
Using TempData
Represents a set of data that persists only from one request to the
next
[HttpPost]
public ActionResult FillStudent(Student student1)
{
TempData["student"]= new Student();
return RedirectToAction("GetStudent","Student");
}
[HttpGet]
public ActionResult GetStudent(Student passedStd)
{
Student std=(Student)TempData["student"];
return View();
}
Alternative way
Pass the data using Query string
return RedirectToAction("GetStudent","Student", new {Name="John", Class="clsz"});
This will generate a GET Request like Student/GetStudent?Name=John & Class=clsz
Ensure the method you want to redirect to is decorated with [HttpGet] as
the above RedirectToAction will issue GET Request with http status
code 302 Found (common way of performing url redirect)
Just call the action no need for redirect to action or the new keyword for model.
[HttpPost]
public ActionResult FillStudent(Student student1)
{
return GetStudent(student1); //this will also work
}
public ActionResult GetStudent(Student student)
{
return View(student);
}
Yes you can pass the model that you have shown using
return RedirectToAction("GetStudent", "Student", student1 );
assuming student1 is an instance of Student
which will generate the following url (assuming your using the default routes and the value of student1 are ID=4 and Name="Amit")
.../Student/GetStudent/4?Name=Amit
Internally the RedirectToAction() method builds a RouteValueDictionary by using the .ToString() value of each property in the model. However, binding will only work if all the properties in the model are simple properties and it fails if any properties are complex objects or collections because the method does not use recursion. If for example, Student contained a property List<string> Subjects, then that property would result in a query string value of
....&Subjects=System.Collections.Generic.List'1[System.String]
and binding would fail and that property would be null
[HttpPost]
public async Task<ActionResult> Capture(string imageData)
{
if (imageData.Length > 0)
{
var imageBytes = Convert.FromBase64String(imageData);
using (var stream = new MemoryStream(imageBytes))
{
var result = (JsonResult)await IdentifyFace(stream);
var serializer = new JavaScriptSerializer();
var faceRecon = serializer.Deserialize<FaceIdentity>(serializer.Serialize(result.Data));
if (faceRecon.Success) return RedirectToAction("Index", "Auth", new { param = serializer.Serialize(result.Data) });
}
}
return Json(new { success = false, responseText = "Der opstod en fejl - Intet billede, manglede data." }, JsonRequestBehavior.AllowGet);
}
// GET: Auth
[HttpGet]
public ActionResult Index(string param)
{
var serializer = new JavaScriptSerializer();
var faceRecon = serializer.Deserialize<FaceIdentity>(param);
return View(faceRecon);
}
[NonAction]
private ActionResult CRUD(someModel entity)
{
try
{
//you business logic here
return View(entity);
}
catch (Exception exp)
{
ModelState.AddModelError("", exp.InnerException.Message);
Response.StatusCode = 350;
return someerrohandilingactionresult(entity, actionType);
}
//Retrun appropriate message or redirect to proper action
return RedirectToAction("Index");
}
i did find something like this, helps get rid of hardcoded tempdata tags
public class AccountController : Controller
{
[HttpGet]
public ActionResult Index(IndexPresentationModel model)
{
return View(model);
}
[HttpPost]
public ActionResult Save(SaveUpdateModel model)
{
// save the information
var presentationModel = new IndexPresentationModel();
presentationModel.Message = model.Message;
return this.RedirectToAction(c => c.Index(presentationModel));
}
}
Let's imagine I have the following action
public ViewResult Products(string color)
{...}
and route which maps url "products" to this action.
According to SEO tips link /products?color=red should return
200 OK
But link /products?someOtherParametr=someValue
404 Not found
So the question is - how to handle unexisting query parameters and return 404 in this case
Considering the accepted answer at What is the proper way to send an HTTP 404 response from an ASP.NET MVC action?, there is a special ActionResult that could fulfill your expectation.
public class HomeController : Controller
{
public ViewResult Products(string color)
{
if (color != "something") // it can be replaced with any guarded clause(s)
return new HttpNotFoundResult("The color does not exist.");
...
}
}
Update:
public class HomeController : Controller
{
public ViewResult Products(string color)
{
if (Request.QueryString.Count != 1 ||
Request.QueryString.GetKey(0) != "color")
return new HttpNotFoundResult("the error message");
...
}
}
Validate this before executing the Action Method. This approach is valid for all Controller of you project or specific Action method.
Controller Action Methods
[attr] // Before executing any Action Method, Action Filter will execute to
//check for Valid Query Strings.
public class ActionResultTypesController : Controller
{
[HttpGet]
public ActionResult Index(int Param = 0)
{
return View();
}
[HttpPost]
public ActionResult Index(MyViewModel obj)
{
return View(obj);
}
}
Action Filter
public class attr : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionParameters.Count == 0 &&
System.Web.HttpContext.Current.Request.QueryString.Count > 0)
{
//When no Action Parameter exists and Query String exists.
}
else
{
// Check the Query String Key name and compare it with the Action
// Parameter name
foreach (var item in System.Web.HttpContext
.Current
.Request.QueryString.Keys)
{
if (!filterContext.ActionParameters.Keys.Contains(item))
{
// When the Query String is not matching with the Action
// Parameter
}
}
}
base.OnActionExecuting(filterContext);
}
}
If you pay attention to the above code, we are checking the Action parameter as shown in the screen show below.
What can we do in case the QueryString passed does not exists in the Action Method Parameter? We can redirect the user to another page as shown in this link.
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{"action", "ActionName"},
{"controller", "ControllerName"},
{"area", "Area Name"},
{"Parameter Name","Parameter Value"}
});
Or
We can do like this. The below mentioned code will be written in the OnActionExecuting Method Override
filterContext.Result = new HttpStatusCodeResult(404);
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var actionParameters = filterContext.ActionParameters.Keys;
var queryParameters = filterContext
.RequestContext
.HttpContext
.Request
.QueryString
.Keys;
// if we pass to action any query string parameter which doesn't
// exists in action we should return 404 status code
if(queryParameters.Cast<object>().Any(queryParameter
=> !actionParameters.Contains(queryParameter)))
filterContext.Result = new HttpStatusCodeResult(404);
}
Actually, if you don't want to write this is in every controller, you should override DefaultControllerFactory
This works in Asp.Net Core.
In Controller ActionMethods use below one liner code:
return StatusCode(404, "Not a valid request.");
In OnActionExecuting method, set StatusCode to context.Result as in below sample:
public override void OnActionExecuting(ActionExecutingContext context)
{
string color = HttpContext.Request?.Query["color"].ToString();
if (string.IsNullOrWhiteSpace(color))
{
// invalid or no color param, return 404 status code
context.Result = StatusCode(200, "Not a valid request.");
}
else
{
// write logic for any common functionality
}
base.OnActionExecuting(context);
}
On POST , if validation failed and before sending back the ViewModel to the same View with Model State errors, do you rebuild ViewModel for all SelectLists, ReadOnly fields etc?
right now I have separate methods for Fill First Time(for GET Edit-Method) / Rebuild ViewModels from domain objects, what is the best practice so I can be DRY and also not have to change two methods any time I add a new readonly property to ViewModel?
My Solution: Followed this Pattern
Followed pattern suggested here: https://stackoverflow.com/a/2775656/57132
In IModelBuilder Implementation
Build(..)
{
var viewModel = new ViewModel();
// and Fill all Non-ReadOnly fields
...
...
call CompleteViewModel(viewModel)
}
CompleteViewModel(ViewModel viewModel)
{
//Fill all ReadOnly & SelectLists
...
}
The reason I went with this solution is because I don't want to store stuff on server to retrieve across the HTTP Requests
I don't rebuild it, because I don't stay at POST. I follow POST-REDIRECT-GET pattern, so if I post to /User/Edit/1 using POST HTTP method, I get redirected to /User/Edit/1 uasing GET.
ModelState is transferred to TempData to follow Post-Redirect-Get and be availabe at GET call. View model is built in one place, at GET call. Example:
[HttpPost]
[ExportModelStateToTempData]
public ActionResult Edit(int id, SomeVM postedModel)
{
if (ModelState.IsValid) {
//do something with postedModel and then go back to list
return RedirectToAction(ControllerActions.List);
}
//return back to edit, because there was an error
return RedirectToAction(ControllerActions.Edit, new { id });
}
[ImportModelStateFromTempData]
public ActionResult Edit(int id)
{
var model = //create model here
return View(ControllerActions.Edit, model);
}
This is code for attributes importing/exporting ModelState:
public abstract class ModelStateTempDataTransferAttribute : ActionFilterAttribute
{
protected static readonly string Key = typeof(ModelStateTempDataTransferAttribute).FullName;
}
public class ExportModelStateToTempDataAttribute : ModelStateTempDataTransferAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Only export when ModelState is not valid
if (!filterContext.Controller.ViewData.ModelState.IsValid)
{
//Export if we are redirecting
if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
{
filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
}
}
base.OnActionExecuted(filterContext);
}
}
public class ImportModelStateFromTempDataAttribute : ModelStateTempDataTransferAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;
if (modelState != null)
{
//Only Import if we are viewing
if (filterContext.Result is ViewResult)
{
filterContext.Controller.ViewData.ModelState.Merge(modelState);
}
else
{
//Otherwise remove it.
filterContext.Controller.TempData.Remove(Key);
}
}
base.OnActionExecuted(filterContext);
}
}
The simplest solution would be to pass in you viewModel to the method and account for null
private MyViewModel BuildViewModel(MyViewModel model = null)
{
model = model ?? new MyViewModel();
model.ReadOnlyList = new .....
.
.
return model;
}
for Create:
var model = BuildViewModel();
for rebuild:
model = buildViewModel(model);
I like #LukLed's answer above - it looks very interesting. If you want another option, here's what I currently do.
In my service layer, I have a method to build my view model. I call that on GET and return the the view model to the view. On POST, I build the model from the incoming ID and then TryUpdateModel(model). From there, you can do whatever you like (save, check model state, etc.). With this method, you only have 1 build method and only have to update it once if your model changes (i.e. add/remove properties in the future, etc.).
[HttpGet]
public ActionResult AssessFocuses(int apaID)
{
var model = this.apaService.BuildAssessFocusesViewModel(apaID);
return this.View(model);
}
[HttpPost]
public ActionResult AssessFocuses(int apaID, string button)
{
var model = this.apaService.BuildAssessFocusesViewModel(apaID);
this.TryUpdateModel(model);
switch (button)
{
case ButtonSubmitValues.Back:
case ButtonSubmitValues.Next:
case ButtonSubmitValues.Save:
case ButtonSubmitValues.SaveAndClose:
{
try
{
this.apaService.SaveFocusResults(model);
}
catch (ModelStateException<AssessFocusesViewModel> mse)
{
mse.ApplyTo(this.ModelState);
}
if (!this.ModelState.IsValid)
{
this.ShowErrorMessage(Resources.ErrorMsg_WEB_ValidationSummaryTitle);
return this.View(model);
}
break;
}
default:
throw new InvalidOperationException(string.Format(Resources.ErrorMsg_WEB_InvalidButton, button));
}
switch (button)
{
case ButtonSubmitValues.Back:
return this.RedirectToActionFor<APAController>(c => c.EnterRecommendationsPartner(model.ApaID));
case ButtonSubmitValues.Next:
return this.RedirectToActionFor<APAController>(c => c.AssessCompetenciesPartner(model.ApaID));
case ButtonSubmitValues.Save:
this.ShowSuccessMessage(Resources.Msg_WEB_NotifyBarSuccessGeneral);
return this.RedirectToActionFor<APAController>(c => c.AssessFocuses(model.ApaID));
case ButtonSubmitValues.SaveAndClose:
default:
return this.RedirectToActionFor<UtilityController>(c => c.CloseWindow());
}
}
I have a ProductController with actions Index (which Loads a blank form). The form also posts to itself as its a complex form and the form elements like dropdowns show posted values
the code is as follows
public ActionResult Index()
{
int id;
id = Convert.ToInt32(Request.Form["ddlLendingType"]);
if (id == 0)
id = 1;
ProductCommonViewModel viewData = new ProductCommonViewModel(_prodRepository.Method1(),_prodRepository.Method2())
return View(viewData);
}
When I click submit from the form, it saves the product and if it fails it should show the validation error messages.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Save(FormCollection fc)
{
Product product = new Product();
try
{
...fill out all properties from form collection
_prodRepository.SaveProduct(product);
return RedirectToAction("Index", "Product");
}
catch (Exception ex)
{
TempData["Message"] = "An Error Occured while saving the product!";
Validation.UpdateModelStateWithRuleViolation(product, ViewData.ModelState);
// WHEN I call redirect to action Index on this view I can see the TempData variable but I cannot see validation summary and individual validation messages.How do I persist the msgs across requests?
}
}
The helper method definition is as follows:
public static void UpdateModelStateWithRuleViolation(IRuleEntity entity, ModelStateDictionary dictModel)
{
List<RuleViolation> violations = entity.GetRuleViolations();
foreach (var item in violations)
{
dictModel.AddModelError(item.PropertyName, item.ErrorMessage);
}
}
Pass modelstate into tempdata too.
Btw, instead of this:
public ActionResult Index()
{
int id; //and here You could join declaration with assignment
id = Convert.ToInt32(Request.Form["ddlLendingType"]);
You can do this:
public ActionResult Index(int ddlLendingType)
{
And using FormCollection is a bad practice which should not be used. For extreme cases - create custom model binder (CodeCampServer has quite nice binding mechanism) or action filter (Kigg`s source).
I had a problem with preserving TempData across multiple requests, I did the following, to refresh the TempData for every redirect action:
protected override RedirectToRouteResult RedirectToAction(string actionName,
string controllerName, System.Web.Routing.RouteValueDictionary routeValues)
{
TempData["Notice"] = TempData["Notice"];
TempData["Error"] = TempData["Error"];
TempData["Warning"] = TempData["Warning"];
return base.RedirectToAction(actionName, controllerName, routeValues);
}