18
\$\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\$
1

2 Answers 2

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\$
2
  • \$\begingroup\$ I quite like that wrapper. Simple but does the job that the OP seems to want \$\endgroup\$ Commented Apr 16, 2013 at 20:50
  • \$\begingroup\$ Does this wrapper takes care of POSTs as well? \$\endgroup\$ Commented Apr 23, 2015 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
  • 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\$ Commented Apr 16, 2013 at 20:47
  • \$\begingroup\$ @dreza Very good point, I'll include that possibility. \$\endgroup\$ Commented Apr 17, 2013 at 6:47

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.