3
votes

I'm trying to write a childaction function in a surface controller that gets called by a macro to render a PartialView.

I need in this function to gain access to my current page properties to then tweak the rendered PartialView with.

I got this from Jorge Lusar's code on ubootstrap and it works fine on the HttpPost ActionResult function :

var renderModel = (UmbracoRenderModel)ControllerContext.RouteData.DataTokens["umbraco"];
var currentPage = renderModel.CurrentNode.AsDynamic();

Problem is I've this error thrown on [ChildActionOnly] PartialViewResult function :

Unable to cast object of type 'System.String' to type 'Umbraco.Cms.Web.Model.UmbracoRenderModel'.
on 'var renderModel = (UmbracoRenderModel)ControllerContext.RouteData.DataTokens["umbraco"];'

Data in DataTokens["umbraco"] seems to change between the two functions. If I diplay DataTokens["umbraco"].ToString() on each one, here is what happens:

On [ChildActionOnly] public PartialViewResult Init() -> "Surface" is displayed.

On [HttpPort] public HandleSubmit(myModel model) -> "Umbraco.Cms.Web.Model.UmbracoRenderModel" is displayed.

Thanks for any advice here.

Nicolas.

7

7 Answers

6
votes

I am using Umbraco 6.0.4 and it is as simple as:

var currentNode = Umbraco.TypedContent(UmbracoContext.PageId);
2
votes

Same problem I have with getting Current Node Id in Surface Controller while Ajax Post, when we are loosing hidden value of uformpostroutevals.

Even if I'm trying to post this value, by taking it from form, rendered by

@using (Html.BeginUmbracoForm("<ActionName>", "<Controller Name>Surface"))

I Still have null in all properties of UmbracoContext, so looks like it is not initialized properly.

HOTFIX: I'm passing CurrentNodeId to each form which I'm sending by by Ajax:

In General master page I'm creating global javascript object:

<script type="text/javascript">
  var Global = {
    //..List of another variables which can be usefull of frontend
    currentNodeId: @CurrentPage.Id
  };
</script>

In any request it is easy to use Global.currentNodeId as one of params of data:

var sendData = {
    currentNodeId: Global.currentNodeId,
    // another params 
};

$.ajax({
    method: 'POST',
    data: JSON.stringify(sendData),
    contentType: 'application/json; charset=utf-8',
    url: '/Umbraco/Surface/<ControllerName>Surface/<ActionName>',
    dataType: 'json',
    cache: false
})

Please pay attention that it is only a hot fix and not a proper solution!

0
votes

To access your currentPage, you implement this constructor in your Controller

public class CommentSurfaceController : SurfaceController
{
    private readonly IUmbracoApplicationContext context;
    public CommentSurfaceController(IUmbracoApplicationContext context)
    {
        this.context = context;
    }
}

It uses umbracos dependency injection, to resolv the Context dependency, and makes it available to you, to use.

Check out the documentation, on a SurfaceController https://github.com/umbraco/Umbraco5Docs/blob/5.1.0/Documentation/Getting-Started/Creating-a-surface-controller.md

0
votes

Although I'd prefer to find a simpler way, I think I've found a workable solution. The key is to differentiate between whether the action method was invoked as a child action (generally via HTTP GET) or directly (via HTTP POST).

The following is a custom base class that exposes a single "CurrentContent" property, which can then be used by inheriting surface controllers.

using System.Web.Mvc;
using Umbraco.Cms.Web;
using Umbraco.Cms.Web.Surface;
using Umbraco.Cms.Web.Model;

namespace Whatever
{
    public abstract class BaseSurfaceController : SurfaceController
    {
        private object m_currentContent = null;

        public dynamic CurrentContent
        {
            get
            {
                if (m_currentContent == null)
                {
                    if (Request.HttpMethod == "POST")
                    {
                        m_currentContent = GetContentForSubmitAction();
                    }
                    else
                    {
                        m_currentContent = GetContentForChildAction();
                    }
                }
                return m_currentContent;
            }
        }

