ASP.NET MVC 3 Adding comments in article view - asp.net-mvc

I have article model with public ICollection<Comment> Comments { get; set; } and comment model. I have created view for article (Details view) and I want to show everything from model article (not problem) and comments to article to and after comments then show form for adding comment to article (not in other page, I want it in the view with article). For now I have this:
#model SkMoravanSvitavka.Models.Article
#{
ViewBag.Title = "Zobrazit";
}
<h2>Zobrazit</h2>
<fieldset>
<legend>Article</legend>
<div class="display-label">Title</div>
<div class="display-field">#Model.Title</div>
<div class="display-label">Text</div>
<div class="display-field">#Model.Text</div>
<div class="display-label">PublishedDate</div>
<div class="display-field">#String.Format("{0:g}", Model.PublishedDate)</div>
</fieldset>
#if (Model.Comments != null)
{
foreach (var comment in Model.Comments)
{
#Html.Partial("_Comment", comment)
}
}
<p>
#Html.ActionLink("Edit", "Edit", new { id = Model.ArticleID }) |
#Html.ActionLink("Back to List", "Index")
</p>
It shows article and there is partial view for all comments to article. And now I am not sure how to add form for adding comments. Thank you
Edit: Here is my comment controller and create methods (vytvorit = create in czech :) ):
public ActionResult Vytvorit(int articleID)
{
var newComment = new Comment();
newComment.articleID = articleID; // this will be sent from the ArticleDetails View, hold on :).
newComment.Date = DateTime.Now;
return View(newComment);
}
[HttpPost]
public ActionResult Vytvorit(Comment commentEntity)
{
db.Comments.Add(commentEntity);
db.SaveChanges();
return RedirectToAction("Zobrazit", "Clanek", new { id = commentEntity.articleID });
}
When I change #Html.RenderAction to #Html.Action it works. It is showing textbox for comment and I can add comment but there is problem that it not just add textbox but it add my site again (not just partial view but all view) and I am sure I add Create view for comment as partial.

Create a new Partial view, make it a strongly typed one of type Comment.
from the scaffolding templates, choose "Create" template.
handle the normal add new scenario of the comment.
add this Partial view to the Article details page.
Note that when you are about to save a new comment, you will need to get the hosting Article ID.
Hope it's now clear, if not, let me know.
update: assuming that you will add the "AddComment" partial view to your "Article details" view, you can do the following in order to add the comment.
1- Modify the GET (Create) action inside your CommentController as the following:
public ActionResult Create(int articleID)
{
var newComment = new CommentEntity();
newComment.articleID = articleID; // this will be sent from the ArticleDetails View, hold on :).
return View(newComment);
}
1- make the POST (Create) action like this:
[HttpPost]
public ActionResult Create(Comment commentEntity)
{
// Complete the rest..
}
2- The Partial view for the comment will look something like this:
#model NGO.Models.Comment
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<div class="addcommentbox">
<h2> Add Comment </h2>
#Html.TextAreaFor(model => model.Description)
<div class="ErrorMessage">
#Html.ValidationMessageFor(model => model.Description)
</div>
<input id="addComment" type="button" onclick="" value="Add" />
</div>
}
3- inside the ArticleDetails page, in the desired place that you need the add comment section to show, use RenderAction method to render the AddComment Partial view like the following:
Html.RenderAction("Create", "Comment",new {articleID = Model.ID});
the previous line will call the GET(Create) action inside CommentColtroller and pass the current Article ID, so the AddComment Partial view will come already populated with the current Article ID (and this is what we want).
that's it, feel free to ask me if it's not clear yet, and do let me know if it worked for you

I strongly recommend you to create view model for your article page. like below one;
public class ArticleViewModel {
public Article _article {get;set;}
//this is for the comment submit section
public Comment _comment {get;set;}
//this for the comments that you will view
public IQueryable<Comment> _comment {get;set;}
}
after that, pass this to your view from your controller and make your view strongly-typed to this ArticleViewModel class.
Create a section which is wrapped inside form tag as below;
#using(Html.BeginForm()){
#*Put your code inside here.
Create inputs for sending comments. like below;*#
#Html.TextboxFor(Model._comment.Content)
}
and then create a method for that;
[HttpPost]
[ActionName("name_of_your_article_page_action")]
public ActionResult Create(Comment commentEntity) {
}
NOTE : Of course, you do not need to create a seperate viewmodel. you can hardcode the name your inputs but this makes it hard to use validation and other kind of things. but not impossible to use validation though !

Related

Adding a dropdown list outside or RenderBody call

I have a .NET MVC project with Razor views and I would like to implement search functionality that consists of a dropdown list, a text box and a search button.
The problem is that I would like to implement this search snippet in my _Layout.cshtml file outside of the #RenderBody() call. This means that the search functionality would be accessible on every page (it would be located at the very top right corner).
I'm trying to find out what is a good way of implementing this. I can get it to work but it would involve adding same code (do get dropdown values) to all controllers and actions.
ViewBag.States = new SelectList(db.States, "Id", "Name");
Is there a better way to implement this? It feels very repetitive to do it this way.
You can have a child action method which returns the partial view needed for your header and call this action method in your layout.
Create a view model for the properties needed.
public class AllPageVm
{
public int SelectedItem { set; get; }
public List<SelectListItem> Items { set; get; }
}
Now create an action method in any of your controller. Mark this action method with ChildActionOnly decorator.
public class HomeController : Controller
{
[ChildActionOnly]
public ActionResult HeaderSearch()
{
var vm = new AllPageVm()
{
Items = db.States
.Select(a => new SelectListItem() {Value = a.Id.ToString(),
Text = a.Name})
.ToList()
};
return PartialView(vm);
}
Now in the HeaderSearch.cshtml partial view, you can render whatever markup you want for your search header. Here is a simple example to render the dropdown. You may update this part to include whatever markup you want (Ex : a form tag which has textbox, dropdown and the button etc)
#model AllPageVm
<div>
<label>Select one state</label>
#Html.DropDownListFor(a => a.SelectedItem, Model.Items, "Select")
</div>
Now in your layout, you can call this child action method
<div class="container body-content">
#Html.Action("HeaderSearch", "Home")
#RenderBody()
<hr/>
<footer>
<p>© #DateTime.Now.Year - My ASP.NET Application</p>
</footer>
</div>
Make sure you are calling PartialView method from the HeaderSearch child action method instead of View method. If you call View method, it will recursively call the same method and you will get a StackOverflow exception

How can i maintain objects between postbacks?

I'm not so experienced using MVC. I'm dealing with this situation. Everything works well until call the HttpPost method where has all its members null. I don't know why is not persisting all the data on it.
And everything works well, because I can see the data in my Html page, only when the user submit the information is when happens this.
[HttpGet]
public ActionResult DoTest()
{
Worksheet w = new Worksheet(..);
return View(w);
}
[HttpPost]
public ActionResult DoTest(Worksheet worksheet)
{
return PartialView("_Problems", worksheet);
}
This is class which I'm using.
public class Worksheet
{
public Worksheet() { }
public Worksheet(string title, List<Problem> problems)
{
this.Title = title;
this.Problems = problems;
}
public Worksheet(IEnumerable<Problem> problems, WorksheetMetadata metadata, ProblemRepositoryHistory history)
{
this.Metadata = metadata;
this.Problems = problems.ToList();
this.History = history;
}
public string Title { get; set; }
public List<Problem> Problems { get; set; } // Problem is an abstract class
public WorksheetMetadata Metadata { get; set; }
public ProblemRepositoryHistory History { get; set; }
}
And my razor view.... the razor view shows successfully my view. I realized something rare, please note in my 5 and 6 lines that I have HiddenFor method, well if I used that, when calls HTTPPOST persists the data, I don't know why.
#model Contoso.ExercisesLibrary.Core.Worksheet
<div id="problemList">
<h2>#Html.DisplayFor(model => model.Metadata.ExerciseName)</h2>
#Html.HiddenFor(model => model.Metadata.ExerciseName)
#Html.HiddenFor(model => model.Metadata.ObjectiveFullName)
#for (int i = 0; i < Model.Problems.Count; i++)
{
<div>
#Html.Partial(Contoso.ExercisesLibrary.ExerciseMap.GetProblemView(Model.Problems[i]), Model.Problems[i])
</div>
}
</div>
UPDATE
I'm using a static class to get the view name, but as I'm testing I'm just using this Partial view
#model Contoso.ExercisesLibrary.AbsoluteArithmetic.Problem1
<div>
<span style="padding:3px; font-size:18px;">#Model.Number1</span>
<span style="padding:5px; font-size:18px;">+</span>
<span style="padding:5px; font-size:18px;">#Model.Number2</span>
<span style="padding:5px; font-size:18px;">=</span>
<span style="font-size:18px">
#Html.EditorFor(model => model.Result, new { style = "width:60px; font-size:18px;" })
#Html.ValidationMessageFor(model => model.Result)
</span>
</div>
#section Scripts {
}
And here the user do the post
#model Contoso.ExercisesLibrary.Core.Worksheet
<form method="post">
#Html.Partial("_Problems", Model)
<input type="submit" value="Continue" />
</form>
The Model Binder will 'bind' or link input fields on your view to the model. It will not bind display fields (like label), that is why you need the HiddenFor it will add an <input type="hidden" which will then be bound to the Model when you Post.
You can use 'TempData'. It is used to pass data from current request to subsequent request means incase of redirection.
This link also helps you.
TempData
SO Tempdata
Make sure your form tag looks like the following, for instance the controller name, action method, the form method and an id for the form. I am referring to the #using statement. In my case the controller name is RunLogEntry, the action method is Create and the id is form.
Normal Post from View to Controller
#using (Html.BeginForm("Create", "RunLogEntry", FormMethod.Post, new { id = "form", enctype = "multipart/form-data" }))
{
<div id="main">
#Html.Partial("_RunLogEntryPartialView", Model)
</div>
}
If you want to post via Jquery, could do the following:
$.post("/RunLogEntry/LogFileConfirmation",
$("#form").serialize(),
function (data) {
//this is the success event
//do anything here you like
}, "html");
You must specify a form with correct attribute in your view to perform post action
<form action="Test/DoTest" method="post">
...
</form>
or
#using(Html.BeginForm("DoTest", "Test", FormMethod.Post)) {
...
}
The second is recommended.
Put your entire HTML code under:
#using(Html.BeginForm())
tag.

