Copied to clipboard

Flag this post as spam?

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


  • Warren Buckley 2106 posts 4836 karma points MVP ∞ admin hq c-trib
    Aug 02, 2013 @ 11:45
    Warren Buckley
    0

    MVC - Custom Route Hijacking with custom model and existing templates

    Hello all, I am trying to work on the CWS reboot where I am rendering out a member's profile page into the Umbraco site chrome, so I am currently hijacking the route with a custom controller as mentioned in the docs here: http://our.umbraco.org/documentation/Reference/Mvc/custom-controllers

    I am painfully close to resolving this after a lot of great help from the community. The final hurdle I am trying to overcome is that my navi partial view is throwing a YSOD

    The model item passed into the dictionary is of type
    'CWSStart.Web.Models.ViewProfileViewModel', but this dictionary requires a model item of type
    'Umbraco.Web.Models.RenderModel'.
    

    If I remove the partial in my top level Master.cshtml view/template then the page renders perfectly fine by mixing my custom model in with the rest of the umbraco page layout as soon as I add my partial back in it breaks.

    @Html.Partial("~/Views/Partials/Navi.cshtml")
    

    Here is my navi partial I am using UmbracoViewPage as I need some Umbraco stuff like .IsAncestorOrSelf()

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<Umbraco.Web.Models.RenderModel>
    
    @{
        var home    = Model.Content.AncestorOrSelf("CWS-Home");
        var pages   = home.Children.Where(x => x.IsVisible());
    
    }
    
    <div class="navbar navbar-fixed-top">
        <div class="container">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".nav-collapse">
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">CWS Start</a>
    
            <div class="nav-collapse collapse">
                <ul class="nav navbar-nav">
                    @foreach (var page in pages)
                    {
                        var isSelected      = page.IsAncestorOrSelf(Model.Content, "active");
                        var hasChildren     = page.Children.Any() ? "dropdown" : string.Empty;
    
                        <li class="@isSelected @hasChildren">
                            @if (page.Children.Any())
                            {
                                <a href="@page.Url" class="dropdown-toggle" data-toggle="dropdown">@page.Name <b class="caret"></b></a>
                                <ul class="dropdown-menu">
                                    @foreach (var child in page.Children)
                                    {
                                        var isChildSelected = child.IsAncestorOrSelf(Model.Content, "active");
    
                                        <li class="@isChildSelected">
                                            <a href="@child.Url">@child.Name</a>
                                        </li>
                                    }
                                </ul>
                            }
                            else
                            {
                                <a href="@page.Url">@page.Name</a>
                            }
                        </li>
                    }
    
                    @if (Umbraco.MemberIsLoggedOn())
                    {
                        <li>
                            @Html.ActionLink("Logout", "Logout", "AuthSurface")
                        </li>
                    }
                </ul>
    
                <form class="navbar-form form-inline pull-right" action="/search" method="POST">
                    <input type="text" class="form-control" name="q" placeholder="Search" />
                    <button type="submit" class="btn btn-default">Search</button>
                </form>
    
            </div><!--/.nav-collapse -->
        </div>
    </div>
    

    Would love any ideas you have on how to resolve this please?

    Thanks. Warren

  • Sebastiaan Janssen 5045 posts 15477 karma points MVP admin hq
    Aug 02, 2013 @ 11:53
    Sebastiaan Janssen
    0

    The partial view needs:

    @model CWSStart.Web.Models.ViewProfileViewModel
    

    And you should call it with:

       @{
           Html.RenderPartial("~/Views/Partials/Navi.cshtml", 
                 new ViewProfileViewModel());   
       }
    
  • Morten Bock 1867 posts 2140 karma points MVP 2x admin c-trib
    Aug 02, 2013 @ 11:59
    Morten Bock
    0

    Hi Warren

    The Html.Partial has an overload, that might be useful.

    @Html.Partial("ViewName", partialModel)

    So depending on how your master.cshtml look, you may be able to pass in the current page like this:

    @Html.Partial("Navi.cshtml", Model.Content)

     

  • Warren Buckley 2106 posts 4836 karma points MVP ∞ admin hq c-trib
    Aug 02, 2013 @ 12:06
    Warren Buckley
    0

    @Seb I am not sure that will work, just tried it.

    Because the line won't work as the model is totally different.

    var home = Model.Content.AncestorOrSelf("CWS-Home");
    

    YSOD that it throws, which is because my view model does not have Content in it.

    CWSStart.Web.Models.ViewProfileViewModel' does not contain a definition for 'Content' and no extension method 'Content' accepting a first argument of type 'CWSStart.Web.Models.ViewProfileViewModel' 
    

    Cheers

    Warren

  • Sebastiaan Janssen 5045 posts 15477 karma points MVP admin hq
    Aug 02, 2013 @ 12:09
    Sebastiaan Janssen
    0

    Never mind, I missed that you were hijacking. Try what Morten said.

    Ps. Why ARE you hijacking and still doing a lot of logic in the view? Isn't the point of hijacking that you can make your views really clean because you can do businesslogic in the controller?
    It's a genuine question, I still haven't been able to wrap my head around the benefits of hijacking routes, it seems so unnecessary to me for most things except for maybe cleaning up your view.

  • Warren Buckley 2106 posts 4836 karma points MVP ∞ admin hq c-trib
    Aug 02, 2013 @ 12:10
    Warren Buckley
    0

    @Morten I am unable to do that either as my top level master template/view is using this

    @inherits UmbracoViewPage<dynamic>
    

    So I am unable to pass Model.Content as a model to the partial . If i have try to do that I gett this YSOD.

    'System.Web.Mvc.HtmlHelper<dynamic>' has no applicable method named 'Partial' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method 
    

    Cheers
    Warren

  • Warren Buckley 2106 posts 4836 karma points MVP ∞ admin hq c-trib
    Aug 02, 2013 @ 12:17
    Warren Buckley
    0

    @Seb I am hijacking a route for a custom member profile view
    http://site.co.uk/profile/seb

    So I have a custom route in the app startup event:

    //Create our custom MVC route for our member profile pages
    RouteTable.Routes.MapRoute(
        "memberProfileRoute",
        "profile/{profileURLtoCheck}",
        new
        {
            controller  = "ViewProfile",
            action      = "Index"
        });       
    

    Here is the controller for binding our member to the ViewProfile ViewModel

    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using CWSStart.Web.Models;
    using umbraco.cms.businesslogic.member;
    using Umbraco.Core.Models;
    using umbraco.MacroEngines;
    using Umbraco.Web;
    using Umbraco.Web.Models;
    using Umbraco.Web.Mvc;
    
    namespace CWSStart.Web.Controllers
    {
        public class ViewProfileController : RenderMvcController
        {
            public ViewProfileController() : this(UmbracoContext.Current)
            {            
            }
    
            public ViewProfileController(UmbracoContext umbracoContext) : base(umbracoContext)
            {
            }
    
            public override ActionResult Index(RenderModel model)
            {
                //Get profileURLtoCheck
                string profileURLtoCheck = Request.RequestContext.RouteData.Values["profileURLtoCheck"].ToString();
    
                //Create a view model
                ViewProfileViewModel profile = new ViewProfileViewModel();
    
                //Check we have a value in the URL
                if (!String.IsNullOrEmpty(profileURLtoCheck))
                {
                    //Try and find member with the QueryString value ?profileURLtoCheck=warrenbuckley
                    Member findMember = Member.GetAllAsList().FirstOrDefault(x => x.getProperty("profileURL").Value.ToString() == profileURLtoCheck);
    
                    //Check if we found member
                    if (findMember != null)
                    {
                        //Increment profile view counter by one
                        int noOfProfileViews = 0;
                        int.TryParse(findMember.getProperty("numberOfProfileViews").Value.ToString(), out noOfProfileViews);
    
                        //Increment counter by one
                        findMember.getProperty("numberOfProfileViews").Value = noOfProfileViews + 1;
    
                        //Save it down to the member
                        findMember.Save();
    
                        //Got the member lets bind the data to the view model
                        profile.Name                    = findMember.Text;
                        profile.MemberID                = findMember.Id;
                        profile.EmailAddress            = findMember.Email;
    
                        profile.Description             = findMember.getProperty("description").Value.ToString();
    
                        profile.LinkedIn                = findMember.getProperty("linkedIn").Value.ToString();
                        profile.Skype                   = findMember.getProperty("skype").Value.ToString();
                        profile.Twitter                 = findMember.getProperty("twitter").Value.ToString();
    
                        profile.NumberOfLogins          = Convert.ToInt32(findMember.getProperty("numberOfLogins").Value.ToString());
                        profile.LastLoginDate           = DateTime.ParseExact(findMember.getProperty("lastLoggedIn").Value.ToString(), "dd/MM/yyyy @ HH:mm:ss", null);
                        profile.NumberOfProfileViews    = Convert.ToInt32(findMember.getProperty("numberOfProfileViews").Value.ToString());
    
                    }
                    else
                    {
                        //Couldn't find the member return a 404
                        return new HttpNotFoundResult("The member profile does not exist");
                    }
                }
                else
                {
                    //Couldn't find the member return a 404
                    return new HttpNotFoundResult("No profile URL parameter was provided");
                }
    
    
    
                //Return template with our profile model
                return CurrentTemplate(profile);
            }
        }
    }
    

    And here is my view to render the profile out of the member in my site chrome:

    @using CWSStart.Web.CWSExtensions
    @using CWSStart.Web.Models
    @inherits Umbraco.Web.Mvc.UmbracoViewPage<ViewProfileViewModel>
    
    @{
        Layout = "~/views/CWS-Master.cshtml";
    }
    
    
    <h2>@Model.Name</h2>
    
    <div class="row">
        <div class="col-9">
            <h3>Email: @Model.EmailAddress</h3>
            <h3>Description: @Model.Description</h3>
    
            <h3>LinkedIn: @Model.LinkedIn</h3>
            <h3>Skype: @Model.Skype</h3>
            <h3>Twitter: @Model.Twitter</h3>
    
            <h3>Member ID: @Model.MemberID</h3>
            <h3>No Of Logins: @Model.NumberOfLogins</h3>
            <h3>No of profile views: @Model.NumberOfProfileViews</h3>
            <h3>Last Login Date: @Model.LastLoginDate</h3>
        </div>
        <div class="col-3">
            <img src="@(Model.EmailAddress.GetGravatarUrl(250, string.Empty))" alt="@Model.Name" class="img-circle"/>
        </div>
    </div>
    
  • Dave Woestenborghs 3504 posts 12133 karma points MVP 8x admin c-trib
    Aug 02, 2013 @ 12:41
    Dave Woestenborghs
    1

    Hi Warren,

    Try to inherit your ViewProfileModel from RenderModel.

    It is stated somewhere on this page : http://our.umbraco.org/documentation/Reference/Mvc/custom-controllers

  • Anthony Dang 1404 posts 2558 karma points MVP 3x c-trib
    Aug 02, 2013 @ 12:43
    Anthony Dang
    0

     

    This is how I did it in uCommentsy..

    In the layout:

    @Html.Action("Index", "uCommentsyContactFormSurface", new { Model.CurrentPage })

     

        public class uCommentsyContactFormSurfaceController : SurfaceController
        {
            public ActionResult Index(IPublishedContent currentPage)
            {
                // do some stuff
    return PartialView("/Views/Partials/uCommentsy/Forms/uCommentsyFormContact.cshtml", new ContactFormModel()); }

    Hope that helps.

     

     

     

  • Warren Buckley 2106 posts 4836 karma points MVP ∞ admin hq c-trib
    Aug 02, 2013 @ 12:43
    Warren Buckley
    0

    How do I inherit from RenderModel on my custom view model? Tried just doing this but this is not quite right

    public class ViewProfileViewModel : RenderModel { [HiddenInput(DisplayValue = false)] public int MemberID { get; set; }

        public string Name { get; set; }
    
        [DisplayName("Email address")]
        public string EmailAddress { get; set; }
    
        [HiddenInput(DisplayValue = false)]
        public string MemberType { get; set; }
    
        public string Description { get; set; }
    
        public string Twitter { get; set; }
    
        public string LinkedIn { get; set; }
    
        public string Skype { get; set; }
    
        public DateTime LastLoginDate { get; set; }
    
        public int NumberOfLogins { get; set; }
    
        public int NumberOfProfileViews { get; set; }
    

    }

  • Morten Bock 1867 posts 2140 karma points MVP 2x admin c-trib
    Aug 02, 2013 @ 12:47
    Morten Bock
    0

    Why do you have the

    @inherits UmbracoViewPage<dynamic>
    

    In the master? Seems to me it serves no purpose?

    You could go another direction alltogether, and make your navigation a SurfaceController instead, and the call Html.Action()? That would solve the issue?

  • Warren Buckley 2106 posts 4836 karma points MVP ∞ admin hq c-trib
    Aug 02, 2013 @ 12:55
    Warren Buckley
    0

    I have removed the inherits in the top level template/view as you suggested.

    Yes it seems it may be the route I have to go, but seems a bit overkill to me to have a surface controller to do a simple navigation.

    If I can do it without a surface controller that would be fantastic if anyone can come up with a solution.

    Cheers,
    Warren

  • Amir Khan 1282 posts 2739 karma points
    Nov 20, 2013 @ 19:23
    Amir Khan
    0

    Any resolution on this? I'm trying to get a property from a node with your Warren's new starter kit and am running into the same issue.

Please Sign in or register to post replies

Write your reply to:

Draft