I really like the idea of using Web Api technology. I have been wondering however for a while about using this technology in multi-tier web application. All the demos I have seen expect to have connection string in the web application exposed by Web Api. I think that not having an ability to run a web application in 3-tier environment is a bit short sighted. So, I wanted to illustrate the concept of how to implement this type of application. I would like to have the following tiers
- Web Application
- Application Server
- Data Server (SQL Server)
So, I am going to structure my project to accommodate this layout. First thing is to put web api controllers into their own project. Again, you do not see such solution architecture in the simple demos. You can however create controllers in their own project, then reference them from other projects. So, I am creating new project (class library), then add all the references I need to create Web Api controller. After that I will create two web apps, both referencing the same api controllers project. To save time I am going to put data code into the same project, but I would not do that in production app.
Here is what my solution looks like.
Now, I am going to create a base controller that will enable me to do single thing – intercept requests and forward them to the application server. What will help me to do that is one method that is always invoked when controller is used – ExecuteAsync.
using System; using System.Configuration; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using System.Web.Http; using System.Web.Http.Controllers; namespace MvcAndData { public class BaseController : ApiController { public override Task<HttpResponseMessage> ExecuteAsync( HttpControllerContext controllerContext, CancellationToken cancellationToken) { if (Convert.ToBoolean(ConfigurationManager.AppSettings["Redirect"])) { var url = controllerContext.Request.RequestUri; url = new Uri(url.AbsoluteUri.Replace( ConfigurationManager.AppSettings["OriginalUriFragment"], ConfigurationManager.AppSettings["ReplacemenUriFragment"])); var client = new HttpClient(); client.DefaultRequestHeaders.Clear(); foreach (var httpRequestHeader in controllerContext.Request.Headers) { client.DefaultRequestHeaders.Add(httpRequestHeader.Key, httpRequestHeader.Value); } if (controllerContext.Request.Method == HttpMethod.Get) { return client.GetAsync(url, cancellationToken); } if (controllerContext.Request.Method == HttpMethod.Post) { return client.PostAsync(url, controllerContext.Request.Content, cancellationToken); } if (controllerContext.Request.Method == HttpMethod.Delete) { return client.DeleteAsync(url, cancellationToken); } if (controllerContext.Request.Method == HttpMethod.Put) { return client.PutAsync(url, controllerContext.Request.Content, cancellationToken); } } return base.ExecuteAsync(controllerContext, cancellationToken); } } }
As you can see from above, I am testing to see what HTTP method was used and invoke appropriate method on the HttpClient class. I also copy the headers from the original request to the new request. To create Url I also add a few settings to configuration file in order to know what to replace in the request with what. So, my app settings section in the web application looks as following.
<appSettings> <add key="webpages:Version" value="2.0.0.0" /> <add key="webpages:Enabled" value="false" /> <add key="PreserveLoginUrl" value="true" /> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> <add key="Redirect" value="true"/> <add key="OriginalUriFragment" value="localhost:3167"/> <add key="ReplacemenUriFragment" value="localhost:3336"/> </appSettings>
Now to make sure I am not faking anything, I am going to remove connection string from web server project (Mvc3Tier), and only leave in the app server web project. (MvcAppServer) Then I am going to test the entire solution.
You can download and look at the code here.
I am very curious as to what you think about this idea.
Thanks.