Best practice to register with multiple files

Rafael Massula 61 Reputation points
2022-01-03T02:34:31.62+00:00

158 / 5.000
Resultados de tradução
Hi people,
I need to create a product registration with several image files. So I created a product template with the following attributes:
161796-modelproduct.png

Well I'm getting the files on my route:
161778-routewithfiles.png

But I would like to know if this is the best way. I have the photo model that will be the object sent to the database:
161737-modelphoto.png

I thought of performing all the validations recommended in the files and extracting all the information and mounting the photo object and only later send it to the database. But I'm using EntityFrameworkCore to do Add() and SaveChanges(), I'm worried about product and photo coupling. Because I would like to save the product data only if the photos are fully valid. I'm currently saving the product data, after validating the files, I feel like I'm doing it wrong. I would like suggestions to perform the registration with performance and security.

Entity Framework Core
Entity Framework Core
A lightweight, extensible, open-source, and cross-platform version of the Entity Framework data access technology.
696 questions
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,187 questions
{count} votes

Accepted answer
  1. Zhi Lv - MSFT 32,016 Reputation points Microsoft Vendor
    2022-01-04T05:47:36.633+00:00

    Hi @Rafael Massula ,

    But let me explain why I am creating a new product instance, I need to dynamically enter the expiration date based on the date of manufacture. I thought about performing such in the constructor.

    From the ProductRepository.Create method, we can see that, you will create a new product instance based on the product, and will transfer the product's Files to the _photoRepository.Create method, in this scenario, how to set the foreign key in the photo table?

    Generally, when using EF core, for this kind relationship entities, we can configure relationship between them, then we can add/update/delete the related entities via the navigation property. More detail information see Relationships.

    So, I suggest you could add the navigation property in the Product model. Code like this:

    public class Product  
    {   
        [Key]  
        public int Id { get; set; }   
        [Required]  
        public string Description { get; set; }   
        [Required]  
        public decimal Price { get; set; }   
        [Required]  
        public int Size { get; set; }   
        [Required]  
        public DateTime ManufactoringDate { get; set; }   
        [Required]  
        public DateTime ExpirationDate { get; set; }  
    
        //navigation property: configure one-t0-many relationship with Photo   
        public List<Photo> Photos { get; set; }  
    
        [FromForm]  
        [NotMapped]  
        public IFormFileCollection Files { get; set; }   
    }  
    public class Photo  
    {  
        [Key]  
        public int Id { get; set; }  
        public byte[] Bytes { get; set; }  
        public string Description { get; set; }  
        public string FileExtension { get; set; }  
        public decimal Size { get; set; }  
        public int ProductId { get; set; }  
        [ForeignKey("ProductId")]  
        public Product Product { get; set; }  
    }  
    

    Then, in the action method: you can refer to the following sample to create product.

        [HttpGet]  
        public IActionResult CreateProduct()  
        {  
            return View();  
        }  
        [HttpPost]  
        public async Task<IActionResult> CreateProductAsync([FromForm]Product product)  
        {  
            if (!ModelState.IsValid)  
                return (IActionResult)Task.FromResult(product);  
            //create a new Product instance.  
            Product newproduct = new()  
            {  
                Description = product.Description,  
                Price = product.Price,  
                Size = product.Size,  
                ManufactoringDate = product.ManufactoringDate  
            };  
    
            //create a Photo list to store the upload files.  
            List<Photo> photolist = new List<Photo>();  
            if (product.Files.Count > 0)  
            {  
                foreach(var formFile in product.Files)  
                {  
                    if (formFile.Length > 0)  
                    {    
                        using (var memoryStream = new MemoryStream())  
                        {  
                            await formFile.CopyToAsync(memoryStream);  
                            // Upload the file if less than 2 MB  
                            if (memoryStream.Length < 2097152)  
                            {  
                                //based on the upload file to create Photo instance.  
                                //You can also check the database, whether the image exists in the database.  
                                var newphoto = new Photo()  
                                {  
                                    Bytes = memoryStream.ToArray(),  
                                    Description = formFile.FileName,  
                                    FileExtension = Path.GetExtension(formFile.FileName),  
                                    Size = formFile.Length,                                      
                                };  
                                //add the photo instance to the list.  
                                photolist.Add(newphoto);  
                            }  
                            else  
                            {  
                                ModelState.AddModelError("File", "The file is too large.");  
                            }  
                        }  
                    }  
                }  
            }  
            //assign the photos to the Product, using the navigation property.  
            newproduct.Photos = photolist;  
    
            _context.Products.Add(newproduct);  
            _context.SaveChanges();  
             
    
            return View();  
        }  
    

    The result is like this: we can see that, even though we just add the product the to the Product table, since we have configure relationship via the navigation property, the relates photo will also add to the Photo table.

    162114-2.gif


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,
    Dillion

    0 comments No comments

2 additional answers

Sort by: Most helpful
  1. Rafael Massula 61 Reputation points
    2022-01-03T10:46:17.1+00:00

    Here it is
    161877--repositorycreate.png

    But let me explain why I am creating a new product instance, I need to dynamically enter the expiration date based on the date of manufacture. I thought about performing such in the constructor.

    0 comments No comments

  2. Rafael Massula 61 Reputation points
    2022-01-09T02:04:13.63+00:00

    Hi @Zhi Lv - MSFT / Guys
    I need to return the product with the photos on the list, but I don't like the Json I got. The object is returned,163320-object-errorjson.png, but I have this .NET glitch. "An unhandled exception occurred during request execution.
    System.Text.Json.JsonException: A possible object loop was detected. This could be due to a cycle or if the object's depth is greater than the maximum allowable depth of 32. Consider using ReferenceHandler.Preserve in JsonSerializerOptions to support cycles. "
    I assume it's the length of the Photo object's property.
    I can't think of a good solution for this. Should I return bytes? Or FileStreamResult. a good idea would be different requests? Or in the same product request to include?

    0 comments No comments