For instance, there is some object that will be model for strongly-typed view:
class SomeModel {
public string SomeUsualField { get; set; }
}
Further, some action exists in controller that will work with object specified above:
public ActionResult Index(SomeModel obj)
{
return View(obj);
}
So the question is why obj isn't a null while Index action first called? It's created new instance of SomeModel class with null SomeUsualField.
The ASP.NET MVC model binding infrastructure tries to fill all properties with data coming from the request object (query string, form fields, ...). Therefore it creates an instance of all parameters of the controller to try to match the properties.
Because you don't pass a SomeUsualField, it is null, but the parameter object has an empty instance.
You can initialize the property SomeUsualField when you call http://localhost/MySite/MyController/Index?SomeUsualField=test. The property SomeUsualField will automagically initialized with 'test'
If you don't want the parameter to be set, you can leave it away and make a 2nd action with the attribute [HttpPost]. A good tutorial is the music store.
public ActionResult Index()
{
var obj = new SomeModel();
return View(obj);
}
[HttpPost]
public ActionResult Index(SomeModel obj)
{
// update logic
return View(obj);
}
Related
I've been developing with MVC for a few years now and one think I was originally taught was in a HttpPost action, the first thing you always do is perform a check on ModelState.IsValid, so...
[HttpPost]
public ActionResult Edit(ViewModel form) {
if(ModelState.IsValid) {
// Do post-processing here
}
}
I've come to an issue now where I have a hashed ID passed through the form. If it's 0, its a new record, if its above 0 it means I'm editing. Should my ModelState.IsValid ever return false, I need to setup some dropdown list data again before returning to the Edit view with the same model. To set some of these items that are returned to the form after failure, I need to know the unhashed number, but unhashing it inside the ModelState.IsValid would make it not available in the else statement.
So, is it acceptable to do the following:-
[HttpPost]
public ActionResult Edit(ViewModel form) {
int myID = 0;
if(/** unhashing is successful...**/)
{
if(ModelState.IsValid) {
// Do post-processing here
}
else
{
// Setup dropdowns using unhashed ID and return original view...
}
}
}
Note that ModelState.IsValid is not the first test inside the HttpPost. Is that acceptable? If not, is there a more appropriate way of doing such logic?
Thanks!
In your source you seem to have written some comment about unhashing but such term doesn't exist. The purpose of a hash function is to be irreversible. I think that on the other hand you have meant decrypting the query string value. Ideally this decryption should happen in a custom model binder for your view model, setting the ModelState.IsValid value to false for this parameter. So that inside your controller action all you need to check is this boolean parameter. A controller action should not be decrypting any query string or whatever parameters. This should be done much earlier in the MVC execution pipeline. A custom model binder or even a custom authorization filter would be a much better fit for this scenario.
So let's take an example:
public class ViewModel
{
public int Id { get; set; }
... some other stuff around
}
Now you could write a custom model binder for this view model:
public class MyDecryptingBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "Id")
{
var idParam = bindingContext.ValueProvider.GetValue("Id");
if (idParam != null)
{
string rawValue = idParam.RawValue as string;
int value;
if (this.DecryptId(rawValue, out value))
{
propertyDescriptor.SetValue(bindingContext.Model, value);
return;
}
}
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
private bool DecryptId(string raw, out int id)
{
// TODO: you know what to do here: decrypt the raw value and
// set it to the id parameter, or just return false if decryption fails
}
}
Now you can register this custom model binder with your view model at bootstrapping time (Application_Start):
ModelBinders.Binders.Add(typeof(ViewModel), new MyDecryptingBinder());
and then your controller action will be as simple as:
[HttpPost]
public ActionResult Index(ViewModel model)
{
if (ModelState.IsValid)
{
// The decryption of the id parameter has succeeded, you can use model.Id here
}
else
{
// Decryption failed or the id parameter was not provided: act accordingly
}
}
In my controller of an ASP.NET MVC project, I have a
return RedirectToAction("CreatePerson", "Home")
This View is a form that creates a person and that works fine. However, I want to RedirectToAction and pre-fill the form with data collected from a form that creates a User for the system.
How would I pass the data from the CreateUser form in the CreatePerson form?
I know that I could use parameters, but would this really be the best method if most of the time I am calling the CreatePerson view without needing those parameters.
Any help in the right direction would be greatly appreciated.
You can't send data with a RedirectAction. That's because you're doing a 301 redirection and that goes back to the client.
So better use TempData
Assuming you will have model to createperson with following properties:
public class CreatePersonData
{
public string name {get; set;}
public string address {get; set;}
}
Now fill the model and store in TempData
CreatePersonData person=new CreatePersonData();
person.name="SomeName";
person.address="SomeAddress";
TempData["person"]=person;
return RedirectToAction("CreatePerson", "Home")
Now while receiving just receive it from tempdata and pass the filled model to the view
public ActionResult CreatePerson()
{
CreatePersonData person=new CreatePersonData()
var loadPerson= TempData["person"];
person = loadPerson;
return View(person);
}
UPDATE
As #StephenMuecke made a point of loosing data with TempData you might need to use .Keep or .Peek with TempData to retain the value for future requests
Ex:
with .Peek
//PEEK value so it is not deleted at the end of the request
var loadPerson= TempData.Peek("person");
or with .Keep
//get value marking it from deletion
var loadPerson = TempData["person"];
//later on decide to keep it
TempData.Keep("person");
or as #Stephen said just pass the id and select the user from database
Ex:
return RedirectToAction("CreatePerson", "Home", new { ID = User.ID });
Now in your CreatePerson ActionResult just get it from db as below:
public ActionResult CreatePerson(int ID)
{
CreatePersonData person=new CreatePersonData();
var user=(from u in tbl_user select u where u.ID=ID);
person.name=user.name;
person.address=user.address;
return View(person);
}
UPDATE 2
You can combine both of the above approaches like storing data in TempData and passing the ID with routeValues and check if TempData isn't null then fallback to retrieval of data using ID approach.
Ex:
public class CreatePersonData
{
public string Id{get; set;}
public string name {get; set;}
public string address {get; set;}
}
public ActionResult CreatePerson(int ID)
{
CreatePersonData person=new CreatePersonData();
var loadPerson=(CreatePersonData)TempData.Peek("person"); //cast the object from TempData
if(loadPerson!=null && loadPerson.Id==ID)
{
person=loadPerson;
}
else
{
var user=(from u in tbl_user select u where u.ID=ID);
person.name=user.name;
person.address=user.address;
}
return View(person);
}
Many Overloads exist for this
protected internal RedirectToRouteResult RedirectToAction(string actionName, object routeValues);
What you want to do is right click on that Method and View Definition, you should see many overloads for it
// Summary:
// Redirects to the specified action using the action name and route values.
//
// Parameters:
// actionName:
// The name of the action.
//
// routeValues:
// The parameters for a route.
//
// Returns:
// The redirect result object.
1- create an object of your model
2- you can use session to pass parameter to your other action,
3- then put the object in a viewbag agg get it in your view
Is it possible for an action controller to accept a literal object. For example, I have several views in which I would like to post various models from to a single controller that can then determine the incoming model object for further processing.
Model sample:
public class Model1
{
// properties, etc.
}
public class Model2
{
// properties, etc.
}
public class Model3
{
// properties, etc.
}
controller sample:
[HttpPost]
public ActionResult ProcessModel(Object anyModel)
{
// determine the model
if((anyModel as Model1) != null)
{
var model1 = anyModel as Model1;
// continue with code
}
else if((anyModel as Model2) != null)
{
var model2 = anyModel as Model2;
// continue with code
}
// continue with model check, etc.
}
I've tried, but my controller does not appear to be picking up the model as my object parameter remains empty. Is this possible?
Have a quick read about how model binding works... The model binder (which takes whatever is posted to your Action and turns it into the anyModel parameter uses the type of the parameter to determine what to do.
Since the type is Object it can't do anything.
My guess (depending on what you're trying to achieve) is that you can have several Action overloads each with a different type of Model as the parameter which then call common code.
[HttpPost]
public ActionResult ProcessModel(Model1 anyModel){}
[HttpPost]
public ActionResult ProcessModel(Model2 anyModel){}
[HttpPost]
public ActionResult ProcessModel(Model3 anyModel){}
That said it's a bit odd to have one action which takes lots of different models. There's a good chance you're better off doing something else.
Your question might gather a better answer if you say what you're trying to achieve
The Default Asp.NET ModelBinder cannot bind generic Objects this way. You should take a look here to understand how the model will be build back in the server by the DefaultModelBinder: Understanding ASP.NET MVC Model Binding.
Given that your form has many Models, you should encapsulate them into a ViewModel to do this kind of operation.
The ViewModel should looks like this:
public class MyViewModel
{
public Model1 Model1 {get; set;}
public Model1 Model2 {get; set;}
public Model1 Model3 {get; set;}
}
And the controller:
[HttpPost]
public ActionResult ProcessModel(MyViewModel myViewModel)
{
// determine the model
if(myViewModel.Model1 != null)
{
// continue with code
}
else if(myViewModel.Model2 != null)
{
// continue with code
}
// continue with model check, etc.
}
Recently I faced the same issue and resolved it as below:
Step 1: From javascript pass 2 parameter :
First, pass model name as String for identification which model is coming
Second, Pass data from javascript using JSON.stringify(data). where your data can be from Model1, Model2 , Model3 etc.
Step2: In your controller:
[HttpPost]
public ActionResult ProcessModel(string modelName, string anyModel)
{
switch(modelName) {
case "Model1":
var modelValue= JsonDeserialize<Model1>(anyModel);
// do something
break;
case "Model2":
var modelValue= JsonDeserialize<Model2>(anyModel);
// do something
break;
}
}
You Need One method like below:
public T JsonDeserialize<T>(string jsonModel){
return JsonConvert.DeserializeObject<T>(jsonModel, jsonSettings);
}
JsonConvert need namespace "Newtonsoft.Json".
You also need to declare jsonSettings as below
JsonSerializerSettings jsonSettings= new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
DefaultValueHandling = DefaultValueHandling.Ignore
};
This solution is kind of workaround. There is one more solution. you can check that also:
How can I make a Controller Action take a dynamic parameter?
Hope this helps.
Hi this is my convert a Ilist of model to a Ilist of ViewModel method
public static IList<PostViewModel> ConvertToPostViewModelList(this IList<Post> posts)
{
return posts.Select(ConvertToPostViewModel).ToList();
}
and also this is the ConvertToPostViewModel
public static PostViewModel ConvertToPostViewModel(this Post post)
{
var blogPostViewModel = new PostViewModel
{
Id = post.Id,
Body = post.Body,
Summary = post.Summary,
Title = post.Title,
Category = post.Category,
CreationDate = post.CreationDate,
SelectedCategory = post.CategoryId,
SelectedTag = post.TagId,
Tag = post.Tag,
UrlSlug = post.UrlSlug
};
return blogPostViewModel;
}
what is the problem with this , I got this error View :
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[Blog.Domain.Model.Post]', but this dictionary requires a model item of type 'System.Collections.Generic.IList`1[Blog.Web.UI.ViewModels.PostViewModel]'.
then ?? I convert the Ilist of Model to ViewModel via this :
return posts.Select(ConvertToPostViewModel).ToList();
then what is going on ??
what I have done in action
public ActionResult Posts()
{
var blogPost = _blogRepository.GetAllPost();
var blogPostViewModel = blogPost.ConvertToPostViewModelList();
return View("Posts", blogPostViewModel);
}
and in View
#model IList<Blog.Web.UI.ViewModels.PostViewModel>
Two possibilities:
The method you posted is not the one being matched. You can verify this easily by throwing an exception in your controller action and seeing if it gets thrown. This could be the result of either: (a) an overloaded Posts controller action, where the other is being matched; or, (b) a custom route that is intercepting the request.
You originally returned the domain object in testing, but after changing the controller action to set the model to the PostViewModel, you forgot to recompile your MVC project. Try recompiling your solution and see if the results change.
I have a ViewModel like so:
public class ProductEditModel
{
public string Name { get; set; }
public int CategoryId { get; set; }
public SelectList Categories { get; set; }
public ProductEditModel()
{
var categories = Database.GetCategories(); // made-up method
Categories = new SelectList(categories, "Key", "Value");
}
}
Then I have two controller methods that uses this model:
public ActionResult Create()
{
var model = new ProductEditModel();
return View(model);
}
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (ModelState.IsValid)
{
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
return View("Success");
}
else
{
return View(model); // this is where it fails
}
}
The first time the user goes to the Create view, they are presented with a list of categories. However, if they fail validation, the View is sent back to them, except this time the Categories property is null. This is understandable because the ModelBinder does not persist Categories if it wasn't in the POST request. My question is, what's the best way of keeping Categories persisted? I can do something like this:
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (ModelState.IsValid)
{
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
return View("Success");
}
else
{
// manually populate Categories again if validation failed
model.Categories = new SelectList(categories, "Key", "Value");
return View(model); // this is where it fails
}
}
But this is an ugly solution. How else can I persist it? I can't use a hidden field because it's a collection.
I would use the repository to fetch whatever data is needed and don't think it's an ugly solution:
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (!ModelState.IsValid)
{
// manually populate Categories again if validation failed
model.Categories = Repository.GetCategories();
return View(model);
}
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
// I would recommend you to redirect here
return RedirectToAction("Success");
}
To further refactor this I would recommend you watching the excellent Putting Your Controllers on a Diet video presentation by Jimmy Bogard.
I typically implement my lists (for drop downs) as a readonly property. When the View gets the value the property is self contained on what it needs to return the values.
public SelectList Categories
{
get
{
var categories = Database.GetCategories(); // made-up method
return new SelectList(categories, "Key", "Value");
}
}
If necessary you can grab the currently selected item (i.e. validation failed) from the property containing the id that was posted and bound to the instance of your class.
In my case I have a BaseModel class where I keep all those property list as class attributes.
Like in the following sample:
public IEnumerable<SelectListItem> CountryList
{
get
{
return GetCountryList().Select(
t => new SelectListItem { Text = t.Name, Value = Convert.ToString(t.CountryID) });
}
}
GetCountryList() is a function that ask a Singleton for data. This would only happen once in the app lifecycle
Another way for doing this, and if those lists are pretty big, would be to have a static utility class with the lookup table that returns the SelectListItem.
If you need to access a list that change from time to time then simply dont use a Singleton class.