Applying styles to the WebForms design view
One of the best developer experience (DX) tools of ASP.NET WebForms is displaying a design view of what the code would look like when rendered during design time. This makes a lot of sense in the natural evolution of .NET tooling when bringing WinForms to the web through WebForms.
Mostly this works out of the box, but there are a couple tricks to making sure stylesheets show up correctly and consistently across various pages during both design time and run time. Here's a quick run through of how to apply stylesheets so they show up in WebForms master layout, pages, and controls.
Let's say there's site.css
file that has a single CSS rule in it:
file: styles/site.cssh1,h2,h3 {
background: lightblue;
padding: 5px;
}
Master Layout #
We'll add the styles to our site.master
page so it's applied everywhere, but we also want to make sure it shows up at design time as well.
Here are three different ways to add a stylesheet to master:
file: site.master<link type="text/css" rel="stylesheet" href="./Styles/Site.css?v=2" />
<link type="text/css" rel="stylesheet" href="<%= Page.ResolveUrl("~/Styles/Site.css") %>?v=2" />
<link type="text/css" rel="stylesheet" href="~/Styles/Site.css?v=2" />
-
The first option,
./Styles/Site.css
, will render the stylesheet at design-time because the master file is sitting at the same folder root as the Styles folder, but will fail at runtime when a request is made to a different location within the application.For example, a request to
/Patient/Search.aspx
would look for the styles folder in a sibling directory at the following location/Patient/Styles/Site.css
-
Another way to dynamically resolve the URL for each page is to use the ASPX View Engine syntax to inject server side code using
<%= %>
and callPage.ResolveUrl()
which will dynamically get the correct path for theSite.css
file based on which page is getting calledHowever, there's a limit to how much code Visual Studio will try to execute at design time in trying to determine what the page will look like, and VS does not call the code render block at design time.
-
The third option leverages the application root operator (
~
) in one of several tags that ASP.NET will automagically resolve for each request.The bonus here is that it will resolve at run-time and design-time
So using option 3, the master page will render correctly like this:
NOTE: There are also a couple other ways to include resources using the ASP.NET bundler with
Microsoft.AspNet.Web.Optimization.WebForms
, but that's a whole other discussion
Page Control #
The pages design view is always rendered inside the master page each page is bound to, so as long as the master page is styled, the page content will be as well.
Here's a simplified master > page inheritance structure:
file: site.master<%@ Master Language="C#" AutoEventWireup="true" CodeFile="Site.master.cs" Inherits="Site" %>
<!DOCTYPE html>
<html>
<head runat="server">
<title> Designer Styles Demo </title>
<link type="text/css" rel="stylesheet" href="~/Styles/Site.css?v=2" />
</head>
<body>
<asp:ContentPlaceHolder id="MainContent" runat="server">
<!-- placeholder for content -->
</asp:ContentPlaceHolder>
</body>
</html>
file: default.aspx<%@ Page MasterPageFile="~/Site.Master" Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="Home" %>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
<h2>Home Page</h2>
<p>Here's some home page content</p>
</asp:Content>
And here's what the designer will look like:
User Control #
The last piece that can be tricky to style is custom user controls that are dropped into pages. That's because they don't have a strongly typed dependency on any master page or page control. At runtime, they'll gain access to site-wide styles when used in an existing page, but there's no way to guarantee where they'll be used at design time.
Here's a simplified Page > Control inheritance structure:
file: search.aspx<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Search.aspx.cs" Inherits="Patient_Search" MasterPageFile="~/Site.Master" %>
<%@ Register Src="~/SearchControl.ascx" TagPrefix="uc1" TagName="SearchControl" %>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
<h2>Search Patient</h2>
<uc1:SearchControl runat="server" ID="SearchControl" />
</asp:Content>
file: SearchControl.ascx<%@ Control Language="C#" AutoEventWireup="true" CodeFile="SearchControl.ascx.cs" Inherits="Patient_SearchControl" %>
<h3>Patient Search Control</h3>
If we look at the current design view for SearchControl.ascx
there won't be any styling applied to the <h3>
:
There are two potential workarounds to applying styles to the designer but excluding them from the runtime payload:
If Statement #
One strategy is to use an if statement inside an embed code block. While this will always execute to false, it's enough to pull in the stylesheet during design time like this:
<% if(false){ %><link href="./Styles/Site.css" rel="stylesheet" type="text/css" /><% } %>
Visible=False #
Another possibility is to include runat="server"
attribute which will convert any tag to be server rendered and allow you to also add Visible="False"
like this:
<link type="text/css" rel="stylesheet" href="../Styles/Site.css" runat="server" Visible="False" id="DesignerStyles" />
In both cases, the user control will now render like this in the designer view:
In this example, the styling of a <h3>
tag might be trivial, but in situations where the user control is taking on a lot of rendering, it can be extremely helpful to have up to date styles when layout out the page.
Wrap Up #
You should include stylesheets on master in a way that's accessible to both the designer and the runtime like this:
<link type="text/css" rel="stylesheet" href="~/Styles/Site.css?v=2" />
If you want styles in the design view for user controls, you can add them like this:
<!-- designer styles -->
<link type="text/css" rel="stylesheet" href="../Styles/Site.css" runat="server" Visible="False" id="DesignerStyles" />
One final note of caution that using the designer as a drag and drop UI builder can be tricky on the web and break content flow by statically positioning elements. But putting that aside, as a read-only view, the ability to have but having an immediate feedback loop on what the rendered page will look like when editing is simply amazing.