Mono WCF Advent Day 6: ASP.NET hosting

| 16 Comments | No TrackBacks

Mono WCF services can be hosted through ASP.NET. Actually, lots of WCF consumers in .NET would be aware only of this path, like, you may be only aware of .svc files and not ServiceHost class.

Service configuration through .config files

Until today I haven't explain any of .config file support while lots of WCF introductions do. I personally don't like WCF config support which rather brought confusion (and I was right; WCF 4.0 came with some simplification in this area), but if we want to use ASP.NET, we sort of need to use it.

We partially support service and client configuration by config files. A WCF configuration would look like this:

<configuration>
  <system.serviceModel>
    <services>
      <service name="HelloService" behaviorConfiguration="b">
        <endpoint binding="basicHttpBinding" contract="IHelloService" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="b">
          <serviceMetadata httpGetEnabled="true" httpGetUrl="wsdl" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

By having this as "service.exe.config", you can omit these lines:

-    host.AddServiceEndpoint (
-      typeof (IHelloService), binding, address);

-    var meta = new ServiceMetadataBehavior () {
-      HttpGetEnabled = true,
-      HttpGetUrl = new Uri ("wsdl", UriKind.Relative) };
-    host.Description.Behaviors.Add (meta);

Note that configuration support is valid only on the full .NET profile. In Moonlight and MonoTouch there is an alternative (and much simpler) way to give configuration ("ServiceReferences.ClientConfig" file). I don't explain it here - try googling ServiceReferences.ClientConfig.

.svc http handler

.svc files in ASP.NET are processed by an IHttpHandler(Factory) to generate a service hosting class that internally starts ServiceHost and serves HTTP requests as a HTTP transport implementation. We do the same.

Now, it's time for instructions. First, create one-liner "test.svc" file as:

<%@ServiceHost Service="HelloService, service" %>

Then comment out the entire "Test" class from service.cs.

+ /*
  public class Test
(...)
  }
+ */

Create "bin" directory, and compile the service class into a library this time:

$ mkdir bin

$ gmcs -t:library -out:bin/service.dll service.cs -pkg:wcf

To run the service in ASP.NET, you need a configuration (now that you don't have configured service host in Test class). To do it, use web.config. Copy the example config above:

$ cp service.exe.config web.config

Then run:

$ xsp2

Now your service is hosted at http://localhost:8080/test.svc (note that the URL now ends with "test.svc"). Go to http://localhost:8080/test.svc/wsdl , there should be the WSDL we exposed at Day 5.

Limitations

ASP.NET integration is under some tricky situation to fit with existing HttpListener-based channel implementation, and actually have some safety nets that blocks concurrent calls. It would work, but is not likely to scale so far.

Though, as a HTTP server, xsp outperforms HttpListener a lot, so it may work nicer in the future (unless you hit WCF-specific bugs). The simpler (especially for debugging) is those without xsp. I have to admit that ASP.NET integration is somewhat buggy so far.

No TrackBacks

TrackBack URL: http://veritas-vos-liberabit.com/monogatari/mt-tb.cgi/89

16 Comments

Hello Atsushi,

Regarding our ASP.NET integration, does this mean that Mono always uses HttpListener, even if XSP is running and providing the data?

Where is the code that hosts a .svc file in XSP?

No, we don't use HttpListener when WCF service host is instantiated inside ASP.NET handler. When it is inside ASP.NET, HTTP transport listener is instantiated from HttpContext. When it isn't, the listener is instantiated from HttpListenerContext.

IHttpHandler for .svc file is in System.ServiceModel.dll - System.ServiceModel.Channels/SvcHttpHandler(Factory).cs.

Hello, I follow step by step the example but when I try to access to http://localhost:8080/test.svc/wsdl I get the error: "Type HelloService not found. Description: HTTP 500. Error processing request."

I have all 2 configuration files, and the dll in bin folder. Which can be the problem?

My wild guess is, you likely don't have bin/service.dll (which contains HelloService class.
I noticed I have bad line ending on my command lines above (I had "$ mkdir bin $ gmcs -t:library -out:bin/service.dll service.cs -pkg:wcf" in one line), which is of bad. So if you have simply copied it, split them into two lines and make sure you ran gmcs.

In case it's still not working - if you can send me the files you created, I'll have a look.

Hello Atsushi,

thank you very much for this tutorial. Since mono is developing very fast, your entries are very useful.

I tried to launch xsp2 to run REST WS, but it resulted in:
"Base addresses must be added before the service description is initialized"

I used a simple config file, that works with .NET 3.5 + Win. Please, could you provide the right configuration for REST hosting?

Thank you very much

Stack Trace:

System.InvalidOperationException: Base addresses must be added before the service description is initialized
at System.ServiceModel.ServiceHostBase.AddBaseAddress (System.Uri baseAddress) [0x00000] in :0
at System.ServiceModel.ServiceHostBase.ApplyConfiguration () [0x00000] in :0
at System.ServiceModel.ServiceHostBase.InitializeDescription (System.ServiceModel.UriSchemeKeyedCollection baseAddresses) [0x00000] in :0
at System.ServiceModel.ServiceHost.InitializeDescription (System.Type serviceType, System.ServiceModel.UriSchemeKeyedCollection baseAddresses) [0x00000] in :0
at System.ServiceModel.ServiceHost..ctor (System.Type serviceType, System.Uri[] baseAddresses) [0x00000] in :0
at System.ServiceModel.Web.WebServiceHost..ctor (System.Type serviceType, System.Uri[] baseAddresses) [0x00000] in :0
at System.ServiceModel.Activation.WebServiceHostFactory.CreateServiceHost (System.Type serviceType, System.Uri[] baseAddresses) [0x00000] in :0
at System.ServiceModel.Channels.SvcHttpHandler.EnsureServiceHost () [0x00000] in :0
at System.ServiceModel.Channels.SvcHttpHandler.ProcessRequest (System.Web.HttpContext context) [0x00000] in :0
at System.Web.HttpApplication+c__Iterator2.MoveNext () [0x00000] in :0
at System.Web.HttpApplication.Tick () [0x00000] in :0

Hi Angwin,

Our configuration stack is incomplete and you may hit some issues, but I have no right idea what is exactly causing the issue at your side. Regarding the configuration support, I have made no change after 2.6 and it wouldn't work with mono from trunk either.

If you file a bug on our bugzilla I'll have a look. A workaround that likely works is to not use the config file.
http://bugzilla.novell.com/

One problem here: I´m building a WCF service with mono 2.6.1 with ASP.NET hosting consuming it from silverlight app.

I´m getting error 500 the first time I call the service, but next few calls works ok. Later, a few minutes crashes and it do not respond any of my queries.

I´ve tried with xsp, xsp2 and mod_mono with apache. I´ve tried with mono virtual machine image, and mono compiled from sources (not svn).

I´m doing something wrong?

Hmm, on the first failure, it's not kind of error I have heard so far. The failure after a couple of connections is likely the one I have fixed recently (it's in svn, and should be fixed in the next release). It didn't involve crash though, unless it is about timeout.

