I have two pages I need, and want to show for the url /index and /review. The only difference between the two pages is on the review I will have a review comment section to show and Submit button. Otherwise the two pages are identical. I thought I could create a user control for the main content.
However, if I could say under the Review action, flag to show review stuff and them return the rest of the index action.
How would you (generic you) do this?
Model example
public class MyModel
{
public bool ShowCommentsSection { get; set; }
}
Controller actions
public ActionResult Index()
{
var myModel = new MyModel();
//Note: ShowCommentsSection (and the bool type) is false by default.
return View(myModel);
}
public ActionResult Review()
{
var myModel = new MyModel
{
ShowCommentsSection = true
};
//Note that we are telling the view engine to return the Index view
return View("Index", myModel);
}
View (somewhere inside your index.aspx probably)
<% if(Model.ShowCommentsSection) { %>
<% Html.RenderPartial("Reviews/ReviewPartial", Model); %>
<% } %>
Or, if Razor is your cup of tea:
#if(Model.ShowCommentsSection) {
Html.RenderPartial("Reviews/ReviewPartial", Model);
}
// ... Make comment section visible
return Index();
Why don't you just always include the review comment form and use client side code to show or hide it. Considering no additional data is required for the review page beyond that of which already needed on the index a round trip to a controller is not necessary. This would allow you to delete the review action and view.
Related
I have a weird need in an ASP.NET MVC 3 application which blocks my current progress. Here is the case:
I have a little search engine for the products and I render this search engine on multiple pages. This SE makes a HTTP POST request to product controller's search action. It fine till here.
Let's assume that I am on home controller's index action (/home/index). I make a search and check if ModelState.IsValid. As a result, it is not valid. So, I should return this back with the entered model (so that user won't lose the values) and model state errors. But when I do that I ended up with different URL (/product/search) as expected.
If I do a redirect, I lose the ModelState and cannot display error messages.
I have different solutions so far and they all look dirty. Any idea?
Edit
Here is a little project which demonstrates this:
This is the ProductController:
public class ProductController : Controller {
[HttpPost]
public ActionResult Search(SearchModel searchModel) {
if (ModelState.IsValid) {
//Do some stuff...
return RedirectToAction("Index", "SearchResult");
}
return View(searchModel);
}
}
This is the SearchModel:
public class SearchModel {
[Required]
public string ProductCategory { get; set; }
[Required]
public string ProductName { get; set; }
}
This is the *_SearchPartial*:
#model MvcApplication20.SearchModel
#using (Html.BeginForm("search", "product"))
{
#Html.EditorForModel()
<input type="submit" value="Search" />
}
And finally this is the Home controller Index action view which renders the *_SearchPartial*:
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
#Html.Partial("_SearchPartialView")
Here, when I submit the form and if the model state fails, how should I proceed at the Product controller Search action?
Here, when I submit the form and if the model state fails, how should
I proceed at the Product controller Search action?
Normally in this case you should render the _SearchPartialView but not as a partial but as a full view with layout so that the user can fix his errors. No need to stay at Home/Index in this case:
[HttpPost]
public ActionResult Search(SearchModel searchModel) {
if (ModelState.IsValid) {
//Do some stuff...
return RedirectToAction("Index", "SearchResult");
}
// since we are returning a view instead of a partial view,
// the _SearchPartialView template should be displayed with the layout
return View("_SearchPartialView", searchModel);
}
And if you wanted to stay on the same page upon error you could use an AJAX call to perform the search. So you would AJAXify this search form and then in the success callback test the result of the Search action and based on it decide whether to refresh the partial in order to show the error or redirect to the results action using window.location.href:
something along the lines of:
$(document).on('submit', '#searchForm', function() {
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function(result) {
if (result.redirectTo) {
// no validation errors we can redirect now:
window.location.href = result.redirectTo;
} else {
// there were validation errors, refresh the partial to show them
$('#searchContainer').html(result);
// if you want to enable client side validation
// with jquery unobtrusive validate for this search form
// don't forget to call the .parse method here
// since we are updating the DOM dynamically and we
// need to reattach client side validators to the new elements:
// $.validator.unobtrusive.parse(result);
}
}
});
return false;
});
This obviously assumes that you have now wrapped the partial call in a div with id="searchContainer" and that you provided an id="searchForm" when generating the search form:
<div id="searchContainer">
#Html.Partial("_SearchPartialView")
</div>
and now the search action:
[HttpPost]
public ActionResult Search(SearchModel searchModel) {
if (ModelState.IsValid) {
//Do some stuff...
return Json(new { redirectTo = Url.Action("Index", "SearchResult") });
}
return PartialView("_SearchPartialView", searchModel);
}
As far as I know the ModelState is lost when doing a RedirectToAction, the solution would be to save the modelstate in the TempData one example of this, that I'm using is this:
http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx#prg
This is also discussed in various posts for instance MVC Transfer Data Between Views
I have this Action:
public ActionResult AddCategory(Category newCategory)
{
...//newCategory.Name is filled up
return new Json(true);
}
And a view that post at this Action:
#using(Html.BeginForm)
{
#Html.TextBoxFor(Model.Name)
.....
}
Now I want to reuse this Action, but in another page.
But in this new View, I already have a Html.TextBox("name") at another . Its a kind of DashBoard.
This new View, have a property NewCategory inside the Model:
public class MyViewModel
{
public Category NewCategory{get;set;}
}
If I do this:
#using(Ajax.BeginForm)
{
#Html.TextBoxFor(Model.NewCategory.Name)
.....
}
Wont work, because my action dont expect any Prefix, in this case NewCategory.
Of course, I can manually call the Action, but doing this I lost built-in validation(I am using DataAnnotation with Unobtrusive validation).
Its a scenario that I fall from time to time
The best choice that I have now is duplicate the Action:
public ActionResult AddCategory([Bind(Prefix="NewCategory")]Category category)
{
...
return new Json(true);
}
Call Html.RenderAction to reuse action result in other views, and pass name parameter to it for your model, for example:
Use Html.RenderAction("AddCategory", new {name = Model.CategoryName})
The solution is to create another method with the "same" assignature:
[ActionName("AddCategory")]
public ActionResult AddCategory2([Bind(Prefix="NewCategory2")]Category category)
{
...
return new Json(true);
}
what i understand from your question is that you are in some View X and you want to render AddCategory View inside this view and Model of View X contains NewCategory which is of type Category and accepted by AddCategory View as model. if so you just have to call render partial in your View X
<%Html.RenderPartial("AddCategory", Model.NewCategory);%>
I have a simple form based off a model called Visitor. I'd like to have a search button by one of the id text fields so the user can click the button and have the page populate with visitor information: first name, last name, etc. In Web Forms I would do something like this:
page_load(){
person = businessManager.FindPersonById(Convert.ToInt32(txtId.Text));
txtFirstName.Text = person.FirstName;
txtLastName.Text = person.LastName;
...
}
Prior to the search button, my view form called SignIn worked just fine; posted the data to the controller and did its thing:
[HttpPost]
public ActionResult SignIn(Visitor visitor) {
if (ModelState.IsValid) {
visitorRepoistory.Add(visitor);
visitorRepoistory.Save();
return RedirectToAction("/");
} else {
return View(new VisitorFormViewModel(visitor));
}
}
But now that I have a search button placed on my view form, I'm totally lost. I don't know how to wire the search button to the controller so I can: 1.) Look up the data and 2.) Return it back to the sign in form to populate the fields. What are the steps I need to take to accomplish this?
Thanks.
This question has been duplicated many times on SO
Multiple forms in ASP.NET MVC
But to answer your question you can have multiple forms on one page and have different Actions handle the submits. That's what the link above outlines.
Specific to your case:
View
<% Html.BeginForm("Search", "<ControllerName>"); %>
Your search controls here
<% Html.EndForm(); %>
<% Html.BeginForm("SignIn", "<ControllerName>"); %>
Your signin controls here
<% Html.EndForm(); %>
Controller
[HttpPost]
public ActionResult Search(FormCollection collection)
{
Do your search and return a view
}
[HttpPost]
public ActionResult SignIn(Visitor visitor)
{
if (ModelState.IsValid) {
visitorRepoistory.Add(visitor);
visitorRepoistory.Save();
return RedirectToAction("/");
} else {
return View(new VisitorFormViewModel(visitor));
}
}
How to check from inside the View if there are any ModelState errors for specific key (key is the field key of the Model)
If you haven't yet, check out this wiki article on the MVC pattern.
Keep in mind that your view is only supposed to be in charge of displaying data. As such, you should try to keep the amount of logic in your view to a minimum. If possible, then, handle ModelState errors (as ModelState errors are the result of a failed model binding attempt) in your controller:
public class HomeController : Controller
{
public ActionResult Index()
{
if (!ModelState.IsValid)
{
return RedirectToAction("wherever");
}
return View();
}
}
If you have to handle ModelState errors in your view, you can do so like this:
<% if (ViewData.ModelState.IsValidField("key")) { %>
model state is valid
<% } %>
But keep in mind that you can accomplish the same thing with your controller and thus remove the unnecessary logic from your view. To do so, you could place the ModelState logic in your controller:
public class HomeController : Controller
{
public ActionResult Index()
{
if (!ModelState.IsValidField("key"))
{
TempData["ErrorMessage"] = "not valid";
}
else
{
TempData["ErrorMessage"] = "valid";
}
return View();
}
}
And then, in your view, you can reference the TempData message, which relieves the view of any unnecessary logic-making:
<%= TempData["ErrorMessage"] %>
I am calling my partial view like this:
<% Html.RenderPartial("~/controls/users.ascx"); %>
Can I pass parameters to partial view? How will I access them in the actual users.ascx page?
You could pass a model object to the partial (for example a list of strings):
<% Html.RenderPartial("~/controls/users.ascx", new string[] { "foo", "bar" }); %>
Then you strongly type the partial and the Model property will be of the appropriate type:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.Collections.Generic.IEnumerable<string>>" %>
<% foreach (var item in Model) { %>
<div><%= Html.Encode(item) %></div>
<% } %>
There is another overload for RenderPartial that will pass your model through.
<% Html.RenderPartial("~/controls/users.ascx", modelGoesHere); %>
How to access? Just like you normally would with any view:
<%= Model.MagicSauce %>
It took a while to sink in, but MVC means you use a Model, a View, and a Controller one way or another for just about everything, including Partial Views. How all three elements fit together can be a little intimidating at first. I'd never done one until just now, and it works --Woohoo!
Hope this helps the next person.... Sorry, I'm using razor instead of .Net forms. I'm also pulling data from a SQL Server database into Entity Framework, which a developer is likely to use. I also probably went a little overboard with WebGrid, which is so much more elegant than a foreach statement. A basic #webgrid.GetHtml() will display every column and row.
Background
In this working example, users have uploaded pictures. Their pictures are displayed in their edit form using a partial view. The ImageID and FileName metadata is persisted in SQL Server while the file itself is persisted in the ~/Content/UserPictures directory.
I know it's kinda half vast, because all the details of uploading and editing personal data isn't shown. Just the germane parts of using a Partial View are focused on, albeit with some bonus EF thrown in. The namespace is MVCApp3 for S&G.
Partial View Model ViewModels.cs
The SQL Server Images table includes many more columns in addition to ImageID and FileName such as [Caption], [Description], a MD5 hash to prevent the same image being uploaded multiple times, and upload date. The ViewModel distills the Entity down to the bare minimum needed for a user to see their pictures.
public class Picts
{
public int ImageID { get; set; }
public string FileName { get; set; }
}
Main View View Edit.cshtml
Note the cast/convert to strongly type the ViewData[].
#Html.Partial(
partialViewName: "Picts",
model: (IEnumerable<MVCApp3.Models.Picts>)ViewData["Picts"]
)
If you don't set the strongly-typed model to use for the Partial View you'll get a "The model item passed into the dictionary is of type 'System.Data.Entity.DynamicProxies..." error because it assumes you're passing the parent/master model.
Partial View View Picts.cshtml (the whole file contents is shown)
#model IEnumerable<MVCApp3.Models.Picts>
#{
var pictsgrid = new WebGrid(Model);
}
#pictsgrid.GetHtml(
tableStyle: "grid",
displayHeader: false,
alternatingRowStyle: "alt",
columns: pictsgrid.Columns(
pictsgrid.Column(format:#<text><img src="#Url.Content("~/Content/Users/" + #item.FileName)" alt="#item.ImageID" width="200" />
#Html.ActionLink(linkText: "Delete", actionName: "DeletePicture", routeValues: new { id = #item.ImageID })
</text>)
))
Controller IdentityController.cs
Set the data content into the ViewData["MyPartialViewModelKeyName"] your partial view will consume. You can give the dictionary key any name you want, but I gave it ViewData["Picts"] to be consistent with the partial view file name and its view model class definition.
Because the pictures may be shared amongst multiple users there is a many-to-many table with a corresponding PITA query in Entity Framework using nested froms and inner joins to return just the pictures belonging to, or shared with, a user:
public class IdentityController : Controller
{
private EzPL8Entities db = new EzPL8Entities();
// GET: /Identity/Edit/5
[Authorize]
public ActionResult Edit(int? id)
{
if (id == null)
return new HttpNotFoundResult("This doesn't exist");
// get main form data
ezpl8_UsersPhysicalIdentity ezIDobj = db.ezpl8_UsersPhysicalIdentity.Find(id)
// http://learnentityframework.com/LearnEntityFramework/tutorials/many-to-many-relationships-in-the-entity-data-model/
// get partial form data for just this user's pictures
ViewData["Picts"] = (from user in db.ezpl8_Users
from ui in user.ezpl8_Images
join image in db.ezpl8_Images
on ui.ImageID equals image.ImageID
where user.ezpl8_UserID == id
select new Picts
{
FileName = image.FileName,
ImageID = image.ImageID
}
).ToList();
return View(ezIDobj);
}
// Here's the Partial View Controller --not much to it!
public ViewResult Picts(int id)
{
return View(ViewData["Picts"]);
}
[Authorize] //you have to at least be logged on
public ActionResult DeletePicture(int id)
{
//ToDo: better security so a user can't delete another user's picture
// TempData["ezpl8_UserID"]
ezpl8_Images i = db.ezpl8_Images.Find(id);
if (i != null)
{
var path = System.IO.Path.Combine(Server.MapPath("~/Content/Users"), i.FileName);
System.IO.File.Delete(path: path);
db.ezpl8_Images.Remove(i);
db.SaveChanges();
}
return Redirect(Request.UrlReferrer.ToString());
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
// get main form data
ezpl8_UsersPhysicalIdentity ezIDobj = db.ezpl8_UsersPhysicalIdentity.Find(id)
// http://learnentityframework.com/LearnEntityFramework/tutorials/many-to-many-relationships-in-the-entity-data-model/
// get partial form data for just this user's pictures
ViewData["Picts"] = (from user in db.ezpl8_Users
from ui in user.ezpl8_Images
join image in db.ezpl8_Images
on ui.ImageID equals image.ImageID
where user.ezpl8_UserID == id
select new Picts
{
FileName = image.FileName,
ImageID = image.ImageID
}
).ToList();
return View(ezIDobj);
}
// Here's the Partial View Controller --not much to it!
public ViewResult Picts(int id)
{
return View(ViewData["Picts"]);
}
[Authorize] //you have to at least be logged on
public ActionResult DeletePicture(int id)
{
//ToDo: better security so a user can't delete another user's picture
// TempData["ezpl8_UserID"]
ezpl8_Images i = db.ezpl8_Images.Find(id);
if (i != null)
{
var path = System.IO.Path.Combine(Server.MapPath("~/Content/Users"), i.FileName);
System.IO.File.Delete(path: path);
db.ezpl8_Images.Remove(i);
db.SaveChanges();
}
return Redirect(Request.UrlReferrer.ToString());
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}