Attach Files to Objects

  • 7 min to read

In this lesson, you will learn how to attach file collections to business objects. For this purpose, the File Attachment module will be added to the application, and the new Resume and PortfolioFileData business classes will be implemented. The Resume class will be used to store and manage a Contact's resume information: a file data collection and a reference to a Contact. The PortfolioFileData class will represent the file data collection item. You will also learn how the file data type properties are displayed and managed in a UI.

Note

Before proceeding, take a moment to review the following lessons.

  • Implement Custom Business Classes and Reference Properties (XPO/EF)
  • Inherit from the Business Class Library Class (XPO/EF)
  • Set a One-to-Many Relationship (XPO/EF)
  • Add the File Attachments module to your WinForms module project. For this purpose, find the WinModule.cs (WinModule.vb) file in the MySolution.Module.Win project displayed in the Solution Explorer. Double-click this file to invoke the Module Designer. In the Toolbox, expand the DX.19.2: XAF Modules tab. Drag the FileAttachmentsWindowsFormsModule item to the Designer's Required Modules section.

    Tutorial_EM_Lesson1_0

  • Add the File Attachments module to your ASP.NET module project. For this purpose, find the WebModule.cs (WebModule.vb) file in the MySolution.Module.Web project displayed in the Solution Explorer. Double-click this file to invoke the Module Designer. In the Toolbox, expand the DX.19.2: XAF Modules tab. Drag the FileAttachmentsAspNetModule item to the Designer's Required Modules section.

    Tutorial_EM_Lesson1_0_1

  • After you have made changes in the Application Designer, rebuild your solution.
  • Add a new Resume business class, as described in the Inherit from the Business Class Library Class (XPO/EF) lesson.
  • Replace the automatically generated Resume class declaration with the following code.

    eXpress Persistent Objects

    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl;
    using DevExpress.Xpo;
    using MySolution.Module.BusinessObjects;
    
    [DefaultClassOptions]
    [ImageName("BO_Resume")]
    public class Resume : BaseObject {
       public Resume(Session session) : base(session) {}
       private Contact contact;
       public Contact Contact {
          get { 
             return contact;
          }
          set {
             SetPropertyValue(nameof(Contact), ref contact, value);
          }
       }
      [Aggregated, Association("Resume-PortfolioFileData")]
      public XPCollection<PortfolioFileData> Portfolio {
         get { return GetCollection<PortfolioFileData>(nameof(Portfolio)); }
      }
    }
    

    Entity Framework

    using System;
    using System.Linq;
    using System.ComponentModel;
    using System.Collections.Generic;
    using DevExpress.Persistent.Base;
    
    namespace MySolution.Module.BusinessObjects {
        [DefaultClassOptions]
        [ImageName("BO_Resume")]
        public class Resume {
            public Resume() {
                Portfolio = new List<PortfolioFileData>();
            }
            [Browsable(false)]
            public Int32 ID { get; protected set; }
            public virtual IList<PortfolioFileData> Portfolio { get; set; }
            public virtual Contact Contact { get; set; }
        }
    }
    
    public class MySolutionDbContext : DbContext {
        //...
        public DbSet<Resume> Resumes { get; set; }
    }
    
    public class Contact : Person {
        public Contact() {
            //...
            Resumes = new List<Resume>();
        }
        //...
        public virtual IList<Resume> Resumes { get; set; }
    }
    

    Declare the PortfolioFileData class, which is the FileAttachmentBase descendant for XPO and FileAttachment - for EF, and DocumentType enumeration (for XPO) as follows.

    eXpress Persistent Objects

    public class PortfolioFileData : FileAttachmentBase {
       public PortfolioFileData(Session session) : base(session) {}
       protected Resume resume;
       [Persistent, Association("Resume-PortfolioFileData")]
       public Resume Resume {
          get { return resume; }
          set { 
             SetPropertyValue(nameof(Resume), ref resume, value); 
          }
       }
       public override void AfterConstruction() {
          base.AfterConstruction();
          documentType = DocumentType.Unknown;
       }
       private DocumentType documentType;
       public DocumentType DocumentType {
          get { return documentType; }
          set { SetPropertyValue(nameof(DocumentType), ref documentType, value); }
       }
    }
    public enum DocumentType { SourceCode = 1, Tests = 2, Documentation = 3,
       Diagrams = 4, ScreenShots = 5, Unknown = 6 };
    

    Entity Framework

    using System;
    using System.Linq;
    using System.ComponentModel;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations.Schema;
    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF;
    
    namespace MySolution.Module.BusinessObjects {
        [ImageName("BO_FileAttachment")]
        public class PortfolioFileData : FileAttachment {
            public PortfolioFileData()
                : base() {
                DocumentType = DocumentType.Unknown;
            }
    
            [Browsable(false)]
            public Int32 DocumentType_Int { get; protected set; }
            [System.ComponentModel.DataAnnotations.Required]
            public Resume Resume { get; set; }
    
            [NotMapped]
            public DocumentType DocumentType {
                get { return (DocumentType)DocumentType_Int; }
                set { DocumentType_Int = (Int32)value; }
            }
        }
    }
    
    public class MySolutionDbContext : DbContext {
        //...
        public DbSet<PortfolioFileData> PortfolioFileData { get; set; }
        public DbSet<FileAttachment> FileAttachments { get; set; }
    }
    

    In the code above, you can see that the Resume and PortfolioFileData classes are related with a One-to-Many relationship. Another important point is that in XPO, the PortfolioFileData.DocumentType property is initialized in the AfterConstruction method, which is called after creating the corresponding object.

    The EF version of code includes a workaround of one noticeable Entity Framework peculiarity. By default, the deletion of a master-object does not cause the deletion of referenced objects. The only thing that happens is the nullification of the links from referenced objects to the master object. Nullification will be successful only if the referenced objects are loaded at the moment of deletion, but if an object is deleted from the List View, this is not always so. To avoid integrity violation, the associations between classes should be configured to allow the referenced objects to be deleted with the master. In the code above, this is done by the [System.ComponentModel.DataAnnotations.Required] attribute of the Resume property in the PortfolioFileData class declaration.

    Note

    Another way to avoid the integrity violation is the use of Fluent API to call the WillCascadeOnDelete(true) method for the Portfolio-Resume relationship. The Fluent API requests should be located in the DbContext.

    Refer to the File Attachment Properties in XPO and File Attachment Properties in Entity Framework topics to learn more about file attachment properties creation.

  • Run the WinForms or ASP.NET application and create a new Resume object.
  • To specify the File property, attach a file in the Open dialog, invoked via the Add From File... (btn_attach) button.

    Tutorial_EM_Lesson1_1

  • To open or save a file attached to the Portfolio collection, or add a new file, use the Open..., Save As... or Add From File... Actions supplied with the collection.

    Tutorial_EM_Lesson1_3

Tip

To save the file stored within the current FileData object to the specified stream, use the IFileData.SaveToStream method.

You can see the code demonstrated here in the Resume.cs (Resume.vb) file of the Main Demo installed with XAF. The MainDemo application is installed in %PUBLIC%\Documents\DevExpress Demos 19.2\Components\eXpressApp Framework\MainDemo by default. The ASP.NET version is available online at http://demos.devexpress.com/XAF/MainDemo/.

 

Next Lesson: Provide Several View Variants for End-Users

See Also