WCF Limitations and Tutorials

Table of Contents

- Introduction

- Limitations of the "Add Service Reference" support (SOAP)

- Adding support for cross-domain calls (CORS)

- Tips for debugging WCF services

- Common issues and solutions

- Tutorial to easily create a SOAP-based client/server app in CSHTML5 (WCF)

- Tutorial to easily create a REST-based client/server app in CSHTML5 (Web API)

- Support

Introduction

C#/XAML for HTML5 provides support for web services and HTTP calls in multiple ways, including:
  • The "Add Service Reference" feature of Visual Studio. Use this feature to easily communicate with SOAP / WCF web services. See below for limitations and a tutorial.
  • The "WebClient" class. Use this class to download strings from the web, as well as to communicate with REST / Web API web services. See below for a tutorial.

Limitations of the "Add Service Reference" support (SOAP)

In the current version, C#/XAML for HTML5 has the following limitations regarding the "Add Service Reference" (SOAP) feature:

  • On the client, you must specify the bindings and the endpoints programmatically, because the client-side "app.config" file is ignored. To do so, follow this simple example:
    ServiceReference1.Service1Client client = new ServiceReference1.Service1Client(new BasicHttpBinding(), new EndpointAddress(new Uri(@"http://example.com/Service1.svc")));
  • On the server, you must add the attribute "[XmlSerializerFormat]" to the class that has the "[ServiceContract]" attribute (after you make such a change, make sure to "Update the Service Reference" on the client to reflect the change)
  • Make sure that all the types that needs to be serialized or deserialized have the "[DataContract]" attribute.
  • Due to JavaScript restrictions in the browser, cross-domain calls require the server to implement CORS. In other words, if your client application is not hosted on the same domain as your WCF web service, you need to add CORS to the web service. To do so, simply follow the tutorial below.
  • It cannot be used in two projects at once. This is related to the fact that all the classes that have the "[DataContract]" attribute need to be located in the same project. Therefore, if your application is made of multiple projects, you should ensure that all the service references are located in the same project.
  • You cannot add 2 service references that share the same types. This is due to the fact that the proxy types generated by the service references contain serializable classes that have the same name, which leads to conflicts. You can work around this limitation by manually modifying the files "Reference.cs" that are generated by each service reference in order to remove the types that are in common, and move those types into a separate class in your project. This ensures that the types are defined only once. Please feel free to contact support for additional help on this topic.

Furthermore, please note that the following features are NOT yet supported:

  • Non-http bindings (only BasicHttpBinding and HTTPS/SSL are currently supported)
  • Authentication
  • Namespaces other than the default one ("tempuri.org")
  • The generic type "FaultException<T>" (only the non-generic FaultException type is currently supported)
  • The following types cannot be serialized at the moment. Therefore, you must not use the following types in web methods arguments or in the return types:
    • Nullables Now supported in Beta 11.5 and newer
    • byte[] Now supported in Beta 11.0 and newer
  • The "Generate asynchronous operations" option (in the Service Reference Settings) cannot be used. However, you can use the "Generate task-based operations" option instead, which lets you make asynchronous calls via the async/await pattern. Alternatively, you can also make synchronous calls.

We are working to add support for all of the above features as soon as possible. Please make sure to vote for your most wanted features on http://cshtml5.uservoice.com

Adding support for cross-domain calls (CORS)

Due to JavaScript restrictions in the browser, cross-domain calls require the server to implement CORS. In other words, if your client application is not hosted on the same domain as your WCF web service, you need to add CORS to the web service. To do so, simply follow these steps:

1) If you are using a SOAP web service:

i) Make sure you have the file "Global.asax.cs" in your server-side SOAP web service project. If you don't have that file, right-click the project name in the Solution Explorer, and click "Add" -> "Global Application Class".

ii) Add the following code to the said file:

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, PUT, DELETE");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, SOAPAction");
        HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
        HttpContext.Current.Response.End();
    }
}

