ASP.NET MVC 2. Cannot create an instance of an interface - asp.net-mvc-2

I use ASP.NET MVC 2 for my project.
I'm trying to edit my product information using next code:
[HttpGet]
public ActionResult Edit(int id)
{
IProduct product = productService.getProductById(id);
return View(product);
}
IProduct and other IEntities are instansing using IoC Castle Windsor. Page for Edit is succesfully loaded. In the top of my page I declared that page must Inherits="System.Web.Mvc.ViewPage〈DomainModel.Entities.IProduct〉. And it is. But when I'm trying to update my changes using next code:
[HttpPost]
public ActionResult Edit(IProduct product)
{
//Whatever i did here i always get the error prevents my any actions
}
then i'm getting the error 'Cannot create an instance of an interface.'
Can anybody clarify why?
Sorry for probably bad English, and thanks for any answer.

This is because MVC needs to know a concrete type to construct for your product parameter.
You cannot instantiate an IProduct, only a concrete type that implements IProduct. The easiest way to make this work, is to use simple concrete DTO types to transfer data from and to the view (sometimes known as the ViewModel).
You can make your example work with the interface type, if you implement a custom model binder for the type. (Basically a way to tell MVC what type to instantiate when it encounters IProduct).

Related

MVC 5 Edit with ViewModel by reusing EF Generated Code

