HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Consuming an ASP.NET Web API call in an MVC controller

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
mvcconsumingcontrollercallnetwebapiasp

Problem

In a new project I am creating for my work, I am creating a fairly large ASP.NET web API. The API will be in a separate Visual Studio solution that also contains all of my business logic, database interactions, and Model classes.

In the test application I am creating (which is ASP.NET MVC4), I want to be able to hit an API URL I defined from the control and cast the return JSON to a Model class. The reason behind this is that I want to take advantage of strongly typing my views to a Model. This is all still in a proof of concept stage, so I have not done any performance testing on it, but I am curious if what I am doing is a good practice, or if I am crazy for even going down this route.

Here is the code on the client controller:

public class HomeController : Controller
{
    protected string dashboardUrlBase = "http://localhost/webapi/api/StudentDashboard/";

    public ActionResult Index() //This view is strongly typed against User
    {
        //testing against Joe Bob
        string adSAMName = "jBob";
        WebClient client = new WebClient();
        string url = dashboardUrlBase + "GetUserRecord?userName=" + adSAMName;
        //'User' is a Model class that I have defined.
        User result = JsonConvert.DeserializeObject(client.DownloadString(url));
        return View(result);
    }
. . .
}


If I choose to go this route, another thing to note is I am loading several partial views in this page (as I will also do in subsequent pages). The partial views are loaded via an $.ajax call that hits this controller and does basically the same thing as the code above:

  • Instantiate a new WebClient



  • Define the URL to hit



  • Deserialize the result and cast it to a Model Class



So it is possible (and likely) I could be performing the same actions 4-5 times for a single page.

Is there a better method to do this that will:

  • Let me keep strongly typed views



  • Do my work on the server rather than on the client (this is just a preference si

Solution

Testing?

Then create a simple unit test and test your API controller or your business layer classes directly.
If you want to create an integration test or need a cleaner solution in production code read further.

Wrapping the API calls

This is just a disposable wrapper around the WebClient which can be easily reused.

public abstract class WebClientWrapperBase : IDisposable
{
    private readonly string _baseUrl;
    private Lazy _lazyClient;

    protected WebClientWrapperBase(string baseUrl)
    {
        _baseUrl = baseUrl.Trim('/');
        _lazyClient = new Lazy(() => new WebClient());
    }

    protected WebClient Client()
    {
        if (_lazyClient == null)
        {
            throw new ObjectDisposedException("WebClient has been disposed");
        }

        return _lazyClient.Value;
    }

    protected T Execute(string urlSegment)
    {
        return JsonConvert.DeserializeObject(Client().DownloadString(_baseUrl + '/' + urlSegment.TrimStart('/')));
    }

    ~WebClientWrapperBase()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(false);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_lazyClient != null)
        {
            if (disposing)
            {
                if (_lazyClient.IsValueCreated)
                {
                    _lazyClient.Value.Dispose();
                    _lazyClient = null;
                }
            }

            // There are no unmanaged resources to release, but
            // if we add them, they need to be released here.
        }
    }
}


Creating a "strongly typed proxy":

class StudentDashboardClient : WebClientWrapperBase
{
    public StudentDashboardClient()
        : base("http://localhost/webapi/api/StudentDashboard/")
    {
        //just for compatibility
    }

    public StudentDashboardClient(string baseUrl)
        : base(baseUrl)
    {
    }

    public User GetUserRecord(string userName)
    {
        return Execute("GetUserRecord?userName=" + userName);
    }
}


And then passing to your controllers where it's needed:

public class HomeController : Controller
{
    private readonly StudentDashboardClient _studentDashboardClient;

    public HomeController(StudentDashboardClient studentDashboardClient)
    {
        _studentDashboardClient = studentDashboardClient;
    }

    public ActionResult Index()
    {
        return View(_studentDashboardClient.GetUserRecord("jBob"));
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _studentDashboardClient.Dispose();
        }
        base.Dispose(disposing);
    }
}


Note that the controller now have a parameterless constructor so you will need a solution to instantiate controllers this way for example a DI framework with MVC support like Ninject.

What you gain? Cleaner code.

Code Snippets

public abstract class WebClientWrapperBase : IDisposable
{
    private readonly string _baseUrl;
    private Lazy<WebClient> _lazyClient;

    protected WebClientWrapperBase(string baseUrl)
    {
        _baseUrl = baseUrl.Trim('/');
        _lazyClient = new Lazy<WebClient>(() => new WebClient());
    }

    protected WebClient Client()
    {
        if (_lazyClient == null)
        {
            throw new ObjectDisposedException("WebClient has been disposed");
        }

        return _lazyClient.Value;
    }

    protected T Execute<T>(string urlSegment)
    {
        return JsonConvert.DeserializeObject<T>(Client().DownloadString(_baseUrl + '/' + urlSegment.TrimStart('/')));
    }

    ~WebClientWrapperBase()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(false);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_lazyClient != null)
        {
            if (disposing)
            {
                if (_lazyClient.IsValueCreated)
                {
                    _lazyClient.Value.Dispose();
                    _lazyClient = null;
                }
            }

            // There are no unmanaged resources to release, but
            // if we add them, they need to be released here.
        }
    }
}
class StudentDashboardClient : WebClientWrapperBase
{
    public StudentDashboardClient()
        : base("http://localhost/webapi/api/StudentDashboard/")
    {
        //just for compatibility
    }

    public StudentDashboardClient(string baseUrl)
        : base(baseUrl)
    {
    }

    public User GetUserRecord(string userName)
    {
        return Execute<User>("GetUserRecord?userName=" + userName);
    }
}
public class HomeController : Controller
{
    private readonly StudentDashboardClient _studentDashboardClient;

    public HomeController(StudentDashboardClient studentDashboardClient)
    {
        _studentDashboardClient = studentDashboardClient;
    }

    public ActionResult Index()
    {
        return View(_studentDashboardClient.GetUserRecord("jBob"));
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _studentDashboardClient.Dispose();
        }
        base.Dispose(disposing);
    }
}

Context

StackExchange Code Review Q#25141, answer score: 6

Revisions (0)

No revisions yet.