Search code examples
asp.net-mvcrestweb-servicesfrontendweb-frontend

What is the practice in MVC used with ASP.NET WebAPI and httpClient when using async calls to get the Model


The story:

I have made a WebAPI to get all kinds of information. I am told that good practice is to as a front end developer, just send requests for information to the back-end through services/API's and use async and await keywords ect.

This is what I want to do:

In my homeController I have this:

namespace NHibernateMVC.Controllers
{
    public class HomeController : Controller
    {
        static HttpClient client = new HttpClient();

        static async Task RunAsync()
        {
            // New code:
            client.BaseAddress = new Uri("http://localhost:64498/");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        }

        static async Task<IEnumerable<Article>> GetArticleAsync(string path)
        {
            IEnumerable<Article> art = null;
            HttpResponseMessage response = await client.GetAsync(path);
            if (response.IsSuccessStatusCode)
            {
                art = await response.Content.ReadAsAsync<IEnumerable<Article>>();
            }
            return art;
        }

        public ActionResult Index()
        {   
             // This is where I am supposed to make a call to get the Articles to return a View who's model is of type IEnumerable<Article>
        }

The bottom line is that I want to initialize the Index page with a IEnumerable<Article> model, but get the articles using the httpClient, calling the WebAPI (which is just a GET request to api/ArticleAPI, a JSON is a response). How is this normally done in practice?? This is my first time doing this, I really do need help, and have searched a bit and found nothing that can help me thus far...


Solution

  • The controller needs to be refactored a little bit so that it can be called properly

    public class HomeController : Controller {
        const string ARTICLE_API_PATH = "api/ArticleAPI";
        static HttpClient client; = new HttpClient();
        static HomeController() {
            // New code:
            client.BaseAddress = new Uri("http://localhost:64498/");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        }
    
        async Task<IEnumerable<Article>> GetArticleAsync() {
            IEnumerable<Article> articles = new List<Article>();
            HttpResponseMessage response = await client.GetAsync(ARTICLE_API_PATH);
            if (response.IsSuccessStatusCode) {
                articles = await response.Content.ReadAsAsync<List<Article>>();
            }
            return articles;
        }
    
        public async Task<ActionResult> Index() {
            //call service method above.
            var articles = await GetArticleAsync();
            //pass the list as the model to the view
            return View(articles);
        }
    }
    

    The above works as a start but can also be improved for maintainability.

    getting the articles can be extracted into a service abstraction

    public interface IArticleService {
        Task<IEnumerable<Article>> GetArticleAsync();
    }
    
    public class ArticleService : IArticleService {
        const string ARTICLE_API_PATH = "api/ArticleAPI";
        static HttpClient client; = new HttpClient();
    
        static ArticleService() {
            client.BaseAddress = new Uri("http://localhost:64498/");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        }
    
        public async Task<IEnumerable<Article>> GetArticleAsync() {
            IEnumerable<Article> articles = new List<Article>();
            HttpResponseMessage response = await client.GetAsync(ARTICLE_API_PATH);
            if (response.IsSuccessStatusCode) {
                articles = await response.Content.ReadAsAsync<List<Article>>();
            }
            return articles;
        }
    }
    

    so that now the controller can be simplified to

    public class HomeController : Controller {
        private readonly IArticleService articleService;
    
        public HomeController() {
           articleService = new ArticleService();
        }
    
        public async Task<ActionResult> Index() {
            //call service method above.
            var articles = await articleService.GetArticleAsync();
            //pass the list as the model to the view
            return View(articles);
        }
    }
    

    In a more advanced scenario you can invert the creation of the service by having it injected into the controller but that topic is outside of the scope of this question.

    It would look something like this.

    public class HomeController : Controller {
        private readonly IArticleService articleService;
    
        public HomeController(IArticleService articleService) {
           this.articleService = articleService;
        }
    
        public async Task<ActionResult> Index() {
            //call service method above.
            var articles = await articleService.GetArticleAsync();
            //pass the list as the model to the view
            return View(articles);
        }
    }