To see this code in action, follow the SOAP tutorial below.

2) If you are using a REST / Web API web service:

i) Modify Web.Config to add the following code (note: you need to add the code to the right section of Web.Config:

<configuration> 
  <system.webServer>

    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type, Content-Length" />
        <add name="Access-Control-Allow-Methods" value="POST,PUT,GET,DELETE,OPTIONS" />
      </customHeaders>
    </httpProtocol>

  </system.webServer>
</configuration>

ii) Add the following code to each of your Web API controller classes:

// OPTIONS
public HttpResponseMessage Options()
{
    return Request.CreateResponse(HttpStatusCode.OK);
}

To see this code in action, follow the REST tutorial below.

Tips for debugging WCF services

  • If you get a Script error when running your C#/XAML for HTML5 application inside the browser, go to the browser "Developer Tools" (usually F12 key) and look at the JavaScript console output to see if there are any error details. If you are using Chrome to debug the JavaScript code, you can check the option "Pause On Caught Exceptions" for easier debugging.
  • In Visual Studio, create a standard C#-based "Console" project (based on the latest .NET Framework) and try to reference your web service from that project. This is useful to know if the error is related to C#/XAML for HTML5 or if it is a generic WCF error.
  • Install and launch the freeware "Fiddler" application to see what information is sent between the client and the server. You can compare this information with the information that is sent to/from the test Console application created above. This is useful to see if there are any differences between the C#/XAML for HTML5 implementation of WCF, and the pure .NET implementation.

Common issues and solutions

  • If you get the error "Could not find the name 'Xml' in the namespace 'Microsoft'.", you must verify that you have added the attribute "[DataContract]" to the class that you want to serialize, deserialize, or pass as argument or return value of a WCF call.
  • If you get the error "XmlSerializer assembly does not contain a serializer for type '...'", you must verify that the said type is publicly accessible, and that it has the [DataContract] attribute. If the error persists, please look at the output of the compilation (in the Output pane of Visual Studio) for possible hints (look especially at the "SerializationAssembliesGenerator" part).
  • If you get the error "No 'Access-Control-Allow-Origin' header is present on the requested resource", it means that the client and the server are not on the same domain and therefore you need to add support for CORS on the server. To do so, read the CORS-related section of this page.

Tutorial to easily create a SOAP-based client/server app in CSHTML5 (WCF)

In this tutorial we are going to create a simple client/server application for managing To-Do items. If you have any feedback regarding this tutorial, please send us an email to support@cshtml5.com

1) Create a new project of type "WCF -> WCF Service Application". Let's call it "WcfService1"

2) Replace IService1.cs with the following code:

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace WcfService1
{
    [ServiceContract]
    [XmlSerializerFormat]
    public interface IService1
    {
        [OperationContract]
        List<ToDoItem> GetToDos();

        [OperationContract]
        void AddOrUpdateToDo(ToDoItem toDoItem);

        [OperationContract]
        void DeleteToDo(ToDoItem toDoItem);
    }

    [DataContract]
    public class ToDoItem
    {
        [DataMember]
        public Guid Id { get; set; }

        [DataMember]
        public string Description { get; set; }
    }
}

3) Replace Service1.svc with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;

namespace WcfService1
{
    public class Service1 : IService1
    {
        private static Dictionary<Guid, ToDoItem> _todos = new Dictionary<Guid, ToDoItem>();

        public List<ToDoItem> GetToDos()
        {
            return _todos.Values.ToList();
        }

        public void AddOrUpdateToDo(ToDoItem toDoItem)
        {
            _todos[toDoItem.Id] = toDoItem;
        }

        public void DeleteToDo(ToDoItem toDoItem)
        {
            if (_todos.ContainsKey(toDoItem.Id))
                _todos.Remove(toDoItem.Id);
            else
                throw new FaultException("ID not found: " + toDoItem.Id);
        }
    }
}

