After searching for a long time 'Cannot bind source type... to model type Umbraco.Core.Models.PublishedContent.IPublishedContent', I thought I could share what I found out about it and how to solve (or rather workaround).
Context: a surfacecontroller with an action returning a view with a model als parameter:
public IActionResult MyAction()
{
MyModel myModel=....;
return View(myModel);
}
This gave me the IPublishedContent error.
I found somewhere on the forum to inherit the view from UmbracoPageView < MyModel > , but that didn't work.
Reason: my view inherits from a master layout which seams to also expect by default an IPublishedContent as Model (even if you don't use there). If I use the view without the master layout, then it works (but looks ugly of course).
Solution:
I don't pass my model to the view as a parameter, but instead I put it in the ViewBag.
A SurfaceController is usually used to handle the posting of a form in Umbraco, (or in previous versions, rendering a Child Action within a template with a PartialView).
In V9 and .net 5, ChildActions have gone, and been replaced by ViewComponents...
but SurfaceControllers still exist in V9 to handle those form submissions.
If you want to work with Umbraco following an MVC pattern, eg a request comes in for a page, it's diverted or hijacked via a Controller and then a View/Template is returned (that inherits a master layout). Then you can use a techique called RouteHijacking:
Which involves creating a controller that inherits from RenderController instead of SurfaceController, and gives you full control over the request, the model that is built up and the view/template that is returned...
thanks for trying to clarify, the documentation is not very clear on how choosing the proper controller type.
I am indeed looking to use a controller that is independant from umbraco content or in other words a conventional mvc controller 'hosted' in or under umbraco.
I've tried the custom controllers concept as you suggested, but I can't make it working. I can compile and run the site but when a navigate to my custom controller I get a 404 error.
I can think of 2 possible reasons:
the path I'm using: /MyRender => is this correct or is there a specific path to use like with surface controllers?
for some parameters in the constructor, I have different possibiblities proposed by intellisence for the namespace to use, maybe I'm choosing the wrong ones.
Error I'm getting:
Page not found
No umbraco document matches the URL '/MyRender'.
SurfaceControllers - (autorouted) - designed to handle form posts on
the 'surface' of an existing controller
RenderControllers - (autorouted) - a convention based method to hijack and extend Umbraco's implementation of the MVC pattern for a content request.
UmbracoPageController - (manually routed) - used to manually route a
request through Umbraco, with an associated IPublishedContent item to
provide an Umbraco Context to the request.
Possibly best explanation is in the github repo conversation for the PR that implemented these controllers in V9:
So it depends on what you are trying to do, the simplest way is to hijack a request, if a content item has Document Type: LandingPage, then a RenderController called LandingPageController, then all requests to that page will be sent via your LandingPageController. If your route is separate to Umbraco Content then you can manually map a route using an UmbracoPageController see:
Still not out of the woods ... I checked the links you mentioned, but couldn't make something working out of it. Probably missing some small detail.
I gathered some possiblities in an overview but unfortunatly can't paste it here. This is a picture of it, maybe you can complete it of correct the errors:
The routing convention for a RenderController is interesting to describe!
Essentially if you create a controller called LandingPageController that inherits from RenderController, that controller will be hit for any request that goes to an Umbraco content item based on a DocumentType with alias LandingPage.
So if you have a site with a Homepage, and under that a page based on the Landing Page document called 'Our Services' then the request for /our-services will be routed via the LandingPageController.
If you have another LandingPage called 'Our Services' then the request to /our-services would be automatically routed via the LandingPageController...
If you had a Contact Us page, using a ContactPage document type that woudn't be routed via the LandingPageController but you could create a ContactPageController to 'hijack' that request...
With the UmbracoPageController this isn't tied to the structure and the urls of the site in the same way that the RenderController is...
... this is much more like a standard MVC controller...
But here's the subtle thing, if you aim to return an Umbraco Template (View) from the controller, and that View uses the same master layout view, you might have code in that layout that calculates the Site Navigation or a Breadcrumb, based on the context of the 'current page'...
... Therefore this kind of route NEEDS an Umbraco Published Content item as Context... but if your MVC Controller returns views that don't share layout and partial views with your Umbraco site, then yes, you can just map a standard MVC Controller...
So if I had a Products Database outside of Umbraco, and each product needs to have a page on the website, but it's details are not in Umbraco, then I might create a 'ProductsSection' node in Umbraco, and hijack requests to this page using a RenderController to build up a viewmodel with a list of Paged Products, drawn from the database.
So it might have a url of /our-products/
and then I might map a route to a VirtualProductController inheriting from UmbracoPageController
endpoints.MapControllerRoute(
"My virtual product controller",
"/our-products/product-{id},
new {Controller = "VirtualProductController", Action = "Index", Id={id}})
.ForUmbracoPage(MyProductsSectionNode);
now when a request came in for /our-products/product-123
it would route to my custom controller, I could read the 123, go off to the database pull back the details from the db and build a viewmodel that then has the context of the Our Products node for any code in the site layout and common partials, so it would be 'as if' the product was in Umbraco...
It's not the only way to do that scenario, but hopefully gives you a scent as to what the difference is between the RenderController (gets it's umbraco context from the route it hijackcs) and the UmbracoPageController (you have to give it a context).
As the context you give it just needs to implement IPublishedContent, you don't have to give a context that is in Umbraco, you can give it any model that implements IPublishedContent, it's just most often easier to create a dummy node in the Umbraco content tree to do so!
Surfacecontroller its view and their model
After searching for a long time 'Cannot bind source type... to model type Umbraco.Core.Models.PublishedContent.IPublishedContent', I thought I could share what I found out about it and how to solve (or rather workaround).
Context: a surfacecontroller with an action returning a view with a model als parameter:
This gave me the IPublishedContent error.
I found somewhere on the forum to inherit the view from UmbracoPageView < MyModel > , but that didn't work.
Reason: my view inherits from a master layout which seams to also expect by default an IPublishedContent as Model (even if you don't use there). If I use the view without the master layout, then it works (but looks ugly of course).
Solution: I don't pass my model to the view as a parameter, but instead I put it in the ViewBag.
If anyone has a better solution, please stand up!
Hi Declercq
A SurfaceController is usually used to handle the posting of a form in Umbraco, (or in previous versions, rendering a Child Action within a template with a PartialView).
In V9 and .net 5, ChildActions have gone, and been replaced by ViewComponents...
but SurfaceControllers still exist in V9 to handle those form submissions.
If you want to work with Umbraco following an MVC pattern, eg a request comes in for a page, it's diverted or hijacked via a Controller and then a View/Template is returned (that inherits a master layout). Then you can use a techique called RouteHijacking:
https://our.umbraco.com/Documentation/Reference/Routing/custom-controllers
Which involves creating a controller that inherits from RenderController instead of SurfaceController, and gives you full control over the request, the model that is built up and the view/template that is returned...
Is that more like what you are after?
regards
Marc
Hi Marc
thanks for trying to clarify, the documentation is not very clear on how choosing the proper controller type.
I am indeed looking to use a controller that is independant from umbraco content or in other words a conventional mvc controller 'hosted' in or under umbraco.
I've tried the custom controllers concept as you suggested, but I can't make it working. I can compile and run the site but when a navigate to my custom controller I get a 404 error.
I can think of 2 possible reasons:
Error I'm getting: Page not found No umbraco document matches the URL '/MyRender'.
Hi Declercq
Yes, it's the names that always confuse me!
There are
SurfaceControllers - (autorouted) - designed to handle form posts on the 'surface' of an existing controller
RenderControllers - (autorouted) - a convention based method to hijack and extend Umbraco's implementation of the MVC pattern for a content request.
Possibly best explanation is in the github repo conversation for the PR that implemented these controllers in V9:
https://github.com/umbraco/Umbraco-CMS/pull/9821
So it depends on what you are trying to do, the simplest way is to hijack a request, if a content item has Document Type: LandingPage, then a RenderController called LandingPageController, then all requests to that page will be sent via your LandingPageController. If your route is separate to Umbraco Content then you can manually map a route using an UmbracoPageController see:
https://our.umbraco.com/Documentation/Reference/Routing/custom-routes#custom-routes-within-the-umbraco-pipeline
Hope that helps!
regards
Marc
Hi Marc,
Still not out of the woods ... I checked the links you mentioned, but couldn't make something working out of it. Probably missing some small detail.
I gathered some possiblities in an overview but unfortunatly can't paste it here. This is a picture of it, maybe you can complete it of correct the errors:
Hi Declerca
The routing convention for a RenderController is interesting to describe!
Essentially if you create a controller called LandingPageController that inherits from RenderController, that controller will be hit for any request that goes to an Umbraco content item based on a DocumentType with alias LandingPage.
So if you have a site with a Homepage, and under that a page based on the Landing Page document called 'Our Services' then the request for /our-services will be routed via the LandingPageController.
If you have another LandingPage called 'Our Services' then the request to /our-services would be automatically routed via the LandingPageController...
If you had a Contact Us page, using a ContactPage document type that woudn't be routed via the LandingPageController but you could create a ContactPageController to 'hijack' that request...
With the UmbracoPageController this isn't tied to the structure and the urls of the site in the same way that the RenderController is...
... this is much more like a standard MVC controller...
In that you have to provide the routing pattern for it: (see https://our.umbraco.com/Documentation/Implementation/Custom-Routing/#custom-mvc-routes)
But here's the subtle thing, if you aim to return an Umbraco Template (View) from the controller, and that View uses the same master layout view, you might have code in that layout that calculates the Site Navigation or a Breadcrumb, based on the context of the 'current page'...
... Therefore this kind of route NEEDS an Umbraco Published Content item as Context... but if your MVC Controller returns views that don't share layout and partial views with your Umbraco site, then yes, you can just map a standard MVC Controller...
So if I had a Products Database outside of Umbraco, and each product needs to have a page on the website, but it's details are not in Umbraco, then I might create a 'ProductsSection' node in Umbraco, and hijack requests to this page using a RenderController to build up a viewmodel with a list of Paged Products, drawn from the database.
So it might have a url of /our-products/
and then I might map a route to a VirtualProductController inheriting from UmbracoPageController
endpoints.MapControllerRoute( "My virtual product controller", "/our-products/product-{id}, new {Controller = "VirtualProductController", Action = "Index", Id={id}}) .ForUmbracoPage(MyProductsSectionNode);
now when a request came in for /our-products/product-123 it would route to my custom controller, I could read the 123, go off to the database pull back the details from the db and build a viewmodel that then has the context of the Our Products node for any code in the site layout and common partials, so it would be 'as if' the product was in Umbraco...
It's not the only way to do that scenario, but hopefully gives you a scent as to what the difference is between the RenderController (gets it's umbraco context from the route it hijackcs) and the UmbracoPageController (you have to give it a context).
As the context you give it just needs to implement IPublishedContent, you don't have to give a context that is in Umbraco, you can give it any model that implements IPublishedContent, it's just most often easier to create a dummy node in the Umbraco content tree to do so!
regards
marc
is working on a reply...