I have a Business Model and an EditBusinessViewModel.
In MVC 4 I would use code something like this to edit a record:
[HttpPost]
public ActionResult Edit(MainMenu mainmenu)
{
if (ModelState.IsValid)
{
db.MainMenus.Attach(mainmenu);
db.ObjectStateManager.ChangeObjectState(mainmenu, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(mainmenu);
}
Now the auto generated code in MVC 5 looks like this, I've modified this Action to only include fields from my EditBusinessViewModel and named it Edit2:
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
public ActionResult Edit2([Bind(Include = "ID,BusinessName,BusinessDescription,BusinessAddress,BusinessPhoneOne,BusinessPhoneTwo,BusinessWeb,BusinessEmail,BusinessMelRef")] EditBusinessViewModel business)
{
if (ModelState.IsValid)
{
db.Entry(business).State = EntityState.Modified;
db.SaveChanges();
return Redirect("~/Home/Index/" + business.ID);
}
return View(business);
}
I have the Get part working, my Model and View are working by returning:
return View(new EditBusinessViewModel(business));
But when I post back, I get an error on this line:
db.Entry(business).State = EntityState.Modified;
The entity type EditBusinessViewModel is not part of the model for the current context. Which it is not and the reason for the ViewModel, I guess?
What I would like to know is can I use this code or is there something else I should be doing?
Update
I've been thinking about this too deeply and a ViewModel is just that, a ViewModel so now I have:
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
public ActionResult Edit2([Bind(Include = "ID,BusinessDescription,BusinessAddress,BusinessPhoneOne,BusinessPhoneTwo,BusinessWeb,BusinessEmail,BusinessMelRef")] EditBusinessViewModel business)
{
if (ModelState.IsValid)
{
business.UserEmail = User.Identity.GetUserName();
Business newbus = db.Businesses.Find(business.ID);
{
newbus.BusinessDescription = business.BusinessDescription;
newbus.BusinessAddress = business.BusinessAddress;
};
db.Entry(newbus).State = EntityState.Modified;
db.SaveChanges();
return Redirect("~/Home/Index/" + business.ID);
}
return View(business);
}
This way I post back the data I need from the view in the View Model, find the entity in the database by its matching ID and update it with the EF scaffold code.
Is there a better way?
Well, you won't be able to use your current code, for reasons I believe you're pointing to in your question itself. You're working with two different types, one that is mapped from a DB table and one that you are using specifically for your Views and is not mapped. Your Entity Model, you don't say which version of EF, but with MVC 5 I assume it's 6 or 6.1.
So you have your entity POCO generated by EF text template, and you have you ViewModel. Even if the properties were identical, EF would not take your ViewModel type, because it has no mapping definition in the edmx, this is the reason it says it's not in the current context as you've already recognized.
There are some decent ways to work in this system though. IF you desire to use separate entities and ViewModels, which I personally do in most of my own code. You could :
It seems like you have an ID, if that ID points to a unique ID on the EF Model, you could do a look up for an entity with that ID and then update the values of the entity with the values from your ViewModel and then save the entity with StateModified instead of the ViewModel.
If the properties are exactly the same or very similar, between your Model and ViewModel, you could look at something like AutoMapper, https://github.com/AutoMapper/AutoMapper, which would enable you to map your ViewModel directly to an instance of your entity Model Type.
If you're Model and ViewModel are vastly different, you could build a static tranform, not sure how many people do this but I like them. Essentially you define two static methods that enable you to convert your Model to your ViewModel and vice versa. The benefit is anywhere you need to do this you can call one method, and if the structure of either type changes you just have to update this in one location.
You say autogenerated code in MVC 5, You could mean just the default example code that comes with EF 5 but I think you're talking about MVC 5 Scaffolding. http://www.asp.net/visual-studio/overview/2013/aspnet-scaffolding-overview; if so, the code for these should not need much alteration at least not in the Controller side, unless you have specialty domain logic which it doesn't appear like you do. If you wanted to use separate ViewModels I suppose you could, in conjunction with one of the recommendations above, but the point of Scaffolding is to remove much of the plumbing that you have to do when exposing DB Models for basic CRUD methods.
If I've missed the mark on what you're looking for please reply in a comment. Also, it's a bit hard to provide code examples for the above recommendations without seeing the class definitions for your two models. I think the descriptions should be enough to go off of, if you think one will fit your use case well? But, if you'd like some simple code examples update your answer with the code for those classes and I can provide some.
From your posted snippet:
return View(new EditBusinessViewModel(business));
Here, business is not your ViewModel, but a variable (presumably your entity from the db) that is used in your ViewModel's constructor. I can only assume it's passed with the intent of storing it in one of your ViewModel's properties.
public ActionResult Edit2([Bind(Include = "...")] EditBusinessViewModel business)
Here, business is your ViewModel. It has the type EditBusinessViewModel, as you can see. But in that method you make the following call:
db.Entry(business).State = EntityState.Modified;
EditBusinessViewModel is not a type known by EF, since it is your viewmodel. You are supposed to pass your entity to the database. The ViewModel should only be used in your MVC project.
I'm pretty sure that one of the properties of your EditBusinessViewModel is the entity you need. This is vaguely confirmed by the fact that you pass your entity in the EditBusinessViewModel constructor.
I don't know what the property is called, since you didn't post the ViewModel's class. Assuming it's called MyEntity, this should do the trick:
db.Entry(business.MyEntity).State = EntityState.Modified;
But for clarity, I'd suggest renaming that parameter to prevent any confusion between separate uses of a business variable. Change it to businessVM or something similar so you're always reminded that you're working with a ViewModel, not an entity.

Single Page Application, upshot.js, DbContext and DbDataController : Only Entity Models are supported?

When using the examples for Single Page Application, I've the following TodoItem controller:
public partial class MVC4TestController : DbDataController<MVC4TestContext>
{
public IQueryable<TodoItem> GetTodoItems()
{
return DbContext.TodoItems.OrderBy(t => t.TodoItemId);
}
}
Question 1:
It seems that only EntityModels are supported ?
When using a real ViewModel (model only used for the Views, not not used as 1:1 mapping to database entity), the DbDataController does not support this.
Also using Linq.Translations or PropertyTranslator does not seem to work, see this code extract:
private static readonly CompiledExpressionMap<TodoItem, string> fullExpression =
DefaultTranslationOf<TodoItem>.Property(t => t.Full).Is(t => t.Title + "_" + t.IsDone);
public string Full
{
get
{
return fullExpression.Evaluate(this);
}
}
Question 2:
What is the recommended design when using SPA, DBContext and ViewModels ?
As far as I know so far is - it instists on the usage of "real" model classes bound to DbContext.
I have the same problem as you - I need to use my own DTO objects which are "flat".
The Json serialisation is currently not able to serialize data which has parent references in child objects (cyclic references). Usually I do not need the entity tree anyways so I created smaller classes which fits perfectly to the view.
I tried to use a normal Controller with JsonResult and parsed the returned model into ko.mapping.fromJS after retrieved the data. Thats working fine. But - you have to take care of all the nice stuff the MVC4 generated viewmodels are already dealing with (like creating navigation, etc.).
Maybe someone finds a workaround to "fake" a DbContext with DTO data.

Problem applying data annotation in asp.net mvc2

Im facing problem when trying to apply data annotation. In my case im passing FormCollection in controller
[HttpPost]
public ActionResult Create(string Button, FormCollection collection)
{
if (ModelState.IsValid)
{
}
else
{
}
}
and in ModelState.IsValid condition always have true value. Although i have left some blank fields in View. Also EnableClientValidation() is also applied in View for client side validation but its not working. what may be the problem
Your view must be strongly typed, and the parameters of your Create function must contain an object to hold your Model, not a generic FormCollection. So if you have a model of name MyClass that you have annotated, then you should have that as the parameter. Otherwise, how will the model binder know what class should it check your form against?

Can I register a custom model binder somewhere other than Global.asax?

It would be handy to limit the scope of a custom model binder for just a specific controller action method or its entire controller. Hanselman wrote a sentence that implied alternative locations for custom model binder registration but never seemed to finish the thought:
You can either put this Custom Model Binder in charge of all your DateTimes by registering it in the Global.asax
Is it possible to make these registrations at a smaller scope of the controller system? If so, is there any reason to avoid doing so outside of the Global.asax MvcApplication (e.g., performance reasons)?
As I was closing the tabs I opened for this question that I hadn't reached before giving up, I found someone with an answer. You can assign a ModelBinderAttribute to your view models:
[ModelBinder(typeof(SomeEditorModelModelBinder))]
public class SomeEditorModel {
// display model goes here
}
public class SomeEditorModelModelBinder : DefaultModelBinder {
// custom model binder for said model goes here
}
While it wasn't quite what I was looking for, it is even more specific than registering it for a controller or controller method.
Update
Thanks to Levi's comment pointing out a much better solution. If you are consuming the object with a custom model binder in an MVC action method directly, you can simply decorate that method's parameter with the ModelBinder property.
public ActionResult SomeMethod([ModelBinder(typeof(SomeEditorModelBinder))]SomeEditorModel model) { ... }

ASP.NET MVC Access model data in masterpage

I have created a UserSiteBaseController that gets commonly used data and sets the data to a UserSiteBaseViewData viewmodel in a method called SetViewData
public T CreateViewData<T>() where T : UserSiteBaseViewData, new()
{
....
}
I then create specific Controllers that inherit from the UserSiteBaseController as well as viewModels that inherit from UserSiteHomeViewData and can be created in the controller like so:
public ActionResult Index(string slug)
{
Slug = slug;
var viewData = CreateUserSiteHomeViewData<UserSiteHomeViewData>();
//If invalid slug - throw 404 not found
if (viewData == null)
return PageNotFound();
viewData.Announcements = _announcementsData.All(slug).ToList();
return View(viewData);
}
private T CreateUserSiteHomeViewData<T>() where T : UserSiteHomeViewData, new()
{
T viewData = CreateViewData<T>();
return viewData;
}
The UserBaseViewData holds data that needs to be use on every page so it would be great to be able to access this data from the Masterpage in a strongly typed manner. Is this possible or am I going about this in the incorrect manner?
If you get your masterpage to inherit from System.Web.Mvc.ViewMasterPage<BaseViewModel> you should be good to go...?
BUT, when I first started using MVC I went down the same route using a BaseViewModel class which contained all my generic ViewModel stuff and then I created specific view models, e.g. EventListingViewModel, which inherited from this BaseViewModel - much like you are doing. My masterpages then inherited from System.Web.Mvc.ViewMasterPage<BaseViewModel> and everything was going swell.
But after a while it all became a bit tightly coupled and a quite brittle. It became a pain in the ass to make changes and what not. I also came across this issue.
So I've reverted back to using the standard ViewData dictionary (with a static VewDataKeys class to avoid magic strings) for all my generic properties and then using custom POCO objects that don't inherit from anything as my presentation models. This is working much better and I wouldn't change back.
HTHs,
Charles