Last post Apr 11, 2008 04:13 PM by Kepler
Mar 30, 2008 11:45 PM|Kepler|LINK
After upgrading to preview 2, I noticed SwitchWriter is no longer available due to the removal of IHttpContext (why did we lose IHttpContext, btw?). I was using SwitchWriter quite effectively for my CMS to capture the html output of controller actions.
I started looking at the new RenderComponent method and it's nice, but falls short in one key area. It relegates the usage of component rendering to Views only. That may have been intentional, but it misses any scenario where the component to be rendered
is determined programmatically. CMS apps fall squarely into this category, but I can think of many other instances where the component to be rendered would be determined programmatically.
Bottom line - how can I capture the html output of a controller action from within a controller with very little overhead? My previous solution was lean and mean, using reflection to create the controller and calling ExecuteAction using a manually-created
ControllerContext with SwitchWriter to capture the output. I've seen some snippets from MvcContrib involving using the response output filters, but they seem pretty involved and I question their speed. I'm probably going to try to do what I did before, but
use a ComponentController for the actions and just return the RenderedHtml string. Basically, I'm going to try to duplicate the RenderComponent Html extension method, but do it from a controller.
I'm wishing I didn't have to hack this in.
Mar 31, 2008 12:57 AM|Kepler|LINK
Well, after looking at this further, I'd really like SwitchWriter back (put it in the HttpContextBase?) or something similar that allows me to capture rendered html on normal controller actions. I have some pretty heavy needs for using normal controller
actions instead of using a different type of controller (ComponentController) for my component html capturing.
Is there any chance we can get this capability back?
Mar 31, 2008 03:01 AM|tgmdbm|LINK
First of all. Can you explain how the ComponentController doesn't do what you need it to? This might be a valid point that needs addressing. I don't understand what you mean by "you can't programatically choose which component to render".
Secondly, The method used in MvcContrib is extremely fast. The steps are as follows
Stream oldFilter = Response.Filter;
Response.Filter = newFilter = new CapturingResponseFilter() ;
// Do the rendering
Response.Filter = oldFilter;
You need to flush before switching the filter so that any buffered content is output to the old filter. And the same after, so that buffered content is captured.
"Do the rendering" can be anything which writes to Response.Output, like calling Execute on a Controller which calls RenderView.
Mar 31, 2008 11:51 AM|Kepler|LINK
Let me answer your second question first - I can't programmatically choose which component to render. This is due to RenderComponent being an Html extension. It can only be called from within a ViewPage or ViewUserControl, which is where I'm trying to
keep all logic out of. Placing all my logic into controllers, this gives me no recourse to programmatically choose the controller, action, and parameters that will make up my component rendering. The old SwitchWriter and the MvcContrib code allow me to get
around that issue, but I'd prefer something "built in" that lets me capture controller action html called from within a controller.
ComponentController doesn't fulfill my needs for the following reason. One of the strengths of my CMS is that any module can be the recipient of a straight RESTful url navigation. For instance, say a Voting module has a Show action to display a poll.
The URL could look like this - "/Voting/Show/27". My framework allows this as a perfectly viable url and will construct the surrounding portal page, if needed. I can do this because I have the concept of module home pages. Each module can have a specifically
built/configured page that acts as the resting place for any navigation that doesn't specify a portal page. So, "Voting/Show/27" would render the output of that action to the main content area, then the framework would realize that the whole page hasn't been
built yet, so it would build the Voting home page around it, ignoring any modules that were configured to go in the main content area. In order for this to work, every module's actions need to reside on real, top-level controllers because they are ignorant
if they will be called straight as a RESTful call in the url, or if they will be "summoned" to fill a secondary position on the page. This also makes it easy for me to expose any module's output in a RESTful api that can return XML, JSON, etc, instead of
html. I think this is an elegant system because module actions are ignorant of all CMS framework code. They just do their thing and Render their views. Well, they used to, until MVC Preview 2 broke my framework by hiding SwitchWriter.
I will definitely look at the MvcContrib method, since it sounds like the fastest path to getting my framework working the way it was previously.
Thanks for the response.
Mar 31, 2008 01:25 PM|tgmdbm|LINK
Logic belongs in the view! Read the following post for an explanation.
The controller should not be concerned with "how" to render html (or dependant on anything which does).
You could easily pass WhatComponentToRender to the view and write a helper method which did basically what RenderComponent does but which takes strings.
Mar 31, 2008 02:01 PM|Kepler|LINK
In a CMS, there's a great deal of logic around what modules to render, and where to render them. There are usually a lot of tables, and model classes to map to them, whose sole responsibility is what html to render, what container to render them in, what
skin to render those in, and where to put them. This is a bit much for a View, so I overrode the base RenderView method in my own controller class to handle all of that complexity.
I realize that, in general, a controller should not have to worry about these things. Although, if there's business logic and a model that deals with what module to render and where, then I could argue that a controller that deals with that model is exactly
where that logic belongs. That's why I wrote a framework that bends that MVC principle in one place (base controller handles all that CMS framework stuff), so that no other controller action will ever have to worry about it again.
People never seem to think of content management when they talk about the principles of MVC. In some other conversations, I've even had people say that MVC isn't a good fit, but I seriously disagree. Especially if I can handle all the CMS logic in one
place, so that every other controller action written against that framework can benefit from everything MVC has to offer.
I'll try to come up with an alternative that takes all the packaged data for a page and handles placing the objects correctly. Where will I put the logic to determine when all the modules for a page need to be pulled from the database? I envision it would
need to be a multi-phased approach. My original solution involved doing everything in RenderView, so I suppose I will look at doing everything in OnActionExecuting as an alternative. Perhaps something can be done before an action occurs.
Mar 31, 2008 02:57 PM|tgmdbm|LINK
Pass all the information needed to render the view, including all the various components and the properties on them (containers, skins, and position), in the ViewData.
Let the view arrange and render the components.
This view logic should be encapsulated in helper methods and components which take the view data and render the appropriate html. This is perfectly feasible assuming the view data contains everything needed.
You're setting yourself up for a nightmare if you start handling html in your controller.
Mar 31, 2008 09:19 PM|sodablue|LINK
I believe I understand what he's trying to do. Essentially it's a dynamically laid out page. Basically like WebParts. So you don't know if you have 1 or 6 user controls, nor what type they are, or what their name is until runtime.
I see no way RenderComponent can be used for this, as the type and expression have to be hard coded in at compile time. I can't see any way to create these dynamically at runtime.
RenderUserControl would work, as it just takes a string path, and an object for viewdata, etc. So you could build collections with these properties and then iterate through them in the view and render each one in order that thy should appear on the page.
For my part, I don't understand why RenderComponent is looking for an .aspx page instead of an .ascx. In my mind, an .aspx page assumes you're going to have fully structured HTML code with a doctype at the top, and it being enclosed in html and body tags,
etc. Hardly what I would want to render and insert into the middle of an existing page.
Mar 31, 2008 09:47 PM|csainty|LINK
I quite like the sound of the layout you are using for the CMS.
Now the framework source code is available, you shoul take a look at how RenderComponent works, there is probably a way to change the parameter to a string and use some reflection.
Alternatively, you could attach a List<Action<ComponentController>> into your view data, in the controller set up some system of list.Add(c => c.Vote(1)); and then in the view go over this list calling RenderComponent on each element.
I find it useful to have a single controller for all my components. (PatchController in my case)
I have not tried to do something like this, though I think I will come up against the hurdle before too long.
Mar 31, 2008 11:26 PM|tgmdbm|LINK
So you don't know if you have 1 or 6 user controls, nor what type they are, or what their name is until runtime.
Pass all the information needed to render all the various components
I see no way RenderComponent can be used for this
write a helper method which did basically what RenderComponent does but which takes strings
For my part, I don't understand why RenderComponent is looking for an .aspx page
there is probably a way to change the parameter to a string and use some reflection
Alternatively, you could attach a List<Action<ComponentController>>
You could of course pass a List<Action<PatchController>>. But ultimately you might still want an overload on RenderComponent which takes either a Type, or a string.
Mar 31, 2008 11:43 PM|csainty|LINK
Yeah it all depends on the precise layout you want to use. I run a single ComponentController (Patch) because I wanted to shorten the Html.RenderComponent<PatchController>(..) code down to just RenderComponent(...) and to do so required a single ComponentController
and an Extension method.
So the List<> option works in my situation. It would also work if on any particular view you were only going to need access to a single ComponentController, say all your Product Views need the ProductComponentController or something like that. But this situation
With a reworked RenderComponent method (based on the source code version) you might find a nice way to pass in a Type and an Action based on that type, then pass a list of these into that function. It would require a bit more work but might work.
Apr 01, 2008 04:09 AM|tgmdbm|LINK
here's a quick workaround for you. Here's an overload for the RenderComponent method.
public static string RenderComponent(this HtmlHelper helper, string actionName, Type controllerType, params object arguments)
if( !typeof( ComponentController ).IsAssignableFrom( controllerType ) )
throw new InvalidOperationException( "Type must target a valid ComponentController" );
var method = controllerType.GetMethod( actionName );
var controller = (ComponentController)Activator.CreateInstance( controllerType );
method.Invoke( controller, arguments );
Hope this helps
[Edit: No need to build an expression tree if it's just going to be dismantled. This is simpler]
Apr 02, 2008 08:14 PM|Kepler|LINK
Great discussion. You guys have me thinking hard on ways to improve this. The RenderComponent code is a great step, but there are some foundational issues which I don't think you've thought of yet, probably just because you haven't had to implement a full
CMS yet :)
Getting string output from a controller action is only a part of the problem. The bigger part of the problem is how the MVC lifecycle must be bent to handle the basic workflow of a CMS. This is where ComponentController really breaks down as I'll describe.
I described earlier the very basic workflow I used (and got working wonderfully) in the last MVC preview build, but I'll give it a quick once-over:
While it may seem unorthodox to do html string manipulation in a controller, it has the benefit of doing all of the "hackish" stuff in one place. The biggest benefit of my current solution, however, is that everything else related to MVC is untouched.
The routing is identical for all modules. All modules use "real" controllers and can be accessed directly via the URL. All modules can handle both GET and POST requests, something ComponentController cannot do (at least I don't think it can). ComponentController
looks like it was designed purely to return html snippets, not to handle saving a News item, or a Profile update for instace.
Here's my current train of thought on a new/different solution (maybe I'll get time to try this tonight). This is based around the concept that my CMS framework has to get all the data for the modules on the page at some point, and that's certainly not
something we want to have to code into every controller action.
Hopefully, you can see now some of the more serious shortcomings of RenderComponent and ComponentController.
I'd be very happy if I can just get a better mechanism to call and capture string output from full-blown controller actions. I'm still leaning towards a homegrown method that does just that as my best solution. BTW, I'm going to release my CMS as open
source (hopefully very soon), so everyone can see this stuff in action as soon as I eliminate what is left of my "real life" to crank this out.
Apr 02, 2008 08:31 PM|tgmdbm|LINK
Or... You could make your ComponentController impelement IController. Then you can directly access its actions via URLs (with a bit of work)
Or... instead of extending ComponentController, extend Controller, and write an overload for RenderComponent which works with Controllers.
Apr 03, 2008 11:01 PM|pure.krome|LINK
Ouch ... my head hurts after reading this :( :(
can we get some thoughts from the MS MVC developers on this? Haacked? Anyone?
(I like where tgmdbm is going with this, though :) )
Apr 11, 2008 04:13 PM|Kepler|LINK
As of now, I wrote my own RenderComponent that works with controllers, like you suggested. It's coded similar to the MvcContrib BlockRenderer method, and I'm up and running again. Here's to hoping they don't break this in the next build :)