Create reusable form contact component in asp.net mvc

I'm newbie at asp.net mvc, I'm trying to develop a reusable contact form component for asp.net mvc .
I have tryed to do it creating a PartialView with a form, I don't know if this is the better approach, for create a reusable component
My PartialView is
#model MyModel.ContactData
#using (Html.BeginForm("ContactForm", "ContactForm", FormMethod.Post)) {
<fieldset>
<p>
#Html.LabelFor(model => model.MailAddress)
#Html.EditorFor(model => model.MailAddress)
</p>
<p>
#Html.LabelFor(model => model.Message)
#Html.TextAreaFor(model => model.Message)
</p>
<input type="submit" value="Save" />
</fieldset>
}
The problems start with the controller, the controller is a specific controller for only this partialView.
public class ContactFormController : Controller
{
[HttpPost]
public ActionResult ContactForm(ContactData contactData)
{
if (ModelState.IsValid)
{
return PartialView("MessageSend");
}
return PartialView();
}
}
My problem is in case of some of the required fields are empty, in that case the controller returns only the partial view, not returning the partival view inside its parent context. I have tryed calling the PartialView from parent View as #Html.Partial, #Html.RenderAction, #Html.RenderPartial and the same occurs.
How can I return the partial view Inside it's parent Context? I have tried with
return View(ParentViewsName, contactData) but I dislike it because on form submitting it changes the url on address bar from /Contact to /ContactForm/ContactForm.
Perphaps I'm trying to create a reusable component with a wrong approach? It's better to update with ajax only the PartialView? Alternatives?
Thanks
From my understanding, you wish to display status message which is a partial view after user successfully submits the form. I think tempdata will be apt for this kind of situation.
public class ContactFormController : Controller
{
[HttpPost]
public ActionResult ContactForm(ContactData contactData)
{
if (ModelState.IsValid)
{
TempData["success"] = true;
return RedirectToAction("parentpage");
}
return View(contactData);
}
}
In parent page, check whether TempData["success"] is null and display the partial view "MessageSend".
Finally as Sundeep explains I have done this with ajax like this example Partial ASP.NET MVC View submit.
Thanks for your help.

