How do i submit the complete viewmodel to an other view? - asp.net-mvc

I'm using MVC3 (razor) and i'm trying to get the following working.
I have a list of snippets. These snippets have some general settings and then have a translation for an unknown ammount of languages.
Now i'm trying to do the following:
On the 'Create' page (url: Screen) of a snippet i set the general settings. under that there is a list of filled translations (empty at the start). When you press the 'Opslaan' button, i want the form to save the general settings and the list of translations.
When i push the 'Add' button i want to submit the complete viewmodel (settings + list of translations) to an other page where i can fill in a translation. After i filled in a translations, i want to return to this page (url: Screen). Here, a translation is filled in the list.
Now i'm doing someting wrong, because i cant get the viewmodel to submit to the 2nd page.
this is my code:
button 'add translation':
#Html.ActionLink("Add", "CreateTranslation", new { oSnippeteditviewmodel = this.Model }, null)
SnippetController:
public ActionResult Create()
{
SnippetEditViewModel oItem = new SnippetEditViewModel();
oItem.lSnippetsPerLanguage = new List<SnippetPerLanguageEditViewModel>();
return View(oItem);
}
[HttpPost]
public ActionResult Create(SnippetEditViewModel Snippeteditviewmodel)
{
if (ModelState.IsValid)
{
Snippeteditviewmodel.Bookmark = Snippeteditviewmodel.Bookmark.Replace(' ', '_');
_repoSnippet.CreateSnippet(Snippeteditviewmodel);
return RedirectToAction("Index");
}
return View(Snippeteditviewmodel);
}
public ActionResult CreateTranslation(SnippetEditViewModel oSnippeteditviewmodel)
{
return View(oSnippeteditviewmodel);
}
And in the controller, action CreateTranslation the object 'oSnippeteditviewmodel' stays null.
annyone who has a simular problem? Or a solution?

First, you should try to generate action link like this
#Html.ActionLink("Add", "CreateTranslation", this.Model, null)
In this case mvc will try to pass correctly serialized model values for your link and if your model is simple enough, CreateTranslations will get its model correctly. But, I would not do it that way. Generated link is static. What if user changes Snippet values on client side? When it comes to adding Translation, all the changed form values will be lost (Link will pass initial, server generated values). So, you should try one of the followings
Create the form with two buttons, one for CratingTranslation and one for Saving. When creating translation, dynamically change form's action and method parameters to GET the CreateTranslation action. This way, form will serialize all its current Snippet settings and pass to desired action, and you get the current snippet model passed to CreateTranslation action.
Use ajax. Just dynamically inject translation creation input fields into same page. That's simple and more user friendly (no bundle of navigations), and more http traffic is reserved (Passing all the translations and snippet to second page, and then returning all of these + 1 translation could get you in trouble). I would reccomend this approach. This is far more simple than first or your approaches.

I am not getting you properly but if you wanna add data by "create" controller then you don't need to specify object in "oSnippeteditviewmodel". You can get all form data by
Request.Form["controlName"]
and fill the Snippeteditviewmodel data member by above and save that.

Related

Can I append data from controller to url and still render same page?

