Can a controller manage more than one model? - c#

I want my controller to put modelA into viewA and modelB into viewB.
From what I know a controller can be associated with only one view using only one model.
Correct me please if I'm wrong.

A Controller-action can only produce 1 View at a time.
But it is possible to build some conditional logic into the Controller and decide which View(s) to
show.
a controller can be associated with only one view using only one model
No, a CRUD controller normally associates with 1 Model and produces List/Edit/Delete/Create Views.
So multiple Views is quite normal, so is multiple ViewModels. And a ViewModel often combines information form more than 1 Model entity.

I want my controller to put modelA into viewA and modelB into viewB.
BO classes:
class modelA{
...
}
class modelB{
...
}
Controller code:
...other using statements...
using MyProj.DAL;
using MyProj.BO;
public class MyController:Controller
{
//Create object of your Data Access Layer's MyDAL functionality
MyDAL DALobj = new MyDAL();
public ActionResult viewAList(){
modelA mobj = DALobj.FetchObjFromDB();
return View(mobj);
}
public ActionResult viewACreate(modelA newobj){
...check if modelstate is okay and tweak your model object here...
DALobj.SendDataToDB(newobj);
return RedirectToView("some other view | index");
}
public ActionResult viewBList(){
...same as viewAList() but with modelB this time...
}
public ActionResult viewBCreate(){
...same as viewACreate() but with modelB this time...
}
}//controller ends here
I guess what is asked is:
From what I know a controller can be associated with only one view
using only one model.
I translate it as :
There can be only one view consuming one model associated with a particular controller.
which I think looks like this:
public class PersonController : Controller
{
static List<Person> people = new List<Person>();
public ActionResult Index()
{
return View(people);
}
public ActionResult Details(Person person)
{
return View(person);
}
public ActionResult Create()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Person person)
{
if (!ModelState.IsValid)
{
return View("Create", person);
}
people.Add(person);
return RedirectToAction("Index");
}
}
Here is one controller, four Actions, one model. So, One Controller is associated with one model, but not only view (which you can get; by removing any other two Functionalities(not functions, Create-functionality has two functions, one for GET & POST each))
Now, question arises, can you use multiple models in 1 controller?
Answer: Yes. I just showed it in first code example.
Question: Can there be two+ models in 1 controller?
Answer: yes. Create a wrapper model, put other two models in it. and BAM!
How?
(Of course. sigh!)
MODEL | BO:
public class BigObjClass{
public A a;
public B b;
}
public class A{
public int serial{get;}
public int age{get;set;}
}
public class B{
public string Name{get;}
public string Address{get;set;}
}
Now, as in very first code example, use BigObjClass's object and pass it to the views.
Please let me know if any of this makes sense to you.

Related

View error when invoking methods from base controller in MVC 5

I have the following controllers
public class StoreController : Controller
{
public ActionResult Index()
{
var model = new SomeViewModel();
return View(model);
}
}
and
public class SofiaStoreController : StoreController
{
public ActionResult GetIndex(string city)
{
return base.Index();
}
}
When invoking the base Index method from the derived class I get this error:
The view 'getindex' or its master was not found or no view engine
supports the searched locations. The following locations were
searched:
Seems the GetIndex() method looks for a view in the derived controller's view folder by default even though no View() method is called but since there isn't such the error occurs.
Any idea why the method implicitly looks for a view and how to overcome the error ?
EDIT: After spending some time research the problem I came across these two posts: http://howtoprogram.eu/question/asp-net-c-asp-net-mvc-inherited-controller-using-base-view,2445 and http://www.davidwhitney.co.uk/Blog/2010/01/19/asp-net-mvc-view-engine-that-supports-view-path-inheritance/ Seems that controller inheritance is not that popular or straightforward decision. The solutions to my problem could be :
1. Not to use controller inheritance
2. Creating a custom view engine as shown in the second link (advanced)
3. As other people mentioned bellow - using full path to the view or RedirectToAction also could work
It does look for a view based on the Action method name you originally called. You can always override this behavior if you use the overloaded View() method that accepts the view name/path:
public class StoreController : Controller
{
public ActionResult Index(string viewName = "Index")
{
var model = new SomeViewModel();
return View(viewName, model);
}
}
public class SofiaStoreController : StoreController
{
public ActionResult GetIndex(string city)
{
return base.Index();
}
}

