Uploading files


#21

Works Perfectly!! Wish I knew this type of thing without having to ask. Any tips on what to research to learn more of a foundation, especially when it comes to Radzen?


#22

The solution here involves some Angular knowledge (property binding and safe URLS). (namely [href]="data.Attachment | safe") and some HTML5 - the download attribute of the <a> (which apparently doesn't work in IE).

Ideally Radzen should hide those details from the developer. We will probably integrate built-in download capabilities in the Link component so you don't have to (also handle IE support).


#23

I have a dilemma. I have the File Upload working very well, using the File Input functionality. This associates each upload with record and stores the files in a SQL table. The issue is usability for my users of this particular app. They have instances where they may have upwards of 40 documents to upload (all small files) for reference. It takes a considerable amount of time to do this manually. Is there a way to use the FileInput control to do multiple file uploads or does this need to be replaced with the Upload control? I'm not sure how to migrate from one to the other now that they have been using it.
Also I've seen mention of an auto feature for the upload control. What does this do? Is it a drag and drop upload feature?

Thanks,

Josh


#24

The FileInput maps to a DB column usually and this is why it doesn't support multiple files (they would require multiple columns to store). If you want to upload multiple files you need the Upload component. It requires to to handle file saving though. One way to do so is via custom method.


#25

I was able to get file uploads working based on the example for a custom method but I'm missing how to relate files to a record on the SQL side. So if I have the Northwind database and I want to upload files for each order, then be able to view only those files and download them from that order in the future how is that link done? I don't want to be able to see any files that are not related to a specific order (database record) etc.


#26

I would do that by creating a new table which maps orders to files. Probably by having OrderID and Filename columns. Then when a new file is uploaded and you return the filename(s) (as shown is our demo) you can make a new Invoke custom method (in the Then event of the one that uploads the files) to associate the uploaded file names with the current OrderID.


#27

Could you simply call the built in Create method for that table and specify the filename and the order ID to create it? Does it have to be a custom method? I didn't see the filename sample. Was that in the UploadFiles example application? Also all of the files get dropped into a single folder. Is there a way to create a folder for an order and drop the files in that location. The reason I ask is that if a user happens to upload a file with the same name it simply overrides the old file with the new one with no warning. My users will definitely have issues with this.

Josh


#28

Could you simply call the built in Create method for that table and specify the filename and the order ID to create it?

Yes, this would work.

I didn't see the filename sample. Was that in the UploadFiles example application?

I meant theupload file section from the invoke custom method documentation. It shows how to return the URL of the uploaded file which can be later used for other purposes.

Is there a way to create a folder for an order and drop the files in that location.

Yes, you can do that. You have full control over where the file gets stored. For example you can add a parameter and pass the current OrderID.

[HttpPost]
public IActionResult UploadFiles(IEnumerable<IFormFile> files, int orderId)
{
    // Combine the orderId with the WebRootPath so files are stored per orderId
    var dir = Path.Combine(hostingEnvironment.WebRootPath, orderId.ToString());
    // Create the directory if it doesn't exist
    Directory.CreateDirectory(dir);

    var result = files.Select(file => {
        // Save the files in the directory
        var path = Path.Combine(dir, file.FileName);

        using (var stream = new FileStream(path, FileMode.Create))
        {
            // Save the file
            file.CopyTo(stream);

            var request = HttpContext.Request;

            // Create the URL for the uploaded file - not that orderId is part of the URL
            var url =  $"{request.Scheme}://{request.Host.Value}/{orderId}/{file.FileName}";

            // Return the FileName and Url
            return new {
                FileName = file.FileName,
                Url = url
            };
        }
    }).ToList();

    // Return the file names and URL of the uploaded files
    return Json(new { value = result }, new JsonSerializerSettings() { 
        ContractResolver = new DefaultContractResolver() 
    });
}

#29

That does work for uploading the files and creating the directory structure. It is giving me an error when it is trying to create the record for the attachment in the DB. I am storing 3 columns, the related OrderId, the FileName, and the Url. I added OrderId so that all 3 items needed where returned values.

Here is the error that is showing.

It shows no payload when looking at the Header instead of the Response.


#30

The custom method returns an array of files. The createOrderAttachment method however accepts a single item. Passing an array as the argument will not work.

If you want to support multiple file uploads it would be easier to just insert the records from the UploadFiles method itself.


#31

Good to know. Is this what you mean by inserting the data in the method? Also is there already a database connection created I can call that was setup when I attached the database to the app as a datasource?

    using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Hosting; 
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Data.SqlClient;

namespace UploadFiles.Controllers
{
    
    [Route("upload")]
    public partial class UploadController : Controller
    {

        private readonly IHostingEnvironment hostingEnvironment;

    public UploadController(IHostingEnvironment hostingEnvironment)
        {
        this.hostingEnvironment = hostingEnvironment;

        }   
        [HttpPost]
public IActionResult UploadFiles(IEnumerable<IFormFile> files, int orderId)
{
    // Combine the orderId with the WebRootPath so files are stored per orderId
    var dir = Path.Combine(hostingEnvironment.WebRootPath, orderId.ToString());
    // Create the directory if it doesn't exist
    Directory.CreateDirectory(dir);

    var result = files.Select(file => {
        // Save the files in the directory
        var path = Path.Combine(dir, file.FileName);

        using (var stream = new FileStream(path, FileMode.Create))
        {
            // Save the file
            file.CopyTo(stream);

            var request = HttpContext.Request;

            // Create the URL for the uploaded file - not that orderId is part of the URL
            var url =  $"{request.Scheme}://{request.Host.Value}/{orderId}/{file.FileName}";

            // Return the FileName and Url
            return new {
                FileName = file.FileName,
                Url = url,
                OrderId = orderId
            };
        }
    }).ToList();


            SqlConnection conn = new SqlConnection("Data source=localhost; Database=Northwind;User Id=sa;Password=!QAZxsw2");
            conn.Open();
            SqlCommand cmd = new SqlCommand("insert into dbo.OrderAttachments values(OrderId,FileName,Url);", conn);
            cmd.ExecuteNonQuery();
            conn.Close();


            // Return the file names and URL of the uploaded files
            return Json(new { value = result }, new JsonSerializerSettings() { 
        ContractResolver = new DefaultContractResolver() 
    });
        }

#32

Also is there already a database connection created I can call that was setup when I attached the database to the app as a datasource?

Radzen doesn't keep any open connections to the database. However you can inject the EF context that has been created for your data source so you don't need to write SQL and embed the connection string in your code. Here is how this is done (step 4):

[Route("api/[controller]/[action]")]
public class ServerMethodsController : Controller
{
    private NorthwindContext northwind;

    public ServerMethodsController(NorthwindContext context)
    {
        this.northwind = context;
    }
}

#33

This is obviously beyond me to a point. I feel like I have something out of order. I'm not sure how to get the result values assigned to the fields to insert.

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Hosting; 
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Data.SqlClient;
using FunTimes.Models.Northwind;
using static Microsoft.AspNetCore.Hosting.Internal.HostingApplication;

namespace UploadFiles.Controllers
{

    [Route("upload")]
    public partial class UploadController : Controller
    {

        private readonly IHostingEnvironment hostingEnvironment;

        public UploadController(IHostingEnvironment hostingEnvironment)
        {
            this.hostingEnvironment = hostingEnvironment;

        }


        [HttpPost]
        public IActionResult UploadFiles(IEnumerable<IFormFile> files, int orderId)
        {
            // Combine the orderId with the WebRootPath so files are stored per orderId
            var dir = Path.Combine(hostingEnvironment.WebRootPath, orderId.ToString());
            // Create the directory if it doesn't exist
            Directory.CreateDirectory(dir);

            var result = files.Select(file =>
            {
                // Save the files in the directory
                var path = Path.Combine(dir, file.FileName);

                using (var stream = new FileStream(path, FileMode.Create))
                {
                    // Save the file
                    file.CopyTo(stream);

                    var request = HttpContext.Request;

                    // Create the URL for the uploaded file - not that orderId is part of the URL
                    var url = $"{request.Scheme}://{request.Host.Value}/{orderId}/{file.FileName}";

                    // Return the FileName and Url
                    return new
                    {
                        FileName = file.FileName,
                        Url = url,
                        OrderId = orderId
                    };


                }
            }).ToList();

            // Return the file names and URL of the uploaded files
            return Json(new { value = result }, new JsonSerializerSettings()
            {
                ContractResolver = new DefaultContractResolver()
            });

        }

        // Connect to Northwind to store Order Attachment Record details
        private FunTimes.Data.NorthwindContext northwind;

        public UploadController(FunTimes.Data.NorthwindContext context)
        {
            this.northwind = context;

            var t = new OrderAttachment
            {

                FileName = ,
                Url = ,
                OrderId = 
            };

            context.OrderAttachments.Add(t);
            context.SaveChanges();
        }

    }
}

#34

You have to inject the context as a second constructor parameter.

        private readonly IHostingEnvironment hostingEnvironment;
        private NorthwindContext context;

        public UploadController(IHostingEnvironment hostingEnvironment, NorthwindContext context)
        {
            this.hostingEnvironment = hostingEnvironment;
            this.context = context;
        }

Then you can use this.context inside the UploadFiles method.


#35

I think it's about there. I know it is trying to save to the DB now as I get an error about my foreign key constraint. When I debug in VS Code I can now see that my ordId parameter is not being passed in now and I don't know why. It was working yesterday as I could see the value. I checked the parameter setting for the invoke custom method in Radzen and it is still there and is using the ${parameters.OrderID} that is being used to pull the record on the edit screen.

The Order ID should be 10248 not 0

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Hosting; 
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Data.SqlClient;
using FunTimes.Models.Northwind;
using static Microsoft.AspNetCore.Hosting.Internal.HostingApplication;

namespace UploadFiles.Controllers
{

    [Route("upload")]
    public partial class UploadController : Controller
    {

        private readonly IHostingEnvironment hostingEnvironment;
        private FunTimes.Data.NorthwindContext context;

        public UploadController(IHostingEnvironment hostingEnvironment, FunTimes.Data.NorthwindContext context)
        {
            this.hostingEnvironment = hostingEnvironment;
            this.context = context;
        }


        [HttpPost]
        public IActionResult UploadFiles(IEnumerable<IFormFile> files, int ordId)
        {
            // Combine the orderId with the WebRootPath so files are stored per orderId
            var dir = Path.Combine(hostingEnvironment.WebRootPath, ordId.ToString());
            // Create the directory if it doesn't exist
            Directory.CreateDirectory(dir);

            var result = files.Select(file =>
            {
                // Save the files in the directory
                var path = Path.Combine(dir, file.FileName);

                using (var stream = new FileStream(path, FileMode.Create))
                {
                    // Save the file
                    file.CopyTo(stream);

                    var request = HttpContext.Request;

                    // Create the URL for the uploaded file - not that orderId is part of the URL
                    var url = $"{request.Scheme}://{request.Host.Value}/{ordId}/{file.FileName}";

                    //Save the Record as an Order Attachment.
                    var t = new OrderAttachment
                    {

                        FileName = file.FileName,
                        Url = url,
                        OrderId = ordId
                    };

                    this.context.OrderAttachments.Add(t);
                    this.context.SaveChanges();

                // Return the FileName and Url
                return new
                {
                    fileName = file.FileName,
                    Url = url,
                    OrderId = ordId
                };      

            }
            
        }).ToList();

        // Return the file names and URL of the uploaded files
        return Json(new { value = result }, new JsonSerializerSettings()
        {
            ContractResolver = new DefaultContractResolver()
        });

    }

}

}


#36

Not sure why that parameter isn't set. As a Radzen Professional user you can send us your project to info@radzen.com so we can troubleshoot.


#37

I sent an email.

Thanks,

Josh


#38

Hi Josh,

Since you are executing the upload method as custom server method you should remove the URL setting for the upload:

Best Regards,
Vladimir


#39

That did it. Thanks! :grinning: