Persisting Collections in ViewModel - c#

I've been struggling for quite some time now with trying to maintain a list of objects when the ViewModel is submitted back to the controller. The ViewModel receives the list of objects just fine, but when the form is submitted back to the controller the list is empty. All of the non-collection properties are available in the controller, so I'm not sure what the issue is. I have already read the guide a few people have referenced from Scott Hanselman here
From what I can see, everyone solves this by building an ActionResult and letting the model binder map the collection to a parameter:
Controller:
[HttpPost]
public ActionResult Submit(List<ConfigurationVariable> variables)
{
}
View:
#for (int i = 0; i < Model.ConfigurationVariables.Count; i++)
{
<div class="row">
<div class="col-xs-4">
<label name="#Model.ConfigurationVariables[i].Name" value="#Model.ConfigurationVariables[i].Name" />
</div>
</div>
<div class="row">
<div class="col-xs-4">
<input type="text" class="form-control" name="#Model.ConfigurationVariables[i].Value" value="#Model.ConfigurationVariables[i].Value" />
</div>
</div>
}
What I really want is to be able to pass my ViewModel back to the controller, including the ConfigurationVariables List:
Controller:
[HttpPost]
public ActionResult Submit(ReportViewModel report) //report.ConfigurationVariables is empty
{
}
View:
#for (int i = 0; i < Model.ConfigurationVariables.Count; i++)
{
<div class="row">
<div class="col-xs-4">
#Html.LabelFor(model => model.ConfigurationVariables[i].Name, new { #class = "form-control" })
</div>
</div>
<div class="row">
<div class="col-xs-4">
#Html.TextBoxFor(model => model.ConfigurationVariables[i].Value, new { #class = "form-control" })
</div>
</div>
}
This will be a complicated form and I can't just put every collection into the ActionResult parameters. Any help would be greatly appreciated

You need to hold the Name property in a hidden input so that it's submitted. Label values are lost.
#Html.HiddenFor(model => model.ConfigurationVariables[i].Name)

Alright, based on your comment you won't be able to utilize mvc's form binding. No worries.
Instead of this controller definition:
public ActionResult Submit(List<ConfigurationVariable> variables)
Use one of these two:
public ActionResult Submit()
public ActionResult Submit(FormCollection submittedForm)
In the first you can access the Request object, you'll need to debug and locate your variable, then you'll need some logic to parse it apart and used the values submitted.
In the second, the form collection will be made up of all the INPUT elements in your form. You will be able to parse through them directly on the object without interference from the other attributes of the Request object.
In both cases you will probably need to use #Html.TextBox, and not TextBoxFor, and you will need to dynamically populate your dropdowns in your view.
I'm not 100% sure about the Request object, but for sure on the FormCollection you will need to create an Input element for each value/collection you want submitted. Including hidden inputs for your textboxes
Your textboxes will need to be SelectListItem collections. those require a key and a value, and when they are submitted you can loop through the collection and check the .Selected attribute.
I would try with FormCollection first, and if that doesn't work fall back to the Request object.
Also note: you are not getting a viewmodel back from the form submission, you will need to rebuild it from the form elements. If you want to post prepopulated data to the view you will need to build a view model and do appropriate parsing on the view to display it.

Related

Adding a save button to the index page

I have written an ASP.NET application that users access to check fields off. Instead of the user having to go into the edit page and hitting save, I want them to be able to just check it off on the index page and it either saves automatically or they can click the save button on the index page. I have made the fields EditorFor instead of Display for, and added the save button to the page. However, I am not sure how to implement the code to save in the controller.
Here is the code I have been trying out on my viewcontroller, but it says "does not contain a definition for save"
public virtual ActionResult Save(Doctor model)
{
Doctor.Save();
}
You are calling Save() on class Doctor which means there must be a static method in Doctor class. If it is not static but it does existing in the Doctor class then call the save method by model.Save().
If you are using EF then make sure inside the Save() method you're attaching the updated values on model object to respective EF entity or adding the object to the context in case new item is being created.
View
#using (Html.BeginForm("Save", "Doctor", FormMethod.Post))
{
<p>
#Html.LabelFor(model => model.Foo):
#Html.EditorFor(model => model.Foo)
#Html.ValidationMessageFor(model => model.Foo)
</p>
<p>
#Html.LabelFor(model => model.barImage)
<input type="file" name="Image" />
</p>
<p>
<input type="submit" value="Save" />
</p>
}
Controller
[HttpPost]
public ActionResult Create(Doctor doc)
{
your_dbcontext db = new your_dbcontext();
db.Add<Doctor>(doc);
db.SaveChanges();
return RedirectToAction("Index");
}

