Decided to learn ASP.NET MVC and instantly got stuck on something simple.
In Web Forms user controls allowed to separate application into components based on functionality and facilitated reuse. It seems partial views are supposed to do something similar in ASP.NET MVC, but either I am getting this wrong, or each visible page is handled by single controller and it is not possible to delegate certain page portions to separate controllers without hard-coding these controller relationships.
RenderAction can render a partial view and insert resulting HTML in the page, but if we want this view to be refreshed when the user clicks on some link within this view together with the entire page, we need all the partial view links to refer to the parent controller?
For example:
Home\Index.aspx:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">Home</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
...
<% Html.RenderAction("Index", "Posts"); %>
...
Posts\Index.aspx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<BlogEngine.Models.PostsViewModel>" %>
<% foreach(var item in Model.Posts){ %>
<p class="postMeta"><%: string.Format("{0} {1}", item.CreatedAt, item.CreatedBy) %></p>
<h1><%: item.Title %></h1>
<div><%: item.Content %></div>
<% } %>
<% if (Model.CurrentPage > 0){ %>
<%: Html.ActionLink("Newer posts", "Index", "Home", new { page=Model.CurrentPage - 1}, null) %>
<%} %>
<% if (Model.CurrentPage + 1 < Model.TotalPages) { %>
<%: Html.ActionLink("Older posts", "Index", "Home", new { page=Model.CurrentPage + 1}, null) %>
<% } %>
PostsController:
public class PostsController : Controller
{
private const int PostsPerPage = 2;
private readonly IPostRepository _postRepository;
public PostsController()
{
...
}
public ActionResult Index(int page = 0)
{
var model = new PostsViewModel();
int totalPages = 1;
model.CurrentPage = page;
model.Posts = _postRepository.GetPosts(page, PostsPerPage, out totalPages);
model.TotalPages = totalPages;
return PartialView(model);
}
}
There's got to be a better way than this?
I don't know if I understand correctly but you could load this Partial View in a using Ajax (by jQuery), and when you need to refresh only this part of content you reload the element. Something like this:
In Javascript:
function LoadComments(page) {
//It'll return a partial view
$("#comments").load("<%=Url.Action("Posts", "Index")%>?page=" + page);
}
$(document).ready(function() {
LoadComments(0);
});
Inside of yoru PartialView you need to render a javascript code to call the "reload" of the next (page) content, so call LoadComments(index-page)...
Look the Ajax APi of jQuery: http://api.jquery.com/category/ajax/
Cheers
Related
I have DropDownList and a submit button on my page. DropDownListhas list of data from the database, and while selecting value from the dropdown and then clicking on submit button, I am getting number of records of selected dropdown list value in partial view of the main view page. My code is giving proper output. I have bind View to controller through model. Using html.hiddenfor.
But whenever I click on submit button as usual my whole page gets refreshed. But I need to refresh only partial view rather than whole page.
This is my code which is working properly. But by this code my whole page is getting refreshed. And I want to prevent it. :
view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ApricaCRMEvent.Models.CRM.DatabaseEntities.CRM_Doctor_Request>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
MDLNoDDLIndex
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script type="text/javascript">
function TestFun() {
var mdlno = $("#ddlMDLNo").val();
var txtmdlno = document.getElementById("Request_For_Id");
txtmdlno.value = mdlno;
}
</script>
<div>
<h2>Search by MDLNo</h2>
<% using (Html.BeginForm())
{ %>
Select MDLno
<%= Html.DropDownList("ddlMDLNo", ViewData["MDLno"] as SelectList, "--Select One--", new { onchange = "TestFun()" })%>
<%: Html.HiddenFor(model => model.Request_For_Id) %>
<input type="submit" value="search" name="SearchMDLNo" id="btnclick" />
<% } %>
</div>
<div id="showtable"> //partial view
<% if (ViewBag.load == true)
{ %>
<%Html.RenderAction("MDLNoDataList"); %>
<% } %>
</div>
</asp:Content>
Controller:
// Search by mdl no
public ActionResult MDLNoDDLIndex()
{
ViewData["MDLno"] = new SelectList(CRMSearchReportDL.getAllMDLno(), "Request_For_Id", "Request_For_Id");
ViewBag.load = false;
return View();
}
[HttpPost]
public ActionResult MDLNoDDLIndex(CRM_Doctor_Request model)
{
ViewData["MDLno"] = new SelectList(CRMSearchReportDL.getAllMDLno(), "Request_For_Id", "Request_For_Id",model.Request_For_Id);
ViewBag.load = true;
return View();
}
public ActionResult MDLNoDataList()
{
List<CRM_Doctor_Request> drlist = new List<CRM_Doctor_Request>();
return PartialView(drlist);
}
[HttpPost]
public ActionResult MDLNoDataList(CRM_Doctor_Request model)
{
return PartialView(CRMSearchReportDL.getMDLNoWiseDetails(model.Request_For_Id));
}
You can use jQuery to do this for you. Capture the form submit in jQuery and instead of performing a full form post through the browser, submit your form data to a controller action using jQuery's .ajax() method.
Something like this:
$.ajax({
url: urlToControllerAction,
data: {
ddlMDLNo: ddlMDLNo,
Request_For_Id: Request_For_Id
},
type: 'POST',
success: function (results) {
var partialData = $(results);
$('#showtable').html(partialData);
},
error: function (xhr, ajaxOptions, thrownError) {
// do something
}
});
Can you please help me understand what is the issue with generic collection? Thanks in advance!
Error: The model item passed into the dictionary is of type 'System.Collections.Generic.List1[DomainModel.Product]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable1[DomainModel.Entities.Product]'.
MODEL:
namespace DomainModel.Concrete
{
public class SqlProductsRepository : IProductsRepository
{
private Table<Product> productsTable;
public SqlProductsRepository(string connString)
{
productsTable = (new ProaductDataContext(connString)).GetTable<Product>();
}
public IQueryable<Product> Products
{
get { return productsTable; }
}
}
}
INTERFACE
namespace DomainModel.Abstract
{
public interface IProductsRepository
{
IQueryable<Product> Products { get; }
}
}
VIEW
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/ViewMaster.Master"
Inherits="System.Web.Mvc.ViewPage<IEnumerable<DomainModel.Entities.Product>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Products
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% foreach (var product in Model)
{ %>
<div class = "item">
<h3> <%=product.Name%></h3>
<%= product.Description%>
<h4><%= product.Price.ToString("c")%></h4>
</div>
<%} %>
</asp:Content>
The error message tells you all your need to know;
The model item passed into the dictionary is of type
'System.Collections.Generic.List1[DomainModel.Product]',
but this dictionary requires a model item of type
'System.Collections.Generic.IEnumerable1[DomainModel.Entities.Product]'.
If you look at your view you can see
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/ViewMaster.Master"
Inherits="System.Web.Mvc.ViewPage<IEnumerable<DomainModel.Entities.Product>>" %>
The Inherits="System.Web.Mvc.ViewPage<IEnumerable<DomainModel.Entities.Product>>" is what's tripping you up. You're passing in an IEnumerable of DomainModel.Product, but you're expecting something else. It's a bit strange you have two classes named the same within close to the same namespace, but never mind, you need to ensure you're using the same class, in the same namespace in both the controller and the view.
So I'd try changing your view to become
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/ViewMaster.Master"
Inherits="System.Web.Mvc.ViewPage<IEnumerable<DomainModel.Product>>" %>
Then try to figure out why you have two Product classes :)
I have the following MVC2 view that is strongly typed with a viewmodel, the viewmodel contains a list of values from one db table, I need to display a single value from a second table in the view, this is my view code
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<CustomerDatabase.WebUI.Models.CustomerSitesListViewModel> " %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Customer Sites
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% foreach (var customerSite in Model.CustomerSites) { %>
<% Html.RenderPartial("CustomerSiteSummary", customerSite); %>
<%} %>
</asp:Content>
This is the viewmodel, notice i am including a Customer member in the viewmodel as i want to display the customer name in addition to the list of customer sites
namespace CustomerDatabase.WebUI.Models
{
public class CustomerSitesListViewModel
{
public IList<CustomerSite> CustomerSites { get; set; }
public PagingInfo PagingInfo { get; set; }
public Customer customer { get; set; }
}
}
This is my controller code for the view
public ViewResult List([DefaultValue(1)] int page)
{
var customerSitesToShow = customerSiteRepository.CustomerSites;
var viewModel = new CustomerSitesListViewModel
{
CustomerSites = customerSitesToShow.Skip((page - 1) * PageSize).Take(PageSize).ToList(),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = customerSitesToShow.Count()
}
};
return View(viewModel); //Passed to view as ViewData.Model (or simply model)
}
This is my partial view that renders the list,
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<CustomerDatabase.Domain.Entities.CustomerSite>" %>
<div class="item">
<div class="customer-list-item">
<h2><%:Model.customer.CustomerName%></h2>
<%: Model.AddressLine1 %>
<%: Model.AddressLine2%>
Although intellisense lets me access the customer object from the view with
<h2><%:Model.customer.CustomerName%></h2>
An error is thrown when i navigate to the view,
Object reference not set to an instance of an object.
Source Error:
Line 7: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
Line 8: <% foreach (var customerSite in Model.CustomerSites) { %>
Line 9: <%:Model.customer.CustomerName%>
Line 10: <% Html.RenderPartial("CustomerSiteSummary", customerSite); %>
Line 11: <%}
I think the error is due to the view rendering a list, i tried changing the viewmodel member to
public IList<Customer> {get; set;}
but this doesn't work either.
Can anyone suggest a way i can achieve this or offer any advice on where i am going wrong this is one problem i haven't been able to resolve after hours or researching on the Internet?
It looks like one of the model properties are not initialized. If you add a breakpoint on that line and check the variables I'm pretty sure you'll find 1 that is null.
I have started doing asp.net mvc programming and like it more everyday.
Most of the examples I have seen use separate views for viewing and editing details of a specific entity.
E.g. - table of music albums linking to separate 'detail' and 'update' views
[Action] | Title | Artist
detail, update | Uuuh Baby | Barry White
detail, update | Mr Mojo | Barry White
With mvc how can I achieve a design where the R and the U (CRUD) are represented in a single view, and furthermore where the user can edit separate parts of the view, thus limiting the amount of data the user can edit before saving?
Example mockup - editing album detials:
I have achieved such a design with ajax calls, but Im curious how to do this without ajax.
Parts of my own take on this can be seen below. I use a flag (enum EditCode)
indicating which part of the view, if any, that has to render a form. Is such a design in accordance with the framework, could it be done more elegantly?
AlbumController
public class AlbumController : Controller
{
public ActionResult Index()
{
var albumDetails = from ManageVM in state.AlbumState.ToList()
select ManageVM.Value.Detail;
return View(albumDetails);
}
public ActionResult Manage(int albumId, EditCode editCode)
{
(state.AlbumState[albumId] as ManageVM).EditCode = (EditCode)editCode;
ViewData["albumId"] = albumId;
return View(state.AlbumState[albumId]);
}
[HttpGet]
public ActionResult Edit(int albumId, EditCode editCode)
{
return RedirectToAction("Manage", new { albumId = albumId, editCode = editCode });
}
// edit album details
[HttpPost]
public ActionResult EditDetail(int albumId, Detail details)
{
(state.AlbumState[albumId] as ManageVM).Detail = details;
return RedirectToAction("Manage", new { albumId = albumId, editCode = EditCode.NoEdit });// zero being standard
}
// edit album thought
[HttpPost]
public ActionResult EditThoughts(int albumId, List<Thought> thoughts)
{
(state.AlbumState[albumId] as ManageVM).Thoughts = thoughts;
return RedirectToAction("Manage", new { albumId = albumId, editCode = EditCode.NoEdit });// zero being standard
}
Flag - EditCode
public enum EditCode
{
NoEdit,
Details,
Genres,
Thoughts
}
Mangae view
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcApplication1.Controllers.ManageVM>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Manage
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Manage</h2>
<% if(Model.EditCode == MvcApplication1.Controllers.EditCode.Details)
{%>
<% Html.RenderPartial("_EditDetails", Model.Detail); %>
<% }else{%>
<% Html.RenderPartial("_ShowDetails", Model.Detail); %>
<% } %>
<hr />
<% if(Model.EditCode == MvcApplication1.Controllers.EditCode.Thoughts)
{%>
<% Html.RenderPartial("_EditThoughts", Model.Thoughts); %>
<% }else{%>
<% Html.RenderPartial("_ShowThoughts", Model.Thoughts); %>
<% } %>
The last part feels messy to me. I would recommend wrapping those with Html Helpers to clean up your view.
<h2>Manage</h2>
<% Html.RenderDetailsPartial(Model.EditCode) %>
<hr />
<% Html.RenderThoughtsPartial(Model.EditCode) %>
Let the HTMLHelper determine which view to use based on the EditCode.
having a problem passing ViewData.Model to the partial views. It always is defaulting to null even if I equate it to a result query. I cannot access the strongly typed data because the Model is null. My current code is this,
ViewPage
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% Html.RenderPartial("header", this.ViewData.Model); %>
<% Html.RenderPartial("test", this.ViewData.Model); %>
<div id="userControls">
</div>
</asp:Content>
UserControl - header
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<testMVCProject.Models.information>" %>
<h2>
ACReport</h2>
<p>
id:
<%= Html.Encode(Model.id) %>
</p>
<p>
type:
<%= Html.Encode(Model.type) %>
</p>
UserControl - test
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<testMVCProject.Models.information>" %>
<% using (Ajax.BeginForm(
"pressureV2",
"Home",
new { id = ViewData.Model.id },
new AjaxOptions
{
UpdateTargetId = "userControls",
HttpMethod = "GET"
},
new { #id = "genInfoLinkForm" }))
{%>
<%= Html.SubmitButton("hey", "Lol") %>
<%} %>
Controller
public ActionResult header(int id)
{
var headerResults = from c in db.information
where c.id == id
select new information
{
id = c.id,
type = c.type
};
ViewData.Model = headerResults.FirstOrDefault();
return View(ViewData.Model);
}
public ActionResult pressureV2(int id)
{
var pressureVResults = from c in db.pressure_volume_tests
where c.id == id
select new pressureVT
{
bottomCVP = c.bottom_CVP,
topCVP = c.top_CVP
};
ViewData.Model = pressureVResults.FirstOrDefault();
return View(ViewData.Model);
}
In the comments you have said that the view is not strongly typed. Because of that:
<% Html.RenderPartial("header", this.ViewData.Model); %>
<% Html.RenderPartial("test", this.ViewData.Model); %>
will not work. If you strongly type your view to testMVCProject.Models.information and then pass an instance of that type from your constructor it will work.
Controller:
public ActionResult ShowAView()
{
Return View("WhateverYourViewIsCalled", new information());
}
You have a misunderstanding of the use of Html.RenderPartial helper.
When you use the RenderPartial you will show the view without requesting the model from the controller.
So you have to refactor your ViewPage and pass the good Model to your usercontrols:
Exemple:
Controller:
ActionResult MainView()
{
var mainviewobj = new MainViewObject();
var headerResults = from c in db.information
where c.id == id
select new information
{
id = c.id,
type = c.type
};
mainviewobj.info = headerResults.FirstOrDefault();
return view(mainviewobj);
}
View Code:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% Html.RenderPartial("header", this.ViewData.Model.info); %>
<% Html.RenderPartial("test", this.ViewData.Model.info); %>
<div id="userControls">
</div>
</asp:Content>
View Code Behind
public partial class MainView : ViewPage<MainViewObject>
{
}
Now the Model will not be null in your usercontrol.
But remember the usercontrol rendering partially dun execute the code in the controller
So you dun need the public ActionResult header(int id) in your Controller
Hope this helps.
Have you tried making the ViewPage generic as well?
The Controller doesn't get called when you RenderPartial - it is bypassed and the view is rendered directly. So whatever you want to pass in as a model needs to be done from the calling View.
I found this worked for me, reference the partial as you do, like so.
...form
#Html.Partial("_AboutYou", Model.AboutYou);
..end form
within the partial view at the top...
#model <namespace1>.<namespace2>.<namespace3>.CustomerInfo.AboutYou
#{
ViewData.TemplateInfo.HtmlFieldPrefix = "AboutYou";
if (this.ViewContext.FormContext == null)
{
this.ViewContext.FormContext = new FormContext();
}
}
I believe the problem might be that you're missing an element in the form with the name "id" so the parameter of the Action method is never populated with a value?
That way the query would always return null with the FirstOrDefault, hence the null Model.
Just my guess...