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)
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Apr 16, 2013 at 14:45
\$\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.

dreza
6,5372 gold badges30 silver badges42 bronze badges
answered Apr 16, 2013 at 19:59
\$\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

answered Apr 16, 2013 at 19:44
\$\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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.