Category - Tutorials
Code First EF with Web API 2
Code First Entity Framework with Web API 2
Overview
During this tutorial we will explore how to setup Entity Framework code first and then tie it into Web API 2. We will explore the basics of setting up Code First, look into the ways we can maintain a code first database with migrations and deal with some of the hiccups we have when tieing it into Web API 2.
Code first is a great choice when you are not dealing with a pre-created database. It allows you to control the structure of your database from your C# code. The addition of being able to use Migrations to control the applications further development, allows for a great way to version your application. With migrations you can make a change to your C# model, run a quick command to generate the upgrade and downgrade code, review it and then one run more command. The application will keep track of which migrations have been run or not in a special database table it will create.
The code for the tutorial is located at Code First With Web API 2 on GitHub. One of the big benefits of using GitHub is I will be able to provide direct links to each steps code as I have flagged each step as a release. This will allow me to link you to the download of code for example Download 1.1 or review the complete changes made in the commit for example Changes for 1.1 .
If you download version 2.1 or later, then go into the .nuget/Nuget.targets file and on line 16 specify that the DownloadNugetExe should be true.
If you download version 5.4 or later, tThen go into the root application web.config and under the EntityFramework -> contexts -> context node change the attribute disableDatabaseInitialization to false. Run the application and go to the tickets or ticket categories page. Go back into the root web.config and change disableDatabaseInitialization back to true.
For this tutorial I will presume you are building the application with me as we go. Let's get started!
Setup
-
To get started we are going to create a blank web application. Fire up Visual Studio and create a new project.
Choose an ASP.NET Web Application. Enter the name "CodeFirstWithWebAPI2" as both the name of the project and the name of the solution. Click OK and then on the "New ASP.NET Project" window select an "Empty" project and check the boxes to add the core references for MVC and Web API.
EF Code First
-
Install EF and setup Nuget Restore
First we need to install Entity Framework, let's open up the Package Manager Console. You can find this under the Tools menu -> Nuget Package Manager -> Package Manager Console.
In the console type:
install-package entityframework
Next, we want to be sure this solution will restore any missing packages, so in the Solution Explorer, right click on the Solution and choose "Enable Nuget Restore". Then build the application with "Ctrl+Shift+b"
-
Add Models for Ticket and TicketCategory
This is where the major work comes into play, first create a file called Ticket.cs in the Models folder with the following code:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web; namespace CodeFirstWithWebAPI2.Models { public class Ticket { //[Key] explicit defines this as the primary key. In this case it is not necessary //as EF will make that assumption based on the naming convention of the ID. [Key] public int TicketID { get; set; } //Listing this as required will make it non-nullable //The string length will be used to define the sql field. //Strings default to NVARCHAR therefore this will become an NVARCHAR(250) [Required] [StringLength(250)] public string Title { get; set; } [Required] public bool IsClosed { get; set; } [Required] public DateTime SprintDate { get; set; } [Required] public string Details { get; set; } //If we did not want tickets to be required to have a category we would //leave off the [Required] tag and make the data type either //int? or Nullable<int> [Required] public int TicketCategoryID { get; set; } //Relationships (virtual allows for lazy loading) public virtual TicketCategory TicketCategory { get; set; } } }
Now create a file called TicketCategory.cs in the Models folder with the following code:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web; namespace CodeFirstWithWebAPI2.Models { public class TicketCategory { [Key] public int TicketCategoryID { get; set; } [Required] [StringLength(150)] public string Name { get; set; } [Required] public bool IsActive { get; set; } //Relationships //ICollection is used to represent a category has many tickets. public virtual ICollection<Ticket> Tickets { get; set; } } }
The things to take note of are the data annotations. This is one way to define the SQL columns that will be created. You can also do it in the context, which we will talk about shortly and compare them then.
For now, you will note that I specified which field was the primary key by putting the annotation [Key]. Technically, because I used good naming conventions I didn't have to specify that. If you use ClassNameID as an int, Entity Framework will automatically assign it as the primary key. I like using the [Key] attribute for clarity in code. By using the [Required] and [StringLength] attributes not only do I define the fields as non-nullable and the size of the NVarChar fields, but MVC will use those attributes to determine validation. So you get double duty from the attributes.
As mentioned if I wanted a column to be nullable, for example a ticket to have an optional ticket category, you would remove the [Required] attribute and set the datatype as int? or Nullable<int>
The last part to mention is how you setup relationships. To setup a one to many, for the class that has a single connection per row (a ticket has a category), you have a property for that type. For the class that has multiple connections per row (a category has many tickets), you set the property for that type as an ICollection of those types. Both are set as a "virtual" type, which will tell Entity Framework to use lazy loading.
To setup many to many connections, you just set both classes to have an ICollection property of the opposing, and EF will create a bridge table. If you need to set additional properties, then you would need to make a class and you can use the [Key] attribute to define a composite primary key.
-
Add Connection String
Now that we have the models created, the next step is to define the name of the database that will be created by Entity Framework. This is best defined in the web.config. Open the root web.config file and add the following code between the closing of the appSettings and the opening of the system.web:
<connectionStrings> <add name="MyConnectionString" connectionString="Data Source=.\sqlexpress;Initial Catalog=CodeFirstWithWebAPI2;Integrated Security=true" providername="System.Data.SqlClient"></add> </connectionStrings>
Note we are defining the name of the database in the Initial Catalog property. The name of the connection string which we called "MyConnectionString", will be very important in the next step.
-
Add Database Context
Create a nwe folder in the root called DAL to hold our Data Access Layer files, then create a file called MyContext.cs with the following code:
using CodeFirstWithWebAPI2.Models; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Web; namespace CodeFirstWithWebAPI2.DAL { public class MyContext : DbContext { //Collections that can be access through the context are expressed as DbSet<>'s public DbSet<Ticket> Tickets { get; set; } public DbSet<TicketCategory> TicketCategories { get; set; } //Use the Constructor to define which connection string will be used by passing //the name to the parent constructor. This MUST match the name defined in the web.config public MyContext() : base("MyConnectionString") { } } }
Note that the context is a child of DbContext. We will use that classes constructor for ours passing in the name of our connection string from our web.config. This is one of the places that you have to use a "magic string".
I mentioned earlier that the data annotations are one way to define some instructions on how to create the SQL columns. Another way is by overriding the OnModelCreating method here in the context. Some people prefer to define the requirements here rather than using annotations in the class. When they set those rules in OnModelCreating method, they use the Fluent API. I prefer to use the class whenever I can as I feel that is where most developers will look for the code. However, this is something of debate and many feel that the Fluent API should only be used. I advise, go with whichever makes the most sense to you and the other developers that will work on the project.
Setup Views
-
We are about to create a lot of code in just a few clicks. First we need to create a Home controller. Right click on the Controllers folder and choose Add -> Controller, then choose an MVC 5 Controller - Empty, name the controller HomeController.
Inside of the public ActionResult Index that has been created for you, riht click in the whitespace and choose Add View -> choose an MVC 5 View -> leave the default options as they are in the popup per the below image and click Add.
You will note a lot of changes, if you see any popups about changes made, be sure to choose reload all. The packages.config was changed to add things like bootstrap, and those were automatically installed as well.
Entity Framework Controllers
-
Add Ticket and Ticket Category controllers.
For this tutorial we are going to let the scaffolding take care of our work and just keep the defaults. Right click on the controllers folder again, this time choose Add -> Controllers -> "MVC 5 Controller with views, using Entity Framework". In the Add Controller popup set the Model Class to be "Ticket" and the Data context class to be "MyContext". Leave the other defaults where they are per the below image then click Add.
Follow the same steps to create the TicketCategory controller by right clicking on the controllers folder, choose Add -> Controllers -> "MVC 5 Controller with views, using Entity Framework", in the Add Controller popup choose the Model Class of "TicketCategory" and then Data context class to be "MyContext". Leave the other defaults and click Add.
You should now have two more folders in the Views folder for Tickets and TicketCategories with 5 views in each.
-
Add Navigation
For this part, we are just going to add some links to the Views/Shared/_Layout.cshtml, in the default code that was added is an empty ul with a class of "nav navbar-nav", replace it with the below code:
<ul class="nav navbar-nav"> <li>@Html.ActionLink("Tickets", "Index", "Tickets")</li> <li>@Html.ActionLink("Categories", "Index", "TicketCategories")</li> </ul>
This is where things get interesting. Run the application and on the home page, you should see a link for Tickets and Categories. Click on either one and you will see a page with an empty table. However, if you check our sql express server you will see a new database called CodeFirstWithWebAPI2. This matches the name we set in the web.config for the Initial Catalog.
You can inspect the database and you will see the structure matches our code. Non-nullable fields, data types and even relationships exist. You'll note there is a table we did not define called _MigrationHistory, this table will be used by EntityFramework to track changes that we make when we turn on Migration shortly.
This is nice and grand, however, we are missing two important pieces. First, wouldn't it be great if the database started off with some default data? Second, what if we need to make changes to the database in the future after additional data has been input? We don't want to lose the data but we might have to add a new field. To create the default data, we will setup a Database Initializer, to make future changes to the database we will setup Migration.
Data Initializer
-
Add Initializer
Add a new class to the DAL folder called MyInitializer.cs with the following code:
using CodeFirstWithWebAPI2.Models; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Web; namespace CodeFirstWithWebAPI2.DAL { public class MyInitializer : DropCreateDatabaseAlways<MyContext> { protected override void Seed(MyContext context) { List<TicketCategory> categories = new List<TicketCategory>() { new TicketCategory { Name = "Feature", IsActive = true }, new TicketCategory { Name = "Enhancement", IsActive = true }, new TicketCategory { Name = "Bug", IsActive = true } }; categories.ForEach(x => context.TicketCategories.Add(x)); context.SaveChanges(); base.Seed(context); } } }
The parent class is extremely important. First, take note that it is a typed parent, that uses the context that we created earlier. You have a few choices for the parent class that effect what happens when the initializer is called. Above I set "DropCreateDatabaseAlways<>". You can also choose "CreateDatabaseIfNotExists<>", "DropCreateDatabaseIfModelChanges<>", "MigrateDatabaseToLatestVersion<>", and "NullDatabaseInitializer<>". These are all fairly explanatory on how they work.
However, this initializer will do absolutely nothing until we tell Entity Framework about it by adding some settings to our web.config
-
Define initializer in web.config
In order for Entity Framework to run the intializer, we need to make one more change. Open the root web.config and replace the entityFramework node with the following code:
<entityFramework> <contexts> <context type="CodeFirstWithWebAPI2.DAL.MyContext, CodeFirstWithWebAPI2" disableDatabaseInitialization="false"> <databaseInitializer type="CodeFirstWithWebAPI2.DAL.MyInitializer, CodeFirstWithWebAPI2"></databaseInitializer> </context> </contexts> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework"></defaultConnectionFactory> <providers> <provider invariantname="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer"></provider> </providers> </entityFramework>
It is very important that before running the application, you make sure you do not have the database open in SQL Server Management Studio or any other application. Entity Framework can not drop a database if it is currently in use. When you start the application and click on the categories link, you will see the three categories we set in the initializer.
Click the create button and create a test category. After you have saved it, close the site down and be sure to close the server down. Then restart the application, you should see the test category that you created disappear. This is because the database was dropped and recreated with the code from the initializer.
This moment is an invaluable spot to develop. You can test the application creating tons of test data that you don't want, then restart the application and get rid of all that crap.
-
Add Tickets
Let's build out a bit more of our default data. Modify the MyInitializer.cs file and add the following code before the base.Seed(context);
List<Ticket> tickets = new List<Ticket>() { new Ticket { Title="1.1 Setup Application", TicketCategoryID = 1, Details = "Create initial web application which will be an empty web application with the Web API and MVC libraries already installed.", SprintDate = new DateTime(2015, 01, 01, 5, 0 , 0), IsClosed = true }, new Ticket { Title="2.1 EF Code First", TicketCategoryID = 1, Details = "Install Entity framework with nuget and setup nuget restore. This will allow individuals that pull the repository down to build and run it.", SprintDate = new DateTime(2015, 02, 01, 5, 0 , 0), IsClosed = true}, new Ticket { Title= "2.2 EF Code First - Add Models", TicketCategoryID = 2, Details = "Add the ticket and ticketcategory models that will drive and define the creation of the SQL database. This is the part that makes it \"code first\". We will use data annotations to define our data types.", SprintDate = new DateTime(2015, 02, 01, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "2.3 EF Code First - Connection", TicketCategoryID = 2, Details = "Just setting up a basic sql connection string in the web.config that we can reference later in the context.", SprintDate = new DateTime(2015, 02, 01, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "2.4 EF Code First - Database Context", TicketCategoryID = 2, Details = "The database context will act like our sql connection. In fact in it's ctor we should specify what the name of the connection string is in the web.config, this is also where we define what collections are accessible. We defined the rules to create our models in the Model files. You can also use the fluent api by overriding the OnModelCreating method. Personally, I prefer to define things in the model where I am able, though it should be noted that I am creating a coupling of my models being built specifically for the entity framework. You can also use the fluent api to define this if you override the OnModelCreating method in the context. This choice should be based on the projects requirement.", SprintDate = new DateTime(2015, 02, 01, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "3.1 Setup Views", TicketCategoryID = 1, Details = "Add home controller and index view. This is typically where the bloat begins. This step will automatically pull in jquery, bootstrap, and bootstrap's requirement of glyphicons.", SprintDate = new DateTime(2015, 03, 01, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "4.1 EF Controllers", TicketCategoryID = 1, Details = "Here we will setup basic EF controllers with views and accept all the default c# and html. This will all be formatted for bootstrap.", SprintDate = new DateTime(2015, 03, 15, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "4.2 EF Controllers - Navigation", TicketCategoryID = 3, Details = "Added navigation to access the new areas of the site.", SprintDate = new DateTime(2015, 03, 15, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "5.1 Data Initializer", TicketCategoryID = 1, Details = "Add database initializer. The class it inherits specifically dictates how it works. Examples are: CreateDatabaseIfNotExists<>, DropCreateDatabaseAlways<>, DropCreateDatabaseIfModelChanges<>, MigrateDatabaseToLatestVersion<>, etc. The two drop database ones are useful for initial construction of the application. However, before too long you will be disabling the initialization and using migrations to avoid data loss.", SprintDate = new DateTime(2015, 04, 01, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "5.2 Data Initializer - Define in web.config", TicketCategoryID = 3, Details = "In order for the intializer to work, you have to define it in the web.config so that Entity Framework knows what to look for where. Due to the string driven configuration, pay close attention to the namespaces and class names. Also, take note of the attribute disableDatabaseInitialization as that will be disabled after you finish initial construction and start putting real data in the database.", SprintDate = new DateTime(2015, 04, 01, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "5.3 Data Initializer - Add tickets", TicketCategoryID = 2, Details = "Be careful how much you seed an application. At some point it'll be more tedious to try to input all of the data as c# objects and it would be better to create an sql script, or use the interface itself. Trust me, this comes from the experience of trying to get the seed data to do too much. Migrations are just around the corner and make future development extremely easy!", SprintDate = new DateTime(2015, 04, 01, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "5.4 Data Initializer - Disable initializer", TicketCategoryID = 2, Details = "The intializer is extremely easy to disable. Just change the disableDatabaseInitialization attribute in your web.config to true and you are done with initial construction and seed data!", SprintDate = new DateTime(2015, 04, 01, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "6.1 Migrations - Enable and run Update", TicketCategoryID = 1, Details = "Enabling migrations is a great way to allow your database to continue to evolve as the project evolves.", SprintDate = new DateTime(2015, 04, 15, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "6.2 Migrations - Add new property", TicketCategoryID = 2, Details = "When you have enabled migrations you can add or remove properties as the application evolves and the business needs change. During each iteration you'll add a new migration for that change.", SprintDate = new DateTime(2015, 04, 15, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "6.3 Migrations - Add Migration Property", TicketCategoryID = 2, Details = "By using the add-migration <name>, Entity Framework will analyze the models and look for any new changes. It will then create the C# needed to change the database to match the current code base. It will also provide up and down code so that you can revert the change later if desired. The change will take effect when you run update-database.", SprintDate = new DateTime(2015, 04, 15, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "6.4 Migrations - Add Migration Remove Property", TicketCategoryID = 2, Details = "To show that it will handle removing properties as well as new properties, we can comment out that new property and rerun the add-migration <name> and then update-database again.", SprintDate = new DateTime(2015, 04, 15, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "7.1 Web API - Add api controllers", TicketCategoryID = 1, Details = "Adding the controllers is very easy with the scaffolding, however, there are several gotchas that we have to deal with in order to get this working well with client side applications like Angular.", SprintDate = new DateTime(2015, 05, 01, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "7.2 Web API - Json and Circular Reference", TicketCategoryID = 3, Details = "By default web api returns xml. It is very easy to change that to render JSON instead. Not so easy to handle is the circular reference problem that comes about when working with Entity Framework, most especially when we are using Code First. First off, is simply \"Include\" the nested properties that we want. Web API will make no assumptions about that. Then we can use the JsonIgnore attribute to stop the circular reference.", SprintDate = new DateTime(2015, 05, 01, 5, 0 , 0), IsClosed = true }, new Ticket { Title= "7.3 Web API - Create a JSON Model", TicketCategoryID = 3, Details = "A better way to handle that, which of course means more code, is to create a JsonModel. Similar to the concept of a ViewModel, this gives us direct control over exactly what is rendered, and is another way to stop circular references.", SprintDate = new DateTime(2015, 05, 01, 5, 0 , 0), IsClosed = true } }; tickets.ForEach(x => context.Tickets.Add(x)); context.SaveChanges();
This must be added after the TicketCategories, so that those are created first. Technically, we don't need to have the context SaveChanges between each set, we could do one call at the end. However, that can make debugging something that goes wrong very difficult due to Entity Frameworks ability to roll back failed transactions.
Be sure you don't have the database open anywhere, restart the application and go to the Tickets page. You will notice it might take a few seconds to load as it rebuilds the database. This can be quite annoying when you have a large database, so typically, I don't build large applications this way. I typically use the initializer just to set some starting data and build the database just once. Let's see how to disable it.
-
Disable data initialization
We have two options here. One option is we can change the MyInitializer class to be a child of "CreateDatabaseIfNotExists<MyContext>", then if we delete the database and rerun the application Entity Framework will realize the database does not exist and create it. This will be a great option for when we finish the application. Typically, it is the option I will use. The other option is to run the application and make sure we have a database generated and then leave the MyInitializer class as a child of "DropCreateDatabaseAlways<MyContext>" and make a small change to the web.config by setting the attribute disableDatabaseInitialization to true per the below code:
<context type="CodeFirstWithWebAPI2.DAL.MyContext, CodeFirstWithWebAPI2" disableDatabaseInitialization="true"> </context>
What you will notice is that if you add another category or a ticket, close down the application and server, then restart it, the data you added will remain.
Presumably, this is the point that we have a deployable version of our code. So if we shipped this product and it was used for a while, we wouldn't want to destroy the database with our next version. So we need a way to update the database as we make changes to our code base, which leads us to Migrations.
Migrations
-
Enable Migrations and add property
Migrations are the way we can make changes to an existing database as our code and application grows. In order to get this going, first open up the Package Manager Console by going to Tools -> NuGet Package Manager -> Package Manager Console and then type:
enable-migrations
This will create a base migration that could be used to create the database, however, we won't use it since we have our own initializer. Entity Framework will disregard that migration if the database already exists. You can test this by going to the Package Manager Console and typing:
update-database
You will see a message that there are no pending migrations. Let's see what happens when we add a new property to a class. Open the Ticket.cs file and add the following property:
public string TheImportantPropertyIForgotAbout { get; set; }
Run the application again, and go to the Tickets page and you will see a wonderful page of awesome-error-ness... Entity Framework recognizes that the class does not match the database. That means we need to learn how to add migrations.
-
add-migration ticket-important-property
To fix the error, we need to go back to the Package Manager Console and type:
add-migration ticket-important-property
The above line will add a new file into the Migrations folder prefixed with the date and time the migration was created. The file has both an Up() and a Down() method, which allows you to update your database and even roll it back. To update it run the following command in the Package Manager Console:
update-database
If you run the application now and go to the Tickets page, you will no longer see the error. Of course the views have not updated for the new property, however, we are going to remove that property in the next step anyway.
-
Remove new property
Let's see what happens when we remove the property. Go to the Ticket.cs file and comment out the property we added before:
//public string TheImportantPropertyIForgotAbout { get; set; }
Build the application and rerun it, then go to the ticket page. Curious enough it does not explode as it did before. Interestingly enough, while you can't add a new property without an exception you can comment out certain properties. Of couse, if a view, or some code tried to access the missing property, we would get an exception.
-
add-migration ticket-important-not-needed
Even though the app currently runs, we should clean things up and make sure our code matches the database. So let's create a migration and run it by typing the following code into the Package Manager Console:
add-migration ticket-important-not-needed
Then run:
update-database
That covers the basics of using migrations. Next let's bring in the Web API and see how that works with Entity Framework code first.
Web API
-
Add Ticket and TicketCategories API controllers
Let's get Web API going, this will be a great chance to compare both and see that they each have their benefits. Additionally, Web API will present a few challenges with circular references that we will need to deal with.
A common misconception about controllers is that they have to be in specific folders. This is only true for Views (when you do not override it). For controllers, you can place them in any folder organized however you wish. In order to avoid issues with naming of files, lets create a new folder to hold our Web API controllers. Add a new folder called APIs. Then right click on the folder and choose Add -> Controller -> Web API 2 Controller with actions, using Entity Framework. Set the Model class as Ticket. The Data context class should default to MyContext and the name will default to TicketsController per the below image. Click Add.
Repeat these same steps to create the TicketCategories controller. Right click on the folder and choose Add -> Controller -> Web API 2 Controller with actions, using Entity Framework. Set the Model class as TicketCategory and click Add.
Build the application, and then run it. If you take note of the WebApiConfig file, you'll see that routes for WebAPI are accessed through the path api/{controller}/{id}, if you try to visit the path api/tickets or api/ticketcategories, you will see errors with serialization. This is due to relationships we setup in our code first models. You'll also notice that the error comes across as xml. A more effecient way to transfer data would be JSON. Next we will explore how to change web api to give us JSON responses and fix the serialization issues.
-
Switch API to use JSON
To change Web API to use JSON it is actually very easy, all you have to do is going to the App_Start/WebApiConfig.cs and add the following code after the comment in that file:
using System; using System.Collections.Generic; using System.Linq; using System.Net.Http.Headers; using System.Web.Http; namespace CodeFirstWithWebAPI2 { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API configuration and services //Switching Web API to send Json config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html")); // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
You can now build the application. However, you will still have the serialization error from before. After building the application if you go to your root/api/tickets, you'll see the serialization error from before. If you look closely, you'll see it doesn't know what to do with ticket categories. To fix this we have to make choices about the properties that reference other models.
First, let's go into Models/TicketCategory.cs and tell the application that whenever Entity Framework loads a TicketCategory instance for JSON it should ignore the reference to Tickets by adding an attribute above the property for Tickets called JsonIgnore per the code below:
//Relationships //ICollection is used to represent a category has many tickets. //One way to stop circular reference with EF and Web API [JsonIgnore] public virtual ICollection<Ticket> Tickets { get; set; }
We could make a similar change to the TicketCategory property in the Ticket model, however, when you pull all tickets or even just one, I'd like to have information about the TicketCategory for each Ticket. At this time if you build and run the application while a route like root/api/tickets/1 will properly load the JSON for a single ticket, if you try to access the primary get route of root/api/tickets it will still blow up. Because the application freaks out about the nested property when you are grabbing multiple tickets. To fix that we will go into the GetTickets() method of the APIs/TicketsController and change it per below:
// GET: api/Tickets public IQueryable<Ticket> GetTickets() { //Include the TicketCategory information return db.Tickets.Include("TicketCategory"); }
Build and run the application, you can now access the api/tickets, api/tickets/1, api/ticketcategories, and even api/ticketcategories/1 without any issues.
-
Create TicketCategoryJsonModel
This is not typically how I handle these problems. The best way is to minimize or limit what data is being passed out to minimize traffic, something along the lines of a "Model/ViewModel/View" pattern. Create a new folder in the root called JsonModels and add a new cs file named TicketCategoryJsonModel.cs with the following code:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace CodeFirstWithWebAPI2.JsonModels { public class TicketCategoryJsonModel { public int TicketCategoryID { get; set; } public string Name { get; set; } public bool IsActive { get; set; } public int TicketCount { get; set; } } }
Now let's configure our APIs/TicketCategoriesController to use our new model by adding a using statement for our new model and changing the GetTicketCategories() method per the below code:
using EntityFramework_with_WebAPI.JsonModels; //....bunch of other code.... // GET: api/TicketCategories public IQueryable<TicketCategoryJsonModel> GetTicketCategories() { var ticketCategories = from t in db.TicketCategories select new TicketCategoryJsonModel() { TicketCategoryID = t.TicketCategoryID, Name = t.Name, IsActive = t.IsActive, TicketCount = t.Tickets.Count }; return ticketCategories; }
We have basically configured our get route to build a collection of our specialized model which includes the count of tickets per category, which could be useful information.
Typically, I will create models such as this for my routes allowing me some extra sense of security by having it disregard properties that perhaps I don't want to allow them to update, or create. Or by making the api responsible for returning the data my views need. Otherwise, the views have to do those counts.
Conclusion
-
Final Words
Most of my code first knowledge came from this tutorial which is updated periodically. Getting Started with Entity Framework 6 Code First using MVC5 If you are at all interested in exploring more about code first, I strongly suggest going through that tutorial.
The benefits of Web API comes into play when building client side applications with libraries like Angular. If you consider the process of loading the tickets, if you view the MVC page at /tickets then right click and view source, compare that to the route api/tickets and you will start to see how much less information is processed. Especially as you consider the html requests for all of those css, js and image links that the browser has to process as well to build the page.
In my next tutorial I will use this base application to build out an angular application allowing us to truly compare a straight MVC site vs an SPA (single page application) with Angular.
You must be logged in to comment.