rename mvc model object in Razor View

I'm using MVC 4 and usually Visual Studio will create all the views for you. I have one form that just has one field and I want to just embed the create form into the Index View.
So the Index View has something like #model IEnumerable<Models.LinkModel>
So I access it by iterating through the Model collection.
But if I try to embed the form for the create action I need #model Models.LinkModel
and it is accessed by Model.Name as well. Is there a way to do this or use a different variable name?
Ok here is some extra info.
SO I have a model.
public class LinkModel
{
public string LinkUrl {get;set;}
}
I have a controller that has the Create and Index ActionResults.
Now in the Index view I have
#model IEnumerable<Models.LinkModel>
#{
ViewBag.Title = "Links";
}
I can do all my fancy logic to list all the links.
#foreach(link in Model)
{
<p>link.LinkUrl<p>
}
The Create View has this
#model Models.LinkModel // Note that it is just one item not IEnumerable
#{
ViewBag.Title = "Add Link";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset class="editor-fieldset">
<legend>LinkModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.LinkUrl)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.LinkUrl)
</div>
<p>
<input type="submit" value="Add Link" />
</p>
</fieldset>
}
Now it seems pretty stupid to have a create form for just one field. I want to put this form on the Index page. Problem is that I access the object using the variable Model. I wanted to know if there is a way to have two seperate instances or be able to access the Model objects with different names.
Have a composite model with a list of items and 1 single item for the create
public class IndexModel {
public LinkModel CreateModel {get; set;}
public IEnumerable<LinkModel> Items {get; set;}
}
#model IndexModel
#using(Html.BeginForm("create")) {
#Html.EditorFor(m => m.CreateModel.Name);
}
#foreach(var item in Model.Items) {
#item.Name
}

ASP.Net MVC2 Custom Templates Loading via Ajax and Model Updating

