Partial view inherits from master layout - asp.net-mvc

I have a partial view and int it, there is no trace of any inheritance from any layout. But whenever I want to use it (render it) inside a view, the layout gets repeated once for the view, and once for the partial view. This post suggests to create an empty layout. But I think this is the workaround. Is there anyway to stop loading layout (master layout) for partial views. I don't understand, why when there is no code to use the master layout, why should it get loaded. It's just like creating a page in ASP.NET and seeing that it inherits from a master page without having <%# Master ... directive.
This is my partial view:
#* Recursive category rendering *#
#using Backend.Models;
#{
List<Category> categories = new ThoughtResultsEntities().Categories.ToList();
int level = 1;
}
#RenderCategoriesDropDown(categories, level)
#helper RenderCategoriesDropDown(List<Category> categories, int level)
{
List<Category> rootCategories = categories.Where(c => c.ParentId == null).ToList();
<select id='categoriesList' name='categoriesList'>
#foreach (Category rootCategory in rootCategories)
{
<option value='#rootCategory.Id' class='level-1'>#rootCategory.Title</option>
#RenderChildCategories(categories, level, rootCategory.Id);
}
</select>
}
#helper RenderChildCategories(List<Category> categories, int level, int parentCategoryId)
{
string padding = string.Empty;
level++;
List<Category> childCategories = categories.Where(c => c.ParentId == parentCategoryId).ToList();
foreach (Category childCategory in childCategories)
{
<option value='#childCategory.Id' class='level-#level'>#padding.PadRight(level, '-') #childCategory.Title</option>
#RenderChildCategories(categories, level, childCategory.Id);
}
level--;
}

I was able to reproduce this issue when rendering partial pages through ajax calls. The
return View("partialpage")
would always accompany with layout. I have overridden this behavior by explicitly calling
return PartialView("partialpage")

The layout might be coming from your ~/Views/_ViewStart.cshtml
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
You could try overriding this in your partial view like:
#{
Layout = null;
}

Related

How to configure an MVC dropdown depending on which view calls it

I have two views, BatchReceipt and Receipt which utilise the same model. Until now they have used the same display template of ReceiptType. But I want to have one exclude certain items and the other to have the full list (so essentially a second .cshtml display template called ReceiptTypeFull). How do I configure each of these views in Visual Studio to utilise the different Display Templates?
Some additions to show the code being used:
I have file ReceiptType.cshtml being used as a DisplayTemplate which contains the following to setup the receipt dropdown
#using Clinton.Web.Helpers.EnumHelpers
#{
var item = EnumsHelper.GetNameFromEnumValue(Model);
}
I want to use a different DisplayTemplate, call it ReceiptTypeFull.cshtml
#using Clinton.Web.Helpers.EnumHelpersFull
#{
var item = EnumsHelper.GetNameFromEnumValue(Model);
}
#item
The difference is in calling the enumhelper or the enumhelperfull to vary the query populating the dropdown. My problem is that I cannot see how to redirect the view to use the different enumhelper/displaytemplate/
Thanks
I think I understand what you are getting at. You want to control which template is used for an Enum in the view.
I will explain using editor templates but it works the same way if you use display templates. You should be able to follow and apply for your scenario.
The idea is to use this overload of the editor html helper.
public static MvcHtmlString Editor(this HtmlHelper html, string expression, string templateName);
It is called like this
#Html.Editor("{property name}", "{template name}").
Below is an example to show it being used.
Suppose we have this enum
public enum MyItems
{
Item1 = 1,
Item2 = 2,
Item3 = 3
}
This helper
public static class MyEnumHelper
{
public static List<MyItems> GetAllItems()
{
return new List<MyItems>()
{
MyItems.Item1,
MyItems.Item2,
MyItems.Item3
};
}
public static List<MyItems> GetSomeItems()
{
return new List<MyItems>()
{
MyItems.Item1,
MyItems.Item2
};
}
}
This controller
public class HomeController : Controller
{
public ActionResult AllItems()
{
return View();
}
public ActionResult SomeItems()
{
return View();
}
}
We have these 2 editor templates, which are put in views/shared/editortemplates
First one called MyItems.cshtml which is the all one
#model MyItems?
#{
var values = MyEnumHelper.GetAllItems().Cast<object>()
.Select(v => new SelectListItem
{
Selected = v.Equals(Model),
Text = v.ToString(),
Value = v.ToString()
});
}
#Html.DropDownList("", values)
Second one called MyItems2.cshtml which is the some one
#model MyItems?
#{
var values = MyEnumHelper.GetSomeItems().Cast<object>()
.Select(v => new SelectListItem
{
Selected = v.Equals(Model),
Text = v.ToString(),
Value = v.ToString()
});
}
#Html.DropDownList("", values)
Then in the AllItems.cshtml to get the MyItems.cshtml template called we need
#model MyItemsViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.MyItem)
<submit typeof="submit" value="submit"/>
}
And in the SomeItems.cshtml to get some of the items by calling MyItems2.cshtml we use
#model MyItemsViewModel
#using (Html.BeginForm())
{
#Html.Editor("MyItem", "MyItems2") #* this bit answers your question *#
<submit typeof="submit" value="submit" />
}

