So I am trying to populate a model, which needs a userId. So right now, my ActionResult Index method just returns a view which prompts for the user to enter their userId. I need to grab this value and then create the viewmodel and then pass it in back to the view so that way I can do stuff like #Model.blah. I was wondering how I can do that, whether it'd be having two different action result methods, or in general how to populate models when the information you need must be queried first before constructing the viewmodel.
Here is my controller:
public ActionResult Index()
{
// Ask for UserID
return View("~/Views/FingerprintTool/Index.cshtml");
}
public ActionResult Index(int userId)
{
var response = _driver.ListFingerprints(userId);
var model = new FingerprintToolModel()
{
Fingerprints = response.Fingerprints
};
return View(model);
}
And here is my html:
model Models.Tools.FingerprintToolModel
<head>
<script type="text/javascript" src="~/Scripts/FingerprintTool.js"></script>
</head>
<body>
<h1>Fingerprint Tool</h1>
<form id="userIdForm" method="post">
Type in your UserId: <input name="userId" type="number" id="formUserId"/>
<input type="submit"/>
</form>
#if (Model != null)
{
<h1>SUCCESS</h1>
}
</body>
I also have a Javascript file that deals with the submit button be clicked and whatnot.
Here is the Js:
window.onload = function() {
$("#userIdForm")
.submit(function(e) {
e.preventDefault();
if ($("#formUserId").length == 0) {
alert("Invalid UserId");
} else {
listUserFingerprints();
}
});
}
function listUserFingerprints() {
// what to do here
}
Form:
First, update your form. You can use a simple HTML form, or the #Html.BeginForm() helper, like so:
#using Html.BeginForm()
{
#Html.AntiForgeryToken()
<label for="userId">Enter your User ID:</label>
<input type="number" name="userId" id="userId" />
<input type="submit" />
}
By default, your Html.BeginForm creates all the necessary elements and form action etc. If you prefer, you can still use standard HTML. Both do the same job.
Note the AntiForgeryToken. This is so useful when using POST form data (and even get, if you really must).
In your Controller, you can then check against this AntiForgeryToken, to protect against malicious posts or data being injected - see below.
Controller:
Create another method in your Controller, with the same name as your existing one; decorate it with the [HttpPost] attribute.
If you are using an AntiForgeryToken, you need to decorate the method with [ValidateAntiForgeryToken] also.
Like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(int userId)
{
// MVC's DefaultModelBinder is smart enough to map your form
// post values to objects of the correct type, given the name in the form
// Get the data from your repository etc.
var model = GetUser(userId);
// Then return this model to the view:
return View(model);
}
Notice the parameter we are looking for in the method signature matches the name attribute of the input in your form.
MVC's DefaultModelBinder is able to make the connection between the two and map the value(s) of any parameters to form values.
You can also check if your model is null (for example, that userId doesn't exist) and then return an error to the page if so.
I like to use validation errors, but you can also use ViewBag or any other kind of method.
You can do a check and add an error, like this:
// Get the data from your repository etc.
var model = GetUser(userId);
if (model == null)
{
ModelState.AddModelError("", "The user ID you entered cannot be found. Please try again");
}
// Then return this model to the view:
return View(model);
This will add a "generic" model error to the view data, which you can then process in the view. More on that, below.
View:
In order to support your view displaying your model, you need to insert an #model statement at the top of your cshtml file.
Like so:
#model MyNameSpace.Models.User
This tells the view engine what Model type to expect from the Controller. In this case I have used User, but it would be whatever your class is called.
Be sure to use the fully-qualified namespace of your class in order to access it.
Then, in your HTML code, you can access the properties of your model using #Model.YourProperty.
Like this:
...
<div>#Model.Username</div>
<div>#Model.FullName</div>
<ul>
#foreach (var fingerPrint in Model.FingerPrints){
<li>#fingerPrint.WhateverProperty</li>
}
</ul>
...
As you can see, this loops through the FingerPrints (or whatever the property is called on your model object) and prints them out in a <ul>. This is to give you an idea of how to access the data from your model.
It is a good idea to create strongly-typed views like this - as this is the WHOLE idea of MVC in the first place :)
Don't forget to add an if check around the part of the page you're access the #Model data (otherwise you will get a NullReferenceException):
#if (Model != null){
<div id="modelDataInHere">
... // Display the data from your model, nice and pretty-like
</div>
}
Note the casing difference between the declaration and printing the values to the HTML output. I won't go into detail, but this is correct and necessary. Don't mix the two up.
Over to that "Validation Error" (AddModelError) we added in the Controller.
If you're using the Html.BeginForm() helper - like in the example - then you can add a Summary in the form, somewhere. When ModelState.AddModelError() is called in your controller, this populates the view data and this error can be displayed on the page.
Like so:
#using Html.BeginForm()
{
#Html.AntiForgeryToken()
// This puts any "generic" error messages at the top of the form
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
// Original form fields
<label for="userId">Enter your User ID:</label>
<input type="number" name="userId" id="userId" />
<input type="submit" />
}
No need for any Ajax or any JavaScript in here. You're simply using MVC for what it was designed in the first place.
You can use Ajax, however.
I would encourage you to read into it. There is also a similar helper: Ajax.BeginForm(), which renders the form as an Ajax one.
A bit of JS set up is required. And your controller action could return a PartialView, rather than a full-blown one.
Have a read up on using Ajax form posts here: http://eliot-jones.com/2014/09/mvc-ajax
The finished article
Time is marching on, and this post is getting longer.
But for the sake of clarity, your view and controller should look something like below. (I've stuck in a made-up class, so you can see the where the properties come from):
View
#model YourNameSpace.Models.User
... all your other code in here...
<div>
#using Html.BeginForm()
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<label for="userId">Enter your User ID:</label>
<input type="number" name="userId" id="userId" />
<input type="submit" />
}
</div>
#if (Model != null)
{
<!-- If there is a model present, display the data for it: -->
<div>
<div>#Model.Username</div>
<div>#Model.FullName</div>
<ul>
#foreach (var fingerPrint in Model.FingerPrints)
{
<li>#fingerPrint.WhateverProperty</li>
}
</ul>
</div>
}
Controller
public ActionResult Index()
{
// This is your standard "GET" request for "Index"
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(int userId)
{
// MVC's DefaultModelBinder is smart enough to map your form
// post values to objects of the correct type, given the name in the form
// Get the data from your repository etc.
var model = GetUser(userId);
if (model == null)
{
ModelState.AddModelError("", "The user ID you entered cannot be found. Please try again");
}
// Then return this model to the view:
return View(model);
}
Sample model
public class User
{
public string Username { get; set; }
public string FullName { get; set; }
public List<FingerPrint> FingerPrints { get; set; }
}
Sincerely hope this helps you, and I wish you the best in your project.
Any questions, please feel free to ask :)
Seems like you need to work with json and AJAX method:
[HttpPost]
public ActionResult BasePage(int userId)
{
// user ID is binded to userId variable, based on the input name
var model = populate(userId);
// Remember to return the model to the view
return Json(model);
}
The Javascript for calling this would be:
function listUserFingerprints() {
$.post("/FingerprintTool/BasePage", {userId: $("#formUserId").val() }, function(model) {
console.log(model); // do whatever you want with model here
}
}
Related
im trying to pass model via parameter with html.beginform to an action but it dont work, debuging the model appears null, dont understand why,
my code>
view
#model IEnumerable<GestorBd.Models.Receptor>
#{
ViewBag.Title = "Index";
}
<h2>Receptores</h2>
<br />
Mostrando #Model.Count() elemento(s).
<br />
<br />
#using (Html.BeginForm("ConvertThisPageToPdf", "Receptors", FormMethod.Post, htmlAttributes: new { parame = Model }))
{
<input type="submit" value="Convert
This Page To PDF" />
}
my controller action
[HttpPost]
public ActionResult ConvertThisPageToPdf(IEnumerable<Receptor> parame)
{
// get the HTML code of this view
string htmlToConvert = RenderViewAsString("Index", parame);
// the base URL to resolve relative images and css
String thisPageUrl = this.ControllerContext.HttpContext.Request.Url.AbsoluteUri;
/// and go on
}
this code create html tag (form) you can write input with name attr like this
<input type="text" name="your-name" id="your-name" />
into form scop
Why you don't use a hidden input. Like this:
<input type="hidden" name="receptors" value="#Model" />
this input will not be visible to your users but it will send to the controller action.
you can get the input value like this :
[HttpPost]
public ActionResult ConvertThisPageToPdf(IEnumerable<Receptor> receptors)
{
//...
}
or you can get the value using HttpContext.Request.Form:
var receptors = HttpContext.Request.Form["receptors"];
but there is one more thing, why you are using BeginForm since you can use the html form tag and asp.net tag helpers, if you want to use the BrginForm for some reasons you can add a hidden input like this:
#Html.HiddenFor(m => m)
make sure that the arguments (action name and controller name) you'r passing to the BeginForm is correct.
There are a few issues with your code. Firstly, I believe you are trying to send a complex model to a POST method via the query string rather than within the request body. To pass parameters via the query string you would need to add them via the RouteValueDictionary rather than the HTML attributes object. The HTML attributes object is used to add attributes, such as an id, to the actual form element.
However, I believe you would be much better passing the object in the request body. To do this you would need to add form HTML elements, for each receptor object, to pass the model properties to the server. For example:
#using (Html.BeginForm("ConvertThisPageToPdf", "Receptors", FormMethod.Post, htmlAttributes: new { id = "receptorsForm" }))
{
<!-- Add the properties of your receptor objects -->
#for (int i = 0; i < Model.Count(); i++)
{
var receptor = Model.ElementAt(i);
<!-- Note the index #i denotes that you are sending an array of items -->
<input type="hidden" name="receptors[#i].Id" value="#receptor.Id" />
<input type="hidden" name="receptors[#i].Name" value="#receptor.Name" />
<input type="hidden" name="receptors[#i].Value" value="#receptor.Value" />
}
<input type="submit" value="Convert This Page To PDF" />
}
Assuming a C# model of:
public class Receptor
{
public int Id { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
Note that the form element name must match the controller action parameter name for the correct model binding to happen. To correct this you would need to change the method signature to accept an enumerable of receptors:
[HttpPost]
public ActionResult ConvertThisPageToPdf(IEnumerable<Receptor> receptors)
{
//...
}
I've not tested this code but hopefully it should get you pretty close to what you need.
I am new to ASP.NET MVC. I have a simple form with a submit button and an html select element, populated with two items. When the form posts to my controller, all the form values are null. I've even tried doing it with $.POST instead and the id variable I am sending is null when it gets to the controller. Here is my code:
HTML
#using (Html.BeginForm("SetOptionForUser", "MyController", FormMethod.Post, new { #class="form-inline" }))
{
#Html.AntiForgeryToken()
<div class="text-center">
<div class="form-group">
<select id="ddlSelect" class="form-control">
#foreach (var item in Model.Persons)
{
<option value="#item.Id">#item.Name</option>
}
</select>
</div>
<button type="submit" class="btn btn-primary" id="btnEnter">Go</button>
</div>
}
MVC Controller
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult SetOptionForUser(FormCollection form)
{
string option = form["ddlSelect"].ToString(); //null error
return RedirectToAction("AnotherAction", "AnotherController");
}
It seems nothing in the form is being sent. I also tried this:
JS
$("#btnEnter").click(function (e) {
var optionId = $("#ddlSelect").val(); //this get val correctly
$.post(#Url.Action("SetOptionForUser", "MyController"), optionId);
});
MVC Controller for JS method
**MVC Controller**
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult SetOptionForUser(int optionId) //null
{
string option = optionId.ToString(); //null error
return RedirectToAction("AnotherAction", "AnotherController");
}
What I am doing wrong?
Normal form submit
Your normal form submit should work if your select element name and your http post action method parameter name is same.
<select name="selectedPerson" id="selectedPerson" class="form-control">
<!-- Options goes here -->
</select>
and your action method
[HttpPost]
public ActionResult SetOptionForUser(string selectedPerson)
{
string option = selectedPerson;
return RedirectToAction("AnotherAction", "AnotherController");
}
You might also consider using the Html.DropDownListFor or Html.DropDownList helper methods to generate the SELECT element from a list of items ( instead of manually writing the loop to render the option items).
Sending data with Ajax
Now if you want this to be ajaxified, you can send the data in either querystring or the request body
$(function(){
$("#btnEnter").click(function (e) {
e.preventDefault();
var optionId = $("#selectedPerson").val();
$.post('#Url.Action("SetOptionForUser", "MyController")', { selectedPerson:optionId});
});
});
Now there is no point in returning a RedirectResult for an ajax call. You might consider returning a json response (indicating whether your action was successful or not) from your action methodo and in the success callback of $.post you can inspect the value and do needed.
[HttpPost]
public ActionResult SetOptionForUser(string selectedPerson)
{
string option = selectedPerson;
return Json(new {status="success"});
}
And you can check the response in the callback
var optionId = $("#selectedPerson").val();
var url="#Url.Action("SetOptionForUser", "MyController")";
$.post(url, { selectedPerson:optionId}, function(response){
if(response.status==="success")
{
alert("Success");
}
else
{
alert("Some trouble!");
}
});
The only thing that I see missing in your code is the name property of your dropdown.
<select id="ddlSelect" name="ddlSelect" class="form-control">
#foreach (var item in Model.Persons)
{
<option value="#item.Id">#item.Name</option>
}
</select>
for your first example your using FormCollection
string option = form["ddlSelect"].ToString();
but your DropDown doesn't have a name property and form collection is using the name property as reference that is why you are getting a null value.
same goes with your second example reference the DropDown name property instead of ID in your Parameter
public ActionResult SetOptionForUser(int myDropDownName) //null
{
string option = myDropDownName.ToString(); //null error
return RedirectToAction("AnotherAction", "AnotherController");
}
The FormCollection in ASP.NET MVC is tied to the name attribute of input elements, add the name attribute to your select HTML element, like this:
<select id="ddlSelect" name="ddlSelect" class="form-control">
Note: The id attribute is how to find things in the DOM and the name attribute is for what gets included/submitted in the form
As for the JavaScript jQuery AJAX POST issue, I believe the issue is that jQuery is making an intelligent guess that your optionId is text. You should create an object literal to hold the JSON data and then stringify that value to send to the controller, like this:
var dataForServer = {
optionId: optionId
};
$.post(#Url.Action("SetOptionForUser", "MyController"),
JSON.stringify(optionId),
null,
json);
The FormCollection in ASP.NET MVC is getting to the name attribute of input elements
so add this attribute name="ddlSelect"
MattoMK,
Please post code for your Model.
Typically, a model will hold all the data required for your webform to run and your post to send the data back. In your view, you should be selecting to a Model field. You seem to have Model.Persons, which is likely an enumerable of a class describing people.
MODEL
You did not describe your model, so a fake one is shown with just two properties, a string to hold the list selection and your list of Persons. Hopefully, your HttpGet Action populates your model correctly.
public class MyModel
{
public string ddSelect { get ; set; }
public List<Person> Persons { get ; set; }
}
HTML (often called the View)
We explicitly tell the world that this view uses this Model.
#model MyModel // Tell MVC what model we are using for this view
#using (Html.BeginForm("SetOptionForUser", "MyController", FormMethod.Post, new { #class="form-inline" }))
{
#Html.AntiForgeryToken()
<div class="text-center">
<div class="form-group">
#Html.DropDownListFor(m => m.ddSelect, Model.PersonList, new {id = "ddlSelect"})
</div>
<button type="submit" class="btn btn-primary" id="btnEnter">Go</button>
</div>
}
MVC Controller
Your controller is expecting your view to return a model of type MyModel and thus will try to push the form variables into a copy of your model (if it can). Since your form does not fill out a model property, you get nulls back.
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult SetOptionForUser(MyModel theModel, FormsCollection collection)
{
var selectedName = theModel.ddSelect;
return RedirectToAction("AnotherAction", "AnotherController");
}
This should return the value you selected from the dropdown list. Pay attention to the generated HTML from your site (Press F12 in browser and view source) to see how MVC does things. It's easier to play along than to fight it. In the above POST Action, see the included parameter collection... That holds all of the form variables. It can be gone through to find non model values if required. It is not necessary and can be removed, but poking around in that collection affords a nice view of how data is structured upon POST.
Hope this Helps.
I have a view where set of Images relating to certain album will be edited to change their description and to make them cover photo.
EditImageViewModel.cs
public class EditImageViewModel
{
public int ImageId{get;set;}
public string ImageUrl{get;set;}
public string ImageDes{get;set;}
public bool IsCoverPic{get;set;}
}
From one of the controller ActionResult, I return model to view as
return PartialView("_ImageEditView",model);.
model returned above is List<EditImageViewModel>
Now in the view I display it as below:
_ImageEditView.cshtml
#model IEnumerable<EditImageViewModel>
#using(Html.BeginForm("UpdateImage","Controller",FormMethod.Post))
{
#foreach(var image in Model)
{
<img src="#image.ImageUrl"/>
#Html.TextAreaFor(m=>image.ImageDes)
#Html.RadioButtonFor(m=>image.IsCoverPic)
}
<button type="submit" class="update" value="Update"></button>
}
I have an ajax piece of code which calls ActionResult as below:
$('.update').on('click',function(e){
e.preventDefault();
var url=$(this).closest('form').attr('action');
var formdata=$(this).closest('form').serialize();
$.ajax({
url:url,
data:formdata,
type:'POST',
dataType:'JSON',
success:function(resp){
},
error:function(resp){
}
})
});
My Controller ActionResult goes like this:
[HttpPost]
public ActionResult UpdateImage(List<EditImageViewModel> model)
{
//actions to be performed
}
My problem here is no matter what, the model will always be null in the controller, when the post occurs. The formdata with have the data when checked in browser console. But it isn't get passed to controller method.
After going through few posts I learnt that it creates duplicate ids for multiple records in List when foreach is used. So I changed it to for loop as below:
#model IEnumerable<EditImageViewModel>
#using(Html.BeginForm("UpdateImage","Controller",FormMethod.Post))
{
#for(int i=0;i<Model.Count();i++)
{
<img src="#Model[i].ImageUrl"/>
#Html.TextAreaFor(m=>m[i].ImageDes)
#Html.RadioButtonFor(m=>m[i].IsCoverPic)
}
<button type="submit" class="update" value="Update"></button>
}
But still the model is null when received in controller. I also tried using serializeArray instead of serialize, but it did not help much. I referred few posts like Post 1, Post 2 etc., but none of them solved this problem.
Ultimately, how can I pass this list of Model from ajax to
controller?
If you want to post a collection to the controller, you will need to add indexes to your name. Otherwise MVC does not know how to bind the model.
For example
#model IEnumerable<EditImageViewModel>
#using(Html.BeginForm("UpdateImage","Controller",FormMethod.Post))
{
#for(int i=0;i<Model.Count();i++)
{
<img src="#Model[i].ImageUrl"/>
<textarea name="#string.Format("ImageDes[{0}]", i)">#Model[i].imageDes</textarea>
<input type="radio" name="#string.Format("IsCoverPic[{0}]", i)" value="#Model[i].IsCoverPic" >
}
<button type="submit" class="update" value="Update"></button>
}
I am fairly new to MVC and had a question about a form I am creating. The page has a form at the top and a grid at the bottom. As people enter data into the form and click the button, the form data is added to the grid below.
My plan is to use a BeginForm and send the form to an HttpPost controller method for processing and then bounce back to the view. Currently, I am using this for the form on the view:
#using (Html.BeginForm("AddRefund", "Refund", FormMethod.Post))
In the controller, I have this:
[HttpPost]
public ActionResult AddRefund(RefundModel refund)
{
if (ModelState.IsValid)
{
(etc...)
My problem is that the "refund" object in controller always arrives from the view empty. From my research, it seems that the model reference in the controller is just there to provide model structure, and NOT to receive the actual model from the view. I don't understand why this is, however, as it would seem very valuable to be able to send a populated viewmodel from the view to a controller.
Also, how would you guys handle the code for this problem? How would you collect all of these form submissions from the user, present them to the user in the grid below the form, and then ultimately submit the page and insert all of the items in the grid into the database?
edit: here is my view
#model RefundsProject.Models.RefundModel
#using (Html.BeginForm("AddRefund", "Refund", FormMethod.Post))
{
(all of the form elements are here)
<input id="button-add" type="submit" value=" Add Refund to List " />
}
Eventually, there will be another button at the very bottom of the view that will submit all of the items the user entered into the grid to the database.
From my research, it seems that the model reference in the controller is just there to provide model structure, and NOT to receive the actual model from the view.
This is completely the opposite of the way ASP.Net MVC was designed. ASP.Net comes with default ModelBinders that are used to Bind data from a Form, Querystring, Ajax (Json and XML) to a strongly typed object for a Controller Method.
My problem is that the "refund" object in controller always arrives from the view empty.
This is most likely due to a lack of knowledge or a misunderstand of how model binders work.
Also, how would you guys handle the code for this problem?
I would Ajax Post the RefundModel back to the controller to validate the refund. If it is valid, then dynamically create fields in the form that will eventually model bind back to an IEnumerable/List on a new method that will then verify all the refunds, one at a time (to validate the data again).
Here is an Extremely broken down example (probably needs some work, but the important parts are there):
Classes:
public class AddRefundsViewModel
{
public RefundModel Refund { get; set; }
}
public class RefundModel
{
public string Reason { get; set; }
public Decimal Amount { get; set; }
}
Methods:
public ActionResult AddRefunds()
{
var model = new AddRefundsViewModel()
model.Refund = new RefundModel();
return this.View(model);
}
[HttpPost]
public ActionResult ValidateRefund(AddRefundsViewModel model)
{
var result = new { isValid = modelState.IsValid };
return this.Json(result);
}
[HttpPost]
public ActionResult ValidateRefunds(IEnumerable<RefundModel> model)
{
var isRefundsValid = true;
foreach (var refund in model)
{
isRefundsValid = TryValidateModel(refund);
if (!isRefundsValid )
break;
}
if (isRefundsValid)
{
}
else
{
// either someone hacked the form or
// logic for refunds changed.
}
}
Views:
#model AddRefundsViewModel
// assuming RefundController
#using (Html.BeginForm("Refund", "ValidateRefunds", FormMethod.Post))
{
#html.EditFor(m => m.Refund.Reason)
#html.EditFor(m => m.Refund.Amount)
<input type="button" id="addRefundButton" name="addRefundButton" value="add"/>
<input type="submit" id="submitRefundButton" name="submitRefundButton" value="submit all"/>
}
<!-- jquery -->
$(document).ready(function()
{
$('#addRefundButton').on('click', function()
{
$.ajax({
url: '/Refund/ValidateRefund',
data: $("addRefundForm").serialize(),
success: function(result)
{
if (result.isValid)
{
// create new hidden imput elements, and grid
$("addRefundForm")[0].reset();
}
else
{
// Refund isn't valid
}
}
});
});
});
From my research, it seems that the model reference in the controller is just there to provide model structure, and NOT to receive the actual model from the view. I don't understand why this is, however, as it would seem very valuable to be able to send a populated viewmodel from the view to a controller.
Your a bit wrong. There is a difference between ViewModel and Domain Model. View Model is a class that you use to process the logic between views and your domain (business).
Then there is Domain Model (in .net) this is usually some data container objects (POCO). This is anemic. Based on DDD there is a little difference.
So what is the best practive?
It is always good to use a ViewModel object to transfer data between your views and controller.
Then in controller you can use a mapper (automapper or valueinjecter) to transform them.
Now you have your domain object that you can process.
Using ViewModels to pass data both up and down between controllers and views is completely acceptable.
To help with your model coming up empty issue, inputs, such as <input id="FirstName" type="text" /> need to have name attributes for the MVC model binder to map what you posted into your RefundModel object. In your View code you shared, you only showed a submit button, so it is unclear if your other elements you expect to get mapped have names or not.
To fix my above example of an input tag, you would do <input id="FirstName" name="FirstName" type="text" /> or use a Razor helper: #Html.TextBoxFor(m => m.FirstName)
Essentially want I'm trying to do is authenticate a user by having them enter their account and their social security number. If they enter an incorrect combination, I do the following on the Authenticate post action:
ModelState.AddModelError("Authenticated", authenticationError);
return View();
This displays the error, but then I lose what was in my query string. An alternative to keep the query string is:
ModelState.AddModelError("Authenticated", authenticationError);
return Redirect(Request.Url + "?returnUrl=" + returnUrl);
This will keep the query string, but the error will not display. I assume this is because the ModelState has changed.
I need the returnUrl because the user is forced to the Authenticate page whenever they click to view a specific event. I want to set it up so that they still go to this event once they authenticate themselves.
Is there a way I can achieve both the preservation of the query string and display the model error?
Ivan Korytin's answer was the best (and only answer I could find which seemed to actually work properly without using hidden field hacks) which I've improved a little with Request.QueryString.
You have to put the parameters as part of the form action:
<form action="#Url.Action("CreateEntity", "Employee")?#(Request.QueryString)"
enctype="multipart/form-data" method="POST">
When you perform the following the query string (and GET parameters) are now preserved:
[HttpPost]
public ActionResult MyAction(MyAction model)
{
if (!ModelState.IsValid)
{
return View(model);
}
Your second scenario doesn't have the model state because when you do a redirection the browser makes a separate request to that location, separate requests = new model state.
I would suggest using your first scenario and place a "ReturnUrl" in your model and render it to the client as a hidden field.
//In your model add the ReturnUrl Property
public class AuthenticatModel
{
public string Account {get; set;}
public string SocialSecurityNumber {get;set;}
public string ReturnUrl {get;set;}
}
ModelState.AddModelError("Authenticated", authenticationError);
//Set the return URL property before returning the view
model.ReturnUrl = returnUrl;
return View(model);
#* add the return URL as a hidden field to your view so it can be posted back *#
#Html.HiddenFor(model => model.ReturnUrl)
For ASP.NET Core
You can use asp-route-* attribute:
<form asp-action="Login" asp-route-returnUrl="#Model.ReturnUrl">
Other in details example:
Imagine that you have a Vehicle Controller with actions
Index
Details
Edit
and you can edit any vehicle from Index or from Details, so if you clicked edit from index you must return to index after edit
and if you clicked edit from details you must return to details after edit.
//In your viewmodel add the ReturnUrl Property
public class VehicleViewModel
{
..............
..............
public string ReturnUrl {get;set;}
}
Details.cshtml
<a asp-action="Edit" asp-route-previous="Details" asp-route-id="#Model.CarId">Edit</a>
Index.cshtml
<a asp-action="Edit" asp-route-previous="Index" asp-route-id="#item.CarId">Edit</a>
Edit.cshtml
<form asp-action="Edit" asp-route-previous="#Model.ReturnUrl" class="form-horizontal">
<div class="box-footer">
<a asp-action="#Model.ReturnUrl" class="btn btn-default">Back to List</a>
<button type="submit" value="Save" class="btn btn-warning pull-right">Save</button>
</div>
</form>
In your controller:
// GET: Vehicle/Edit/5
public ActionResult Edit(int id,string previous)
{
var model = this.UnitOfWork.CarsRepository.GetAllByCarId(id).FirstOrDefault();
var viewModel = this.Mapper.Map<VehicleViewModel>(model);//if you using automapper
//or by this code if you are not use automapper
var viewModel = new VehicleViewModel();
if (!string.IsNullOrWhiteSpace(previous)
viewModel.ReturnUrl = previous;
else
viewModel.ReturnUrl = "Index";
return View(viewModel);
}
[HttpPost]
public IActionResult Edit(VehicleViewModel model, string previous)
{
if (!string.IsNullOrWhiteSpace(previous))
model.ReturnUrl = previous;
else
model.ReturnUrl = "Index";
.............
.............
return RedirectToAction(model.ReturnUrl);
}