Create ViewModel for Navigation - asp.net-mvc-4

I have an MVC 4 application with several views. I.e. Products, Recipes, Distrubutors & Stores.
Each view is based around a model.
Let's keep it simple and say that all my controllers pass a similar view-model that looks something like my Product action:
public ActionResult Index()
{
return View(db.Ingredients.ToList());
}
Ok so this is fine, no problems. But now that all of my pages work I want to change my navigation (which has dropdowns for each view) to load the items in that model.
So I would have a navigation with 4 Buttons (Products, Recipes, Distrubutors & Stores).
When you roll over each button (let's say we roll over the products button) then a dropdown would have the Products listed.
To do this I need to create some type of ViewModel that has all 4 of those models combined. Obviously I can't just cut out a PartialView for each navigation element and use
#model IEnumerable<GranSabanaUS.Models.Products>
And repeat out the Products for that dropdown, because then that navigation would only work in the Product View and nowhere else.
(After the solution)
AND YES ROWAN You are correct in the type of nav I am creating, see here:

Introduction
I'm going to be making a few assumptions because I don't have all the information.
I suspect you want to create something like this:
Separating views
When you run into the issue of "How do I put everything into a single controller/viewmodel" it's possible that it's doing too much and needs to be divided up.
Don't treat your a final page as one big view - divide the views up into smaller views so they are doing 'one thing'.
For example, the navigation is just one part of your layout. You could go even further to say that each dropdown menu is a single view that are part of the navigation, and so on.
Navigation overview
Suppose you have a _Layout.cshtml that looks like this:
<body>
<div class="navbar">
<ul class="nav">
<li>Products</li>
<li>Recipes</li>
</ul>
</div>
#RenderBody()
</body>
As you can see we have a simple navigation system and then the main body is rendered. The problem that we face is: How do we extract this navigation out and give it the models it needs to render everything?
Extracting the navigation
Let's extract the navigation into it's own view. Grab the navigation HTML and paste it into a new view called __Navigation.cshtml_ and put it under ~/Views/Partials.
_Navigation.cshtml
<div class="navbar">
<ul class="nav">
<li>Products</li>
<li>Recipes</li>
</ul>
</div>
Create a new controller called PartialsController. Create a new action to call our navigation.
PartialsController.cs
[ChildActionOnly]
public class PartialsController : Controller
{
public ActionResult Navigation()
{
return PartialView("_Navigation");
}
}
Update our Layout to call the navigation.
_Layout.cshtml
<body>
#Html.Action("Navigation", "Partials")
#RenderBody()
</body>
Now our navigation is separated out into its own partial view. It's more independent and modular and now it's much easier to give it model data to work with.
Injecting model data
Suppose we have a few models such as the ones you mentioned.
public class Product { //... }
public class Recipe { //... }
Let's create a view-model:
NavigationViewModel.cs
public class NavigationViewModel
{
public IEnumerable<Product> Products { get; set; }
public IEnumerable<Recipe> Recipes { get; set; }
}
Let's fix up our action:
PartialsController.cs
public ActionResult Navigation()
{
NavigationViewModel viewModel;
viewModel = new NavigationViewModel();
viewModel.Products = db.Products;
viewModel.Recipes = db.Recipes;
return PartialView("_Navigation", viewModel);
}
Finally, update our view:
_Navigation.cshtml
#model NavigationViewModel
<div class="navbar">
<ul class="nav">
#foreach (Product product in Model.Products)
{
#<li>product.Name</li>
}
#foreach (Recipe recipe in Model.Recipes)
{
#<li>recipe.Name</li>
}
</ul>
</div>

public class MenuContents
{
public IEnumerable<Products> AllProducts { get; set; }
public IEnumerable<Recepies> AllRecepies { get; set; }
public IEnumerable<Distributors> AllDistributors { get; set; }
public IEnumerable<Stores> AllStores { get; set; }
private XXXDb db = new XXXUSDb();
public void PopulateModel()
{
AllProducts = db.Products.ToList();
AllRecepies = db.Recepies.ToList();
AllDistributors = db.Distributors.ToList();
AllStores = db.Stores.ToList();
}
}
Then in your controller
public ActionResult PartialWhatever()
{
MenuContents model = new MenuContents();
model.PopulateModel();
return PartialView("PartialViewName", model);
}
Then in your partial view
#Model MenuContents
... do whatever here

Related

Unobtrusive validation doesn't work with ViewComponents

I'm implementing a form with ASP.NET Core v3.1.
I have code for a drop-down on a Razor page which looks like this:
<div class="form-group">
<label asp-for="MySelectedItem" class="m-b-none"></label>
<help-text asp-for="MySelectedItem"></help-text>
<div class="input-group">
<select asp-for="MySelectedItem" asp-items="#Model.MyItems" class="form-control"></select>
<partial name="_ValidationIcon" />
</div>
<span asp-validation-for="MySelectedItem" class="validation-message"></span>
</div>
In my model class, I've included a validation rule to ensure that a valid option must be selected. This renders nicely:
Now, I want to genericise this as a View Component so that I don't have to paste this lump of code anytime I want a drop-down. Here is my implementation:
public class DropdownViewComponent : ViewComponent
{
public IEnumerable<SelectListItem> Items { get; set; }
public ModelExpression SelectedItem { get; set; }
public async Task<IViewComponentResult> InvokeAsync(ModelExpression selectedItem, IEnumerable<SelectListItem> items)
{
Items = items;
SelectedItem = selectedItem;
return View(this);
}
}
/Dropdown/Default.cshtml
#model Web.ViewComponents.DropdownViewComponent
<div class="form-group">
<label asp-for="SelectedItem" class="m-b-none"></label>
<help-text asp-for="SelectedItem"></help-text>
<div class="input-group">
<select asp-for="SelectedItem" asp-items="#Model.Items" class="form-control"></select>
<partial name="_ValidationIcon" />
</div>
<span asp-validation-for="SelectedItem" class="validation-message"></span>
</div>
Usage:
<vc:dropdown selected-item="MySelectedItem" items="Model.MyItems"></vc:dropdown>
This code correctly renders the drop-down, but the validation attributes are missing from the rendered HTML. Why?
I'm also not sure how to get the DisplayName from the model expression that I passed to the View Component.
Where have I gone wrong?
Thanks.
Presumably, your validation attributes are on the MyItems property of your parent model, which isn't listed here. E.g.,
public class ParentModel {
[Required]
public IEnumerable<SelectListItem> MyItems { get; set; }
}
As such, the issue is that your view is no longer binding to that model or its MyItems property. Instead, it's binding to the Items property on the DropdownViewComponent model, which doesn't have any validation attributes on it. Those two properties may both be pointing to the same IEnumerable<SelectListItem> object reference, but their metadata is entirely different. As such, ASP.NET Core is correctly displaying the validation attributes associated with the DropdownViewComponent.Items property.
I see this as an unfortunate limitation of view components—but one that's conceptually difficult to reason out of. For more information, see my answer to a similar question I previously posed, How to bind a ModelExpression to a ViewComponent in ASP.NET Core.
That said, given your particular requirements, you can work around this by passing the model which contains the target property to your view component—instead of passing the value of the target property itself—and then relaying that via your view component's view model:
public class DropdownViewComponent : ViewComponent
{
public ParentModel ParentModel { get; set; }
public ModelExpression SelectedItem { get; set; }
public async Task<IViewComponentResult> InvokeAsync(ModelExpression selectedItem, ParentModel model)
{
ParentModel = model;
SelectedItem = selectedItem;
return View(this);
}
}
Note: I would typically create a separate, lightweight view model for your view component, instead of passing your view component object down to its view. But I'm maintaining this structure for consistency with your original code.
You would then be able to bind to the original property in your view component's view, thus maintaining all of the original validation attributes:
<select asp-for="SelectedItem" asp-items="#Model.ParentModel.MyItems"></select>
On first approximation, this doesn't buy you much—and may even defeat your entire reason for pursuing a view component in the first place—as it forces you to operate against a single model. If multiple views with different models want to use this view component, that introduces some problems. You can mitigate these, however, by introducing a layer of abstraction.
There are a few approaches to this, such as establishing an interface, but the one I recommend is to develop a specialized list class which you use to model IEnumerable<SelectListItem>:
public class DropdownList: List<SelectListItem> {
public virtual IEnumerable<SelectListItem> Items { get; } = this;
}
And then working off of that in your DropdownViewComponent:
public class DropdownViewComponent : ViewComponent
{
public DropdownList DropdownList { get; set; }
public ModelExpression SelectedItem { get; set; }
public async Task<IViewComponentResult> InvokeAsync(ModelExpression selectedItem, DropdownList dropdownList)
{
DropdownList = dropdownList;
SelectedItem = selectedItem;
return View(this);
}
}
And, finally, implementing it as follows in your view component's view:
<select asp-for="SelectedItem" asp-items="#Model.DropdownList.Items"></select>
This would allow you to override this class in order to add attributes as needed. E.g.,
public class RequiredDropdownList : DropdownList {
[Required]
public override Items { get; } = this;
}
Note: if you have a need to use a variety of different collections on different view models, and were relying on IEnumerable<SelectListItem> to unify them, this approach won't work. In that case, creating something like an IDropdownList interface makes more sense. Regardless, the concept is virtually identical.

How to add a list<T> to view with a single model

Getting an error while trying to add a grid to my detail page. The error is:
The model item passed into the dictionary is of type 'GridMvc.Html.HtmlGrid1[MyApp.Models.RecipientActivityMetadata]', but this dictionary requires a model item of type 'System.Collections.Generic.List1[MyApp.Models.RecipientActivityMetadata]'.
MVC4 View is a combination of a detail page and a list. I am using a viewmodel that looks like this:
public class FormViewModel()
{
public RecipientMetadata Recipient { get; set; }
public StudentActivityMetadata StudentActivity { get; set; }
public List<RecipientActivityMetadata> RecipientActivites { get; set; }
}
The view top is:
#model MyApp.Models.ViewModels.FormViewModel
and it renders a partial view which contains the list:
#Html.Partial("_grid", Model.RecipientActivites)
and the partial looks like this:
#using GridMvc.Html
#model List<MyApp.Models.RecipientActivityMetadata>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<div>
#Html.Grid(Model).Columns(columns =>
{
columns.Add(c => c.ActCount).Titled("Activity Num");
columns.Add(c => c.ActivityType).Titled("Activity Type");
columns.Add(c => c.FundCode).Titled("FundCode");
columns.Add(c => c.Hours).Titled("Hours");
}).WithPaging(10)
</div>
From Comment to Answer
According to the documentation provided by Grid.Mvc, #Html.Grid uses a partial view _Grid.cshtml. Because your partial view also has same name, the solution is to use a different name for your partial view.

How to Use Two Same Model in a View? [MVC4]

I'm trying to create a status update page where I want a user to insert status message in Index page and also, I want to show all inserted all status messages in the same Index page.
This is my Model code:
public class Statuses
{
[Key]
public int StatusID { get; set; }
[DataType(DataType.MultilineText)]
[Required]
public string message { get; set; }
}
public class StatusContext : DbContext
{
public DbSet<Statuses> Status { get; set; }
}
And, I used #Html.EditorFor(model => model.message) in the Index.cshtml page.
To show the editor, I used the following model in View.
#model LearnStart.Models.Statuses
However, to show all the status messages below the Multiline TextArea, I think I'm supposed to use the below one.
#model IEnumerable<LearnStart.Models.Statuses>
How to use both model in same view so that I can display both the text area (to insert the status message) and to list all available status messages below it?
First, you should not be passing your entities directly to your view. The recommended best practice is to use View Models, which are models tailored specifically to your view.
Second, when using a view model you can now do this, since it's not tied to your data model entities:
public class MyActionViewModel {
public List<StatusesViewModel> StatusList {get;set;}
public StatusesViewModel CreatedStatus {get;set}
}
Then in your view:
#model MyActionViewModel
#Html.EditorFor(x => x.CreatedStatus)
.............................................
#Html.DisplayFor(x => x.StatusList)
Then you can create two templates, an EditorTemplate and a DisplayTempate:
In ~/Views/Shared/EditorTemplates/StatusesViewModel.cshtml
#model StatusesViewModel
#using(Html.BeginForm()) {
#Html.EditorFor(x => x.Message)
<input type="submit" value="Create Status" />
}
In ~/Views/Shared/DisplayTemplates/StatusesViewModel.cshtml
#model StatusesViewModel
<div>
<span>#Model.Message</span>
</div>
The thing that's nice about using the templates is that they will automatically iterate over your collection.. no foreach or for statement is used. A single EditorFor works on the entire collection, then renders the template based on the type, which in this case translates to StatusViewModel.cshtml
Easy way is to put a list inside Viewbag and show list in View as shown :-
Controller :
Public Actionresult Myaction()
{
.........
Viewbag.data = //bind list of messages here
return View();
}
View :
#model LearnStart.Models.Statuses
.........
.........
.........
#if(Viewbag.data != null){
<table>
#foreach(var item in Viewbag.data)
{
<tr><td>#item.message</td></tr>
}
</table>
}