Return an mvc action with a viewmodel type different to the passed one

I have an ItemsController with an Index action returning and rendering items on the client like
return View(itemViewModels);
Each itemViewModel has some bootstrap tabs. In each tab a partialView is rendered.
When the user edits a partialView and send the data to the Tab1Controller how can I return the View for the whole itemViewModel showing validation errors for the one partial view inside that tab1?
I have made the View work requesting/responsing with the sent itemViewModel but then Only the single item`s html is returned not the full items html.
AND when I return the Index View to the ItemsViewModels then I can NOT return my passed itemViewModels to show the validation errors.
[HttpGet]
public ActionResult Index(ItemViewModel viewModel)
{
return View("ItemsViewModels", viewModel);
}
This code does not work because the View does not match to the viewmodel type.
But I need to show the Html of the ItemsViewModels with the invalid (data-val attributes) html of the single edited itemViewModel.
I can NOT post and return the ItemsViewModels because it has many other properties which would make the modelstate invalid...
Do you have any idea?
UPDATE
I am NOT allowed to use ajax else the problem would be done quickly... this is for a common website and the customer wants a postback no ajax/SPA behavior.
At the moment I get the whole items from the service again and render it but then every ItemViewModel`s html has an invalid e.g. textbox. But I want that only a certain ItemViewModel is invalid.
[HttpPost]
public virtual async Task<ActionResult> SaveLanguage(ItemViewModel itemViewModel)
{
var viewModels = GetItemsViewModelsFromSErvice();
viewModels.Items.ElementAt(0).Settings = itemViewModel;
return View(MVC.Test.Items.Views.Index,viewModels );
}
If you are forced to do a full postback, but each partial view contains just a form with the elements for that 1 item then you'll have to reconstruct the other items in your controller before returning.
Your controller method would be something like
public ActionResult Index()
{
var vm = GetAllItems(); //method to get all your items for first load
return View(vm);
}
[HttpPost]
public ActionResult Index(ItemViewModel viewModel)
{
//get the full list of items, and then replace just the altered one
var vm = GetAllItems(); // assume returns a list
var index = vm.FindIndex(x => x.ID == viewModel.ID);
vm[index] = viewModel;
//might have to rename items in the ModelState.Errors dictionary
//so they are associated with the correct item index.
//first get the list of errors. As viewModel is not a list for this method
//they will have keys like "PropertyName".
//For a listItem need renaming to something like "[#].PropertyName" (# = index)
var errs = from ms in ModelState
where ms.Value.Errors.Any()
let fieldKey = ms.Key
let errors = ms.Value.Errors
from error in errors
select new {fieldKey, error.ErrorMessage};
//clear the ModelState, and then re-add any model-errors with the renamed key
ModelState.Clear();
foreach(var item in errs)
{
ModelState.AddModelError(
String.Format("[{0}].{1}", index, item.fieldKey), item.ErrorMessage);
}
return View("ItemsViewModels", vm);
}
In addition you might need to rename your form elements so that the model binder treats them as list items after postback. I'm not 100% sure this is necessary though.
If you can use ajax this becomes neater...
It looks like your index Model is a List<ItemViewModel>
Then your Main view (Index.cshtml) would be something like..
#model List<ItemViewModel>
...
#for(int i = 0; i < Model.Count; i++)
{
<div id="#String.Format("partialContainer{0}", Model[i].ID)">
#Html.Partial("Partial1", Model[i])
</div>
}
(notice that the ID of the container div is something that we can reference as the ajax update target)
And then have your partial Views use the relevant partial Models
Eg Partial1.cshtml:
#model ItemViewModel
... etc
#using (Ajax.BeginForm("RefreshPartial", "Home", null, new AjaxOptions() {
UpdateTargetId = String.Format("partialContainer{0}", Model.ID), HttpMethod = "Post" }, null))
{
#Html.TextBoxFor(m => m.Property1);
#* form controls... *#
<input type="submit" value="Submit" />
}

MVC 4 TreeView Menu