I have a simple MVC application that presents an IQ test to the person.
I have a controller call TestMyIQController and on this controller I have three action methods and corresponding to three views.
1) Index.cshtml
2) Questions.cshtml
3) Score.cshtml
On the first page localhost:12345/TestMyIQ/Index of the application I ask the user to input their First and Last name and click button Next to go to second page.
On the second page of the application localhost:12345/TestMyIQ/Questions I present a few multiple choice questions and I have the user select True/False for each question. Once the user complete all the question she can click button Submit to see her IQ score.
On the final score page localhost:12345/TestMyIQ/Score I show the score via a ViewBag object.
However, I want to know how to modified the final page url so that I can append the First Name of the user? For example if the person name is John Doe then I want my final score url will be like this localhost:12345/TestMyIQ/Score?firstname=John.
Note it is not necessary to append the First Name to all other url...because I only want it on the final score page.
Please help me. Thanks
Considering you received the first name in the Questions action, and added as a property of your model, you can add it to your form as a hidden field:
Questions.cshtml
using(Html.BeginForm())
{
#Html.HiddenFor(x => x.UserFirstName);
// rest of form
}
TestMyIQController
[HttpPost]
public ActionResult Question(QuestionModel model)
{
// form processing
return RedirectToAction("Score", new{ firstName = model.UserFirstName })
}
Because in the Question page you stored it as a hidden field, it will only show up in the url of the score page.
But keep in mind that while this is ok if you're learning and just want to do some cool stuff to show your friends, this isn't the best way to handle all of this.
EDIT to add more info on the problems of using this method.
The negative is that anyone can change the URL. Nothing stops me from changing ?firstName=foo to ?firstName=bar. Second, names can contain invalid characters for URLs, which will need to be encoded. Third, it's overall bad design.
There are better ways to handle this, but it depends on the what you need from it. Will the users be able to share the url? If not, you can add it to the Session. This is definitely easier to implement in your current design. If they'll do, you could store the result in a SQL table, and share the url as ?scoreId=f88f9426-04d7-4ae2-8e15-a4bbd8d6faad.
Not sure I understand your needs, but you dont have to store the name in url, you can use for example session variable, but if you insist on it being on url, why not just redirect(urlWithParameter).
For a good listing of possibilities, see
Msprogrammer
Session may be choice to store the name and retrieve it on the score page.
[HttpPost]
public ActionResult Index(UserModel model)
{
// save the name
Session["FirstName"] = model.FirstName;
return RedirectToAction("Questions")
}
On the score method, retrieve the name from session
[HttpPost]
public ActionResult Score(ScoreModel model)
{
// save the name
string firstName = (string)Session["FirstName"];
return RedirectToAction("Index")
}
Hope it helps.

MVC3 Validation on Sub-Form

