I port my ASP.NET ecommerce application to ASP Net Core. In the my application i was using LayoutViewModel and i was filling it in the base controller (for example categories, because categories is neccessary by all view) so i could use it in _Layout.cshtml.
How can i port this structure to ASP.NET Core or do you have any suggestion? Am i use a middleware or?
Thank you.
public class BaseController : Controller
{
protected override IAsyncResult BeginExecute(HttpContext requestContext,
AsyncCallback callback,
object state)
{
...
var layoutViewModel = new LayoutViewModel
{
Categories = Categories,
};
ViewBag.LayoutViewModel = layoutViewModel;
...
}
}
public class HomeController:BaseController
{
public ActionResult Index()
{
var myHomeViewModel= new MyHomeViewModel{Prop="Test"};
return View(myHomeViewModel);
}
}
//In _Layout.cshtml
#{
var layoutViewModel = (LayoutViewModel)ViewBag.LayoutViewModel
}
<div class="container">
<div class="header">
For Example Categories Count: #layoutViewModel.Categories.Count
</div>
<div class="body">
#RenderBody()
</div>
<div class="footer">
</div>
</div>
In ASP.Net Core , you can use View components to defines your logic to get data in an InvokeAsync method , and render it in your partial view .
Another option is to use ActionFilter . For example , if you have view model :
public class MainLayoutViewModel
{
public string PageTitle { get; set; }
}
Creating ActionFilter class :
public class CommonViewBagInitializerActionFilter : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
((Controller)context.Controller).ViewBag.MainLayoutViewModel = new MainLayoutViewModel() {
PageTitle = "MyTitle"
};
}
}
Register the filter in ConfigureServices function:
services.AddMvc(config =>
{
config.Filters.Add(new CommonViewBagInitializerActionFilter());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
And in your _Layout.cshtml :
#{
var viewModel = (MainLayoutViewModel)ViewBag.MainLayoutViewModel;
}
<title>#viewModel.PageTitle</title>
Related
I have an razor page receiving an PageModel descendant.
For exibition-only pages (dashboards, reports, etc), it works like a charm. I use the base PageModel class to make available to turn all pages reflect my hosting.json configuration, making links inside the app dynamic.
So the hierarchy is: PageModel --> BasePageModel --> ConfigTransmissorFragModel
Now I have this SendingConfiguration model (I will process it in lower level layer):
public class SendingConfiguration
{
// I will edit this one
[DisplayName("Send it to production server?")]
public bool ProductionServer { get; set; }
}
The PageModel is as below:
// BasePageModel will process the urls of the site.
public class ConfigTransmissorFragModel : BasePageModel
{
public SendingConfiguration SendConfig { get; set; }
private void UpdateSendingConfiguration()
{
var Config = InfoExibicao.ConfigAtiva;
ConfigEnvio.ProductionServer = Config.ProductionServer;
}
private InfoConfiguration _InfoExibition;
public InfoConfiguration InfoExibition
{
get { return _InfoExibition; }
set
{
_InfoExibition = value;
}
}
public ConfigTransmissorFragModel() : base(null)
{
SendConfig = new SendingConfiguration();
}
public ConfigTransmissorFragModel(
ConfigDashboard PConfigDash,
InfoConfiguration PInfoConfig
) : base(PConfigDash)
{
ConfigEnvio = new SendingConfiguration();
this.InfoExibition = PInfoConfig;
UpdateSendingConfiguration();
}
}
The Model is generated in controller:
var Fch = FacadeInfoConfig;
var InfoConfig = Fch.ObterInfoConfiguracao(IdTransmissor);
var CfgDash = ConfigDash;
var Modelo = new ConfigTransmissorFragModel(CfgDash, InfoConfig);
Modelo.UrlServer = ConfigHost.WebServiceUrl;
In the razor page I have this header:
#page
#using ESenderWebService.ModeloPagina
#using Microsoft.AspNetCore.Mvc.RazorPages;
#using Negocio.Integracao.Configuracao;
#model ESenderWebService.ModeloPagina.ConfigTransmissorFragModel
#{
//ViewData["Title"] = "Configuracao de Transmissor para envio";
Layout = "~/Pages/Shared/_Layout_Dashboard.cshtml";
}
The form:
<form autocomplete="off" asp-controller="InfoConfig" asp-action="SalvarConfig" method="post">
<div class="form-group">
<fieldset>
<legend style="width: auto; margin-bottom: auto;"> Configurações </legend>
<div>
#Html.CheckBoxFor(m => m.SendConfig.ProductionServer)
#Html.LabelFor(m => m.SendConfig.ProductionServer)
</div>
<div>
<button class="btn btn-primary " name="submit" type="submit">Save</button>
</div>
</fieldset>
</div>
</form>
My problem is: how to receive SendingConfiguration on my post handler?
// This one explodes complaining that HttpContext is missing
[HttpPost()]
public IActionResult SalvarConfig(ConfigTransmissorFragModel PModel)
// This one never reflects what I mark in the form
// It always returns false in PModel.ProductionServer
[HttpPost()]
public IActionResult SalvarConfig([FromForm] SendingConfiguration PModel)
What I'm doing wrong?
I am not sure why the first HttpPost syntax exploding in your case, because its working fine in my sample project.
Using Core 3.1 and Razor Pages
I trying to undertake the simple task of passing a search string into a ViewComponent and invoke the results.
I have encountered two issue I cannot find help with:
How to pass the input search string to the view component?
How to invoke the view component when the search button is clicked?
_Layout Page
<input id="txt" type="text" />
<button type="submit">Search</button>
#await Component.InvokeAsync("Search", new { search = "" })
//Should equal input string
I am new to core so any nudges in the right direction would be appreciated.
View component is populated on server side and then return to your client for rendering, so you can't directly pass client side input value into view component . In your scenario , when clicking search button , you can use Ajax to call server side method to load the view component and pass the input value :
Index.cshtml
<input id="txt" type="text" />
<button onclick="loadcomponents()">Search</button>
<div id="viewcomponent"></div>
#section Scripts{
<script>
function loadcomponents() {
$.ajax({
url: '/?handler=Filter',
data: {
id: $("#txt").val()
}
})
.done(function (result) {
$("#viewcomponent").html(result);
});
}
</script>
}
Index.cshtml.cs
public IActionResult OnGetFilter(string id)
{
return ViewComponent("Users", new { id = id });
}
UsersViewComponent.cs
public class UsersViewComponent : ViewComponent
{
private IUserService _userService;
public UsersViewComponent(IUserService userService)
{
_userService = userService;
}
public async Task<IViewComponentResult> InvokeAsync(string id)
{
var users = await _userService.GetUsersAsync();
return View(users);
}
}
Edit: Oh, you edited the razor tag in after I posted my answer. Well, my answer is only valid for ASP.NET Core MVC.
I assume that your controller looks something like this:
[HttpGet]
public IActionResult Index()
{
var model = new IndexVM();
return View(model);
}
[HttpPost]
public IActionResult Index(IndexVM model)
{
// you can do something with the parameters from the models here, or some other stuff
return View(model);
}
Your ViewModel can look like this:
public class IndexVM
{
public string SearchTerm {get;set;}
}
Your View where you use your ViewComponent:
#model IndexVM
// <form tag ...
<input asp-for="SearchTerm" />
<button type="submit">Search</button>
#await Component.InvokeAsync(nameof(Search), Model)
ViewComponent:
public class Search : ViewComponent
{
public IViewComponentResult Invoke(IndexVM indexVM)
{
// Do something with indexVM.SearchTerm
}
}
View of ViewComponent:
#model IndexVM
// ...
This question already has answers here:
The model item passed into the dictionary is of type .. but this dictionary requires a model item of type
(7 answers)
Closed 5 years ago.
I'm using .net core 2.0 (preview2) to build a MVC web app. What I'm trying to do is to have a part of the web page to refresh on a certain interval, so that new data will be loaded.
(For the purpose of this example, it's just the output DateTime.Now)
Here's what I've got so far:
index.cshtml (Main View)
<div id="content">
#Model.name
<br />
#Model.city
<div id="employee">#Html.Partial("Employee")</div>
</div>
<script>
$(document).ready(function () {
var url = "#(Html.Raw(Url.Action("Index", "Employee")))";
$("#employee").load(url);
setInterval(function () {
var url = "#(Html.Raw(Url.Action("ActionName", "Employee")))";
$("#employee").load(url);
}, 1000); //Refreshes every second
$.ajaxSetup({ cache: false }); //Turn off caching
});
</script>
HomeController.cs (Controller1)
using System;
using Microsoft.AspNetCore.Mvc;
using DemoApp.Models;
namespace DemoApp.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
CustomersViewModel customers = new CustomersViewModel();
customers.name = "John";
customers.city = "New York";
return View(customers);
}
}
}
CustomerViewModel.cs (Model 1)
using System;
namespace DemoApp.Models
{
public class CustomersViewModel
{
public string name { get; set; }
public string city { get; set; }
}
}
Employee.cshtml (Partial view)
#model EmployeeViewModel
<div id="employeeContent">
Hello Employees!
<br />
#Model.employeeName
<br />
#Model.employeeCity
<br />
#Model.time
</div>
EmployeeViewModel.cs (Model 2)
using System;
namespace DemoApp.Models
{
public class EmployeeViewModel
{
public string employeeName { get; set; }
public string employeeCity { get; set; }
public string time { get; set; }
}
}
EmployeeController.cs (Controller2)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using DemoApp.Models;
namespace DemoApp.Controllers
{
public class EmployeeController : Controller
{
public IActionResult Index()
{
EmployeeViewModel evm = new EmployeeViewModel();
evm.employeeName = "Jack";
evm.employeeCity = "Los Angeles";
evm.time = DateTime.Now.ToString();
return View();
}
}
}
As you can see, I'm trying to show data from the logic in Index() from the EmployeeController inside the partial view. To check if it works, the current date/time should be showed.
With this state, I get the error:
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'DemoApp.Models.CustomersViewModel', but this ViewDataDictionary instance requires a model item of type 'DemoApp.Models.EmployeeViewModel'.
I tried a lot of different things I found here, but actually nothing really helped. Sure, I avoided the error message, but then I wasn't able to load any data into the partial view.
Where do I go from here, what am I missing?
EDIT: This is not an exact duplicate at all. The duplicate link refers to something in MVC, but not in .net core MVC, where #Html.Action doesn't exist.
But the link did help :-)
You need to change your EmployeeController:
public class EmployeeController : Controller
{
public PartialViewResultIndex()
{
EmployeeViewModel evm = new EmployeeViewModel();
evm.employeeName = "Jack";
evm.employeeCity = "Los Angeles";
evm.time = DateTime.Now.ToString();
return PartialView("Employee", evm);
}
}
I'm using this class :
public class SteamGet
{
public delegate ActionResult ResultDone(List<Steam> List);
public event ResultDone OnResultDone;
public void Get()
{
Debug.Write("Result Received !");
using (WebClient client = new WebClient())
{
string data = client.DownloadString("http://api.steampowered.com/ISteamApps/GetAppList/v0001/");
JObject steamobject = JObject.Parse(data);
var rslt = steamobject.SelectToken("applist").SelectToken("apps");
var objd = JsonConvert.DeserializeObject<ObjectResult>(rslt.ToString());
OnResultDone(objd.MyList);
}
}
}
And my home controller looks like this :
public class HomeController : Controller
{
// GET: Home
protected SteamGet Getter = new SteamGet();
public ActionResult Index()
{
Getter.OnResultDone += Getter_OnResultDone;
Task.Run(() => Getter.Get());
return View();
}
private ActionResult Getter_OnResultDone(List<Models.Steam> List)
{
return View("ViewTest",List);
}
}
so as you can see i'm calling the Get() Method then Returning the View , when the Event OnresultDone Raised i want to Call another View or refreshing the home view
my home view :
#using TemplateAspTest.Repository
#model List<TemplateAspTest.Models.Steam>
#{
ViewBag.Title = "Home";
Layout = "~/Views/Shared/_Main.cshtml";
}
<section id="intro" class="main">
<span class="icon fa-diamond major"></span>
<h2>Test one section ! </h2>
<p>
test done !
</p>
<ul class="actions">
#{
if (#Model == null)
{
<li>waiting ....</li>
}
else
{
<li>ViewBag.Message;</li>
#Model[0].Name;
}
}
</ul>
</section>
EDIT :
i'am Returning View and waiting for a event to be raised i want to call another view when the even is raised
I have a situation I can't solve alone... I have this object:
public class Service
{
...
public Configuration Conf{get; set;}
...
}
public class Configuration
{
...
public List<Gateway> Gateways{get; set;}
...
}
Now I have a page to create a new service and I want to add runtime (client-side) a partial view.
I have a page that accept as model the Service class.. and a partial view that have the gateway as model..
Everything seems to work..
#model ObjectModel.Entities.Configurations.Service
...
#section scripts {
<script type="text/javascript">
function loadPartial(event) {
event.preventDefault();
event.stopPropagation();
var $div = $(event.target).closest(event.data.divContainer),
url = $(this).data('url'), model = event.data.model;
$.post(url, function (model) {
$div.prepend(model);
});
}
$('#link_add_gateway').live('click', { divContainer: "#new_gateway", model: #Html.Raw(Json.Encode(Model)) }, loadPartial);
</script>
}
...
<div id="new_gateway">
<a id="link_add_gateway" class="cursor_pointer"
data-url='#Url.Action("RenderGateway", "Configuration")'>Aggiungi gateway</a>
</div>
<input type="submit" value="Create" class="btn btn-default" />
And here the controller:
//EDIT: Now service is valorized here too..
public ActionResult RenderGateway(Service service)
{
Gateway g = new Gateway();
service.Configuration.Gateways.Add(g);
return PartialView("~/Views/_Partials/Gateway/Edit.cshtml", g);
}
[HttpPost]
public ActionResult Create(Service service)
{
//Still nothing
}
Here the problem:
Service has no gateway valorized.. I think is correct, but I don't know how to solve it! I would like to associate the model of the partial view to the model of the page.
How can I do?
thank you
UPDATE:
public class Configuration
{
[XmlElement(ElementName = "gateway")]
public GatewaysList Gateways { get; set; }
public Configuration()
{
this.Gateways = new GatewaysList();
}
}
[Serializable]
public class GatewaysList : List<Gateway>
{
public Gateway this[int gatewayId]
{
get
{
return this.Find(g => g.GatewayId == gatewayId);
}
}
}
I think you shouldn't use get to do this call because you have to send parameters
So try something like this
$().ajax({method: 'POST', data: #Html.Raw(Json.Encode(Model)), //other parameters})
and then change
public ActionResult RenderGateway(ObjectModel.Entities.Configurations.Service service)
{
return PartialView("~/Views/_Partials/Gateway/Edit.cshtml",service);
}
the key for your problem is to use #Html.Raw(Json.Encode(Model)) to reSend your Model over pages
update
this code came from my working project so I'm sure that works, try sending the parameter as a string and then deserialize it
public ActionResult RenderGateway(string service)
{
var jsSettings = new JsonSerializerSettings();
jsSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
var deserializedModel = JsonConvert.DeserializeObject<Service >(service, jsSettings);
//now deserializedModel is of type Service
return PartialView("~/Views/Shared/something.cshtml", deserializedModel);
}
update 2
I see from your GatewayList class that it's an indexer. They can't be serialized by xmlserializer
You can do something like this
public class GatewaysList : List<Gateway>
{
[XmlIgnore]
public Gateway this[int gatewayId]
{
get
{
return this.Find(g => g.GatewayId == gatewayId);
}
}
[XmlArray(ElementName="GatewaysList")]
[XmlArrayItem(ElementName="Gateway", Type=typeof(Gateway))]
public List<Gateway> GatewaysList
{
get
{
}
set
{
}
}
}
Solved...
"Just" 1 row missed in my code... In my PartialView:
#using (Html.BeginCollectionItem("Configuration.Gateways"))
Now everything works correctly..
Ah... I have forgot to say that I had to install the BeginCollectionItem plugin and add to web.config the following line:
<add namespace="HtmlHelpers.BeginCollectionItem" />
in system.web.webPages.razor -> pages -> namespaces