5

I have achieved creating the WebApi itself and I can browse it from the browser and get the output.

The thing which is not working for me is that I am trying to consume the WebAPI from an MVC Controller, and I have written the code for calling the WebAPI in my "cshtml" view.

But it doesn't work, as I am getting the error in loading the page, I understand I am doing something wrong. So the first question would be: am I doing this correctly, or is it completely wrong to create a WebAPI part in an MVC Project and then try to consume it in the same MVC project from the controller?

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
user1570094
  • 169
  • 1
  • 3
  • Post your error. However, why would you want to consume the web API from the same application as I would assume the application already has access to all the code that the service is encapsulating? Why spend the extra overhead when you don't need to? – Maess Jan 22 '14 at 17:09
  • 1
    I fully agree with you, but is there any technical reason why I can't do it or it's just a wrong way of implementing it? – user1570094 Jan 22 '14 at 21:19
  • 1
    @Maess, MVC and WebAPI are designed to live in the same application. You can send your specific API controllers to a separate folder, such as "API" in the root directory to separate them, if you want to. This is why you have a `RouteConfig.cs` and a `WebApiConfig.cs` living in the same `App_Start` folder - because it's expected to leverage both in the same project. It's not required, though. – Mike Marks Jan 23 '14 at 17:00
  • @user1570094, you don't want to call your WebAPI from inside your View. You want to call your WebAPI from inside your MVC front end controller, as I describe below. – Mike Marks Jan 23 '14 at 17:01

1 Answers1

2

To answer your question, it's actually "as designed" and recommended to have your WebAPI and MVC client inside the same project. This is why you have both a RouteConfig.cs and a WebApiConfig.cs inside your MVC project. RouteConfig.cs is for your MVC controllers, and WebApiConfig.cs is obviously for your Api Controllers.

To have both in the same project is easy. What I do is add a folder named "API" in my root, and place all my WebAPI controllers in there. Keep in mind, and I'm sure you know, that the only difference between a WebAPI controller and an MVC controller is that a WebAPI controller inherits ApiController which is part of System.Web.Http (I believe) whereas an MVC controller inherits Controller which is part of System.Web.MVC.

Below is the proper way to make GET/PUT/DELETE/POST requests TO your WebAPI FROM an MVC front end. It doesn't matter if it's in the same project or not because you specify the WebAPI URL in your controller's constructor. If your WebAPI is on a different server than your front end MVC app, you will need to enable CORS support, which is a feature available in WebAPI version 2 and above.

This is the proper way to call a WebAPI from your front end, MVC client.

In your controller page, remove anything that has to do with DbContext, Entity Framework, etc. The reason is by default, the controller will want to perform CRUD operations by calling the DbContext, and we don't want this. We want to call the WebAPI instead to do this. When I refer to "Controller", I'm referring to the MVC controller, not the WebAPI controller.

