ViewModel becomes null when passed between actions - c#

I'm having trouble passing a viewmodel into a view. I have two views: a Search view and a GeneralForm view. Search passes search results into GeneralForm's viewmodel.
Say the GeneralForm is a complex viewmodel that holds two other viewmodels:
public class GeneralFormViewModel
{
public GeneralInfoViewModel GeneralInfo { get; set; }
public NotesViewModel Notes { get; set; }
}
public class GeneralInfoViewModel
{
[Required(ErrorMessage = "Please enter the person's name.")]
[DisplayName("Name:")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter the person's ID.")]
[DisplayName("ID:")]
public int ID { get; set; }
}
public class NotesViewModel
{ // etc.
(I set up this way in order to use multiple #Html.BeginForms on my GeneralForm view. In this way, I hope to POST and validate small sections of the entire general form, one at a time, using KnockoutJS and AJAX.)
[HttpPost]
public ActionResult Search(SearchViewModel vm)
{
var query = // do some searching
var viewmodel = new GeneralFormViewModel()
{
GeneralInfo = new GeneralInformationViewModel
{
ID = query.id,
Name = query.name
}
};
return RedirectToAction("GeneralForm", viewmodel);
}
At this point, viewmodel.GeneralInfo is not null, and the viewmodel is passed to the GeneralForm controller.
[HttpGet]
public ActionResult GeneralForm(GeneralFormViewModel model)
{
return View(model);
}
Now model.GeneralInfo is null. What conventions of MVC am I breaking by doing this, and how can I get the GeneralForm view to render the data acquired via the Search controller to the GeneralForm view?

Problem is You can't send data with a RedirectAction.
you're doing a 301 redirection and that goes back to the client.
Store it in a TempData or Session ...
See the following post:
passing model and parameter with RedirectToAction

Related

How to pass data from different controllers

I am trying to pass a value which is stored in one controller to another, code is below:
Charities Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Donate([Bind(Include = "ID,DisplayName,Date,Amount,Comment")] Charity charity)
{
if (ModelState.IsValid)
{
if (!string.IsNullOrEmpty(charity.Comment))
{
var comment = charity.Comment.ToLower().Replace("hot", "###").Replace("cold", "###").Replace("Slow", "###").Replace("enjoy", "###").Replace("BAD", "###");
charity.Comment = comment; //Replaces textx from model variable - comment
charity.TaxBonus = 0.20 * charity.Amount;
}
if (string.IsNullOrEmpty(charity.DisplayName))
{
charity.DisplayName = "Annonymus"; //If user doesnt enter name then Annonymus
}
db.Donations.Add(charity);
db.SaveChanges();
TempData["Name"] = charity.DisplayName;
TempData["Amount"] = charity.Amount;
TempData["Comment"] = charity.Comment;
return RedirectToAction("../Payments/Payment", "Charities", new { id = charity.Amount });
}
return View(charity);
}
Charities Class
public class Charity
{
public int ID { get; set; }
[RegularExpression(#"^[a-zA-Z]+$", ErrorMessage = "Use letters only please")]
public string DisplayName { get; set; }
[DataType(DataType.Currency)]
[Range(2, Int32.MaxValue, ErrorMessage = "Atleast £2.00 or a whole number please")]
public int Amount { get; set; }
[DataType(DataType.Currency)]
public Double TaxBonus { get; set; }
public String Comment { get; set; }
public static object Information { get; internal set; }
}
Payment Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Payment([Bind(Include = "ID,CardName,CardNumber,ValidFrom,Expires,CardSecurityCode,EmailAddress,ConfrimEmailAddress,Address,City,Country,PostCode")] Payment payment)
{
if (ModelState.IsValid)
{
db.Payments.Add(payment);
db.SaveChanges();
TempData["Name"] = charity.DisplayName
TempData["Amount"];
TempData["Comment"];
TempData["Name"] = payment.CardName;
TempData["Email"] = payment.EmailAddress;
return RedirectToAction("Confirmation", "Payments", new { id = payment.ID });
}
return View(payment);
}
Payment Class
public class Payment
{
public int ID { get; set; }
}
public class CharityDBContext : DbContext //controls information in database
{
public DbSet<Charity> Donations { get; set; } //creates a donation database
}
public class PaymentDBContext : DbContext //controls information in database
{
public DbSet<Payment> Payments { get; set; } //creates a donation database
public System.Data.Entity.DbSet<CharitySite.Models.Charity> Charities { get; set; }
}
}
I am trying to get this from the Charities Controller
TempData["Name"] = charity.DisplayName;
To display in Payment controller
TempData["Name"] = charity.DisplayName;
Right now theres a squigly red line under "charity" in the payment controller with the message - doesnt exist in current context. I just wanted to know if it is possible to pass data from different controllers using temp data.
First of all, the line return RedirectToAction in your Donate method is going to send a 302 response to your browser which will issue a GET request to the url in the location header of the response, which in this case is Payment/Payment. But your Payment method is marked with HttpPost. Are you sure you want to send a second GET request to a method marked with HttpPost to save some part of the data(Payment) you want to save ?
I think you should save your charity and payment info in the same action method( Create a PaymentCharity view model and use that instead of using the Bind attribute and the entity classes created by EF to transfer data from your view to action method). Also, insteaof using TempData to pass data, What you should do is, get the unique id of the Payment record you saved, pass that in querystring to the second action method and in that using the unique payment id,read the payment record again and use that.
So in your Donate method,
public ActionResult Donate(PaymentCharirtVm model)
{
var charity = new Charity { DisplayName =model.Amount,Comment =model.Comment};
var payment = new Payment ();
//set the properties of payment here
db.Donations.Add(charity);
db.SaveChanges();
//now save Payment
db.Payment.Add(payment);
db.SaveChanges();
return RedirectToAction("Confirmation","Payment", new { id=payment.Id });
}
I have never tried to do it with tempdata. Have never needed to. I recomend you add the string with the charity name to the view model used by the payment controller.
Please correct code as below to retrieve value from TempData.
charity.DisplayName= TempData["Name"]
As already answered here, most answer are correct. It would be better to use routeValues in the RedirectToAction method to pass value from one action to another action in the same controller or different controller.
return RedirectToAction("actionName", "anotherControllerName", new { id = charity.DisplayName });
In the another controller, data can be retrieved by
string displayName = RouteData.Values["id"].ToString();
Here, in the routeValues, you can pass the whole Charity object as well and do some casting in the another controller to get the object properly in the that case when you need more than one properties to be sent:
return RedirectToAction("actionName", "anotherControllerName", new { id = charity });
And get it by:
Charity chatiry = (Charity)RouteData.Values["id"];
I hope that you will get some idea from this.

ASP.NET MVC4 with EF 6 Model Update Issue

I have two models as below
public class Category
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; },
[Required]
public string category { get; set; }
[Required]
public string Desc { get; set; }
}
public class Product
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; },
public int CatID { get; set; },
[ForeignKey("CatID")]
public virtual Category Category { get; set; },
[Required]
public string Desc { get; set; },
public string DisplayName
{
get
{
return string.format("{0} - {1}",this.Category.category,this.Desc);
}
}
}
This is my Edit Action
public ActionResult Edit(int id)
{
ViewBag.PossibleCategories = categoryRepository.All;
return View(productRepository.Find(id));
}
[HttpPost]
public ActionResult Edit(Product product)
{
if (ModelState.IsValid) //<== This becomes false saying category.desc is required
{
productRepository.InsertOrUpdate(product);
productRepository.Save();
return RedirectToAction("Index");
}
else
{
ViewBag.PossibleCategories = categoryRepository.All;
return View();
}
}
I have a scaffolded a Edit view of product and it shows ID and DisplayName as Readonly. All the other fields a editable.
The edit view also has the product -> category -> category has a read-only text field
#Html.TextBoxFor(model => model.Category.category, new Dictionary<string, object>() { { "readonly", "true" } })
The Post back sends this and tries to create a new category. This is not required. The category link will be carried forward using the product.CatID.
How can i display these types of fields??
When the Edit view Post back the Model state appears as invalid because the product's category's desc is null (product -> category -> desc).
if i comment out the DisplayName property in Product this issue doesn't occur.
From my understanding, this is because the DiaplayName property refers to Category property and the view view doesn't have category.desc field so when the model is created back on the POST action, the desc is not populated. Adding the category.desc field to the view is one way of solving this problem.
Is there any other method to solve this?
Note: This is not the only model i'm having this issue. There are many complex models which have the same problem and to me having these fields also included in the view would make for (1) a very cluttered view (2) the amount of data making the round trip will be high.
Simple Solution
Check for null. Really you should be making this a habit anyway.
public string DisplayName
{
get
{
if(this.Category != null)
{
return string.format("{0} - {1}",this.Category.category,this.Desc);
}
else
{
return String.Empty;
}
}
}
Complex Solution
Instead of directly using your database model in your Views another solution is to create ViewModels. These are models meant specifically for your View. As a simplified example, let's take your Product model and create a ViewModel.
Create a folder for your ViewModels
Create ViewModel files that match your Controller
Create a ViewModel that you will use in your View
Say you have a Store Controller. This would be the file structure you would create.
Models
ViewModels
StoreViewModels.cs
Inside the StoreViewModels you would create a ViewModel called ProductViewModel which you would fill in with information from Product.
public class ProductViewModel
{
public int ID { get; set; }
public string Description { get; set; }
public string DisplayName { get; set; }
public ProductViewModel() { }
public ProductViewModel(Product product)
{
this.ID = product.ID;
this.Description = product.Description;
this.DisplayName = product.DisplayName;
}
}
In your View you reference ProductViewModel instead of Product. On the receiving end you then translate the ViewModel fields back to your Model. Let me know if you have any questions.

