My model is like this
public class MyModel
{
string ID {get;set;}
string Title {get;set;}
MyOtherModel Meta {get;set;}
}
How to define custom model binder for type (MyOtherModel) so when default binder binds MyModel it calls custom model binder for 'Meta' property.
I registered it in App start like:
ModelBinders.Binders[typeof(MyOtherModel)] = new MyCustomBinder();
but this doesn't work. Any idea or any good article with more infor regarding to model binders?
There is an article about collections that touches a bit the complex type mapping stuff:
Collections and a bit about complex types
In the other hand this article could give you some useful tips:
http://odetocode.com/Blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx
I suggest you as a workaround to use a model binder for MyModel class, it's not a perfect solution but you can refactor it easily once you discover a better solution. : )
Actually, If you will put in some Edit/Create View with Model of your MyModel class something like this:
<%= Html.TextBox("Meta.Prop1") %>
where Prop1 is property of your MyOtherModel class,
then
UpdateModel(model);
will populate textbox value to your custom submodel property. And back, textbox value would be populated with that submodel value as well.
So, once you update your current model, you are updating submodels too.
Related
I had a Model with a property that is an abstract class
public class MyModel
{
public Foo Property { get; set; }
}
I have several implementations of Foo, each with their own EditorTemplate. Each implementation also has fields unique to it. Let's call these AFoo, BFoo, and CFoo.
Now, when I post my form, the model binder will try to create these all as Foo. Obviously, this will not work, because Foo is abstract.
Short of writing Model.GetType() to a hidden field, then using a custom model binder, is there a way to tell the default model binder how to correctly construct the correct type?
I have the following model:-
[MetadataType(typeof(TMSServer_Validation))]
[Bind(Exclude = "TMSRack,ServerModel")]
public partial class TMSServer
{
}
and I have the following drop down inside my view:-
#Html.DropDownListFor(model =>model.Server.TMSRack.DataCenterID, ((IEnumerable<TMS.Models.DataCenter>)ViewBag.DataCenters).Select(option => new SelectListItem {
Text = (option == null ? "None" : option.Name),
Value = option.ID.ToString(),
Selected = (Model.Server.TMSRack != null) && (option.ID == Model.Server.TMSRack.DataCenterID)
}), "Choose...")
Then on my controller class I have the following :-
ViewBag.Racks = repository.getrelatedracks(Server.TMSRack.DataCenterID);
But since I have excluded the TMSRack navigation property (mainly to avoid over-posting attacks), so the Server.TMSRack.DataCenterID will always be null. And to get its value I wrote the following:-
ViewBag.Racks = repository.getrelatedracks(Int32.Parse( Request.Form["Server.TMSRack.DataCenterID"]));
But I know that using Request.Form is not the right approach to follow, so my question is there a way to get the excluded property using more reliable way ?
Thanks
My answer is going to assume TMSServer is a domain model.
With that in mind, this is the perfect example of when to use a view model. By using a view model instead, you have complete control over how the properties are mapped from the view model to the domain model. Something like:
public class RackViewModel
{
public int DataCenterID
// other Rack properties
}
Then either send a list of RackViewModel to your view, or create a view model that encompasses all of that, too:
public class ContainerViewModel
{
public List<RackViewModel> Racks { get; set; }
// other view-specific properties
}
Now, when you POST the data back, not only do you have complete control over what properties you want to bind to your view models, you also have complete control over the mapping that takes place from converting your view models to domain models.
The bottom-line is this: if your view accepts a view model that only allows the user to POST the data they should be allowed to POST, over-posting doesn't even exist. Well-designed view models, or even making the distinction between a view model and an input model (i.e. a separate model that represents the data you want to bind back to in your action), eliminates over-posting entirely.
Over-posting only exists because you're not restricting the model binding process enough. If you ask it to bind to a class that has 10 properties in it when you only need 3 you're allowing the user to potentially stuff data into those other 7 properties.
This is one reason why view models are so popular. They allow you to narrow the scope of your view, whilst also narrowing the scope of the model binder. That leaves you free to properly manage the process of mapping from your view model to your domain model, without introducing a vulnerability.
Update
As you don't want to go the view model approach, your idea will work but you can do it slightly differently. Something along the lines of:
public ActionResult SomeAction(SomeModel model, TMSRack rack)
Where:
SomeModel is the type of model you're decorating with Bind(Exclude...) (it's not obvious what type that is from your question.
TMSRack is the type I assume you want to bind to.
As TMSRack is defined in your main model anyway, as long as you're using the Html.* helpers, it will have the correct names generated for it on the form in order to bind straight back to it as a separate parameter on your action. Then you can do whatever you want with it, without resorting to Request.Form.
I am working on my model hierarchy and have a few questions.
1) I have a base model for each view type (Add, Edit and View). I put thing in these that are specific to each view type.
2) I then have a model base which the bases above inherit from. This allows me to include things that pertain to ALL views.
3) In my ModelBase, i have a couple of other view models like FeedbackViewModel, ShoppingCartViewModel, etc that I can consume on any view.
4) I have my MasterPage inheriting ModelBase.
Example
Public MustInherit Class ModelBase
Public Property ErrorMessage As String
Public Property InformationMessage As String
Public Property WarningMessage As String
Public Property FeedbackModel As New FeedbackViewModel
End Class
Public MustInherit Class ViewModelBase
Inherits ModelBase
'View Model Specific Stuff
End Class
'Allows contact us form to be submitted.
Public Class ContactUsViewModel
Inherits ViewModelBase
Public Property Name As String
Public Property EmailAddress As String
Public Property Phone As String
Public Property Comments As String
End Class
That is the basic structure of my models, but a few questions:
1) What do I do with a view that requires no model, but I need to pass the FeedabckViewModel, SHoppingCartViewModel, etc.? I was thinking of a GenricViewModel?
2) Do you see any flaws in this design?
Thank You!
Some points:
Why use ErrorMessage, InformationalMessage, WarningMessage, etc. ModelState should be more than enough, and it ties in better with the validation helpers as opposed to you writing the manual stiching in the View.
I think a "base" model to handle different view types is a bit overkill. I think a enum specifying the mode would be better, then you can make decisions accordingly.
Overall, there is nothing really wrong with your design. It's a matter of opinion - for which mine is i usually create a ViewModel per View. Keeps it simple. I use areas extensively so it doesn't get messy. I generally try and create the ViewModels to make the View simple - that's the ultimate goal, not to re-use code across View's, but to keep the View simple. Such as augmenting the model with nested models to make use of partials/templates, as opposed to having a bunch of strings.
1) What do I do with a view that requires no model, but I need to pass the FeedabckViewModel, SHoppingCartViewModel, etc.?
Doesn't that kind of contradict itself? :) If you just need "parts" of a couple of ViewModels, either create another ViewModel, or if it's just a couple of fields, just stick it in the ViewData.
I have a domain object Thing which can contain several Categories. So I have implemented my HTML helper to create a checkbox group of all possible Categories. I have no problem receiving:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Thing Thing, List<string> Categories)
However I am wondering whether I could use a custom Model binder to use just this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Thing Thing)
So basically I am looking for a way to use the model binder to bootstrap the object tree/graph.
Any pointers appreciated. Thanks.
Christian
I was mixing up domain objects and DTOs. Making Thing a DTO (or view object?) does the trick.
so having something like this works:
class Thing
(
List Categories;
)
The model binder figures out how to put the posted data into the Categories
Is there a way to use something like this: System.Web.Mvc.ViewUserControl<DateTime>? I get an exception that the type is a value type, not a reference type. What is the proper way to resolve this? Thanks.
Edit
What I am trying to accomplish is having a control that takes a DateTime to render a calendar. I want to pass in the DateTime from my ViewData using "dot notation" for MVC.
Edit 2
I heard/seen that some MvcContrib projects might have this capability, but I can't seem to find it again.
There is no way to resolve this - only workarounds. You cannot include Value types as TModel in ViewUserControl as TModel has a constraint to be a reference type.
The easy workaround is to wrap your value type in a class as your model.
class MyModel {
public DateTime? DateTime {get;set;}
}
By defining your own class like MyModel above, you can now pass a DateTime to your views, like so
ActionResult MyActionMethod() {
var db = new MyDataContext();
var dbThing = db.Things.Where(t=> t.DateTimeProperty>=DateTime.Now).First();
return View("myView", new MyModel{DateTime = dbThing.DateTimeProperty});
}
Your view of course will need to define MyModel as it's model type, like so
public partial class MyView:ViewUserControl<MyModel> {
//snip
}
And inside your View, simply refer to the DateTime property to access the DateTime.
<%=Model.DateTime%>