4) Create the file "Global.asax" by right-clicking the project in the Solution Explorer, and clicking Add -> Global Application Class -> OK. Add the following code to it:

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, PUT, DELETE");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, SOAPAction");
        HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
        HttpContext.Current.Response.End();
    }
}

5) Click "Start Debugging" (F5) and take note of the URL of the service. It should be something like: http://localhost:4598/Service1.svc
Keep the project running.

6) In a new instance of Visual Studio, create a new project of type C#/XAML for HTML5 -> Empty Application.

7) Click Project -> Add Service Reference, and paste the URL of the service that you created above.  It should be something like: http://localhost:4598/Service1.svc where you must replace 4958 with your port number. Click GO and then OK (leave the default name "ServiceReference1").

8) Manually remove the following references from the project references: System, System.Runtime.Serialization, System.ServiceModel, System.Xml (and any other DLL that starts with "System.").

9) Modify the page MainPage.XAML by removing the default TextBlock and replacing it with the following code:

<StackPanel>
    <TextBlock Text="CREATE A NEW TO-DO:" Margin="0,20,0,0" Foreground="Black" HorizontalAlignment="Left"/>
    <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
        <TextBox x:Name="SoapToDoTextBox" Width="200" Text="Enter your To-Do here" Foreground="Black" Background="#FFEEEEEE"/>
        <Button Content="Create" Click="ButtonAddSoapToDo_Click" Foreground="White" Background="#FFE44D26" Margin="5,0,0,0"/>
    </StackPanel>
    <TextBlock Text="LIST OF TODO's:" Margin="0,20,0,0" Foreground="Black" HorizontalAlignment="Left"/>
    <ItemsControl x:Name="SoapToDosItemsControl" HorizontalAlignment="Left">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
                    <TextBlock Text="{Binding Description}" Foreground="Black"/>
                    <Button Content="Delete" Click="ButtonDeleteSoapToDo_Click" Foreground="White" Background="#FFE44D26" Margin="5,0,0,0"/>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    <Button Content="Refresh the list" Foreground="White" Background="#FFE44D26" Click="ButtonRefreshSoapToDos_Click" HorizontalAlignment="Left" Margin="0,10,0,0"/>
</StackPanel>

10) Add the following code to MainPage.xaml.cs (IMPORTANT: be sure to replace the URL in red with the correct one - ie. use the same URL as above):

ServiceReference1.Service1Client _soapClient =
        new ServiceReference1.Service1Client(
            new System.ServiceModel.BasicHttpBinding(),
            new System.ServiceModel.EndpointAddress(
                new Uri("http://localhost:4598/Service1.svc")));

void ButtonRefreshSoapToDos_Click(object sender, RoutedEventArgs e)
{
    var todos = _soapClient.GetToDos();
    SoapToDosItemsControl.ItemsSource = todos;
}

void ButtonAddSoapToDo_Click(object sender, RoutedEventArgs e)
{
    var todo = new ServiceReference1.ToDoItem()
    {
        Description = SoapToDoTextBox.Text,
        Id = Guid.NewGuid()
    };
    _soapClient.AddOrUpdateToDo(todo);
    ButtonRefreshSoapToDos_Click(sender, e);
}

void ButtonDeleteSoapToDo_Click(object sender, RoutedEventArgs e)
{
    try
    {
        var todo = (ServiceReference1.ToDoItem)((Button)sender).DataContext;
        _soapClient.DeleteToDo(todo);
        ButtonRefreshSoapToDos_Click(sender, e);
    }
    catch (System.ServiceModel.FaultException ex)
    {
        // Fault exceptions allow the server to pass information such as "Item not found":
        System.Windows.MessageBox.Show(ex.Message);
    }
}

11) Click "Start Debugging" to test your client/server To-Do items application.

Tutorial to easily create a REST-based client/server app in CSHTML5 (Wep API)

