I am going to create basic web API access to the contacts data I have been using in previous posts. To start with I added an API folder to my project to hold my API controller. Next I added a contacts controller to the API folder by right clicking on the folder and selecting add new item.
From the add new item under DNX selected Web API Controller Class, entered a name and clicked add.
From the resulting code I removed all the actions except for two get functions.
[Route("api/[controller]")] public class ContactsController : Controller { private readonly ContactsDbContext _dbContext; public ContactsController(ContactsDbContext dbContext) { _dbContext = dbContext; } // GET: api/values [HttpGet] public async Task<IEnumerable<ContactModel>> Get() { return await GetContacts().ToListAsync(); } // GET api/values/5 [HttpGet("{id}")] public async Task<ContactModel> Get(int id) { return await GetContacts() .Where(c => c.Id == id).FirstOrDefaultAsync(); } private IQueryable GetContacts() { var contacts = from c in _dbContext.ContactModels .Include(c => c.AddressModels) .Include(c => c.EmailAddressModels) .Include(c => c.PhoneModels) select c; return contacts; } }
The above code contains a lot of new concepts I am going to break it down more.
[Route("api/[controller]")] public class ContactsController : Controller
The first thing to notice is the route attribute on the class declaration. The route attribute is how the routing engine determines where to send requests. Using [controller] tells the routing engine to use the class name minus the word controller. For example the above route handles api/contacts.
private readonly ContactsDbContext _dbContext; public ContactsController(ContactsDbContext dbContext) { _dbContext = dbContext; }
The constructor takes the DbContext needed to access contacts. Note that the context is being automatically injected via the constructor thanks to the fact that ASP.NET 5 now comes with dependency injection out of the box.
// GET: api/values [HttpGet] public async Task<IEnumerable<ContactModel>> Get() { return await GetContacts().ToListAsync(); } // GET api/values/5 [HttpGet("{id}")] public async Task<ContactModel> Get(int id) { return await GetContacts() .Where(c => c.Id == id).FirstOrDefaultAsync(); }
First get function returns all contacts and the second returns a specific contact based on the contact’s ID.
private IQueryable<ContactModel> GetContacts() { var contacts = from c in _dbContext.ContactModels .Include(c => c.AddressModels) .Include(c => c.EmailAddressModels) .Include(c => c.PhoneModels) select c; return contacts; }
Note that the query contains three includes and each of the included classes contain a navigation property back to the main contact. For example here is the email address model.
public class ContactEmailModel { public int ContactId { get; set; } public int Id { get; set; } [EmailAddress] public string Address { get; set; } public ContactModel Contact {get; set;} }
All of the above compiles and seems to run fine, but will not provide a response. The navigation property for contact creates a circular reference that the response serializer throws an exception trying to serialize.
Thankfully the framework has a configuration option to work around this problem. In the ConfigureServices function of the Startup class add the following.
services.ConfigureMvcJson(options => { options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.All; });
The above options marks the Contact property as a reference and does not try to circularly serialize it.
Now by running the project and going to http://localhost:port/api/contacts/1 in the browser I get all the contact data related to the contact with an ID of 1. I recommend using something like Postman to make the result more readable if you don’t have a front end to display the data.
Hey Eric, I cannot find the ConfigureMvcJson() method. Is there a specific assembly I should reference?
Jerrie, ConfigureMvcJson() was replaced in beta 7. Try the following.
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.All;
});
Wonderful thanks!
This was my hack before:
services.Configure(options =>
{
var jsonFormatter = options.OutputFormatters.FirstOrDefault(formatter => formatter is JsonOutputFormatter) as JsonOutputFormatter;
if (jsonFormatter != null)
{
var settings = jsonFormatter.SerializerSettings;
settings.Formatting = Formatting.Indented;
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
settings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
settings.NullValueHandling = NullValueHandling.Ignore;
}
jsonFormatter = options.InputFormatters.FirstOrDefault(formatter => formatter is JsonOutputFormatter) as JsonOutputFormatter;
if (jsonFormatter != null)
{
var settings = jsonFormatter.SerializerSettings;
settings.Formatting = Formatting.Indented;
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
settings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
settings.NullValueHandling = NullValueHandling.Ignore;
}
});
And after your help, it now looks like this:
services.AddMvc()
.AddJsonOptions(options =>
{
var settings = options.SerializerSettings;
settings.Formatting = Formatting.Indented;
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
settings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
settings.NullValueHandling = NullValueHandling.Ignore;
});
Glad to help!
The get method return type should be Task instead of public async Task Get(int id)
Not sure what happen but the return type should be Task. I updated the post to reflect this. Is the async causing you issues?
Hi. I’m sorry, but it seems my comment was not correct either. I’m pretty sure I intended to write “return type should be Task” but for some reason it looks like I wrote only Task. Apologies.
Oh, ok. It looks like your site it removing the < / > tags…
Yes it is. At least it stuck in the post this round! Sorry for the confusion.
Yes it did. No worries :)
+ re: your question – I haven’t had issues with the async so far. IS there anything I should watch out for?
Not that I have hit so far. Seems to work like a charm.