Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Matt Brailsford 2276 posts 11085 karma points MVP 6x c-trib
    Jun 06, 2018 @ 10:31
    Matt Brailsford
    2

    The best way to get custom data to a veiw

    This started from a Twitter thread here https://twitter.com/mattbrailsford/status/1004298568218365952

    The discussion is based around the topic of the best way to get custom data to a view.

    With the introduction of Models Builder we now have strongly typed models for the pages on the site, and we get quick and easy access to properties declared on our model.

    The question though is what is the best way to get custom data to our views? Say you are using TeaCommerce and want to pass the current store down? Or maybe you store extra info about a page in a custom database table and you want to pass that down too? What is the best approach.

    To date I've seen a few approaches:

    1. The Lars Approach
      I've seen Lars talk about his models builder approach a few times and a google example can be found here https://blog.aabech.no/archive/comparing-modelsbuilder-and-ditto/ but essentially, his approach is to create partials to extend the MB models and add extra properties not serviced by Umbraco. I'm not 100% on his approach for populating custom properties however, but I'd guess these would just be populated in the controller.

    2. The Dave Approach
      In this 24 days article https://24days.in/umbraco-cms/2016/getting-started-with-modelsbuilder/, Dave shows an approach of using a content finder to populate a custom property

    3. The Ditto Approach
      If you are using Ditto the idea here is to create custom view models for your views which is a lot of extra work beyond just using MB models, however by using Ditto, you can create custom value resolvers to resolve values from any data source, so you can map custom data to a view model pretty easily.

    4. The UmbMapper Approach
      I guess this is ultimately a similar approach to Ditto in that you use view models and create custom Mappers to resolve a custom property value.

    5. The AutoMapper Approach
      Similar again to Ditto/UmbMapper but this is just plain old mapping, there is no umbraco context in your mappings though, but if using MB this may be ok.

    I've seen people bend MB, extending it with custom properties, but I've also seen in the docs for MB that you shouldn't do this, however, I do like how simple it keeps things.

    The later mappers that promote view models to introduce an extra concept which can add a lot to a project, and make things a tad more complex.

    Ultimately I'd like to know what the official suggested approach would be in these scenarios? But maybe also to think of what could be an ideal solution keeping the simplicity of MB without adding a complex view model pattern on top?

    Thoughts?

  • André Santos 10 posts 81 karma points
    Jun 06, 2018 @ 10:41
    André Santos
    3

    I use what you call the Lars Approach:

    1. Partial classes to extend the ModelsBuilder models with extra properties
    2. Populate the extra properties in the Controller

    Simple and clean.

  • Lars-Erik Aabech 335 posts 1053 karma points MVP 3x c-trib
    Jun 06, 2018 @ 21:11
    Lars-Erik Aabech
    2

    Tl;dr: It depends! :)

    All roads lead to Rome. But 95% of them will get you in trouble.

    The real Lars Approach(es)

    Disclaimer: These are all examples of "it depends". For anyone looking for the "right" approach, don't read this. Look further down in the post.

    You're right: I have put external data on the typed models in controllers through the times. I recognize I haven't mentioned it specifically. Most of my posts just advocate adding logic for traversing the hierarchy or calculating stuff in your models. Not to mention exploiting interfaces for compositions and common things. The latter is what I find is the most powerful about adding partials. You can adapt concepts that your reusable logic know about.

    Most of the times I've had external data, I've ended up creating partial controller actions and passing some id from the model. Then we're back in a specific MVC context for that external data. Data from the content can be passed as well (even the content).

    Some times the domain entities and the site are in so tight a bounded context that it doesn't make sense to create view models either. With UCommerce for instance, you've got a behemmoth of a cache layer and can access a lot from memory. You'll probably never re-use your views with Tea-Commerce instead, so you might as well tightly couple the whole thing and go...

    @{
    var product = Product.Get(Model.ProductId);
    }
    
    <h1>@product.Name</h1>
    

    ... in your views.

    Of course, I've had several @Model.Product implementation as well. Ranging from wrapping the singleton call above in the partial to using dependency injection with the controller to get IRepository<Product> or even more domain specific abstractions. These are all different sized projects with more or less automated tests and more or less rules and logic. It could all be mapped and/or cached on some level, but the decisions are ultimately based on volatility, traffic and project size.

    But then I've also had variations of posted form records or order confirmations for instance. Those vary! Which brings us to...

    NuCache concequences

    As late as in your original Twitter thread, and several times over the last years, @zpqrtbnk has mentioned that some day typed models will be cached instances, henceforth the same instance for multiple requests. But until that happes, and since the birth, the ContentCache have asked the current PublishedContentModelFactory to adapt an XmlPublishedCache instance for every query. (Code here).

    So for now, it's been safe to add variable data to a typed model in a controller. Time to stop, though.

    We got to discuss POCOs vs. typed models at UK-fest '17. @James Jackson-South had recently launched UmbMapper, and had expressed a wish for pure POCOs as models. There's a really cool way to go about that, and I think Stephane picked up on it. We can have a cross reference dictionary (xref, idmap, whatever) that keeps the concrete published content and a pure poco referenced, so we can get one or the other based on the id or "the other". Say, give me the POCO for this ID or Model, or give me the Model for this ID or POCO. The POCO is just what you'd want in your view. Which mapper you'd use, I'd leave for habit and preference (Your 3-5). If you don't want to map to a POCO, you could still have it create an adapter like now.

    In any case, when NuCache hits production and (whatever) models are cached across requests, we need a way to intercept the model creation. This is the extension point where we can choose our mapper or add stuff to an adapter (like 1-2). We just have to start minding that it will be cached.

    I imagine that extension point might just be your own implementation of IPublishedContentModelFactory, but an event or something more concrete would also make sense. Maybe a virtual Create<T>.

    An idea

    While thinking about this the last 8 hours (Damn you... ;) ), it struck me that there's also another (better?) solution for custom content. It has some similarities to Ditto in that it exploits inherited views.

    If you dig into the ASP.NET ViewPage.Model property, you'll see it delegate to its ViewData's Model property. ViewData is actually a ViewDataDictionary. I can't be bothered to pop DotPeek, but I'm pretty confident ViewDataDictionary has something like this implementation:

    public object Model
    {
        get { return _dictionary["model"]; }
    } 
    

    So your UmbracoViewPage<T>'s magic Model is actually just an "adapter" around the casting of one of your ViewData values.

    Who said there should only be one model? How about introducing MultiModelViewPage<TContent, TSomethingElse> or even more domain specific types like ProductViewPage?

    You could have

    public ModelsBuilderType Model // Content?
    {
        get { return (ModelsBuilderType)ViewData.Model; }
    } 
    
    public ProductViewModel Product
    {
        get { return (ProductViewModel)ViewData["productModel"]; }
    }
    

    And in your controller you just stuff things into ViewData. Possibly nicely abstracted into your domain by a base class or extension.

    I haven't done this myself before, but it seems like a nice way to separate different parts of your page into their own models. They'd probably be properties of your root Model anyway.

    Don't get me wrong, I've used ViewData lots, but it never really struck me that it could be formalized like this.

    You'd even have the option of granular control over cache per model.


    I've had several other thoughts about the subject matter, but I think I've got to stop now. Thanks for putting a nickle in me. :)

  • Andy Butland 362 posts 1946 karma points MVP 3x c-trib
    Jun 07, 2018 @ 07:28
    Andy Butland
    2

    My first - and I think still last - instinct and practice here is to go with hijacked routes, controllers and view models.

    The analogy I've considered in the past is to take Umbraco out of the equation and consider you were working in a standalone MVC web application, where you have some domain model classes (likely populated from a database via an ORM). And then on a particular view you need to display some data drawn from this domain model, and some from elsewhere, perhaps from an external API. In that case I don't think it would be common to look to consider augmenting your domain model classes with properties to populate from the external API, rather practice would be to create a view model that represents the data needed for that view, and populate it in a controller.

    I think it boils down to whether you see the Umbraco content as your "domain model" in the analogy above, or as already a form of "view model". My preference and instinct is for the former, which just I find just more familiar from when working in other MVC application scenarios. But I know others - probably some on this thread whose blog posts I've read - perhaps see it more as the latter.

    Anyway... that's my 2pth!

    Andy

  • Lars-Erik Aabech 335 posts 1053 karma points MVP 3x c-trib
    Jun 07, 2018 @ 07:43
    Lars-Erik Aabech
    2

    Is an IPublishedContent a domain model or a view model? In fact it's Umbraco's "official" base view model for content. What we use it for is up to us. :D
    It's all about bounded contexts and project size. If you're making a small one off site with no volatility I'm not against the IPublishedContent being both my domain model and view model.

    If you're building a complex system with structured/normalized data and whatnot, you might have several bounded contexts from where you shouldn't get anything but DTOs. Those can be composed in or mapped to a view model (or several like suggested above).

    If the IPublishedContent are domain models for anything, then it's presenting content. In the content domain. IMO that makes perfect sense. The price for a product though? Maybe not... An order? Never. And you can never persist changes on them, so they can only have so much behavior - related to presentation.

  • Ethan Scott 3 posts 73 karma points
    Jun 08, 2018 @ 05:57
    Ethan Scott
    0

    I have been following this method for a while now and this yields results for me.

  • Wouter van der Beek 18 posts 249 karma points
    Jun 07, 2018 @ 13:55
    Wouter van der Beek
    0

    At Perplex we use custom ViewModels for all our views. The ViewModels are inherited from a generic base class which holds the PCM (Published Content Models) for the current page (ala "public T Content {get; set;}", where T is your PCM). This completely avoids any unnecessary property mapping. For non-trivial pages we use custom controllers (using route hijacking). Lastly we use a default umbraco controller which always generates the generic base ViewModel for the given page. That way even if we don't have a predefined controller or a ViewModel for a given page (read: doctype) we still can work with the view and load up our ViewModel. Using the above steps we have a setup that works for all pages out of the box, and whenever we need custom logic, we simply create a custom controller and ViewModel for the given page.

    As for our views, we use UmbracoViewPage<T> for all our views, but if you really wanted to use UmbracoTemplatePage you could simply let your base ViewModel inherit from RenderModel, which will basically make your ViewModel usable in any situation. We also load up our generic ViewModel with some frequently used properties such as the current hompage, information about the menu, culture, etc. This also makes it easy to use in the base _layout.

    On the subject of PCM usage: I strongly advise against customizing PCM's to hold additional data. They should encapsulate the data they are designed to hold: node (document) data. Customizing PCM's to hold ViewModel data (or anything else really) seems like trying to make it do two things at the same time, when really they should not be strongly tied to the View. Also it requires the additional properties to be loaded up using a controller (or some other location), when in fact it is Umbraco that should be handling the initialization of PCM's. This could also lead to weird situations where some properties on your PCM's are not set when you expect them to be. Furthermore if I am not mistaken, PCM's are managed by Umbraco and are cached, so this would also raise questions regarding concurrency when it comes to loading up the PCM (what if you have multiple requests trying to populate your PCM at the same time?). And what if you would use your PCM's outside of the context of just a page?

    It could be argued that having to create a custom ViewModel for every template/document type is tedious, but if that is the concern you could simply go for a generic ViewModel which should wipe that concern off the table.

    That being said, there are certainly situations where extending or modifying your PCM's might be desirable. For example we have run into the situation where the properties on a PCM needed to be returned or processed in a specific way, e.g. converting user input or a string. In this case we use the Modelsbuilder "AppData" setting and supply the extra properties in a seperate (partial) class file:

    /App_Data/Models/Homepage.cs // Custom properties
    /App_Data/Models/Homepage.generated.cs // Generated by MB
    

    Although in many scenario's you could also use a custom PropertyValueConverters to get the same effect, but this tents to be a bit more labour intensive.

    To summarise, I would suggest to always keep the "Umbraco.ModelsBuilder.ModelsMode" to "Dll", unless you have a vey good reason not to, and use custom controllers and ViewModels.

  • Lars-Erik Aabech 335 posts 1053 karma points MVP 3x c-trib
    Jun 07, 2018 @ 22:39
    Lars-Erik Aabech
    0

    Furthermore if I am not mistaken, PCM's are managed by Umbraco and are cached

    Managed by Umbraco: yes. Cached: Not yet, but with NuCache, yes.

  • Sebastiaan Janssen 4847 posts 14373 karma points MVP admin hq
    Jun 07, 2018 @ 14:07
    Sebastiaan Janssen
    2

    @Wouter I think it's a bit unhappy about your usage of <T> somehow. I've updated it for you.

Please Sign in or register to post replies

Write your reply to:

Draft