In this tutorial we are going to create a simple client/server application for managing To-Do items. If you have any feedback regarding this tutorial, please send us an email to support@cshtml5.com

1) Create a new project of type "Web -> ASP.NET MVC 4 Web Application" (or newer). Let's call it "MvcApplication1".

When you click OK, a second dialog appears that lets you choose a Project Template. Be sure to select the "Web API" project template. Makes sure the option "Create a unit test project" is NOT checked, and click OK.

2) Modify Web.Config to add the following code (note: you need to add the code to the right section of Web.Config):

<configuration> 
  <system.webServer>

    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type, Content-Length" />
        <add name="Access-Control-Allow-Methods" value="POST,PUT,GET,DELETE,OPTIONS" />
      </customHeaders>
    </httpProtocol>

  </system.webServer>
</configuration>

3) Create a new class called "ToDoItem.cs" inside the folder named "Models". Copy/paste the following code:

using System;

namespace MvcApplication1.Models
{
    public class ToDoItem
    {
        public Guid Id { get; set; }
        public Guid OwnerId { get; set; }
        public string Description { get; set; }
    }
}

4) Right-click the folder "Controllers" and click "Add -> Controller...". Enter the name "ToDoController" and choose "Empty MVC Controller" in the "Template" drop-down. Then click OK.
Note: the name of the controller is very important because it will have a direct impact on the URL of your REST web service.

using MvcApplication1.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace MvcApplication1.Controllers
{
    public class ToDoController : ApiController
    {
        private static Dictionary<Guid, ToDoItem> _todos = new Dictionary<Guid, ToDoItem>();

        // GET api/ToDo
        public IEnumerable<ToDoItem> GetToDos()
        {
            return _todos.Values.ToList();
        }

        // GET api/ToDo/5
        public ToDoItem GetToDo(Guid id)
        {
            if (_todos.ContainsKey(id))
                return _todos[id];
            else
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
                    {
                        Content = new StringContent("ID not found: " + id),
                        ReasonPhrase = "ID not found"
                    });
        }

        // OPTIONS
        public HttpResponseMessage Options()
        {
            return Request.CreateResponse(HttpStatusCode.OK);
        }

        // PUT api/Todo/5
        public HttpResponseMessage PutToDoItem(Guid id, ToDoItem toDoItem)
        {
            if (id != toDoItem.Id)
                return new HttpResponseMessage(HttpStatusCode.BadRequest)
                {
                    Content = new StringContent("The ID must be the same as the ID of the todo."),
                    ReasonPhrase = "The ID must be the same as the ID of the todo"
                };

            _todos[toDoItem.Id] = toDoItem;
            return Request.CreateResponse(HttpStatusCode.OK);
        }

        // POST api/Todo
        public HttpResponseMessage PostToDoItem(ToDoItem toDoItem)
        {
            _todos[toDoItem.Id] = toDoItem;

            return Request.CreateResponse(HttpStatusCode.OK);
        }

        // DELETE api/Todo/5
        public HttpResponseMessage DeleteToDoItem(Guid id)
        {
            if (_todos.ContainsKey(id))
            {
                var toDoItem = _todos[id];
                _todos.Remove(id);
                return Request.CreateResponse(HttpStatusCode.OK, toDoItem);
            }
            else
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
                    {
                        Content = new StringContent("ID not found: " + id),
                        ReasonPhrase = "ID not found"
                    });
        }
    }
}

5) Click "Start Debugging" (F5) and take note of the URL of the service. It should be something like: http://localhost:4858
Keep the project running.

6) In a new instance of Visual Studio, create a new project of type C#/XAML for HTML5 -> Empty Application.

7) Modify the page MainPage.XAML by removing the default TextBlock and replacing it with the following code:

