Error creating RenderPartialAsync, problem in controller? - asp.net

There is an Index controller, in it I associate data from a database with a modelview and my View collects user data and displays it. And accordingly below I will attach PartialView
public class CustomerController : Controller
{
private ICustomerRepository _customerRepository;
public CustomerController(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
[HttpGet]
public IActionResult Index()
{
IEnumerable<CustomerViewModel> customers =
_customerRepository.GetAllCustomers().Select(s => new
CustomerViewModel
{
CustomerId = s.CustomerId,
Name = s.Name,
Adress = s.Adress
});
return View("Index", customers);
}
[HttpGet]
public IActionResult Create()
{
return Redirect("Index");
}
}
#model IEnumerable<CustomerViewModel>
<h2>Create Customer</h2>
#{
await Html.RenderPartialAsync("Create");
}
<table class="table">
#Html.DisplayNameFor(model => model.Name)
#foreach (var item in Model)
{
#Html.DisplayFor(modelItem => item.Name)
}
</table>
This is PartialView itself:
#model CustomerViewModel
<div class="col-md-4">
<form asp-action="Create" asp-controller="Customer">
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input type="text" asp-for="Name" class="form-control" />
</div>
When the application starts, an error occurs:
InvalidOperationException: The model item passed into the
ViewDataDictionary is of type 'System.Linq.Enumerable+SelectEnumerableIterator`
2[Store.DAL.Entity.Customer,Store.Web.ViewModels.CustomerViewModel]', but this
ViewDataDictionary instance requires a model item of type 'Store.Web.ViewModels.
CustomerViewModel
If partialView is put on a separate page, just to create a link to the View, everything will be displayed and there will be no error.
Maybe it's all about how I override the data in the Controller for customerViewModel?
Who dealt with this?

What happens
In your code, you're not giving your View a model it expects.
If you use Html.RenderPartialAsync(viewName) you are automatically passing the entire model from the main view to the partial. Since the main view has model type of IEnumerable<CustomerViewModel> - that's what's passed to your partial view.
Solutions
Use separate page for creating objects instead of reusing the same page for displaying existing ones
Make your model more complex so that it can be used for both views and use an overload of Html.RenderPartialAsync(string viewName, object model) to pass the model properly.
For solution #2 the example code could be:
New class
public class CustomerListViewModel
{
IEnumerable<CustomerViewModel> existingCustomers;
CustomerViewModel newCustomer;
}
Controller
[HttpGet]
public IActionResult Index()
{
IEnumerable<CustomerViewModel> customers =
_customerRepository.GetAllCustomers().Select(s => new
CustomerViewModel
{
CustomerId = s.CustomerId,
Name = s.Name,
Adress = s.Adress
});
CustomerListViewModel model = new CustomerListViewModel
{
existingCustomers = customers.AsEnumerable();
newCustomer = new CustomerViewModel();
}
return View("Index", model);
}
Main view
#model CustomerListViewModel
<h2>Create Customer</h2>
#{
await Html.RenderPartialAsync("Create", Model.newCustomer);
}
<table class="table">
#foreach (var item in Model.existingCustomers)
{
<tr>
<td>#Html.DisplayFor(item => item.Name)</td>
</tr>
}
</table>

Related

How to incorporate another controller's view and behavior into "this" controller's view?

