Instantiating ViewModel for a View - asp.net-mvc-4

Update Binding hidden fields of a viewmodel.
Let me try to explain my situation. I may be completely wrong but this is what I believe causing issue to me.
I have a ViewModel
Project Create View Model
[Bind(Exclude="List")]
public class ProjectCreateViewModel : ProjectViewModelBase
{
public CourseViewModelBase CourseVM { get; set; }
public ProjectCreateViewModel()
: base()
{
this.CourseVM = new CourseViewModelBase();
}
}
Project View Model Base is the base viewModel for a project and all associated actions derive from this so that I don't need to write property names again and again.
Create View Model Base is similar to ProjectViewModelBase(handled or used by ProjectController) but for a course (handled by CourseController).
Now I've created a form for "Create New Project" which uses ProjectCreateViewModel. In Form post action however CourseVM is always null.
Create New Project .cshtml
#model LMSPriorTool.ViewModels.ProjectCreateViewModel
#* --- labels and other stuff -- *#
#using (Html.BeginForm("CreateNewProject", "Project",
FormMethod.Post, new { #class = "form-horizontal",
name = "createNewProjectForm" }))
{
<!-- Hidden Fields -->
#Html.HiddenFor( x => x.ProjectId)
#Html.HiddenFor( x => x.CourseVM) // CourseVM is null in post action
#Html.TextBoxFor(x => x.CourseVM.CourseNumberRoot) // This is displayed properly
}
ProjectController
[HttpGet]
public ActionResult CreateNewProject(CourseViewModelBase courseVM = null)
{
ProjectCreateViewModel projectCreateViewModel = new ProjectCreateViewModel
{
CourseVM = courseVM,
};
// OTHER STUFF
return View("CreateNewProject", projectCreateViewModel);
}
Error
In HTTPPOST action I'm getting CourseVM as null, though I have provided it as a hidden field in form.
Possible Issue I belive issue is with the Constructor of ProjectCreateViewModel as when HTTPPOST action occur, view will try to create new instance of ProjectCreateViewModel and instantiate the CourseVM as null. Then same instance is passed to the HTTPPOST method in which CourseVM is appearing as null.
UPDATE: ISSUE ROOT CAUSE Complex objects cannot be bind to a viewmodel using Hidden Fields.
Any suggestions or thoughts appreciated.

You don't need that HiddenFor of CourseVM. MVC will create the class automatically for you because you are binding CourseVM.CourseNumberRoot
At the moment, you are attempting to bind CourseVM, which is a complex object, from a hidden input which MVC can't do, so it is returning null.

Related

Umbraco custom controller not using declared model type

Umbraco 9 - I've created a page type called SiteSearch and a controller to hijack requests to pages of that page type. This all works correctly.
The controller gets an IEnumerable from a very simple search service and sets it on a ViewModel, which is then passed to the view.
However, I simply cannot get the view to respect the model declaration. I am getting the error:
ModelBindingException: Cannot bind source type
Maysteel_Web.ViewModels.SiteSearchViewModel to model type
Umbraco.Cms.Core.Models.PublishedContent.IPublishedContent.
It seems to be forcing my view to use an instance IPublishedContent (singular), even though I'm declaring the model as my custom object. I've tried changing the model declaration to string just to see what would happen:
InvalidOperationException: The model item passed into the
ViewDataDictionary is of type
'Maysteel_Web.ViewModels.SiteSearchViewModel', but this
ViewDataDictionary instance requires a model item of type
'System.String'.
There it recognized the model, but when I declare my custom object as a model, it goes back to trying to bind to IPublishedContent (singular). I've verified that the model I'm passing is not null and actually has results. I'm not sure what else to try. Can anyone help me undertand why this is happening?
SiteSearchController action:
public override IActionResult Index()
{
var searchPage = new SiteSearch(CurrentPage, _publishedValueFallback);
var results = _searchService.QueryUmbraco("about");
var viewModel = new ViewModels.SiteSearchViewModel();
viewModel.Results = results;
return View("~/Views/SiteSearch/index.cshtml", viewModel);
}
View:
#model Maysteel_Web.ViewModels.SiteSearchViewModel
#{
Layout = "/Views/Main.cshtml";
}
<h1>Site Search Page</h1>
ViewModel:
public class SiteSearchViewModel
{
public IEnumerable<IPublishedContent> Results { get; set; }
}
I just figured it out. My layout view had the following line in it:
#inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
Once I removed that, I no longer get the error. It must have been forcing the ViewModel to be IPublishedContent.

How to set and get multi select dropdown list for on form load using model binding in MVC [duplicate]

