cant get Serialize HtmlHelper to work - c#

i looked at : http://weblogs.asp.net/shijuvarghese/archive/2010/03/06/persisting-model-state-in-asp-net-mvc-using-html-serialize.aspx
but when i post the page the model(ie person) returns as null?
[HttpPost]
public ActionResult Edit([DeserializeAttribute]Person person, FormCollection formCollection)
{
//this line has an error:
TryUpdateModel(person, formCollection.ToValueProvider());
return View(person);
}
<% using (Html.BeginForm("Edit", "Home"))
{%>
<%=Html.Serialize("person", Model)%>
<%=Html.EditorForModel() %>
<button type="submit">
go</button>
<%
}%>
[Serializable]
public class Person
{
public string name { get; set; }
public string suburb { get; set; }
}

Why you are trying to bind the person from the request using the following:
TryUpdateModel(person, formCollection.ToValueProvider());
when you explicitly know that there is no such thing in the request? This line invokes the model binder and tries to read it from the request values. But in your form you only have a single hidden field. So your action should look like this:
[HttpPost]
public ActionResult Edit([DeserializeAttribute]Person person)
{
// Do something with the person object that's passed as
// action argument
return View(person);
}
Also your scenario looks strange. You have a view which seems to be strongly typed to this Person object in which you are using Html.EditorForModel meaning that you are offering the user possibility to edit those values. If you serialize the model you will get old values in your controller action. This attribute is only useful when you want to persist some model between multiple requests but there is no corresponding input fields in the form so that the default model binder can reconstruct the instance in the POST actions.

Related

Combine controller and action parameter for ActionLink / RedirectToAction

My application has a view that is linked from various different other views located in different controllers. I would like to have a 'Back' button in this view that will send the user back to the previous view, which ever that may be.
To this end I have added a string attribute to the viewmodel which I would like to use to reference the originating view /MyController/MyAction in the #Html.ActionLink parameters.
Some of the views linking to this view belongs to current controller, some belong to other controllers. This means I have to pass the controller as well as the action to the ActionLink.
As it stands, my code looks something like this:
ViewModel:
public class MyViewModel
{
public int MyData { get; set; }
[HiddenInput]
public string ReturnUrl { get; set; }
}
View:
#Html.ActionLink("Back", Model.ReturnUrl)
This produces the undesirable result of localhost:####/CurrentController/MyController/MyAction
Of course I could always save two strings on the ViewModel (one for the controller and one for the action) and pass them to the ActionLink seperately, but if possible I would like to avoid that. Is there an overload of ActionLink that allows me to use a single return url string, without making implications about the controller?
Also, is it possible to achieve the same thing on the controller side, f.ex. like this:
[HttpPost]
public ActionResult DoStuff(MyViewModel model)
{
// do stuff
return RedirectToAction(model.ReturnUrl);
}
You shouldn't use ActionLink. Just use an anchor tag, like this:
<a href='#Url.Content(Model.ReturnUrl)'>Back</a>
And, in your Action, you can do it like below:
[HttpPost]
public ActionResult DoStuff(MyViewModel model)
{
// do stuff
return Redirect(model.ReturnUrl);
}
Also, another solution would be to have two properties (ReturnController and ReturnAction) in your model.

Keep a value in a ViewModel even if the value is not modified