<StackPanel>
    <TextBlock Text="CREATE A NEW TO-DO:" Margin="0,20,0,0" Foreground="Black" HorizontalAlignment="Left"/>
    <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
        <TextBox x:Name="RestToDoTextBox" Width="200" Text="Enter your To-Do here" Foreground="Black" Background="#FFEEEEEE"/>
        <Button Content="Create" Click="ButtonAddRestToDo_Click" Foreground="White" Background="#FFE44D26" Margin="5,0,0,0"/>
    </StackPanel>
    <TextBlock Text="LIST OF TODO's:" Margin="0,20,0,0" Foreground="Black" HorizontalAlignment="Left"/>
    <ItemsControl x:Name="RestToDosItemsControl" HorizontalAlignment="Left">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
                    <TextBlock Text="{Binding Description}" Foreground="Black"/>
                    <Button Content="Delete" Click="ButtonDeleteRestToDo_Click" Foreground="White" Background="#FFE44D26" Margin="5,0,0,0"/>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    <Button Content="Refresh the list" Foreground="White" Background="#FFE44D26" Click="ButtonRefreshRestToDos_Click" HorizontalAlignment="Left" Margin="0,10,0,0"/>
</StackPanel>

8) Add the following code to MainPage.xaml.cs (IMPORTANT: be sure to replace the URL in yellow with the correct one - ie. use the same URL as above):

const string BaseUrl = "http://localhost:4858";

void ButtonRefreshRestToDos_Click(object sender, RoutedEventArgs e)
{
    var webClient = new WebClient();
    webClient.Encoding = Encoding.UTF8;
    webClient.Headers[HttpRequestHeader.Accept] = "application/xml";
    string response = webClient.DownloadString(BaseUrl + "//api/Todo");
    response = response.Replace(@"xmlns=""http://schemas.datacontract.org/2004/07/MvcApplication1.Models""", "");
    response = "<ToDoItemsWrapper>" + response.Replace("ArrayOfToDoItem", "ToDoItems") + "</ToDoItemsWrapper>"; // Workaround for the fact that "ArrayOf" types cannot be directly deserialized by the XmlSerializer in this Beta version.
    var deserializer = new XmlSerializer(typeof(ToDoItemsWrapper));
    var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(response));
    var xmlReader = XmlReader.Create(memoryStream);
    var items = (ToDoItemsWrapper)deserializer.Deserialize(xmlReader);
    RestToDosItemsControl.ItemsSource = items.ToDoItems;
}

void ButtonAddRestToDo_Click(object sender, RoutedEventArgs e)
{
    string data = string.Format(@"{{""Id"": ""{0}"",""Description"": ""{1}""}}", Guid.NewGuid(), RestToDoTextBox.Text.Replace("\"", "'"));
    var webClient = new WebClient();
    webClient.Headers[HttpRequestHeader.ContentType] = "application/json";
    webClient.Encoding = Encoding.UTF8;
    string response = webClient.UploadString(BaseUrl + "/api/Todo/", "POST", data);
    ButtonRefreshRestToDos_Click(sender, e);
}

void ButtonDeleteRestToDo_Click(object sender, RoutedEventArgs e)
{
    ToDoItem todo = (ToDoItem)((Button)sender).DataContext;
    var webClient = new WebClient();
    string response = webClient.UploadString(BaseUrl + "/api/Todo/" + todo.Id.ToString(), "DELETE", "");
    ButtonRefreshRestToDos_Click(sender, e);
}

[DataContract]
public class ToDoItem
{
    public Guid Id { get; set; }
    public Guid OwnerId { get; set; }
    public string Description { get; set; }
}

// Workaround for the fact that "ArrayOf" types cannot be directly deserialized by the XmlSerializer in this Beta version:
[DataContract]
public class ToDoItemsWrapper
{
    public List<ToDoItem> ToDoItems { get; set; }
}

9) Click "Start Debugging" to test your client/server To-Do items application.

Support

For any question, please post a message on the forums or contact us at support@cshtml5.com