passing blazor parameters to another page - blazor-server-side

I have been trying to pass parameters trough another page and this works, however i'm not getting what I desired and it has probably to do with what i pass.
The first thing i pass is a name but includes spaces and special character, the second thing i pass is a web link
how i send it:
<div class="col-sm-4">
<h3>Programming</h3>
#if (programming == null)
{
<p><em>Loading...</em></p>
}
else
{
foreach (var program in programming)
{
#program.Name
<br />
}
}
</div>
where it goes to
#page "/CourseDetails"
#using Portfolio.Models;
#using Portfolio_Frontend.Data;
#using Microsoft.AspNetCore.WebUtilities
#inject NavigationManager NavigationHelper
<h3>CourseDetails</h3>
#if (Name == null)
{
<p><em>Loading...</em></p>
}
else
{
<p>#Name</p>
}
#code {
public string Name { get; set; }
protected override void OnInitialized()
{
var uri = NavigationHelper.ToAbsoluteUri
(NavigationHelper.Uri);
if (QueryHelpers.ParseQuery(uri.Query).
TryGetValue("name", out var name))
{
Name = name.First();
}
}
}
i tried parameters as well and now tried query string gives the same result.
the name it should pass in this particular case is: C# Intermediate: Classes, Interfaces and OOP
What i get is only 'C' I assume because it is not able to translate the #.
is there a way to pass literal strings?
where it goes to: https://localhost:5105/CourseDetails/?name=C#%20Intermediate:%20Classes,%20Interfaces%20and%20OOP
this seems right to me.

Minor correction of URL syntax methodology
You have:
#program.Name
Which has a URL of /CourseDetails/?name=C#
Normally, you would do either
/CourseDetails/C#
/CourseDetails?name=C#
Except, Blazor doesn't explicitly support optional route parameters (/CourseDetails?name=C#)
REF: https://blazor-university.com/routing/optional-route-parameters/#:~:text=Optional%20route%20parameters%20aren%E2%80%99t%20supported%20explicitly%20by%20Blazor,,then%20replace%20all%20references%20to%20currentCount%20with%20CurrentCount.
It looks as though you can keep the optional query parameters and fiddle with the QueryHelpers.ParseQuery() I don't quite buy into that but if you want to keep going that route check out this post by #chris sainty
Link: https://chrissainty.com/working-with-query-strings-in-blazor/
I would much rather create a new model (DTO) that knows exactly how to display the CourseDetails name in a URL encoded fashion for the link, and the display name for the user.
public class ProgramModel
{
private readonly string name;
public ProgramModel(string name)
{
this.name = name;
}
public string DisplayName => name;
public string RelativeUrl => HttpUtility.UrlEncode(name);
}
And when we need to render the links on the 'Courses' page, it would look like this:
#page "/courses"
#using BlazorApp1.Data
<div class="col-sm-4">
<h3>Programming</h3>
#foreach (var program in programming)
{
#program.DisplayName
<br />
}
</div>
#code {
public IEnumerable<ProgramModel> programming { get; set; }
protected override void OnInitialized()
{
programming = new List<ProgramModel>()
{
new ProgramModel("Rust Things"),
new ProgramModel("JavaScript Things"),
new ProgramModel("C# Things")
};
}
}
And finally, when displaying the CourseDetails page, we can simply decode the name from the URL with the same utility that encoded the string in the first place, instead of guessing whether or not it's the apps fault, or the browsers fault that the '#' is not getting encoded properly to '%23'
#page "/CourseDetails/{Name}"
#inject NavigationManager NavigationHelper
#using System.Web
<h3>CourseDetails</h3>
<p>#HttpUtility.UrlDecode(Name)</p>
#code {
[Parameter]
public string Name { get; set; }
}

