Mvc as rendering engine

Topics: MVC
Mar 5, 2011 at 4:55 PM

Hey all Mvc lovers! im working on a pure mvc implementation of the rendering. For that i have some questions about conventions. I'm not that much into Mvc myself, so im wondering how to map controllers and views when requesting a page.

Lets say, we browse http://omnicorp.com/somepage/someotherpage.aspx. What will the controller and view be for this request?

Im thinking something like this

  • Page type in C1 backend will map to a controller. Say, Page type is Content, then the controller will be Content
  • Template will map into the view. So if this page has a template named ContentUltraWide a view with the name ContentUltraWide will be used

For the view this could mean that either

  1. Content of the template in C1 is ignored, page is only rendered according to markup in the ContentUltraWide view
  2. Content from the template in C1 should somehow be merged into the View defined in either some aspx of cshtml file

What do you all think? Please come with suggestions and input. 

Mar 5, 2011 at 6:27 PM

So far i've bypassed the Templates inside C1 since the MVC convention dictates a relationship between Controller a Views. Instead i've made a HtmlHelper that fetched the PlaceHolder content for the page in question and renders it like this

Html.RenderPlaceHolder("contentplaceholder")

where contentplaceholder is the id the given placeholder has in the used Template inside C1. This will of course execute all c1 functions as well.

This means that a simple razor page for rendering a page from C1 looks like this

@model CompositeC1Contrib.Web.Mvc.PageModel
@using CompositeC1Contrib.Web.Mvc

<html>
<head>
   <title>@Model.Document.Title</title>
</head>
<body>
   <h1>@Model.Document.Title<h1>
   <div class="content">
      @Html.RenderTemplatePlaceHolder("contentplaceholder")
   </div>
</body>
<html>

Mar 5, 2011 at 11:09 PM

It's so exciting. Keep up the good job.

Mar 6, 2011 at 1:59 AM

you don't have any more input than that.... :( hehe, just kidding!

I would like to hear from someone who works with MVC daily though. I know Composite C1 has the MVC player but works locally on a single page and doesn't integrate if want a 100% mvc application.

One thing that is per default in my implementation is of course extensionless urls. I'm therefor also interested in hearing whether people want to be able to define actions. Lets say we have a page in C1 that maps into the url http//omnicorp.com/page/news

Should we be able to do

  • http//omnicorp.com/page/news -> Action: Index
  • http//omnicorp.com/page/news/list -> Action: List
  • http//omnicorp.com/page/news/view/123 -> Action: View, ID: 123

This would work by backtracking the url until we find a valid page in C1 and the leftover is used for resolving action and eventually id

Or what kind of features do we actually want?

Mar 6, 2011 at 9:35 AM

So far so good. :)

The URLs you've provided is fine. Of course as we know in a normal MVC application it can be ~/controller/action/... and you can see that there is no Page. Although the URL can be continued and complicated like ~/news/search/title/test/date/start/23/1/2011/ends/30/1/2011 :D but these are all parameters and can be well handled.

Thinking of one who is developing over MVC the best approach would be to create the MVC application completely in VS and then have it imported to C1. Placing a MVC Player on each page and then integrating can be a pain as you mentioned. The process can go like this : after you have created the mvc application then you should be able to copy & paste the MVC views, controller, models and all the classes to the root (or some where else) folder of C1 console. If there is not a page named News in the C1 website then C1 engine should look for a MVC controller named NewsController and then render the view based on the input having the rendered content placed in the template which is mentioned by the view. This way converting your MVC application to C1 MVC will be as easy as pasting it and then everything works and can be controlled using C1 console. Do you think this is possible?

I also would like to place editable place holders in my MVC application for texts, static data and even packages and edit them later using C1 console.

Mar 6, 2011 at 1:10 PM
Edited Mar 6, 2011 at 1:11 PM

I fully agree with you that it should be possible to execute normal controllers outside of a C1 context and that is of course no problem since you can add all the custom routes you wants. Actually, the route for resolving a C1 page is just a normal route as well so no magic there. But isn't that possible today already? I mean, is there anything hindering you in running a MVC application in the same applicationside by side with C1 rendering via webforms?