ViewBags using #Html.Action to render form on partial view

View:
<p>Parent ViewData: #ViewData["Test"]</p>
#Html.Action("MemberSignup","MemberSignupSurface")
PartialView:
<p>PartialView ViewData: #ViewData["Test"]</p>
#using (Html.BeginUmbracoForm<MemberSignupSurfaceController>
("MemberSignupSubmit", "MemberSignupSurfaceController",FormMethod.Post))
{
<!-- Some form controls -->
<input type="submit" value="Signup" />
}
Controller:
public class MemberSignupSurfaceController : SurfaceController
{
public ActionResult MemberSignup()
{
ViewData["Test"] = "From MemberSignup";
// Do database stuff and create model from that
return PartialView("MemberSignupView", model);
}
[HttpPost]
public ActionResult MemberSignupSubmit(MemberViewModel model)
{
ViewData["Test"] = "From MemberSignupSubmit";
if (ModelState.IsValid)
{
// Redirect to success using TempData
}
else
{
return CurrentUmbracoPage();
}
}
}
When my page load MemberSignup is called and the page shows
Parent ViewData:
PartialView ViewData: From MemberSignup
Then when i submit the form on the partial view with invalid input so it won't validate and it calls CurrentUmbracoPage() in the action MemberSignupSubmit
I get the following:
Parent ViewData: From MemberSignupSubmit
PartialView ViewData: From MemberSignup
If i use #Html.Partial to render my partial view both viewbags shows the same value set from the submit action.
I've tried TempDatabut it is not working either. Is there really no way to pass anything back to the partial view after i return from the submit action when using #Html.Action to render a partial view form.
The overall problem I am trying to solve is to popluate a dropdown in my form with values from the database. Using #Html.Partial don't allow me to do this but have a working viewbag.
I did this to render a dynamic dropdown list with values from a database. Maybe it will help someone.
It is a music player which needs a dynamic db populated menu to list the playlists
I made a base controller which all other controllers inherit from. In that base class, I have a PlaylistPopupMenu action which gets the list of playlists from a db.
public PartialViewResult PlaylistPopupMenu()
{
try
{
return PartialView("_PlaylistPopupMenu", db.GetPlaylists(1).ToList());
}
catch (Exception)
{
throw;
}
}
Then I have a _PlaylistPopupMenu partial view as follows:
#model List<OneMusic.Models.GetPlaylists_Result>
#if (Model.Count > 0)
{
<li style="height:2px" class="divider"></li>
foreach (var item in Model)
{
<li style="height:30px">#Html.DisplayFor(p => item.Name)
#Html.ActionLink(item.Name, "AddSong", "Playlist", new { playlistId = #item.PlaylistId, songId = 1 }, "")
</li>
}
}
this renders the dynamic parts of the menu (ie the playlists)
Finally the main page has this to build the dynamic part of the menu:
<ul class="dropdown-menu" style="margin-top:10px"><p class="text-primary" style="margin-left:18px; margin-top:6px">Add To</p>
<!-- other static menu items here-->
<li style="margin-top:-60px; height:0px">#Html.Action("PlaylistPopupMenu")</li>
</ul>

