Category Archives: WCF

Insert a Stream into SQL

I have written a couple of articles around this; relating to transmitting large files over WCF and enabling filestream in SQL. This article deals with actually inserting one of those large files into the DB and retrieving it back out again.

The following method does not use FILESTREAM; that requires a slightly different syntax.

The Database

If you have a look at the linked articles, you’ll already have seen how the data that I’m dealing with is arranged; however, here’s a create statement for the table; just in case you want to try this:

CREATE TABLE [dbo].[BinaryDataTest](
	[ROWGUID] [uniqueidentifier] ROWGUIDCOL  NOT NULL,
	[DataName] [nchar](10) NOT NULL,
	[Data] [varbinary](max) FILESTREAM  NULL,
	[Data2] [varbinary](max) NULL,
UNIQUE NONCLUSTERED 
(
	[ROWGUID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] FILESTREAM_ON [fs_fg_filestream]

For completeness, my DB is called TestDB.

You’ll notice that `Data` uses FILESTREAM. However, I won’t cover that in this post.

The Service

Here’s an example of how you would write the insert statement in your service (the same method should work whether or not a service is used):

        public void InsertData(Stream value)
        {            
            string connectionString = ConfigurationManager.ConnectionStrings["TestDB"].ConnectionString;
            using (SqlConnection cn = new SqlConnection(connectionString))
            using (SqlCommand cmd = cn.CreateCommand())
            {
                cn.Open();                

                cmd.CommandText = $"INSERT INTO [dbo].[BinaryDataTest] (" +
                    "[ROWGUID],[DataName],[Data2] ) " +
                    "VALUES (NEWID(), 'test', @DataVarBinary)";
                cmd.CommandType = System.Data.CommandType.Text;

                MemoryStream newStream = new MemoryStream();
                value.CopyTo(newStream);
                SqlParameter sqlParameterBin = new
                    SqlParameter("@DataVarBinary", SqlDbType.VarBinary);
                sqlParameterBin.Value = new SqlBytes(newStream);
                cmd.Parameters.Add(sqlParameterBin);

                cmd.ExecuteNonQuery();
            }
        }

As you can see, I have a connection string called “TestDB”; other than that, I think the only remarkable thing (that is: thing worthy of remark – not astounding) is the SqlParameter set-up. Use the VarBinary SQL type, and the ADO.NET SQL function SqlBytes(), and you’re good to go.

Next, there’s the data retrieval:


        public Stream GetData(string dataName)
        {
            string connectionString = ConfigurationManager.ConnectionStrings["TestDB"].ConnectionString;
            using (SqlConnection cn = new SqlConnection(connectionString))
            using (SqlCommand cmd = cn.CreateCommand())
            {
                cn.Open();
                cmd.CommandText = "SELECT [ROWGUID],[DataName],[Data],[Data2]" +
                    " FROM [dbo].[BinaryDataTest]" +
                    " WHERE DataName = @DataName";
                cmd.CommandType = System.Data.CommandType.Text;
                cmd.Parameters.AddWithValue("DataName", dataName);
                using (var rdr = cmd.ExecuteReader())
                {
                    while (rdr.Read())
                    {
                        Stream str = rdr.GetStream(rdr.GetOrdinal("Data2"));

                        return str;
                    }
                }
            }

            throw new Exception("Invalid data");
        }

A familiar looking idea. You’ll see that I’m only returning that `Data2` as stated earlier, and am using the SqlDataReader.GetStream() function.

The Client

I’m deliberately missing out the configuration that enables you to send these files, and which is documented here.

Here’s the Main() function of a client console app:


        static void Main(string[] args)
        {
            ServiceReference1.Service1Client svc = new ServiceReference1.Service1Client();
            Stream stream = File.OpenRead(@"c:\tmp\test.bmp");

            svc.InsertData(stream);

            Stream strDest = File.OpenWrite(@"c:\tmp2\testdestination.bmp");
            Stream str2 = svc.GetData("test");                        
            str2.CopyTo(strDest);

        }

Summary

So, we’re reading a file from c:\tmp into a stream, and sending that, via WCF into the SQL DB. Then, we’re reading that back out of the SQL DB, and sending it back over to the client. The client then writes this out to a file.

I fully intend to cover how this differs in a FILESTREAM column in a later post.

WCF Debugging

WCF is a good product in so many respects; but error reporting isn’t one of them. Whilst trying to get a WCF service to work, I encountered this error:

Exception thrown: ‘System.ServiceModel.CommunicationException’ in mscorlib.dll

Additional information: An error occurred while receiving the HTTP response to http://localhost:17065/Service1.svc. This could be due to the service endpoint binding not using the HTTP protocol. This could also be due to an HTTP request context being aborted by the server (possibly due to the service shutting down). See server logs for more details.

WCFError

Diagnosis

The following will produce a surprisingly useful diagnosis file.

To add diagnostics, edit the web.config of the service and add:

<configuration>
  <system.diagnostics>
    <sources>
      <source name="System.ServiceModel" switchValue="Warning" propagateActivity="true">
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>

      <source name="TraceSource" switchValue="Warning, ActivityTracing" propagateActivity="true">
        <listeners>
          <add name="xml"/>
        </listeners>
      </source>      

    </sources>
    <sharedListeners>
      <add name="xml" type="System.Diagnostics.XmlWriterTraceListener" initializeData="c:\trc.svclog"/>
    </sharedListeners>
  </system.diagnostics>

And then:

  <system.serviceModel>
    <diagnostics wmiProviderEnabled="true">
      
    </diagnostics>

Now replicate the error and have a look in the directory you pointed the initializeData to (in my case c:\trc.svclog). Providing you’ve used the correct extension, double clicking the file should give you something like this:

WCFTrace

Sending Binary Files Over WCF

This is an interesting one – it is possible to load a binary file (such as an exe) in a .NET service and return it to the client via WCF. It’s actually not that complex either; in this example, I’ve created a basic service and console application.

Code

The code for the service is really straight forward:

    public class Service1 : IService1
    {
        public Stream GetData(int value)
        {
            FileStream stream = File.OpenRead(@"c:\tmp\MyFile");
            return stream;
        }
    }

And here is the Interface:

    [ServiceContract]
    public interface IService1
    {

        [OperationContract]
        Stream GetData(int value);
    }

The important part about the above code is that the method must return a Stream, and can only accept a single parameter (you can send large data by reversing this).

The client code is a little more involved. The Main() function of the console app is here:

        static void Main(string[] args)
        {
            Task rd = ReceiveData();
            rd.Wait();

And ReceiveData() looks like this:

        private static async Task ReceiveData()
        {
            ServiceReference1.Service1Client sc = new ServiceReference1.Service1Client();
            Stream stream = await sc.GetDataAsync();            

            using (var fs = File.Create(@"c:\tmp2\newfile.exe"))
            {
                int b;
                do
                {
                    b = stream.ReadByte();
                    fs.WriteByte((byte)b); 
                } while (b != -1);
            }
        }

As you can see, although the code is a bit messy, it’s not complex, and the bulk of the code is actually turning the Stream into a file (I’m sure there’s an easier way of doing this).

Configuration

The config files are the key here. The encoding and transfer mode need to be changed.

The service web.config:

    <bindings>
      <basicHttpBinding>
        <binding name="BasicStreaming" messageEncoding="Mtom" transferMode="Streamed"
                 closeTimeout="10:00:00"
                 />

      </basicHttpBinding>
    </bindings>

Create an endpoint for the service and bind it to above:

  <system.serviceModel>
    <services>
      <service name="FileService.Service1">
        <endpoint address="Service1.svc" binding="basicHttpBinding"
          bindingConfiguration="BasicStreaming" contract="FileService.IService1" />
      </service>
    </services>

So, the transfer mode is streamed, and the encoding is ‘Mtom’. Make sure that the endpoint is configured against the binding (otherwise it’ll use the default binding, repeatedly moan that it’s out of memory and mismatched for no apparent reason and you’ll spend ages wondering why).

The client config can be updated using the “Update Service Reference” option; however, double check what it adds.

The App.config changes from the client:

    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicStreaming" closeTimeout="10:00:00" 
                    maxReceivedMessageSize="400000000" messageEncoding="Mtom" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:17065/Service1.svc/Service1.svc"
                binding="basicHttpBinding" bindingConfiguration="BasicStreaming"
                contract="ServiceReference1.IService1" name="BasicHttpBinding_IService1" />
        </client>
    </system.serviceModel>

The only real thing of note in the client is the `maxReceivedMessageSize`; this has to be big enough to take your largest file. Obviously, the timeout settings matter, but maybe if you’re taking 10 minutes to transfer then you’ve got bigger problems.

The End

That’s it – it all works neatly, and looks deceptively easy!

Returning an unknown interface from a WCF Service

The Problem

There are times when creating a WCF service that you may want to return a value, whilst not knowing precisely which value; for example, a service contract such as this:

[ServiceContract]
public interface IMyService
{
    [OperationContract()]
    FunctionResult MyFunction(string parameter);
}

… and a data contact like this:


[DataContract]
public class FunctionResult
{
    private int _myData;
    [DataMember(Name="MyData")]
    public int MyData
    {
        get { return _myData; }
        set { _myData = value; }
    }
	
    // Here, I want to return a class, 
    // but it may be a number of different 
    // options that share no commonality
}

So, how?

One solution to this, right or wrong, is to create a blank interface:

interface IData { }

Then to implement it in whichever class I want to return:

public class MyClass : IData

Finally, include this in my `FunctionResult` return class:

[DataContract]
public class FunctionResult
{
    private int _myData;
    [DataMember(Name="MyData")]
    public int MyData
    {
        get { return _myData; }
        set { _myData = value; }
    }

    private IData _returnData;
    [DataMember(Name="ReturnData")]
    public IData ReturnData
    {
        get { return _returnData; }
        set { _returnData = value; }
    }
}

You do need to declare the concrete class as a `ServiceKnownType`:


[ServiceContract]
[ServiceKnownType(typeof(MyClass))]
public interface IMyService
{
    [OperationContract()]
    OperationResult MyFunction(string paramater);
}

Acknowledgements

The `ServiceKnownType` wasn’t something I had come across before – thankfully, we have StackOverflow for that kind of thing:

http://stackoverflow.com/questions/310160/passing-interface-in-a-wcf-service

Service has zero application (non-infrastructure) endpoints

The Error

Service ‘Namespace.ServiceName’ has zero application (non-infrastructure) endpoints. This might be because no configuration file was found for your application, or because no service element matching the service name could be found in the configuration file, or because no endpoints were defined in the service element.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: Service ‘Namespace.ServiceName’ has zero application (non-infrastructure) endpoints. This might be because no configuration file was found for your application, or because no service element matching the service name could be found in the configuration file, or because no endpoints were defined in the service element.

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:

[InvalidOperationException: Service ‘Namespace.ServiceName’ has zero application (non-infrastructure) endpoints. This might be because no configuration file was found for your application, or because no service element matching the service name could be found in the configuration file, or because no endpoints were defined in the service element.]
System.ServiceModel.Description.DispatcherBuilder.EnsureThereAreApplicationEndpoints(ServiceDescription description) +345
System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost(ServiceDescription description, ServiceHostBase serviceHost) +292
System.ServiceModel.ServiceHostBase.InitializeRuntime() +90
System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout) +175
System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout) +740
System.ServiceModel.HostingManager.ActivateService(ServiceActivationInfo serviceActivationInfo, EventTraceActivity eventTraceActivity) +125
System.ServiceModel.HostingManager.EnsureServiceAvailable(String normalizedVirtualPath, EventTraceActivity eventTraceActivity) +901
[ServiceActivationException: The service ‘/ServiceDirectory/ServiceName.svc’ cannot be activated due to an exception during compilation. The exception message is: Service ‘Namespace.ServiceName’ has zero application (non-infrastructure) endpoints. This might be because no configuration file was found for your application, or because no service element matching the service name could be found in the configuration file, or because no endpoints were defined in the service element..]
System.Runtime.AsyncResult.End(IAsyncResult result) +624522
System.ServiceModel.Activation.HostedHttpRequestAsyncResult.End(IAsyncResult result) +196075
System.Web.AsyncEventExecutionStep.OnAsyncEventCompletion(IAsyncResult ar) +166

When are you likely to get this

This appears frequently when setting up new WCF services, or changing namespace or service names of existing ones. Unless, you’re a machine and never mistype anything, that is.

The cause

This is caused by a configuration error in the web.config. In my experience, it’s always caused by this; even if you’re sure that you’ve got it configured right – you haven’t.

Resolution

1. Check that the web.config has an endpoint defined:


      <service behaviorConfiguration="commonBehavior" name="Namespace.ServiceName">
        <endpoint binding="..." contract="Interface.IServiceName"/>
      </service>

2. Check the spelling. Compare the namespace against the service.svc file:


<%@ ServiceHost Service="Namespace.ServiceName" %>

3. Make sure you have the namespace specified in the web.config. The easiest way to determine why a WCF service is not working is navigating to the service.svc in internet explorer.

4. Don’t forget to do an IISReset if you change the web.config.