I recommend letting go of the idea of navigating from page to page, and using components:
<div>
#if (SelectedItem is not null)
{
<MyResultsPage SelectedProgramClass=#SelectedItem />
}
</div>
#code
{
ProgramClass SelectedItem {get; set;}
void SomeWayToSelectMyItem(ProgramClass newSelection){
SelectedItem = newSelection;
StateHasChanged();
}
}
Then in your display page, MyResultsPage.blazor
<div>
<div>#SelectedProgramClass.name</div>
. . .
</div>
#code {
[Parameter]
ProgramClass SelectedProgramClass{get; set;}
}
<MyResultsPage> will not show up in any way on the client, or even be initialized, until you've assigned something to SelectedProgramClass.

Related

How can I check in the login whether the email exists in a database - razor page?

I want the site login form to check if the email is in the system or not, to return an error message.
I can not do this because I am new. I tried to do something I think that's the direction, I'd be happy if you could help me.
public class ConnectionAttribute : ValidationAttribute
{
private readonly OrderContext _context;
private readonly string _connectingMail;
public ConnectionAttribute(string connectingMail)
{
_connectingMail = connectingMail;
}
protected override ValidationResult IsValid(
object value, ValidationContext validationContext)
{
List<Customer> allCustomers = _context.Customer.ToList();
List<string> allMails = new List<string>();
foreach (var item in allCustomers)
{
allMails.Add(item.CustomerMail);
}
var file = value as string;
if (allMails.Contains(_connectingMail))
{
return new ValidationResult(GetErrorMessage());
}
return ValidationResult.Success;
}
public string GetErrorMessage()
{
return $".";
}
}
I think this code is correct, but I do not know what to call it after clicking connect .
What can you call him?
You could try to use Remote validation.
First, Remote validation in ASP.NET (Core) relies on Unobtrusive AJAX, so you will need to install that first. The easiest way to do this is via LibMan. Please check the wwwroot/lib folder whether you have installed them or not. If doesn't install it, refer the following steps:
Please right click on the lib folder in wwwroot, choose Add » Client-side Library, and then choose jsdelivr as the source, and type in jquery-ajax-unobtrusive.
Second, since your application is an Asp.net Core Razor application, in the PageModel, add Email property with PageRemote attribute and a OnPostCheckEmail method to check whether the email is exist or not:
public class RemoteValidateModel : PageModel
{
[PageRemote(ErrorMessage = "Email Address already exists", AdditionalFields = "__RequestVerificationToken",
HttpMethod = "post",PageHandler = "CheckEmail")]
[BindProperty]
public string Email { get; set; }
public void OnGet()
{
}
//this method is used to check whether the email is exist or not.
public JsonResult OnPostCheckEmail()
{
//query the database and get all existing Emails or directly check whether the email is exist in the database or not.
var existingEmails = new[] { "jane#test.com", "claire#test.com", "dave#test.com" };
var valid = !existingEmails.Contains(Email);
return new JsonResult(valid);
}
}
Code in the Razor Page:
#page
#model RazorSample.Pages.RemoteValidateModel
#{
}
<form method="post">
<input asp-for="Email" />
<span asp-validation-for="Email" class="text-danger"></span><br>
<input type="submit" />
</form>
#section scripts{
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<partial name="_ValidationScriptsPartial" />
<script src="~/lib/jquery-ajax-unobtrusive/dist/jquery.unobtrusive-ajax.min.js"></script>
}
Then, the result as below:
More detail information, please check this tutorial:
Remote Validation in Razor Pages
Besides, if you want to use Remote validation in Asp.net Core MVC application, you can check [Remote] attribute.

