ASP.NET form submit part of a model - c#

This is just something that has been puzzling me, I'm wondering if there's a built in way for this.
Say you have a Package class
public class Package
{
public A AObject { get; set; }
public B BObject { get; set; }
}
And you have a view that uses this Package.
public ActionResult Action()
{
return View(new Package());
}
Now the view will accept this model and have 2 forms.
#model Path.To.Package
#Html.BeginForm("SubmitA", "MyController")
{
#Html.TextBoxFor(m => m.AObject.SomeProperty);
<input type="submit" />
}
#Html.BeginForm("SubmitB", "MyController")
{
#Html.TextBoxFor(m => m.BObject.AnotherProperty);
<input type="submit" />
}
If one would create two actions needed above that take Package as argument, this would work without question...
public JsonResult SubmitA(Package items) { ... }
public JsonResult SubmitB(Package items) { ... }
But at SubmitA the BObject would be null and in SubmitB AObject would be null.
My question here is whether you can submit only a part of the model? So the first form would only submit AObject and the second BObject so you could actually reach these via the following actions:
public JsonResult SubmitA (A a) { ... }
public JsonResult SubmitB (B b) { ... }

You can use the Prefix property of BindAttribute to bind to complex properties of a model. The attribute effectively removes the prefix from the submitted name/value pairs when binding to model.
Your controller methods would be
public JsonResult SubmitA([Bind(Prefix = "AObject")]A model) { ... }
public JsonResult SubmitB([Bind(Prefix = "BObject")]B model) { ... }

You should really use separate view model for each form. You can of course, use bind attribute or use specific property names in the controller action. But, that doesn't solve your real problem. You can only get either of the values and the other object will be unassigned or NULL. This is why you should have separate view model for each view / form. You can build your Package object once you have values for both objects.

Related

pass key value pair to controller

I've seen a couple possible solutions but they look very messy to me. Does anyone have a simple solution for this?
Model:
public class MyClass
{
public KeyValuePair<int,string> Field { get; set; }
}
Get method in controller:
public ActionResult Index()
{
var model = new MyClass();
model.Field = new KeyValuePair<int, string>(1, "test");
return View(model);
}
View:
#model WebApplication1.Models.MyClass
#using (Html.BeginForm("MyMethod", "Home", FormMethod.Post))
{
#Html.Hidden("Field", Model.Field);
<input type="submit" value="Submit" />
}
Post method in controller:
public ActionResult MyMethod(MyClass input)
{
var x = input.Field;
....
}
The Key Value pair is not passed with this method as it just comes up empty. What would be the easiest way of getting 'Field' passed to the controller?
Replace following line and try it.let me know any problem.
#Html.Hidden("Field", Model.Field);
TO
#Html.HiddenFor(m => m.Field);
If memory serves correctly, it has a Key and Value property, which like any other object would be bound like:
#Html.HiddenFor(m => m.Field.Key)
#Html.HiddenFor(m => m.Field.Value)
One input field can't represent the key and value in this way, unless you look at using a Custom Model Binder to manage the value posted back, and a Display/Editor template to render the input in a way the model binder will understand.
Following Brians answer, I've had to accept I must do it like this:
Class:
public class MyClass
{
public KeyValuePair<int,string> Field { get; set; }
public string FieldKey { get; set; }
public string FieldValue { get; set; }
}
View:
#Html.Hidden("FieldKey",Model.Field.Key)
#Html.Hidden("FieldValue", Model.Field.Value)
It will get messy as I have many of these kvp's I'd like to pass back to the controller, but I guess it's the only uncomplicated way. It's not the first time I've wished it was possible to pass objects to a controller, and I'm sure it won't be the last. Maybe we'll get the happy news in a dotnet update one day that we can do things like this more easily.

Bind checkboxes to List in partial view

