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
- How to enable SQL FileStream Types
- Using SQL FileStream Types in your Databases
- MaxRequestLength – Packet sizes: Size isn’t everything
- RIA Services: File uploading
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.
December 26th, 2011 on 08:43
Hi,
Thank you for a great article, it has been an eye opener.
I am relatively new to Silverlight.
I have managed to follow your article up until Domain Service ‘AttachmentService’.
I created a new item, Domain service Class, called it ‘AttachmentService’
How is it that you do not get the following error message on ‘LinqToEntitiesDomainService’:-
Using the generic type ‘System.ServiceModel.DomainServices.EntityFramework.LinqToEntitiesDomainService’ requires 1 type arguments
Is it possible you can release the source code.
Cheers
Glen
January 5th, 2012 on 05:12
Hey Glen,
Nice pick up there. Didn’t realise it was missing lol
What is missing is the DataContext that your Domain Service is linked to.
If you are using EntityFramework and have a DataModel class, then this parameter will be your DataModel’s Context / Entities class.
For example: If your Data model is called MyModel.edmx there should be a class auto-generated called MyEntities. This is the Type you need to pass into your DomainService definition.
ie: public class AttachmentService : LinqToEntitiesDomainService<MyEntities>
Hope this makes sense, if not let me know and I’ll clarify further!
January 16th, 2012 on 18:02
Hi,
Thanks very much for replying.
I understood your reply regarding ” and have applied it and now it works.
We have a table called ‘Attachment’ but in class ‘AttachmentRepository’ I can see ‘AttachmentPM’, what is it.
Sorry to be so basic.
Cheers
Glen
January 16th, 2012 on 22:51
Hi Glen,
AttachmentPM is a presentation model object – ie its an object that is declared specifically for the UI layer.
For all intensive purposes, just consider AttachmentPM as any POCO business object passed from the Repository to the UI, and quite possibly vice versa.
In my design, AttachmentPM just contains a subset of properties that the UI needs from the Attachment table (eg ID, OriginalFileName, NewFileName, FileSize etc)
The UI didnt need ALL the fields from this table, so I created a ‘PM’ object to store only what was required.