I have to following code and there's a problem and I can't figure out what's the problem. Well, I have two ideas about the place where the problem may appear but I can't find a solution.
The problem is that my layout is rendered twice.
Layout.cshtml
<div id="container">
<div id="left_side"> #RenderPage("left_side.cshtml") </div>
<div id="center_side"> #RenderBody() </div>
<div id="right_side"> #RenderPage("right_side.cshtml") </div>
</div>
left_side.cshtml
#if (ViewBag.LeftColumnVisible == true)
{
#Html.Action("GetCategories", "Products");
}
GetCategories method from Products controller
public ActionResult GetCategories()
{
List<Categories> categories = db.Categories.ToList();
...
return View();
}
GetCategories.cshtml
#foreach (System.Collections.DictionaryEntry de in ViewBag.LeftColumnContent)
{
<div> #((Ads.Models.Categories)(de.Key)).Name; </div>
}
It enters the get categories and renders the content.
The problem for rendering twice may be at this line
#Html.Action("GetCategories", "Products"); or when it calls View(). If I comment the line, my layout will be rendered only once.
I'd say there are a couple of issues with your code. First of all, if left_side.cshtml/right_side.cshtml are partial views then you want to be using
#Html.RenderPartial("view")
to render them and not #RenderPage - although #RenderPage will work it's better from a readability point of view to understand exactly what the type of view it is you are working with.
Secondly, if your GetCategories view is a partial view you want to be returning a PartialView and not a View i.e.
public ActionResult GetCategories()
{
...
return 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.
I am trying to replace my view components with razor pages but it seems that it's not possible to load a partial razor page because a model is expected to be passed yet it is my understanding that the model for a razor page should be declared in the OnGetAsync method. Here is my code...
Razor Page
#page "{id:int}"
#model _BackgroundModel
<form method="POST">
<div>Name: <input asp-for="Description" /></div>
<input type="submit" />
</form>
Razor Page Code-Behind
public class _BackgroundModel : PageModel
{
private readonly IDataClient _dataClient;
public _BackgroundModel(IDataClient dataClient)
{
_dataClient = dataClient;
}
[BindProperty]
public BackgroundDataModel Background { get; set; }
public async Task OnGetAsync(int id)
{
Background = await _dataClient.GetBackground(id);
}
public async Task OnPostAsync()
{
if (ModelState.IsValid)
{
await _dataClient.PostBackground(Background);
}
}
}
Razor View
<div class="tab-pane fade" id="client-background-tab">
<div class="row">
<div class="col-sm-12">
#await Html.PartialAsync("/Pages/Client/_Background.cshtml", new { id = 1 })
</div>
</div>
</div>
Page Load Error
InvalidOperationException: The model item passed into the
ViewDataDictionary is of type '<>f__AnonymousType0`1[System.Int32]',
but this ViewDataDictionary instance requires a model item of type
'WebApp.Pages.Client._BackgroundModel'
In this example (as per MS recommended approach in their docs) the model is set inside the OnGetAsync method which should be run when the page is requested. I have also tried #await Html.RenderPartialAsync("/Pages/Client/_Background.cshtml", new { id = 1 }) but the same error result.
How can I load the razor page into my existing view?
Microsoft confirmed this cannot be achieved and therefore razor pages cannot be used as a replacement for view components.
See the comments of their docs...
MS docs
#RickAndMSFT moderator15 hours ago
#OjM You can redirect to the page, or you can make the core view >code into a partial and call it from both.
Pages are not a replacement for partials or View Components.
I have a form and a partial view on my razor page, the idea being that if I change the dropdownlist, the Controller does some work and sets a ViewBag.ShowAlert (bool) that triggers the partial view to be displayed.
While this works, instead of just showing the code within the partial view, the partial view shows as a new view rather than on the same view.
Any idea why?
The view looks like this
#using (Html.BeginForm("AlterVote", "ChangeVoteType"))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h1>New voting preference</h1>
<hr />
<p>Please select the type of vote you wish to change to #Html.DropDownListFor(model=>model.SelectedType, ViewBag.myList as SelectList, "Voting type", new { onchange = "this.form.submit();"})</p>
<div id="partialDiv">
#if (ViewBag.ShowAlert)
{
#Html.Partial("VotingChange")
}
</div>
</div>
}
The controller handling the HttpPost is this
[HttpPost]
public PartialViewResult AlterVote(Dropdown dropType)
{
ChangeBoilerPlate(dropType.SelectedType);
dropType.CurrentType = VoteTypeNames[(int)HomeController.VoterModel.CurrentVoteType];
return PartialView("VotingChange", dropType);
}
I'm guessing that this is down to the initial view being a form, so the partial gets confused as to where to insert the view.
If I understand correctly, by the partial view shows as a new view you mean it comes with a html tag, body and the full layout again. To solve this, you need to set up the layout to null inside your partial view, like so:
#model YourNamespace.Dropdown
#{
Layout = null;
}
<!-- partial view html below -->
<div>
</div>
The div tag is just to illustrate.
While this might solve your problem, you might want to load the partial view without reloading the whole page again. This is possible using ajax, like so:
Main View
#using (Html.BeginForm("AlterVote", "ChangeVoteType"))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h1>New voting preference</h1>
<hr />
<p>Please select the type of vote you wish to change to #Html.DropDownListFor(model=>model.SelectedType, ViewBag.myList as SelectList, "Voting type", new { id = "vote"})</p>
<div id="partialDiv">
</div>
</div>
}
<script type="text/javascript">
$(document).ready(function () {
$('#vote').change(function() {
var selectedType = $(this).val();
$.post('yourserver/YourController/AlterVote', { "SelectedType": selectedType })
.done(function (data) {
$('#partialDiv').html(data);
})
.fail(function () {
console.log('Whoops, something went wrong!!!');
});
});
});
</script>
So I just added a javascript to listen to that same change event on your dropdrown, but instead of submitting the form, I just use ajax to load the partial view html without reloading the entire page.
Just fix the URL and remember to set up layout to null in your partial view. Also, you might want this javascript in a separate file, thus loading it with bundles.
In our application we are using Tabs to display the information for example contacts, in this tab the user can switch between view contacts and created contact. When the user clicks on ‘Create’ I want the partial view changed to CreateContact within the same tab. What is the best way to show hide a Partial Views in jquery UI tabs? Should I use Ajax code to do this?
View
<div id="tabs">
<ul>
<li>Claim</li>
<li>Products</li>
<li>Contact Us</li>
</ul>
</div>
Controller
public ActionResult GetContact()
{
return PartialView();
}
public ActionResult CreateContact()
{
return PartialView();
}
Partial View
<li>#Html.ActionLink("Create", "Test", "Home")</li>
Thanks
Yes, you can use AJAX. That is in case you don't want to load all partial views at once.
If it doesn't matter, you can just render them at once:
<div id="tabs-1">
#Html.Partial("GetClaim")
</div>
<div id="tabs-2">
#Html.Partial("GetProduct")
</div>
<div id="tabs-3">
#Html.Partial("GetClaim")
</div>
jQuery hides the elements that are not currently visible and will manage the switching automatically.
You can use an Ajax.ActionLink() - http://msdn.microsoft.com/en-us/library/dd493106.aspx
This will asynchronously submit your form to your controller method. If you set the AjaxOptions.InsertionMode to be AjaxOptions.InsertionMode.Replace then it will replace the partial view content with whatever you return from the controller. Then, you just need to return the GetContacts view in the creatcontacts controller action, as below:
[HttpGet]
public ActionResult GetContact()
{
return PartialView(RetrieveListOfContacts());
}
[HttpPost]
public ActionResult CreateContact(StronglyTypedContactModel contact)
{
if (ModelState.IsValid)
{
// .. write your contact here
return PartialView("GetContact", RetrieveListOfContacts());
}
else
{
return PartialView(contact);
}
}
EDIT
Also, you can specify that it be a GET request in the HttpMethod of the AjaxOptions, so as not to require form (as I originally assumed you did).
How do I prevent renderaction from rendering the masterpage and giving it back to me? I only want it to render 1 section, for eg.
The controller
public ActionResult PaymentOptions()
{
return View(settingService.GetPaymentBanks().ToList());
}
The PaymentOptions View:
#model IEnumerable<Econo.Domain.PaymentBank>
<h2>Payments</h2>
<!-- Stuff here -->
The View
<div class="grid_10">
</div>
<div class="grid_14">
#{Html.RenderAction("PaymentOptions", "Administrator");}
</div>
In grid_14, the header, footer and everything else gets rendered. Is there a way to prevent this?
public ActionResult PaymentOptions()
{
return PartialView(settingService.GetPaymentBanks().ToList());
}
In Razor, partial views and full views have the same extension, so you need to explicitly use the PartialViewResult result type to specify a partial view.
This:
return View(settingService.GetPaymentBanks().ToList());
Has to use the overload so you can specify a master:
return View("PaymentOptions", "", settingService.GetPaymentBanks().ToList());