MVC: One Model, Two Views - Passing Parent to Child - asp.net-mvc

I am having a problem creating a Model across two Views. I have not been able to find a example of this although I suspect part of the reason is not knowing how to phrase the question well. Please see below for a description of what I am trying to do.
I have a Model called a Referral which has a many-to-one relationship with a Model called Visitor (a Visitor can make multiple Referrals). When the user navigates to the Referral section of the web page, they should be prompted to first fill out the form for the Visitor info. Once the Visitor info has been filled out, the user should be taken to a new form (new View) where they fill out the Referral info. Since a portion of the Referral info is the Visitor info, I need to pass one to the other.
The way I've gone about this is to have two separate Views, one for getting Visitor info and one for getting Referral info. Each has a Get and a Post with the Post for Visitor returning a RedirectToAction with the name of the Referral View and a Referral object with the Visitor info populated:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateVisitor(Visitor visitor)
{
if (ModelState.IsValid)
{
var visitorExists = db.Visitors.Any(v => v.FirstName == visitor.FirstName && v.LastName == visitor.LastName);
if (visitorExists == false)
{
db.Visitors.Add(visitor);
db.SaveChanges();
}
Referral referral = new Referral();
referral.Visitor = visitor;
return RedirectToAction("CreateReferral", referral);
}
return View("Index");
}
The idea is to pass this info to the next (Referral) form so that the Visitor portion is filled out and all that's left to do is enter the Referral info. I tried passing the partially created view the Get but then the Post began to complain that there was another method taking in the same model.
//GET: Referrals/CreateReferral
public ActionResult CreateReferral(Referral referral)
{
return View(referral);
}
Is there a way to pass this information that won't cause the Post method to complain? Maybe via a Visitor ID that I can use in the Get method to create the referral?
Any input is greatly appreciated. I've been trying to figure it out myself but haven't been able to find relevant info online and am worried about going too far down the wrong path.
Hope to hear from you,
Yusif Nurizade

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.

Create Linked Database Record in MVC5 Controller

I'm just starting out with ASP.NET MVC, and anything but the basics throws me at the moment, so please bear with me.
I am writing a little application รก la UserVoice, whereby users can post ideas, and other users can vote for those posts.
I have a View which displays the detail of a particular Post (suggestion). On that View I have button labelled Vote which, when clicked, should create a record in my database's PostVote table linked to the Post. I just can't get this work, though! Here's what I have:
Post > Details.cshtml
Vote
PostVoteController.cs
[HttpPost]
public ActionResult VoteForPost(int id, Post post)
{
if (ModelState.IsValid)
{
db.PostVote.Add(new PostVote
{
PostID = id,
VoteTime = DateTime.Now,
VoteUser = UserHelper.GetUsername()
});
db.SaveChanges();
}
return View(post);
}
The only reason I'm requiring the post parameter in my method is because, as I understand it, unless I want to use Ajax I need to use an ActionResult and return a View, and so I've tried to copy the code in the Edit method of the PostController which was generated automatically by Visual Studio and works fine. The idea is just to reload the Post Details View after the new PostVote record has been created,
I realise I've probably written nonsense, but nothing I'm trying works. When I click the Vote button, I'm directed to a page with a URL of http://localhost:58974/PostVote/VoteForPost/1, which I sort of understand, but is obviously wrong. I can't figure out how to just pass the ID of the Post to which the new PostVote record should be linked as a variable rather than it forming part of the URL. Obviously I get a 404 error because the page doesn't exist.
Update
Ok, thanks to the comments posted I've now got this in my View:
#using (Html.BeginForm("VoteForPost", "PostVote", new { id = Model.PostID }, FormMethod.Post))
{
<button type="submit">Vote</button>
}
This is successfully creating the records as desired, but is still trying to redirect the browser to http://localhost:58974/PostVote/VoteForPost/1 afterwards. Bearing mind that the Controller (PostView.cs) doing the record creation is not the one which displayed the Post Details in the first place (that's Post.cs), how do I get my method to just return me back to the Details View I'm already looking at?

MVC - Returning a model from controller for Edit Profile

