I have a mature webforms Umbraco v4.11 implementation but it tends not to use any webforms functionality, i.e. postbacks/viewstate etc. and instead rely purely on a more barebones approach or ajax where form submittal is required.
Personally I hate the project and wish that it was MVC but it would not be financially viable to move the project to an MVC implementation, so I'm looking for an alternative to implementing elements like forms that allow for a more MVC-like approach.
I have used the MVCBridge project but this does not support MVC4. Does anyone have any ideas on implementing a more MVC approach into a mature webforms project? Or does anyone have any patterns or techniques they use for working with webforms projects?
I'm smack bang in the middle of a conversion of my site from webforms to MVC. Initially i'm doing this on 4.11.9 but will move up to 6.x once we've dumped Courier. One of the key things I completely mis-understood at first is once you switch your site into MVC mode, rather miraculously masterpages (webforms) continue to co-exist quite nicely alongside anything new in MVC! Quite how the two techs work along side each other is a mystery to me, I assume there's combination of Umbraco/Microsoft practices at play here....but why the Umbraco team arent jumping up and down and advertising the fact....I dont know!
This was a revelation and meant I could take a much more staged approach to my conversion and not feel like I had to rush in and change everything in one go.
So for me stage 1 has been to switch over all of my masterpage templates to views and partials. Macros that use XSLT have remained for now, and may or may not get converted to macroscripts in the future. The caveat is macro based user-controls or any custom dlls that rely on the webforms page lifecyle/event model. These HAVE to be converted to views or macroscripts, I've used surface controllers with custom models for this successfully, although my biggest issue was passing in my custom model while continuing to inherit from the Umbraco model. The documentation says its possible in a partial, but in the end I had to pass my model over to a custom view via @Html.Action I just kept getting an error saying i was passing in object mymodel.model when it was expecting umbraco.model! Very annoying!
Thanks for the answer - yes, I was aware that the masterpages could co exist with views - you just can't reference views from masterpages etc.
I would very much like to move to using views instead of masterpages, but it's the risk of introducing errors and unnecessary work when the client hasn't paid/asked for it.
I'm just wondering if there's a middle ground - a better implementation pattern for webforms.
I suppose it depends on your site implementation, ours is very code light in the masterpages and very code heavy in XSLT. In fact just about all of the heavy lifting on our site is done in XSLT. So moving over to MVC is very easy for us.
I'm not aware of any middle ground of the two main techs, and are like comparing apples and pears anyway the approaches are completly different.
In contrast I'm not so sure what we'll do about our XSLT code in the long term, we'll probably leave it well alone unless it's either dropped from Umbraco or if any of it requires a re-write! Personally I bug bear our XSLT over anything we've done in webforms! I've never been much of a fan of XSLT.
I think the point i'm trying to make here is you can take a far more pragmatic approach to your code. Change only what you need to change!
As far as I know the Umbraco team have no plan to remove support for the older techs... so if it works...
Basically what you do is find anything that has "runat=server" on it and replace it, the cheatsheet shows it's only a handful of things that need updating.
My advise: spend half a day, take 2 hours of client time and 2 hours of your free time and see if it's viable.
I actually tried that tutorial to convert one of our user controls which was pulling in a bunch of YouTube videos from our feed. I couldn't get it to work with a partial (Umb 4.11.9).
Every time I tried to pass in my model it just started running the view code without the surface controller, resulting in object instance errors. In frustrastion I tweaked the inheritence of the views slightly but then got errors along the line of you tried to pass in model mymodel but it was expecting umbraco.templatepage.
In the end to get it to work I used @html.Action but I could only pass my model into that. Trying to inherit from templatepage resulted in the error described above.
@martin Sure, I was referring to the specific want of Kieron who says he doesn't depend on webforms too much.
Not sure what your user control code looks like but it should be fairly easy to rewrite properly in MVC. It's most likely that there's something simple missing in what you were trying to do. :)
If I had time to dive into that I would try to help, but unfortunately I haven't at the moment! :)
Thanks for the reply Sebastiaan. I did see that article and was toying with the idea. The thing that concerns me is that I have 40+ masterpages, nested and 3 of which are pretty hefty webforms that do use postbacks and a bespoke MVP implementation that we inherited. So I would have to maintain two base layouts and the given that this is not a small site, the testing time would be large.
I'll look into it further though, it may just be worth it.
@Seb in my experience I was left scratching my head a lot, which makes what looks like a "simple" 2 hour webforms conversion a whole day task!
MVC is a radical shift in methodology and its this shift which takes up precious time learing, not really something you can pass on to your clients unfortunately!
For me I think my problem was every code example seems to involve form building using BeginUmbracoForm, which is fine if you want to capture user input on a POST action. I didnt need any of that! I just wanted the surface controller to go out and grab a bunch of videos using the .net YouTube API and return them to a view in a standard list with paging. I thought it would make sense to pass the paging out as actions too, but that caused me all sorts of routing issues! So in the end I did it the macroScript way and shoved all of the paging logic into the view itself!
@martin Cool, that's pretty easy to do by creating a [ChildOnly] action. Example:
In your template (or MacroPartial) you do:
@Html.Action("RenderVideos", "Videos")
So: use the RenderVideos action on the VideosController
The controller can do:
public class VideosController : SurfaceController
{
[ChildActionOnly]
public ActionResult RenderVideos()
{
var model = new VideosModel();
foreach (var video in videos)
model.Videos.Add(video);
return PartialView("Videos", model);
}
}
And finally you'll need a View for the list of videos you want to show, something like: Views\Videos\Videos.cshtml:
@model Your.Models.Namespace.VideosModel
@foreach (var video in Model.Videos)
{
//video html
}
Yep that's pretty much what I did, but try adding another action inside the template for say paging, it fires off a custom route if ChildActionOnly is removed or is refused if it's present.
Also that way you cannot pass in/inherit from IPublishedContent.
@martin ???
[ChildActionOnly] just means: this is not routable and can ONLY be called from Html.Action.
I just added another [ChildActionOnly] action and it works just fine..
@Html.Action("RenderSomething", "Search")
Controller (just added new action to VideosController):
[ChildActionOnly]
public ActionResult RenderSomething()
{
var model = new VideosModel();
model.PageName = "hello";
return PartialView("Something", model);
}
Your template inherits from UmbracoTemplatePage and a MacroPartial from PartialViewMacroPage so you have all the IPublishedContent you need there. Where else will you be accessing the content? In your controller?
Thanks a lot for the code examples I see where you're coming from and it might be that i'm not using a "macro partial" as such, but then again I may be?? LOL.
What I really wanted here was a partial...But anyway the action calls another view in a folder in views called "YouTube.cshtml". Does that make it a macro partial? Or do I have to physically create a macro in the back office like with MacroScripts??
In the controller I wanted to add all of the logic for paging without having to resort to adding it all to the view/macro partial/partial god whatever it is this is it!
@inherits Umbraco.Web.Mvc.UmbracoViewPage<YouTubeModel>
@using lcp.Controllers
@using lcp.Models
@using System
@{int pageSize = 10; // How many items per pageint page = 1;
int count = 1;
/* Set up parameters */if (Request.QueryString["page"] == null)
{
if (Request.QueryString["move"] == null) { page = 1; }
}
else
{
page = int.Parse(Request.QueryString["page"]);
}
/* This is your basic query to select the nodes you want */var videos = Model.Video;
int totalVideos = videos.Count();
int totalPages = (int)Math.Ceiling((double)totalVideos / (double)pageSize);
/* Bounds checking */switch (Request.QueryString["move"])
{
case"next":
page++;
if (page > totalPages)
{
page = totalPages;
}
break;
case"previous":
page--;
if (page < 1)
{
page = 1;
}
break;
default:
if (page > totalPages)
{
page = totalPages;
}
elseif (page < 1)
{
page = 1;
}
break;
}
}<ulid="paging_info"><liclass="pipe first"><strong>@totalVideos videos.</strong></li></ul>@foreach (var item in Model.Video.Skip((page - 1) * pageSize).Take(pageSize))
{
var duration = TimeSpan.FromSeconds(Convert.ToDouble(item.Media.Duration.Seconds)).ToString();
<divclass="team_member_container"><divclass="one_third_column"><p><imgclass="youtube"id="@item.Media.VideoId.Value"src="@item.Media.Thumbnails[1].Url"alt="@item.Title"style="width: 100%; cursor: pointer; margin: 010px00"/></p></div><divclass="two_thirds_column"><p><strong><aclass="youtube"id="@item.Media.VideoId.Value"href="#">@item.Title</a></strong></p><p><strong>Duration:</strong>@duration</p><p><strong>Published:</strong>@item.AtomEntry.Published.Date.ToShortDateString()</p><p>@item.Description</p></div></div>
count++;
}
<divclass="pager_bar"><divclass="prev grid_2"><ahref="?page=@page&move=previous">Previous</a></div><divclass="pages grid_8"><ulclass="paging">@for (int p = 1; p < totalPages + 1; p++)
{
string selected = (p == page) ? "current no_link" : String.Empty;
<liclass="@selected"><ahref="?page=@p"title="Go to page @p of results">@p</a></li>
}
</ul></div><divclass="next grid_2"><ahref="?page=@page&move=next">Next</a></div></div>
So where paging bit is, I wanted @Html.ActionLink("Prev"), next and numbered pages rather than doing all of the logic in the view as you see here and also save a whole bunch of request.querystrings too!
Excuse my code, I threw it together so it's not very well optimised! lol.
I assume due to the fact I'm calling an Action within an action by decorating the controller with ChildActionOnly, it's going to tell me where to stick it! It's an inheritance thing I guess? If take it off, the route becomes custom and it fires the action within an action but not all of the surrounding templates....just the dinky bit in the middle heh! Which is not very helpful!
Sometimes it's great going back to the drawing board and learning something completely new! Luckily for me I work for a company that affords me such luxuries, without having to dedicate precious home time! LOL.
I'm sure you'll be telling me i'm doing it all wrong! It's as much about best practices as anything...much like inline code vs code behind was in the early days of webforms! ahhh the memories of moving from Classic ASP to ASP.net!
It's occurred to me to completely seperate the video list from the paging and maybe drop the paging into the multimedia view below the first action? That way I could call the two actions from within the same template level and hopefully pass in and out the necessary page data?
What I really wanted here was a partial...But anyway the action calls another view in a folder in views called "YouTube.cshtml". Does that make it a macro partial? Or do I have to physically create a macro in the back office like with MacroScripts??
No a macro partial is just a partial view wrapped in an umbraco macro inheriting from UmbracoMacroPartial. You don't need one, you have the Html.Action in your template already. If you wanted to be able to insert the YouTube partial in the RTE or if you wanted to cache the YouTube partial you could create a macro partial. But as I said, it's not necessary, you're doing things from your template already, that's great.
return PartialView("YouTube", vm);
This will go look for ~\Views\YouTube\YouTube.cshtml and that will expect YouTubeModel as model (you're confusing route hijacking with doing stuff much more simply.
So go to your YouTube.cshtml and wipe everything out of it (make a backup :P)
Then try this:
@model YouTubeModel
@foreach (var video in Model.Video) {
@video.Title<br />
}
That works, right? Keep your paging server side. So on first request do:
vm.Video = videoFeed.Entries.ToList().Take(10);
Use ModelBinding to your advantage, if you add ?page=1 to your querystring, you can catch that:
public ActionResult YouTube(int page = 0)
So when there's no querystring, the page is 0, otherwise the page is an int as provided in the querystring.
Then you can start doing the paging calculations by using videoFeed.Entries.ToList().Count().. etc. So then you'll be doing something like (it's probably incorrect but do play with it):
This will go look for ~\Views\YouTube\YouTube.cshtml and that will expect YouTubeModel as model (you're confusing route hijacking with doing stuff much more simply.
So do I need to change this bit at all? Yes, I read about route hijacking and I was aware I may be getting it confused, Do I just return CurrentUmbracoPage?
No no, return PartialView("YouTube", vm); is perfect. Then you can build up that view like I said starting with:
@model YouTubeModel
@foreach (var video in Model.Video) {
@video.Title<br />
}
Just to prove that you get a result. And then you can build it up from there.
I mentioned route hijacking because that's usually what you'd use Umbraco.Web.Mvc.UmbracoViewPage
Yup thats all good works fine, but where and how do I build up a paging bar? Do I do that in the view? Outside the view? How are the page numbers pre-populated in the pager bar and how do I call @html.ActionLink to post/get the next/previous or absolute page?
Awesome Martin, looking good, glad you got it all working together so nicely.
I would populate the duration in your controller as well and the ToShortDateString too, but other then that it looks great!
Had another quick thunk and decided to wrap another simple class just around the bits of information I needed from the YouTube API from which I could build a list.
Just wanted to say thanks - I took the leap and started converting my project. I can actually start seeing some light at the end of the tunnel. Sure, I'm going to have to retain a masterpage for one template but I will be able top replace this with time
@kieron Yeah the webforms are going to be a problem and I would not
want to maintain two "master" layouts! :(
"just" convert the webforms to MVC man, you know you want to! ;-)
disclaimer: just messing with you.. :)
It took a few hours and there'll be some more from testing but worth it.
Working with a mature WebForms
I have a mature webforms Umbraco v4.11 implementation but it tends not to use any webforms functionality, i.e. postbacks/viewstate etc. and instead rely purely on a more barebones approach or ajax where form submittal is required.
Personally I hate the project and wish that it was MVC but it would not be financially viable to move the project to an MVC implementation, so I'm looking for an alternative to implementing elements like forms that allow for a more MVC-like approach.
I have used the MVCBridge project but this does not support MVC4. Does anyone have any ideas on implementing a more MVC approach into a mature webforms project? Or does anyone have any patterns or techniques they use for working with webforms projects?
Hi Kieron
I'm smack bang in the middle of a conversion of my site from webforms to MVC. Initially i'm doing this on 4.11.9 but will move up to 6.x once we've dumped Courier. One of the key things I completely mis-understood at first is once you switch your site into MVC mode, rather miraculously masterpages (webforms) continue to co-exist quite nicely alongside anything new in MVC! Quite how the two techs work along side each other is a mystery to me, I assume there's combination of Umbraco/Microsoft practices at play here....but why the Umbraco team arent jumping up and down and advertising the fact....I dont know!
This was a revelation and meant I could take a much more staged approach to my conversion and not feel like I had to rush in and change everything in one go.
So for me stage 1 has been to switch over all of my masterpage templates to views and partials. Macros that use XSLT have remained for now, and may or may not get converted to macroscripts in the future. The caveat is macro based user-controls or any custom dlls that rely on the webforms page lifecyle/event model. These HAVE to be converted to views or macroscripts, I've used surface controllers with custom models for this successfully, although my biggest issue was passing in my custom model while continuing to inherit from the Umbraco model. The documentation says its possible in a partial, but in the end I had to pass my model over to a custom view via @Html.Action I just kept getting an error saying i was passing in object mymodel.model when it was expecting umbraco.model! Very annoying!
I hope this is of use to you
Martin
Thanks for the answer - yes, I was aware that the masterpages could co exist with views - you just can't reference views from masterpages etc.
I would very much like to move to using views instead of masterpages, but it's the risk of introducing errors and unnecessary work when the client hasn't paid/asked for it.
I'm just wondering if there's a middle ground - a better implementation pattern for webforms.
I suppose it depends on your site implementation, ours is very code light in the masterpages and very code heavy in XSLT. In fact just about all of the heavy lifting on our site is done in XSLT. So moving over to MVC is very easy for us.
I'm not aware of any middle ground of the two main techs, and are like comparing apples and pears anyway the approaches are completly different.
In contrast I'm not so sure what we'll do about our XSLT code in the long term, we'll probably leave it well alone unless it's either dropped from Umbraco or if any of it requires a re-write! Personally I bug bear our XSLT over anything we've done in webforms! I've never been much of a fan of XSLT.
I think the point i'm trying to make here is you can take a far more pragmatic approach to your code. Change only what you need to change!
As far as I know the Umbraco team have no plan to remove support for the older techs... so if it works...
Martin.
@Kieron it is actually SURPRISINGLY simple to change your master pages over to views, if you are not using forms it is super easy and you should not run into errors, have a read here: http://umbraco.com/follow-us/blog-archive/2013/7/14/moving-from-webforms-to-mvc.aspx (especially the "Convert Masterpages to MVC Views" section).
Basically what you do is find anything that has "runat=server" on it and replace it, the cheatsheet shows it's only a handful of things that need updating.
My advise: spend half a day, take 2 hours of client time and 2 hours of your free time and see if it's viable.
Hi Seb
I actually tried that tutorial to convert one of our user controls which was pulling in a bunch of YouTube videos from our feed. I couldn't get it to work with a partial (Umb 4.11.9).
Every time I tried to pass in my model it just started running the view code without the surface controller, resulting in object instance errors. In frustrastion I tweaked the inheritence of the views slightly but then got errors along the line of you tried to pass in model mymodel but it was expecting umbraco.templatepage.
In the end to get it to work I used @html.Action but I could only pass my model into that. Trying to inherit from templatepage resulted in the error described above.
Martin.
@martin Sure, I was referring to the specific want of Kieron who says he doesn't depend on webforms too much.
Not sure what your user control code looks like but it should be fairly easy to rewrite properly in MVC. It's most likely that there's something simple missing in what you were trying to do. :)
If I had time to dive into that I would try to help, but unfortunately I haven't at the moment! :)
Thanks for the reply Sebastiaan. I did see that article and was toying with the idea. The thing that concerns me is that I have 40+ masterpages, nested and 3 of which are pretty hefty webforms that do use postbacks and a bespoke MVP implementation that we inherited. So I would have to maintain two base layouts and the given that this is not a small site, the testing time would be large.
I'll look into it further though, it may just be worth it.
@Seb in my experience I was left scratching my head a lot, which makes what looks like a "simple" 2 hour webforms conversion a whole day task!
MVC is a radical shift in methodology and its this shift which takes up precious time learing, not really something you can pass on to your clients unfortunately!
For me I think my problem was every code example seems to involve form building using BeginUmbracoForm, which is fine if you want to capture user input on a POST action. I didnt need any of that! I just wanted the surface controller to go out and grab a bunch of videos using the .net YouTube API and return them to a view in a standard list with paging. I thought it would make sense to pass the paging out as actions too, but that caused me all sorts of routing issues! So in the end I did it the macroScript way and shoved all of the paging logic into the view itself!
M
@martin Cool, that's pretty easy to do by creating a [ChildOnly] action. Example:
In your template (or MacroPartial) you do:
So: use the RenderVideos action on the VideosController
The controller can do:
And finally you'll need a View for the list of videos you want to show, something like: Views\Videos\Videos.cshtml:
That's all there is to it.
@kieron Yeah the webforms are going to be a problem and I would not want to maintain two "master" layouts! :(
"just" convert the webforms to MVC man, you know you want to! ;-)
disclaimer: just messing with you.. :)
@Seb
Yep that's pretty much what I did, but try adding another action inside the template for say paging, it fires off a custom route if ChildActionOnly is removed or is refused if it's present.
Also that way you cannot pass in/inherit from IPublishedContent.
M.
@martin ???
[ChildActionOnly] just means: this is not routable and can ONLY be called from Html.Action.
I just added another [ChildActionOnly] action and it works just fine..
Controller (just added new action to VideosController):
New Views\Videos\Something.cshtml view:
Your template inherits from UmbracoTemplatePage and a MacroPartial from PartialViewMacroPage so you have all the IPublishedContent you need there. Where else will you be accessing the content? In your controller?
Oh and if the answer to the last question is yes:
This should get you there.. :)
@Seb
Thanks a lot for the code examples I see where you're coming from and it might be that i'm not using a "macro partial" as such, but then again I may be?? LOL.
Ok so lets give you a better idea...
Template view (Multimedia.cshtml).
What I really wanted here was a partial...But anyway the action calls another view in a folder in views called "YouTube.cshtml". Does that make it a macro partial? Or do I have to physically create a macro in the back office like with MacroScripts??
Then the controller...
In the controller I wanted to add all of the logic for paging without having to resort to adding it all to the view/macro partial/partial god whatever it is this is it!
So where paging bit is, I wanted @Html.ActionLink("Prev"), next and numbered pages rather than doing all of the logic in the view as you see here and also save a whole bunch of request.querystrings too!
Excuse my code, I threw it together so it's not very well optimised! lol.
I assume due to the fact I'm calling an Action within an action by decorating the controller with ChildActionOnly, it's going to tell me where to stick it! It's an inheritance thing I guess? If take it off, the route becomes custom and it fires the action within an action but not all of the surrounding templates....just the dinky bit in the middle heh! Which is not very helpful!
M.
@Seb
Sometimes it's great going back to the drawing board and learning something completely new! Luckily for me I work for a company that affords me such luxuries, without having to dedicate precious home time! LOL.
I'm sure you'll be telling me i'm doing it all wrong! It's as much about best practices as anything...much like inline code vs code behind was in the early days of webforms! ahhh the memories of moving from Classic ASP to ASP.net!
Martin.
It's occurred to me to completely seperate the video list from the paging and maybe drop the paging into the multimedia view below the first action? That way I could call the two actions from within the same template level and hopefully pass in and out the necessary page data?
It's an off the cuff idea..lol
M.
No a macro partial is just a partial view wrapped in an umbraco macro inheriting from UmbracoMacroPartial. You don't need one, you have the Html.Action in your template already. If you wanted to be able to insert the YouTube partial in the RTE or if you wanted to cache the YouTube partial you could create a macro partial. But as I said, it's not necessary, you're doing things from your template already, that's great.
This will go look for ~\Views\YouTube\YouTube.cshtml and that will expect YouTubeModel as model (you're confusing route hijacking with doing stuff much more simply.
So go to your YouTube.cshtml and wipe everything out of it (make a backup :P) Then try this:
That works, right? Keep your paging server side. So on first request do: vm.Video = videoFeed.Entries.ToList().Take(10);
Use ModelBinding to your advantage, if you add ?page=1 to your querystring, you can catch that:
So when there's no querystring, the page is 0, otherwise the page is an int as provided in the querystring.
Then you can start doing the paging calculations by using videoFeed.Entries.ToList().Count().. etc. So then you'll be doing something like (it's probably incorrect but do play with it):
This will go look for ~\Views\YouTube\YouTube.cshtml and that will expect YouTubeModel as model (you're confusing route hijacking with doing stuff much more simply.
So do I need to change this bit at all? Yes, I read about route hijacking and I was aware I may be getting it confused, Do I just return CurrentUmbracoPage?
No no, return PartialView("YouTube", vm); is perfect. Then you can build up that view like I said starting with:
Just to prove that you get a result. And then you can build it up from there. I mentioned route hijacking because that's usually what you'd use Umbraco.Web.Mvc.UmbracoViewPage
Yup thats all good works fine, but where and how do I build up a paging bar? Do I do that in the view? Outside the view? How are the page numbers pre-populated in the pager bar and how do I call @html.ActionLink to post/get the next/previous or absolute page?
You put all of the paging information in your model! And then you just loop through it
And so on.. Get your businesslogic out of your view, that's what MVC promotes, clean seperation! :)
Totally hijacked this thread by the way, sorry Kieron!! :-)
heh yeah sorry Kieron! But blooming helpful Seb! Thanks!
Hi Seb
Thanks again for your help on this...Here's what I finally settled on....
Model
Controller
View snippet
Thanks again, much tidier and closer to best practice methinks!
M.
Awesome Martin, looking good, glad you got it all working together so nicely. I would populate the duration in your controller as well and the ToShortDateString too, but other then that it looks great!
Hi Seb
I wasnt entirely sure how to do that as it's part of the YouTube object model and it's built into a list.
M.
@Seb
Had another quick thunk and decided to wrap another simple class just around the bits of information I needed from the YouTube API from which I could build a list.
This meant I could take out the two other bits of code in the view that were doing conversions and I ended up with this in the model...
Now the view is very clean!
Thanks for your help.
Martin.
Sweet! You've just discovered the concept of ViewModels! :-)
Hi Seb,
Just wanted to say thanks - I took the leap and started converting my project. I can actually start seeing some light at the end of the tunnel. Sure, I'm going to have to retain a masterpage for one template but I will be able top replace this with time
It took a few hours and there'll be some more from testing but worth it.
is working on a reply...