Microsoft.AspNetCore.Components.Forms.InputRadioGroup` does not support the type xxx

I want to use radio group in blazor so after implementing edit form and select one of the radio button I got this error :
Microsoft.AspNetCore.Components.Forms.InputRadioGroup`1[EGameCafe.SPA.Models.GameModel] does not support the type 'EGameCafe.SPA.Models.GameModel'.
here is my edit form :
<EditForm Model="ViewModel" OnValidSubmit="HandleCreateGroup">
#if (ViewModel.Games.List.Any())
{
<InputRadioGroup Name="GameSelect" #bind-Value="Gamemodelsample">
#foreach (var game in ViewModel.Games.List)
{
<InputRadio Value="game" />
#game.GameName
<br />
}
</InputRadioGroup>
}
</EditForm>
#code{
public GameModel GameModelSample { get; set; } = new();
}
and GameModel is :
public class GameModel
{
public string GameId { get; set; }
public string GameName { get; set; }
}
The InputRadioGroup, like other Blazor components, supports only a limited amount of types like String or Int32. You had the right idea, but unfortunately, you run into a kind of limitation of Blazor.
You could try to create a wrapper field.
private String _selectedGameId = "<Your Default Id>";
public String SelectedGameId
{
get => _selectedGameId;
set
{
_selectedGameId = value;
// Set the property of the ViewModel used in your Model Property of the EditContext or any other property/field
ViewModel.SelectedGame = ViewModel.Games.List?.FirstOrDefault(x => x.GameId == value);
}
}
Use the property SelectedGameId as the bind value of the InputRadioGroup component.
<InputRadioGroup Name="GameSelect" #bind-Value="SelectedGameId" >
#foreach (var game in ViewModel.Games.List)
{
<InputRadio Value="game.GameId" />
#game.GameName
<br />
}
</InputRadioGroup>
As an alternative, you can create a custom component that inheriting from InputRadioGroup to create a kind of GameBasedInputRadioGroup. If you are interested I can post a sample.
Because in your code #bind-Value="Gamemodelsample",you are trying to bind GameName(string) to Gamemodelsaple(object), which will cause type mismatch problems.
You only need to modify your code to:
#bind-Value="GameModelSample.GameName"

How do I change the title and other html-elements in the head of a HTML-document in Razor Components?

