THE final part to our File Uploading via RIA Services exercise is the Domain Service itself. This article continues from RIA Services: File uploading. Before reading this article, I encourage you to have a glimpse of the prerequisite articles list below:

Prerequisites

Here I will go into detail on how to code your domain service class to receive data packets sent across the wire from the File Stream Uploader generic handler class.

Attachment Service

Ok first up – The Domain Service.  I called it ‘Attachment Service’.  This example will include two basic functions: Reading streamed data from a SQL FileStream type, and Uploading streamed data to a SQL FileStream type.

The service is pretty basic.. just pipe your data packet parameters from the client, into the Manager class. Here’s the code:

   [EnableClientAccess()]
    public class AttachmentService : LinqToEntitiesDomainService<MyEntities>
    {
        private Manager.IAttachmentManager attachmentManager;

        public AttachmentService()
        {
            this.attachmentManager = new Manager.AttachmentManager();
        }

        [Invoke(HasSideEffects = false)]
        public byte[] ReadStreamed(Guid fileID, int offset, int lengthToRead)
        {
            return attachmentManager.ReadStreamed(fileID, offset, lengthToRead);
        }

        [Invoke(HasSideEffects = false)]
        public Guid UploadStreamed(Guid attachmentID, string newFileName, string originalFileName, byte[] fileData, int position)
        {
            return attachmentManager.UploadStreamed(attachmentID, newFileName, originalFileName, fileData, position);
        }

    }

Manager Class:

Our manager class is also pretty straight forward. There is nothing tricky that is needed here, so we again simply pipe our input parameters into the Repository class.

    public class AttachmentManager : IAttachmentManager
    {
        private Repository.IAttachmentRepository attachmentRep { get; set; }

        public AttachmentManager()
        {
            this.attachmentRep = new Repository.AttachmentRepository();
        }

        public byte[] ReadStreamed(Guid fileID, int offset, int lengthToRead)
        {
            AttachmentRepository rep = new AttachmentRepository();
            return rep.ReadStreamed(fileID, offset, lengthToRead);
        }

        public Guid UploadStreamed(Guid attachmentID, string newFileName, string originalFileName, byte[] fileData, int position)
        {
            AttachmentRepository rep = new AttachmentRepository();
            return rep.UploadStreamed(attachmentID, newFileName, originalFileName, fileData, position);
        }

    }

The Repository:

This is where the fun starts!  Our repository will contain the bulk of the code; and the majority of it is the actual communication with the Data tier.  Our class will contain 3 methods instead of just 2.  The extra method is called ‘InsertAttachmentStub’. This method will be used to create the initial attachment ‘stub’ in our database table.  As you will see below, the stored procedure for this insertion will simply create the record with the AttachmentID, and set the necessary file names and other data fields.

Note: Our AttachmentID being a GUID was generated in the code beforehand, and passed into the Generic Handler.

Note: This stub method is only needed for the very first data packet. Subsequent data packets will re-use the same Attachment record, and update the data component of the FileStream type accordingly.

Without further or do, here’s the signature for our 3 functions:

    public class AttachmentRepository
    {

        public Entities.AttachmentPM InsertAttachmentStub(System.Guid attachmentID, string newFileName, string originalFileName)
        { ... }

        public byte[] ReadStreamed(Guid fileID, int offset, int lengthToRead)
        { ... }

        public Guid UploadStreamed(Guid attachmentID, string newFileName, string originalFileName, byte[] fileData, int position)
        { ... }
    }

Inserting the stub:

As mentioned above, pretty simple Repository function here.  Just pipe all the basic details for your Attachment record into the InsertAttachmentStub Stored Proc:

        public Entities.AttachmentPM InsertAttachmentStub(System.Guid attachmentID, string newFileName, string originalFileName)
        {
            var list = DataAccessUtility.ExecuteStoredProcedure("uspAttachment_InsertAttachmentStub"
                                        , new SqlParameter("ID", attachmentID)
                                        , new SqlParameter("NewFileName", newFileName)
                                        , new SqlParameter("OriginalFileName", originalFileName)
                                        , new SqlParameter("UpdatedDateTime", System.DateTime.Now)
                                        , new SqlParameter("UpdatedBy", System.Data.SqlDbType.Int) { Value = 0 }
                                     );
            return list.FirstOrDefault();
        }

Here’s the SQL proc for InsertAttachmentStub. Once we complete this insert, we will have a database record to store to re-use when uploading the next packets of data!

CREATE PROCEDURE [dbo].[uspAttachment_InsertAttachmentStub]
	@ID						AS UNIQUEIDENTIFIER
	, @NewFileName			AS NVARCHAR(255)
	, @OriginalFileName		AS NVARCHAR(255)
	, @UpdatedDateTime		DATETIME2(7)
	, @UpdatedBy			AS INT
