Basics
This example contains a FileSystemDataModule - an XAF module that you can reuse in your own applications. You will be interested in two types that allow you to attach a file to an object. Both types do not store the files in the database, but keep them in the file system:
- FileSystemStoreObject - stores files in a centralized file system location instead of the database. Use the module's static FileSystemStoreLocation property to specify the file location.
- FileSystemLinkObject - stores soft links to files instead of saving their contents to the database. This type is intended for use in WinForms applications only.
Both types implement the IFileData interface and thus work with the DevExpress File Attachments module.
Refer to the following video to see this functionality in action: http://www.screencast.com/t/Xl1GMfxw.
Steps to implement
- Copy the "FileSystemData" project and include it into your solution. Rebuild the solution and make sure the new project was built successfully.
- Register the FileSystemDataModule in the YourSolutionName.Module/Module.xx file as described in the following help topic: Ways to Register a Module > Add a Module in Code.
- Extend a business class with a property that will hold file attachments. Choose the FileSystemStoreObject or FileSystemLinkObject type for the property. See descriptions above to compare the two types.
- Add the following attributes to the file attachment property: Aggregated, ExpandObjectMembers(ExpandObjectMembers.Never) and ImmediatePostData.
- Decorate the business class with the FileAttachmentAttribute that links to the new property. This attribute enables additional file management UI commands.
- Handle the CustomOpenFileWithDefaultProgram event of the DevExpress.ExpressApp.FileAttachments.Win.FileAttachmentsWindowsFormsModule class. Review the implementation in this example (E965.Win\WinApplication.xx). Move that code to the following file in your project: YourSolutionName.Win/WinApplication.xx.
- XPO Only. Make sure that your application and related modules do not override the DevExpress.Persistent.BaseImpl.BaseObject.OidInitializationMode property. The property must return OidInitializationMode.AfterConstruction to ensure the correct operation of this module. (FileSystemDataModule sets this value as needed.)
Important Notes
- The FileSystemLinkObject class can be used in WinForms applications only.
- The current version of this example does not support the middle-tier scenario. For more information, refer to the following ticket: A problem occurs when file attachments are stored in the file system with middle-tier configuration.
- If you plan to migrate existing FileData objects from the database to a file system, use the techniques described in the following topic: Data Manipulation and Business Logic. The article explains how you can read FileData objects and create new FileSystemStoreObject objects based on their content. Since both classes implement IFileData, you can call their LoadFromStream() and SaveToStream() methods to copy data. Even though we do not provide an example for this migration procedure, we hope that the snippet below help you get started:
C#// Use any other IObjectSpace APIs to query required data.
FileData fd = ObjectSpace.FindObject<FileData>(null);
FileSystemStoreObject fss = ObjectSpace.CreateObject<FileSystemStoreObject>();
Stream sourceStream = new MemoryStream();
((IFileData)fd).SaveToStream(sourceStream);
sourceStream.Position = 0;
((IFileData)fss).LoadFromStream(fd.FileName, sourceStream);
ObjectSpace.CommitChanges();
Note that you can rework this code to use UnitOfWork instead of IObjectSpace.
Files to Review (XPO)
Files to Review (EF Core)
Documentation
See Also
- Working with links to files instead of storing their contents in the database
- XAF - Store file attachments in Dropbox instead of the database (XPO)
- FILESTREAM (SQL Server)
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
C#using System;
using System.IO;
using DevExpress.Xpo;
using DevExpress.ExpressApp;
using System.ComponentModel;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Persistent.Validation;
namespace FileSystemData.BusinessObjects {
/// <summary>
/// This class enables you to add soft links to real files instead of saving their contents to the database. It is intended for use in Windows Forms applications only.
/// </summary>
[DefaultProperty(nameof(FileName))]
public class FileSystemLinkObject : BaseObject, IFileData, IEmptyCheckable, ISupportFullName {
public FileSystemLinkObject(Session session) : base(session) { }
#region IFileData Members
[Size(260), Custom("AllowEdit", "False")]
public string FileName {
get { return GetPropertyValue<string>(nameof(FileName)); }
set { SetPropertyValue(nameof(FileName), value); }
}
void IFileData.Clear() {
Size = 0;
FileName = string.Empty;
}
//Dennis: Fires when uploading a file.
void IFileData.LoadFromStream(string fileName, Stream source) {
Size = (int)source.Length;
FileName = fileName;
}
//Dennis: Fires when saving or opening a file.
void IFileData.SaveToStream(Stream destination) {
try {
if (destination == null)
FileSystemDataModule.OpenFileWithDefaultProgram(FullName);
else
FileSystemDataModule.CopyFileToStream(FullName, destination);
} catch (Exception exc) {
throw new UserFriendlyException(exc);
}
}
[Persistent]
public int Size {
get { return GetPropertyValue<int>(nameof(Size)); }
private set { SetPropertyValue(nameof(Size), value); }
}
#endregion
#region IEmptyCheckable Members
public bool IsEmpty {
get { return !File.Exists(FullName); }
}
#endregion
#region ISupportFullName Members
[Size(260), Custom("AllowEdit", "False")]
public string FullName {
get { return GetPropertyValue<string>(nameof(FullName)); }
set { SetPropertyValue(nameof(FullName), value); }
}
#endregion
}
}
C#using System;
using System.IO;
using DevExpress.Xpo;
using DevExpress.ExpressApp;
using System.ComponentModel;
using DevExpress.Persistent.Base;
using DevExpress.ExpressApp.Utils;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Persistent.Validation;
namespace FileSystemData.BusinessObjects {
/// <summary>
/// This class enables you to store uploaded files in a centralized file system location instead of the database. You can configure the file system store location via the static FileSystemDataModule.FileSystemStoreLocation property.
/// </summary>
[DefaultProperty(nameof(FileName))]
public class FileSystemStoreObject : BaseObject, IFileData, IEmptyCheckable {
private Stream tempSourceStream;
private string tempFileName = string.Empty;
private static object syncRoot = new object();
public FileSystemStoreObject(Session session) : base(session) { }
public string RealFileName {
get {
if (!string.IsNullOrEmpty(FileName) && Oid != Guid.Empty)
return Path.Combine(FileSystemDataModule.FileSystemStoreLocation, string.Format("{0}-{1}", Oid, FileName));
return null;
}
}
protected virtual void SaveFileToStore() {
if(!string.IsNullOrEmpty(RealFileName) && TempSourceStream != null) {
try {
using(Stream destination = File.Create(RealFileName)) { //T582918
FileSystemDataModule.CopyStream(TempSourceStream, destination);
Size = (int)destination.Length;
}
}
catch(DirectoryNotFoundException exc) {
throw new UserFriendlyException(exc);
}
}
}
private void RemoveOldFileFromStore() {
//Dennis: We need to remove the old file from the store when saving the current object.
if (!string.IsNullOrEmpty(tempFileName) && tempFileName != RealFileName) {//B222892
try {
File.Delete(tempFileName);
tempFileName = string.Empty;
} catch (DirectoryNotFoundException exc) {
throw new UserFriendlyException(exc);
}
}
}
protected override void OnSaving() {
base.OnSaving();
Guard.ArgumentNotNullOrEmpty(FileSystemDataModule.FileSystemStoreLocation, "FileSystemStoreLocation");
lock (syncRoot) {
if (!Directory.Exists(FileSystemDataModule.FileSystemStoreLocation))
Directory.CreateDirectory(FileSystemDataModule.FileSystemStoreLocation);
}
SaveFileToStore();
RemoveOldFileFromStore();
}
protected override void OnDeleting() {
//Dennis: We need to remove the old file from the store.
Clear();
base.OnDeleting();
}
protected override void Invalidate(bool disposing) {
if (disposing && TempSourceStream != null) {
TempSourceStream.Close();
TempSourceStream = null;
}
base.Invalidate(disposing);
}
#region IFileData Members
public void Clear() {
//Dennis: When clearing the file name property we need to save the name of the old file to remove it from the store in the future. You can also setup a separate service for that.
if (string.IsNullOrEmpty(tempFileName))
tempFileName = RealFileName;
FileName = string.Empty;
Size = 0;
}
[Size(260)]
public string FileName {
get { return GetPropertyValue<string>(nameof(FileName)); }
set { SetPropertyValue(nameof(FileName), value); }
}
[Browsable(false)]
public Stream TempSourceStream {
get { return tempSourceStream; }
set {
//Michael: The original Stream might be closed after a while (on the web too - T160753)
if (value == null) {
tempSourceStream = null;
} else {
if (value.Length > (long)int.MaxValue) throw new UserFriendlyException("File is too long");
tempSourceStream = new MemoryStream((int)value.Length);
FileSystemDataModule.CopyStream(value, tempSourceStream);
tempSourceStream.Position = 0;
}
}
}
//Dennis: Fires when uploading a file.
void IFileData.LoadFromStream(string fileName,Stream source) {
//Dennis: When assigning a new file we need to save the name of the old file to remove it from the store in the future.
if(fileName != FileName) {// updated, old code was: if (string.IsNullOrEmpty(tempFileName))
tempFileName = RealFileName;
}
FileName = fileName;
TempSourceStream = source;
Size = (int)TempSourceStream.Length;
OnChanged();//T582918
}
//Dennis: Fires when saving or opening a file.
void IFileData.SaveToStream(Stream destination) {
try {
if(!File.Exists(RealFileName)) {
return;
}
if (!string.IsNullOrEmpty(RealFileName)) {
if (destination == null)
FileSystemDataModule.OpenFileWithDefaultProgram(RealFileName);
else
FileSystemDataModule.CopyFileToStream(RealFileName, destination);
}
else if (TempSourceStream != null)
FileSystemDataModule.CopyStream(TempSourceStream, destination);
} catch (DirectoryNotFoundException exc) {
throw new UserFriendlyException(exc);
} catch (FileNotFoundException exc) {
throw new UserFriendlyException(exc);
}
}
[Persistent]
public int Size {
get { return GetPropertyValue<int>(nameof(Size)); }
private set { SetPropertyValue(nameof(Size), value); }
}
#endregion
#region IEmptyCheckable Members
public bool IsEmpty {
//T153149
get { return FileDataHelper.IsFileDataEmpty(this) || !(TempSourceStream!= null || File.Exists(RealFileName)); }
}
#endregion
}
}
C#using System;
using System.IO;
using DevExpress.ExpressApp;
using System.ComponentModel;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl.EF;
using DevExpress.Persistent.Validation;
using DevExpress.ExpressApp.DC;
namespace FileSystemData.BusinessObjects {
/// <summary>
/// This class enables you to add soft links to real files instead of saving their contents to the database. It is intended for use in Windows Forms applications only.
/// </summary>
[DefaultProperty(nameof(FileName))]
public class FileSystemLinkObject : BaseObject, IFileData, IEmptyCheckable, ISupportFullName {
#region IFileData Members
[FieldSize(260)]
[ReadOnly(true)]
public virtual string FileName { get; set; }
void IFileData.Clear() {
Size = 0;
FileName = string.Empty;
}
//Dennis: Fires when uploading a file.
void IFileData.LoadFromStream(string fileName, Stream source) {
Size = (int)source.Length;
FileName = fileName;
}
//Dennis: Fires when saving or opening a file.
void IFileData.SaveToStream(Stream destination) {
try {
if (destination == null)
FileSystemDataModule.OpenFileWithDefaultProgram(FullName);
else
FileSystemDataModule.CopyFileToStream(FullName, destination);
} catch (Exception exc) {
throw new UserFriendlyException(exc);
}
}
public virtual int Size { get; set; }
#endregion
#region IEmptyCheckable Members
public bool IsEmpty {
get { return !File.Exists(FullName); }
}
#endregion
#region ISupportFullName Members
[FieldSize(260)]
[ReadOnly(true)]
public virtual string FullName { get; set; }
#endregion
}
}
C#using System;
using System.IO;
using DevExpress.ExpressApp;
using System.ComponentModel;
using DevExpress.Persistent.Base;
using DevExpress.ExpressApp.Utils;
using DevExpress.Persistent.BaseImpl.EF;
using DevExpress.Persistent.Validation;
using DevExpress.ExpressApp.DC;
using System.ComponentModel.DataAnnotations.Schema;
namespace FileSystemData.BusinessObjects {
/// <summary>
/// This class enables you to store uploaded files in a centralized file system location instead of the database. You can configure the file system store location via the static FileSystemDataModule.FileSystemStoreLocation property.
/// </summary>
[DefaultProperty(nameof(FileName))]
public class FileSystemStoreObject : BaseObject, IFileData, IEmptyCheckable {
private Stream tempSourceStream;
private string tempFileName = string.Empty;
private static object syncRoot = new object();
public string RealFileName {
get {
if (!string.IsNullOrEmpty(FileName) && ID != Guid.Empty)
return Path.Combine(FileSystemDataModule.FileSystemStoreLocation, string.Format("{0}-{1}", ID, FileName));
return null;
}
}
protected virtual void SaveFileToStore() {
if(!string.IsNullOrEmpty(RealFileName) && TempSourceStream != null) {
try {
using(Stream destination = File.Create(RealFileName)) { //T582918
FileSystemDataModule.CopyStream(TempSourceStream, destination);
Size = (int)destination.Length;
}
}
catch(DirectoryNotFoundException exc) {
throw new UserFriendlyException(exc);
}
}
}
private void RemoveOldFileFromStore() {
//Dennis: We need to remove the old file from the store when saving the current object.
if (!string.IsNullOrEmpty(tempFileName) && tempFileName != RealFileName) {//B222892
try {
File.Delete(tempFileName);
tempFileName = string.Empty;
} catch (DirectoryNotFoundException exc) {
throw new UserFriendlyException(exc);
}
}
}
public override void OnSaving() {
base.OnSaving();
if (!ObjectSpace.IsObjectToDelete(this))
{
Guard.ArgumentNotNullOrEmpty(FileSystemDataModule.FileSystemStoreLocation, "FileSystemStoreLocation");
lock (syncRoot)
{
if (!Directory.Exists(FileSystemDataModule.FileSystemStoreLocation))
Directory.CreateDirectory(FileSystemDataModule.FileSystemStoreLocation);
}
SaveFileToStore();
RemoveOldFileFromStore();
}
else
{
Clear();
}
}
#region IFileData Members
public void Clear() {
//Dennis: When clearing the file name property we need to save the name of the old file to remove it from the store in the future. You can also setup a separate service for that.
if (string.IsNullOrEmpty(tempFileName))
tempFileName = RealFileName;
FileName = string.Empty;
Size = 0;
}
[FieldSize(260)]
public virtual string FileName { get; set; }
[Browsable(false)]
[NotMapped]
public Stream TempSourceStream {
get { return tempSourceStream; }
set {
//Michael: The original Stream might be closed after a while (on the web too - T160753)
if (value == null) {
tempSourceStream = null;
} else {
if (value.Length > (long)int.MaxValue) throw new UserFriendlyException("File is too long");
tempSourceStream = new MemoryStream((int)value.Length);
FileSystemDataModule.CopyStream(value, tempSourceStream);
tempSourceStream.Position = 0;
}
}
}
//Dennis: Fires when uploading a file.
void IFileData.LoadFromStream(string fileName,Stream source) {
//Dennis: When assigning a new file we need to save the name of the old file to remove it from the store in the future.
if(fileName != FileName) {// updated, old code was: if (string.IsNullOrEmpty(tempFileName))
tempFileName = RealFileName;
}
FileName = fileName;
TempSourceStream = source;
Size = (int)TempSourceStream.Length;
}
//Dennis: Fires when saving or opening a file.
void IFileData.SaveToStream(Stream destination) {
try {
if (!File.Exists(RealFileName)) {
return;
}
if (!string.IsNullOrEmpty(RealFileName)) {
if (destination == null)
FileSystemDataModule.OpenFileWithDefaultProgram(RealFileName);
else
FileSystemDataModule.CopyFileToStream(RealFileName, destination);
}
else if (TempSourceStream != null)
FileSystemDataModule.CopyStream(TempSourceStream, destination);
} catch (DirectoryNotFoundException exc) {
throw new UserFriendlyException(exc);
} catch (FileNotFoundException exc) {
throw new UserFriendlyException(exc);
}
}
public virtual int Size { get; set; }
#endregion
#region IEmptyCheckable Members
public bool IsEmpty {
//T153149
get { return FileDataHelper.IsFileDataEmpty(this) || !(TempSourceStream!= null || File.Exists(RealFileName)); }
}
#endregion
}
}