I started going through the process of deconstructing John Papa’s amazing CodeCamper Single Page Application project and trying to break it down to a basic stripped down project template. I love a lot of what was accomplished in that project (great DI, awesome Repositories and patterns, etc.)! While breaking down the project, I’ve learned quite a bit. I’ve posted the full source of the template to my github, but its still a work in progress. Its in pretty good shape right now. Be warned that it does use some slightly more advanced stuff in the backend layers (data, data.contracts, models, unit of work, repositories, dependency injection, etc.) However the template is setup in a way that it should be pretty easy to manipulate to your needs without fully understanding everything under the hood.
Here are the general steps involved in working with the template, modifying it to suite your needs, and getting up and running:
(note the template is already up and running, but uses “samplemodel” as the only data object)
Step 1: Setup your Data Models
In the App.Model project, you will store all of your basic POCOs (plain ol’ class objects) for your data models. There’s really nothing fancy going on in here, just setup your classes.
Note that the models defined here will be used throughout the entire solution, including the MVC web project. Any specific ViewModels you want to use you should still store in the Models folder of the web project (and should be kept in the presentation layer, separate from the business logic).
Be sure to also define a DbSet for each of your models in the main AppDbContext class in App.Data.
Error Warning: In some cases, (many probably) you will have a navigation property in your model(s) that is used to define a 1-many relationship. These are typically great because you can loop through your model and find all related models simply by using this navigation property. However, when serializing your models to JSON later this typically will result in this awesome error:
A circular reference was detected while serializing an object of type …
If this is the case, be sure to include [JsonIgnore] above your virtual collections as well as include using Newtonsoft.Json in your model.cs file (Newtonsoft.Json can be included via NuGet). I spent a good hour searching on how to fix this as well as a lot of trial and error and this simple solution seemed to do the trick!
Step 2: Setup your model’s configuration including relationships, foreign keys, etc
Any special configurations you need to make with regard to your models and how they behave (their relationships, foreign keys, etc) will need to be created in the App.Data/Configuration folder. Be sure to add those to the App.Data -> AppDbContext class in the OnModelCreating method. Adding each configuration method to the modelBuilder.Configurations list.
Step 3: Define the Interfaces for any of your model’s custom Repositories
The App.Data project sets up a very nice base Repository that handles most CRUD operations including GetByID and GetAll. Anything else you specifically want to set up you will define in the IRepositories folder of the App.Data.Contracts project. You don’t have to create a custom repository for your models if you aren’t doing anything other than the basic CRUD that’s already taken care of.
Make sure you include each of the new interfaces for your repositories in the App.Data.Contracts -> IAppUow interface and in the actual App.Data -> AppUow class.
Step 4: Create the actual Repositories for your models
Matching the interfaces you created for your models repositories in step 3, actually define those methods now in App.Data -> Repositories. Note that you don’t have to create a repository for each of your models..
Be sure to include your new Repositories in the GetAppFactories method of App.Data -> Helpers/RepositoryFactories.
Step 5: Start working on the actual WebAPI
This will be the core of your development. Using the WebAPIs defined in the Controllers folder of the App.Web project, define a controller for each of your data models (that will need to be accessible via the front end). Each controller will contain the CRUD operations that the website will actually communicate with. Each method in the controllers will use the AppUow object which will be injected automatically using Ninject (configured via the IocConfig in App_Start).
Good or bad, the WebAPI is basically acting as our Service layer here. Justifiably we could (should) pull that logic out into yet another project. However for the purposes of the SPA, this is probably not necessary.
Step 6: A few things about the front end
- The web.config has webpages:Enabled set to true in the appSettings keys. This is to allow us to have basic index.cshtml file in the root of the project (and have it not require a controller and all the bells and whistles). The root index.cshtml file is the only “page” that our SPA will load.
- The Views folder also just contains basic HTML pages. These are all loaded into the single page using basic razor RenderPage calls. This is really just more for organization..
- The Scripts folder is broken into app and lib folders. The App folder contains all of your custom code (views, models, templates, routers, controllers, whatever, whatever, etc). The Lib folder contains our 3rd party libraries and other dependencies (Backbone.js, Underscore.js, Require.js, Moment.js, etc, etc, etc)
- The app is currently setup to use Require.js:
- index.cshtml only loads jquery (from Google cdn) and require, with requires data-main attribute set to “main” (which will load main.js)
- main.js is the “bootstrapper” of our app, which sets up require and would typically load in our actual app (app/app.js?) and any dependencies. In this case its just loading a test class (script).
- test.js is defined as a require.js module, and proves communication with our WebAPI using REST. It also tests the dependency injection of require.js by referecing test_model as being required.
- test_model.js is just a generic model created using another module definition of require and only exists to test our require.js setup (see bullet above)
- At no point did I ever worry about making jquery a dependency or deal with any of that stuff. I found it just easier to simply load jquery via Googles CDN and avoids a lot of minor headaches and config issues etc.
I will continue to maintain the template, hopefully getting it more and more setup to use Backbone, and tweaking a few minor things with the multiple layers (maybe adding that Service layer I mentioned, etc.).
Hope you find this post useful, and the template helps you out and saves you time working on your next project!
Full credit given to John Papa et al for the incredible work on the Code Camper project!