So as I understand it
Given a view model
public class MyViewModel{
public DateTime Date {get; set;}
public MyClass Class {get; set;}
}
.. a View Views\MyController\MyAction.cshtml
#model MyViewModel
#Html.DisplayForModel()
.. a partial View Views\Shared\DisplayTemplates\DateTime.chstml
#model DateTime
Some Date
.. another partial View Views\Shared\DisplayTemplates\MyClass.cshtml
#model MyClass
My Class
.. I should get
Date
Some Date
Class
My Class
.. But I just get
Date
Some Date
So it seems DisplayForModel finds DateTime template but not my custom template, even though I am following the conventions of naming it by the type of the property.
Am I missing something. I am using MVC 3 and believe this feature was already available in MVC 2
Having perused the MVC source code, it turns out that this is, in fact, not possible.
The reason is that #Html.DisplayForModel() attempts to find a template to use for rendering, by:
first looking for the name of the model's type, i.e. MyViewModel.cshtml or MyViewModel.vbhtml or MyViewModel.ascx etc, in location ~\Views, ~\Views[ControllerName], ~\Views\DisplayTemplates, ~\Views\Shared, ~\Views\Shared\DisplayTemplates
if it is not found it will walk down the model's base types, attempting each type's name in turn
if none are found, it will eventually end up at Object, for which there exists a built-in template
The object template is designed such that it retrieves all the model's properties, for rendering, from metadata based on the following condition:
metadata.ShowForDisplay
&& metadata.ModelType != typeof(EntityState)
&& !metadata.IsComplexType
&& !templateInfo.Visited(metadata)
Therefore any property that is a complex type will always be excluded. I think the confusion arises from Brad Wilson's post on custom object template, where he creates a custom object template and addresses the issue of Shallow Dive vs Deep Dive. By implementing a custom deep dive object template this will override the built-in object template and complex types can be rendered.
Related
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.
The Scenario:
I'm building a site in Umbraco 6 with MVC - I'm fairly new to Umbraco but I've done everything so far by following tutorials etc, and for the most part everything works nicely.
So I have a "contact us" form built as a partial view, rendered with the following code:
#using (Html.BeginUmbracoForm("SendEmail", "ContactFormSurface"))
{
which posts back to my ContactFormSurfaceController:
public class ContactFormSurfaceController : Umbraco.Web.Mvc.SurfaceController
{
[HttpPost]
public ActionResult SendEmail(ContactFormModel model)
{
Now, my ContactFormModel inherits from the Umbraco RenderModel, and I am "hijacking" the route for my Contact Us view in a separate ContactFormController:
public class ContactFormController : RenderMvcController
{
//
// GET: /Contact-us/
public override ActionResult Index(RenderModel model)
{
var contactFormModel = new ContactFormModel(model);
return CurrentTemplate(contactFormModel);
}
I want this so that I can have flexible headers and submit button text within the contact form based on Umbraco content. My ContactFormModel takes a RenderModel in it's constructor so that it has access to the underlying Umbraco content:
public class ContactFormModel : RenderModel
{
#region Ctr
public ContactFormModel(RenderModel model) : base(model.Content, model.CurrentCulture) {}
#endregion
#region Contact Form Fields
[Display(Name = "Your Name")]
[Required]
public string Name { get; set; }
The Problem:
When the form posts back to my surface controller SendEmail method, it appears there is an attempt to instantiate a new ContactFormModel, and I get a YSOD with the following exception:
No parameterless constructor defined for this object.
My first thought was, ok, I'll supply a parameterless constructor, since I don't actually need access to the Umbraco content within the SendEmail surface controller method, I only want that when initially rendering the view. But that's not so easy, since the base RenderModel requires an IPublishedContent object passed to it's constructor. I tried just passing null, and also:
public ContactFormModel() :base(new DynamicPublishedContent(null)) {}
but that results in a "Value cannot be null" exception.
I then tried changing my Umbraco form declaration to:
#using (Html.BeginUmbracoForm("SendEmail", "ContactFormSurface", new {model = #Model}))
to ensure that the ContactFormModel being passed to the view is sent back to the surface controller. This gets past the YSOD, but within the surface controller SendEmail method, "model" is null.
So there's a couple of things I don't really understand:
Why is there an attempt to call a parameterless constructor on my
ContactFormModel in the first place? Why is my ContactFormModel from
the view not just available in the surface controller method, since
that is what I've specified?
When I explicitly add the model to the route values for the form
post, why does it come through as null?
It feels like there must be simple solution to this, and I'm maybe missing something fundamental. Searching the forums there are plenty of examples of hijacking routes and inheriting from RenderModel, and also using a custom model and surface controller to process a form post, but not the 2 things combined, when the custom model inherits from RenderModel.
If I can't find a solution to this, I'll have to resort to not inheriting from RenderModel and hence not allowing any editable content within the contact us form, which seems to defeat the object of Umbraco. Or, create another model purely for use with the surface controller that doesn't inherit from RenderModel but duplicates all the fields in my ContactFormModel, which would be plain crazy!
Thanks for any ideas or advice.
Ok, I've had no responses to this question but am now in a position to answer it myself. Maybe it was a fundamental oversight, but not that obvious imho, and information on the Umbraco forum etc about inheriting from RenderModel is fairly limited.
Essentially the answer, as was my first instinct, is to solve the original exception "No parameterless constructor defined for this object" by providing a paramaterless constructor. The difficulty is working out what to put inside the parameterless constructor for my model, since it inherits from the Umbraco RenderModel which requires an IPublishedContent instance passed to it's constructor.
Luckily while browsing around I happened across this post on the Umbraco forum: http://our.umbraco.org/forum/developers/api-questions/40754-Getting-CurrentPage-from-SurfaceController.
The thing I had not fully understood was how the current page/view information is passed through Umbraco Context. So as per the above post, I created a couple of new constructors for my custom model:
public ContactFormModel() : this(new UmbracoHelper(UmbracoContext.Current).TypedContent(UmbracoContext.Current.PageId)) {}
public ContactFormModel(IPublishedContent content) : base(content) {}
With these in place, when I now post the form back to my Surface Controller method, as if by magic, my ContactFormModel is populated with all the details entered.
I have a really strange situation. I have a single Model, and two templates that are both strong-typed on the type of this model. The templates are nested, or in other words, I use DisplayFor on the second Template from within the first template. If I use the model associated with the first template, in the DisplayFor call for the second template, the second template does not render. If I use another instance of the same model type, everything works fine. It seems like there is some sort of cycle checking on the models associated with nested templates.
Model:
public class MyModel
{
public string Value { get; set; }
public MyModel Copy
{
get { return (MyModel) this.MemberwiseClone(); }
}
public MyModel MySelf
{
get { return this; }
}
}
DisplayTemplate1:
#model TestNestedTemplateSameReference.Models.MyModel
<div>Model1</div>
<div>#Model.Value</div>
<div>#Html.DisplayFor(x=>x, "ModelTemplate2")</div>
DisplayTemplate2:
#model TestNestedTemplateSameReference.Models.MyModel
<div>Model2</div>
<div>#Model.Value</div>
Interestingly if instead of calling
#Html.DisplayFor(x=>x, "ModelTemplate2")
I call it with the Copy property
<div>#Html.DisplayFor(x=>x.Copy, "ModelTemplate2")</div>
everything works fine as the actual instance of the MyModel class is different.
Does anyone know why this is done. Is there a viable workaround. It seems like this is a perfectly legitimate usage which shouldnot cause a stack overflow, or any similar issues. I could see how this could be used to protect against cycles for DisplayFor call without template name, but if I specify the template name seems like it should work fine.
It seems like it would be dangerous to bind the same model to multiple EditFor templates, but DisplayFor seems safe.
I can of course create a separate model for nesting level, but that is creating redundand class.
Any help is appreciated.
If what you were trying to do worked, it would result in a stack overflow as object after object was created via the Copy method. Each object would in turn create a new copy of itself, and you would quickly either run out of memory or run out of stack.
The default template executes this method before showing a property
bool ShouldShow(ModelMetadata metadata) {
return metadata.ShowForEdit
&& metadata.ModelType != typeof(System.Data.EntityState)
&& !metadata.IsComplexType
&& !ViewData.TemplateInfo.Visited(metadata);
}
My guess is that it's tripping either IsComplexType or TemplateInfo.Visited.
There's more info on this here:
http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html
I'm using the ASP.NET MVC DefaultModelBinder to bind a request to a model class, but only using two of its properties:
namespace MVCApp.Models
{
public class Ticker
{
public string Symbol {get; set;
public string Exchange {get; set;}
}
}
namespace Domain //in another assembly
{
public class Quote
{
public string Symbol {get; set; }
public string Exchange {get; set; }
//lots of other properties we need for output
}
}
public ActionResult ShowQuote(Ticker ticker)
{
var quote = quoteRespository.GetQuoteBy(ticker);
return View(quote);
}
In the view, they can specify the Ticker and Exchange; and that's ModelBound using the DefaultModelBinder. However, each time we need to actually use this Ticker object for something, we need to go to the QuoteRespository and get all of the properties populated for that Ticker.
Question
Should I get rid of the Ticker Object and just create a custom modelbinder to Model-bind to the Quote object; and in the Modelbinder make the respository calls to actually fill out the Quote object? Or should I violate DRY and make the call to that respository in every place we need a quote? Is there a built-in framework way of doing this that I'm missing?
It appears there is a school of thought that says not to make service-layer calls in the Modelbinder.
Update
I created the Ticker class just because we had these two properties in (almost) every single action:
public ActionResult ShowQuote(string symbol, string exchange)
Since they always belong together, I created a small Model class in the UI layer to push them around together (the aforementioned Ticker class). Ticker is not a view model class, and it isn't meant to be.
"Or should I violate DRY and make the call to that respository in every place we need a quote? Is there a built-in framework way of doing this that I'm missing?"
You could always retrieve the quote as part of a Quote controllers OnActionExecuting function.
I wouldn't consider this a DRY violation. Just the cost of doing business. Chances are the way you retrieve quotes won't change and you'll probably have < 10 places were you need this functionality. Depends on how many times you'll need to include that line.
Better to have short and concise action methods than getting all mangled up in base controller and onactionexecuting stuff.
Don't get into model binding against your repository. Did it in a previous project and its the worst and most brittle piece of the application.
I would use AutoMapper to map between view models and domain models.
For me it would be important to see what does Quote contains ? You mention it has other properties for output but does it have other properties and methods which are only relevant to the Domain namespace? If yes then I would like to keep an abstraction between the types used for views and the types in your domain namespace.
So you could end up having a TicketViewModel which contains everything required by your views and as Darin mentioned, use AutoMapper to map TicketViewModel to Quote.
EDIT:
If you really want to ensure DRY, then you can create your own ModelBinder (it's easy, tons of tutorials on Google) and bind your viewmodel from repository in it.
I've created a one table contact DB, which has only 3 columns (Id, Name, and Phone). I've then created the ContactsDataContext using my table Contacts in the model folder. Finally, I create a partial class still in the model folder (public partial class Contact).
now when I write this
public partial class Contact
{
public string MyContact
{
get
{
string name = this.Name ?? String.Empty;
}
// ... Other lines omitted
}
}
I get the following error :"'ContactsManager.Models.Contact' does not contain a definition for 'Name' and no extension method 'Name' accepting a first argument of type 'ContactsManager.Models.Contact' could be found (are you missing a using directive or an assembly reference?)"
Is something wrong??? Even the Intellisense in not showing the properties from my DataContext class. Yet, I've written some partial classes in the past with no problem.
Thank you.
Are namespaces the same on the two partials?
Chris Roden,
Yes, I've resolve it. In fact, I've asked the above question many months ago when I started learning ASP.NET MVC. I bought a book called "ASP.NET MVC - The Beer House/Nick Berardi/Wrox." Is a god book, but it's not recommendable for beginners. Generaly, things are thrown like that without telling where they come from.
The response came from applying the definition of a partial class. Among others, partial classes must:
have the same name
be preceded by 'partial' keyword,
be defined in the same namespace,
etc.
If you miss any of the above criteria, then you'll be in trouble because those criteria, ll be used to merge all the partial classes into a unique one.
In my case, I created a table called ContactDB. After I've created the datacontext class, I've dropped the ContactDB table on the Linq2Sql editor. As you must know, that creates the following class:
public partial class ContactDB
{
//All the columns in the table become properties in this class
}
The partial keyword allow me to write this:
public partial class ContactDB
{
//I can reference members of the above partial class... using this keyword
//After all, the 2 constitute one class.
}
After reading the definition of partial classes, I found out that I failed one of the criteria. I called my other partial class "Contact" which's different from ContactDB. That was enough to make me go crazy for 3 days until I read the definition. Moreover, if you defines the partial class with the right name but you put it in a different namespace, you'll get in trouble as well.
So, if the above answer doesn't work for you (I don't know exactly your problem), check the definition of the partial class.Don't forget to read the ScottGu's series on Linq2Sql.
Hope it helps.