I'm fairly new to Razor Components and I would like to change the title and other data inside the HTML-document-head depending on what page a user visits (e.g. if a user visits the product page of product X, I want to change the title and other relevant data in the head accordingly).
I already had a look at https://github.com/aspnet/Blazor/issues/1311#issuecomment-470760857 but the presented solutions look a bit hacky.
I also know that I could theoretically use JavaScript interop to achieve my goals, but that also seems to be a bit messy.
I tried to render the app like this:
#page "{*clientPath}"
<!DOCTYPE html>
<html>
#(await Html.RenderComponentAsync<App>())
</html>
But the attempt failed due to an error in components.server.js (Uncaught Error: There is no browser renderer with ID 0.). I know I shouldn't place JavaScript-tags into components, but I thought I would give it a try.
Hope you guys can help me.
Thanks for reading. :)
Here is how honkmother(https://github.com/honkmother) did it. Here is the link:
https://github.com/aspnet/Blazor/issues/842#issuecomment-490671409
The Index.cshtml:
#page "{*clientPath}"
<!DOCTYPE html>
<html>
#(await Html.RenderComponentAsync<html>())
</html>
Then two classes called "html" and "metatags".
The html class creates the html tags and store the header (script tags, etc) inside of a html file that is injected as markup.
public class html : ComponentBase
{
public static string HeadTXT = File.ReadAllText("head.htm");
Metatags meta = new Metatags() { Title = "some title" };
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.RenderTree.RenderTreeBuilder builder)
{
builder.OpenElement(0, "head");
builder.AddMarkupContent(1, HeadTXT);
builder.OpenComponent<Head>(2);
builder.AddAttribute(3, "MetaInfo", meta);
builder.CloseComponent();
builder.CloseElement();
builder.OpenElement(4, "body");
builder.OpenComponent<Body>(5);
builder.AddAttribute(6, "MetaInfo", meta);
builder.CloseComponent();
builder.AddMarkupContent(7, "<script src=\"_framework/components.server.js\"></script>");
builder.CloseElement();
}
}
public class Metatags
{
public string Title { get; set; } = "example.nyc — weirdest hack ever";
public string Description { get; set; } = "testing";
public Head Component;
}
A "body" Component.
<DetectPrerender MetaInfo="#MetaInfo">
<Router AppAssembly="typeof(Startup).Assembly" />
</DetectPrerender>
#functions {
[Parameter]
private Metatags MetaInfo { get; set; }
}
And a "head" component.
#using example.Shared
<title>#MetaInfo.Title</title>
<meta name="description" content="#MetaInfo.Description">
#{
MetaInfo.Component = this;
}
#functions {
[Parameter]
private Metatags MetaInfo { get; set; }
private bool ShouldRend = false;
protected override bool ShouldRender()
{
if (ShouldRend)
{
ShouldRend = false;
return true;
}
return false;
}
public void ShouldRe()
{
ShouldRend = true;
base.Invoke(() => base.StateHasChanged());
}
}
I found a NuGet that makes it very simple:
Toolbelt.Blazor.HeadElement
Startup.cs
using Toolbelt.Blazor.Extensions.DependencyInjection;
public void ConfigureServices(IServiceCollection services)
{
services.AddHeadElementHelper();
Then all you have to do is add the component to your page:
#page "/customers"
<Toolbelt.Blazor.HeadElement.Title>
Customers
</Toolbelt.Blazor.HeadElement.Title>
<h1>Customers</h1>
The result will be
<title>customers<title>
in your head section. This plugin also has components for meta tags and links.

ASP.Net Core Razor Pages: How to return the complex model on post?

I created a new ASP.Net Core 2 (Razor Pages) Project
My model is:
public class FormularioGenerico
{
public FormularioGenerico()
{
}
public string IP { get; set; }
public List<string> items { get; set; } = new List<string>();
}
On the page I put
on the page.cshtml.cs
public class EditarModel : PageModel
{
[BindProperty]
public FormularioGenerico ff { get; set; }
[BindProperty]
public string Message { get; set; }
public void OnGet()
{
this.ff = new FormularioGenerico();
ff.IP = "C# FORM";
ff.items.Add("OK1");
ff.items.Add("OK2");
ff.items.Add("OK3");
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var m = ModelState.IsValid; // true
Debug.WriteLine(this.ff.IP); // is Always returning null
Debug.WriteLine(this.ff.items.Count); // is Always returning null
}
}
on the page.cshtml:
#model Formulario.Pages.EditarModel
...
<h1>#Model.ff.IP</h1>
#foreach (var i in Model.ff.items)
{
<div>#i</div>
}
<button type="submit">Enviar</button>
The items are correctly output. But the complete object does not go to the OnPost.
The problem is: The model is not coming fully populated on the OnPost.
How to receive the full object that was created on the OnGet, plus the changes made by the user on the form, on the post to OnPostAsync() ?
The BindProperty attribute is used to inform ASP.NET Core that the values that the form submitted should be mapped to the specified object. In your case you set the values for the ff property but you do not have the equivalent input values so that ASP.NET Core will get these values in order to store them back to the ff property.
In order to make it work you will have to replace your razor code with the following code:
<form method="post">
<h1>#Model.ff.IP</h1>
<input asp-for="#Model.ff.IP" type="hidden" /> #* create a hidden input for the IP *#
#for (int i = 0; i < Model.ff.items.Count(); i++)
{
<input asp-for="#Model.ff.items[i]" type="hidden" /> #* create a hidden input for each item in your list *#
<div>#Model.ff.items[i]</div>
}
<button type="submit">Enviar</button>
</form>
Very important. To make this work you can not use the foreach loop because ASP.NET core will not be able to find the values. You will have to use a for loop.
The inputs that I added are hidden because I guess you do not want them to be visible but you can remore the type="hidden" so that you will be able to see them. Every change that you make to these inputs will be submitted to the OnPostAsync method.

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>