Working in big projects can be difficult, however most of that difficulty comes from accidental complexity (mostly unnecessary complexity introduced by ourselves). When a code base reaches a certain dimension this frequently happens:
- Increase in the average time taken to locate where the functionality for a give feature is implemented
- Low quality changes and hacks are more frequent. Sometimes this happens because people lose patience, sometimes they don’t know better. In a bigger code base it is easier for these types of changes to slip in, because it’s just harder to notice them.
- Day to day ancillary coding tasks like checking code in, getting changes, waiting for integration tests to finish take longer.
Everything we can do to mitigate this is welcome.
If instead of looking at a big application as a big application, we instead look at it as a conglomerate of small applications working together, perhaps we can maintain the levels of productivity that are characteristic of green field projects, well beyond when a project reaches a respectable size.
Web applications are an ideal class of applications where we can apply this. The stateless disconnected nature of the web lends itself well to having small apps that work together but share very little code and ideally, no state between them.
When you are using a website today, outside of the request/response cycle, the server maintains no information about the client. A connection is open, an http request is sent to the server, the server sends back an http response. End of story.
The server knows about you, not because it keeps track of your IP address or any other similar mechanism. The server knows about you because when you sign in, the server gives you a cookie that contains information that only it understands (because it’s encrypted and signed, hopefully), and that uniquely identifies you. And on each subsequent request, you (your browser, really) sends it back, every time, until you sign out.
A cookie applies to a whole domain (e.g. blinkingcaret.com), meaning, each time you visit some page inside that domain (e.g. blinkingcaret.com/myawesomepage) the cookie is sent.
Nothing stops you from taking advantage of this to break your web application into smaller web applications, for example one that responds to yoursite.com/music
, the other to yoursite.com/movies
. They can be totally different apps, developed by different teams, different code bases. I promise you, if you use the same styles, no one will ever know.
For this to really kick in, try this, it will only take 5 minutes: open 2 instances of Visual Studio, on each do File -> New Project -> Asp.New Web Application -> MVC (set authentication to Individual User Accounts). After both visual studio instances finish creating the projects run them both at the same time. Register a user in one of them (that user will be logged in, since after registration you are automatically signed in). Now go to the other project’s tab and refresh the page. You are signed in on both! That other app doesn’t even have a database created yet. The default template uses the CreateIfNotExists database initializer, which didn’t even have a chance to run since you performed no action that involves an Entity Framework DbContext). Despite this, you’ll see your email/username at the top.
Example
Setting up a project this way involves several considerations. Namely:
- Sharing layout pages and styles
- Links between pages in different sub web applications
- How to setup your development environment
- Setup authentication and authorization in each of the web applications
- Share components between different sub web applications
I’ve setup a sample project in GitHub using ASP.NET MVC. It’s an hypothetical store that sells music and movies. The main application is configured to deal with signing in users and then there are two other sub web applications, one for music, and another for movies. All the apps are setup to run in (Local) IIS, you’ll have to setup the main app to run on port 48000 using IIS Manager or else you’ll get an error while opening the project in Visual Studio, also you will need to make sure all projects are built before running the main web application. The reason for using Local IIS will become clear in (3). Also, all the projects are in the same solution, this was just more convenient for me. There’s no reason not to have several solutions.
The example project has no database. I initially created it with a database, but in the end decided that as an example, it was not worth the hassle of having to deal with all the necessary configuration just to run a sample.
Signing in an signing out users is therefore “faked” by manually creating a ClaimsIdentity
and using Owin’s cookie middleware. If you are unfamiliar with this, check Security testing without committing to a particular authentication mechanism in ASP.NET MVC and What does it mean to be authenticated in ASP.NET MVC?.
1. Sharing layout pages and styles
The first hassle you’ll encounter when doing this is “how do I share the layout between each of the web applications?”. You might be tempted to use the Add Existing Item As a Link feature in visual studio for the _Layout.cshtml file. Unfortunately that is not supported for razor views. So the easiest thing to do is to have multiple _Layout.cshtml
files, one in each sub web app.
It’s not ideal, this is definitely a compromise. Fortunately however, this is only necessary for your layout file. You can host all your style sheets (and some javascript if appropriate) from your main web application and access it from your sub web applications.
2. Links between pages in different sub web applications
This is another compromise. You won’t be able to use MVC’s mechanisms to generate these links. Url.Action, Url.RouteUrl, Html.ActionLink
, etc won’t work. You’ll have to provide full links, for example, https://yourwebsite.com/musicstore/latestnews
to access the latest music news from the main web application. Thankfully this should be rare enough that it is not a major maintenance problem.
3. How to setup your development environment (machine key, local IIS)
You should setup your web applications to run in a local instance of IIS, instead of running them in IIS Express (this is what happens when you create a new web project and run it in visual studio with the default configuration). Thankfully, this is a very simple change in the project’s configuration (you can do it in your projects properties and using IIS Manager). Also, you won’t be able to use LocalDb
and you will instead have to use either full SQL Server, or SQL Server Express.
The reason why this is recommended is because you can setup the main application as a site in IIS and the sub applications as IIS applications within the site. This way they can all share the same port, i.e., they can all be located under http://localhost:48000
, for example. This makes certain tasks easier, for example, you wont have to deal with CORS if you want to perform ajax calls between the web applications (even though they are all in the same domain, if they are hosted in different ports, it’s as if they are in different “origins” in CORS terminology).
Another important thing to be aware is that the Machine Key must be set to the same value in all web applications. The machine key is used to encrypt and decrypt the cookies. When you run in IIS Express this is not an issue, however, in local IIS you’ll have to manually set the keys (if you ever had a web application behind a load balancer you probably have done this already, each of the web applications has to be configured with the same key). The easiest way to do this is to use IIS Manager, double click machine key, uncheck both automatically generate at runtime, use the generate keys funcionallity, and the copy those keys (validation + decryption key) to all other sub web applications.
4. Setup authentication and authorization in each of the web applications
You should setup authentication in all the web applications, however, the login/register/forget password/etc pages should only be in one place. Typically the main application, and you should setup the sub web applications to redirect the user to the main login page in the main app if he tries to access a resource and is not authenticated/has insufficient permissions. If you are using a security token service, you also need to have the configuration in all apps.
In case you are using the Owin cookie authentication middleware you have to be aware that you’ll have to handle the redirects, since the cookie authentication middleware normally does not allow redirects to urls “outside” of the application it is running.
An example will make this clearer. Imagine your main application, where the login page lives, is in http://localhost:4800/
(the login page is in http://localhost:4800/account/login
).
You have setup an action in the music controller to require the user to be authenticated. This action lives in a sub web app in http://localhost:4800/musicstore
. So if your user types in the browser http://localhost:4800/musicstore/music/buy
you want that user to be redirected to http://localhost:4800/account/login
.
To achieve this, this is how you would setup your cookie authentication middleware in the musicstore sub web application’s Startup.cs
:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = "ApplicationCookie",
LoginPath = new PathString("/account/login"),
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = context =>
{
context.Response.Redirect("http://localhost:48000/account/login");
}
}
});
The LoginPath = new PathString("/account/login")
is actually irrelevant (it would redirect to http://localhost:4800/musicstore/account/login
), since we are taking over the redirect mechanism by providing our handler to OnApplyRedirect
. This way we ensure that if the user is not authenticated he will always be redirected to the right login page, in the main web application.
5. Share components between different sub web applications
Apart from sharing razor
files there’s no limitations about sharing “components”. Say for example that you have a controller action that returns the latest music news in the music web application and want to show those news in the main web application home page. You can just make an Ajax call to fetch the html for the latest news from the main application to the music web application, and inject that html in the home page. There’s no need to deal with CORS, and no other inconvenience apart than not being able to use the MVC helpers to generate the url.
Conclusion
Splitting up your web application into several web applications may seem like a lot of work right off the bat, but it’s something you will only have to do once.
Compare that to the pain of having to deal with a project with hundreds of controllers and views; waiting for updates and check ins/commits/fetches pulls and pushes; waiting for integration tests to finish that are unrelated to your changes on your build server; having to deploy the whole application just because of a small change in a specific section of the app; having someone else’s change, maybe incorrect IoC configuration, floor the whole web site, etc…
Even if you are a small team working in a small project, the benefits of being able to deploy parts of the web application separately might be enough to make it worth it. There’s only a small price to pay up front, and a few things you have to be aware while going forward. In the end, maintaining several small applications is always easier than maintaining a big one, and if those small applications work indistinguishably, then why not?