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:
- 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 since I can write C# faster than I can write JavaScript)
-
\$\begingroup\$ This method will probably help your cause: i-skool.co.uk/net/re-usable-webapi-request-wrapper Let's you call an API route and return the delegated object. \$\endgroup\$Martin Dixon– Martin Dixon2013年12月06日 15:48:02 +00:00Commented Dec 6, 2013 at 15:48
2 Answers 2
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.
-
\$\begingroup\$ I quite like that wrapper. Simple but does the job that the OP seems to want \$\endgroup\$dreza– dreza2013年04月16日 20:50:31 +00:00Commented Apr 16, 2013 at 20:50
-
\$\begingroup\$ Does this wrapper takes care of POSTs as well? \$\endgroup\$juanreyesv– juanreyesv2015年04月23日 03:18:34 +00:00Commented Apr 23, 2015 at 3:18
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.
-
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– dreza2013年04月16日 20:47:16 +00:00Commented Apr 16, 2013 at 20:47
-
\$\begingroup\$ @dreza Very good point, I'll include that possibility. \$\endgroup\$Patryk Ćwiek– Patryk Ćwiek2013年04月17日 06:47:59 +00:00Commented Apr 17, 2013 at 6:47
Explore related questions
See similar questions with these tags.