I have a jQueryUI tabbed html page, and in its content area for one of the tabs, I have put as follows:
<div id="tabs-1ua">
#RenderPage("~/Views/Admin/Create.cshtml")
</div>
The Create.cshtml page does correctly appear within my tab, however when I create the user (this view is a basic user creation page) and click the button, nothing happens. No user is created and no error is presented. The "this" html with the tabs is in a different controller which does not have any model associations. The user creation is inside the AdminController, pertinent methods shown below:
public ActionResult Create()
{
return View();
}
[HttpPost]
public async Task<ActionResult> Create(CreateModel model)
{
if (ModelState.IsValid)
{
AppUser user = new AppUser { UserName = model.Name, Email = model.Email};
IdentityResult result = await UserManager.CreateAsync(user,
model.Password);
if (result.Succeeded)
{
return RedirectToAction("Index");
}
else
{
AddErrorsFromResult(result);
}
}
return View(model);
}
I put a breakpoint at the beginning of the Post method, but it was never hit when I accessed the create page from within my other page.
When I access this page directly and create a user, I get the expected behavior for new creation and validation. The model is as follows:
public class CreateModel
{
[Required]
public string Name { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
And the Create.cshtml view is as follows:
#model IdentityDevelopment.Models.CreateModel
#{ ViewBag.Title = "Create User";}
<h2>Create User</h2>
#Html.ValidationSummary(false)
#using (Html.BeginForm())
{
<div class="form-group">
<label>Name</label>
#Html.TextBoxFor(x => x.Name, new { #class = "form-control" })
</div>
<div class="form-group">
<label>Email</label>
#Html.TextBoxFor(x => x.Email, new { #class = "form-control" })
</div>
<div class="form-group">
<label>Password</label>
#Html.PasswordFor(x => x.Password, new { #class = "form-control" })
</div>
<button type="submit" class="btn btn-primary">Create</button>
#Html.ActionLink("Cancel", "Index", null, new { #class = "btn btn-default" })
}
My questions are, is it possible to do what I am trying to do? If so what changes do I need to make in order to reuse the existing available code?
Thank you.
You may explcitly specify which action method the form should post to when submit button is clicked.
You can use this overload of Html.BeginForm method to do so.
public static MvcForm BeginForm(
this HtmlHelper htmlHelper,
string actionName,
string controllerName
)
So update your Create view.
#model IdentityDevelopment.Models.CreateModel
#using (Html.BeginForm("Create","Admin"))
{
#Html.TextBoxFor(x => x.Name, new { #class = "form-control" })
<button type="submit" class="btn btn-primary">Create</button>
}
Now nomatter where you include this view, it will always post to Admin/Create
You should move your create form into a partial view which you can then render with RenderPartial. Then in your parent html page form do an ajax post to the partial views controller that defines the create method. Then you can use your partial anywhere you like with the logic centralized into the partial views controller.

Passing multiple models from one view to a controller

If I google for "multiple models in one view" I can only find results about how I can pass models to a view. But I'm interested in the "from view to controller" direction.
So let's assume:
I have 3 different forms and 1 table (WebGrid) in one view.
And I have one model per form and one model for the table.
Let my model classes be ModelF1, ModelF2, ModelF3 and ModelT.
All the examples I have seen until now uses a container ViewModel like
class MyViewModel {
ModelF1 inst1,
ModelF2 inst2,
ModelF3 inst3,
ModelT instT
}
And then they pass it between view <-> controller in 2 ways.
But I want to catch my models this way without using a viewmodel:
class MyController {
ActionResult Index() {
return new View(modelF1Instance, modelF2Instance, modelF3Instance, modelTInstance);
}
ActionResult Form1Action(ModelF1 inst1, ModelT instT) {
// process models after a form1 submit
}
ActionResult Form2Action(ModelF2 inst2, ModelT instT) {
// process models after a form2 submit
}
ActionResult Form3Action(ModelF3 inst3, ModelT instT) {
// process models after a form3 submit
}
}
Is this possible without parsing the whole form elements in a CustomBinder?
Firstly you can only send a strongly typed view model back to your view using
return View(model);
View is a method on the base class, not a class to be instantiated with return new View(...
Then to your real question: Yes you can do this, but using a top level ViewModel which contains your different form items is far, far easier in the majority of use cases. The main problem the top level container ViewModel handles really well is value persistence and server-side validation and error messages between round trips.
If you are only worried about the perception of inefficiency from creating a top level ViewModel container, then don't. This is far more efficient than all the workarounds you may have to put in place in order to get well behaved forms working without the top level ViewModel.
There is some example code below. The code below should demonstrate that using models contained within the top level ViewModel is just simpler and neater: some of the forms deliberately don't round trip some of the state. Note the usage of HiddenFor and ModelState.Clear which are both related to what you are trying to do, but even these won't persist the value for inst4.Name for Form4Submit. The various options explored are:
Use a query parameter to denote which form is being posted
Use a different form name, but still with the view model.
Use a redirect-only Action for the form (send new instances, and only part of the viewmodel)
Use a mixture of the above
public class TestController : Controller
{
//
// GET: /Test/
[System.Web.Mvc.HttpGet]
public ActionResult Index(string msg = null)
{
var model = new MyViewModel
{
Inst1 = new ModelF1 { Name = "Name of F1" },
Inst2 = new ModelF2 (),
InstT = new ModelT {Name = "Name of T"},
PostNumber = 0,
Message = msg
};
return View(model);
}
[System.Web.Mvc.HttpPost]
public ActionResult Index(MyViewModel model, int option = 1)
{
// process models after a form1/2 submit
model.Message = "You posted " +
((option == 1) ? model.Inst1.Name : model.Inst2.Name)
+ " to Index for "
+ ((option == 1) ? "inst1" : "inst2");
model.PostNumber ++;
// This, and the hiddenFor are required to allow us to update the PostNumber each time
ModelState.Clear();
return View(model);
}
[System.Web.Mvc.HttpPost]
public ActionResult Form2Submit(MyViewModel model)
{
// process models after a form2 submit
model.Message = "You posted " + model.Inst2.Name + " to Form2Submit";
model.PostNumber++;
ModelState.Clear();
return View("Index", model);
}
[System.Web.Mvc.HttpPost]
public ActionResult Form3Submit(ModelF3 inst3, ModelT instT)
{
// process models after a form3 submit
var n = instT.Name;
var msg = "You posted " + inst3.Name + ", " + n + " to Form3Submit";
// We no longer have access to pass information back to the view, so lets redirect
return RedirectToAction("Index", new { msg = msg });
}
[System.Web.Mvc.HttpPost]
public ActionResult Form4Submit(ModelF4 inst4, MyViewModel model)
{
// process models after a form4 submit
var n = model.InstT.Name;
model.Message = "You posted " + inst4.Name + ", " + n + " to Form4Submit";
model.PostNumber++;
ModelState.Clear();
return View("Index", model);
}
public class MyViewModel
{
public int PostNumber { get; set; }
public string Message { get; set; }
public ModelF1 Inst1 { get; set; }
public ModelF2 Inst2 { get; set; }
public ModelT InstT { get; set; }
}
public class ModelBase { public string Name { get; set; } }
public class ModelF1 : ModelBase {}
public class ModelF2 : ModelBase { }
public class ModelF3 : ModelBase { }
public class ModelF4 : ModelBase { }
public class ModelT : ModelBase { }
}
Then for the multi-form view:
#using MyWebSite.Controllers;
#model TestController.MyViewModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<p>
#Html.Raw(Model.PostNumber) : #Html.Raw(Model.Message)
</p>
<p>
#Html.LabelFor(m => Model.InstT) : <br />
#Html.DisplayFor(m => Model.InstT)
</p>
<div>
<p>Default form submit</p>
#using (Html.BeginForm())
{
<div>
#Html.HiddenFor(m => m.PostNumber)
#Html.LabelFor(m => Model.Inst1.Name)
#Html.TextBoxFor(m => Model.Inst1.Name)
</div>
<input type="submit" value="Submit Index" />
}
</div>
<div>
<p>Use a parameter to denote the form being posted</p>
#using (Html.BeginForm("Index", "Test", new { option = 2 }))
{
<div>
#* Omitting these will not persist them between trips
#Html.HiddenFor(m => Model.Inst1.Name)
#Html.HiddenFor(m => Model.InstT.Name)*#
#Html.HiddenFor(m => m.PostNumber)
#Html.LabelFor(m => Model.Inst2.Name)
#Html.TextBoxFor(m => Model.Inst2.Name)
</div>
<input type="submit" value="Submit with option parameter" />
}
</div>
<div>
<p>Use a different form name, but still use the ViewModel</p>
#using (Html.BeginForm("Form2Submit", "Test"))
{
<div>
#Html.HiddenFor(m => Model.Inst1.Name)
#Html.HiddenFor(m => Model.InstT.Name)
#Html.HiddenFor(m => m.PostNumber)
#Html.LabelFor(m => Model.Inst2.Name)
#Html.TextBoxFor(m => Model.Inst2.Name)
</div>
<input type="submit" value="Submit F2" />
}
</div>
<div>
<p>Submit with a redirect, and no ViewModel usage.</p>
#using (Html.BeginForm("Form3Submit", "Test"))
{
var inst3 = new TestController.ModelF3();
<div>
#Html.HiddenFor(m => Model.InstT.Name)
#Html.LabelFor(m => inst3.Name)
#Html.TextBoxFor(m => inst3.Name)
</div>
<input type="submit" value="Submit F3" />
}
</div>
<div>
<p>Submit with a new class, and the ViewModel as well.</p>
#using (Html.BeginForm("Form4Submit", "Test"))
{
var inst4 = new TestController.ModelF4();
<div>
#Html.HiddenFor(m => Model.Message)
#Html.HiddenFor(m => Model.PostNumber)
#Html.HiddenFor(m => Model.Inst1.Name)
#Html.HiddenFor(m => Model.Inst2.Name)
#Html.HiddenFor(m => Model.InstT.Name)
#Html.LabelFor(m => inst4.Name)
#Html.TextBoxFor(m => inst4.Name)
</div>
<input type="submit" value="Submit F4" />
}
</div>
</body>
</html>

MVC3- Passing an entire model with dynamic data from view to controller

I have two models: OuterModel and InnerModel. There is a one to many relationship between OuterModel and InnerModel, respectively. To clarify my question, my model is of type IEnumerable<OuterModel>. I'm passing a random number of OuterModels to the view and the user creates any number of InnerModels for each OuterModel. Then on submission, I want the controller to receive the list of OuterModels so that the InnerModels can be added to the database to their intended OuterModels.
I believe I have the naming convention correct to make use of MVC's built in model binding. Here's what that looks like:
OuterModel[i].InnerModel[j].Property
My problem is, I don't really know how to get a list of OuterModels passed to the controller. Here's what I've tried in my View:
#model IEnumerable<OuterModel>
#using (Html.BeginForm("Create", "Controller", new { OuterModels = Model }, FormMethod.Post))
{
//Code to create the InnerModels here
}
And here's what I have in my Controller:
[HttpPost]
public ActionResult Create(IEnumerable<OuterModel> OuterModels, FormCollection fc)
{
String[] keys = fc.AllKeys;
if(ModelState.IsValid){
//Add to db
}
}
Keys shows that all of my properties are following the naming convention that I specified earlier, but ModelState.IsValid is returning false. It shows that OuterModels' count is 0.
Even though I'm telling the form to submit OuterModels = Model before any InnerModels are created, you would think there would still be data in OuterModels considering it's passed to the view. I am really tired today, so I'm guessing I'm looking over one (or many) small detail(s). Any suggestions?
--EDIT--
Passing a list of OuterModels to the controller may not be the best approach. If anybody has a better suggestion, please share.
As long as indexes are used properly, then this should not be an issue. Here is how I would envision the form names.
Model[0].foo
Model[0].Inner[0].bar
Model[0].Inner[1].bar
Where outer model has a property called foo and
Outer model has a property called inner which is a collection of inner objects. Inner object has a property called bar. If your form is rendered with the correct indexes then the model binding should work. Things can get tricky if form fields are generated client side. I recommended going back to server in order to manipulate the model. There are some extra round trips, but you can make them via Ajax request.
Here are some more details in a more fleshed out example.
public class InnerModel{
public string Name{get; set;}
}
public class OuterModel{
public List<InnerModel> InnerModels{get; set;}
public string Name{get; set;}
}
Here is what I would envision my view would look like:
#model IEnumerable<OuterModel>
<ul>
#{int i = 0;}
#foreach(var item in Model){
<li>
Outer Name : #Html.TextBoxFor(m=>Model[i].Name)
<br />
#{int j = 0;}
<ul>
#foreach(var innerItem in Model[i].InnerModels){
<li>Inner Name : #Html.TextBoxFor(m=> Model[i].InnerModels[j].Name)</li>
j++;
}
</ul>
i++;
</li>
}
</ul>
If this is wrapped in a form--- and the controller action looks like this:
public ActionResult Action(List<OuterModel> model){
}
then I would think model would be populated correctly.
I noticed your form.. it doesn't look right to me... I wouldn't think that the passing the OuterModels like that is going to work-- although frankly I might be wrong.
#using (Html.BeginForm("Create", "Controller", new { OuterModels = Model }, FormMethod.Post))
{
//Code to create the InnerModels here
}
Here is an example I did for the class I teach.. that definitely works..
public class Author
{
public string Name { get; set; }
}
public class Book
{
public string Name { get; set; }
public List<Author> Authors { get; set; }
}
Controller:
public class BookController : Controller
{
public static List<Book> _model = null;
public List<Book> Model
{
get
{
if (_model == null)
{
_model = new List<Book>
{
new Book{
Name = "Go Dog Go",
Authors = new List<Author>{
new Author{Name = "Dr. Seuss"}
}},
new Book{
Name = "All the Presidents Men",
Authors = new List<Author>{
new Author{Name = "Woodward"},
new Author{Name = "Bernstein"}
}},
new Book{
Name = "Pro ASP.NET MVC Framework",
Authors = new List<Author>{
new Author{Name = "Sanderson"},
new Author{Name = "Stewart"},
new Author {Name = "Freeman"}
}}
};
}
return _model;
}
}
public ActionResult Index()
{
return View(Model);
}
public ActionResult Edit()
{
return View(Model);
}
[HttpPost]
public ActionResult Edit(List<Book> books)
{
_model = books;
return RedirectToAction("Index");
//return View(books);
}
}
and View:
#model List<AmazonWeb.Models.Book>
#{
ViewBag.Title = "Index";
}
<div class="content">
#Html.ActionLink("Index", "Index")
#using (Html.BeginForm())
{
<input type="submit" value="save" />
<ul class="book-list">
#for (var i = 0; i < Model.Count; i++ )
{
<li>
<label>Book Name</label> : #Html.TextBoxFor(m => Model[i].Name)
<ul>
#for (var j = 0; j < Model[i].Authors.Count; j++ )
{
<li><label>Author Name</label> : #Html.TextBoxFor(m => Model[i].Authors[j].Name)</li>
}
</ul>
</li>
}
</ul>
<input type="submit" value="save" />
}
</div>