Where to put common code in view controller View functions?

In one of the controllers, every view has a fixed preprocessing. Is there a better way of doing this, instead of the below code; such that SomeFunctionAsync works without writing that line before return View() for all functions with return View() in this controller? I also have some ajax post functions.
public async Task<ActionResult> View1()
{
await SomeFunctionAsync();
return View();
}
public async Task<ActionResult> View2()
{
await SomeFunctionAsync();
return View();
}
In other words, at the end I want to be able to write the following with having the same effect
public async Task<ActionResult> View1()
{
return View();
}
public async Task<ActionResult> View2()
{
return View();
}
If Action Filter as suggested by Varun doesnt suits you, you can try another way.
Create a parent View of all the view. In the action method for your parent view. Call this Method SomeFunctionAsync(). Thus, parent view will be called for all of yours views and the required method will be executed
You can create a base class for your controller, and have have each view in your code call a generic method. I use GetView as a method name (or you could override the existing ones).
Like so:
public class MyControllerBase : Controller {
public Task<ActionResult> GetView() {
yourCommonMethod();
return View();
}
public Task<ActionResult> GetView(string viewName) {
yourCommonMethod();
return View(viewName);
}
public Task<ActionResult> GetView(object model) {
yourCommonMethod();
return View(model);
}
public Task<ActionResult> GetView(string viewName, object model) {
yourCommonMethod();
return View(viewName, model);
}
}
Then in your controller, inherit from that:
public class MyController : MyControllerBase {
public async Task<ActionResult> View1()
{
return GetView();
}
}
If the common method is the same for all controllers and has no controller-specific dependencies, it could be that simple. However, you may want to look at using generics as well:
public class MyControllerBase<T> : Controller {
// base class stuff here based on type T's interface
}
public class MyController : MyControllerBase<MyController> {
// regular class here, sending MyController to the base
}
These are pretty much building blocks of OOP. You may do well to get a book that covers the basics of OOP and work through this type of stuff.
There are tow ways :
Use a single Action with different views like return View("View1") or retrun View("View2"), you can make if else conditions there so you will call your function at a single place.
If you want to go with your current procedure(not recommended) then you have to use Action Filter attribute and decorate it on Controller level then every action would execute your logic before execution of your Action

About multiple models in one view, again

I'm trying to create a one view where I'll could edit data from 2 different model. I read this manual: Multiple models in a view and some other topics on SO, and I successful got data from 2 models in my view, but I can't understand why I can't edit it in my view.
So, in my view I have smth like:
#model Tuple<GroupProjectsModel,InfrastructureModel>
#Html.EditorFor(m => m.Item1.Data)
#Html.EditorFor(m => m.Item2.Data)
Then, when I'm trying to save data, some mistakes happened. I have following code in controller:
[HttpPost]
[InitializeEditPageAttribute]
public ActionResult Edit(GroupProjectsModel get, InfrastructureModel getInf)
{
if (ModelState.IsValid)
{
....
return ReturnView(get.Id, NameModule);
}
var tuple = new Tuple<GroupProjectsModel, InfrastructureModel>(get, getInf);
return View(tuple);
}
In this case variable ModelState.IsValid is equal false anyway. But I'm trying another case:
[HttpPost]
[InitializeEditPageAttribute]
public ActionResult Edit(Tuple<GroupProjectsModel, InfrastructureModel> tupleFromModel)
{
if (ModelState.IsValid)
{
...
return ReturnView(tupleFromModel.Item1.Id, NameModule);
}
var tuple = new Tuple<GroupProjectsModel, InfrastructureModel>(tupleFromModel.Item1, tupleFromModel.Item2);
return View(tuple);
}
and got mistake too. I have no idea what to do.
Just created another class that holds both classes.
public class EditViewModel
{
public GroupProjectsModel groupProjectsModel {get; set;}
public InfrastructureModel infrastructureModel {get; set;}
}