In a C# MVC 5 Internet application, I have a HTTP Get Edit action result, that gets an object, and places this object in a ViewModel and this is then displayed in a View.
One of the fields in the ViewModel is a value that is not edited in the view. In the HTTP Post Edit action, the value that is not edited in the view has been reset.
How can I keep this value so that it is the same value in the HTTP Post method as the HTTP Get method?
Thanks in advance
EDIT
Here is the ViewModel code:
public class MapLocationViewModel
{
[Editable(false)]
public int mapCompanyForeignKeyId { get; set; }
public MapLocation mapLocation { get; set; }
}
Here is the code at the bottom of the HTTP Get Edit Action result, where the mapCompanyForeignKeyId is set:
MapLocationViewModel mapLocationViewModel = new MapLocationViewModel();
mapLocationViewModel.mapLocation = maplocation;
mapLocationViewModel.mapCompanyForeignKeyId = maplocation.mapCompanyForeignKeyId;
return View(mapLocationViewModel);
Here is the HTTP Post Edit Action result code:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(MapLocationViewModel mapLocationViewModel)
In the above HTTP Edit Action result code, the mapLocationViewModel.mapCompanyForeignKeyId is reset to 0, after this value has been set to a number in the HTTP Get Edit Action result.
You should try make hidden input. With Razor syntax it would be:
#using (Html.BeginForm())
{
#Html.HiddenFor(model => model.YourProperty)
}
YourProperty will not be visible but it's value will be in the view model sent to the POST method.
You can also use HiddenInputAttribute for this:
[HiddenInput(DisplayValue=false)]
public int YourProperty {get; set;}
If you are using the #Html.TextboxFor(m => m.MyField) or similar helpers within a form, by default, it should automatically spit out all the existing values for each field and thus you should see all values whether modified or not. When it is posted, each included field will be serialized. If you use the helpers, you won't have to worry about naming convention as Razor and the model binder will do the work for you.
Check the request coming into your POST action to see if it is a model binding issue or a client issue. If you don't see the desired members in the body (or query string, if a GET) then you must not be sending them from the client, which can be due to improper serialization/naming of fields, not including the field in the page, not sending the value of the field to the page, or including the field outside of the form, among other reasons...
Example:
public class MyViewModel
{
[Required]
public string Field1 { get; set; }
[Required]
public string Field2 { get; set; }
}
...
#model MyViewModel
#using (Html.BeginForm("MyAction", ...)
{
#Html.LabelFor(m => m.Field1)
#Html.TextboxFor(m => m.Field1)
<br />
#Html.LabelFor(m => m.Field2)
#Html.TextAreaFor(m => m.Field2)
<button type="submit">Submit</button>
}
...
[HttpPost]
public ActionResult MyAction(MyViewModel model)
{
if (!ModelState.IsValid)
return MyGetAction(model);
...
}

null on HTTPPost on ASP.NET

