17
\$\begingroup\$

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<User>(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:

  1. Instantiate a new WebClient
  2. Define the URL to hit
  3. 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:

  1. Let me keep strongly typed views
  2. Do my work on the server rather than on the client (this is just a preference since I can write C# faster than I can write JavaScript)
\$\endgroup\$
6
\$\begingroup\$

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<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.
        }
    }
}

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<User>("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.

\$\endgroup\$
  • \$\begingroup\$ I quite like that wrapper. Simple but does the job that the OP seems to want \$\endgroup\$ – dreza Apr 16 '13 at 20:50
  • \$\begingroup\$ Does this wrapper takes care of POSTs as well? \$\endgroup\$ – juanreyesv Apr 23 '15 at 3:18
3
\$\begingroup\$

All the controlers are regular classes with methods.

Assuming your API controller StudentDashboard has a Get(string name) verb method, you can do this:

public ActionResult Index() //This view is strongly typed against User
{
    //testing against Joe Bob
    string adSAMName = "jBob";
    var apiController = new StudentDashboardController(); //or whatever other API controller returns your data
    var result = apiController.Get(adSAMName);
    return View(result);
}

This should give you strong typing. You can also instantiate and use 'regular' MVC controllers too.

[EDIT]

Per comment, it's even better if you delegate the controller creation to the framework

ControllerBuilder.GetControllerFactory().CreateController(Request.RequestContext, controllerName);

You can either set the string or extract it from the type if you want stronger typing.

Reference MSDN

\$\endgroup\$
  • 2
    \$\begingroup\$ I think I would consider using the framework to make the controller as that way and DI will be sorted for you i.e. ControllerBuilder.Current.GetControllerFactory().CreateController(Request.RequestContext, controllerName) \$\endgroup\$ – dreza Apr 16 '13 at 20:47
  • \$\begingroup\$ @dreza Very good point, I'll include that possibility. \$\endgroup\$ – Patryk Ćwiek Apr 17 '13 at 6:47

protected by Jamal Jul 18 '14 at 18:43

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.