Associate PartialView to model of the page - c#

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

Related

How to pass an input string into a ViewComponent?

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
// ...

Port Layout View Model To Asp.Net Core

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>

Refreshing content in an .net core mvc partial view [duplicate]

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);
}
}

connecting controller with model to display results in view page

So i have this aps.net mvc project in which i created a service layer, model views, controller, and a view page. But i am having trouble displaying my results to the view page. I am starting this would by passing in a specific linq statement in the service layer so i should be able to return it to show up on the view. Here is what i have:
Service:
public IEnumerable<RoleUser> GetUsers(int sectionID)
{
var _role = DataConnection.GetRole<RoleUser>(9, r => new RoleUser
{
Name = RoleColumnMap.Name(r),
Email = RoleColumnMap.Email(r)
}, resultsPerPage: 20, pageNumber: 1);
return _role;
}
Models:
public partial class Role
{
public RoleView()
{
this.Users = new HashSet<RoleUser>();
}
public ICollection<RoleUser> Users { get; set; }
}
public class RoleUser
{
public string Name { get; set; }
public string Email { get; set; }
}
Controller:
public ActionResult RoleUser(RoleView rvw)
{
var rosterUser = new RosterService().GetUsers();
ViewBag.RosterUsers = rosterUser;
return View();
}
View:
<div>
<span>#Model.Name</span>
</div>
I am not sure what i am missing or doing wrong but any tips will be great. I basically want to return the results from the linq statement i am testing to see that the connection is correct and functionality is there before enhancing. Thanks...
Well, if I were to go off the code you've provided I would say that I'm unsure how this compiles:
public partial class Role
{
public RoleView()
{
this.Users = new HashSet<RoleUser>();
}
public ICollection<RoleUser> Users { get; set; }
}
it feels like that should be:
public partial class RoleView
and then I would say that at the top of your view you're missing this:
#model NamespaceToClass.RoleView
and then I would say you're not going to be able to issue this:
#Model.Name
because RoleUser isn't your model. You're going to need to loop through the users:
#foreach (RoleUser ru in Model.Users)
and then inside that loop you can build some HTML with this:
ru.Name
but I would also question your controller. Right now it's receiving a model to return that model. There is some code missing here but generally speaking, inside the method:
public ActionResult RoleUser(RoleView rvw)
you would actually go get the data, construct the model, and then return that:
var users = serviceLayer.GetUsers(...);
// now construct the RoleView model
var model = ...
return View(model);
Based off of our conversation you currently have something like this in your controller:
public ActionResult View(int id)
{
// get the menu from the cache, by Id
ViewBag.SideBarMenu = SideMenuManager.GetRootMenu(id);
return View();
}
public ActionResult RoleUser(RoleView rvw)
{
var rosterUser = new RosterService().GetUsers();
ViewBag.RosterUsers = rosterUser;
return View();
}
but that really needs to look like this:
public ActionResult View(int id)
{
// get the menu from the cache, by Id
ViewBag.SideBarMenu = SideMenuManager.GetRootMenu(id);
var rosterUser = new RosterService().GetUsers();
ViewBag.RosterUsers = rosterUser;
return View();
}
because you're launching this page from the sidebar which is hitting this action because you're passing the id in the URL. You don't even need the other action.

In ASP.NET MVC, deserialize JSON prior to or in controller's action method