AS
BEGIN

	INSERT INTO Attachment (ID, NewFileName, OriginalFileName, FileData, UpdatedDateTime, UpdatedBy)
	OUTPUT inserted.FileData.PathName()		AS LogicalFilePath
			, inserted.ID					AS ID
	VALUES (@ID, @NewFileName, @OriginalFileName, 0x, @UpdatedDateTime, @UpdatedBy)
END

Reading SQL Filestream data:

Lets begin with the easier of the two methods.. the reading of data.

Our input parameters will identify which AttachmentID we are interested in. It also tells us which byte offset to start our reading from; and how many bytes we want to read and return back to the calling method.

So here’s the reading method, and it performs the following steps:

  • Create a Transaction Context so that SQL knows who you are when reading the FileStream data
  • Make an additional call to grab the File attachment record.  Include in this select the FilePath that we will use later on.
  • Using the Transaction Context AND the FilePath of the file we are interested in reading, create and open your connection to the SQLFileStream.
  • Once the SQLFileStream is opened, we can now read our required data packet.
  • Read the data from the SQLFileStream object into a MemoryStream object
  • Return the data back to the caller
        public byte[] ReadStreamed(Guid fileID, int offset, int lengthToRead)
        {
            System.IO.MemoryStream ms = new System.IO.MemoryStream();

            using (var transScope = new System.Transactions.TransactionScope())
            {

                    byte[] txContext = (byte[])DataAccessUtility.ExecuteScalar(System.Data.CommandType.Text, "SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()");

                    var list = DataAccessUtility.ExecuteStoredProcedure("uspAttachment_GetAttachmentByID"
                                         , new SqlParameter("ID", fileID)
                                         );
                    string filePath = list.FirstOrDefault().LogicalFilePath;

                    System.Data.SqlTypes.SqlFileStream sqlFS = new System.Data.SqlTypes.SqlFileStream(filePath, txContext, System.IO.FileAccess.Read);

                    byte[] buffer = new byte[lengthToRead];

                    sqlFS.Position = offset;
                    sqlFS.Read(buffer, 0, lengthToRead);
                    ms.Write(buffer, 0, lengthToRead);
                    ms.Flush();

                    ms.Close();

                    sqlFS.Close();

                transScope.Complete();
            }
            return ms.ToArray();

        }

Uploading to a SQL FileStream:

Uploading is simply the opposite of what you have accomplished above.

To upload a data packet we perform the following steps:

  • Check if this is the first packet uploaded
  • If yes – call the InsertAttachmentStub method to generate an AttachmentID
  • If not – grab the AttachmentID (and its corresponding FilePath data) from the database
  • Create a Transaction Context so that SQL knows who you are.
  • Using the Transaction Context AND the FilePath of the file we are interested in writing to, create and open your connection to the SQLFileStream.
  • Once the SQLFileStream is opened, we can now write our required data packet.
  • Grab the data packet from our input parameters, and write into the SQLFileStream object
  • Once written, we can close the SQLFileStream object safely and return from this function.
        public Guid UploadStreamed(Guid attachmentID, string newFileName, string originalFileName, byte[] fileData, int position)
        {
            Entities.AttachmentPM attachment;

            using (var transScope = new System.Transactions.TransactionScope())
            {
                    // If Position = 0, then we are inserting the first data packet.
                    if (position == 0)
                    {
                        attachment = this.InsertAttachmentStub(attachmentID, newFileName, originalFileName);
                    }
                    else    // Else Attachment record already exists. Just retrieve the details
                    {
                        var list = DataAccessUtility.ExecuteStoredProcedure("uspAttachment_GetAttachmentByID"
                                        , new SqlParameter("ID", attachmentID)
                                        );
                        attachment = list.FirstOrDefault();
                    }
                    string filePath = attachment.LogicalFilePath;

                    byte[] txContext = (byte[])DataAccessUtility.ExecuteScalar(System.Data.CommandType.Text, "SELECT GET_FILESTREAM_TRANSACTION_CONTEXT()");

                    using (System.Data.SqlTypes.SqlFileStream sqlFS = new System.Data.SqlTypes.SqlFileStream(filePath, txContext, System.IO.FileAccess.ReadWrite))
                    {
                        byte[] buffer = new byte[fileData.Length];

                        sqlFS.Position = sqlFS.Length;
                        sqlFS.Write(fileData, 0, fileData.Length);
                    }

                transScope.Complete();
            }
            return attachment.ID;
        }

Conclusion:

Well that’s pretty much all there is to it!  :)

If you have any question, please let me know!  Hope this article helped!

Cheers.