As I wrote in this entry, ASP.NET hosting has some other issues and some of them may bite you. It is safer to use xsp so far.

Thanks for your reply.

I´ve got the same problem with xsp. Indeed, when i use xsp with --verbose, i can see that the very first access to whatever wcf method in the server, I think it isn´t doing the response.

I´ve tried mono live cd with version 2.6.1, not-svn sources compiled and your virtualmachine mono/linux image.

So far, this is what i get (maybe it can help):

root@jackomeg/var/www# xsp --verbose
Adding applications '/.'...
Registering application
Host any
Port any
Virtual path /
Physical path /var/www
xsp2
Listening on address 0.0.0.0
Root directory /var/www
Listening on port 8080 (non-secure)
Hit Return to stop the server.
Enter AspNetReplyChannel.WaitForRequest
-->> Here, the client do the first query
Enter HttpContextAcquired: http://172.16.0.131:8080/Service1.svc
-->> Error 500 it´s received at the client (win silverlight app).
-->> Until new query to service, xsp do not show nothing new.

-->> I do a new query (the first one or new one,
-->> same result). It work ok
Exit HttpContextAcquired: http://172.16.0.131:8080/Service1.svc
Exit AspNetReplyChannel.WaitForRequest
Enter AspNetReplyChannel.WaitForRequest

-->> If i do new querys, everything works like
-->> a charm, until (i presume) some timeout happens!
...........