Passing data to a Controller method via POST

I have a 'Survey' page which is declared as follows:
#using (Html.BeginForm("Survey", "Home", new { questionList = Model.Questions }, FormMethod.Post))
{
<div class="survey">
<ol class="questions">
#foreach (Question q in Model.Questions)
{
<li class="question" id="#q.QuestionName">
#q.QuestionText<br />
#foreach (Answer a in q.Answers)
{
<input class="answer" id="#a.DisplayName" type="checkbox" /><label for="#a.DisplayName">#a.AnswerText</label>
if (a.Expandable)
{
<input type="text" id="#a.DisplayNameFreeEntry" maxlength="250" /> <span>(250 characters max)</span>
}
<br />
}
</li>
}
</ol>
</div>
<div class="buttons">
<input type="submit" value="Finish" />
</div>
}
When I'm stepping through my code, it hits the method I've set up to process their survey:
[HttpPost]
public ActionResult Survey( List<Question> questionList, FormCollection postData)
{
//Process Survey
}
However, when I step through I am finding that the variable questionList is null and the variable postData does not contain any data from the Form. Trying to access checkboxes via Request[a.Displayname also does not work.
Everything I've read indicates that this is the correct way to persist values from the Model to the submission method, and that I should be able to access the FormCollection this way.
What am I doing wrong?
You have to save questionList as a hidden field on the page. Non-primitive types do not get persisted simply by passing them in.
One way you can do that is
#Html.HiddenFor(m => m.Foo)
Or you could do it directly in HTML like this
<input type="hidden" name="Var" value="foo">
where m is your model.
The fact that the postData is empty is weird, since every input element with id inside a form tag should be passed with the POST request.
But the questionList won't be received that way, since its a list of complex class (not just a string or int), and the default ModelBinder (the thing that turns the HTTP Request Variables into parameters passed to the action method) don't support lists of complex classes.
If you want to be able to receive List you will have to implement your own binding mechanism with CustomModelBinder.
This article can help you implement it.
One problem is your checkbox and your textbox are not properly bound to your model.
You should be using #Html.CheckBoxFor and #Html.TextBoxFor

How to render a newsfeed in an asp.net mvc4 partial view

I have a new MVC4 project.
In my _Layout.cshtml I have the following:
<div class="container maincontent">
<div class="row">
<div class="span2 hidden-phone">
#*
In here is a RenderSection featured. This is declared in a section tag in Views\Home\Index.cshtml.
If this is static text for the whole site, don't render a section, just add it in.
You should also be able to use #Html.Partial("_LoginPartial") for example.
This _LoginPartial will need to be a cshtml in the Shared Views folder.
*#
#{ Html.RenderPartial("_NewsFeed"); }
</div>
<div class="span10">
#RenderBody()
</div>
</div>
</div>
My partial view is
<div class="row newsfeed">
NEWS FEED
#foreach (var item in ViewData["newsfeed"] as IEnumerable<NewsItem>)
{
<div class="span2 newsfeeditem">
<h3>#item.NewsTitle</h3>
<p>#item.NewsContent</p>
#Html.ActionLink("More", "NewsItem", "News", new {id=#item.Id}, null)
</div>
}
Is there a way I can make the partial view make the data call. Currently I have to do the following in my controller for every action:
ViewData["newsfeed"] = _db.NewsItems.OrderByDescending(u => u.DateAdded).Where(u => u.IsLive == true).Take(4);
return View(ViewData);
I come unstuck where I already pass in a model to a view as I cannot then pass this into it as well.
I know I am doing something wrong, just not sure what or where.
I just want to be able to make a render call in my _layout and then the partial view to know to collect the data and then render itself. Or have I got the wrong end of the stick? I suppose I am trying to use it like an ascx...
You should switch from using a RenderPartial to a RenderAction. This allows you to go through the pipeline again and produce an ActionResult just like a partial, but with server side code. For example:
#Html.RenderAction("Index", "NewsFeed");
Then you make a NewsFeedController and provide an Index action method:
public class NewsFeedController : Controller
{
public ActionResult Index()
{
var modelData = _db.NewsItems.OrderByDescending(...);
// Hook up or initialize _db here however you normally are doing it
return PartialView(modelData);
}
}
Then you simply have your CSHTML like a normal view in your Views/NewsFeed/Index.cshtml location.

How can I pass a value from my page to the controller?

I'm using MVC with Razor and C#. I would like to update an element... a counter with ajax. Here is my code:
#model Domain.CounterObject
#using (Ajax.BeginForm("Count", "CounterObject", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "my-control" }))
{
<div id="my-control" class="bid-object">
<div>
<span class="title">#Html.Encode(Model.Title)</span>
</div>
<div>
<span class="display">#Html.Encode(Model.GetFinalDate())</span>
</div>
<div>
<span class="display">#Html.Encode(Model.GetValue())</span>
</div>
<div>
<input type="submit" value="Count" />
</div>
</div>
}
In my controller I have this code:
[HttpPost]
public PartialViewResult Count(CounterObject counter)
{
// Special work here
return PartialView(counter);
}
The problem is that my CounterObject counter I receive in my Count method is always null. How can I pass a value from my page to the controller?
I receive in my Count method is always null
First of all you are not submitting anything from the form then how does the binding happens?
If the user is not allowed to edit the values but still you want to submit them through the form then you have to use hidden fields along with them.
For ex.
<div>
<span class="title">#Html.Encode(Model.Title)</span>
#Html.HiddenFor(m => m.Title)
</div>
Note that the hidden fields should have the same names as the properties to make the binding happen successfully.
It is better to have properties in the Model that store the GetFinalDate() and GetValue() results so you can easily bind the things like in Title.
You'll have to define a input field with a name and id that the ModelBinder can then Bind to your CounterObject.
You could use #Html.EditorForModel once and then inspect the generated Html to see what kind of name/id pairs it is generating. With those you can go on and handcraft your Html if you wanted to.
use
<span class="title">#Html.Encode(Model.Title)</span>
<div class="editor-field">#Html.EditorFor(Model => Model.Title)<div>
//For other fields
In this way you can bind to your object.

PartialView form validation then calling a controller method in MVC3

I am very new to MVC3 and having problems wrapping my head around things. Right now I have a partial view which I have simplified below:
#model blah.blah.blah.blah.ForumPost
#using (Html.BeginForm()) {
<fieldset>
<legend>ForumPost</legend>
<div class="editor-label">
#Html.LabelFor(model => model.ForumID)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ForumID)
#Html.ValidationMessageFor(model => model.ForumID)
</div>
<p>
<input type="submit" value="Create" />
#Html.ValidationSummary(true)
</p>
</fieldset>
<div>
#Html.ActionLink("Back to List", "Index")
</div>
}
I am not what to do for form validation. I have been trying to using jquery validation but I cant seem to find a good example that fits what I am doing and just get lost. I was basing it off this example here but it isn't enough.
After I am done I want to call a method in some code and I am not really sure of a clean way to do this. The way I have it currently working is using an ajax call and it's really ugly. Also a colleague suggested I pass the method an actual forum post but I don't know how. The code for the method I want to call is below:
public void PostToForum(ForumPost post)
{
UserService cu = new UserService();
int PostUserID = cu.GetUserIDByUsername(base.User.Identity.Name);
if (this.ModelState.IsValid)
{
ForumPost nfp = service.CreateForumPost(post);
}
}
Anyone have some tips? Thanks.
I can provide more code if it's necessary.
Html forms are usually submitted to controller actions:
[HttpPost]
public ActionResult Create(ForumPost model)
{
if (!ModelState.IsValid)
{
// validation failed => redisplay the view so that the user can fix the errors
return View(model);
}
// at this stage the model is valid => process it:
service.CreateForumPost(model);
return ...
}
Now since this is a partial view you must be careful with the view you are returning from this controller action as well as the model. If you don't use AJAX you should return the entire parent view and the parent view model. If you use an AjaxForm then you could only work with the partial model and view. Also in this case in the event of success you could return a Json result to the view to indicate this success so that the javascript handler that will be executed could take the respective actions.

Categories

Resources