First and foremost, declare some member variables in your MVC controller. The rest of your MVC controller will utilize these:

    HttpClient client = new HttpClient();
    HttpResponseMessage response = new HttpResponseMessage();
    Uri contactUri = null;
  1. In your MVC controller, create a constructor for your controller, as such:

    public ContactController()
    {
        // set base address of WebAPI depending on your current environment
        // the URL below, if the API is in the same project, will be something 
        // like "http://server/YourProjectName" - replace server with either
        // "localhost", etc.
        client.BaseAddress = new Uri("http://server/YourAPI/");
    
        // Add an Accept header for JSON format.
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
    }
    
  2. Replace the Index action's code with something like the following. Note that the only relevant pieces are the client.GetAsync() call and the var contacts assignment. Everything else is not necessary for the context of this problem. The value inside the client.GetAsync() should be the name of your controller, prepended by any custom routing you set up in your WebApiConfig.cs - in my case, I added the api part in my route to distinguish between API calls and normal calls:

    public ActionResult Index()
    {
        response = client.GetAsync("api/contact").Result;
        if (response.IsSuccessStatusCode)
        {
            var contacts = response.Content.ReadAsAsync<IEnumerable<Contact>>().Result;
            return View(contacts);
        }
        else
        {
            // add something here to tell the user hey, something went wrong
            return RedirectToAction("Index");
        }
    }
    
  3. Replace the Create action (the HttpPost action) with something like the following. Again, the only important piece is the client.PostAsJsonAsync() part - this is what calls the WebAPI's POST action which takes care of, in my case, inserting a new record into the database:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(Contact contact)
    {
        // Create a new product
        response = client.PostAsJsonAsync("api/contact", contact).Result;
        if (response.IsSuccessStatusCode)
        {
            return RedirectToAction("Index");
        }
        else
        {
            // add something here to tell the user hey, something went wrong
            return RedirectToAction("Index");
        }
    }
    
  4. Replace the Edit action (the non-HttpPost action) with something like the following. This was a little tricky because in order to edit, you had to retrieve the record first, so basically, the HttpPost version of Edit will contain somewhat similar code, with an additional line of code that performs the edit POST (PUT). Below, we're getting the response from the WebAPI by passing it a specific record ID. So, just like for Index (GET), we are doing the same thing only passing in the ID so we only get back one record. Then, we cast the response to an actual object that can be operated on in the View:

    public ActionResult Edit(int id = 0)
    {
        response = client.GetAsync(string.Format("api/contact/{0}", id)).Result;
        Contact contact = response.Content.ReadAsAsync<Contact>().Result;
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }
    
  5. Replace the Edit action (the HttpPost action) with something like the following. Below, we're getting the record to be edited by calling client.GetAsync() and passing in the primary key as a parameter (contact_id). Then, we're getting the RequestUri from that response and saving it. Then, we're calling client.PutAsJsonAsync() and passing in the Uri.PathAndQuery (what we just saved) as well as the object to be edited.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(Contact contact)
    {
        response = client.GetAsync(string.Format("api/contact/{0}", contact.contact_id)).Result;
        contactUri = response.RequestMessage.RequestUri;
        response = client.PutAsJsonAsync(contactUri.PathAndQuery, contact).Result;
        if (response.IsSuccessStatusCode)
        {
            return RedirectToAction("Index");
        }
        else
        {
            // add something here to tell the user hey, something went wrong
            return RedirectToAction("Index");
        }
    }
    
  6. Replace the Delete action (the non-HttpPost action) with something like the following. So again, we're getting the record from the database by simply calling client.GetAsync() and casting it to an actual object my app knows of.

    public ActionResult Delete(int id = 0)
    {
        response = client.GetAsync(string.Format("api/contact/{0}", id)).Result;
        Contact contact = response.Content.ReadAsAsync<Contact>().Result;
    
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }
    
  7. Finally, replace the Delete action (the HttpPost action) with something like the following. Again, we're doing something similar to that of the Edit action. We are getting the record to be deleted, casting it to an object, and then passing that object into a client.DeleteAsync() call, as shown below.

    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(int id)
    {
        response = client.GetAsync(string.Format("api/contact/{0}", id)).Result;
        contactUri = response.RequestMessage.RequestUri;
        response = client.DeleteAsync(contactUri).Result;
        return RedirectToAction("Index");
    }
    
Mike Marks
  • 10,017
  • 17
  • 69
  • 128
  • Thanks a lot for this detailed and great explanation and I completely go it. But in this similar scenario where I have a WebApi and MVC client in the same project, can I access the WebApi by writing the jquery in the view and that view is returned as an ActionResult by an MVC Controller? Please provide me an answer for this also, because I think it should work. I tried this but it didn't worked. Thanks. – user1570094 Jan 24 '14 at 21:23
  • Nice answer but what about a login? If we use [Authorize] attribute over WebAPI controller how do we have change code? Please, clarify it. Thank you! – NoWar May 06 '16 at 11:54
  • I ran into a problem using web-api-2 with your step 2 adding action index code like `public ActionResult Index()`. Based on this [question on why ... -ihttpactionresult-instead-of-httpresponsemessage](http://stackoverflow.com/questions/21758615/) i assume that for web-api-2 the code at step 2 needs to be changed to `public IHttpActionResult Index()`. Is this correct? – surfmuggle Oct 23 '16 at 22:57