I want to bind #Html.DropDownListFor from Model data without using Viewbag and look at many different examples on the web. But most of them use Viewbag or an extension method and I want a better approach to solve this problem. I tried the the following methods but it does not seem to work:
#Html.DropDownListFor(m => m.LabId, new SelectList(Model.Lab, "Id", "Name"),
"---- Select----", new { #class = "selectpicker" } )
Is it possible to bind #Html.DropDownListFor directly from Model without using Viewbag or any extra method in the Controller in ASP.NET MVC5? Could you please give some examples to perform this best?
The strongly typed view model approach which does not use dynamic stuff like ViewBag
You can add a new property to your view model for the SELECT options of type
IEnumrable<SelectListItem>.
view model is a simple POCO class used to transfer data between view to action method and vice versa. They are specific to the views. Add properties only needed for the view.
public class CreateUserVm
{
public IEnumrable<SelectListItem> Labs { set;get;}
public int SelectedLabId { set;get;}
//Add other properties as needed for the view
}
and in your GET action, create an object of this view model, load the Labs property and send that to the view.
public ActionResult Create()
{
var vm= new CreateUserVm();
// Hard coded for demo. You can replace with real data from db
vm.Labs = new List<SelectListItem> {
new SelectListItem { Value="1", Text="One" },
new SelectListItem { Value ="2", Text="Two" }
};
return View(vm);
}
and in the view which is strongly typed to this view model, call the DropDownListFor helper method
#model CreateUserVm
#Html.DropDownListFor(f=>f.SelectedLabId, Model.Labs,"Select one")
Pre-selecting an option in the dropdown
If you like to pre select one option when razor renders the page, You can set the SelectedLabId property value of your view model to the value property value of of the Option item(SelectListItem).
public ActionResult Create()
{
var vm= new CreateUserVm();
// Hard coded for demo. You can replace with real data from db
vm.Labs = new List<SelectListItem> {
new SelectListItem { Value="1", Text="SugarLab" },
new SelectListItem { Value ="2", Text="CandyLab" },
new SelectListItem { Value ="3", Text="SodaLab" }
};
vm.SelectedLabId = 2; // Will set "CandyLab" option as selected
return View(vm);
}
If you want to use real data, instead of the hard coded 2 items, you can do this
vm.Labs = dbContext.Labs.Select(x=>new SelectListItem { Value=x.Id.ToString(),
Text= x.Name }).ToList();
Assuming dbContext is your DbContext class object and it has a Labs property of type DbSet<Lab> where each Lab entity has an Id and Name property.
You are on a good track by having the list items in your model. I don't know how you have implemented that in your code.
The Lab property in your model class should be an IEnumerable, List or Collection of what you want. and then in your razor view, the looks fine.
Perhaps what you're forgetting is to initialise the list from the action method in the controller before sending it to the view. E.g:
var model = new ViewModel{
Labs = repository.GetLabs();
}
Above i assume the repository should be something that has access or means of getting the needed data, and also that the Labs property is defined as IEnumerable<Lab> Labs in the ViewModel class.
All should work. Perhaps you should be clear as to what error you're getting.

InvalidOperationException rendering ViewComponent in Strongly-Typed View

Recently updated dotnet core 1.0.1 to 1.1 and ViewComponent in MVC starts failing with the below exception:
InvalidOperationException: One or more errors occurred. (The model item passed into the ViewDataDictionary is of type 'App.Models.HomeViewModel', but this ViewDataDictionary instance requires a model item of type 'App.Components.LoginViewComponent'.)
The Index.cshtml renders LoginViewComponent:
#model App.Models.HomeViewModel
<html>
#Component.InvokeAsync("LoginViewComponent")
</html>
Views/Home/Index.cshtml
The ViewComponent LoginViewComponent/Default.cshtml is just a class that displays it's model's info:
#model App.Components.LoginViewCompoent
<body>
<div>#Html.DisplayFor(v=>v.LoginName)</div>
</body>
Views/Shared/Components/LoginViewComponent/Default.cshtml
It renders fine when the #model directives is removed from Default.cshtml.
Isn't ViewComponent suppose to be separated and agnostic from the parent view that is wrapping it? From the exception it seems that it would be require to declare the LoginViewComponent ViewComponent in HomeViewModel class in order to render it.
Couldn't find any change note on this on asp.net core github.
Comment and help on this would be greatly appreciated.
I've came across the same error, someone in my team updated Microsoft.AspNetCore.Mvc version from 1.0.0 to 1.1.0 and some of the components I had in Strongly-Type views started throwing
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'My.StronglyView.ObjectType', but this ViewDataDictionary instance requires a model item of type 'My.ViewComponent.ObjectType'.
I am not sure if this change was intentional, but it is definitely not what we would expect.
I haven't got the time to research about the reasons of this 'breaking' change but I came with a solution.
Thing is, if you pass a null object to your ViewComponent.View() method, we get this exception. Any non-null object passed through it, would update the ViewData.ModelExplorer and the correct object-type would have been registered, avoiding this exception.
Using Tester-Doer pattern, same pattern used in some classes of .Net Framework, We can now pass a non-null object to the ViewComponent and use it and its wrapped object as We need.
What I did was, I created a interface IViewComponentModel<T> as class ViewComponentModel<T> as below:
// Interface for ViewComponentModel
public interface IViewComponentModel<T>
where T : class
{
T Data { get; }
bool HasData();
}
// ViewComponentModel class for the Strongly-Type View Component
public class ViewComponentModel<T> : IViewComponentModel<T>
where T : class
{
public T Data { get; private set; }
public ViewComponentModel(T data)
{
Data = data;
}
public bool HasData() => Data != null;
}
In my ViewComponent class implementation, I return View(new ViewComponentModel<AnyReferenceType>(myModel));
public async Task<IViewComponentResult> InvokeAsync()
{
var myViewData = _myService.GetSomeObjectForMyViewComponent();
var model = new ViewComponentModel<MyViewDataType>(myViewData);
return await Task.FromResult(View(model));
}
And finally, in my Component View (.cshtml) I have the code below:
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#model ViewComponentModel<MyViewDataType>
Now you can do whatever you want with this object inside of the view and to use your real model, just call #Model.Data and voilĂ .
In this way, We will never pass a null object to the ViewComponent and it won't 'inherit' the object type from the View.
Hopefully it helps!
asp.net-core asp.net-core-mvc dotnet-core asp.net-core-1.1
Simple solution is that, in ViewComponent class check null for view model, and if view model is null, then return empty Content result as following:
public async Task<IViewComponentResult> InvokeAsync()
{
var vm = some query for ViewModel;
if(vm != null)
{
return View(vm);
}
return Content("");
}
By default it will try to pass in the model of the "Parent View" ,not sure what you would call that, try updating your code like this
#Component.InvokeAsync("LoginViewComponent", new App.Components.LoginViewComponent())
so that partial view can have what it needs, in order to serve up it's content.

Getting form collection value in a common place

I have a service that I initialize on controller's contructor.
I want to pass UserName(this is my model's property) to this service while instantiating the service on contructor.
First question is, is it possible to get model on controller contructor? I tried but couldn't find any way to do this.
If not then the other way I thought of is to have a common function that will be called everytime any view action is executed, where I can access FormCollection and assign it to the service.
For this I tried overriding few controller's method like Initialize, OnExecuting etc. But I couldn't find form collection in them.
Is there any way to achieve this?
Edit
Some more description
private IService _service;
public HomeController()
{
_service = new ServiceImplementation(/*I want to pass UserName here*/);
}
public ActionResult Submit(MyModel model)
{
_service.UserName = model.UserName;
/* This UserName assignment part I want to centralize,
somewhere in the constructor or in any common event,
so that this will be initialized before any action method is called */
...
...
}
public ActionResult Delete(MyModel model)
{
_service.UserName = model.UserName;
...
...
}

Same view for both create and edit in MVC4

Can we have a single razor view for both Create and Edit operations?
If yes, how do we achieve this?
I don't recommend it.
This should be a rather long answer, because there's a lot of things involved in the process, request and workflow of a normal MVC GET/POST workflow. I will try to answer your question with the minimum information required and why I do not recommend the use of the same view.
First, why?
You don't have control over the views, which may have over-posting;
No flexibility;
Not reusable views or parts;
Hard to maintain the views (one change in the view must be tested on both actions).
My suggested approach would be to have different actions/views but share common code:
Create both views as normal.
You will have duplicated code, but not all code is the same, for example, you may not want to send an ID on the create action, this is not directly related to your question, but using the same view implies you are also sending the same data, and this is not recommended, especially for over-posting or mass assignment. More info about mass assignment here (an Architectural Approach is what I'm using here).
So let's start from what are you going to receive in your controllers.
In this case I used inheritance but it's not the only strategy.
Binding models
public class UpdateBindingModel : CreateBindingModel {
// since we are not using the same binding model,
// we can have a "real" validation rules on our update binding and view.
[Required]
public int? Id {get;set;}
}
public class CreateBindingModel {
// no id here prevent overposting.
[Required]
public string Name {get;set;}
[Required]
public int? CountryId {get;set;}
}
That will make sure the data you send to your Create and Edit is the minimum needed and nothing else.
Let's then see the View Models that will be sent to the View, for this example I will include a List that will be used to select some value but should not be posted (the list) to the controller, only the selected value.
View models
public class CreateViewModel : CreateBindingModel {
public IEnumerable<SelectListItem> CountryList {get;set;}
}
public class UpdateViewModel : UpdateBindingModel {
public IEnumerable<SelectListItem> CountryList {get;set;}
}
As you can see, this gives you lot of flexibility but still have some duplicated code (the extra information needed on view model for both views) which can be mitigated in several ways (depending the needs/context):
Have an action to retrieve the common data and using #Html.Action("GetCountryList");
Use the same View Model aka CreateUpdateViewModel and discarding extra UpdateBindingModel properties in the view but still posting the corresponding model on POST.
Having your binding models as properties and select one or the other in the specific view. (better use #Html.EditorFor instead of partials so Model Binder will work with no additional change on code)
The controller actions will look like:
Controller
[HttpGet]
public ActionResult Create(){
ViewData.Model = new CreateViewModel();
return View();
}
[HttpPost]
public RedirectToRouteResult Create(CreateBindingModel binding) {
// check valid model state and create data
return RedirectToAction("Index");
}
[HttpGet]
public ActionResult Update(int id) {
var objectToEdit = service.GetObjectToEdit(id);
ViewData.Model = new UpdateViewModel(objectToEdit);
return View();
}
[HttpPost]
public RedirectToRouteResult Update(UpdateBindingModel binding) {
// check valid model state and update data
return RedirectToAction("Index");
}
And your views:
Views
Update.cshtml
<form action="Update">
#Html.HiddenFor(Model.Id);
#Html.Partial("EditFieldsPartial")
<button>delete</button> // no delete button on create.
<button>create new</button> // you can have a create new instead of update.
</form>
Create.cshtml
<form action="Create">
#Html.Partial("EditFieldsPartial")
</form>
Note: code is incomplete and didn't use helpers in most cases for brevity and clarity. Do NOT copy paste :D
Sure you can.
On post, check in your controller whether the primary key has value 0 then Insert, otherwise Update.
View should be the same for Create and Edit.
Just remember to include:
#Html.HiddenFor(model=>model.ID)
In your view
For example:
Model:
public class DescriptionModel
{
[Key]
public int ID { get; set; }
public string Description { get; set; }
}
CreateEdit.cshtml:
#model DescriptionModel
#using (Html.BeginForm("CreateEdit"))
{
#Html.HiddenFor(model=> model.ID)
#Html.EditorFor(model=> model.Description)
<input type="submit" value='Submit' />
}
DescriptionModel controller:
public ActionResult Create()
{
return View("CreateEdit", new DescriptionModel());
}
public ActionResult Edit(int id)
{
return View("CreateEdit", db.DescriptionModels.Find(id));
}
// Submit and add or update database
[HttpPost]
public ActionResult CreateEdit(DescriptionModel model)
{
if (ModelState.IsValid)
{
// No id so we add it to database
if (model.ID <= 0)
{
db.DescriptionModels.Add(model);
}
// Has Id, therefore it's in database so we update
else
{
db.Entry(model).State = EntityState.Modified;
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
A View can definitely be shared for create and edit operations, using the same model. However, i would strongly recommend to think about it twice. In many cases, you will want to have a different view for edit operations(eg. hide some inputs that should not be editible) as well as the model could be slightly different, altought it might share some (or most) values. These difference will lead to some conditions in the view, checking whether you are creating or editing - which could make the code chaotic.
Conclusion: before deciding whether to have a shared view, try to think of how much is the edit screen gonna differ from create screen, then you may decide.
You certainly can, but usually that's something I will try to avoid. If the create and edit actions are virtually the same then you end up duplicating a lot of code in the controller. Usually in this situation I will have only a few fields on my 'Add' controller, and then once the item has been added I redirect the user to the edit page where they can fill in the rest of the information.
I wouldn't recommend that approach but you could have the main form be loaded into both views from a partial
[HttpGet]
public ActionResult myFun(int id = 0)
{
MyClass cls = new MyClass();
if (id == 0)
{
//Insert mode ... no data will be shown to textboxes , when primary key ie. id=0
//Display whole data
}
else
{
//Update mode... if id is not 0 ,data will be shown to textboxes
}
return View(cls);
}