Copied to clipboard

Flag this post as spam?

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


  • Tom Engan 430 posts 1173 karma points
    Jun 28, 2017 @ 10:31
    Tom Engan
    0

    How to add extra field to model into view?

    I'm not sure if the question is correct, but wilI have a model that doesn't contain all the fields because, in addition to the model, I will get value from a relational service, so how do I add an additional field to view?

    View (simplefied):

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<IEnumerable<HikingDestinationViewModel>>
    @using Neoweb.Models
    
    @using (Html.BeginUmbracoForm<Neoweb.Controllers.HikingDestinationSurfaceController>("ListHikingDestinationsByUserName"))
    {
    <table>
    @foreach (var hd in Model)
    {
        <tr>
            <td>@Html.DisplayFor(m => @hd.SelectedHikingDestination)</td>
            <td>@Html.DisplayFor(m => @hd.StartDate)</td>
            <td>@Html.DisplayFor(m => @hd.HikingCode)</td>
        </tr>
    }
    </table>
    }
    

    SelectedHikingDestination exists today only as int in the model, but assumes it's best to remove the field since the same value is also stored in RelationService.


    Repository class, petapoco table (NodeId and MemberId is the same):

    public static IList<HikingDestinationViewModel> GetAllByNodeId(int NodeId)
    {
        UmbracoDatabase db = Umbraco.Core.ApplicationContext.Current.DatabaseContext.Database;
        return db.Fetch<HikingDestinationViewModel>("SELECT * FROM HikingDestinations WHERE NodeId = @0 ORDER BY StartDate ", NodeId);
    }
    

    SurfaceController (simplefied, NodeId and MemberId is the same)::

    IEnumerable<HikingDestinationViewModel> models = new List<HikingDestinationViewModel>();
    var db = ApplicationContext.DatabaseContext.Database;
    this.HikingDestinations = new HikingDestinations();
    
    models = HikingDestinations.GetAllByNodeId(Members.GetCurrentMemberId());
    
    var relMember = Services.RelationService.GetByParentId(Members.GetCurrentMemberId()).Where(r => r.RelationType.Alias == "memberHikingDestinations");
    var docItems = Services.RelationService.GetChildEntitiesFromRelations(relMember);
    var results = docItems.Select(x => new { x.Id, x.Name, x.Path });
    

    I'm not sure if I need all the last lines, but the most important question now, is how to add an additional field to models so that I can retrieve the text or path to SelectedHikingDestination to appear in view since there is no add method directly to IEnumerable < HikingDestinationViewModel > ? enter image description here

  • Dan Diplo 1554 posts 6205 karma points MVP 5x c-trib
    Jun 28, 2017 @ 13:20
    Dan Diplo
    0

    I'm not sure I'm following, but if you want to expand your model then I'd create a new model that has the extra properties. You can then make one of those properties your IEnumerable<HikingDestinationViewModel> and add any other properties you like. Something like:

    public class MyHikingModel
    {
        public IEnumerable<HikingDestinationViewModel> Destinations { get; set; }
    
        public string SomeOtherValue { get; set; }
    
        public int SomeIntValue { get; set; }
    }
    

    Then in your controller you can populate your Destinations collection plus fill in any other properties you have added. You then just change your controller to return that model and your view to inherit from it:

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<MyHikingModel>
    
  • Tom Engan 430 posts 1173 karma points
    Jun 28, 2017 @ 14:03
    Tom Engan
    0

    Thanks for you answer, but do I need an extra class (or I could expand the current HikingDestinationViewModel with an additional property?), when the name or the path I want to list out in view already exists somwhere in the Umbraco database since it's published pages?

    (Note: When generating a database table from a petapoco class, should each property in the model match a field in the database table, or can I have more features in the model, than fields in the database table? Is this common practice?)

    I already have this two properties in the HikingDestinationViewModel, where I use ListOfHikingDestinations to a dropdownlist before saving, and SelectedHikingDestination for collecting the value of the selected HikingDestination whitch I add to the RelationService (RelationService - which I don't even really know what to use for yet, but it seem like an advantage to use).

    public IEnumerable<SelectListItem> ListOfHikingDestinations { get; set; } 
    public int SelectedHikingDestination { get; set; }
    
  • Dan Diplo 1554 posts 6205 karma points MVP 5x c-trib
    Jun 28, 2017 @ 15:04
    Dan Diplo
    1

    It depends if you want the property once per Hiking Destination (so you'll have many) or once per view. I assumed the latter. But if you need it on HikingDestinationViewModel then yes, by all means add an extra property. You can populate this from your controller.

    I'm pretty sure PetaPoco will ignore properties that aren't mapped from a table or query when selecting. PetaPoco also has [Ignore] attribute you can decorate a property with that tells it to definitely ignore the property.

    NB. Is there a reason you don't store this data in Umbraco rather than using a custom database? It seems like it would be simpler to do so...

  • Tom Engan 430 posts 1173 karma points
    Jun 28, 2017 @ 15:27
    Tom Engan
    0

    Thanks, and the [Ignore] was a pretty good tip.

    I just try to do best practice, and focus on learning different techniques, such as CRUD from front-end with use of Membership and Model and Surfacecontroller, and integrating my own solutions by expanding the umbraco database with petapoco tables. This, I understand may be appropriate where I have saved all member properties in Umbraco (also all the custom properties) where I have one-to-many connections to many hiking destinations for each member instead of associating all the tourist destinations directly to the Umbraco nodes. Is that what you mean?

  • Dan Diplo 1554 posts 6205 karma points MVP 5x c-trib
    Jun 28, 2017 @ 15:35
    Dan Diplo
    2

    Umbraco is flexible, so there is room for many ways of doing things.

    I don't know all your circumstances, but normally I would store as much as I can in Umbraco. If I wanted a one to many relationship between, say, a member and a hiking destination I'd either:

    a) Create a hiking destination page and give it a members property that uses a member picker to associate the members or b) Add a hiking destination picker to a member

    The advantage of storing content in Umbraco is you already have an easy way of editing the content set-up and you can use the same content APIs for all your queries. You can then use things like Examine for searching. You also get versioning, audit trail, easier deployment etc. out-of-the-box.

    The disadvantage is that you can't enforce referential integrity as easily as you can in a database. But there are ways of tapping into events that make this possible, if required.

  • Tom Engan 430 posts 1173 karma points
    Jun 28, 2017 @ 16:01
    Tom Engan
    0

    Thanks for the feedback. It's retrieving data that sometime seems somewhat complex (like now), but expecting this may come more into use in the future, so I'll don't get stucked when the time comes. It also gives me more training in MVC .Net development. It has sometimes been a time-consuming process to learn different Umbraco APIs, as I find it sometimes difficult to find relevant documentation.

    Do you know the benefits of using RelationService to link member to nodes?

    I've made a schema for creating hiking destinations that works fine where the innlogged member must add one hiking destination from a dropdown. It's the listing of several hiking destinations under the registration form I'm currently working on now (turmål = hiking destination).

    enter image description here

  • Tom Engan 430 posts 1173 karma points
    Jun 28, 2017 @ 22:19
    Tom Engan
    0

    I will try further and retrieve the name or the path of the published page each hiking destination refers to into the surfacecontroller tomorrow, and then I'll start with extending the model:

    [Ignore] <= No Petapoco db. Used for dropdownlist
    public IEnumerable<SelectListItem> ListOfHikingDestinations { get; set; }
    [Ignore] <= No Petapoco db. Used for setting the RelationService
    public int SelectedHikingDestination { get; set; }
    [Ignore] <= No Petapoco db. Used for viewing IPublishedContent
    public string HikingDestination { get; set; }
    

    The last thing I have to do, is to find the best way to matching values in two different IEnumerable<T> (There must be a better method than compare values in two nested foreach loop, I hope). Se above:

    IEnumerable<HikingDestinationViewModel> models <= Custom Umbraco db
    IEnumerable<IPublishedContent> results <= Standard Umbraco db
    
  • Dan Diplo 1554 posts 6205 karma points MVP 5x c-trib
    Jun 29, 2017 @ 11:51
    Dan Diplo
    0

    I've not used the Relationship API much, but it's basic advantage is that it creates the relationship at the database level, so it is quicker to query.

    The last thing I have to do, is to find the best way to matching values in two different IEnumerable

    What are you matching on? Do you have the Node ID stored in your HikingDestinationViewModel?

  • Tom Engan 430 posts 1173 karma points
    Jun 29, 2017 @ 12:33
    Tom Engan
    0

    Yes the nodeId is stored in HikingDestinationViewModel, and is the same as nodeId in dbtable cmsMember.

    [Column("nodeId")]
    public int nodeId { get; set; }
    

    So I want to list all records from custom dbtable HikingDestinations for each member. Listing from one table is easy with nodeId, as it's the same as members id (se belove the image):

    enter image description here

    As an example, for member id 1293, I've got three hit with this in the surfacecontroller (note that same hikingdestination are saved twice):

    IEnumerable<HikingDestinationViewModel> models = new List<HikingDestinationViewModel>();
    

    In addition to Title (preliminary not included in picture of schema above), StartDate and HikingCode, I would like to retrieve a field with the name or path of the hikingdestination (only one hiking destination per post can be saved), which now goes through RelationService and point to the published document - or maybe directly)?

    enter image description here

    As an example, for member id 1293 (parentId), I've got two hit with this (two different childId) in the surfacecontroller:

    IEnumerable<IPublishedContent> hikingDestinations = GetRelations(Members.GetCurrentMemberId(), "memberHikingDestinations");
    

    The parentId is the same as nodeId (also the members id), and the childId in dbtable umbracoRelation is the documents node where I want to find the Name or Path.


    Of course, the easiest thing would be to expand the HikingDestinationViewModel model with an additional field where I save the Name or Path when I save each hikingdestination, but then the need for RelationService disappears, and I still don't know what to use RelationService for, but I want to learn me how this could be done. And double storageing is anyway a bad practice. Better to retrieve values that already have been saved, than to save them again somewhere else.

  • Dan Diplo 1554 posts 6205 karma points MVP 5x c-trib
    Jun 29, 2017 @ 16:02
    Dan Diplo
    0

    You might be able to use LINQ to update your view models with the published content names. Something like:

    var mapped = models.Select(m => m.HikingDestination = results.First(r => r.Id == m.NodeId).Name);
    

    Another alternative is to dynamically return it when a property is accessed. This would require passing the Umbraco helper object into your class via the constructor. Something like:

    public class HikingDestinationViewModel
    {
        private UmbracoHelper umbHelper;
    
        public HikingDestinationViewModel(UmbracoHelper umbHelper)
        {
            this.umbHelper = umbHelper;
        }
    
        public int NodeId { get; set; }
    
        public string HikingDestination
        {
            get
            {
                return umbHelper.TypedContent(this.NodeId).Name;
            }
        }
    }
    

    Obviously you'd want to do some null checks and maybe cache the result in a variable.

  • Tom Engan 430 posts 1173 karma points
    Jun 30, 2017 @ 10:57
    Tom Engan
    0

    Yes, that's what I'm thinking of (I probably need a little more LINQ experience). Mapped: models with their three hits in the example (Id, 40, 41, 42 / nodeId 1293), and results have two hits (parentId 1293 / childId 1127, 1140), but I have not figured out how to use the var mapped (with it's three hits).

    In view, I get the list with the following method, currently:

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<IEnumerable<HikingDestinationViewModel>>
    @using Neoweb.Models
    @using (Html.BeginUmbracoForm<Neoweb.Controllers.HikingDestinationSurfaceController>("ListHikingDestinationsByUserName"))
    {
        @foreach (var hikingdestination in Model)
        {
            <Tr>
                <Th> @Html.ActionLink ("Endre", "UpdateHikingDestinationById", new {id = @hikingdestination.Id}) </ th>
                <Th> @Html.ActionLink ("Slett", "DeleteHikingDestinationById", new {id = @hikingdestination.Id}) </ th>
                <Td> @Html.DisplayFor (m => @hikingdestination.HikingDestination) </ td>
                <Td> @Html.DisplayFor (m => @hikingdestination.StartDate) </ td>
                <Td> @Html.DisplayFor (m => @hikingdestination.HikingCode) </ td>
            </ Tr>
        }
    }
    

    But I'm trying to figure out how to assign HikingDestination a value to each hit (three hits in this example) in Surfacecontroller when a foreach loop can't be used (var model in models {model.HikingDestination = ... })

    HikingDestination should also be HikingDestination.Name and HikingDestination.Url so I can list links to the published pages they refer to (inside an a-tag, or maybe a Html helper?). Maybe I change public string HikingDestination {get; set; } to public IPublishedContent HikingDestination {get; set; } in HikingDestinationViewModel ( but then the var mapped must be changed ), and assign them with Umbraco.TypedContent(id) in the surfacecontroller in a loop so I can retrieve .Name and .Url from HikingDestination, if I find out how.

    Thank you for all the tips, and Method 2 looks very interesting and advanced (since I have not used this method in the model class before), but I will first try to get the LINQ solution you have here.

  • Tom Engan 430 posts 1173 karma points
    Jun 30, 2017 @ 15:08
    Tom Engan
    0

    I have changed HikingDestinationViewModel to public IPublishedContent HikingDestination {get; set; }. Now only the three int i need in var mapped, and the correct numbers come in this order: 1127, 1140, 1127, and the links will work all fine in view. I only need to find them and replace the number 0000 with these three numbers in that order, but I haven't figured out where I find the numbers in var mapped. Had to slightly change var mapped, but it doesn't seem to work properly in Visual Studio (no active Step Into button in VS). Almost there..

    IEnumerable<HikingDestinationViewModel>  models = HikingDestinations.GetAllByNodeId(Members.GetCurrentMemberId());
    IEnumerable<IPublishedContent> hikingDestinations = GetRelations(Members.GetCurrentMemberId(), "memberHikingDestinations");
    var mapped = models.Select(m => m.HikingDestination = hikingDestinations.First(r => r.Id == m.nodeId));
    
    foreach (var model in models)
    {
        // 0000 = the three values in var mapped (loop running three times).
        model.HikingDestination = Umbraco.TypedContent(0000);
    }
    
  • Dan Diplo 1554 posts 6205 karma points MVP 5x c-trib
    Jun 30, 2017 @ 15:22
    Dan Diplo
    0

    Yeah, I was going to suggest just returning IPublishedContent if you need to access multiple properties.

    If you are using a loop your probably don't need "mapped. I think you can just do:

    foreach (var model in models)
    {
        int id = hikingDestinations.First(h => h.Id == model.Id).Id;
        model.HikingDestination = Umbraco.TypedContent(id);
    }
    
  • Tom Engan 430 posts 1173 karma points
    Jul 03, 2017 @ 12:27
    Tom Engan
    0

    The methods I use look like this.

    Three hits, id 40, 41, 43:

    IEnumerable<HikingDestinationViewModel>  models = HikingDestinations.GetAllByNodeId(Members.GetCurrentMemberId());
    
    public static IList<HikingDestinationViewModel> GetAllByNodeId(int NodeId)
    {
        UmbracoDatabase db = Umbraco.Core.ApplicationContext.Current.DatabaseContext.Database;
        return db.Fetch<HikingDestinationViewModel>("SELECT * FROM HikingDestinations WHERE nodeId = @0 ORDER BY StartDate", NodeId);
    }
    

    Two unique hits, id 13, 14:

    IEnumerable<IPublishedContent> hikingDestinations = GetRelations(Members.GetCurrentMemberId(), "memberHikingDestinations");
    
    public IEnumerable<IPublishedContent> GetRelations(int memberId, string relationTypeAlias)
    {
        var rs = Services.RelationService;
        var relType = rs.GetRelationTypeByAlias(relationTypeAlias);
        var relationList = new List<IPublishedContent>();
    
        if (memberId > 0)
        {
            var relations = rs.GetByParentId(memberId).Where(r => r.RelationType.Alias == relationTypeAlias);
            foreach (var relation in relations)
            {
                relationList.Add(UmbracoContext.Current.ContentCache.GetById(relation.ChildId));
            }
        }
        return relationList;
    }
    
  • Tom Engan 430 posts 1173 karma points
    Jul 03, 2017 @ 14:40
    Tom Engan
    0

    Unfortunately, hikingDestinations.First(h => h.Id == model.Id).Id still does not work since I get an error message saying "Sequence contains no matching element", (same error when I tested and modify model.Id to model.nodeId). I believe the hits should matching like this:

    named: models           named: hikingDestinations 
    HikingDestination       umbracoRelations (don't store same valuepair twice)
    
    id  nodeId              id    parentId  childId
    40  1293            ->  13    1293      1127
    41  1293            ->  14    1293      1140    
    43  1293            ->  13    1293      1127
    
    foreach (var model in models)
    {
        // Three hits in the loop example
        model.HikingDestination = Umbraco.TypedContent(childId);
    }
    

    The logic seems simple: "Get hikingDestinations:childId and put it into the loop where (models:nodeId = hikingDestinations:parentId)"

    I find the one model.nodeId in the loop, but don't know how to find the one hikingDestination.childId and hikingDestination.parentId.

  • Tom Engan 430 posts 1173 karma points
    Jul 04, 2017 @ 13:15
    Tom Engan
    0

    This may seem like a simple phrase to write, but I still have no idea how this should be written. Does anyone know how to write out childId?

        IEnumerable<HikingDestinationViewModel>  models = HikingDestinations.GetAllByNodeId(Members.GetCurrentMemberId());
        IEnumerable<IPublishedContent> hikingDestinations = GetRelations(Members.GetCurrentMemberId(), "memberHikingDestinations");
    
        models:                 hikingDestinations: 
        id  nodeId              id    parentId  childId
        40  1293            ->  13    1293      1127
        41  1293            ->  14    1293      1140    
        43  1293            ->  13    1293      1127
    
        foreach (var model in models)
        {
            // int childId:  "Get hikingDestinations:childId and put it into the loop where (hikingDestinations:parentId = models:nodeId)"
            model.HikingDestination = Umbraco.TypedContent(childId);
        }
    
  • Tom Engan 430 posts 1173 karma points
    Jul 05, 2017 @ 13:21
    Tom Engan
    0

    Now I think there may be no good method to find hits from umbracoRelation table to HikingDestinations table without inserting ID from the umbracoRelation table (new column in HikingDestinations table: rsId), because where can I find correct connections when RelationalService only saves new combination (if the linkage doesn't already exist), or maybe there is underlying methods in RelationalService I don't know about?

    enter image description here enter image description here

  • Tom Engan 430 posts 1173 karma points
    Jul 07, 2017 @ 09:19
    Tom Engan
    0

    What I think I should have done from the beginning, was to insert id to umbracoRelation as a column in table HikingDestinations instead of nodeId, because the same value is also added to umbracoRelations as parentId.

    I think I should insert only the members nodeId in column parentId, and nodeId to PublishedContent in column childId in table umbracoRelation - then read id of table umbracoRelation, and insert the newly created id in table HikingDestination table along with the rest of the values from the HikingDestination form. Thus, the LINQ queries would work (and that the parentId / childId combination would likely be easier to use).

    But now I have chosen to come up with a simpler solution: I put parentId / childId (member id / PublishedContent id) directly in table HikingDestinations instead, and now choose not to use RelationalService most for convenience, and because I provisional don't see any benefits with RelationalService so far in this project.

Please Sign in or register to post replies

Write your reply to:

Draft