Motivation
The default ASP.NET MVC Internet Application comes with forms authentication. It has all layers and classes in a single project. This is good for a simple and small
application, but for an enterprise application, I would like to have a better architecture. So, I changed the default Internet Application to have more layers
and put the layers in separate projects.
Analysis
In order to re-architecture this default application, I need to use the DDD (Domain Driven Design) concept to find the domain and services first. Because the core
functionality in this application is authenticating users with forms authentication, the domain is membership management. The membership management is actually implemented
in AspNetSqlMembershipProvider
which is the realization of Microsoft’s Provider pattern, so the Domain is actually hiding
behind the scenes. What are ChangePasswordModel
, LogOnModel
, and RegisterModel
, you may ask? First of all, they are Models, but they are View Models instead
of Domain Models, from my perspective. Domain model is the core of your application and should contain both the data and behavior to reflect the specific domain. ChangePasswordModel
,
LogOnModel
, and RegisterModel
really just help move data from the View to the Domain and vice versa. After identifying the domain, I am able to
decide what should stay in the default project and what should be moved to a different project to have a better separation of concern, reusability, and testability.
Change
The below steps are used to change the default application to a different architecture.
Step 1: Create View Models
Like I mentioned above, ChangePasswordModel
, LogOnModel
, and RegisterModel
are actually View Models from my perspective. So, I created a
ViewModels folder in the default project and created ChangePasswordViewModel
, LogOnViewModel
, and
RegisterViewModel
in it. All the places that used ChangePasswordModel
, LogOnModel
,
and RegisterModel
originally are refactored to use ChangePasswordViewModel
, LogOnViewModel
, and RegisterViewModel
.
Step 2: Create Utilities
There is an AccountValidation
class in the default application that is used to convert MembershipCreateStatus
to a description for displaying in the view. I created
a Utilities folder to contain it. This folder will contain all the common classes and helper classes. Therefore, ValidatePasswordLengthAttributes
is moved into it also.
Step 3: Create Server Interfaces
The default application already has a good decoupling for Membership and Forms Authentication functions to allow switching implementation in different
environments, like in Tests project, MockMembershipService
is used instead of AspNetSqlMembershipProvider
to allow testing logon, register, etc., functions
without connecting to the membership repository. However, I like to only keep the service interface in the web project. A ServiceInterfaces folder is created and
IFormAuthenticationService
and IMembershipService
are moved into it.
Step 4: Create Security Project
From step 3, you will know I like the implementation to stay in a separate project (assembly) to better decouple them from the contract and the caller.
The Demo.Web.Secruity project is created and AccountMembershipService
and FormAuthenticationService
are moved into it. There is a problem.
The AccountController
creates AccountMembershipService
and FormAuthenticationSerrvice
instances directly to provide for using
in the controller actions. I don’t want to let the Web project reference the Security project and seeing the concrete classes because this ruins the separation of concern.
This problem will be fixed in the following steps with Dependency Injection.
Step 5: Create Library
Before I can have Dependency Injection in place, I need to have some cornerstone first. The IoC container I use in here is Unity. It’s not the best IoC
container on the market, but it has all the features I need, like lifetime management, configuration file, etc. I created a Library folder in the folder
of the solution file and copied all the Unity assembly files into it.
In order to let the solution manage all the Unity files, I also created a Library solution folder and made references to all files in the Library folder.
Step 6: Create Base Project
For an enterprise application, there are always some utility and helper classes that need to be used in all projects. The Demo.Base project is created to contain those
classes. So far, I only have one interface IDependencyLocator
in it that is implemented in the project that has a dependency injection access point.
Step 7: Create Dependency Injection
ASP.NET MVC3 has built-in Dependency Injection support (DependencyResolver
), but I prefer to manage Dependency Injection via a Registry class. A WebRegistry
class is created in the Web project. It has DependencyLocator
to wrap up the IoC container. The FormsService
property and MembershipService
property
will query the IoC container to get the instance object.
The UnityDependencyLocator
binds with WebRegistry
in Global.asax.cs.
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
RegisterUnityContainer();
}
private void RegisterUnityContainer()
{
var container = new UnityContainer();
UnityConfigurationSection section = (UnityConfigurationSection)
ConfigurationManager.GetSection("unity");
section.Configure(container);
WebRegistry.DependencyLocator = new UnityDependencyLocator(container);
}
The dependency configuration is in the web.config:
<configSections>
<section name="unity"
type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration" />
</configSections>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<alias alias="IMembershipService"
type="Demo.Web.ServiceInterfaces.IMembershipService, Demo.Web" />
<alias alias="MembershipService"
type="Demo.Web.Security.AccountMembershipService, Demo.Web.Security" />
<alias alias="IFormsAuthenticationService"
type="Demo.Web.ServiceInterfaces.IFormsAuthenticationService, Demo.Web" />
<alias alias="FormsAuthenticationService"
type="Demo.Web.Security.FormsAuthenticationService, Demo.Web.Security" />
<container>
<register type="IMembershipService" mapTo="MembershipService">
<constructor />
</register>
<register type="IFormsAuthenticationService" mapTo="FormsAuthenticationService" />
</container>
</unity>
The AccountController
is changed to initialize the FormsService
and MembershipService
variables with WebRegistry
.
protected override void Initialize(RequestContext requestContext)
{
if (FormsService == null) { FormsService = WebRegistry.FormsService; }
if (MembershipService == null) { MembershipService = WebRegistry.MembershipService; }
base.Initialize(requestContext);
}
One more thing needs to be done here, which is to add:
xcopy /Y /F "$(SolutionDir)Demo.Web.Security\$(OutDir)\
"Demo.Web.Security.dll "$(SolutionDir)Demo.Web\bin"
in the Security project “Build Events -> Post-build event command line” to copy Demo.Web.Security.dll into the Web application’s bin folder because there is no
direct reference between the Web project and the Security project. The IoC container needs the assembly file to bind the implementation to the interface.
Step 8: Clean up
Finally, I have all the pieces here, I just need to remove the original Models folder from the Web project and refactor the entire solution to replace the old classes with
the new classes and layers.
Comparison
After the change, the View, ViewModel, Controller, and Service interface are still in the Web project. The Model layer is in AspNetSqlMembershipProvider
.
The Service implementation is in the Security project.
Before the change:
After the change:
Conclusion
What I did in this article is just to reflect my thoughts on how a web application with Forms Authentication should be organized. There is no big innovation. It’s just
a different idea on developing an application with ASP.NET MVC that has Forms Authentication for entitlement management.
Using the Code
The code is developed in Visual Studio 2010. SQL Server database aspnetdb is needed for membership that can be created with aspnet_regsql.exe.