Can you elaborate a bit more on your last comment, about placeholders. Is this the placeholders you add in the template in C1, that an editor can put content and functions into when editing a page that you are referring to? Or do you want Html Helpers for easy outputting of a Page's metadata, media-urls and global/page data?

Mar 6, 2011 at 3:19 PM
Edited Mar 6, 2011 at 4:46 PM

I've uploaded my code i have so far to http://compositec1contrib.codeplex.com/SourceControl/list/changesets. I've tested it against MVC3 using normal .aspx views and the new razor cshtml.

Its very easy to get up at running, all you need is three changes. Two of them in your web.config, besides the normal MVC changes of course, and in global.asax

  1. Under appSettings you add a key named useMvcForContentRendering and set the value to true
    <appSettings>
       <add key="useMvcForContentRendering" value="true" />
    </appSettings>
    
     
  2. Under modules you add the url-filter, this is so the outputted urls from C1 ran be recognized by the mvc route
    <modules>
       <add name="UrlFilter" type="CompositeC1Contrib.Web.UrlFilterModule, CompositeC1Contrib" />
       
       ... the rest of the C1 modules
    </modules>

  3. In global.asax you replace ALL the content with this line... this is so the correct route can be added if we're using Mvc rendering
    <%@ Application Language="C#" Inherits="CompositeC1Contrib.Web.CompositeC1Application" %>
    
     

And then you need the controllers and the views. The convention so far is that the PageType name in C1 is the name of the Controller. So say you only have one PageType called Default you would create a controller named DefaultController which inherits from CompositeC1Contrib.Web.Mvc.ContentController. So far there is only one default action named Index that ContentController implements and returns a View with the PageModel-model. This model has so far only one property named Document which is a reference to the IPage that represents the page being rendered.

In the view you will inherit from System.Web.Mvc.ViewPage<CompositeC1Contrib.Web.Mvc.PageModel> and you then have access to stuff like the page's title, description... not so damn interesting actually, but its a start. There is one extensionmethod to the HtmlHelper so far which can render the content of a page's placeholders, including the output of any functions the content may contain. This helper is used like this

Html.TemplatePlaceHolder("contentplaceholder")

where contentplaceholder is the id used in the Template in C1.

Its all quite simple actually, at least so far. The trick list in the ContentRoute class which you can see here http://compositec1contrib.codeplex.com/SourceControl/changeset/view/64233#1119262. Here we just try to resolve a C1 page from a given url. If we succeed we construct a MvcRouteHandler telling the system which controller and action to use. This has to be extended of course, if we want to dynamically resolve actions and arguments from the url, but its a start.

Mar 6, 2011 at 4:29 PM
Edited Mar 6, 2011 at 4:30 PM

So, for those of you who want to keep the existing C1 Url schema (/[lang]/website/page.aspx) i've update the ContentRoute so it can recognize these urls as well and execute it through MVC. Remember, MVC is not about extensionless urls, using .aspx in MVC is perfectly legal.

For this to work you need to remove the CompositeRequestInterceptor module, since it will do rewrite of the url so it doesn't end up in the routing engine.

<modules>
   <!-- <add name="CompositeRequestInterceptor" type="Composite.Core.Web.WebClient.Renderings.RequestInterceptorModule, Composite" /> -->
<modules>

You also need to disable transforming the C1 urls by the UrlFilter module, which is done by adding the following key to AppSettings section in web.config

<appSettings>
   <add key="useFriendlyExtensionlessUrls" value="false" />
</appSettings>
Coordinator
Mar 9, 2011 at 12:32 AM

