MVC5.1 with Web API 2 and AngularJS

J86 picture J86 · Feb 1, 2014 · Viewed 10.9k times · Source

I am working on a side project to teach myself AngularJS and Web API and how the two can work together nicely.

I have good ASP.NET MVC knowledge, but I still can't get my head around AngularJS and Web API and how all three can work together.

At the moment, I have a Web API Controller with the following code:

public class PlanController : ApiController
{
    [Route("api/thing")]
    public HttpResponseMessage Post(ThingVM model)
    {
        HttpResponseMessage response;

        if (ModelState.IsValid)
        {
            using (var context = new MyContext())
            {

                var thing = new Thing();

                context.Thing.Add(thing);
                context.SaveChanges();
                response = Request.CreateResponse(HttpStatusCode.Created);
                string uri = Url.Link("GetThingById", new {id = thing.Id});
                response.Headers.Location = new Uri(uri);
            }
        }
        else
        {
            response = Request.CreateResponse(HttpStatusCode.BadRequest);
        }
        return response;
    }
}

In my Create.cshtml view I have the ng-app directive and I have created a JS controller and placed the ng-controller directive around the form, and have pointed it at the JS controller.

But here I am stuck. First of all, how do I bind my ThingVM.cs ViewModel to Angular? Do I need to return a JSONResult on my MVC controller? If Yes, how? Cause I tried, the following, and it isn't compiling.

[HttpGet]
public JsonResult Create()
{
    using (var context = new MyContext())
    {
        var model = new ThingVM();
        return Json(model);
    }
}

Assuming I get that to work, how do I bind it to AngularJS, so that it knows what my ViewModel structure is like? Because my ThingVM has many levels of complexity.

Finally, how do I handle the form submission, so that angular points at my Web API Controller for the POST request.

Answer

Khanh TO picture Khanh TO · Feb 2, 2014

In MVC SPA like angular, you should separate models from views. I would suggest that your asp.mvc is where you serve your views (HTML) and your asp.net web api is where you serve your models (JSON) with CRUD operations.

Your asp.net mvc controller:

[HttpGet]
public ActionResult Create()
{
    return View(); //this return Create.cshtml
}

Your asp.net api controller:

public class PlanController : ApiController
{
    public ThingVM Get()
    {
        using (var context = new MyContext())
        {
            var model = new ThingVM();
            return model;
        }
    }

    public HttpResponseMessage Post(ThingVM model)
    {
        HttpResponseMessage response;
        //It's better to write the modelstate validation as an attribute. See improvement suggestion below
        if (ModelState.IsValid)
        {
            using (var context = new MyContext())
            {

                var thing = new Thing();

                context.Thing.Add(thing);
                context.SaveChanges();
                response = Request.CreateResponse(HttpStatusCode.Created);
                string uri = Url.Link("GetThingById", new {id = thing.Id});
                response.Headers.Location = new Uri(uri);
            }
        }
        else
        {
            response = Request.CreateResponse(HttpStatusCode.BadRequest);
        }
        return response;
    }
}

Your angular controller, here I use $http for quick demonstration. In real app, you could try angular resource to create a REST client

app.controller("planController", function ($scope, $http){
    $scope.thingVM = $http.get("api/Plan"); //load view model as json from web api

    $scope.saveThingVM = function(){
          http.post("api/Plan",$scope.thingVM); //send a post request to web api to update 
    }
});

Your Create.cshtml could be like this:

<form ng-submit="saveThingVM()" ng-controller="planController">
   <input ng-model="thingVM.Name" type="text"></input>
   <input type="submit">Save model</input>
</form>

Improvement suggestion:

Model validation is a cross-cutting concern, it's better to write the logic as an attribute to reuse the logic. Take a look at my another answer at How can I centralize modelstate validation in asp.net mvc using action filters?