locked
Sharing Common Parameter Values to Populate Variables in _Layout.cshtml RRS feed

  • Question

  • User-53740444 posted

    I have some parameters which I set in a page model such as:

    SystemVersion from appsettings.json

    The current user details from a database

    I then want to show these variables in _Layout.cshtml as part of the page header or footer.

    Whilst I can make these into functions in their own class and call them from each page, I still need to pass in the database context and the IConfiguration and this doesn't seem a very good approach.

    Is there a way to set these as sort of global variables so they are always guaranteed to be available in all page models to be called in _Layout.cshtml (assuming no database issues etc)?

    I tried methods such as creating a class based on Page Model and then basing the new pages on my custom model but so far nothing I have tried has worked.

    I would have thought this would be a common requirement but I can't seem to find anything that explains how to do it.

    Thanks for any pointers.

    Robin

    Monday, June 17, 2019 1:53 PM

Answers

All replies

  • User475983607 posted

    The page model is not used for this type of data. 

    The System Version is global and should be a singleton created when the application starts.  User information can be cached in a the user's auth cookie, a separate cookie, or Session.

    Monday, June 17, 2019 2:09 PM
  • User-53740444 posted

    Hello

    In this instance SystemVersion is something I am defining so say it is Version 1 then I make some changes I will switch it to Version 2 so it shows in _Layout.cshtml. Is it possible you could provide an example of how I could read appsettings values in and make them available?

    For the second example say this is something else such as number of outstanding orders then I would want it to be current as users navigate so a cookie would not work here unless I was reading and writing it each page load.

    I just need an easy way of making variables available to _Layout.cshtml without having to declare the variable on each page and then add code that sets it.

    Some will be static (such as appsettings values) and others will not (such as current open calls).

    Thanks

    Robin

    Monday, June 17, 2019 2:28 PM
  • User475983607 posted

    robinwilson16

    In this instance SystemVersion is something I am defining so say it is Version 1 then I make some changes I will switch it to Version 2 so it shows in _Layout.cshtml. Is it possible you could provide an example of how I could read appsettings values in and make them available?

    Asp.NET Core, the subject of this thread, does not have appSettings.  appsetting.json?

    robinwilson16

    For the second example say this is something else such as number of outstanding orders then I would want it to be current as users navigate so a cookie would not work here unless I was reading and writing it each page load.

    I just need an easy way of making variables available to _Layout.cshtml without having to declare the variable on each page and then add code that sets it.

    This is a state management question that exists for all web dev.  You can either cache the value per user or read the values by user from the DB.  Cookie and Session are good for caching if the values not do change often.  If the values can change often, then fetch the values from the database on each request and add the values to the current context.  The tools you use depends on the kind of application.  Core has middlewareinject a service into the layout page, or use a view component.  In MVC you could use a filter or a base controller.

    What kind of application are you building?  Core?  

    Monday, June 17, 2019 2:49 PM
  • User-53740444 posted

    Sorry I should probably have been more specific.

    I have a set of razor pages in a .NET Core web application where each razor page defines the content of each page and then _Layout.cshtml provides the overall page template such as logo and navigation bar.

    I know how to get the values loaded into each page model for every razor page but then I must always define the same strings and set these on every page otherwise I get a NullReferenceException.

    It doesn't seem best practice to copy the same parameters onto each page so I was wondering if there was a way to make them available globally. An example would be if I loaded a string value containing the number of items in the basket and I then decided I would like to also include the total cost then I would need to define and set this new value on each page.

    I tried a few different methods but the issue tended to be that I could set static values but not values I pulled from the database as the context was unavailable.

    I want to pull some values from appsettings.json (containing the settings for the .NET Core Website) and others from the SQL Server database but I cannot find any examples online that cover this scenario.

    Thanks

    Robin

    Monday, June 17, 2019 6:47 PM
  • User475983607 posted

    robinwilson16

    Sorry I should probably have been more specific.

    I have a set of razor pages in a .NET Core web application where each razor page defines the content of each page and then _Layout.cshtml provides the overall page template such as logo and navigation bar.

    I know how to get the values loaded into each page model for every razor page but then I must always define the same strings and set these on every page otherwise I get a NullReferenceException.

    It doesn't seem best practice to copy the same parameters onto each page so I was wondering if there was a way to make them available globally. An example would be if I loaded a string value containing the number of items in the basket and I then decided I would like to also include the total cost then I would need to define and set this new value on each page.

    I tried a few different methods but the issue tended to be that I could set static values but not values I pulled from the database as the context was unavailable.

    This problem, managing state, is a problem that must be solved in any web framework regardless of the technology.   Each framework will have tools to make it a bit easier to handle.

    You certainly do not want to use static variables.  Do you want to cache the data or do you need the data to come from a table because it changes often?  This determines where you are getting the data.  The next step is figuring out what tools are available in your framework of choice.

    I think a View Component fits your requirement.

    https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-2.2

    robinwilson16

    I want to pull some values from appsettings.json (containing the settings for the .NET Core Website) and others from the SQL Server database but I cannot find any examples online that cover this scenario.

    This is already done for you in the Core 2.2 templates and easily handled in previous versions.  Simply inject configuration.  This concept is covered openly in the ASP.NET Core docs.  Here is an example.  Ignore the role manager.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    using Microsoft.Extensions.Configuration;
    
    namespace RazorIdentityScaffold.Pages
    {
        public class IndexModel : PageModel
        {
            private readonly RoleManager<IdentityRole> _roleManager;
            public IConfiguration _configuration { get; }
    
            public IndexModel(
                RoleManager<IdentityRole> roleManager, 
                IConfiguration configuration )
            {
                _roleManager = roleManager;
                _configuration = configuration;
            }
            public void OnGet()
            {
                Roles = _roleManager.Roles.ToList();
                ConnectionString = _configuration.GetConnectionString("DefaultConnection");
            }
    
            [BindProperty]
            public List<IdentityRole> Roles { get; set; }
    
            [BindProperty]
            public string ConnectionString { get; set; }
    
        }   
    }
    <div>
        @Model.ConnectionString
    </div>

    Monday, June 17, 2019 7:09 PM
  • User-821857111 posted

    There is no one-size-fits-all solution to the various scenarios that you have described. Seems to me that each one needs to be approached differently. The version number, for example, could be a global variable that's populated in Startup. This could be implemented as a static property on a class or as a service registered as a singleton in the DI container, for example.

    User specific information that might change on each request could be generated and managed in a global filter (https://www.learnrazorpages.com/razor-pages/filters). 

    Monday, June 17, 2019 7:26 PM
  • User-53740444 posted

    Thanks I will take a look at the view component as this looks like it might do the job.

    Regarding the configuration example you have provided, this is how I am doing it already but as I want the variable to appear in _Layout.cshtml it means I need to repeat all this code on every single razor page (say 20 pages) which would seem like a bit of duplication. So if later I decided I needed to get @Model.CompanyName then I would need to define this parameter in every page's model and set it as part of the OnGet method otherwise a NullReferenceException would be thrown in _Layout.cshtml. But then it probably shouldn't be coming from the model.

    Maybe that is the best way to do it but it just feels there should be something that could avoid so much repetition.

    Perhaps I could use a View Component for both these scenarios.

    Rather than just do what works I am trying to follow best practice and this was one area I thought maybe I wasn't. So I have no issue getting the parameters but the repetition of code is what I was wondering about. I will have a go with the view components.

    Thanks

    Robin

    Monday, June 17, 2019 10:08 PM
  • User-53740444 posted

    Thanks Mike

    It looks like filters may also be an option I could use although I have a feeling I tried these and couldn't pass in the dbcontext but then maybe that was something else. I will give them a go.

    Another scenario would be to build the page navigation from the database (which I think I will be needing to do soon). The page navigation appears in _Layout.cshtml and would be common to all pages but it would seem a bad idea to have to pass this data in from every page model. Not so much from a performance point of view but thinking best practice.

    Microsoft do provide some really helpful tutorials but this is not really a scenario they cover.

    Thanks

    Robin

    Monday, June 17, 2019 10:19 PM
  • User-1764593085 posted

    Hi robinwilson16,

    IMO, you need to deal with it for each page(using DI like this)...Have your tried to use ViewData in razor pages?It will not have error if you does not set a value to it.

    public async Task OnGetAsync()
            {
                ViewData["MyNumber"] = 42;
                //...
             }

    _layout.cshtml:

    <p>@ViewData["MyNumber"]</p>
    <div class="container">
            <partial name="_CookieConsentPartial" />
            <main role="main" class="pb-3">
                @RenderBody()
            </main>
    </div>

    Refer to 

    https://www.learnrazorpages.com/razor-pages/viewdata

    https://www.tutorialspoint.com/asp.net_core/asp.net_core_razor_layout_views.htm

    Best Regards,

    Xing

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, June 18, 2019 2:17 AM
  • User-821857111 posted

    robinwilson16

    Another scenario would be to build the page navigation from the database (which I think I will be needing to do soon). The page navigation appears in _Layout.cshtml and would be common to all pages but it would seem a bad idea to have to pass this data in from every page model.

    A database-driven menu should definitely be implemented as a view component.

    https://www.learnrazorpages.com/razor-pages/view-components

    Tuesday, June 18, 2019 9:20 AM
  • User475983607 posted

    Regarding the configuration example you have provided, this is how I am doing it already but as I want the variable to appear in _Layout.cshtml it means I need to repeat all this code on every single razor page (say 20 pages) which would seem like a bit of duplication. So if later I decided I needed to get @Model.CompanyName then I would need to define this parameter in every page's model and set it as part of the OnGet method otherwise a NullReferenceException would be thrown in _Layout.cshtml. But then it probably shouldn't be coming from the model.

    Maybe that is the best way to do it but it just feels there should be something that could avoid so much repetition.

    The previous links specifically cover how to inject a service into a View.  

    appsettings.json

    {
      "Version": "1.2.3.4",

    Service

    using Microsoft.Extensions.Configuration;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace RazorIdentityScaffold.Infrastructure
    {
        public interface IVersionService
        {
            string GetVersion();
        }
    
        public class VersionService : IVersionService
        {
            private IConfiguration _configuration { get; }
            public VersionService(IConfiguration configuration)
            {
                _configuration = configuration;
            }
    
            public string GetVersion()
            {
                return _configuration["Version"];
            }
        }
    }
    

    Startup.cs

    services.AddSingleton<IVersionService, VersionService>();
    
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    _ViewImports.cs

    @using RazorIdentityScaffold.Infrastructure;

    _Layout.cshtml

    @inject IVersionService versionService

    Implementation

    <div>@versionService.GetVersion()</div>

    Reference docs

    https://docs.microsoft.com/en-us/aspnet/core/mvc/views/dependency-injection?view=aspnetcore-2.2

    Tuesday, June 18, 2019 11:31 AM
  • User475983607 posted

    Also, I thought I should mention, a service is not needed to display appsettings.json values.  I wrote the service to show how to create a simple service.  Configuration can be injected anywhere as it is part of the default configuration.

    _ViewImports.cshtml

    @using Microsoft.Extensions.Configuration;

    _Layout.cshtml

    @inject IConfiguration config
    <!DOCTYPE html>

    Implementation

            <div>
                @config["Version"]
            </div>

    Tuesday, June 18, 2019 1:28 PM
  • User-53740444 posted

    Thanks for the detailed example.

    This looks like what I was trying to achieve and it removes the dependency on each variable having to be defined on every page model which was what I was after.

    Could I inject the database context into `VersionService` in the same way as `IConfiguration` is injected (like you would with a PageModel)?

    Thanks for your help.

    Robin

    Tuesday, June 18, 2019 2:59 PM
  • User-53740444 posted

    Thanks for the easier way to obtain configuration values too.

    I didn't realise you could do it that way. That will save a lot of code.

    Robin

    Tuesday, June 18, 2019 3:00 PM
  • User475983607 posted

    Could I inject the database context into `VersionService` in the same way as `IConfiguration` is injected (like you would with a PageModel)?

    Yes

    Tuesday, June 18, 2019 3:28 PM
  • User-821857111 posted

    Could I inject the database context into `VersionService` in the same way as `IConfiguration` is injected (like you would with a PageModel)?
    If you register your context with the DI system, you can inject it anywhere.

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<MyDbContext>(options => {
            options.UseSqlServer("server=.;database=myDb;trusted_connection=true;"));
        });
    }

    Then in the service:

    public class VersionService : IVersionService
    {
        private readonly MyDbContext _context;

        public VersionService(MyDbcontext context)
        {
            _context = context;
        }
        ...
    }

    Tuesday, June 18, 2019 3:33 PM