I have a CreateViewModel.
public class CreateViewModel
{
public AttributesViewModel AttributesInfo { get; set; }
}
The AttributesViewModel is sent to a partial view.
public class AttributesViewModel
{
public AttributesViewModel()
{
ChosenAttributes = new List<int>();
}
public List<Attributes> Attributes { get; set; }
public List<int> ChosenAttributes { get; set; }
}
The List of Attributes is outputted in the partial view. Each one has a checkbox.
foreach (var attribute in Model.Attributes)
{
<input type="checkbox" name="ChosenAttributes" value="#attribute.ID" /> #Attribute.Name
}
When I post CreateViewModel, AttributesInfo.ChosenAttributes is always empty even though I checked some boxes. How do I properly name each checkbox so that it binds to the ChosenAttributes List?
My Solution
I took Stephen Muecke's suggestion to do the two way binding. So, I created a CheckboxInfo class that contained Value, Text, and IsChecked. I created a EditorTemplate for it:
#model Project.CheckboxInfo
#Html.HiddenFor(model => model.Text)
#Html.HiddenFor(model => model.Value)
#Html.CheckBoxFor(model => model.IsChecked) #Model.Text
One GIANT caveat. To get this to work properly, I had to create an EditorTemplate for the AttributesViewModel class. Without it, when CreateViewModel is posted, it cannot link the checkboxes to AttributesInfo.
Your naming the checkbox name="ChosenAttributes" but CreateViewModel does not contain a property named ChosenAttributes (only one named AttributesInfo). You may be able make this work using
<input type="checkbox" name="AttributesInfo.ChosenAttributes" value="#attribute.ID" /> #Attribute.Name
but the correct approach is to use a proper view model that would contain a boolean property (say) bool IsSelected and use strongly typed helpers to bind to your properties in a for loop or using a custom EditorTemplate so that your controls are correctly names and you get 2-way model binding.
I had a similar scenario, but this was how I did it. The solution is not perfect so please excuse if I have left something out, but you should be able to relate. I tried to simplify your solution as well :)
I changed the Attribute class name to CustomerAttribute, rename it to whatever you like, use a singular name, not plural. Add a property to your CustomerAttribute class, call it whatever you like, I called mine IsChange.
public class CustomerAttribute
{
public bool IsChange { get; set; }
// The rest stays the same as what you have it in your Attributes class
public string Name { get; set; } // I'm assuming you have a name property
}
Delete your AttributesViewModel class, you don't really need it, I like simplicity.
Modify your CreateViewModel class to look like this:
public class CreateViewModel
{
public CreateViewModel()
{
CustomerAttributes = new List<CustomerAttribute>();
}
public List<CustomerAttribute> CustomerAttributes { get; set; }
}
Your controller will look something like this:
public ActionResult Create()
{
CreateViewModel model = new CreateViewModel();
// Populate your customer attributes
return View(model);
}
Your post controller action method would look something like this:
[HttpPost]
public ActionResult Create(CreateViewModel model)
{
// Do whatever you need to do
}
In your view, you will have something like this:
<table>
<tbody>
#for (int i = 0; i < Model.CustomerAttributes.Count(); i++)
{
<tr>
<td>#Html.DisplayFor(x => x.CustomerAttributes[i].Name)</td>
<td>#Html.CheckBoxFor(x => x.CustomerAttributes[i].IsChange)</td>
</tr>
}
<tbody>
</table>
Create a sample app and try out the code above and see if it works for you.

Send a list of objects through ajax to an action in ASP.NET MVC

