Skip to main content
A newer version of this page is available. .
All docs
V21.2

Attach Files to Objects (.NET 5)

  • 5 minutes to read

This lesson describes how to attach file collections to business objects. In this tutorial, we will add the File Attachment Module to an ASP.NET Core Blazor application, and implement new business classes:

  • Resume - to store a Contact’s resume information.
  • PortfolioFileData - to save file data collection items.

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)

Step-by-Step Instructions

  1. Add the DevExpress.ExpressApp.FileAttachment.Blazor NuGet package to the MySolution.Blazor.Server project. See the following topic for more information on how to install DevExpress NuGet packages: Install DevExpress Controls Using NuGet Packages.

  2. In the ASP.NET Core Blazor application constructor, add the File Attachment Module to the Modules collection:

    File: MySolution.Blazor.Server\BlazorApplication.cs

    using DevExpress.ExpressApp.FileAttachments.Blazor;
    
    namespace MySolution.Blazor.Server {
        public partial class MySolutionBlazorApplication : BlazorApplication {
            public MySolutionBlazorApplication() {
                InitializeComponent();
                Modules.Add(new FileAttachmentsBlazorModule());
            }
            // ...
        }
    }
    
  3. Right-click the Business Objects folder in the MySolution.Module project, choose Add Item | Class…, specify Resume.cs as the new file name, and click Add.

  4. Replace the generated class declaration with the following code (the code is different for XPO and EF Core-based applications):

    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl;
    using DevExpress.Xpo;
    using MySolution.Module.BusinessObjects;
    
    namespace MySolution.Module.BusinessObjects {
        [DefaultClassOptions]
        [ImageName("BO_Resume")]
        public class Resume : BaseObject {
            public Resume(Session session) : base(session) {}
            private Contact contact;
            [Association("Contact-Resumes")]
            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)); }
            }
        }
    }   
    
    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; }
        }
    }
    
  5. Add the Resume property to the Contact class:

    File: MySolution.Module.BusinessObjects\Contact.cs

    using System.Collections.Generic;
    
    namespace MySolution.Module.BusinessObjects {
        [DefaultClassOptions]
        public class Contact : Person {
            // ...
            [Association("Contact-Resumes")]
            public XPCollection<Resume> Resumes {
                get { return GetCollection<Resume>(nameof(Resumes)); }
            }
        }
    }
    
    namespace MySolution.Module.BusinessObjects {
        [DefaultClassOptions]
        public class Contact : Person {
            public Contact() {
                // ...
                Resumes = new List<Resume>();
            }
            // ...
        public virtual IList<Resume> Resumes { get; set; }
    }
    
  6. Add another class to the MySolution.Module project, as described in step 3, and name it PortfolioFileData. Replace the generated class declaration with the following code:

    File: MySolution.Module.BusinessObjects\PortfolioFileData.cs

    using DevExpress.Persistent.BaseImpl;
    using DevExpress.Xpo;
    
    namespace MySolution.Module.BusinessObjects {
        public class PortfolioFileData : FileAttachmentBase {
            public PortfolioFileData(Session session) : base(session) { }
            private Resume resume;
            [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
        };
    }    
    
    using System;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    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; }
            [Required]
            public virtual Resume Resume { get; set; }
    
            [NotMapped]
            public DocumentType DocumentType {
                get { return (DocumentType)DocumentType_Int; }
                set { DocumentType_Int = (Int32)value; }
            }
        }
        public enum DocumentType {
            SourceCode = 1, Tests = 2, Documentation = 3,
            Diagrams = 4, ScreenShots = 5, Unknown = 6
        };
    }
    
  7. In EF Core-based applications, register the Resume, PortfolioFileData, and FileAttachments entities in the DbContext:

    File: MySolution.Module.BusinessObjects\MySolutionDbContext.cs

    public class MySolutionDbContext : DbContext {
        //...
        public DbSet<Resume> Resumes { get; set; }
        public DbSet<PortfolioFileData> PortfolioFileData { get; set; }
        public DbSet<FileAttachment> FileAttachments { get; set; }
    }
    
  8. Run the application. Open the Resume List View and create a new Resume object. Fill the Contact field and add a new Portfolio File Data object. In the Portfolio File Data window, specify the Document Type and select the file that you wish to attach.

    XAF Add a Resume object

    Users can click the file link to download the resume file.

Detailed Explanation

Add the Module Automatically

If you add the File Attachment Module when you create an XAF application, the Solution Wizard generates the code used to add the File Attachment Module automatically.

Add Business Objects and their Relations

In this lesson, we derive PortfolioFileData from the FileAttachmentBase class in the XPO-based application, and from the FileAttachment class in the EF Core-based application. FileAttachmentBase and FileAttachment are Business Class Library classes. These classes already contain fields specific to entities of the FileAttachment type. For more information about file attachment properties, see the following topics: File Attachment Properties in XPO/EF Core. To save a file stored within a FileData object to a stream, use the IFileData.SaveToStream method.

The Resume and PortfolioFileData classes are related with a One-to-Many relationship. For more information on how to create a one-to-many relationship between business objects, see the following topics: Set a One-to-Many Relationship (XPO/EF).

In the XPO-based application, we initialize the PortfolioFileData.DocumentType property in the AfterConstruction() method, which is called after the corresponding object’s creation. See the following topic for details: How to: Initialize Business Objects with Default Property Values in XPO.

In the EF Core-based application, a master object’s deletion does not delete referenced objects. To avoid integrity violation, the associations between classes should be configured to delete the referenced objects with the master object. To do this, we add the Required attribute to the Resume property in the PortfolioFileData class. Alternatively, you can use the Fluent API and specify the OnDelete method for the Portfolio-Resume relationship as described in the following topic: The Fluent API OnDelete Method.

Next Lesson

Create Several View Variants