I am working on a website that will post a JSON object (using jQuery Post method) to the server side.
{
"ID" : 1,
"FullName" : {
"FirstName" : "John",
"LastName" : "Smith"
}
}
At the same time, I wrote classes on the server side for this data structure.
public class User
{
public int ID { get; set; }
public Name FullName { get; set;}
}
public class Name
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
When I run the website with following code in my controller class, the FullName property doesn't get deserialized. What am I doing wrong?
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Submit(User user)
{
// At this point, user.FullName is NULL.
return View();
}
I resolved my problem by implementing an action filter; code sample is provided below. From the research, I learned that there is another solution, model binder, as takepara described above. But I don't really know that pros and cons of doing in either approach.
Thanks to Steve Gentile's blog post for this solution.
public class JsonFilter : ActionFilterAttribute
{
public string Parameter { get; set; }
public Type JsonDataType { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.ContentType.Contains("application/json"))
{
string inputContent;
using (var sr = new StreamReader(filterContext.HttpContext.Request.InputStream))
{
inputContent = sr.ReadToEnd();
}
var result = JsonConvert.DeserializeObject(inputContent, JsonDataType);
filterContext.ActionParameters[Parameter] = result;
}
}
}
[AcceptVerbs(HttpVerbs.Post)]
[JsonFilter(Parameter="user", JsonDataType=typeof(User))]
public ActionResult Submit(User user)
{
// user object is deserialized properly prior to execution of Submit() function
return View();
}
1.create custom model binder
public class UserModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
User model;
if(controllerContext.RequestContext.HttpContext.Request.AcceptTypes.Contains("application/json"))
{
var serializer = new JavaScriptSerializer();
var form = controllerContext.RequestContext.HttpContext.Request.Form.ToString();
model = serializer.Deserialize<User>(HttpUtility.UrlDecode(form));
}
else
{
model = (User)ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
}
return model;
}
}
2.add model binder in application_start event
ModelBinders.Binders[typeof(User)] = new UserModelBinder();
3.use jQuery $.get/$.post in view client JavaScript code.
<% using(Html.BeginForm("JsonData","Home",new{},FormMethod.Post, new{id="jsonform"})) { %>
<% = Html.TextArea("jsonarea","",new {id="jsonarea"}) %><br />
<input type="button" id="getjson" value="Get Json" />
<input type="button" id="postjson" value="Post Json" />
<% } %>
<script type="text/javascript">
$(function() {
$('#getjson').click(function() {
$.get($('#jsonform').attr('action'), function(data) {
$('#jsonarea').val(data);
});
});
$('#postjson').click(function() {
$.post($('#jsonform').attr('action'), $('#jsonarea').val(), function(data) {
alert("posted!");
},"json");
});
});
</script>
You could try Json.NET. The documentation is pretty good and it should be able to do what you need. You'll also want to grab JsonNetResult as it returns an ActionResult that can be used in ASP.NET MVC application. It's quite easy to use.
Json.NET also works well with Date serialization. More info regarding that can be found here.
Hope this helps.
Try this;
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Submit(FormCollection collection)
{
User submittedUser = JsonConvert.DeserializeObject<User>(collection["user"]);
return View();
}
After some research, I found Takepara's solution to be the best option for replacing the default MVC JSON deserializer with Newtonsoft's Json.NET. It can also be generalized to all types in an assembly as follows:
using Newtonsoft.Json;
namespace MySite.Web
{
public class MyModelBinder : IModelBinder
{
// make a new Json serializer
protected static JsonSerializer jsonSerializer = null;
static MyModelBinder()
{
JsonSerializerSettings settings = new JsonSerializerSettings();
// Set custom serialization settings.
settings.DateTimeZoneHandling= DateTimeZoneHandling.Utc;
jsonSerializer = JsonSerializer.Create(settings);
}
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object model;
if (bindingContext.ModelType.Assembly == "MyDtoAssembly")
{
var s = controllerContext.RequestContext.HttpContext.Request.InputStream;
s.Seek(0, SeekOrigin.Begin);
using (var sw = new StreamReader(s))
{
model = jsonSerializer.Deserialize(sw, bindingContext.ModelType);
}
}
else
{
model = ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
}
return model;
}
}
}
Then, in Global.asax.cs, Application_Start():
var asmDto = typeof(SomeDto).Assembly;
foreach (var t in asmDto.GetTypes())
{
ModelBinders.Binders[t] = new MyModelBinder();
}

Categories

Resources