Create Linked Database Record in MVC5 Controller - asp.net-mvc

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?

Related

MVC: One Model, Two Views - Passing Parent to Child

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

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.

ASP MVC AJAX link not updating page after it completes

I'm following Stephen Walther's tutorial on safely deleting via POST + AJAX (found here: http://stephenwalther.com/blog/archive/2009/01/21/asp.net-mvc-tip-46-ndash-donrsquot-use-delete-links-because.aspx), but am having problems he did not mention in his article.
I modified his code slightly, so that I have an Index.aspx file which contains a Movies.ascx partial view. The partial view is strongly typed, and is where my delete link lives. The delete logic is as such:
public ActionResult Delete(int id)
{
var movieToDelete = (from m in _entities.MovieSet
where m.Id == id
select m).FirstOrDefault();
_entities.DeleteObject(movieToDelete);
_entities.SaveChanges();
return RedirectToAction("Index");
}
When a delete link is clicked, Delete is called, the object is deleted, and RedirectToAction returned. However, the page doesn't update. If you click the link again, an exception is thrown (since the object with that ID is already deleted), and the page updates. Remembering I was working with partials, I changed the return to
return PartialView();
thinking it would fix the problem, but it had no effect. The object is still deleted and the page never refreshed.
I'm stumped - not sure where the problem is at this point, seems to be a something wrong with my return, but I'm not sure.
since you're deleting using ajax, why don't you try delete the object from the dom or return the new data on success. Check out these examples: here or here.

Render action return View(); form problem

I'm new to MVC, so please bear with me. :-)
I've got a strongly typed "Story" View. This View (story) can have Comments.
I've created two Views (not partials) for my Comments controller "ListStoryComments" and "CreateStoryComment", which do what their names imply. These Views are included in the Story View using RenderAction, e.g.:
<!-- List comments -->
<h2>All Comments</h2>
<% Html.RenderAction("ListStoryComments", "Comments", new { id = Model.Story.Id }); %>
<!-- Create new comment -->
<% Html.RenderAction("CreateStoryComment", "Comments", new { id = Model.Story.Id }); %>
(I pass in the Story id in order to list related comments).
All works as I hoped, except, when I post a new comment using the form, it returns the current (parent) View, but the Comments form field is still showing the last content I typed in and the ListStoryComments View isn’t updated to show the new story.
Basically, the page is being loaded from cache, as if I had pressed the browser’s back button. If I press f5 it will try to repost the form. If I reload the page manually (reenter the URL in the browser's address bar), and then press f5, I will see my new content and the empty form field, which is my desired result.
For completeness, my CreateStoryComment action looks like this:
[HttpPost]
public ActionResult CreateStoryComment([Bind(Exclude = "Id, Timestamp, ByUserId, ForUserId")]Comment commentToCreate)
{
try
{
commentToCreate.ByUserId = userGuid;
commentToCreate.ForUserId = userGuid;
commentToCreate.StoryId = 2; // hard-coded for testing
_repository.CreateComment(commentToCreate);
return View();
}
catch
{
return View();
}
}
The answer is to use return RedirectToAction(). Using this enforces the PRG pattern and achieves my goal.
My earlier comment to my original post did cause an error, that I guess I'm still confused about, but this works:
return RedirectToAction("Details", "Steps", new { id = "2" });
I, and this is a personal opinion, think you've tackled this the wrong way.
Personally I would;
Create a view called ListStories.
Create a partial view that lists the
stories.
Create a partial view to create a
story.
When you want to add a story, simply
show the add story html.
Then when the user presses a button
you do a jQuery postback, add the
new story and return a PartialView
of either the new story or all the
stories.
If you return a partial view of all
stories then replace the bounding
div that contains all the stories
with the new data.
If you return only a single story
then append it to the end of the div
containing the stories.
I know this means a lot of re-work and it sounds complex and like a lot of work but doing it like this means greater flexibility later on because you can re-use the partial views or you can make a change once and all views using that partial view are now updated.
also, using jQuery means that the adding of stories looks seemless w/out any obvious post back which is nice.
Since the problem seems to be caching, you can simply disable/limit caching.
Add the following attribute to your actions:
[OutputCache(Duration = 0, VaryByParam = "none")]
This will tell the browser to cache the page, but for 0 seconds. When the post reloads the page, you should see the desired results.
The answer is to make sure your form action is properly set. If you have used renderaction and not set the form controller and action manually then the action will be the current URL.
Try:
<% using (Html.BeginForm("ActionName", "ControllerName")) {%>
Instead of:
<% using (Html.BeginForm()) {%>

Resources