I have a page that I want to have a list of items (header items) with some details and a link to view and a link to edit them. In addition at the top of the list I want to have a form to get the basic details (start and end date) and create a new header item using that data.
What I've done:
created a header controller
created an index view (gets list of items)
created a startHeader view (has a form with start, end date and a button, form has an actionName of "addHeader")
added the "ValidationMessageFor" for the start and end date
added the startHeader view as a partial view to the index view
Everything generally appears to be working except for some validation. The required field validation appears to be working as expected (i.e. if you don't put in a start date and click the button it says "Start Date is required").
However I've added some additional validation that doesn't appear to be working:
added IValidatableObject interface to the header class
added validation code
if (this.StartDate > this.EndDate)
{
yield return new ValidationResult("Start Date must be prior to End Date.", new[] { "StartDate" });
}
when the button is pressed this code runs as expected
however it doesn't stay on the index screen and display the validation message
rather it goes through to the addHeader view
i had expected this to work the same as the required field validation and display the validation message
Is there something I'm missing here?
why is this validation running, but not displaying the error message?
do i need to add something to get this validation to run and remain on the page?
is there a better method of doing something like this?
It could be that your required field validation is displaying via Unobtrusive Client Validation (your model might include the data annotation [Required]). This validation runs on the client-side via JavaScript, and so doesn't post back to the server at all.
The validation code you have written, contrastingly, runs after the form has posted-back to the server. You will need to trap this in you addHeader() method. Something along these lines:
[HttpPost]
public ActionResult addHeader(addHeader model)
{
if (!ModelState.IsValid)
{
return View();
}
else
{
//Do Work to add your header...
return View("Index");
}
}
As long as you have #Html.ValidationSummary(true) in your view, then it will display the ValidationResult automatically. See this for more details.

ASP.NET MVC Form post

I'm a beginner in ASP.NET MVC application. What I'm trying to do is creating a form with some inputs that the user will be filling up, and once the user click the post button, I want the form to be posted with the information filled up and ready for printing. The way I'm doing it right now is as follow:
// the controller that returns the initial form using ReportCreate.aspx which creates a Html form
public ActionResult ReportCreate()
{
return View(viewData);
}
// my post action which gets the information for the submitted form
// and use the ReportPost.aspx to view a similar page as ReportCreate.aspx but with all the Html.TexBox inputs replaced with their values obtained from the submitted form
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ReportCreate(FormCollection form)
{
ReportFormData formData = new ReportFormData();
formData.Date = form["date"];
formData.Company = form["company"];
formData.SiteNameA = form["siteNameA"];
formData.SiteNameB = form["siteNameB"];
formData.FreqBand = form["freqBand"];
formData.FileNumber = form["fileNumber"];
formData.ResponseDate = form["responseDate"];
formData.SiteAddressA = form["siteAddressA"];
formData.SiteAddressB = form["siteAddressB"];
this.TempData.Add("viewData", viewData);
return View("ReportPost", formData);
}
What I don't like about this way, is that I have to aspx pages (ReportCreate.aspx & ReportPost.aspx) that I need to keep similar and modify both of them together if I need to do any changes to the look of the form. I feel there should be a more professional way to handle this common issue. I tried to look it up online, but couldn't get anything. Please let me know. Thanks a lot in advance.
If you want to display the posted data in the same form just use the same aspx page as when you created the data.
However the usual way is to have one page for:
Create - to input values first time and after a succesful input redirect to
Details - where the data is not on a form but as regular text
If you need to modify the data use
Edit
To display a collection of data use
Index
Another point to note is that you dont need to use manually set all of the values from the form to your ReportFormData class, instead do:
[HttpPost]
public ActionResult Create(ReportFormData formData)
{
if(!ModelState.Isvalid){
return View(formData);
}
else
{
RedirectToAction("Index");
}
}
If all the formatting is the same other than the textbox should be a label, just use a conditional in your view to determine if you should display a textbox or not.
<%if(model.ReadOnly){%><%=Html.LabelFor(m => m.Company)%><%else%><%=Html.TextBoxFor(m => m.Company)%><%}%>

asp.net mvc: What is the correct way to return html from controller to refresh select list?

I am new to ASP.NET MVC, particularly ajax operations. I have a form with a jquery dialog for adding items to a drop-down list. This posts to the controller action.
If nothing (ie void method) is returned from the Controller Action the page returns having updated the database, but obviously there no chnage to the form. What would be the best practice in updating the drop down list with the added id/value and selecting the item.
I think my options are:
1) Construct and return the html manually that makes up the new <select> tag
[this would be easy enough and work, but seems like I am missing something]
2) Use some kind of "helper" to construct the new html
[This seems to make sense]
3) Only return the id/value and add this to the list and select the item
[This seems like an overkill considering the item needs to be placed in the correct order etc]
4) Use some kind of Partial View
[Does this mean creating additional forms within ascx controls? not sure how this would effect submitting the main form its on? Also unless this is reusable by passing in parameters(not sure how thats done) maybe 2 is the option?]
UPDATE:
Having looked around a bit, it seems that generating html withing the controller is not a good idea. I have seen other posts that render partialviews to strings which I guess is what I need and separates concerns (since the html bits are in the ascx). Any comments on whether that is good practice.
look at the ContentResult you can specify the mime type of what you return (text/html)
You could alternatively make a control that take a IEnumerable of whatever you put in the selectlist, and build it using the view engine. That way you keep the formatting of the html (in this case a list of options) into a view, and not in your code.
<%# Control Language="C#"Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Article>>"%>
<%foreach (var article in Model){%>
<option><%:article.Title %></option>
<%} %>
I think I would go for that second one
From what I understood, the jQuery dialog contains a form that, when submitted, will post to an action which updates the database with some information. You want to get the newly added database information and update the same form that was used to trigger the database update.
If that is the case, then I think the best clean and logical option is to return JSON serialization of the items to be put in the drop down right after you update the database. Then, using jQuery, you would clear the drop down and append option tags into it.
You can also write a new, seperate action that returns the JSON serialization of the database objects you need. You would have jQuery call another post to this action as a callback to your first ajax post (the one used to update the database).
Here is a quick snippet
public ActionResult UpdateDatabase(string something)
{
/// update the database
IEnumerable<Items> items = getItemsFromDatabase(); // or w/e
var vals = items.Select(x=> new { value = x.ID, text = x.Name }); // something similar
return Json(vals);
}
Personally, I would write a separate function that returns JSON. This ensure separation of concerns, and gives me a function I can use in many different places.
Returning a JsonResult with all the items is the most versatile and least-bandwidth intensive solution as long as you are happy to iterate through the list in jQuery and update your drop-down list.
Using a partial view is nice for HTML that you can .load(...) directly into your select, but less versatile.
I would go with the JsonResult.
In your Controller:
public JsonResult UpdateItem(string sItem)
{
// 1. Insert new item into database if not exist...
// {update code here}
// 2. retrieve items from database:
IEnumerable<Item> Items = GetItems();
// 3. return enumerable list in JSON format:
return new JsonResult{ Data = new {Items = Items, Result = "OK" }};
}
On client-side:
Iterate through Items array and add the items to your list.

