I have this method:
[HttpPost]
public async Task<ActionResult> AddBookingCreditCard(TripViewModel model)
{
if (ModelState.IsValid)
{
// do nothing right now
}
TempData["ccInfo"] = model.CcInfo;
return RedirectToAction("Book", new{ id = model.Trip.TripId, travellers = model.Travellers});
}
In this case, model.CcInfo DOES exist and contains an object of type CcInfo. After the RedirectToAction, this method is called:
[HttpGet]
public async Task<ActionResult> Book(int id, int travellers)
{
var vm = new TripViewModel();
vm.Travellers = travellers;
if (TempData["ccInfo"] != null)
vm.CcInfo = TempData["ccInfo"] as CcInfo;
// lot of code
return View("Index", vm);
}
The if-statement always fails: TempDate["ccInfo"] is null, while it should contain my object.
What am I doing wrong?
EDIT:
The CcInfo class:
public class CcInfo
{
[Display(Name = "Name on Card"), Required]
public string CcName { get; set; }
[Display(Name = "Card Number"), DataType(DataType.CreditCard), Required]
public int CcNumber { get; set; }
[Display(Name = "Card CVV"), Required, Range(100, 999)]
public int CcCheck { get; set; }
}
I've tried to replicate your issue, and I can spot the problem.
This code works fine for me:
public class FooController : Controller
{
public async Task<ActionResult> AddBookingCreditCard()
{
TempData["ccInfo"] = "Hell world";
return RedirectToAction("Book", new { id = 1, travellers = 2 });
}
[HttpGet]
public async Task<ActionResult> Book(int id, int travellers)
{
var vm = new object();
if (TempData["ccInfo"] != null)
{
vm = new object();
}
// lot of code
return new EmptyResult();
}
}
However, if I change AddBookingCreditCard to recieve POST requests only:
[HttpPost]
public async Task<ActionResult> AddBookingCreditCard()
Then the TempData dictionary is empty.
To cut to the chase, you need to use session state for storing items between requests in your case.
I don't recall the exact details, but I think TempData does work well for situations where you need to redirect to another controller action, but only if it's within a GET request. Coming from a POST and then redirecting to a GET seems to wipe TempData to a clean slate, which makes sense, as TempData is for that scenario only I believe (please feel free to correct me on that).
Related
I have POST action in controller like below
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public async Task<IActionResult> SignIn([ModelBinder(typeof(SignInRequestModelBinder))] SignInRequestModel request, string returnUrl = null)
{
if (!ModelState.IsValid)
{
return View();
}
var result = await _accountService.SignInAsync(request);
if (!string.IsNullOrWhiteSpace(returnUrl))
{
return Redirect(returnUrl);
}
return Json(result);
}
I have a request model like this
public class SignInRequestModel
{
[Required(ErrorMessage = "Email is required.")]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember Me")]
public bool RememberMe { get; set; }
}
I post data to Model binder via jQuery AJAX
But I can do that like this
var request = bindingContext.ActionContext.HttpContext.Request;
var model = new SignInRequestModel()
{
Email = request.Form["Email"],
Password = request.Form["Password"],
}
This is hardcoded but how can I do that another way. When post data custom model binder do that instead of writing request.Form[]...
Thank you for helping.
In this scenario, you don't need to create your own binder. The built-in model binder has already done it for you. So the easiest way is to let the ASP.NET Core bind the model automatically:
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public async Task SignIn(
[ModelBinder(typeof(SignInRequestModelBinder))]
[FromForm]SignInRequestModel request, // the [FromForm] is required when you're using an ApiController
string returnUrl = null
)
{
...
}
(Or if you're using a plain Controller (instead of ApiController), you don't even have to use the [FormForm] attribute)
In case you want to bind the form data by yourself, you can use Reflection to inspect all the properties dynamically:
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
var request = bindingContext.ActionContext.HttpContext.Request;
var model = new SignInRequestModel();
var pis = typeof(SignInRequestModel).GetProperties();
foreach(var pi in pis){
var pv = bindingContext.ValueProvider.GetValue(pi.Name); // prop value
pi.SetValue(model,pv.FirstValue);
}
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
This also works. But the first way is preferred because that's a robust way.
I have a post method, the parameter School is always null every time it is being called. I make sure to send the right data across, I am using Postman, but when I inspect it with a breakpoint, I get a null object.
What I send with Postman:
{
"Id": "",
"Name": "Test"
}
Controller:
[HttpPost]
public IActionResult Post([FromBody] School school)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest();
}
var schoolData = Mapper.Map<School, Data.School>(school);
var schoolModel = _schoolRepository.AddSchool(schoolData);
return CreatedAtRoute("GetSchool", new { id = schoolModel }, schoolModel);
}
catch (Exception e)
{
return LogException(e);
}
}
Model:
public class School
{
public Guid Id { get; set; }
[Required(ErrorMessage = "Name is required")]
public string Name { get; set; }
public IEnumerable<Teacher> Teachers { get; set; }
public IEnumerable<Class> Classes { get; set; }
}
The problem is that your model requires a Guid and you are sending an empty string:
"Id": ""
Regardless of the Id not being [Required], the model still cannot parse the data. What you can do, however, is to exclude it from binding:
public IActionResult Post([FromBody][Bind(Include = nameof(School.Name))] School school)
Or, a better option is to use a ViewModel. In this case, however, since you only have one property, I'd just use this:
public IActionResult Post([FromBody]string name)
I'm trying to pass a Complex Object(Object within an object) to my action method using RedirectToAction method but it is returned null. Is there a way to do this using RouteValueDictionary?
My Model:
public class ModelUser
{
public UserLine User { get; set; }
}
public class UserLine
{
public int? UserID { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
My Action method:
[HttpPost]
public async Task<ActionResult> Create(UserCreate model)
{
var valDTORegister = new RegisterLine() { Email = model.Email, Password = model.Password, OwnerType = model.OwnerType };
var result = await PostRequest<RegisterLine, UserLine>("http://localhost:10853/", "api/user/Create", valDTORegister);
var usermodel = new ModelUser();
usermodel.User = result;
return RedirectToAction("ProfileUser", new RouteValueDictionary(usermodel));
}
public ActionResult ProfileUser(ModelUser usermodel) //User object is null
{
return View();
}
I tried passing only UserLine object using RouteValueDictionary and the values has been properly passed to my ProfileUser method. This is good but I want to pass the whole ModelUser object because I may need to add more object within it.
you can connect the ProfileUser page to model ModelUser and than call this view like this:
return View("ProfileUser", usermodel);
the action shold be without parameters
public ActionResult ProfileUser()
{
return View();
}
And all data is in the model ("ModelUser") of ProfileUser
When I am catching a HttpPost, I am redirecting to another ResultAction. It is preserving my int values, but not my list values. Can't seem to figure out why. If I get the post with page number = 2, searchAction = 3 and clearanceResults (a List) with 25 items. It comes back with what I would expect on the post, however when I get to the Details ActionResult, it only preserves pageNumber and searchAction, not the list of clearanceResults. Weird thing, the list isn't null, it just has a count of 0.
Model:
public class ClearanceListViewModel
{
public ClearanceListViewModel()
{
this.pageNumber = 1;
this.searchAction = 1;
this.lastPage = false;
}
public ClearanceListViewModel(int pageNumber, int searchAction)
{
this.pageNumber = pageNumber;
this.searchAction = searchAction;
}
public int pageNumber { get; set; }
public int searchAction { get; set; }
public List<ClearanceViewModel> clearanceResults { get; set; }
public bool lastPage { get; set; }
}
Post in the controller:
[HttpPost]
public ActionResult Details(ClearanceListViewModel model, FormCollection collection)
{
ClearanceListViewModel cModel = new ClearanceListViewModel();
cModel = model;
cModel.clearanceResults = model.clearanceResults;
// do something
return RedirectToAction("Details", cModel);
}
Action Result in the controller:
public ActionResult Details(ClearanceListViewModel model)
{
DataTable dt = new DataTable();
List<ClearanceViewModel> clearanceList = new List<ClearanceViewModel>();
//save any changes
if (model.clearanceResults != null)
{
ClearanceSave(model);
model.clearanceResults = null;
}
string inQuery = "select sku, qty from products";
// call the query
dt = AS400DAL.Instance.ExecuteQueryAsDataTable(inQuery);
model = Utility.Utility.ProcessClearanceSkus(dt, model);
return View("Index",model);
}
Any input would be appreciated.
Thanks!
Study the overloads of RedirectToAction. None of the allow the passing of a model. Normally your post would modify the database, and then you'd redirect to an action that recreates the model from the database. Because redirection is something that occurs at the client, the redirected request is entirely separate from the post that issued the redirect, so the model is not persisted.
Use a session to store the model,
You can do:
Session["mymodel"] = model;
Then after your redirect get the model from the session by doing
ClearanceListViewModel newModel = (ClearanceListViewModel)Session["mymodel"];
This will allow you to pass the model successfully
I am coverting my app from webforms to mvc, at the moment i am at a design issue (well i just dont know how to do it in mvc).
Basically my model would be something like this:
public class DamagedItem
{
public Int32 LoanId {get;set;}
public String IdentityCode {get;set;}
public virtual ICollection<DamagedItems> DamagedItems {get;set;}
}
In my controller i would like to do:
public ActionResult Add(DamagedItem damagedItem)
{
//Do update logic here
}
Then in my view i can add to the ICollection as needed.
But, i can't do this because if i try and access the ICollection from my controller it is null.
Here is an image of when i want to do:
I just dont know how to lay it out in my view, how to i add such items to my ICollection, update the view then when i need to save i have access to what i have added from my controller?
Thanks,
Nick
Edit:
I was thinking of using a partial in the view and doing all the logic for the bottom half using ajax and storing it in a session variable, but i would prefer NOT to make it reliant on ajax.
It is better to separate: you shoud have 2 actions, which produce 2 view.
You should have LoadInformationModel classe:
public class LoadInformationModel
{
public string StudentCode { get; set; }
public string FirstName { get; set; }
// etc..
public ICollection<Damage> Type { get; set; }
}
corresponding action
[HttpGet]
public ActionResult LoanInformation(int id)
{
var loanInfo = // get data by given id..
var model = new LoadInformationModel {
StudentCode = loanInfo.StudentCode,
// etc
Type = new List<Damage> { new Damage { Value = "Damaged"}, new Damage { Value = "Damaged Again" }
}
return View(model);
}
As well as RepairDataModel class
public class RepairDataModel
{
public bool CoveredByWarranty { get; set; }
public ICollection Status { get; set; }
}
And corresponding action
[HttpGet]
public ActionResult Repair(int id)
{
// logic
return View(model);
}
Your task is to create Post handler, that would save data to DB then form submitted
[HttpPost]
public ActionResult(RepairDataModel model)
{
// save to db
return View();
}
The view returned by Index() method, could be created like
#Html.RenderAction("LoanInformation")
#Html.RenderAction("Repair")
The rest depends on your desing and imagination. I hope that would give you direction.
What I can see is only the DamagedItem lacks a contructor with values for Collection;
public class DamagedItem
{
public DamagedItem()
{
DamagedItems = new List<DamagedItems>();
DamagedItems.Add(new DamagedItem { Description = "Damaged" } );
}
public Int32 LoanId {get;set;}
public String IdentityCode {get;set;}
public virtual ICollection<DamagedItems> DamagedItems {get;set;}
}