How to get JSON object by Query string

I have to work with queries like:
Controller/Action?query ={"action":"test","id":"13037313353","pin":"452312"}
by GET.
My ViewModel:
public class ValidatePinViewModel
{
public ActionType action { get; set; }
public int Id { get; set; }
public int Pin { get; set; }
}
Controller
public JsonResult ValidateVisit(CommonViewModel model)
{
//model is null
return Json(new InvalidPin());
}
Now I got null for my view. How I can get the correct model
As there is very little supportive information on this question, I'm going to take a shot and say that you're not POSTING to an action. e.g.
[HttpPost] // <-- Make sure you define your POST action
public JsonResult ValidateVisit(CommonViewModel model)
{
...
}
A GET, as you speficy in your tags, is not going to post a model. Unless you are specifically denoting it both in where you define your form element as well as on the action itself, it will be null.
I could add a string to get object.
Something like:
public JsonResult ValidateVisit(string query)
{
ValidatePinViewModel model = Json.Deserialize<ValidatePinViewModel>(query);
return Json(new InvalidPin());
}

Controller using multiple models?

Untill now in C# MVC3 i only had to use one controller which used only one model.
But now i think i need access to two models in my controller. I'm wondering if this is allowed in the MVC pattern?
I have a ProfileController. The ProfileController shows the profile info of a user obviously. But there's also some other data that i wish to show, like the groups a user created. For that i also have a GroupsModel.
How am i supposed to get both data from one controller?
How am i supposed to get both data from one controller?
By using a view model:
public class MyViewModel
{
public ProfileModel Profile { get; set; }
public GroupsModel Groups { get; set; }
}
and then passing this view model to the view:
public ActionResult ShowProfile()
{
var model = new MyViewModel();
model.Profile = ...
model.Groups = ...
return View(model);
}
And now your view will be strongly typed to the view model:
#model MyViewModel
and you can display information:
#Html.DisplayFor(x => x.Profile.SomeProperty)
Assuming that you aren't putting too much into a single view/controller, why not compose a simple view model which has the data you need?
public class ProfileInfo
{
public Person Person
{
get;
set;
}
public List<Group> Groups
{
get;
set;
}
}

