Would like to display a content item in my custom controller.

Topics: Customizing Orchard, Writing modules
Nov 30, 2011 at 7:18 PM

One of the things that we would like to be able to do is have orchard custom content display in certain areas inside our custom controllers. Basically I'm looking for the easiest way to take a content item I grab off the content manager and convert it into the HTML that would normally render were I viewing the content item. This would include the dashed box and edit link if I were an admin but not for regular users. Is there a method in orchard for this?

Nov 30, 2011 at 7:25 PM

Yes, you can see how it does it in Orchard.Core/Contents/Controllers/ItemController.cs:

        public ActionResult Display(int id) {
            var contentItem = _contentManager.Get(id, VersionOptions.Published);

            if (contentItem == null)
                return HttpNotFound();

            dynamic model = _contentManager.BuildDisplay(contentItem);
            return new ShapeResult(this, model);
        }

 

Nov 30, 2011 at 10:37 PM

So if I wanted to get a part specified by a string identifier would the simplest way be to have a controller with an action that took the name identifier and use Html.RenderAction on that controller action?

Nov 30, 2011 at 10:57 PM

Rather than trying to pull the part out and render it alone (which will be difficult and require copy/pasting a lot of ContentDisplay.cs), you can make up a new DisplayType as follows:

            dynamic model = _contentManager.BuildDisplay(contentItem,"MyCustomDisplayType");

Now in Placement.info you can just hide all parts you don't want (and customise the display of the parts you're after):

<Match DisplayType="MyCustomDisplayType">
  <Place Parts_RoutableTitle="-"/>
  <Place Parts_Common_Body="-"/>
  <Place Parts_MyPartShape="Content:1;alternate=Parts_MyPartShape_MyOverride"/>
</Match>

You can also use a Content.MyCustomDisplayType.cshtml override for the main Content template.

There's a lot more you can do with Placement (and I'm working on a module that extends it even further by allowing you to send in all kinds of custom data to invent more complex Match terms).

Nov 30, 2011 at 11:28 PM

Sorry I mistyped. I don't want to render the part alone. I want to render the entire content item as it would be rendered normally. My only problem is that I want to render it in the context of a custom controller. My idea was to make a controller to do the rendering and return the HTML like this.

        public ActionResult Render() {
 
            var promotion = _contentManager.Query().ForType(new[] { "Promotion" }).List().FirstOrDefault();
            return new ShapeResult(this, _contentManager.BuildDisplay(promotion));
        }

Note: Promotion is just a random custom part I had lying around the actual logic would be more complex

That seems to work fine when I put a route in the route table like this

                                new RouteDescriptor {
                    Priority = 1,
                    Route = new Route(
                        "ManagedContentBlock",
                        new RouteValueDictionary {
                            {"area""MyDomain.MyProject.Member"},
                            {"controller""ManagedContentBlock"},
                            {"action""Render"}
                        },
                        new RouteValueDictionary(),
                        new RouteValueDictionary {
                            {"area""MyDomain.MyProject.Member"}
                        },
                        new MvcRouteHandler())
                },

I can get there directly and see the item render (of course it's unthemed). For some reason though calling RenderAction in the view like this

@{Html.RenderAction("ManagedContentBlock""Render"new { area = "MyDomain.MyProject.Member" });}

is giving me this error.

 

Server Error in '/' Application.

The controller for path '/account' was not found or does not implement IController.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Web.HttpException: The controller for path '/account' was not found or does not implement IController.

Source Error:

Line 74: 
Line 75:             // fail as appropriate for MVC's expectations
Line 76:             return base.GetControllerInstance(requestContext, controllerType);
Line 77:         }
Line 78:     }

Nov 30, 2011 at 11:56 PM

Ok, you are confusing "Part" and "Item". A Content Item is what you are rendering here, not a part. When you do Query().ForType(new[] { "Promotion" }), what you are doing is pulling out all Content Items with a Content Type of "Promotion". There's no Part involved (the content item will itself be a collection of parts, however). Hope that makes sense :)

As to that error, I'm not quite sure why it's happening; maybe you just got your area name wrong? If your module is called "MyDomain.MyProject" then the area should also be "MyDomain.MyProject" - not sure why you added ".Member"?

Dec 1, 2011 at 12:12 AM

Yes sorry. I am still confusing them with my words but I know the difference in my head. It is an item of type promotion that I am trying to render (and it renders successfully if I go to \ManagedContentBlock)

 

Member is the name of that module one of several modules for the project we're working on.

Dec 1, 2011 at 12:33 AM

I'm not sure why that doesn't work (and RenderAction works in other contexts) although I wonder, do you have any security checks in your controller that could be misbehaving?

Another way to look at this is: why do you need to call RenderAction? Orchard is building the content item for you in one line of code anyway, do you really need to package this off to a separate action (with whatever performance overheads that will bring), or could you just generate the shape in the originating controller, i.e. wherever you're generating your view from in the first place? Orchard also has a powerful Shapes system, maybe you could just create a shape to package up your rendering code instead of doing it this way.

Dec 1, 2011 at 12:47 AM

That's actually what I wanted to do originally but I couldn't figure out how that would work. Would I use @Display in my view? Like add a content item onto my model and then call @Display and pass in the content item? Or do I need to make it into a shape first? I think I just don't fully understand the shape system and how and when I can turn something into HTML.

Dec 1, 2011 at 1:12 AM
Edited Dec 1, 2011 at 1:13 AM

Yep, make it into a shape first (using BuildDisplay) then pass that in on a model property. Then @Display(Model.MyShape) will do the trick. You can also instance ad-hoc shapes with @Display.MyShape(ContentItem:Model.SomeContentItem) - then create an IShapeFactoryEvents that builds the shape etc. - but for your purpose I think it's easier to just build the shape first.

Dec 1, 2011 at 1:37 AM

That's so awesome! It worked thanks!