        // from Lee Gunn's response
        // http://our.umbraco.org/forum/core/umbraco-5-general-discussion/29178-In-a-controller-how-do-I-get-the-current-pages-hiveId?p=2

        private object GetContentForChildAction()
        {
            ViewContext vc = ControllerContext.RouteData.DataTokens[
                    "ParentActionViewContext"] as ViewContext;
            var content = vc.ViewData.Model
                    as global::Umbraco.Cms.Web.Model.Content;
            return content.AsDynamic();
        }

        // from Nicholas Ruiz
        // http://our.umbraco.org/forum/core/umbraco-5-general-discussion/30928-Surface-Controller-and-Current-Node-Properties

        private object GetContentForSubmitAction()
        {
            UmbracoRenderModel rm =
                    ControllerContext.RouteData.DataTokens["umbraco"] as UmbracoRenderModel;
            if (rm == null)
            {
                return GetContentForChildAction();
            }
            return rm.CurrentNode.AsDynamic();
        }
    }
}

Still, it seems like there should be a way easier way of doing this.

Bryan

0
votes

In Umbraco 7.2.1 I got this working using the ChildActionOnly attribute and passing the model to the partial view from the parent.

        [ChildActionOnly]
    public ActionResult InitializeDataJson(KBMasterModel model)
    {
        var pluginUrl = string.Concat("/App_Plugins/", KBApplicationCore.PackageManifest.FolderName);
        bool isAuthenticated = Request.IsAuthenticated;
        IMember member = null;
        if (isAuthenticated)
            member = UmbracoContext.Application.Services.MemberService.GetByUsername(User.Identity.Name);
        var data = new { CurrentNode = model.IContent, IsAuthenticated = isAuthenticated, LogedOnMember = member, PluginUrl = pluginUrl };
        var json = JsonConvert.SerializeObject(data, Formatting.None, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
        var kbData = new TLCKBData() { InitializationJson = json };
        return PartialView(kbData);
    }

Now the partial view code:

@model TLCKBData
<script>
    (function () {
        var data = JSON.parse('@Html.Raw(Model.InitializationJson)');
        tlckb.init(data);
    })();
</script>

And the parent view that renders the child action:

@section FooterScript {
    @{Html.RenderAction("InitializeDataJson", "KBPartialSurface", new { model = Model });}
}

Note: I am using strong models as I have route hijacked all my document types for a plugin I am developing, however if I was route hijacking and just using UmbracoTemplatePage models (default in Umbraco), then I would change the parameter on my child action to take just RenderModel or UmbracoTemplatePage.

Then I would pass model to it the same way.

Because it's a child action on the surface controller, the model that was already loaded in Index is just passed down to the child action. This prevents having GetContent code run twice in the pipeline.

The reason I went about doing this is I have some basic data I need to initialize my angular API layer with. Like whether it's authenticated, who the logged on member is, etc. Eventually I have some tags in there, categories, etc etc.

I also want to build the plugin as efficiently as I can and not have redundant logic. I figured all the information was on the Master views Model, why should I have to look it up again? That's when I figured out how to do this.

0
votes
var currentNode = Umbraco.TypedContent(UmbracoContext.PageId);

Or if your in a surface controller there is the object

var currentNode = CurrentPage;

(Go To Definition)

 //
 // Summary:
 //     Gets the current page.
 protected virtual IPublishedContent CurrentPage { get; }

Make sure you check for null first as there are a few instances where you can call the action without the currentPage context being resolved.

0
votes

Here is something that helped me overcome this.

After the jquery inclusion i added a custom header tag like below.

    <script type="text/javascript">
        $.ajaxSetup({
            headers: { 'umbraco-page-id': '@CurrentPage.Id' }
        });
    </script> 

Now each jquery post takes across the current umbraco page. you can access this custom header from the Request Header properties.