Model validation with viewModel don't works

I use viewModels to communicate between my controller and my view.
To get model validation, i use a partial class like this :
[MetadataType(typeof(EvaluationValidation))]
public partial class Evaluation
{
public class EvaluationValidation
{
[DisplayName("Title of evaluation")]
[Required( ErrorMessage="Please give a title")]
public string Title { get; set; }
}
}
The Displayname is binded to view with no problem but when i try to submit the view, i get this error :
The model item passed into the
dictionary is of type
'FOOBAR.Models.Evaluation',
but this dictionary requires a model
item of type
'FOOBAR.Areas.Evaluation.ViewModels.EvaluationFormViewModel'.
This is the code used in my controller
[HttpPost]
public ActionResult Create(FormCollection formValues)
{
Models.Evaluation data = new Models.Evaluation();
if (TryUpdateModel(data, "evaluations"))
{
this.daoe.Create(data);
return RedirectToAction("Index");
}
return View(data);
}
And this is my viewModel
public class EvaluationFormViewModel
{
public FOOBAR.Models.Evaluation evaluations;
public SelectList selectlist_evaluationtypes { get; set; }
public SelectList selectlist_evaluationstatus { get; set; }
}
Have you got an idea ?
Thank's by advance
You are passing a Models.Evaluation instance to your view, which is bound to a model of another type.
Models.Evaluation data = new Models.Evaluation();
if (TryUpdateModel(data, "evaluations"))
{
// ...
}
return View(data);
If TryUpdateModel returns false (which happens when the form does not pass validation, for example), you are effectively passing data to the View, which is of type Models.Evaluation.
Try mapping it to type FOOBAR.Areas.Evaluation.ViewModels.EvaluationFormViewModel before passing it to the view.

Categories