I tried to follow this, as it was quoted as an example in many places:
http://weblogs.asp.net/mikebosch/archive/2008/11/25/hierarchical-treeview-with-asp-net-mvc-jquery.aspx
and have come up with the following (a few alterations due to me using mvc4 not 2)
In the controller class:
public ActionResult Index()
{
MenuDBContext db = new MenuDBContext();
ViewData["AllMenu"] = db.Menus.ToList();
return View(db.Menus.ToList().Where(c => c.PId == null));
}
The view class:
#{
ViewBag.Title = "Index";
}
#model IEnumerable<Geo.Models.Menu>
<h2>Index</h2>
#foreach (var item in Model)
{
<li>
#item
#{var children = ViewData["AllMenu"].Where(c => c.PId == item.Id);}
#if (children.Count() > 0)
{
<ul>
#{Html.RenderPartial("ItemControl", children);}
</ul>
}
</li>
}
I can't for the life of me work out why I can't use .Where on ViewData["AllMenu"] or .Count() on children. Any tips on where I am going wrong would be fantastic.
You need to cast ViewData["AllMenu"] as IEnumerable<
var children = (ViewData["AllMenu"] as IEnumerable<GeomantApp.Models.Menu>).Where(c => c....
ViewData is Dictionary<string,object> so when you try and get a property from that dictionary, ASP.NET has no way of knowing what it is before hand so you get compiler errors if you don't cast your object first.

Repeating view within a view in ASP MVC3

If I have an elaborate repeating layout which I would like to only define once, say some fancy grid of boxes, but the content of those boxes would vary both in view and model from grid to grid (but not within a grid), e.g. one page has a fancygrid of product summaries and another page has a fancygrid of category introductions. What's the most sensible pattern for implementing that MVC3?
You could use display templates. For example define a property on your view model that is of type Enumerable<SomeViewModel>:
public class MyViewModel
{
public IEnumerable<SomeViewModel> Models { get; set; }
}
and in the view use the DisplayFor helper:
#model MyViewModel
#Html.DisplayFor(x => x.Models)
then define a custom display template that will automatically be rendered for each element of the Models collection (~/Views/Shared/DisplayTemplates/SomeViewModel.cshtml):
#model SomeViewModel
<div>
#Html.DisplayFor(x => x.SomeProperty)
...
</div>
Templated helpers work by convention. By default it will first look in the ~/Views/CurrentController/DisplayTemplates folder and then in the ~/Views/Shared/DisplayTemplates folder for a template which is named the same way as the type of the collection property (SomeViewModel.cshtml).
You can move the repeating section into a partial view. Then that partial view can be reused anywhere
Action GenreMenu in Store Controller:
[ChildActionOnly]
public ActionResult GenreMenu()
{
var genres = storeDB.Genres.ToList();
return PartialView(genres);
}
in the View this will repeat the partial view three times:
#for (int i = 0; i < 3; i++)
{
Html.RenderAction("GenreMenu", "Store");
}

How to create a function in a cshtml template?

I need to create a function that is only necessary inside one cshtml file. You can think of my situation as ASP.NET page methods, which are min web services implemented in a page, because they're scoped to one page. I know about HTML helpers (extension methods), but my function is just needed in one cshtml file. I don't know how to create a function signature inside a view.
Note: I'm using Razor template engine.
why not just declare that function inside the cshtml file?
#functions{
public string GetSomeString(){
return string.Empty;
}
}
<h2>index</h2>
#GetSomeString()
You can use the #helper Razor directive:
#helper WelcomeMessage(string username)
{
<p>Welcome, #username.</p>
}
Then you invoke it like this:
#WelcomeMessage("John Smith")
If your method doesn't have to return html and has to do something else then you can use a lambda instead of helper method in Razor
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
Func<int,int,int> Sum = (a, b) => a + b;
}
<h2>Index</h2>
#Sum(3,4)
Take a look at Declarative Razor Helpers
In ASP.NET Core Razor Pages, you can combine C# and HTML in the function:
#model PagerModel
#{
}
#functions
{
void PagerNumber(int pageNumber, int currentPage)
{
if (pageNumber == currentPage)
{
<span class="page-number-current">#pageNumber</span>
}
else
{
<a class="page-number-other" href="/table/#pageNumber">#pageNumber</a>
}
}
}
<p>#PagerNumber(1,2) #PagerNumber(2,2) #PagerNumber(3,2)</p>
If you want to access your page's global variables, you can do so:
#{
ViewData["Title"] = "Home Page";
var LoadingButtons = Model.ToDictionary(person => person, person => false);
string GetLoadingState (string person) => LoadingButtons[person] ? "is-loading" : string.Empty;
}

Resources