I have a base class for model:
public abstract class ViewModel {
public string Title{ get; set; }
public abstract string Type { get; }
}
and I created two classes:
public class SomeViewModel: ViewModel {
public override string Type { get { return "a type" } }
public int Id { get; set; }
}
public class AnotherModel: ViewModel {
public override string Type { get { return "another type" } }
public string System { get; set; }
}
Now, I have a view which uses these classes ( I send a List<ViewModel> to the view)
#model List<ViewModel>
...
<form>
#Html.DisplayFor(model => model)
</form>
I have a view called ViewModel.cshtml in DisplayTemplates folder
#model ViewModel
#if(Model is AnotherModel) {
// do something and print value
AnotherModel conv = Model as AnotherModel;
#Html.TextboxFor(model => conv.System)
} else {
// put some inputs here
}
#Html.HiddenFor(model => model.Type)
Now I have a javascript ajax. I want to send a list of objects to an action which renders a partial view
The ajax:
$.ajax({
url: 'My/GetData',
dataType: "html",
type: "POST",
data: { id: 3, myList: $("form").serialize() }
success: function(data) {
// print html
}
});
and action looks like:
[HttpPost]
public ActionResult GetData(int id, List<ViewModel> myList){
...
return PartialView("myView", someModel)
}
The problem is myList parameter which always is 0 as length (count)... I expect to be 2...
Where is my mistake ?
The reason myList would contain no elements is no doubt because the control names do not match the property names, but you have more problems than just that. The Type property in the derived models has a getter only, so even if the collection did bind correctly (it wont as discussed below), the value of Type would be an empty string because there is no setter on the property (the IsReadonly property of ModelMetadata is set to true so the DefaultModelBinder ignores it), and you would have no way of knowing what type your objects were.
Next, The DefaultModelBinder will only initialize instances of ViewModel, not your derived types, so only those properties of ViewModel with public getters/setters would be bound (for example the Id property of SomeViewModel wont exist)
I am assuming your models must be far more complex than what you have shown (otherwise there would be no point doing this) which means your template (and it should be an EditorTemplate not a DisplayTemplate!) must be polluted with lots of other logic to determine which properties to render and how (and a nightmare to debug).
The simple solution would be to create a view model with collections for each type (probably also including the id property you want to post, but not sure what that is or how it relates to your model) and then use an EditorTemplate for each type
public class TypeVM
{
public List<SomeViewModel> SomeTypes { get; set; }
public List<AnotherModel> AnotherTypes { get; set; }
}
/Views/Shared/EditorTemplates/AnotherModel.cshtml
#model SomeViewModel
....
#Html.TextBoxFor(m => m.Id)
/Views/Shared/EditorTemplates/SomeViewModel.cshtml
#model SomeViewModel
....
#Html.TextBoxFor(m => m.System)
and in the view
#model TypeVM
....
#Html.EditorFor(m => m.SomeTypes)
#Html.EditorFor(m => m.AnotherTypes)
If you need to submit using ajax, then
var url = '#Url.Action("GetData", "My")'; // don't hard code your url's!
var data = $("form").serialize();
$.post(url, data, function(data) {
// update DOM
});
and post back to
[HttpPost]
public ActionResult GetData(TypeVM model)
This would mean rendering different the types in groups. If that is not suitable, then you need to create a custom ModelBinder where you can use the bindingContext to read the Type property and initialize each derived type and set the appropriate properties. A daunting task, but The Features and Foibles of ASP.NET MVC Model Binding
might help you get started with how to approach it (the section on Abstract Model Binder).
Side note: Do you really need to post back the whole form in order to return a partial view?

Default constructor being called during POST and I lose all model data

