Entity Framework 4 allows us to create complex types. Typically, such types are used to get the result of a stored procedure. However, when we try to send such a complex type using a WCF RIA Domain Service, the following error is encountered.
What to do? Adding attributes to EF designer generated classes is a bad idea; Also, we cannot use partial classes to add attributes to an already defined property. However, luckily, WCF RIA Services uses metadata classes in conjunction with the entity model classes and hence we can always create a meta data class for our complex type and decorate one of the fields with a [Key] attribute. E.g., If our stored procedure is returning StoredProcedure_Result, then we may just need to add the following in our DomainService.metadata.cs file:
Yesterday I encountered the following question in the MSDN forums about calling a cross-domain WCF RESTful service from Chrome.
The problem with doing the above is because there is a cross-domain call to a WCF service. When you are using Chrome, the browser tries to find out if it can do the call by sending a cross-origin resource sharing (CORS) request, as explained in this article. In IE, you will most likely get a cross-domain exception.
After searching the web a bit, I found that the immediate solution is to change the supported HTTP method of the operation to “*”, and to add the special cross-origin headers to the outputted response, like so:
WebOperationContext.Current.OutgoingResponse.Headers.Add(
"Access-Control-Allow-Origin", "*"); WebOperationContext.Current.OutgoingResponse.Headers.Add( "Access-Control-Allow-Methods", "POST"); WebOperationContext.Current.OutgoingResponse.Headers.Add(
"Access-Control-Allow-Headers", "Content-Type, Accept");
There are two problems with this solution:
You need to repeat these lines in each of your service methods.
The service method is called twice, once for the “preflight” request (the request with the OPTIONS HTTP verb), and a second time for the invocation itself.
So I took the time and written a special endpoint behavior that can be attached to any webHttpBinding based endpoint.
The code does the following:
1. Every response message gets the “Access-Control-Allow-Origin” header with the value of “*”, to tell the client that the service allowed the request from the client.
2. If the client sends a request to the service with the verb OPTIONS (this is referred to as a “preflight” request), the service will automatically return a 200 (ok) response with the required headers: “Access-Control-Allow-Headers” and “Access-Control-Allow-Methods” (in addition to the allow-origin header). Clients that look for the “allow” headers in the response will then send the original required request to the service.
The benefits of using this behavior is:
The code that returns the message with the headers is located in the behavior itself – no need to place it in each service method.
The behavior catches the OPTIONS request, and does not require running the service method twice.
You do not need to change any part of your service contract or service implementation. The behavior is only needed to be applied using configuration.
To implement this behavior I used a message inspector that checks the request and changes the response, and an operation invoker that wraps the unhandled operation invoker. The custom operation invoker handles the OPTIONS message requests, which otherwise would have caused the service to return an error message (because no method is set to handle “Options” requests).
The endpoint behavior can be attached to the endpoint through configuration, like so:
<behaviors>
<endpointBehaviors>
<behavior name="webSupport">
<webHttp />
<CorsSupport />
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="CorsSupport" type="WebHttpCors.CorsSupportBehaviorElement, WebHttpCors, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<services>
<service name="Service.JSonService">
<endpoint address="http://localhost:8080" behaviorConfiguration="webSupport” binding="webHttpBinding" contract="Service.IJSonService" />
</service>
</services>
Or, if you are using IIS to host your services, I also created a service host factory that creates a WebServiceHost and adds the behavior to every webHttpBinding based endpoint created by the host. To use it in your .svc file, just write something like this:
<%@ ServiceHost Language="C#" Debug="true" Service="Service.JSonService, Service" Factory="WebHttpCors.CorsWebServiceHostFactory, WebHttpCors" %>
To test the two hosts, open the webform1.aspx and change the target URL to access the IIS/self-hosted service.
Enjoy!
WCF/ASP.Net Web Services are used for developing web services. Here is a list of differentiation between these two.
Feature
|
ASP.NET Web Service
|
WCF
|
Data Transformation
|
To and from Data Transition is done through XML Serializer
|
DataContractSerializer is used for data transition
|
File Extension
|
File extension is asmx
|
File extension is .svc
|
Webmethods vs DataContract Attributes
|
ASP.NET WebService uses Webmethods to translate .NET FW types in to XML.</SPAN< td>
|
The WCF uses the DataContractAttribute and DataMemeberAttribute to translate .NET FW types in to XML.
|
Limitations
|
Only Public fields or Properties of .NET types can be translated into XML.Only the classes which implement IEnumerable interface. ICollection interface can be serializable
|
Public/Private fields or properties of .NET types can be translated.
|
IDictionary Interface class
|
Classes that implement the IDictionary interface, such as Hash table can not be serialized.
|
The DataContractSerializer can translate the Hash Table into XML. Hence using WCF we can even translate HashTable into XML
|
Security
|
WCF is more secured than WebService due to ->
|
It is based on WS Standards. capable to run in any .NET executable, so it needs independent security capabilities.
Transfer security Responsible for providing message confidentiality, data integrity, and authentication of communicating parties. Authorization Responsible for providing a framework for making authorization decisions. Auditing Responsible for logging security-related events to the audit log
|
Binding
|
Web service supports only HTTP.
|
WCF supports multiple bindings HTTP,TCP,MSMQ,WS-HTTP etc
|
Messaging
|
ASP.Net web service uses only SOAP (Simple Object Access Protocol) for sending and receiving data. It uses Xml Schema to defines structure of message.
|
Windows Communication Foundation (WCF) can send message in any format. It uses SOAP for communication by default. It can use any other transport protocol for message transport .
|
Performance
|
Slower compared to WCF
|
The main advantage of the design of the DataContractSerializer is better performance over XML serialization
|
Fields / Properties
|
XMLSerialization does not indicate the which fields or properties of the type are serialized into XML
|
DataContratSerializer Explicitly shows the which fields or properties are serialized into XML
|
Exception handling
|
In ASP.NET Web services, Unhandled exceptions are returned to the client as SOAP faults.
|
In WCF Services, unhandled exceptions are not returned to clients as SOAP faults. A configuration setting is provided to have the unhandled exceptions returned to clients for the purpose of debugging.
|
Example
|
[WebService] public class Service : System.Web.Services.WebService { [WebMethod] public string Demo(string strDemog) { return strDemo; } }
|
[ServiceContract] public interface ITest { [OperationContract] string ShowMessage(string strDemo); } public class Service : ITest { public string Demo(string strDemo) { return strDemo; } }
|
The following article summarizes all the possible problems that may arise when deploying a WCF RIA Service application to a shared host. ASPHostCentral.com, as the premier ASP.NET and Windows Hosting provider, proudly presents this article to anyone and we believe it will help many ASP.NET communities; especially to those who are using WCF RIA Service. In case you are looking for WCF RIA Service Hosting, you can always consider ASPHostCentral.com and you can start from our lowest Standard Plan @$4.99/month to host your WCF-service site.
Why : RIA framework dynamically creates WCF service (Domain services) and add endpoints to the service. It first checks if endpoint does not exist then create it, and it checks for 3 endpoints (http, SOAP and binary). After creating end points it adds authentication schema to end points. It picks IIS authentication schemas and tries to apply on end points and failed to apply.
If we could create desired end points in web.config RIA framework will not create or do anything with endpoints and it works successfully.
You just need to follow the simple steps as mentioned on the followings:
1. Add following code to you web.config to solve issue “This collection already contains an address with scheme http..”
<serviceHostingEnvironment aspNetCompatibilityEnabled="true">
<baseAddressPrefixFilters>
<add prefix="http://www.yoursite.com"/>
</baseAddressPrefixFilters>
</serviceHostingEnvironment>
Note: Your service can be only accessed by URL mentioned in above settings. As configured above you can’t access your service via http://yoursite.com. You could also use factory code to host WCF (see below) to resolve this error however alone with that you need to create svc files for each domain service.
2.Add AspNetCompatibilityRequirementsMode attribute to your RIA Domain services classes
Eg .Attributes added to AuthenticationService class under services folder
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class AuthenticationService : AuthenticationBase<User> { }
RIA framework dynamically apply these attributes after creating end points. Since we are now bypassing endpoint creation, we need to manually apply these attributes.
3. For each RIA domain service add following to you configuration file.
E.g. Is shown for AuthenticationService and UserRegistrationService
Where SparkExams is my custom namespace.
<services>
<service name="SparkExams.Web.AuthenticationService"
behaviorConfiguration="RIAServiceBehavior">
<endpoint address="" binding="wsHttpBinding"
contract="SparkExams.Web.AuthenticationService" />
<endpoint address="/soap"
binding="basicHttpBinding"
contract="SparkExams.Web.AuthenticationService" />
<endpoint address="/binary"
binding="customBinding"
bindingConfiguration="BinaryHttpBinding"
contract="SparkExams.Web.AuthenticationService" />
</service>
<service name="SparkExams.Web.UserRegistrationService"
behaviorConfiguration="RIAServiceBehavior">
<endpoint address=""
binding="wsHttpBinding"
contract="SparkExams.Web.UserRegistrationService" />
<endpoint address="/soap"
binding="basicHttpBinding"
contract="SparkExams.Web.UserRegistrationService" />
<endpoint address="/binary"
binding="customBinding" bindingConfiguration="BinaryHttpBinding"
contract="SparkExams.Web.UserRegistrationService" />
</service>
Please note that RIA adds 3 endpoints and if any of these endpoints are missing from web.config it will throw "IIS specified authentication schemes 'Basic, Anonymous'..." error.
Add following behaviours and bindings to your web.config
<behaviors>
<serviceBehaviors>
<behavior name="RIAServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<customBinding>
<binding name="BinaryHttpBinding">
<binaryMessageEncoding />
<httpTransport />
</binding>
</customBinding>
</bindings>
Test you WCF end points using WCF client test tool (Test client for Windows Communication Foundation services). WcfTestClient.exe : Go to VS 2008 Console and type WcfTestClient.exe.
Note that there is no need to host you service,or change IIS settings by ISP.
-----------------------------------------------------------------------------
Read further if you want to know how this configuration has been discovered...
1. Have Used Red Gate's .NET Reflector to examine RIA assemblies.
2. Plugin my custom DomainServiceHost factory to host service.
3. Set debug points on overridable methods "ApplyConfigutation()" in CustomHost
Please find code for class used to injected service host factory at the end of this post.
4. Check where the code was failing and what RIA has configured before failing.Found that it have configured 3 endpoints for each service. Noticed the minimal configuration and rectified other errors one by one.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Ria.Services;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.ComponentModel;
using System.Web.DomainServices;
using System.Net;
namespace System.Web.Ria {
public class DomainServiceHostFactoryEx : DomainServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
Uri baseAddress=null;
try
{
baseAddress = baseAddresses[0];
}
catch (Exception e)
{
baseAddress = new Uri("http://localhost:52878");
}
CustomHost customServiceHost =
new CustomHost(serviceType, baseAddress);
return customServiceHost;
}
}
class CustomHost : DomainServiceHost
{
DomainServiceDescription _domainServiceDescription;
ServiceDescription _sdecreption;
ContractDescription _contract;
public CustomHost(Type serviceType, params Uri[] baseAddresses)
: base(serviceType, baseAddresses)
{
this._domainServiceDescription = DomainServiceDescription.GetDescription(serviceType);
}
protected override ServiceDescription CreateDescription(out IDictionary<string, ContractDescription> implementedContracts)
{
ServiceDescription d = base.CreateDescription(out implementedContracts);
//_contract = implementedContracts[0];
_sdecreption = d;
return d;
}
protected override void ApplyConfiguration()
{
//base.LoadConfigurationSection(new System.ServiceModel.Configuration.ServiceElement(_sdecreption.ConfigurationName));
// try
//{
string error = "";
try
{
base.ApplyConfiguration();
}
catch (Exception applyconfigerror) { error += "Error1:" + applyconfigerror.Message + "\r\n"; }
try
{
//this.AddEndpoints();
}
catch (Exception applyconfigerror) { error += "Error2:" + applyconfigerror.Message + "\r\n"; }
try
{
// this.AddDefaultBehaviors();
}
catch (Exception applyconfigerror) { error += "Error3:" + applyconfigerror.Message + "\r\n"; }
//if (error.Length > 0) HttpContext.Current.AddError(new Exception( error));
//this.AddEndpoints();
//this.AddDefaultBehaviors();
using (IEnumerator<ServiceEndpoint> enumerator = base.Description.Endpoints.GetEnumerator())
{
while (enumerator.MoveNext())
{
ServiceEndpoint current = enumerator.Current;
//current.Binding.Scheme
}
}
//}
//catch (Exception ex)
// {
//HttpContext.Current.AddError(ex);
// }
}
}
}