ActionResult cast parameter base class to derived class

I've written a base class and some classes which derive from it.
I want to use these classes in one ActionResult, but if I'm trying to cast PSBase to PS1 I'm getting a System.InvalidCastException that type PSBase can not be converted to PS1.
Classes:
public class PSBase
{
public int stationId { get; set; }
public string name { get; set; }
}
public class PS1 : PSBase
{
public string reference { get; set; }
}
public class PS2 : PSBase
{
}
ActionResult:
[HttpPost]
public ActionResult ProductionStep(PSBase ps)
{
if (ModelState.IsValid)
{
var product = db.Product.FirstOrDefault(.........);
switch (ps.stationId )
{
case 1:
{
product.Reference = ((PS1)ps).reference;
db.SaveChanges();
break;
}
}
}
return View();
}
As I don't want to have for each class a own ActionResult (repeating much of the same code many times) I wanted put all this to one ActionResult. Any Ideas how I could implement this?
What you are trying to do will never work without custom ModelBinder (and even then it will be a huge mess I'd not recommend to implement), sorry.
Only when you are passing a model from Controller to View it remembers what type it was originally (including inheritance, etc.) because at that point you are still on the server side of the page and you are merely passing an object.
Once you enter a view and submit a form all that does it creates some POST request with body containing list of values based on input names.
In your case if you have a form based on PS1 and used all the fields as inputs, you would get something like:
POST:
stationId = some value
name = some value
reference = some value
(there is no mention of the original type, controller, method, etc.)
Now, what MVC does is that it checks what argument you are using in the header of the method (in your case ProductionStep(PSBase ps)).
Based on the argument it calls a model binder. What the default model binder does is that it creates new instance of that class (in your case PSBase) and goes via reflection through all the properties of that class and tries to get them from the POST body.
If there are some extra values in the POST body those are forgotten.
Unless you write a custom model binder for this default MVC implementation can't help you there.
I'd recommend creating two separate methods, one of each accepting different implementation of PSBase.
If you want to read more on Model Binders check this out http://msdn.microsoft.com/en-us/magazine/hh781022.aspx
EDIT:
By creating two separate methods I mean something like this:
[HttpPost]
public ActionResult ProductionStepA(PS1 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
[HttpPost]
public ActionResult ProductionStepB(PS2 ps)
{
if (ModelState.IsValid)
{
}
return View();
}
then you have to distinguish between them in the view via different form action.

Best way to populate SelectList for ViewModel on GET/POST

I have the following ViewModel:
public class EditViewModel
{
public int FooType { get; set; }
public IEnumerable<SelectListItem> FooTypes { get; set; }
}
I originally populated it in my Edit action like so:
public ActionResult Edit(int id)
{
EditViewModel model = new EditViewModel();
model.FooTypes = new SelectList(repository.GetFooTypes(), "Id", "Value");
return View(model);
}
When I created the action to POST the values I had to repeat the same code:
public ActionResult Edit(int id, EditViewModel model)
{
if( !ModelState.IsValid )
{
model.FooTypes = new SelectList(repository.GetFooTypes(), "Id", "Value");
return View(model);
}
return RedirectToAction("Index");
}
I don't like having this code in two separate locations. Is there any common practice for refactoring this into a single spot so I dont need to repeat this code?
Given that c# is an object oriented language, there are plenty of options available.
The simplest would be to just wrap it in a method within the controller:
private SelectList GetFooTypesList()
{
return new SelectList(repository.GetFooTypes(), "Id", "Value);
}
and call it when setting up your model
or if you're using it in multiple classes you could create a helper method in another class that accepts the repository or an IEnumerable as a parameter.
If you want to get really advanced, you could use a ModelFactory to create the FooType model for you, with a prepopulated FooType property so the controller doesn't need to worry about it at all.
There's plenty of options, you just need to pick the one that's best for you.
My personal preference is the simple helper method in the controller.
I've done it in the model before (when it was the coding practice for that project team), but it depends on your philosophy on what's "business logic" and what's "data access", and what belongs in the model vs controller. Different, and justifiable, opinions exist.
Model, where you need a nullable type for FooType:
public class EditViewModel
{
public int? FooType { get; set; }
public IEnumerable<SelectListItem> GetFooTypes(object selectedFooType = null)
{
return new SelectList(repository.GetFooTypes(), "Id", "Value", selectedFooType);
}
}
"Get" controller, where you need to create the model first to ensure the Model property is available in the view:
public ActionResult Edit(int id)
{
EditViewModel model = new EditViewModel();
return View(model);
}
The View (sans Barbara Wawa):
#Html.DropDownListFor(m => m.FooType, Model.GetFooTypes(Model.FooType))
An alternative that takes the "view stuff" out of the model might look like so:
Model:
public class EditViewModel
{
public int? FooType { get; set; }
public IEnumerable<int?> FooTypes
{
get
{
// declare/define repository in your model somewhere
return repository.GetFooTypes();
}
}
}
View:
#Html.DropDownListFor(m => m.FooType, new SelectList(Model.FooTypes, "Id", "Value", Model.FooType))
In the reply by "nekno" (answered Sep 30 at 22:19), there are two alternatives of a ViewModel which either returns a 'IEnumerable<SelectListItem>' or a 'IEnumerable<int?>'.
Both of these alternative uses a repository but without actually creating it, so I would like to extend the code example a little bit, and chooses the second alternative i.e. the class with the property typed 'IEnumerable<int?>' :
using Microsoft.Practices.ServiceLocation; // ServiceLocator , http://commonservicelocator.codeplex.com/
using MyOwnRepositoryNameSpace; // IRepository
public class EditViewModel
{
public int? FooType { get; set; }
public IEnumerable<int?> FooTypes
{
get
{
return Repository.GetFooTypes();
}
}
private IRepository Repository
{
get
{
return ServiceLocator.Current.GetInstance<IRepository>();
}
}
}
The above kind of code with a "Dependecy Lookup" is now using a dependency to a third-part library, in this case the Common Service Locator library.
My question is how can the above code be replaced with "Dependency Injection" ?
The ViewModel itself would indeed be very trivial to implement, just like this:
using MyOwnRepositoryNameSpace; // IRepository
public class EditViewModel
{
private readonly IRepository _repository;
public EditViewModel(IRepository repository)
{
_repository = repository;
}
public int? FooType { get; set; }
public IEnumerable<int?> FooTypes
{
get
{
return _repository.GetFooTypes();
}
}
}
The problem is how to make the ViewModel become injected with an implementation, when the ASP.NET MVC framework will instantiate the 'EditViewModel' and send it as a parameter into an Action method such as tihs method signature:
public ActionResult Edit(int id, EditViewModel model) {
// How do we make the framework instantiate the above 'EditViewModel' with an implementation of 'IRepository' when the Action method is invoked ???
The official MVC tutorial does not seem to provide any nice solution as far I can see.
In the section "Handling Edits" (methods 'public ActionResult Edit(...)' ) at the below pages they are duplicating the creation of the options in a similar way as in the poster of this stackoverflow question you are now reading.
http://www.asp.net/mvc/tutorials/mvc-music-store/mvc-music-store-part-5
http://mvcmusicstore.codeplex.com/SourceControl/changeset/view/d9f25c5263ed#MvcMusicStore%2fControllers%2fStoreManagerController.cs
If there is a solution about how to make the framework inject view model with your data retrievers (such as a repository) then I believe it may be to use some implementation of either 'IModelBinderProvider' or 'IModelBinder' but I have experimented with these without real success...
So, can anyone provide a link to a complete working example with ASP.NET MVC 3 code that enables injection of a data retriever into the constructor of a view model that the framework instantiates and will send as parameter into an action method ?
Update 2012-01-01:
For those intrested in a solution to this specific question about constructor injection of a ViewModel instance, when the framework instantiates it and sends it as parameter to an MVC Action Method parameter, I have created a new question with a more specific subject, and thus hopefully more likely that someone with a solution will find it and post a good answer:
Constructor injection of a View Model instance used as an Action method parameter

Categories