Pass data to layout that are common to all pages

I have a website which have a layout page. However this layout page have data which all pages model must provide such page title, page name and the location where we actually are for an HTML helper I did which perform some action. Also each page have their own view models properties.
How can I do this? It seems that its a bad idea to type a layout but how do I pass theses infos?
If you are required to pass the same properties to each page, then creating a base viewmodel that is used by all your view models would be wise. Your layout page can then take this base model.
If there is logic required behind this data, then this should be put into a base controller that is used by all your controllers.
There are a lot of things you could do, the important approach being not to repeat the same code in multiple places.
Edit: Update from comments below
Here is a simple example to demonstrate the concept.
Create a base view model that all view models will inherit from.
public abstract class ViewModelBase
{
public string Name { get; set; }
}
public class HomeViewModel : ViewModelBase
{
}
Your layout page can take this as it's model.
#model ViewModelBase
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Test</title>
</head>
<body>
<header>
Hello #Model.Name
</header>
<div>
#this.RenderBody()
</div>
</body>
</html>
Finally set the data in the action method.
public class HomeController
{
public ActionResult Index()
{
return this.View(new HomeViewModel { Name = "Bacon" });
}
}
I used RenderAction html helper for razor in layout.
#{
Html.RenderAction("Action", "Controller");
}
I needed it for simple string. So my action returns string and writes it down easy in view.
But if you need complex data you can return PartialViewResult and model.
public PartialViewResult Action()
{
var model = someList;
return PartialView("~/Views/Shared/_maPartialView.cshtml", model);
}
You just need to put your model begining of the partial view '_maPartialView.cshtml' that you created
#model List<WhatEverYourObjeIs>
Then you can use data in the model in that partial view with html.
Another option is to create a separate LayoutModel class with all the properties you will need in the layout, and then stuff an instance of this class into ViewBag. I use Controller.OnActionExecuting method to populate it.
Then, at the start of layout you can pull this object back from ViewBag and continue to access this strongly typed object.
Presumably, the primary use case for this is to get a base model to the view for all (or the majority of) controller actions.
Given that, I've used a combination of several of these answers, primary piggy backing on Colin Bacon's answer.
It is correct that this is still controller logic because we are populating a viewmodel to return to a view. Thus the correct place to put this is in the controller.
We want this to happen on all controllers because we use this for the layout page. I am using it for partial views that are rendered in the layout page.
We also still want the added benefit of a strongly typed ViewModel
Thus, I have created a BaseViewModel and BaseController. All ViewModels Controllers will inherit from BaseViewModel and BaseController respectively.
The code:
BaseController
public class BaseController : Controller
{
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var model = filterContext.Controller.ViewData.Model as BaseViewModel;
model.AwesomeModelProperty = "Awesome Property Value";
model.FooterModel = this.getFooterModel();
}
protected FooterModel getFooterModel()
{
FooterModel model = new FooterModel();
model.FooterModelProperty = "OMG Becky!!! Another Awesome Property!";
}
}
Note the use of OnActionExecuted as taken from this SO post
HomeController
public class HomeController : BaseController
{
public ActionResult Index(string id)
{
HomeIndexModel model = new HomeIndexModel();
// populate HomeIndexModel ...
return View(model);
}
}
BaseViewModel
public class BaseViewModel
{
public string AwesomeModelProperty { get; set; }
public FooterModel FooterModel { get; set; }
}
HomeViewModel
public class HomeIndexModel : BaseViewModel
{
public string FirstName { get; set; }
// other awesome properties
}
FooterModel
public class FooterModel
{
public string FooterModelProperty { get; set; }
}
Layout.cshtml
#model WebSite.Models.BaseViewModel
<!DOCTYPE html>
<html>
<head>
< ... meta tags and styles and whatnot ... >
</head>
<body>
<header>
#{ Html.RenderPartial("_Nav", Model.FooterModel.FooterModelProperty);}
</header>
<main>
<div class="container">
#RenderBody()
</div>
#{ Html.RenderPartial("_AnotherPartial", Model); }
#{ Html.RenderPartial("_Contact"); }
</main>
<footer>
#{ Html.RenderPartial("_Footer", Model.FooterModel); }
</footer>
< ... render scripts ... >
#RenderSection("scripts", required: false)
</body>
</html>
_Nav.cshtml
#model string
<nav>
<ul>
<li>
Mind Blown!
</li>
</ul>
</nav>
Hopefully this helps.
There's another way to handle this. Maybe not the cleanest way from an architectural point of view, but it avoids a lot of pain involved with the other answers. Simply inject a service in the Razor layout and then call a method that gets the necessary data:
#inject IService myService
Then later in the layout view:
#if (await myService.GetBoolValue()) {
// Good to go...
}
Again, not clean in terms of architecture (obviously the service shouldn't be injected directly in the view), but it gets the job done.
You don't have to mess with actions or change the model, just use a base controller and cast the existing controller from the layout viewcontext.
Create a base controller with the desired common data (title/page/location etc) and action initialization...
public abstract class _BaseController:Controller {
public Int32 MyCommonValue { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
MyCommonValue = 12345;
base.OnActionExecuting(filterContext);
}
}
Make sure every controller uses the base controller...
public class UserController:_BaseController {...
Cast the existing base controller from the view context in your _Layout.cshml page...
#{
var myController = (_BaseController)ViewContext.Controller;
}
Now you can refer to values in your base controller from your layout page.
#myController.MyCommonValue
UPDATE
You could also create a page extension that would allow you to use this.
//Allows typed "this.Controller()." in cshtml files
public static class MyPageExtensions {
public static _BaseController Controller(this WebViewPage page) => Controller<_BaseController>(page);
public static T Controller<T>(this WebViewPage page) where T : _BaseController => (T)page.ViewContext.Controller;
}
Then you only have to remember to use this.Controller() when you want the controller.
#{
var myController = this.Controller(); //_BaseController
}
or specific controller that inherits from _BaseController...
#{
var myController = this.Controller<MyControllerType>();
}
I do not think any of these answers are flexible enough for a large enterprise level application. I'm not a fan of overusing the ViewBag, but in this case, for flexibility, I'd make an exception. Here's what I'd do...
You should have a base controller on all of your controllers. Add your Layout data OnActionExecuting in your base controller (or OnActionExecuted if you want to defer that)...
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext
filterContext)
{
ViewBag.LayoutViewModel = MyLayoutViewModel;
}
}
public class HomeController : BaseController
{
public ActionResult Index()
{
return View(homeModel);
}
}
Then in your _Layout.cshtml pull your ViewModel from the ViewBag...
#{
LayoutViewModel model = (LayoutViewModel)ViewBag.LayoutViewModel;
}
<h1>#model.Title</h1>
Or...
<h1>#ViewBag.LayoutViewModel.Title</h1>
Doing this doesn't interfere with the coding for your page's controllers or view models.
if you want to pass an entire model go like so in the layout:
#model ViewAsModelBase
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8"/>
<link href="/img/phytech_icon.ico" rel="shortcut icon" type="image/x-icon" />
<title>#ViewBag.Title</title>
#RenderSection("styles", required: false)
<script type="text/javascript" src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
#RenderSection("scripts", required: false)
#RenderSection("head", required: false)
</head>
<body>
#Html.Action("_Header","Controller", new {model = Model})
<section id="content">
#RenderBody()
</section>
#RenderSection("footer", required: false)
</body>
</html>
and add this in the controller:
public ActionResult _Header(ViewAsModelBase model)
Creating a base view which represents the Layout view model is a terrible approach. Imagine that you want to have a model which represents the navigation defined in the layout. Would you do CustomersViewModel : LayoutNavigationViewModel? Why? Why should you pass the navigation model data through every single view model that you have in the solution?
The Layout view model should be dedicated, on its own and should not force the rest of the view models to depend on it.
Instead, you can do this, in your _Layout.cshtml file:
#{ var model = DependencyResolver.Current.GetService<MyNamespace.LayoutViewModel>(); }
Most importantly, we don't need to new LayoutViewModel() and we will get all the dependencies that LayoutViewModel has, resolved for us.
e.g.
public class LayoutViewModel
{
private readonly DataContext dataContext;
private readonly ApplicationUserManager userManager;
public LayoutViewModel(DataContext dataContext, ApplicationUserManager userManager)
{
}
}
Other answers have covered pretty much everything about how we can pass model to our layout page. But I have found a way using which you can pass variables to your layout page dynamically without using any model or partial view in your layout. Let us say you have this model -
public class SubLocationsViewModel
{
public string city { get; set; }
public string state { get; set; }
}
And you want to get city and state dynamically. For e.g
in your index.cshtml you can put these two variables in ViewBag
#model MyProject.Models.ViewModel.SubLocationsViewModel
#{
ViewBag.City = Model.city;
ViewBag.State = Model.state;
}
And then in your layout.cshtml you can access those viewbag variables
<div class="text-wrap">
<div class="heading">#ViewBag.City #ViewBag.State</div>
</div>
You can also make use of RenderSection , it helps to you to inject your Model data into the _Layout view.
You can inject View Model Data, Json, Script , CSS, HTML etc
In this example I am injecting Json from my Index View to Layout View.
Index.chtml
#section commonLayoutData{
<script>
var products = #Html.Raw(Json.Encode(Model.ToList()));
</script>
}
_Layout.cshtml
#RenderSection("commonLayoutData", false)
This eliminates the need of creating a separate Base View Model.
Hope helps someone.
Why hasn't anyone suggested extension methods on ViewData?
Option #1
Seems to me by far the least intrusive and simplest solution to the problem. No hardcoded strings. No imposed restrictions. No magic coding. No complex code.
public static class ViewDataExtensions
{
private const string TitleData = "Title";
public static void SetTitle<T>(this ViewDataDictionary<T> viewData, string value) => viewData[TitleData] = value;
public static string GetTitle<T>(this ViewDataDictionary<T> viewData) => (string)viewData[TitleData] ?? "";
}
Set data in the page
ViewData.SetTitle("abc");
Option #2
Another option, making the field declaration easier.
public static class ViewDataExtensions
{
public static ViewDataField<string, V> Title<V>(this ViewDataDictionary<V> viewData) => new ViewDataField<string, V>(viewData, "Title", "");
}
public class ViewDataField<T,V>
{
private readonly ViewDataDictionary<V> _viewData;
private readonly string _field;
private readonly T _defaultValue;
public ViewDataField(ViewDataDictionary<V> viewData, string field, T defaultValue)
{
_viewData = viewData;
_field = field;
_defaultValue = defaultValue;
}
public T Value {
get => (T)(_viewData[_field] ?? _defaultValue);
set => _viewData[_field] = value;
}
}
Set data in the page. Declaration is easier than first option, but usage syntax is slightly longer.
ViewData.Title().Value = "abc";
Option #3
Then can combine that with returning a single object containing all layout-related fields with their default values.
public static class ViewDataExtensions
{
private const string LayoutField = "Layout";
public static LayoutData Layout<T>(this ViewDataDictionary<T> viewData) =>
(LayoutData)(viewData[LayoutField] ?? (viewData[LayoutField] = new LayoutData()));
}
public class LayoutData
{
public string Title { get; set; } = "";
}
Set data in the page
var layout = ViewData.Layout();
layout.Title = "abc";
This third option has several benefits and I think is the best option in most cases:
Simplest declaration of fields and default values.
Simplest usage syntax when setting multiple fields.
Allows setting various kinds of data in the ViewData (eg. Layout, Header, Navigation).
Allows additional code and logic within LayoutData class.
P.S. Don't forget to add the namespace of ViewDataExtensions in _ViewImports.cshtml
The best way to use static strings such as page title, page name and the location etc, is to define via ViewData. Just define required ViewData in ViewStart.cshtml
#{
Layout = "_Layout";
ViewData["Title"] = "Title";
ViewData["Address"] = "1425 Lane, Skardu,<br> Pakistan";
}
and call whenever require like
<div class="rn-info-content">
<h2 class="rn-contact-title">Address</h2>
<address>
#Html.Raw(ViewData["Address"].ToString())
</address>
</div>
You could create a razor file in the App_Code folder and then access it from your view pages.
Project>Repository/IdentityRepository.cs
namespace Infrastructure.Repository
{
public class IdentityRepository : IIdentityRepository
{
private readonly ISystemSettings _systemSettings;
private readonly ISessionDataManager _sessionDataManager;
public IdentityRepository(
ISystemSettings systemSettings
)
{
_systemSettings = systemSettings;
}
public string GetCurrentUserName()
{
return HttpContext.Current.User.Identity.Name;
}
}
}
Project>App_Code/IdentityRepositoryViewFunctions.cshtml:
#using System.Web.Mvc
#using Infrastructure.Repository
#functions
{
public static IIdentityRepository IdentityRepositoryInstance
{
get { return DependencyResolver.Current.GetService<IIdentityRepository>(); }
}
public static string GetCurrentUserName
{
get
{
var identityRepo = IdentityRepositoryInstance;
if (identityRepo != null)
{
return identityRepo.GetCurrentUserName();
}
return null;
}
}
}
Project>Views/Shared/_Layout.cshtml (or any other .cshtml file)
<div>
#IdentityRepositoryViewFunctions.GetCurrentUserName
</div>
In .NET Core, you can use View Components to do this.
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-5.0
From the link above, add a class Inheriting from ViewComponent
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;
namespace ViewComponentSample.ViewComponents
{
public class PriorityListViewComponent : ViewComponent
{
private readonly ToDoContext db;
public PriorityListViewComponent(ToDoContext context)
{
db = context;
}
public async Task<IViewComponentResult> InvokeAsync(
int maxPriority, bool isDone)
{
var items = await GetItemsAsync(maxPriority, isDone);
return View(items);
}
private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
{
return db.ToDo.Where(x => x.IsDone == isDone &&
x.Priority <= maxPriority).ToListAsync();
}
}
}
Then in your view (_layout in my case)
#await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })
If you need a view, make a folder at ~/Views/Shared/Components/<Component Name>/Default.cshtml. You need to make the folder Components then in that, make a folder with your component name. In the example above, PriorityList.
instead of going through this
you can always use another approach which is also fast
create a new partial view in the Shared Directory and call your partial view in your layout as
#Html.Partial("MyPartialView")
in your partial view you can call your db and perform what ever you want to do
#{
IEnumerable<HOXAT.Models.CourseCategory> categories = new HOXAT.Models.HOXATEntities().CourseCategories;
}
<div>
//do what ever here
</div>
assuming you have added your Entity Framework Database
what i did is very simple and it's works
Declare Static property in any controller or you can make a data-class with static values if you want like this:
public static username = "Admin";
public static UserType = "Administrator";
These values can be updated by the controllers based on operations.
later you can use them in your _Layout
In _layout.cshtml
#project_name.Controllers.HomeController.username
#project_name.Controllers.HomeController.UserType
It's incredible that nobody has said this over here. Passing a viewmodel through a base controller is a mess. We are using user claims to pass info to the layout page (for showing user data on the navbar for example).
There is one more advantage. The data is stored via cookies, so there is no need to retrieve the data in each request via partials.
Just do some googling "asp net identity claims".
You can use like this:
#{
ApplicationDbContext db = new ApplicationDbContext();
IEnumerable<YourModel> bd_recent = db.YourModel.Where(m => m.Pin == true).OrderByDescending(m=>m.ID).Select(m => m);
}
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body">
<div class="baner1">
<h3 class="bb-hred">Recent Posts</h3>
#foreach(var item in bd_recent)
{
#item.Name
}
</div>
</div>
</div>
</div>