I'm new to MVC and not finding an example on the proper way of returning a typed model to the view. I need to create a view to allow an authenticated user to edit some of their profile.
As a test I created a controller action that returns an ApplicationUser model and view page that displays the profile in form fields. This works, but it contains all of the user profile and that's not what I want. To test I wrote the following:
Public ActionResult EditProfile()
{
ApplicationUser user = UserManager.FindById(User.Identity.GetUserId());
return View(user)
}
Of course, this does work and I realize I can display whatever I like in the view, though I'm not sure if it's a best practice to be returning all of the user's profile to the view when I only need to allow editing a few of their settings.
So I created a new ViewModel with only the fields needed and a new view based on that model. It works, but I still don't feel I'm doing it properly. In the controller action I did this:
public ActionResult EditProfile()
{
ApplicationUser user = UserManager.FindById(User.Identity.GetUserId());
EditProfileViewModel model = new EditProfileViewModel();
model.Email = user.Email;
model.Company = user.Company;
model.Name = user.Name;
model.PhoneNumber = user.PhoneNumber;
model.CountryCode = user.CountryCode;
model.StateProvince = user.StateProvince;
model.Language = user.Language;
model.StateProvinceCode = user.StateProvinceCode;
return View(model);
}
This just seems unnecessary and horrible to maintain. I'm sorry for asking such a basic question. I have honestly searched the 'net for examples and I know they are out there, but I'm not finding what I feel applies to my question.
Btw, the controller is decorated with [Authorize] and so I assume this action/view is only be accessed by an authenticated user.
I will understand better if someone could show me an example of the proper way of populating a model and passing it to the controller. Seems this should be able to be done in just a couple of lines of code.
Well, first of all there is nothing to worried about as everything is handled on server side. if you wish to create custom model its seems overload as you have to now do 2 more transaction
Copy data from original model to customized model.
In [HttpPost] action you have to get original model data and overwrite newly updated field from customized model.
Better if you use original model, just take necessary fields in view and in [HttpPost] action you need to fetch original record and replace fields get from the view.

How do I pass the userId into the model ASP.NET MVC?

I've had a thorough search around but really can't find anything addressing the scenario I'm facing (oddly because I'd have thought it's quite a common thing to do).
Background
I'm creating an application with ASP.NET MVC 4 and Entity Framework 5 Code First. For the purpose of this question, think of it as a blogging application with posts and users.
Project
The post model requires that every post have a corresponding UserId.
With the ASP.NET MVC 4 Membership it is easy to find the username of the person logged in with
User.Identity.Name.
This isn't ideal, we want the ID, but a query such as this can search the db for the name and get the ID.
db.UserProfiles.Single(a => a.UserName == User.Identity.Name);
Problem
The problem arises when trying to create a post. Model.IsValid is false, as no UserId is being passed in from the view. Obviously, as the user isn't expected to enter their ID.
I've tried putting the ID value into the ViewBag and using a #Html.Hidden() field in the view, however I've had no success with this. Model.IsValid always returns false.
Should this information be input through the create view? Or should it be done directly in the controller? Its quite a frustrating problem as I have the information and just need to figure how to pass it into the model.
CONTROLLER CODE
This is basically just the default scaffolded code. The commented code is how I tried setting the model value directly from the controller, however that was little more than trial and error.
//
// POST: /Post/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Post post)
{
if (ModelState.IsValid)
{
//var userId = db.UserProfiles.Single(a => a.UserName == User.Identity.Name);
//post.User.UserId = userId.UserId;
db.Posts.Add(post);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
Be careful with hidden fields. Anyone could put whatever value they want in that field (i.e. they could spoof another user). You'd be better off caching the ID in the session at login, and using that value.
This is a typical case where you want to create an EditModel as a data transfer object (DTO) between your view and controller layers.
Create a class BlogPostEditModel that has all properties you need the user to fill in when creating a new blog post. Then, map this type (e.g. using AutoMapper) to your BlogPost entity, and fill in the user ID as well.
To use built-in validation such as Model.IsValid(), put the data annotations attributes on the DTO instead.
Honestly, I would have the value assigned via the controller. If you had someone messing with your html via Firebug, they could actually change the id before it was passed and submitted to your form. I would remove it from your Create view and submit from the controller.

How do i submit the complete viewmodel to an other view?

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.

Resources