-->> Here I press ENTER to quit xsp!
AspNetReplyChannel caught an error System.Threading.ThreadAbortException Thread was being aborted
at (wrapper managed-to-native) System.Threading.WaitHandleWaitOne_internal (intptr,int,bool)
at System.Threading.WaitHandle.WaitOne (TimeSpan timeout, Boolean exitContext) [0x00000] in filename unknown0
at System.Threading.WaitHandle.WaitOne (TimeSpan timeout) [0x00000] in filename unknown0
at System.ServiceModel.Channels.HttpListenerManager.GetHttpContextAsync (TimeSpan timeout, System.Action`1 callback) [0x00000] in filename unknown0
at System.ServiceModel.Channels.AspNetReplyChannel.WaitForRequest (TimeSpan timeout) [0x00000] in filename unknown0
at System.ServiceModel.Channels.AspNetReplyChannel.TryReceiveRequestCore (TimeSpan timeout, System.ServiceModel.Channels.RequestContext& context) [0x00000] in filename unknown0
at System.ServiceModel.Channels.AspNetReplyChannel.TryReceiveRequest (TimeSpan timeout, System.ServiceModel.Channels.RequestContext& context) [0x00000] in filename unknown0
ChannelDispatcher caught an exception inside dispatcher loop, which is likely thrown by the channel listener System.ServiceModel.Channels.AspNetChannelListener`1[System.ServiceModel.Channels.IReplyChannel]
System.Threading.ThreadAbortException Thread was being aborted
at (wrapper managed-to-native) object__icall_wrapper_mono_thread_interruption_checkpoint ()
at (wrapper managed-to-native) System.Threading.WaitHandleWaitOne_internal (intptr,int,bool)
at System.Threading.WaitHandle.WaitOne () [0x00000] in filename unknown0
at System.ServiceModel.Dispatcher.ListenerLoopManager.LoopCore () [0x00000] in filename unknown0
at System.ServiceModel.Dispatcher.ListenerLoopManager.Loop () [0x00000] in filename unknown0


Sorry about my poor english.

When I start xsp2 from the directory where my Service1.svc is, browsing to it I'm offered to download it "which is: a BIN file"
What have I done wrong?

It sounds like typical missing mime-type misconfiguration. It should be fixed if you get web.config or machine.config fixed, but instead you might want to check that you are not running older mono, since if you are running xsp with mono 2.6.* it should not happen.

Hi Atsushi,

I've developed a REST WCF service and it fails after a couple of calls as you mentioned in your comment on "February 2, 2010 11:30 AM" in this article.

How can I get the sources from the svn? what do I have to do exactly to get it work?

Thanks a lot and great job.

The WCF stack is part of mono and mcs, so just go along with our build instructions: http://mono-project.com/Compiling_Mono

Thanks!

Hi again,

I've downloaded and installed the latest version from the SVN and I get the same issue.

I try with this test method in a REST WCF service:

public Message GetGzipData()
{
using (SqlConnection con = new SqlConnection("Data Source=192.168.1.33\\SQLEXPRESS;Initial Catalog=demo;User Id=xxx;Password=xxx"))
using (SqlCommand cmd = new SqlCommand("select * from xxx", con))
{
con.Open();
using (SqlDataReader dr = cmd.ExecuteReader())
using (MemoryStream ms = new MemoryStream())
using (GZipStream ds = new GZipStream(ms, CompressionMode.Compress))
using (StreamWriter ws = new StreamWriter(ds, Encoding.UTF8))
{
ws.WriteLine("");
String pattern = null;

while (dr.Read())
{
if (pattern == null)
{
StringBuilder sbpattern = new StringBuilder(" for (int i = 0; i {
sbpattern.Append("p" + i.ToString() + " = \"{" + i.ToString() + "}\"");
}
sbpattern.Append(" />\n");
pattern = sbpattern.ToString();
}

Object[] values = new Object[dr.FieldCount];
dr.GetValues(values);
ws.WriteLine(pattern, values);
}

ws.WriteLine("");
ws.Flush();
ds.Flush();
ms.Seek(0, SeekOrigin.Begin);
return Message.CreateMessage(MessageVersion.None, "*", ms.ToArray());
}
}
}


Then I deploy to my local IIS and to a Linux machine, and from my machine I execute this code:

for (int i = 0; i {
WebRequest req = WebRequest.Create("http://localhost/RESTfulService/Service1.svc/GetGzipData");
WebResponse res = req.GetResponse();
Console.WriteLine("local iis: " +res.ContentLength.ToString());

req = WebRequest.Create("http://192.168.1.74/resttest/Service1.svc/GetGzipData");
res = req.GetResponse();
Console.WriteLine("apache: " +res.ContentLength.ToString());

Console.WriteLine("\n");
}

The execution gets stuck in the third call to the apache server, besides the result is slighly different from one to each other:
-------------
local iis: 88885
apache: 38161


local iis: 88885
apache: 38161


local iis: 88885
(here it gets stuck)
-------------

I use a very straightforward configuration (The xml doesn't appears in this reply but maybe you can see it in the source):



















[ServiceContract]
public interface IRESTfulService
{
[WebGet(UriTemplate="/")]
[OperationContract]
Message GetRoot();

[WebGet(UriTemplate = "/GetDeflateData")]
[OperationContract]
Message GetDeflateData();

[WebGet(UriTemplate = "/GetGzipData")]
[OperationContract]
Message GetGzipData();

[WebGet(UriTemplate = "/GetData")]
[OperationContract]
Message GetData();
}

I hope this helps somehow.

Kind regards.

REST service on trunk is very unstable these days. Anyways let's please use bugzilla instead of mere blog comments. It's not very good place to track issues.

Leave a comment