I am having a problem where a form is posting back and the model is being lost. As soon as the page is posted back .net is calling the default constructor of the object even though the object already exists.
I have 2 actions, one for GET and one for POST
[HttpGet]
public ActionResult Quote(string sku, string network, string grade )
{
QuoteModel qm = new QuoteModel();
// build model here
return View("Quote", qm);
}
[HttpPost]
public ActionResult Quote(QuoteModel qm, string grade, string network)
{
// update model
return View("Quote",qm);
}
The GET function works perfectly but as soon as the form is posted, the default constructor is called and I lose all the model data.
My view is like:
#model PriceCompare.Models.QuoteModel
<div class="clarify">
#if (Model.clarify == true)
{
using (Html.BeginForm("Quote", "Home", FormMethod.Post))
{
#Html.DropDownList("network", Model.availableNetworks);
#Html.DropDownList("grade", Model.grades);
<button type="submit">Get Quote</button>
}
}
</div>
Why is the default contructor being called when there is an existing model to pass back?
I have tried specifying the model in the form like:
using (Html.BeginForm("Quote", "Home", new { #qm = Model}, FormMethod.Post)
If I do this then the default constructor is not being called but qm is null.
I've been going round in circles here trying to figure this out. Can anyone explain what I am doing wrong?
You can solve this problem by overloading the constructor, and only explicitly calling the second constructor with a boolean parameter set to true.
Model
public class MyModel
{
public int NumberRequested { get; set; }
// This constructor will be called by MVC
public MyModel()
{
RefreshReport();
}
// Call this constructor explicitly from the controller
public MyModel(bool load)
{
if (!load)
{
return;
}
NumberRequested = 10;
RefreshReport();
}
public void RefreshReport()
{
// Do something
}
}
Constructor
public class MyController
{
public ActionResult MyView()
{
var myModel = new MyModel(true);
return View(myModel);
}
[HttpPost]
public ActionResult MyView(MyModel myModel)
{
myModel.RefreshReport();
return View(myModel);
}
}

ASP.NET MVC4 One View Multiple Model

I've the following class structure.
public BaseClass
{
public int Id { get; set; }
public string Name { get; set; }
}
public ClassOne : BaseClass
{
}
public ClassTwo : BaseClass
{
}
My Controller is like this
public TestController : Controller
{
public ActionResult ClassOne()
{
ClassOne model = new ClassOne();
return View("Create",model);
}
public ActionResult ClassTwo()
{
ClassTwo model = new ClassTwo();
return View("Create",model);
}
My View ("Create") is like this :
#model MvcApplication.Models.BaseClass
#using(Html.BeginForm("Post","Test",FormMethod.Post))
{
#Html.HiddenFor(model => mode.Id)
#Html.TextBoxFor(model => model.Name)
<input type="submit" value="Submit"/>
}
My Post Action is the same for both the models i.e. ClassOne & ClassTwo.
On Post how can I know which model is passed in the Post Action whether it is ClassOne or ClassTwo.
[HttpPost]
public ActionResult Post(BaseClass model)
{
/* Code */
}
Inside the post just check the type. Either by if(model is ClassOne) or if(model.GetType() == typeof(ClassOne))
I'm not sure that it is possible without creating your own ModelBinder. In this case you'll always get in your action instance of BaseType class.
You can use typeof():
if(model.GetType() == typeof(ClassOne)){
// do something
}else {
// do something else.
}
Edit:
thanks to #JoreanVannevel
if(model is ClassOne){}
You can just check the type of the object.
if(model.GetType() == typeof(ClassOne))
{
// Do something
}
Jonah,
You should use one model for a view. Now you can do two things
First thing,
you can use custom model binder see here http://www.codeproject.com/Articles/605595/ASP-NET-MVC-Custom-Model-Binder
or (Second thing),
in Html.Beginform also place hidden field to for identifying type (like 1 for ClassOne , 2 for ClassTwo). and now create a third class which has distinct property of both ClassTwo and ClassOne , common property of BaseClass and a extra property for type information.(do not use any type of validation here)
for the post use the newly created type, then check the type field.
your problem is action can't judge which type.because view to action the type is loose.
but we can get type at view and send the type to the action.
view like this:
#model MvcApplication.Models.BaseClass
#using(Html.BeginForm("Post","Test",FormMethod.Post))
{
#Html.HiddenFor(model => mode.Id)
#Html.TextBoxFor(model => model.Name)
#Html.Hidden("type", model.GetType().Tostring())
<input type="submit" value="Submit"/>
}
action like this:
[HttpPost]
public ActionResult Post(BaseClass model)
{
/* Code */
if(Request["type"] == typeof( ClassOne).Tostring())
{
}
if(Request["type"] == typeof( ClassTwo ).Tostring())
{
}
}

Categories

Resources