ASP.NET MVC Form repopulation

I have a controller with two actions:
[AcceptVerbs("GET")]
public ActionResult Add()
{
PrepareViewDataForAddAction();
return View();
}
[AcceptVerbs("POST")]
public ActionResult Add([GigBinderAttribute]Gig gig, FormCollection formCollection)
{
if (ViewData.ModelState.IsValid)
{
GigManager.Save(gig);
return RedirectToAction("Index", gig.ID);
}
PrepareViewDataForAddAction();
return View(gig);
}
As you can see, when the form posts its data, the Add action uses a GigBinder (An implemenation of IModelBinder)
In this binder I have:
if (int.TryParse(bindingContext.HttpContext.Request.Form["StartDate.Hour"], out hour))
{
gig.StartDate.Hour = hour;
}
else
{
bindingContext.ModelState.AddModelError("Doors", "You need to tell us when the doors open");
}
The form contains a text box with id "StartDate.Hour".
As you can see above, the GigBinder tests to see that the user has typed in an integer into the textbox with id "StartDate.Hour". If not, a model error is added to the modelstate using AddModelError.
Since the gigs property gigs.StartDate.Hour is strongly typed, I cannot set its value to, for example, "TEST" if the user has typed this into the forms textbox.
Hence, I cant set the value of gigs.StartDate.Hour since the user has entered a string rather than an integer.
Since the Add Action returns the view and passes the model (return View(gig);) if the modelstate is invalid, when the form is re-displayed with validation mssages, the value "TEST" is not displayed in the textbox. Instead, it will be the default value of gig.StartDate.Hour.
How do I get round this problem? I really stuck!
I think the problem is that your ViewModel does not match closely enough with your View. It's really important in MVC that your ViewModel matches your View as closely as possible.
In your ViewModel you're assuming an integer, but in your View you're using a TextBox to render the property, which will allow any kind of text. There's a mismatch here and the difficulties you are experiencing trying to map them is a symptom of the mismatch.
I think you should either:
1. Change the type of the ViewModel property to string and then do validation in your controller to ensure the string entered is actually a number or:
2. Change the control that the View renders to a control that will only allow a number to be entered via a custom control or Javascript validation (as #Qun Wang recommends)
Personally, I'd recommend option 1. That way the ViewModel is not dependent on the View implementation.
Could you do this in your PrepareViewDataForAddAction method?..
if (!ViewData.ModelState.IsValid)
{
ViewData["StartDate.Hour"] = "Error";
}
The other fields on the form will still populate based on the properties of the Gig object.
I think you need to do some basic client side validation first.
don't allow it to post to the server.

Resources