In this post I am going to describe a solution to the following problem. I would like to create a single WCF Service and expose it via a standard SOAP endpoint and REST endpoint using Entity Framework, WCF and WCF REST. Then I would like to consume it from WinRT from two different view models working against the same view. This is an exercise of research into data options in WinRT.
First of, let’s create a service. I am going to use the following data class:
public class Session { public int SessionID { get; set; } public string Title { get; set; } public string Description { get; set; } public string Speaker { get; set; } public DateTime When { get; set; } }
My data context for EF Code First is just as simple:
public class Context : DbContext { public Context() : base("Name=VSLive") { } public DbSet<Session> Sessions { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Session>().Property(p => p.Title).HasMaxLength(100).IsRequired(); modelBuilder.Entity<Session>().Property(p => p.Speaker).HasMaxLength(50).IsRequired(); modelBuilder.Entity<Session>().Property(p => p.Description).IsRequired(); modelBuilder.Entity<Session>().Property(p => p.When).IsRequired(); } }
Now, the service. I am just going to perform basis CRUD opertions. The key to the service is my interface that I am going to decorate with both SOAP(OperationContract) and REST(WebGet or WebInvoke) attributes.
[ServiceContract] public interface IVSLiveService { [OperationContract] [WebGet(UriTemplate = "/GetList", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] Session[] GetList(); [OperationContract] [WebInvoke(UriTemplate = "/Create", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] Session Create(Session session); [OperationContract] [WebInvoke(UriTemplate = "/Update", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] Session Update(Session session); [OperationContract] [WebInvoke(UriTemplate = "/Delete?sessionId={sessionId}", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] void Delete(int sessionId); }
The implementation is not quite as interesting, but for the same of completeness of this post, here it goes:
using System.Data.Entity; using System.Linq; using System.ServiceModel; using System.ServiceModel.Activation; using WinRT.Data; using WinRT.DataAccess; namespace WcfService { [AspNetCompatibilityRequirements( RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] public class VSLiveService : IVSLiveService { public VSLiveService() { Database.SetInitializer(new Initializer()); } public Session[] GetList() { using (var context = new Context()) { context.Configuration.LazyLoadingEnabled = false; context.Configuration.ProxyCreationEnabled = false; return context.Sessions.ToArray(); } } public Session Create(Session session) { using (var context = new Context()) { context.Sessions.Add(session); context.SaveChanges(); } return session; } public Session Update(Session session) { using (var context = new Context()) { context.Entry(session).State = System.Data.EntityState.Modified; context.SaveChanges(); } return session; } public void Delete(int sessionID) { using (var context = new Context()) { var session = new Session { SessionID = sessionID }; context.Entry(session).State = System.Data.EntityState.Deleted; context.SaveChanges(); } } } }
Now, the part that took me the longest to figure out: web.config.
I have single service node, and I have two endpoints for it, using the same contract, but two different bindings and behaviors. I am putting entire web.config:
<?xml version="1.0"?> <configuration> <connectionStrings> <add name="VSLive" connectionString="Server=.;Database=VSLive;Trusted_Connection=True;" providerName="System.Data.SqlClient"/> </connectionStrings> <system.web> <compilation debug="true" targetFramework="4.0" /> </system.web> <system.serviceModel> <behaviors> <endpointBehaviors> <behavior name="jsonBehavior"> <webHttp/> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior> <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment --> <serviceMetadata httpGetEnabled="true"/> <!—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 --> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> <bindings> <basicHttpBinding> <binding name="VSLiveService_BasicHttpBinding" maxBufferSize="1000000" maxReceivedMessageSize="1000000"> <readerQuotas maxBytesPerRead="1000000" maxArrayLength="1000000" maxDepth="1024" maxStringContentLength="1000000"/> </binding> </basicHttpBinding> <webHttpBinding> <binding name="VSLiveService_WebHttpBinding" maxBufferSize="1000000" maxReceivedMessageSize="1000000"> <readerQuotas maxBytesPerRead="1000000" maxArrayLength="1000000" maxDepth="1024" maxStringContentLength="1000000"/> </binding> </webHttpBinding> </bindings> <services> <service name="WcfService.VSLiveService"> <endpoint address="soap" binding="basicHttpBinding" bindingConfiguration="VSLiveService_BasicHttpBinding" contract="WcfService.IVSLiveService"/> <endpoint address="rest" binding="webHttpBinding" behaviorConfiguration="jsonBehavior" bindingConfiguration="VSLiveService_WebHttpBinding" contract="WcfService.IVSLiveService"/> </service> </services> <serviceHostingEnvironment multipleSiteBindingsEnabled="true" /> </system.serviceModel> <system.webServer> <modules runAllManagedModulesForAllRequests="true"/> </system.webServer> <system.diagnostics> <sources> <source name="System.ServiceModel" switchValue="Information, ActivityTracing" propagateActivity="true"> <listeners> <add name="traceListener" type="System.Diagnostics.XmlWriterTraceListener" initializeData= "c:Traces.svclog" /> </listeners> </source> </sources> </system.diagnostics> </configuration>
As you can see above, SOAP endpoint comes first, and it is using basicHttpBinding. My REST endpoint is second, and it is using webHttpBinding I am asing a behavior configuration to the latter one, enabling webHttp get/post methods.
This is all nice and simple, and you can now test it in browser. I an now add service reference to my WinRT project to support WCF. You can find more details about that in my previous post http://dotnetspeak.com/index.php/2011/12/getting-started-with-wcf-services-in-winrt/
Today, I am documenting REST consumption.
I am using HttpClient class to accomplish this task. For example, here is how I am going to get the list of sessions.
public async Task LoadData() { IsBusy = true; _client = new HttpClient(); _client.MaxResponseContentBufferSize = int.MaxValue; var response = await _client.SendAsync(new HttpRequestMessage(HttpMethod.Get, new Uri(_serviceUri + "GetList"))); var data = response.Content.ReadAsString(); DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(List<Session>)); using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) { var list = serializer.ReadObject(stream) as List<Session>; Sessions = new ExtendedObservableCollection<Session>(list); } IsBusy = false; }
A few points about the code above. I should have wrapped the call inside Try/Catch, I am just skipping it for the sake of a demo and to minimize the code I am showing. I am using standard serializer to convert my JSON message into an object. I also have a little progress ring that is playing while server communication is going on, and that is what my IsBusy property above is bound to.
Now, let’s take a look at Create/Update call. It is just as simple, but I am using Post method of HttpClient and I am creating a string content to post by converting Session object to JSON, again using the same serializer.
public async void OnSave(object parameter) { if (SelectedSession != null) { IsBusy = true; string method = "Update"; if (selectedSession.SessionID == 0) { method = "Create"; } _client = new HttpClient(); _client.MaxResponseContentBufferSize = int.MaxValue; var content = new StringContent(ConvertSessionToJson()); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); var response = await _client.PostAsync(new Uri(_serviceUri + method), content); var data = response.Content.ReadAsString(); var session = ConvertJsonToSession(data); Sessions[Sessions.IndexOf(selectedSession)] = session; SelectedSession = session; IsBusy = false; } }
For delete method I am also using Post method, just my content is blank and my ID is passed to the server as query string parameter
public async void OnDelete(Session parameter) { if (parameter != null) { if (parameter.SessionID > 0) { IsBusy = true; _client = new HttpClient(); _client.MaxResponseContentBufferSize = int.MaxValue; var content = new StringContent(string.Empty); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); var response = await _client.PostAsync(new Uri(_serviceUri + "Delete?sessionId=" + parameter.SessionID.ToString()), content); var data = response.Content.ReadAsString(); Sessions.Remove(parameter); IsBusy = false; } else { Sessions.Remove(parameter); IsBusy = false; } } }
Please let me know your questions.
Thanks.
Indeed the article is short clear and to the point but i am keen to know how to secure the rest services from anonymous and unauthenticated users.
I wrote a post on that exact subject. You can see it here http://dotnetspeak.com/index.php/2012/01/securing-wcf-with-forms-authentication/
This Article is very helpful to me in my project..
nice article ..
but there is a problem occur in my project
Please Help Me to Find Out Solution
I am Facing This Problem in My REST Service
Any Idea?
to this:
[OperationContract]
[WebGet( UriTemplate = “strCustomLabelCollection/{Name},{Type},{Id}” )]
int CheckCustomLabelMaster(List strCustomLabelCollection);
Is there a way to make the Name,Type,Id be a variable length list of parameters? So it could be
Name1,name2.. …., namen? And end up with a service method such as:
int CheckCustomLabelMaster(List strCustomLabelCollection);
Or something along those lines?
Hi, I created simple rest service. I am using Entityframework for CRUD operations.
I getting below error when i call persons. Could you please advise me to resolve the issue.
Error:
The type ‘PersonData.PersonInfo’ cannot be serialized to JSON because its IsReference setting is ‘True’. The JSON format does not support references because there is no standardized format for representing references. To enable serialization, disable the IsReference setting on the type or an appropriate parent class of the type.
My Contract :
[OperationContract]
[WebGet(UriTemplate = “/Persons”, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
IList GetPersons();
Implimentation:
public IList GetPersons()
{
IList result = new List();
using (PersonEntities contxt = new PersonEntities())
{
//result = contxt.PersonInfoes.Include(“Sex”).ToList();
result = contxt.PersonInfoes.ToList();
}
return result;
//throw new CustomException(ResxMessage.Message_InvalidServiceRequest);
}
Did you try disabling proxy generation and lazy loading on EF context?
I think then you are running into EF Entity Object limitation with Javascript serializer. This is a known issue.
http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/9bff1c80-41e3-483a-8762-304efa630328
Sergey Barskiy
What abount jquery ajax requests? will it be blocked from other domain? (cors) or your code provided is already configured for cors?
No, it does not handle CORS. You would need to implement that. Here is how: http://blogs.microsoft.co.il/idof/2011/07/02/cross-origin-resource-sharing-cors-and-wcf/
And also http://blogs.microsoft.co.il/idof/2011/07/02/cross-origin-resource-sharing-cors-and-wcf/ uses CorsSupport/> tag, which doesn’t exist in app.config of Self-Hosted WCF
Thanks! it works like a charm