@burningice when we get past the release crunch we will dive 'proper deep' into this thread. This is definitively interesting and part of the job getting there is fixing our routing system (how url's look and what can control them) which is a highly requested feature. This would be a great workshop topic - we could consider doing some meetups online for those interested?

Jun 21, 2011 at 8:34 PM

@burninggice, great work on this so far!  I would love to see this implemented into Composite C1.  I am testing out several CMS solutions right now for our MVC3/Razor website and this is the only thing holding me back from choosing CompositeC1 as our CMS.  It's not very practical to create a page in the admin side and then add an mvcPlayer to each page.

@mawtex, did you ever dive "proper deep" into possibly implementing this?

Jun 21, 2011 at 8:48 PM

Since revision 7423 where rewriting was replaced with routing, and with revision 7871 where proper url configuration is now native supported and unrecognized parts are "swallowed" and accessible by an API, i was thinking of looking into this once again.

If you want you're welcome to come with input of you would expect the mapping between Page types and templates to Controllers and how extra url parts should map to actions.

 

At the moment im just waiting for patch 9737 to be applied so i can make a new release of Contrib without breaking support for Masterpages.

Jun 21, 2011 at 9:04 PM
Edited Jun 21, 2011 at 9:05 PM

definitely... i'm just now testing out the CMS so i'll hop back on here and give some feedback after I give it a thorough run-through.  I do however really like the feedback that Aboo gave (see above) back in March.  being able to run mvc views natively and still being able to manage the content of each View via the console would be stellar.

Coordinator
Aug 17, 2011 at 5:21 PM
Edited Aug 17, 2011 at 5:22 PM

We have life on the feature request for "Pure MVC" - the MVC crowd is very welcome to add their wishes here:

http://compositec1.codeplex.com/workitem/591

Nov 8, 2011 at 4:12 PM

I'll hijack this thread as it's the most fitting for my problem.

I want to have a pure MVC rendering capability. With the current release this "works" out of the box but XDocument always needs a <html> tag before parsing so I'm in the process to find some ways to circumvent this. 

Which leads me straight to my questions: 

- Why is XDocument so deeply integrated in C1? I haven't worked with it but I find it odd as it's rather unflexible. 
- Why do I always need a full <html><head>...</html> in the content? (I mean the Source view directly in the CMS)

Basically what I'm trying to do is working in the same way with C1 as I'm working with a standard MVC application. Haveing a "_Viewstart.cshtml" and "Layout.cshtml" that renders my views then.

I'd have nothing against using the built in template system but I like to have programmatic control over head and body. 

And I've found a value in web.config which is called xhtmlConformance with the mode="Strict" . Has this something to do with my problem? What are the values I can set? Is there a documentation with the web.config values for C1? 

Phew, lot of questions. I hope someone can help me out. I'm enjoying working with C1 more and more and this is the only problem that is left.

Greetings and thanks,
Thomas

Nov 8, 2011 at 6:29 PM

hi thomas! always good to get some input! _Viewstart.cshtml and Layout.cshtml is definitely possible. But MVC is so much that just about the views. How do you imagine Controllers and Models should work with C1?

If its just because you like Razor instead of Xslt or Webforms UserControls you should look at http://docs.composite.net/ASP-NET/Razor-Functions

Nov 8, 2011 at 7:15 PM

hey burningice!

No it's not just about Razor. We made the change from .NET 3.5 to MVC for our last 2 projects and honestly, I never want to go back again. :)

Models wouldn't be a problem. C1 already creates a model for every data type and from what I see it's exactly what I want. Haven't really tested it because I've got the layout/template problem to figure out. It's kind of a bummer that the Entity Framework is so difficult to get working but I'm perfectly fine with traditional LINQ queries.

Working with controllers would be no problem either.
I mean, the standard workflow would be that you create a page in C1 and insert the MVC player which references directly to the controller via the route. You'll miss subpages or to be precise the controller actions but I thought I'd just put everything I need into a page data type to configure everything that the client needs. It certainly conflicts with the page driven concept of C1 but it's a workaround that works for me.

I still have some things to figure out like what if the client wants an HTML input field for just some mildly styled text.

But back to the layout problem as I'm really interested in your approach. I took a look at the Contrib project but couldn't compile it because some methods are not public anymore. I guess it's not really up to date, is it?



 

Nov 8, 2011 at 7:20 PM
Edited Nov 8, 2011 at 7:23 PM

The Renderings.Mvc project is not up to date at all... my problem was how to map Controllers and Actions into the C1 world. Would you want a controller per page type/template, or should it configurable as Metadata on each page? and what about Actions, should everything be Index or would we want Edit/Delete/whatever actions as well, and how would they be invoked?

I agree that regarding the Model its not so difficult, Razor Functions already incorporates some of those concepts by having access to the page-object being rendered etc. And views is something you would implement all by yourself by returning whatever you want from the Controllers.

With the risk of being stamped as a copycat, maybe i should look into how Umbraco tries to solve this with controllers and actions in their upcoming release. You can never get enough of inspiration.

Nov 8, 2011 at 7:50 PM

The question is, do you really need to map all the actions in the C1 tree view as well? I mean, when you split all the CMS into groups you could say that there are designer driven CMS and programmer driven CMS. I don't mean this negative but there are more than enough designer driven CMS that completely lacks features and/or flexibility. Programmer driven CMS on the other hand are very very rare. Especially with MVC this gap could be filled but the .NET MVC is too young to have a fully fletched out CMS. On another note, .NET CMS are already pretty rare.

I'd design every page type/template into a group. So to give an example you'd have a single page called 'Account' with a MVC player node either in the content or directly in the page template. (Haven't tested this out if this already works.) The enduser wouldn't see all the different actions like 'Register, 'Login' or 'Detail' but to come back to my initial point, do you really need this in the CMS to see when you can configure everything you need via a page data type? The way I think about this problem is definitely programmer driven.

I didn't have enough time to look at Umbraco, maybe it's the better choice when it comes to MVC.

Nov 8, 2011 at 8:10 PM
Edited Nov 8, 2011 at 8:23 PM

I 100% agree that a programmer driven Cms is preferable. 

Umbraco 5 is no way finished, its still in Alpha mode so i wouldn't use it for anything else than playing around. After looking at the source for a few minutes it looks like they are used the name of PageType (Node Alias in Umbraco terms) to map a route a controller.

I have to admit that i don't know much about Mvc, but i know that when the CMS has to map a incoming request to a handler it has to specify both a Controller and a Action for Mvc to execute. Thats whats basically going on here http://compositec1contrib.codeplex.com/SourceControl/changeset/view/f42445403141#Rendering.Mvc%2fWeb%2fMvc%2fContentRoute.cs in the section 

 

using (var data = new DataConnection())
                {
                    var pageType = data.Get<IPageType>().Single(p => p.Id == page.PageTypeId);
                    var routeData = new RouteData(this, new MvcRouteHandler());

                    routeData.DataTokens.Add("ID", page);
                    routeData.Values["controller"] = pageType.Name;
                    routeData.Values["action"] = "Index";
                    routeData.Values["ID"] = page.Id;
                    routeData.Values["dataScope"] = dataScope;

                    return routeData;
                }

 

But lets take the above example, when requesting the url http://mydomain.com/Account, we figure out that Account maps to a page with id 1234 and pagetype "Content". Now Mvc will go and find a controller named "ContentController" and execute its Index action, where its up to you to return a View that can render some content for this particular page.

What if the page contained a form where you would like to execute a Save action when submitting the form, how would that work? Would decorating a second Index action with a HttpPost attribute actually do the trick for ie. submitting a form and calling another action on the Controller?

Nov 8, 2011 at 8:15 PM

i would be more than happy to have a session with you where you could explain more in detail how things should/could work in practice and i could code some proof-of-concept that would show it running in real life.

Nov 9, 2011 at 1:24 AM

Yeah, I agree. Umbraco is far from finished and integrating controllers is not documented at all so I have not the slightest clue how to do it.

I'll just give some examples, sorry if you already know this stuff:

The default format is always /controller/action/id. You can set default values for controller, action and id directly when you register the route. So to give a few examples

/Account -> controller: Account action: Index
/Account/Login -> controller: Account action: Login
/Account/Detail/342 -> controller: Account action: Detail Id: 342 

The routing is very straightforward and works out of the box. 

If you have a form, let's say Login, you'd have 2 actions, both called Login but one has the HttpPost attribute and gets the posted values directly or in form of a model as a input variable.

From what you've written you seem to get what MVC is about. It's not an overly complex design pattern, one thing why it's so cool and easy to work with so I'm not sure how much more I can actually teach you. ;)

I've looked at so many CMS now and none implements MVC in a way that satisfies me. Either it has no model, no CRUD, no controller support and so on. Something crucial is always missing. I'll stick with C1 now as it seems it's the only CMS that has the best base to work with.

Some more ideas how to better integrate MVC in the future:

- First of, have a page that integrates the MVC player which references either the controller or even better, without the player and use the pageType.Name as in your above example
- The child pages are the actions, with that you could have a data type for every view/action and you could enable the feature to translate different sections of the page
- Having a design view for the page for translation/content

To further explain my last point. I was working now for around 3 years with KenticoCMS and what was great about it was that we built textbox/textarea controls that you could place in the page. The editor only has to fill in the textboxes to translate the page and it was easy to understand because the design view reflected the  actual layout. So for example the headline of the page was just a big textbox that he had to fill out. (It's so late and I get the feeling I can't even explain the simplest things anymore ^^)
Having content placeholders is cool too but not every area is so big. Sometimes it's just the headline or a form field value that you want to translate.  

Coordinator
Nov 9, 2011 at 2:01 AM

Let me play the devils advocate here...

I have problem figuring out "who should be in control of the URL":

  • In MVC it is a routing rule based thing, typically something a dev define.
  • In a CMS like Composite C1 the user editing pages can move, create and rename pages at any depth and the user is in control of routing (at least down to the page level).

I would really hate to see users loose the ability to shape and name their website URL's - I would see it as a step backwards. Question is if the two can live happily side by side?

We tried this when we created the MVC Player - the route leading to the page is "owned" by the user (could be a dev, but it would require creating a CMS page). From that point on the MVC Player can take over and own "the rest" of the route.

Basically CMS have for years been doing what MVC does (I would argue that a well designed, pluggable CMS is a far better MVC than MVC) and hence "CMS" would have to yield this control for "MVC" to take it. In Composite C1 the stuff that happen between request and final page is pretty much what you want - you may want to hook in at different levels, but the overall pattern is there. Taking care of content page routing, assembling pages/templates/functionalities etc. is pretty much why you want a CMS.

These are open questions - I simply can not wrap my head around how the two 'patterns' can properly coexist at the same level. And if one is do yield to the other, how would UX be affected when editing pages etc. I am slowly getting to the realization that "ASP.NET MVC by @haacked from Microsoft" is very popular and people want to use it, but it is a platform and to make it usable you would basically rebuild a CMS once again. That CMS would yet again put routing power in the hands of the editing user etc. (or they would revolt I expect).

So what would we gain? What are the important features we introduce? "ASP.NET MVC by @haacked from Microsoft" is not a feature. If there are such features, could those important features be obtained much easier and perhaps way more elegantly than the MVC pattern would yield?

Nov 14, 2011 at 11:43 AM

I agree with you, the user should have the control over the routes.

I have rewritten some things now (not too many) in the C1 core and the MVCPlayer. Basically what I can do now is using MVC in the same way as I'm used too.

My current setup is:

- I have a page in C1 with a MVC player. In my case I have /FB for a facebook page. :)
- In this page the MVC player has the path to /Home. I'd call him the same but then you get the problem with redirect loops
- Everything behind that is a conventional MVC app. I have a layout, the homeController and my Views.
- The standard route is  still {controller}/{action}/{id} and it could possibly stay that way


The biggest change is in the rendering where I had the problem that the C1 template gets rendered and where the placeHolder is, everything was replaced by the layout. Quickly said, I had two full <html> pages nested and what I did was simply merging the body and head. It's not fail proof code and currently it just fits my personal need. :)

The second change was that I had a big problem with routes. So what is different now? Well, to be specific it's just the way the Request Path gets rewritten.

Something like /FB is transformed into /Home. Even routes are compatible now so /FB/Detail/3 transforms to /Home/Detail/3. It's definitely a hack but I'm happy with it right now. :)