On this episode of Eat Sleep Code, Steve Smith discusses the new ASP.NET Core development model, Razor Pages. Steve outlines why developers shouldn't dismiss Razor Pages before giving it a proper try. How Razor Pages compare to WebForms, MVC, and Web API are discussed.
Steve recently started his first podcast, WeeklyDevTips.com, in which he shares one quick developer tip each week.
Steve Smith (@ardalis) is an entrepreneur and software developer with a passion for building quality software as effectively as possible. He provides mentoring and training workshops for teams with the desire to improve. Steve has been recognized as a Microsoft MVP for over 10 consecutive years, and is a frequent speaker at software developer conferences and events. He is one of the top contributors to the official documentation on ASP.NET Core and enjoys helpings others write maintainable, testable applications using Microsoft’s developer tools. Connect with Steve at ardalis.com.
00:16 Ed Charbeneau: Hello, and welcome to Eat, Sleep, Code, the official Telerik podcast. I'm your host, Ed Charbeneau, and with me today is Steve Smith. How you doing, Steve?
00:25 Steve Smith: I'm good. How you doing, Ed?
00:26 EC: Excellent. The other day, I was watching some conversations happen on Twitter because of the big.NET Core release. We just had the release of.NET Core standard or.NET Core 2.0.NET standard 2.0, and ASP.NET Core, whole lots of good things being shoved out at an early release from Microsoft. And one of the brand new features of this release is Razor Pages, and there was some banter going back and forth on Twitter that you and I were both commenting on. I thought it'd be time to do a great podcast on the subject of Razor Pages. And I'm thankful that you made some time for me today.
01:17 SS: Oh yeah, no problem. I've been definitely eagerly following ASP.NET Core for a long time now, helping to work on the documentation at docs.asp.net, which is now docs.microsoft.com, and have an article that's probably out by the time this podcast goes live on Razor Pages that's in MSDN Magazine in their September issue. If you haven't played with Razor Pages yet, I encourage people to withhold judgment on them, since I know a lot of folks are thinking that they're just like the web pages support that's come out in the past, or they feel like web forms, and MVC developers are put off by that. Have a moment to give 'em a chance, and listen to this podcast, at least, and see what you think after you've had a chance to play with them.
02:04 EC: Absolutely. And before we get started, Steve, why don't you give us a little background about yourself?
02:11 SS: Oh, sure. I've been working with ASP and ASP.NET for about 20 years now. Have been working with the ASP.NET team on various parts of ASP.NET Core, helping them with documentation. I'm a Microsoft MVP for about, I don't know, 14 or 15 years now. And I helped a lot of clients who are working with ASP.NET and ASP.NET Core, looking at transitioning, moving from MVC 5 or Web API 2, helping them with their architecture so they can write cleaner code that's easier to test and easier to change over time. I have a bunch of courses out on Pluralsight. If you wanna learn more about SOLID principles or N-Tier architecture, I have some courses on those things, as well as domain-driven design. And more recently, I've got a course on ASP.NET Core at a site called DevIQ. And so, I have a ASP.NET Core Quick Start, which is a fairly small course that basically gets you up to speed with ASP.NET Core as quickly as possible. And haven't had a chance to update that for Razor Pages and 2.0 yet, but I expect that'll be coming soon.
03:15 EC: I have to say I've followed some of your material, and I like the Refactoring Principles that you have up on Pluralsight, so a good course. If you guys out there listening wanna check that out, it's definitely something that I enjoyed.
03:32 SS: Good, thanks. Yeah, that one covers a lot of different code smells and how to fix them in your code.
03:37 EC: Yes, it's always good stuff. Speaking of writing good clean code, maybe that's a good way to segue into the topic at hand. We're gonna be talking about the newest feature added to the.NET web development stack, and that is Razor Pages. And I think you mentioned to me before that you started looking into Razor Pages after writing a feature for MSDN.
04:06 SS: Yeah. Last year in 2016, I wrote an article there on feature folders and talked about different ways you could organize your MVC or ASP.NET application. And one of the problems that I've run into, especially in larger MVC applications, is that the standard folder layout is to use the Controllers folder and the Views folder, and usually, a Models and a ViewModels folder to store all of your various controllers and views, etcetera. And storing those files based on their file type is a nice default template organizational strategy, but it tends to fall down as you get to a much larger project because it doesn't map to how you're actually working within the project. Typically, if you're adding a new feature, let's say you're adding a customer's controller to let the administrator manage details related to customers, well, you're gonna need a customer's controller, you're gonna need some different customers' views, those are gonna go in the Views folder. You'll probably need some view models, those are going to ViewModels folder. And you'll probably need a service or a repository, or some other model types to work with as well.
05:10 SS: And so, as you're working on this feature, you are gonna find that you're working inside of five different folders of different depths within your Solution Explorer, none of which are next to each other 'cause they're all separated alphabetically from controllers down to views, typically. And it results in a lot of scrolling and spelunking inside that folder that just adds friction to the process. There's a few different ways you can address that. You can use Areas, which is a built-in feature, or you can use feature folders, which again, I talked about in that article. And there's a couple of NuGet packages out there that can help you get started on using feature folders in ASP.NET Core, and you can do similar things in ASP.NET MVC. But Razor Pages actually gives you a lot of the same benefits in terms of organizing your code as feature folders do. And that's because with Razor Pages, you can have your view, which is your Razor Page, along with all the actions that take place that are related to that view in your page model. And then, your page model can act as the view model. It's in place all inside that same folder as well, and it really can save you a lot of navigating around when you're working with your code.
06:24 EC: You say all of the parts of this are in a folder, or all of the pages are in a folder, or kind of both?
06:32 SS: Well, the default organization for Razor Pages in ASP.NET Core 2.0 is that there is a new root-level folder called Pages. And if you're upgrading a project to ASP.NET Core 2.0, you don't have to do anything to add support for Razor Pages; they're just built-in. They're built into MVC. As long as you're saying, "Use MVC, and add MVC," in your startup class, you're going to have access to Razor Pages. All you have to do to start using them is add a new root-level folder called Pages, and then add what are basically gonna be view files in there. They're.cshtml extension file, but at the top, as a directive, you're just gonna add the app page directive, and that will tell ASP.NET Core that this is a Razor Page, and it'll give it that behavior instead of treating it like a view.
07:20 EC: So the combination of the Pages folder in the app page directive at the top is pretty much all we need to get started then.
07:29 SS: That's right.
07:30 EC: In an MVC application.
07:33 SS: In an ASP.NET Core application that has ASP.NET Core MVC installed, yes.
07:38 EC: Okay. Now, when we put these pages in the folder, are there separate code files? Or is this all of our markup and all of our logic in one file?
07:52 SS: It supports both. If you want to, you can have just a simple view, throw app page on it, and maybe it doesn't do anything. Maybe it doesn't even need a getter or anything. At its simplest, you can have, essentially, a static page that just says "App Page" at the top and displays "Hello world." And that will just work. And you don't have to have a controller, you don't have to have an action, you don't have to have an OnGet, or anything like that. The simple case is as simple as it gets. And that's one of the reasons for this feature to exist, is that there was feedback from a lot of customers that MVC simply made it too difficult to do simple things like, "I just wanna add a contact page," or, "I just wanna add an about page. Why do I have to add a controller, and an action, and a view in a folder." There's a lot of overhead to that simple scenario. Pages eliminates a lot of that, and uses a convention-based approach for the routing aspect so you don't even have to deal with having a custom route in your table, or adding an attribute to the controller to tell it what the route is. Everything is pretty simple, in that sense.
08:56 SS: Now, when you wanna do some custom logic like most of us in non-trivial pages or endpoints on our application, you can put that inside of the page itself, inside the.cshtml file. But the recommendation is to put that into a separate page model. Now, you can define a page model in the page, but again, the preferred choice, if you're gonna write code that's maintainable, is to put it in a separate class, in a separate file. And then, that file is referenced from the page itself. You would add a directive saying, "App Model," specify the name of the class, "Index Model" or "About Model" or whatever it might be. And then, that page model type is defined in a separate class in a separate file. Normally, by convention, you'll name that. Let's say it's an index.cshtml. You'll name it index.cshtml.cs. And if you're using Visual Studio, it will link those together, and give you a little plus sign to expand, just like it does with web forms, and with a lot of other files like minified Script files, and things like that. So that when you're navigating through your Solution Explorer, it's very easy to see these two files go together.
10:47 SS: Exactly. And yeah, they really are code behind files. And again, I know that's one of the things that really sets off MVC developers that are cringing from the times that they've worked with web forms applications. These are not at all related to web forms. They do use code behind files, and those are actually helpful in this case, just like code behind files were better in web forms than web forms that didn't have code behind files, which you could do. Even though the nomenclature is similar or even the same in some cases, don't totally throw out Razor Pages with the bathwater thinking, "Oh my gosh! It's got code behind files. It must be web forms. It must be awful. Run and hide."
11:30 EC: So there's no ViewState, is what you're saying?
11:33 SS: There is no ViewState, that is true. That's the least of it. More importantly, the separate code behind files, those page model types, inherit from a helper class called PageModel, similar to how controllers in ASP.NET Core MVC typically inherit from the controller class that's built-in. And this provides some helper methods and things that are available. But it's... Otherwise, it's not tightly-coupled to anything. You can easily write unit test against the page model. You can write functional or integration tests against your pages just like you can with controllers and actions in ASP.NET Core. It's definitely not something that has a lot of dependencies that make it difficult to work with like a code behind file and web forms was, and pretty much still is.
13:19 SS: Right, yeah, it really is more of a Model-View-Presenter pattern than MVC, although it's built on top of ASP.NET Core MVC. In fact, David Fowler, one of the chief architects of MVC and the.NET team recently said on Twitter that... Or maybe it was Slack, that he wishes perhaps they hadn't called it ASP.NET MVC because it's not strictly MVC at this point. The PageModel approach is really a different pattern than MVC, but it's using all of their same framework and libraries for routing and model-binding and everything else, that are built into the ASP.NET Core MVC project and package on NuGet. And so, it's using ASP.NET Core MVC, the package, even though it isn't necessarily using the Model-View-Controller UI pattern, in this case.
14:13 EC: Right. Speaking of the View part of this scenario, what type of views can we expect from this? Obviously, the name "Razor" implies that we're using Razor for the markup.
14:25 SS: That's correct, which is no different than the regular views inside of ASP.NET Core. And in fact, the similarity doesn't end there. It's using, pretty much, the same view functionality. You have support for things like Layout, View Imports, Includes, things of that nature. All of that stuff carries over into Razor Pages. Inside of your Pages folder, if you wanna have a shared layout, you can have, for instance, underscore layout.cshtml that is then used as the layout file for your different pages. Much the way that you do now when you have a shared folder inside of your Views folder where you put Layout and Partial Views, and things of that nature. All the View functionality that you have in MVC, you have in pages. You can do partial views, you can do tag helpers, you can do view components. All that stuff carries over.
15:20 EC: So the brand new tag helpers work in this new Razor Page scenario.
15:25 SS: Correct. And they have some additional new tag helpers as well. For instance, if you're creating an a href anchor tag in your HTML, in MVC, you have some additional tag helpers you can add to that to say something like "asp-controller is this, asp-action is that, asp-area is something else." There's a new one for asp-page. You can specify the page that you want it to reference. And those exist on the places where they logically should. For instance, on buttons, on forms, etcetera, you'll find those in the places you would expect.
16:00 EC: Yeah, you beat me to the question. I was gonna ask where the Controller and Action fit in on some of those HTML helpers or tag helpers if there's not a concept. Or is there a concept of a Controller with the Razor Pages?
16:16 SS: There really isn't. In fact, if you look at the route table that is used with Razor Pages, there's a helper that I recently added to my blog that's a route debugger that'll just dump out your routing table. And if you're using Razor Pages, you'll see when you dump out that set of routes that the Controller and the Action values inside that route table are empty because it doesn't use them. Instead, the routing in Razor Pages uses a convention approach. So the location of the page within the Pages folder will correspond to its route. If you have an about.cshtml that's sitting inside of your Pages folder, it's gonna be referenced by a route of "About." If someone goes to your website, they navigate to /about, they will get that page. If you put it inside of a subfolder, then that subfolder becomes part of its hierarchy on the URL as well, by default. You can override all this inside of Startup. You can remove things from Routes or change how they're routed. But the default convention should be sufficient for most cases. If you just need a page and you want it to be called "Whatever," you just call it that in the file system, drop it into the Pages folder, and by default, it will just work.
17:29 EC: So the page hierarchy controls the routing in Razor Pages. And your tool, we'll put a link to that in the show notes so people can go find that. I thought that was a pretty helpful little tool that you created, kinda spit all those out and see what your routes look like in a Razor Page application. If our routing's being handled that way, and we don't have controllers, and our logic is inside of these few pages, or with code behind pages, how do we handle events and things that are happening on the page? How do we interact with the data that's going back and forth?
18:20 SS: Again, this is normally gonna be used by convention. In a controller, you would have to add a method, and then decorate that method in order for routing to work with it. You might create a HttpGet attribute to put on something to say that it will respond to "GET" requests or an HttpPost. You might give it a route to specify which route it would respond to. In Razor Pages, you don't have to do any of that if you're following the default conventions. In order to get it to respond to a "GET," you don't have to do anything, it does that by default. If you want it to respond to a "POST" or a pull it, or whatever, you can add an additional handler. That handler, by default, will have a signature of "ON," and then whatever the Http verb is. And then optionally, if it's async, it can have an async suffix. For instance, you might have OnGet, OnPost, OnGetAsync, OnPostAsync, etcetera.
19:13 SS: Now, in some cases, you may want to have more than one of those types of handlers on a given page, just like you might have more than one action on a controller that will support GET. Maybe you've got a GetById in addition to a GET that lists everything, for instance. In that case, you can also add a name to the handler. You could say OnGetList. And then, that string that you put at the end becomes a variable that you can pass in, so that when you're specifying an action on a page, you can specify that list part of the handler name, and it will route that to the correct handler when it renders that page.
19:52 EC: And do these handlers render the entire page when they're called? Or can we set up partial rendering where we have an Ajax call coming and getting just a little bit of data back?
21:13 EC: Can we mix in things that are part of the MVC world and with Razor Pages, and have a controller, Model-View-Controller scenario with the Razor Pages? Also, 'cause if I remember back, you said if we add a page directory to our project and just put the app page decorator on a Razor Page, then we have the support. Does that mean that we have everything in one if we wanted?
21:42 SS: Yes, you can actually have pages run side by side with Views, with Controllers, with APIs, all inside the same ASP.NET Core application. There's nothing to prevent you from doing that. You do have to be careful that you don't have routes that are inside of the controller side of things that happen to have the same convention as a page with its convention would have. You'll end up with a conflict there, and you'll get an error saying that the routing engine couldn't determine which one it should use. But other than that, they can exist side by side perfectly well.
22:12 EC: If we wanted to have things that are doing pure JSON responses, we could spin up a web API and do it that way.
22:21 SS: Yep, you just drop those into your Controllers folder or API controllers or whatever, wherever you wanna put it inside your project. Those can still exist and those can return JSON or action results that are gonna use whatever content is negotiated with the client. All that stuff works the same, and it works side by side with Razor Pages inside the same project if you want.
22:41 EC: So far, it just sounds like we have some additional flexibility that we didn't have prior to this being released. Does this look to you maybe like a baby step for people getting to.NET development on doing web applications? Or is it simplified enough for that type of a thing?
23:03 SS: I think it does serve that purpose, to some extent. And I think that there probably is a target audience for this. Microsoft shipped web pages with WebMatrix back in 2010. And that was primarily targeted at novice web developers, and was pretty much ignored by most ASP.NET developers that were already comfortable with ASP.NET and with MVC at that time. With Razor Pages, I think Microsoft risks them being perceived the same. And that many experienced developers will just ignore them outright and say, "Well, you know, those are training wheels for new developers. I don't need that. I know what I'm doing." And I think that developers that do that might be missing out on another useful tool in their toolbox. And that Razor Pages are really much more than that because they are architecturally sound, they're built on top of the same features as everything else that you're used to in ASP.NET Core and in MVC.
24:01 SS: They do have a good place, at least, for simple things where you don't really need a controller. You don't really need a separate action method. You don't really necessarily... You wanna have a separate view model class. Maybe you just wanna add a page or just use the About Page for the application. They're great for that. I would argue too that you might consider them even for more complex scenarios where you have a CRUD-based form. It has, basically, "GET" and "POST," and those types of action methods, typically, that you would specify in MVC. And you don't wanna have to have a whole bunch of different files and a whole bunch of different folders just to deal with this thing. Instead, you could create a page or a couple of pages inside of a folder in your Pages folder, and put all your logic there, and not have to scatter it across half a dozen different folders inside your web project.
25:00 EC: I'm really interested in trying this out myself and getting my hands on it. Like you said earlier, I did actually have this confused with the ASP.NET web pages until I read a couple of blog posts on what was actually being done. And now, it seems like there could be some really valid uses for it. I'm looking forward to getting a hold of it, kicking the tires, checking it out, and making some experience-based decisions on what we've got now.
25:33 SS: Yeah, and it's a pretty easy thing to switch back and forth from. In fact, it wouldn't surprise me if at some point, Microsoft or a third party creates a tool that will let you switch to and from Razor Pages from MVC Controllers and Views. I cover in the MSDN article a series of steps that you would take to take an Action and a View with a Controller and convert that into a Razor Page, or vice versa. And it's not that difficult. I think there's 10 steps in the article, but they're simple steps like, "Move this file here, and rename this action to this," and things like that. And if you just follow those, 99% of the time, I think you'll be able to take simple MVC Controller-Action-View-based pages, in air quotes, and convert them into Razor Pages that consist of just a single page.cshtml, and a page model in a separate CS file that's backing it.
28:22 EC: Yeah, I think that was excellently put. Use the right tool for the job is usually what it boils down to. I think a lot of us developers, especially in the corporate world that do line of business applications, we run into a lot of scenarios where we're just doing data entry. It doesn't have to be flashy or fancy, it just needs to get done, and you have to have the inputs there to let somebody key in some data. But then, there's larger projects that may be customer-facing and open to the outside web that need to have that extra polish and finish and reactiveness to user input, and stuff like that. All those things have their places. Personally, I don't see server-side rendering with C# going away anytime soon. And in addition to that, if you wanna comment, Steve, feel free, but I've been following the WebAssembly stuff pretty closely, and I'm starting to see some experiments out there. One of them's called Blazor, which is really cool that lets us do C# development on the client. We may see our.NET tools come into this client space where we haven't seen 'em before, and be able to write things like Razor write on the client side.
30:52 SS: Yeah. No, that sounds cool, I'll also check that out more.
31:54 SS: Sure, yeah. Online, if people are interested, they can follow me on Twitter. I'm Ardalis 'cause Steve Smith is a pretty common name, and it's hard to get that username anywhere. And likewise, I have a blog at ardalis.com, that's A-R-D-A-L-I-S. And I've got a newsletter with a little over a thousand subscribers now where I've been doing weekly dev tips for the last year-and-a-half or so. And I also just started doing a podcast that's covering the same topic at weeklydevtips.com. It has exactly zero followers, I think, as I'm telling you this.
32:27 SS: But it is out there in the iTunes podcast store, and sometime soon, I'll get it on Stitcher, and other places where podcasts go. But it's a real short five- or 10-minute long episodes that just cover one quick tip each week. Look for me there. And again, check out my latest course on ASP.NET Core, which is at aspnetcorequickstart.com.
32:49 EC: Yeah, and always, starting up any of these new endeavors like a podcast or anything is... It's always a rough start when you look at those initial numbers, and try to get followers, but I wish you the best. Everybody, go check out Steve's podcast; give it a shot. And look forward to hearing from you again, Steve. I think this is the second time on the show now, and we always have a good chat, man.
33:14 SS: Great! Well, thanks for having me.
33:16 EC: Thank you.
Subscribe to be the first to get our expert-written articles and tutorials for developers!
All fields are required