My Problem is like this ,Im trying to get a model object from a view after seinding it with a form,the model Looks like this:
public class PackageModel
{
public PackageDTO Package { get; set; }
public IEnumerable<SelectListItem> Allcategories { get; set; }
}
while PackageDTO is just an DTO object conatining many attributes.
Now the view for this model,ist just showing the attributes and this model will be sent within a httppost request to the index page as normal(there it will be processed ans saved ),
the index method in the Controller Looks like this:
[HttpPost]
public ActionResult Index(PackagemODEL packageModel, FormCollection form)
{
}
Now i dont know what im doing wrong,but the Object packageModel is not totally null,just the list Allcategories and another string Attribute in the PackageDTO object,the rest seems to be working.
The view contains this code
<fieldset>
<legend>#Resources.AppvManagementService_EditPackage_Title</legend>
#using (Html.BeginForm("Index","WantedController",FormMethod.Post,new {enctype="multipart/form-data"}))
{
#Html.ValidationSummary()
<labelName </label>#Html.TextBoxFor(model=>model.Package.Name) <br/>
<label>Sid </label>#Html.TextBoxFor(model=>model.Package.Sid,new {#disabled="disabled"}) <br/>
<label>Category </label>#Html.DropDownList("CategoryName",Model.Allcategories,Model.Package.Category)<br/>
<label>Description: </label>#Html.TextBoxFor(model=>model.Package.Description) <br/>
<label>Type: </label>#Html.TextBoxFor(model=>model.Package.Type) <br/>
<button type="submit">submit</button>
}
Doest anyone have any idea why ist like this?? am i doing something wrong(im sure i am :))
thx for every one
How do you expect Allcategories to be populated? Your view contains a field, which posts a value under the name "CategoryName" - there's nothing in your view that populates a list of categories. More importantly; to you really need it to be populated? It seems to me that Allcategories is only really needed for populating the dropdown in the view. On the post, you shouldn't need it. If you DO still need it, you're going to have to either:
Repopulate it in the controller on the HttpPost method:
[HttpPost]
public ActionResult Index(PackagemODEL packageModel, FormCollection form)
{
packageModel.Allcategories = new IEnumerable<SelectListItem>();
}
Clutter up your view with pointless hidden fields to pass the values back in (I wouldn't recommend this for a list of items unless you really need to):
#for (int i = 0; i < Model.Allcategories.Count; i++)
{
#Html.HiddenFor( m => m.Allcategories[i])
}
Populate it in the model constructor:
public class PackageModel
{
public IEnumerable<SelectListItem> Allcategories { get; set; }
public PackageModel()
{
Allcategories = new IEnumerable<SelectListItem>();
/* Add values to Allcategories here */
}
}
If the values of Allcategories doesn't change, you could also consider making it a static readonly property of your model and hardcoding the values (or pulling them from a config file or similar).
As for getting back the selected CategoryName, you need a field in your model in which to store it, otherwise the only way to access it at the moment is via Request.Form:
public class PackageModel
{
public IEnumerable<SelectListItem> Allcategories { get; set; }
public string CategoryName { get; set; }
}
#Html.DropDownListFor(m => m.CategoryName, Model.Allcategories, Model.Package.Category)
An aside: Please, please, please take your DTO out of your model and set appropriate properties in your model itself. Your DTO does not belong in your view model, which is a model for your view and nothing more.

ActionResult cast parameter base class to derived class

I've written a base class and some classes which derive from it.
I want to use these classes in one ActionResult, but if I'm trying to cast PSBase to PS1 I'm getting a System.InvalidCastException that type PSBase can not be converted to PS1.
Classes:
public class PSBase
{
public int stationId { get; set; }
public string name { get; set; }
}
public class PS1 : PSBase
{
public string reference { get; set; }
}
public class PS2 : PSBase
{
}
ActionResult:
[HttpPost]
public ActionResult ProductionStep(PSBase ps)
{
if (ModelState.IsValid)
{
var product = db.Product.FirstOrDefault(.........);
switch (ps.stationId )
{
case 1:
{
product.Reference = ((PS1)ps).reference;
db.SaveChanges();
break;
}
}
}
return View();
}
As I don't want to have for each class a own ActionResult (repeating much of the same code many times) I wanted put all this to one ActionResult. Any Ideas how I could implement this?
What you are trying to do will never work without custom ModelBinder (and even then it will be a huge mess I'd not recommend to implement), sorry.
Only when you are passing a model from Controller to View it remembers what type it was originally (including inheritance, etc.) because at that point you are still on the server side of the page and you are merely passing an object.
Once you enter a view and submit a form all that does it creates some POST request with body containing list of values based on input names.
In your case if you have a form based on PS1 and used all the fields as inputs, you would get something like:
POST:
stationId = some value
name = some value
reference = some value
(there is no mention of the original type, controller, method, etc.)
Now, what MVC does is that it checks what argument you are using in the header of the method (in your case ProductionStep(PSBase ps)).
Based on the argument it calls a model binder. What the default model binder does is that it creates new instance of that class (in your case PSBase) and goes via reflection through all the properties of that class and tries to get them from the POST body.
If there are some extra values in the POST body those are forgotten.
Unless you write a custom model binder for this default MVC implementation can't help you there.
I'd recommend creating two separate methods, one of each accepting different implementation of PSBase.
If you want to read more on Model Binders check this out http://msdn.microsoft.com/en-us/magazine/hh781022.aspx
EDIT:
By creating two separate methods I mean something like this:
[HttpPost]
public ActionResult ProductionStepA(PS1 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
[HttpPost]
public ActionResult ProductionStepB(PS2 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
then you have to distinguish between them in the view via different form action.

Annotation Validation not working

Please help me on the following code. The Model class is using System.ComponentModel.DataAnnotation:
namespace Proj.Models
{
public class Customer
{
[Required]
public string CustomerID{get;set;}
[Required]
public string CustomerName{get;set;}
}
}
I have created a controller using this model, the action method being:
public class Customer:Controller
{
public ActionResult Details()
{
return View();
}
}
The razor view is Details.cshtml, having following markup and code:
#model Proj.Models.Customer
<form method="post">
#Html.EditorForModel()
<button>Submit!!</button>
</form>
However, when I click submit, no validation errors are seen as expected.
You need to create a method which takes your model as input like this:
[HttpPost]
public ActionResult Index(Customer customer)
{
return View();
}
The [HttpPost] ensures that the method is only called on POST requests.
You need to create editor template for your model. By default no validation messages will be emitted. Inside your editor template, you will have to use #ValidationMessageFor for your Required fields.
Hope this helps.

Categories