I have a controller that looks like this:
public class PersonController : Controller
{
public ActionResult Result()
{
var s = new PersonResult();
s = GetPerson(ViewBag.PersonInfo);
return View(s);
}
[...]
}
The controller calls the view Result which looks like this:
#model My.Class.Library.DTO.PersonController
#if (Model != null && Model.Persons.Count > 0)
{
#Html.Partial("Persons", #Model.Persons)
}
So the model can hold many Persons. Persons is sent to its own view like this (view Persons):
#using My.Class.Library.DTO
#model List<Person>
<section>
#foreach (Person person in #Model)
{
#Html.Partial("Person", person)
}
</section>
So I'm sending each Person person to my view Person. And in that view I'm drawing each Person like so:
#model Person
#if (Model.Fields.TryGetValue("description", out description)
{
var descSplit = description.Split('#');
foreach (string s in descSplit)
{
<div class="row-fluid">
<div class="span2">Person</div>
<div class="span10">#s</div>
</div>
}
}
But instead of doing that, I want to pass the string s to its own view. Something like this:
#model Person
#if (Model.Fields.TryGetValue("description", out description)
{
var descSplit = description.Split('#');
<section>
#foreach (string s in descSplit)
{
#Html.Partial("Description", s)
}
</section>
}
But "s" is just a primitive type: a string. How do I pass that to my view "Description"? What should my view "Description" look like? I'm thinking something like this:
#model string
<div class="row-fluid">
<div class="span2"><b>TEST</b></div>
<div class="span10">#s</div>
</div>
But that's not correct... What should my model be and how can I present the string (s) that I'm sending from the other view?
Your code looks right but in your partial view, try using the Model property.
#model string
<div class="row-fluid">
<div class="span2"><b>TEST</b></div>
<div class="span10">#Model</div>
</div>
When you strongly type your Views/PartialViews, you have to use the Model property to read the value you have passed as a Model to this View/PartialView.
Related
I have a busy view where most of the sections on there work off an ID. I'm looking for a more component way to handle each section so I'm using RenderAction() for each section where they have their own controllers. However I have a search section/"component" and when they put in a new Id and submit on that section/"component", I need a way for that to communicate to all the other RenderActions() that new Id so they can do their thing (query DB to get more info specific to that section).
My Search section would be something like:
public class SearchController : Controller
{
[HttpGet]
public ActionResult SearchContract()
{
var vm = new SearchVM();
return PartialView(vm);
}
[HttpPost]
public ActionResult SearchContract(SearchVM Search)
{
return PartialView(Search);
}
}
#using (Html.BeginForm())
{
<div class="row">
<div class="col-md-3">Contract Id</div>
<div class="col-md-6">
#Html.TextBoxFor(m => m.Id, new { #class = "form-control" })
</div>
</div>
<input type="submit" />
}
Let's say ContractHeader is a section/"component" using RenderAction() that hits a different controller and method from the search:
public class ContractController : Controller
{
public ActionResult ContractHeader(int ContractId)
{
// query contracts
return PartialView(vm);
}
}
Again, I'm looking for a more component oriented way with this. Yes it could all be in one controller but that's not what I'm looking for here. I want a more decoupled/compartmentalized approach to these areas on my views but trying to figure out how they can communicate with each other when "events" happen.
I think I have it figured out. Basically on each search "component" (I'm calling components a separate controller and view that you use RenderAction() to get on your main view) the method that gets called when the search button is pressed will return the following code (I subclassed Controller and put tis method in)
public ActionResult RedirectWithQueryString()
{
// get the referrer url without the old query string (which will be the main view)
var uri = new Uri(Request.UrlReferrer.ToString());
var url = Request.UrlReferrer.ToString().Replace(uri.Query, "");
var allQS = System.Web.HttpUtility.ParseQueryString(uri.Query);
var currentQS = System.Web.HttpUtility.ParseQueryString(Request.Url.Query);
var combinedQS = new NameValueCollection();
// update existing values
foreach (var key in allQS.AllKeys)
{
combinedQS.Add(key, allQS[key]);
}
// add new values
foreach (var key in currentQS.AllKeys)
{
if (combinedQS.AllKeys.Contains(key))
combinedQS[key] = currentQS[key];
else
combinedQS.Add(key, currentQS[key]);
}
var finalUrl = url + combinedQS.ToQueryString();
return Redirect(finalUrl);
}
public class ContractSearchController : MyBaseController
{
// GET: ContractSearch
public ActionResult Index(ContractSearchVM model)
{
return PartialView("ContractSearch", model);
}
public ActionResult SearchContracts(ContractSearchVM model)
{
return RedirectWithQueryString();
}
}
public class StopsSearchController : MyBaseController
{
public ActionResult Index(StopsSearchVM model)
{
// query to get some search related reference data like states list for drop down
return PartialView("StopsSearch", model);
}
public ActionResult SearchStops(StopsSearchVM model)
{
return RedirectWithQueryString();
}
}
SearchContracts() and SearchStops() methods are called from their own forms in their own views using HttpGet. In those methods then we are provided with just that forms query string but we also can get the UrlReferrer query string which will have other search forms key/values in it. So RedirectWithQueryString() basically makes sure the final query string has ALL keys required to satisfy the model binding of any search components on the view and will update the given keys with the current value for the current search component that the submit button was on.
So this then causes us to refresh to the current view with all current key/values in query string for all search components which then is calling all the RenderActions() and the values can be passed.
#model FocusFridayComponents.Models.CombinedVM
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-12">
#{ Html.RenderAction("Index", "ContractSearch"); }
</div>
</div>
<div class="row">
<div class="col-md-12">
#{ Html.RenderAction("Index", "StopsSearch"); }
</div>
</div>
</div>
<div class="col-md-6">
<!-- Contract Header -->
<div class="row">
<div class="col-md-12">
#{ Html.RenderAction("Header", "ContractHeader", new { ContractId = Model.ContractSearch.Id }); }
</div>
</div>
<!-- Contract Routes -->
<div class="row">
<div class="col-md-12">
#* #{ Html.RenderAction("Index", "ContractRoutes", new { ContractId = Model.Id }); } *#
</div>
</div>
</div>
In the main view you're working on you just make a VM that combines the search VM's you're using on the view. The model binding will correctly map the query string keys that match the search VM's even when they are inside the combined VM. The catch here would be to make sure the keys/props of each search VM don't share the same names of any kind.
What's interesting is for the RenderAction() for contract and stops search I don't need to pass the model into it. The binding just does this automatically. For ContractHeader and ContractRoutes I am passing in a parameter because the idea is those are separate components and have their own input requirements and those can be named completely separate from any search models you may be using in your view so the binding wouldn't be able to map anything. This is a good thing though as it decouples your actual view components from your search components.
So you would do all of this to get components that are decoupled from each other but can still talk to each other and you can assemble your views and reuse a lot of these components by just gluing the RenderAction() parameters between them. This can help reduce giant monolithic VM's that tend to pop up on complex views you're making.
The problem is that I need to "call" the PersonName field in the view of School, but the model in the view School is #model IList<Project.Presentation.Models.SchoolViewModel>, and the field PersonName is in the model #model IList<Project.Presentation.Models.PersonViewModel>. So, I guess I have to use two models in the same view, but I don't know how to do it. I don't know if I can only "call" the field I need using just one code line or if I have to do something behind.
Here is the code in the view School:
#model IList<Project.Presentation.Models.SchoolViewModel>
#{
ViewBag.Title = "Start view";
}#
{
<div class="row">
<div class="col-md-6 ">
<h2>
Details of the person #Html.DisplayFor(Project.Presentation.Models.PersonViewModel.PersonName)
</h2>
</div>
</div>
}
I'm trying with#Html.DisplayFor(Project.Presentation.Models.PersonViewModel.PersonName), but obviously it doesn't works.
your viewmodel will include all the property you will need in a view - so PersonViewModel should be a property in your viewmodel
you did not show the relationship between SchoolViewModel and PersonViewModel
but judge by the name, I am guessing it is a one to many relationship - i.e one SchoolViewModel will have many PersonViewModel representing the person in school
so base on that assumption, your SchoolViewModel may look like this:
public class SchoolViewModel
{
// other property ..
public IList<Project.Presentation.Models.PersonViewModel> PersonList {get; set;}
}
then in your view, it will look like:
#model IList<Project.Presentation.Models.SchoolViewModel>
#// first loop school
#for(int i =0; i < Model.Count; i++)
{
<div class="row">
#// then loop all person in the school
#for(int j = 0; j < Model[i].PersonList.Count; j++)
{
<div class="col-md-6 ">
<h2>
Details of the person #Html.DisplayFor(modelItem => Model[i].PersonList[j].PersonName )
</h2>
</div>
}
</div>
}
so the key is, put all your needed property to your viewmodel
Create a View Model and Include this Two Model in there
public class SchoolPersonViewModel
{
public IList<Project.Presentation.Models.PersonViewModel> PersonList {get; set;}
public IList<Project.Presentation.Models.SchoolViewModel> SchoolList {get; set;}
}
In View
<div class="row">
<div class="col-md-6 ">
<h2>
Details of the person
#Html.DisplayFor(model => model.PersonList)
</h2>
</div>
</div>
PersonList is List, So Use foreach
Same Like SchoolList
I needed to have a View in my application that has 2 tabs. After struggling, I came up with the following with Bootstap Tabs and Partial Views:
<div id="tabs">
<ul class="nav nav-tabs">
<li class="active">
Optin Status
</li>
<li>
Optin History
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="OptinStatus">
#Html.Partial("_OptinStatus")
</div>
<div class="tab-pane" id="OptiHistory">
#Html.Partial("_OptinHistory")
</div>
</div>
</div>
As long as I do this in my Results action, I can display the two tabs side by side and switch:
public ActionResult Results(OptinStatusViewModel viewModel)
{
// return RedirectToAction("OptInStatus");
return View();
}
The problem is now that I want to do some processing in the controller and display the results in the Partial View. so I switched the above to:
return RedirectToAction("OptInStatus");
That Action Method is supposed to be for the 1st partial view:
public ActionResult OptInStatus(OptinStatusViewModel viewModel)
{
HarmonyOperation harmonyoper = new HarmonyOperation();
ListProfileData res = new ListProfileData();
string listid = "60285206-7c9e-4af3-a0c4-0a69ec8d4bb4"; // Preferences Master List
string email = (string) TempData["User"];
string customerKey = Tools.GetCustomerKeyByEmail(email);
res = harmonyoper.GetProfileFromListByCustomerKey(listid, customerKey);
IDictionary<string, string> profileData = new Dictionary<string, string>();
// Found record in LIST for email
if (res != null)
{
profileData = Tools.ListKeyValueToDictionary(res.attributes);
//Check existence of this email
}
return View("_OptinStatus");
The problem when I do this is I get a new View that displays the "contents" of the Partial View instead of that Partial View in its tab. How can I get data to my partial view?
You're returning a View from your controller, when you should be returning PartialView.
So instead of:
return View("_OptinStatus");
You do:
return PartialView("_OptinStatus", model); //model is not required
And you should get this model in the actual view "_OptinStatus", not in the "main view".
An example of using seperate partial models:
Controllers:
public ActionResult Results()
{
return View();
}
public ActionResult OptInStatus()
{
return PartialView("_OptinStatus");
}
... etc
Results-view:
<div class="tab-content">
<div class="tab-pane active" id="OptinStatus">
#Html.Action("_OptinStatus")
</div>
<div class="tab-pane" id="OptiHistory">
#Html.Action("_OptinHistory")
</div>
</div>
_OptinStatus-partialView:
#model OptinStatusModel
<div>My partial for Status</div>
_OptinHistory-partialView:
#model OptinHistoryModel
<div>My partial for History</div>
The same example using viewModel for both partials:
Controllers:
public ActionResult Results(OptinStatusViewModel viewModel)
{
return View("Results", viewModel);
}
Results-view:
#model OptinStatusViewModel
<div class="tab-content">
<div class="tab-pane active" id="OptinStatus">
#Html.Partial("_OptinStatus", Model.OptionStatusModel)
</div>
<div class="tab-pane" id="OptiHistory">
#Html.Partial("_OptinHistory", Model.OptionHistoryModel)
</div>
</div>
_OptinStatus-partialView:
#model OptinStatusModel
<div>My partial for Status</div>
_OptinHistory-partialView:
#model OptinHistoryModel
<div>My partial for History</div>
My prefered solution:
jQuery, included in _layouts
$(".partialContents").each(function(index, item) {
var url = $(item).data("url");
if (url && url.length > 0) {
$(item).load(url);
}
});
Now, instead of using Razor to render the views, you just use a div (and ajax through the code above):
<div class="partialContents" data-url="/Home/OptInStatus">
HomeController will get hit on action "OptInStatus".
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[MvcApplication1.Models.News]', but this dictionary requires a model item of type 'MvcApplication1.Models.News'.
//my Controller
public class HomeController : Controller
{
CouncilDb _db=new CouncilDb() ;
public ActionResult Index()
{
var News = _db.News
.Take(10);
var Banner = (from r in _db.Banner orderby r.id descending select r).FirstOrDefault();
maz model = new maz();
model.Banner = Banner;
model.News = News.ToList();
return View(model);
}
protected override void Dispose(bool disposing)
{
if (_db != null)
{
_db.Dispose();
}
base.Dispose(disposing);
}
}
public class maz
{
public List<News> News { get; set; }
public Banner Banner { get; set; }
}
//Index view
#model MvcApplication1.Controllers.maz
#{
ViewBag.Title = "Home Page";
}
<!-- Banner -->
<!-- Banner -->
<div id="banner">
<h2> #Model.Banner.H2</h2>
<span class="byline"> #Model.Banner.Span </span>
</div>
#Html.Partial("_News",Model.News )
//Partialview
#model MvcApplication1.Models.News
<!-- Carousel -->
<div class="carousel">
<div class="reel">
<article>
<a class="image featured">
<img src="#Model.ImgUrl " alt="" /></a>
<header>
<h3>#Html.ActionLink(#Model.Title , "serch", "Home")</h3>
</header>
<p>#Model.Body </p>
</article>
</div>
</div>
The .News property of your model is of type List<News> not News so the model declaration in your partial view and the model you are passing to it don't match.
Depending on what you want to acheive, you can either loop through the List inside the index view:
#foreach (var news in Model.News)
{
#Html.Partial("_News", news)
}
or adjust the partial view model declaration and loop there
#model List<News>
....
#for (var news in Model)
{
<article>
<a class="image featured">
<img src="#news.ImgUrl" alt="" /></a>
<header>
<h3>#Html.ActionLink(#news.Title , "serch", "Home")</h3>
</header>
<p>#news.Body</p>
</article>
}
The error is pretty straightforward, once you get past the generic syntax. In C#-speak it's saying this:
The model item passed into the dictionary is of type 'List<News>',
but this dictionary requires a model item of type 'News'.
Your partial view is declared with a #model clause that specifies an item of type News:
#model MvcApplication1.Models.News
but when you pass in data to that partial view, what you're passing in is a List<News>:
#Html.Partial("_News",Model.News)
What you probably want is a loop (a #foreach or similar) that creates one partial view for each element in your News list.
My ViewModel class (ItemViewModel.cs) looks like this:
public class ItemViewModel
{
public ItemViewModel(xxx.Product product)
{
this.product = product;
}
private readonly xxx.xxx.Product product;
private readonly Pers pers;
private readonly Item item;
public xxx.xxx.Product Product
{
get{ return product;}
}
public Item Item
{
get { return item; }
}
public ItemList Items
{
get { return product.Items; }
}
public Pers Pers
{
get { return pers; }
set { value = pers; }
}
public PersList PersList
{
get { return product.PersList; }
}
}
The view has this code defined in it (I took out some other case lines, just to show one of them as an example):
<%# Page Language="C#" MasterPageFile="~/Views/Shared/MasterPages/Item.Master" Inherits="System.Web.Mvc.ViewPage<xxx.ViewModels.ItemViewModel>" %>
<% foreach (Pers p in Model.Perslist)
{
switch(p.DispType)
{
case DisType.Dropdown:
Model.Pers = p;
Html.RenderPartial("~/Views/Shared/Controls/Custom/PForm/DropDown.ascx",*Model);
break;
}
}
%>
And the RenderPartial looks like this:
<fieldset>
<div class="xxx">
<span class="xxx">*</span><label><%=Model.Pers.Name %></label>
<p class="xxx"><%=Model.Pers.Info %></p>
</div>
<div class="formField"><% Html.DropDownList(Model.Pers.Name, new SelectList(Model.Items[0].DropdownItems));%></div>
</fieldset>
The problem or dilemma I'm having is I not only need the p from the foreach but the entire ItemViewModel instance that was originally passed to my View. Because I need to use the Pers in that foreach as well as be able to reference the Items. So what I tried is to set the Pers property of the ItemViewModel class instance to the current p in the foreach. Then tried to send the whole Model (now that I have that Pers set on the property) which is of type ItemViewModel so that I can now use the property Pers on that object and also still be able to reference the Items property that was populated already when it hit the View.
So when the page renders I get:
System.NullReferenceException: Object reference not set to an instance of an object.
for this line:
<span class="xxx">*</span><label><%=Model.Pers.Name %></label>
So far I'm unsuccesful because I still get a null reference error on the property Pers when I attempt to use the ITemViewModel in my Partial View.
This could be because your partial view file needs this line above your html:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<xxx.ViewModels.**ItemViewModel**>" %>
So this is how your partial view should look:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<xxx.ViewModels.**ItemViewModel**>" %>
<fieldset>
<div class="xxx">
<span class="xxx">*</span><label><%=Model.Pers.Name %></label>
<p class="xxx"><%=Model.Pers.Info %></p>
</div>
<div class="formField"><% Html.DropDownList(Model.Pers.Name, new SelectList(Model.Items[0].DropdownItems));%></div>
</fieldset>
Good God, I set the property wrong. resolved. duh.