Passing view from one controller to another controller's view - c#

Is it possible to pass another controller's view to the first controller's view? I have controller1 with view1. I need to call another controller2 action method from view1 and pass the view2 to a div in view 1.
I tried #html.Action("action","controller"). This called controller 2, but was not passing the view2 to view1.
Am I doing it wrong? How can I do that?

This example is something you can use. I did not put it into an ASP.NET Fiddle because we are dealing with TWO view.
Controller/ViewModel of First
namespace Testy20161006.Controllers
{
//I'm showing how to pass data from one Controller Action to another Controller Action.
//With the data you can render your second view however you like with the data.
//We pass data NOT views. You could use a partial view, but I am showing the most basic way.
public class NewbieDevViewModel
{
public String DataToPassToNewControllerAction { get; set; }
}
public class HomeController : Controller
{
//I am using Tut145 for my first Controller/Action/View, but you could have called it Index
[HttpPost]
public ActionResult Tut145(NewbieDevViewModel passedData)
{
//passing simple string, so I can pass it using my QueryString
return RedirectToAction("MyAction2", "Home2", new { passedData = passedData.DataToPassToNewControllerAction });
}
public ActionResult Tut145()
{
return View();
}
View of First
#model Testy20161006.Controllers.NewbieDevViewModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Tut145 - View 1</title>
</head>
<body>
#using (Html.BeginForm())
{
#Html.LabelFor(r=>r.DataToPassToNewControllerAction)
#Html.TextBoxFor(r => r.DataToPassToNewControllerAction, new { #Value = "ValueOfData" })
<input type="submit" value="Submit data - to send to new Controller Action" />
}
</body>
</html>
Controller of Second
namespace Testy20161006.Controllers
{
public class Home2Controller : Controller
{
//I named my Controller Home2 and Action MyAction2, but you can name it anything you want
public ActionResult MyAction2(string passedData)
{
//reconstruct the ViewModel and pass into second view
NewbieDevViewModel viewModel = new NewbieDevViewModel { DataToPassToNewControllerAction = passedData };
return View(viewModel);
}
View of Second
#model Testy20161006.Controllers.NewbieDevViewModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>MyAction2 </title>
</head>
<body>
<div>
- Final View - I passed data into here from different Controller Action -and-
I can render this page anyway I which
</div>
<p/>
#Html.LabelFor(r => r.DataToPassToNewControllerAction)
#Html.TextBoxFor(r => r.DataToPassToNewControllerAction)
</body>
</html>

Partial view can be used to render a view inside another view. Create a partial view for an action in controller 2. Call that partial view from the view of controller 1.
Here is the example :
First Controller :
public class Controller1Controller : Controller
{
public ActionResult Edit()
{
return View();
}
}
First Controller View :
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Controller 1 View</h4>
<hr />
<h1>Fisrt Controller</h1>
<div>
#{
Html.RenderAction("GetSubject", "Controller2");
}
</div>
</div>
}
Second Controller :
public class Controller2Controller : Controller
{
public ActionResult GetSubject()
{
Subject s = new Subject() { id = 2, SubjectName = "XYZ" };
return PartialView(s);
}
}
Second Controller View :
<div>
<h4>Controller 2 view</h4>
<hr />
<h1>Second Controller</h1>
</div>

After spending some time on the code and a bit of googling, I figured out my issue.
The child action method I was calling from parent view was an async method, so I did something like the below,
Parent view
<div id="childView"></div>
Ajax Call to populate the parent view
$(document).ready(function () {
$.ajax({
type: 'GET',
url : '#Url.Action(actionName: "ChildAction", controllerName: "ChildController")',
dataType: "html",
async:true,
success: function (result) { $("#childView").html(result); }
});
});
Hope it will be useful for some one.

Related

ASP .NET MVC - Adding text from TextBox to table

How can i add something to the list and display it in table in ASP .NET CORE MVC? I'm trying to do simple URL shortener, but I can't even pass full link to the view.
My model:
public class Link
{
public string FullLink { get; set; }
public string ShortenedLink { get; set; }
public static List<Link> _list = new List<Link>();
}
My controller:
public class LinkController : Controller
{
public IActionResult Index()
{
return View(Link._list);
}
[HttpPost]
public IActionResult Add(string link)
{
Link._list.Add(new Link { FullLink = link });
return RedirectToAction("Index");
}
}
My view:
#model List<UrlShortener.Models.Link>
<html>
<head>
<title></title>
</head>
<body>
#using (Html.BeginForm("Add", "Link", FormMethod.Post))
{
#Html.TextBox("myTextBox");
<input type="submit" value="Add" />
}
<div>
<table>
#foreach (var item in Model)
{
<tr><td>#item.FullLink</td></tr>
}
</table>
</div>
</body>
</html>
Can someone explain me what am I doing wrong that after clicking "Add" button nothing happens?
You need to tell your form what your textbox represents.
Instead of using "myTextBox" for the name parameter of your textbox, give it the name (link in your example) of the parameter in your controller method, public IActionResult Add(string link), you are binding your form to.
In short, you need to change the textbox declaration in your view to this:
#Html.TextBox("link")
Note: the semicolon after the Html.TextBox(...) tag is unnecessary

How to separate ViewBag information that is common to multiple Controllers? [duplicate]

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>

ASPNET MVC 5 Html helper Action method view results input id missing (sometimes..) Possible Bug?

To recreate the issue I'm having I've set up two simple controller actions, two views and a view model:
MultiForms Action (in the form controller class)
public class FormController : Controller
{
[HttpGet]
public ActionResult MultiForms()
{
return View("MultiForms");
}
// ...
MultiForms.cshtml - Razor View
#Html.Action("MyFormGet", "Form")
Notice in the action method I call the controller method that returns the form partial view.
MyFormGet Action
[HttpGet]
public PartialViewResult MyFormGet()
{
var viewModel = new MyFormViewModel();
ViewData.TemplateInfo.HtmlFieldPrefix = Guid.NewGuid().ToString().Replace("-", "_");
return PartialView("MyForm", viewModel);
}
View Model
public class MyFormViewModel
{
public string TextInput1 { get; set; }
}
MyForm.cshtml - Razor View
#model Mvc5App.Controllers.MyFormViewModel
#using (Html.BeginForm("MyFormPost", "Form", FormMethod.Post))
{
<br />
#Html.TextBoxFor(m => m.TextInput1)
}
When I point my browser at /Form/MultiForms I get the output I expect:
<input type="text" value="" name="e166fa0d_46fe_40d4_a970_73c52a35f224.TextInput1" id="e166fa0d_46fe_40d4_a970_73c52a35f224_TextInput1">
Then I refresh the page and the output becomes this:
<input type="text" value="" name="48edc339_69ad_4b9b_9583_198038beab45.TextInput1">
Where did the id attribute go? Is this a bug in ASPNET MVC5 or have I done something wrong with this setup?
Thanks to #demo I think I figured out when the id doesn't show up...
If the id starts with a digit(or underscore) it will not render. So this GUID will render:
f3b1a447_3786_4472_9dfe_14c3ae8aae24
But this one will not:
1c26ce3d_5c71_408a_aae2_7be414f1d383
The solution for the above seems to be prefixing with something like this:
FOO_1c26ce3d_5c71_408a_aae2_7be414f1d383
And the problems go away.

Filtering Data from the Controller and Passing it to the View

I have a small scenario :
I fetch data from the database and I want to filter it in my ActionExecuted Filter and then send it to the View. I am accessing the fetched data from the Database using TempData, in my Filter. After I modify the data inside my Filter I want to send it to the View.
What are the possible solutions for this ?
When you modify your TempData inside OnActionExecuted method of your custom filter the view will get that modified TempData by design.
Example:
Filter:
public class MyFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.Controller.TempData["key"] += "modified";
}
}
Controller:
[MyFilter]
public class HomeController : Controller
{
public ActionResult Index()
{
TempData["key"] = "some_value";
return View();
}
}
View(Index.cshtml):
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
</head>
<body>
<div>
#TempData["key"]
</div>
</body>
</html>
When you run this you will see Index page showing modified data pulled from TempData.
Here is some article explaining action filters feature.

Model Binding to a List MVC 4

Is there a pattern to bind an IList of items to the view. I seem to be having issues with the HttpPost. I know Phil Haack wrote a nice article but it is dated and he said they might have a fix with MVC 4.
This is how I do it if I need a form displayed for each item, and inputs for various properties. Really depends on what I'm trying to do though.
ViewModel looks like this:
public class MyViewModel
{
public List<Person> Persons{get;set;}
}
View(with BeginForm of course):
#model MyViewModel
#for( int i = 0; i < Model.Persons.Count(); ++i)
{
#Html.HiddenFor(m => m.Persons[i].PersonId)
#Html.EditorFor(m => m.Persons[i].FirstName)
#Html.EditorFor(m => m.Persons[i].LastName)
}
Action:
[HttpPost]public ViewResult(MyViewModel vm)
{
...
Note that on post back only properties which had inputs available will have values. I.e., if Person had a .SSN property, it would not be available in the post action because it wasn't a field in the form.
Note that the way MVC's model binding works, it will only look for consecutive ID's. So doing something like this where you conditionally hide an item will cause it to not bind any data after the 5th item, because once it encounters a gap in the IDs, it will stop binding. Even if there were 10 people, you would only get the first 4 on the postback:
#for( int i = 0; i < Model.Persons.Count(); ++i)
{
if(i != 4)//conditionally hide 5th item,
{ //but BUG occurs on postback, all items after 5th will not be bound to the the list
#Html.HiddenFor(m => m.Persons[i].PersonId)
#Html.EditorFor(m => m.Persons[i].FirstName)
#Html.EditorFor(m => m.Persons[i].LastName)
}
}
A clean solution could be create a generic class to handle the list, so you don't need to create a different class each time you need it.
public class ListModel<T>
{
public List<T> Items { get; set; }
public ListModel(List<T> list) {
Items = list;
}
}
and when you return the View you just need to simply do:
List<customClass> ListOfCustomClass = new List<customClass>();
//Do as needed...
return View(new ListModel<customClass>(ListOfCustomClass));
then define the list in the model:
#model ListModel<customClass>
and ready to go:
#foreach(var element in Model.Items) {
//do as needed...
}
~Controller
namespace ListBindingTest.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
List<String> tmp = new List<String>();
tmp.Add("one");
tmp.Add("two");
tmp.Add("Three");
return View(tmp);
}
[HttpPost]
public ActionResult Send(IList<String> input)
{
return View(input);
}
}
}
~ Strongly Typed Index View
#model IList<String>
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
#using(Html.BeginForm("Send", "Home", "POST"))
{
#Html.EditorFor(x => x)
<br />
<input type="submit" value="Send" />
}
</div>
</body>
</html>
~ Strongly Typed Send View
#model IList<String>
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Send</title>
</head>
<body>
<div>
#foreach(var element in #Model)
{
#element
<br />
}
</div>
</body>
</html>
This is all that you had to do man, change his MyViewModel model to IList.

Categories