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.
Related
I was wondering if it's possible to have something like:
[HttpGet]
public JsonResult AddTextFile(string path)
{
if(string.IsNullOrEmpty(path))
{
// return error
}
}
But in the case where I might have a lot of parameters in my controller method I don't want to use string.IsNullOrEmpty() for each one. I know that I could use view-models with a [Required] field indicator and that will allow me to use ModelState, but because these are all API endpoints, I'm requiring information through get parameters.
Is there an elegant way of requiring controller method parameters, so that if any of them are not set it would return a generic response message?
Use a complex object as the parameter:
[HttpGet]
public JsonResult AddTextFile(MyObject obj) {
if(!ModelState.IsValid) {
// return error
}
}
public class MyObject {
[Required]
public string Path { get; set; }
}
The properties of MyObject will be taken from the query parameters, like: /addtextfile?path=blah
And the model validation will apply.
I have lots of controllers, which return data, based on parameters.
But users can fake the parameters they send to server, so I would like to intercept all requests, and if some parameter that come back with model is not valid, give warning/error.
the example header of controller is this
[Intercept<CarModel>] <-- I want something like this, <CarModel> because the interceptor knows what type to cast the model when intercepted
public object Cards(CarModel model) {
I would like to create this kind of attribute, that intercepts the controller, checks if model is valid.
How could I do this? I googled about it, but not found anything like this.
Just put your validation code in the action. Better yet, inherit from an IValidatable interface and implement HasPropertiesValid() on all your models. Then you just call model.HasPropertiesValid(); at the beginning of your action.
Validatable interface
interface IValidatable {
bool IsPropertiesValid();
}
A model example
public class CarModel : IValidatable {
public string ModelName {get;set;}
public string ManufacturerName {get;set;}
public bool IsPropertiesValid() {
if(ManufacturerName == "Toyota") {
if(ModelName == "Prius") return true;
}
return false;
}
}
Controller
public ActionResult ToyotaCar(CarModel model){
if(!model.IsPropertiesValid()) return RedirectToAction("QuitMessingAround","CaughtYou");
}
I can't figure out how to "customize" the rules for the [Required] attribute when I stick it to a custom typed property. Code looks like this:
public class MyProp
{
public Guid Id {get;set;}
public string Target {get;set;}
}
public class MyType : IValidatableObject
{
public string Name {get;set;}
public MyProp Value {get;set;}
private MyType()
{
this.Name = string.Empty;
this.Value = new MyProp { Id = Guid.Empty, Target = string.Empty };
}
public MyType(Guid id) : this()
{
this.Value.Id = id;
// Fill rest of data through magic
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(this.Value.Id == Guid.Empty)
yield return new ValidationResult("You must fill the property");
}
}
This model shows up in forms (through its own EditorTemplate) as a textbox with a button which allows for selection from a list (the backing data is a Dynamics CRM 2011 Environment, and this model is actually aimed to represent a lookup attribute).
public class MyModel
{
// Many props
[Required] // This one is enforced correctly
public string MyString {get;set;}
[Required] // This one isn't
public MyType MyData {get;set;}
public MyModel() { this.MyData = new MyType(); }
}
The resulting view shows the field (empty, of course). User can only input data by clicking the field and choosing from a list (a jquery dialog takes care of this, and it already works).
The IValidatableObject interface sounds promising but the code doesn't seem to be ever invoked.
In the controller, I'm simply doing
[HttpPost]
public ActionResult MyAction(FormCollection data)
{
if (!ModelState.IsValid) return View();
// magic: handle data
}
What am I missing ? I probably misunderstood the IValidatableObject interface usage ?
Your controller action should take the view model as parameter instead of weakly typed FormCollection which has absolutely no relation to your model (and its validation rules):
[HttpPost]
public ActionResult MyAction(MyModel model)
{
if (!ModelState.IsValid)
{
return View();
}
// magic: handle model
}
Now the default model binder is going to be invoked in order to bind the view model from the request and evaluate any validation logic you might have in this model.
How do you expect from your code, ASP.NET MVC, to ever know that you are working with this MyModel class? You absolutely never used it in your POST action, so you cannot expect to have any validation on it.
Once you start using view models you should forget about weakly typed collections such as FormCollection and start working with those view models.
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.
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