One ASP.NET - Making JSON Web APIs with ASP.NET MVC 4 Beta and ASP.NET Web API
ASP.NET MVC 4 Beta came out last week. It's got lots of new features as well as some surprises that move us closer to the "One ASP.NET" idea. I talked about this a little in this week's MSDN Flash email newsletter (you can subscribe to MSDN Flash here; it's reasonably high signal, low noise). Here's part of what I said:
Don't think of ASP.NET as an island. It's a citizen of the larger community. More and more of ASP.NET is open source, and we push hard every day to stay open and be open. We want to make ASP.NET more pluggable, more open, more fun. We've got big things planned - some that will surprise you. I hope you'll join the conversation and the community.
Here's some of the stuff that's been improved in MVC 4.
New Features in the Beta
- ASP.NET Web API
- Refreshed and modernized default project templates
- New mobile project template
- Many new features to support mobile apps
- Recipes to customize code generation
- Enhanced support for asynchronous methods
- Read the full feature list in the release notes
You may have heard me talking about LEGO in the past, and showing how you can fit things together better with NuGet. I've mentioned One ASP.NET in the context of the new features in Web Forms as well. Here's a diagram I've shown internally a few times. We'll keep adding more information about how these fit together and what you can build with them on http://asp.net/vnext.
In fact, in the interest of focusing on One ASP.NET, the "WCF Web API" is now ASP.NET Web API and it comes with ASP.NET MVC now. Even though it ships with MVC 4 Beta today, don't let that take away from the One ASP.NET vision. You can use Web API in ASP.NET Web Forms no problem. That's kind of the point. ;)
Why do you want a Web API?
If your app - your business's data model - has an API, then suddenly your Web API is opened up to native apps, iPhone apps, Windows 8 apps, whatever, apps. It's Web Services. Remember those?
I can use XML or JSON or something else with my API. JSON is nice for mobile apps with slow connections, for example. You can call an API from jQuery and better utilize the client's machine and browser. You can make a Gmail type single page, or a hybrid; it's up to you.
How it all fits into One ASP.NET
The idea behind One ASP.NET is that I want folks to be able to make apps that have real-time components with SignalR, clean, simple APIs with Web API, all in one pages with KnockoutJS, pages with MVC, Web Forms or Web Pages, as well as existing ASP.NET systems like OData, ASMX, and more. I want open source projects like JSON.NET, KnockoutJS, SignalR, Backbone, MongoDB, Scaffolding, NHIbernate, Ninject (and the list goes on) to all play in the same ASP.NET LEGO sandbox. We'll put all these subcomponents on NuGet and they'll live alongside community components and you'll be able to build ASP.NET applications starting from some base template and add just the pieces you want. We are getting there. I want folks to use the parts they want, and swap out the parts they don't. Everything should work together. I've always said I want to open source everything we can as fast as Microsoft can take it, and I'll keep pushing if it kills me my boss.
NotTwitter
Lemme make a NotTwitter app real quick. Here's a quick model:
public class NotATweet
{
public int ID { get; set; }
public string Username { get; set; }
public string Text { get; set; }
public DateTime Published { get; set; }
}
I'll use the default scaffolding to get the UI, but then I'll install the MvcScaffolding extra NuGet package and scaffold it using a Repository pattern.
PS>Scaffold -ModelType NotATweet -DbContext NotTwitterContext -Scaffolder Repository -Force
Then I'll scaffold a controller that uses this Repo (you can do this from the UI or from the NuGet package console):
Scaffold -Controller NotATwitter -ModelType NotATweet -DbContext NotTwitterContext -Scaffolder MvcScaffolding.Controller -Repository
And here's the resulting Scaffolded UI.
The controller for my pages is standard fare. Now I'll add one via Add Controller to make an API for my NotTwitter application.
I'll change my route to make /api go to my app's API controller;
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{id}",
defaults: new { controller = "NotATwitterAPI", id = RouteParameter.Optional }
);
Here's my Web API controller code. A few things to notice. I'm talking to the same IRepository that the page controller uses. I'm returning HTTP Status Codes that are appropriate for each response. See how after the Create (where I POST a JSON representation of NotATweet) that I return HttpStatusCode.Created 201 and set the header's location to include the location of the new resource?
public class NotATwitterAPIController : ApiController
{
private readonly INotATweetRepository notatweetRepository;
public NotATwitterAPIController(INotATweetRepository notatweetRepository)
{
this.notatweetRepository = notatweetRepository;
}
// GET /api/notatwitterapi
public IQueryable<NotATweet> Get()
{
return notatweetRepository.All;
}
// GET /api/notatwitterapi/5
public NotATweet Get(int id)
{
var notatweet = notatweetRepository.Find(id);
if (notatweet == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
return notatweet;
}
// POST /api/notatwitterapi
public HttpResponseMessage<NotATweet> Post(NotATweet value)
{
if (ModelState.IsValid)
{
notatweetRepository.InsertOrUpdate(value);
notatweetRepository.Save();
//Created!
var response = new HttpResponseMessage<NotATweet>(value, HttpStatusCode.Created);
//Let them know where the new NotATweet is
string uri = Url.Route(null, new { id = value.ID });
response.Headers.Location = new Uri(Request.RequestUri, uri);
return response;
}
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
// PUT /api/notatwitterapi/5
public HttpResponseMessage Put(int id, NotATweet value)
{
if (ModelState.IsValid)
{
notatweetRepository.InsertOrUpdate(value);
notatweetRepository.Save();
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
// DELETE /api/notatwitterapi/5
public void Delete(int id)
{
var notatweet = notatweetRepository.Find(id);
if (notatweet == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
notatweetRepository.Delete(id);
}
}
Then I'll hit /api with Fiddler. Notice how JSON popped out the other end?
I'll change the accept header to Accept: application/xml and xml pops out. You can plug in your own and make Web API spit out iCal or whatever you like. You can make media formatters or even plug in others for the defaults. For example, here's a JSON.NET Formatter for Web API.
Now can we make NotTweets? You can use tools like the JSON Formatter to handcraft JSON for testing. Now I'll POST a Json serialized NotTweet:
Notice that the result is an HTTP 201 Created. If I then GET /api, I can see it's there:
I can also affect things with the URL and query strings like this GET /api?$orderby=Username HTTP/1.1 so I can so query composition without totally buying into OData-style URLs if I don't want to.
As I mentioned, I can use XML or JSON or something else with my API. JSON is good for mobile apps with slow connections, for example. You can call this API from jQuery and better utilize the client's machine and browser.
There's also the "calling" side of this, which is HttpClient. You can call out to other APIs like Twitter and authenticate using OAuth. Other people might call your API from the client side using jQuery and Javascript or from the server side using HttpClient.
Web API has many more possibilities than this example. Spend some time on Henrik's blog (he helped invent HTTP! Listen to him on his episode of the Hanselminutes Podcast)
We've got a new section up on the ASP.NET site http://asp.net/web-api with a lot of info. Here's some resources for easily adding an API to your app. You can even self-host your own Web API without IIS in a service or other application!
Related Links
- ASP.NET MVC 4 Beta for Visual Studio 2010:
- ASP.NET MVC 4 Web Platform Installer for Visual Studio 2010
- Download the standalone ASP.NET MVC 4 Beta installer executable
- Jon Galloway on ASP.NET MVC 4 Beta
- Henrik's blog
- Async Streaming in ASP.NET Web API
- Using ASP.NET Web API with ASP.NET Web Forms (see how it all plugs in together!)
- Using MongoDB with ASP.NET Web API (yes, Mongo)
- ASP.NET MVC 4 Developer Preview for Visual Studio 11 Developer Preview:
- ASP.NET Web API and HttpClient Available on NuGet
- NuGet Packages
- Web API hosted in ASP.NET: AspNetWebApi
- Self-hosted Web API: AspNetWebApi.Selfhost
- HttpClient including XML and JSON formatters: System.Net.Http.Formatting
- JsonValue for navigating and manipulating JSON: System.Json
- NuGet Packages
- Videos: Getting Started with Web API
- Tutorial: Your First Web API
- Video: Your First Web API
- Building HTTP services with ASP.NET Web API in MVC 4 Beta
- Creating a Web API that Supports CRUD Operations
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Rob - yes, links under Related Links for NuGet stuff
Simon - Can you be more specific? I've done JSON.net and IHttpHandler, Kayak and Owin, using a single .cshtml web page to return JSON, or just return json() from a controller. All are very lightweight. Returning Json() doesn't invoke any HTML building or page generation stuff (views).
Rob - We are all friends here! ;)
Ignat - I'll put something on bitbucket but have you tried any of the samples above?
It would be the ultimate web api extension :)
Thanks again for the article.
Authentication mode="Forms" works great with [Authorize(Roles = "Myrole")]
But how do my NonHuman client logon ?
Great article
I have read that the PUT and DELETE actions should be idempotent methods, meaning that multiple identical requests should have the same effect as a single request.
Looking at your code does this is not true for the delete method. At least the second call results in an exception thrown. If the entity does not exist even the first call would result in an exception.
Apart from that I am rather keen on the Web API.
Thanks for a great post!
Regards
Andreas Kroll
We did a prototype using Web API, SignalR and MVC3 and it all worked, but the lack of a lightweight server is a problem. So I'm now running Louis DeJardin's firefly with Nancy and will probably stay with that.
MVC4 looks great. But there is still that big unwieldy lego piece -the server- that steers me away from the entire stack.
Having to deal with http.sys , netsh, urlacl, running things as admin. There has to be a better way.
much similar to Rails 3 default restful routing
with xml and json format.
Thanks
This is really cool stuff.
The only doubt I have about this is in comparison to *.ws is the descriptional part. With web services it's quite easy to implement the service and send the wsdl to someone else (including the comments) and they will be able to use it.
Is there some sort of discovery method/extension for Web API?
BTW: The OpenID sign in doesn't work in Chrome
You are correct that DELETE is an Idempotent operation, but idempotency relates to how the server state is affected. In this case once the resource has been removed and a DELETE is sent you get a 404, which is completely valid as the server state is not effected.
Technically PUT should really compare the state being updated to the state of the resource and if it has not changed then it is no-op. However in this case it is just overwriting itself with the same values so from the client perspective nothing has really change.
Cheers
Glenn
In response to my first comment above, I think I've found a simple & valid work-around that doesn't break the IQueryable<> chain:
public IQueryable<NotATWeetViewModel> Get( ) { return _repo.ListAll( ).Select( x=> AutoMapper.Mapper.Map( x, new NotATweetViewModel() ) ); }
It still doesn't offload the mapping to an ActionFilter as I would like, but the end result is all that matters.
You can host Nancy on the WebAPI using the OWIN adapter for WebAPI. It's not quite an Open Source web server, but it does make the applications a little more portable since they don't depend on IIS.
More info here: http://owin.org/
Information on the GATE adapter for OWIN and WebAPI.
http://whereslou.com/2012/02/20/gate-adds-owin-support-for-the-new-asp-net-web-api-beta
Brilliant work. Can't wait to see these "surprises" too.
I can't find any info on how to use the Web Api from Web Pages (without MVC). Is it not possible? If yes, any docs?
I will try and add the POST and PUT examples soon, and I welcome any feedback!
I am interested in the One ASP.NET concept as it relates to Web API and SignalR. I don't know much about SignalR other that what I read from your post in August. My question is, how are these two services meant to be utilized in one ASP.NET application? What type of requests would each service handle?
Michael - I will do a post on WCF, Web API and SignalR together, OK?
Nuri - I'll see if I can do a post. You can find all docs (so far) at http://www.asp.net/web-api
I'm still waiting to see the iOS and WP7 version of NerdDinner. It seems to be missing from the must-have list....
So do we need to have 2 controllers? 1 extending from Controller base and another from ApiController base?
Is it true that you're not adding support for OData $select queries for the RTM?
Seems kind of a shame if you aren't.
namespace NotTwitter.Controllers
{
public class NotATwitterAPIController : ApiController
{
public NotATwitterAPIController() : this(new NotATweetRepository())
{
}
Thanks...again awesome work on MVC 4, it's really great!
I worked on MVC Razor v.1 for Webapplication.
I agree WEB-API is the best REST Complaince service tech available now.
But in application scenarios, one controller will be dealing with multiple views and View1 may call a method GetAdminUsers() and View2 may call GetCommonUsers().
In this scenario, how we can handle with WEB-API.
To summarize, how we can use custom action method names in WEB-API . . .
If i use entity framework, using à stored procedure, to gather data; lets say that i get the objekt from class that have id like below
Class myclass
{
Public int id;
}
I want to insert à link to the resppnse like below:
<myclass>
<id>10000</<id>
<link hef="bablablabla" rel="self">
<myclass>
how easy is that??
Thaks in advance
I do see other options that use Repository for normal controlller only?
http://www.mindstick.com/Articles/f50bdd96-7941-495c-aa53-169d9711a096/?Select%20Insert%20Update%20and%20Delete%20using%20Stored%20Procedure%20in%20ASP%20NET%20MVC4
http://www.c-sharpcorner.com/UploadFile/krishnasarala/select-insert-update-and-delete-with-Asp-Net-mvc/
Two questions:
1) Is there a web page somewhere that shows all the options for the Scaffold command line options?
2) Is there a way for the scaffold to look at the entity framework for keys and default values without having to modify the entity framework generated code?
Thanks,
Michael
Comments are closed.
I've been asking around in the official wep api forums but haven't had much luck. Any insight would be appreciated.. thanks! = )