Asp.net MVC 3: Model comes empty in the controller?

I've a form in which I only have to choose between several entities.
So here is what I've done:
Created a view Model:
public class EntityChooserModel
{
public IEnumerable<Entity> AvailableEntities { get; set; }
public int SelectedId;
}
I've two actions in the controller, one with [HTTPPost], one without. In both I have a "SessionUserData", which is session data, bound through data model binding.
[Authorize]
public ActionResult EntityChooser(SessionUserData sessionData)
{
List<Entity> entities = _userStore.GetRoles(User.Identity.Name).Select(ur => ur.Entity).ToList();
Entity entity = sessionData.IdCurrentEntity != null ? entities.Where(e => e.Id == sessionData.IdCurrentEntity).First() : entities.First();
return View(new EntityChooserModel() { SelectedId = entity.Id, AvailableEntities = entities });
}
.
[Authorize]
[HttpPost]
public ActionResult EntityChooser( EntityChooserModel model, SessionUserData sessionData, String returnUrl)
{
if (ModelState.IsValid)
{
Entity entity = _userStore.GetRoles(User.Identity.Name).Select(ur => ur.Entity).Where(e => e.Id == model.SelectedId).First();
sessionData.IdCurrentEntity = entity.Id;
RedirectToDefaultPage(returnUrl);
}
return View(model);
}
And the strongly typed view:
#model My.Full.Namespace.EntityChooserModel
#{
ViewBag.Title = "EntityChooser";
Layout = "~/Views/Shared/_LoginLayout.cshtml";
}
<div id="login-wrapper">
<div id="title">
<h1>Login</h1>
</div>
<div id="login">
#using (Html.BeginForm("EntityChooser", "Auth", FormMethod.Post, new { id = "ChooseFormId" }))
{
#Html.ValidationSummary(true, "Loggin error")
<div>
<span>Choose an entity:</span>
#Html.DropDownListFor(x => x.SelectedId, new SelectList(Model.AvailableEntities, "Id", "Name"))
</div>
<p class="right-aligned">
<a href="javascript:document.getElementById('ChooseFormId').submit();" class="adm-button">
<span class="next-btn"><span>Select</span></span></a>
</p>
<input type="submit" style="display: none" />
}
</div>
</div>
The problem is that when I receive the data in the second model, I've an empty object. The collection is empty, and the id is set to the default value(0).
What is wrong? I can't find out what I missed on this one.
It's the first time I do an mvc form which isn't ForModel(), and the first time I've to use a dropdownlist
The only input element that your <form> contains is a single drop down list which sends only the selected value to the server when POSTed. This explains why your AvailableEntities collection property is empty.
As far as why the SelectedId field is empty, it is because you have defined it as a field and not as a property. The default model binder works only with properties. So if you want it to be populated you must define it as a property:
public int SelectedId { get; set; }
As far as the AvailableEntities property is concerned you could populate it from your repository in the POST action using the selected id.

LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression

This is my controller class
public class HomeController : Controller
{
private rikuEntities rk = new rikuEntities();
public ActionResult Index()
{
var db = new rikuEntities();
IEnumerable<SelectListItem> items = db.emp.Select(c => new
SelectListItem
{
Value = c.Id.ToString(),
Text = c.name
});
ViewBag.CategoryID = items;
return View();
}
}
this is my view
#using (Html.BeginForm("viewToController", "Home"))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>emp</legend>
<div class="editor-field">
#Html.DropDownList("CategoryID", (IEnumerable<SelectListItem>) ViewBag.Categories)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
whenever I run this program I get this error:
LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression." in the statement #Html.DropDownList("CategoryID", (IEnumerable) ViewBag.Categories). i am using entity framework mechanism for databse connection. please help me to find out the error...
I would recommend you to use view models and strongly typed views instead of ViewBag. So start with defining your view model:
public class EmployeeViewModel
{
public string CategoryId { get; set; }
public IEnumerable<Employee> Categories { get; set; }
}
then in the controller populate this view model:
public class HomeController : Controller
{
public ActionResult Index()
{
var db = new rikuEntities();
var model = new EmployeeViewModel
{
Categories = db.emp.ToArray() // <-- you probably want categories here
};
return View(model);
}
}
and in the view:
#model EmployeeViewModel
#using (Html.BeginForm("viewToController", "Home"))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>emp</legend>
<div class="editor-field">
#Html.DropDownListFor(
x => x.CategoryId,
new SelectList(Model.Categories, "Id", "name")
)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Sadly EF does not know how to convert ToString() to an SQL statement.
You must, therefore, use the embedded function SqlFunctions.StringConvert.
There is no overload for int so you will need to typecast to double :-(
var items = from v in db.emp
select new SelectListItem
{
Text = c.name,
Code = SqlFunctions.StringConvert((double)c.Id)
};

Resources