While developing an HTML5 mobile app I’ve become more and more in contact with Microsoft WCF Services to provide the app with data.
So I’ve spent some more time to take a deeper look into the matter of WCF Services to resolve my issues with the HTML5 app communication taking into account the “Best practices”.
But anyway, what came out of this project, you can see below.
You’ll also find the complete VisualStudio 2012 Solution as a download at the end of the article.
Step 1. Create an basic WCF service and interface
[ServiceContract] public interface IMyService { [OperationContract] [WebInvoke(UriTemplate = "/UpdateItems", Method = "POST", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)] bool UpdateItems(IEnumerable items); [OperationContract] [WebInvoke(UriTemplate = "/Update", Method = "POST", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)] bool Update(StatusItem item); [OperationContract] [WebInvoke(UriTemplate = "/Delete", Method = "POST", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)] void Delete(int ID); [OperationContract] [WebGet(UriTemplate = "ItemData/{value}", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped)] [return: MessageParameter(Name = "results")] TestItem GetItemData(string value); [OperationContract] [WebGet(UriTemplate = "Items", RequestFormat=WebMessageFormat.Json, ResponseFormat=WebMessageFormat.Json, BodyStyle=WebMessageBodyStyle.Wrapped)] [return: MessageParameter(Name = "results")] List GetItems(); }
public class MyService : IMyService { public bool UpdateItems(IEnumerable items) { //proceed updates. return true; } public bool Update(StatusItem item) { //proceed update. return true; } public void Delete(int ID) { //proceed delete. } public TestItem GetItemData(string ID) { int id = 0; if (int.TryParse(ID, out id)) { List items = new List(); for (int i = 0; i <= id; i++) { TestItem tmp = new TestItem { Name = "Name-" + i, Color = "#ffff" + i }; items.Add(tmp); } //Return Last (single item) return items.Last(); } return null; } public List GetItems() { List items = new List(); for (int i = 0; i <= 10; i++) { TestItem tmp = new TestItem { Name = "Name-"+i, Color="#ffff"+i }; items.Add(tmp); } return items; } }
Step 2. Enable CORS (cross) domain calls within the global.asax
protected void Application_BeginRequest(object sender, EventArgs e) { HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin" , "*"); if (HttpContext.Current.Request.HttpMethod == "OPTIONS" ) { //These headers are handling the "pre-flight" OPTIONS call sent by the browser HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods" , "GET, POST" ); HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Authorization, Origin, Content-Type, Accept, X-Requested-With"); HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000" ); HttpContext.Current.Response.End(); } }
Step 3. Setup the web.config configuration for Service
<!-- To avoid disclosing metadata information, set the values below to false before deployment --> <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information --> <!--<serviceCredentials> serviceCertificate findValue="MyWebSite" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" /> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="TestWcfService.CustomLoginValidator, TestWcfService" /> </serviceCredentials>--> <!-- binding for .NET 4.0 crossDomainScriptAccessEnabled --> <!--<security mode="TransportCredentialOnly"> <transport clientCredentialType="Basic" /> </security>--> <!-- this have no affect for crossDomainScriptAccessEnabled (jsonp) because custom endpoints use binding instead --> <!-- To browse web app root directory during debugging, set the value below to true. Set to false before deployment to avoid disclosing web app folder information. -->
Step 4. Create a custom ServiceAuthorizationManager for Basic authentication
public class MyServiceAuthorizationManager : ServiceAuthorizationManager { protected override bool CheckAccessCore(OperationContext operationContext) { // Get the Header Authorization Value var reqest = operationContext.RequestContext.RequestMessage.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty; string authHeader = reqest.Headers[System.Net.HttpRequestHeader.Authorization]; if (authHeader != null && authHeader.StartsWith("Basic")) { authHeader= this.DecodeBase64String(authHeader.Replace("Basic ", "")).Trim(); // Get Username and Password string userName = authHeader.Substring(0, authHeader.IndexOf(":")); string password = authHeader.Substring(authHeader.IndexOf(":") + 1, authHeader.Length - userName.Length - 1); //validate username and password ... if (userName.Equals("test") && password.Equals("testpw")) { return true; } } // If this point is reached, return false to deny access. return false; } private string DecodeBase64String(string encodedData) { byte[] encodedDataAsBytes = System.Convert.FromBase64String(encodedData); string returnValue = System.Text.ASCIIEncoding.ASCII.GetString(encodedDataAsBytes); return returnValue; } }
Step 5. Creating a JQuery test page that makes some calls
<h1>WCF Service with CORS and JQuery Test Page</h1> <div id="results" style="border: 1px solid black; width: 450px; min-height: 150px;"></div> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22text%2Fjavascript%22%3E%2F%2F%20%3C!%5BCDATA%5B%0A%24.support.cors%20%3D%20true%3B%0A%0Avar%20myUserName%20%3D%20'Test'%3B%0Avar%20myUserPassword%20%3D%20'test1234'%3B%0Avar%20serviceUrl%20%3D%20'http%3A%2F%2Fyour-service.com%2F'%3B%0A%0Afunction%20getHeaders()%20%7B%0A%2F%2Fbtoa%20is%20a%20built%20in%20browser%20cmd%20for%20encode%20Base64%0Areturn%20%7B%20'Authorization'%3A%20%22Basic%20%22%20%2B%20btoa(%22test%3Atestpw%22)%20%7D%3B%0A%7D%0A%0A%2F%2FGetItems%0A%24.ajax(%7B%0Aheaders%3A%20getHeaders()%2C%0Aurl%3A%20serviceUrl%20%2B%20%22MyService.svc%2FItems%22%2C%0Atype%3A%20%22GET%22%2C%0A%2F%2F%20tell%20jQuery%20we're%20expecting%20JSONP%0AdataType%3A%20%22json%22%2C%0A%2F%2F%20work%20with%20the%20response%0Asuccess%3A%20function(%20response%20)%20%7B%0Aconsole.log(%20response%20)%3B%0A%24('%23results').append('%0A%0ATesting%20GetItems%20Service...%0A%0A').append('%3Ccode%3E'%2B%20JSON.stringify(response)%20%2B'%3C%2Fcode%3E')%3B%0A%7D%2C%0Aerror%3A%20function%20(xhr)%20%7B%20console.log(xhr.responseText)%3B%20%7D%0A%7D)%3B%0A%0A%2F%2FGetItem%0A%24.ajax(%7B%0Aheaders%3A%20getHeaders()%2C%0Aurl%3A%20serviceUrl%20%2B%20%22MyService.svc%2FItemData%2F44%22%2C%0Atype%3A%20%22GET%22%2C%0A%2F%2F%20tell%20jQuery%20we're%20expecting%20JSONP%0AdataType%3A%20%22json%22%2C%0A%2F%2F%20work%20with%20the%20response%0Asuccess%3A%20function(%20response%20)%20%7B%0Aconsole.log(%20response%20)%3B%0A%24('%23results').append('%0A%0ATesting%20GetItem%20Service...%0A%0A').append('%3Ccode%3E'%2B%20JSON.stringify(response)%20%2B'%3C%2Fcode%3E')%3B%0A%7D%2C%0Aerror%3A%20function%20(xhr)%20%7B%20console.log(xhr.responseText)%3B%20%7D%0A%7D)%3B%0A%0A%2F%2FUpdate%0A%24.ajax(%7B%0Aheaders%3A%20getHeaders()%2C%0Aurl%3A%20serviceUrl%20%2B%20%22MyService.svc%2FUpdate%22%2C%0AcontentType%3A%20%22application%2Fjson%22%2C%0AdataType%3A%20%22json%22%2C%0Atype%3A%20%22POST%22%2C%0Adata%3A%20JSON.stringify(%7B%20ID%3A%20%226%22%2C%20Name%3A%20%22TEST%22%2C%20BackgroundColor%3A%20%22000%22%2C%20FontColor%3A%20%22fff%22%20%7D)%2C%0A%2F%2F%20work%20with%20the%20response%0Asuccess%3A%20function(%20response%20)%20%7B%0Aconsole.log(%20response%20)%3B%20%2F%2F%20server%20response%0A%24('%23results').append('%0A%0ATesting%20Update%20Service...%0A%0A').append('%3Ccode%3E'%2B%20JSON.stringify(response)%20%2B'%3C%2Fcode%3E')%3B%0A%7D%2C%0Aerror%3A%20function%20(xhr)%20%7B%20console.log(xhr.responseText)%3B%20%7D%0A%7D)%3B%0A%2F%2F%20%5D%5D%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
You can download the examples (VS2012) via https://github.com/dablumino/Codeninja.TestWcfService