I have a view model with a collection of other objects in it.
public ParentViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<ChildViewModel> Child { get; set; }
}
public ChildViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
}
In one of my views I pass in a ParentViewModel as the model, and then use
<%: Html.EditorFor(x => x) %>
Which display a form for the Id and Name properties.
When the user clicks a button I call an action via Ajax to load in a partial view which takes a collection of Child:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Child>>" %>
<%: Html.EditorFor(x => x) %>
which then uses the custom template Child to display a form for each Child passed in.
The problem I'm having is that the form created by the Child custom template does not use the naming conventions used by the DefaultModelBinder.
ie the field name is (when loaded by Ajax):
[0].FirstName
instead of:
Child[0].FirstName
So the Edit action in my controller:
[HttpPost]
public virtual ActionResult Edit(int id, FormCollection formValues)
{
ParentViewModel parent = new ParentViewModel();
UpdateModel(parent);
return View(parent);
}
to recreate a ParentViewModel from the submitted form does not work.
I'm wondering what the best way to accomplish loading in Custom Templates via Ajax and then being able to use UpdateModel is.
Couple of things to start with is that you need to remember the default ModelBinder is recursive and it will try and work out what it needs to do ... so quite clever. The other thing to remember is you don't need to use the html helpers, actual html works fine as well :-)
So, first with the Model, nothing different here ..
public class ParentViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<ChildViewModel> Child { get; set; }
}
public class ChildViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
}
Parent partial view - this takes an instance of the ParentViewModel
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ParentViewModel>" %>
<h2>Parent</h2>
<%: Html.TextBox("parent.Name", Model.Name) %>
<%: Html.Hidden("parent.Id", Model.Id) %>
<% foreach (ChildViewModel childViewModel in Model.Child)
{
Html.RenderPartial("Child", childViewModel);
}
%>
Child partial view - this takes a single instance of the ChildViewModel
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ChildViewModel>" %>
<h3>Child</h3>
<%: Html.Hidden("parent.Child.index", Model.Id) %>
<%: Html.Hidden(string.Format("parent.Child[{0}].Id", Model.Id), Model.Id)%>
<%: Html.TextBox(string.Format("parent.Child[{0}].FirstName", Model.Id), Model.FirstName) %>
Something to note at this point is that the index value is what is used for working out the unique record in the list. This does not need to be incremental value.
So, how do you call this? Well in the Index action which is going to display the data it needs to be passed in. I have setup some demo data and returned it in the ViewData dictionary to the index view.
So controller action ...
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
ViewData["Parent"] = GetData();
return View();
}
private ParentViewModel GetData()
{
var result = new ParentViewModel
{
Id = 1,
Name = "Parent name",
Child = new List<ChildViewModel>
{
new ChildViewModel {Id = 2, FirstName = "first child"},
new ChildViewModel {Id = 3, FirstName = "second child"}
}
};
return result;
}
In the real world you would call a data service etc.
And finally the contents of the Index view:
<form action="<%: Url.Action("Edit") %>" method="post">
<% if (ViewData["Parent"] != null) { %>
<%
Html.RenderPartial("Parent", ViewData["Parent"]); %>
<% } %>
<input type="submit" />
</form>
Saving
So now we have the data displayed how do we get it back into an action? Well this is something which the default model binder will do for you on simple data types in relatively complex formations. So you can setup the basic format of the action which you want to post to as:
[HttpPost]
public ActionResult Edit(ParentViewModel parent)
{
}
This will give you the updated details with the original ids (from the hidden fields) so you can update/edit as required.
New children through Ajax
You mentioned in your question loading in custom templates via ajax, do you mean how to give the user an option of adding in another child without postback?
If so, you do something like this ...
Add action - Need an action which will return a new ChildViewModel
[HttpPost]
public ActionResult Add()
{
var result = new ChildViewModel();
result.Id = 4;
result.FirstName = "** to update **";
return View("Child", result);
}
I've given it an id for easy of demo purposes.
You then need a way of calling the code, so the only view you need to update is the main Index view. This will include the javascript to get the action result, the link to call the code and a target HTML tag for the html to be appended to. Also don't forget to add your reference to jQuery in the master page or at the top of the view.
Index view - updated!
<script type="text/javascript">
function add() {
$.ajax(
{
type: "POST",
url: "<%: Url.Action("Add", "Home") %>",
success: function(result) {
$('#newchild').after(result);
},
error: function(req, status, error) {
}
});
}
</script>
<form action="<%: Url.Action("Edit") %>" method="post">
<% if (ViewData["Parent"] != null) { %>
<%
Html.RenderPartial("Parent", ViewData["Parent"]); %>
<% } %>
<div id="newchild"></div>
<br /><br />
<input type="submit" /> add child
</form>
This will call the add action, and append the response when it returns to the newChild div above the submit button.
I hope the long post is useful.
Enjoy :-)
Hmm... i personally would recommend to use a JSON result, instead of a HTML result, that you fiddle in the page...
makes the system cleaner. and your postback working ;-)
I found another way to accomplish this which works in my particular situation.
Instead of loading in a partial via via Ajax that is strongly typed to a child collection like:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Child>>" %>
I created a strongly typed view to the parent type and then called EditorFor on the list like so:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Parent>" %>
<%: Html.EditorFor(x => x.ChildList) %>
This then calls a Custom Display Template and the result is that all the HTML elements get named correctly and the Default Model binder can put everything back together.

Resources