ASP.NET Core 3.1 MVC File Upload with MS SQL SERVER FileTable

This article shows how to upload and download files in ASP.NET Core 3.0 MVC and save the files to a MS SQL Server using FileTable. The data access for the application is implemented in a separate project and EF Core migrations is used to setup the select logic for the database.

Code: https://github.com/damienbod/AspNetCoreFileUploadFileTable

2019-12-21: Updated to .NET Core 3.1
2019-10-05: Updated to .NET Core 3.0
2018-12-05: Updated to .NET Core 2.2
2018-11-22: Updated bootstrap 4, nuget packages, ui build
2018-06-07: Updated to ASP.NET Core 2.1
2017-08-21: Updated to ASP.NET Core 2.0
2017-01-07: Updated to VS2017 csproj
2016-02-07: Updated to ASP.NET Core RTM
2016-05-18: Updated to ASP.NET Core RC2
2015-12-08: config files cleaned up and returning proper 415 response for unsupported media type

Step 1: Settings up the database FileTable

A new database is created in MS SQL Server which has Filestreams enabled. This feature only works with windows authentication. Firstly if not already configured, the Filestream access level is set to 2.

EXEC sp_configure filestream_access_level, 2
RECONFIGURE
GO

Once this has been set, create a new directory to save the files. In this example, C:\damienbod\WebApiFileTable is used. Now execute the following command:


CREATE DATABASE AspNetCoreFileUploadFileTable
ON PRIMARY
(Name = AspNetCoreFileUploadFileTable,
FILENAME = 'C:\databases\AspNetCoreFileUploadFileTable\FTDB.mdf'),
FILEGROUP FTFG CONTAINS FILESTREAM
(NAME = AspNetCoreFileUploadFileTableFS,
FILENAME='C:\databases\AspNetCoreFileUploadFileTable\FS')
LOG ON
(Name = AspNetCoreFileUploadFileTableLog,
FILENAME = 'C:\databases\AspNetCoreFileUploadFileTable\FTDBLog.ldf')
WITH FILESTREAM (NON_TRANSACTED_ACCESS = FULL,
DIRECTORY_NAME = N'AspNetCoreFileUploadFileTable');
GO

Now you can check if your database settings are ok.

SELECT DB_NAME(database_id),
non_transacted_access,
non_transacted_access_desc
FROM sys.database_filestream_options;
GO

The database should be configured as follows:
FileTableWebApi01

Now create a table for the file uploads:

USE WebApiFileTable
GO
CREATE TABLE WebApiUploads AS FileTable
WITH
(FileTable_Directory = 'WebApiUploads_Dir');
GO

The files can be saved, deleted or updated using the following path:

\\{yourPCname}\{mssqlserver}\WebApiFileTable\WebApiUploads_Dir

The files can also be accessed using plain SQL.

INSERT INTO [dbo].[WebApiUploads]
([name],[file_stream])
SELECT
'NewFile.txt', * FROM OPENROWSET(BULK N'd:\NUnit-2.6.1.msi', SINGLE_BLOB) AS FileData
GO>

Step 2: Adding the Entity Framework Core data access layer

A file description table is created for searching and returning multiple records. This is used to setup a download link and provide a small description of the file. To create the table, Entity Framework code first is used in this example.

Add Entity framework Core to the csproj file in your project. The EF Core dependencies need to be added and also the ef commands.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
  </ItemGroup>
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
  </ItemGroup>
  <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
    <DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
  </PropertyGroup>
</Project>

An entity context class has to be created to use the database. This is used for the migrations and also the data access.

using DataAccess.Model;
using Microsoft.EntityFrameworkCore;

namespace DataAccess
{
    public class FileContext : DbContext
    {
        public FileContext(DbContextOptions<FileContext> options) :base(options)
        { }
        
        public DbSet<FileDescription> FileDescriptions { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<FileDescription>().HasKey(m => m.Id);
            base.OnModelCreating(builder);
        }
    }
}

The class used as the entity also needs to be created. The primary key for this class is also defined in the context class.

using System;
using System.ComponentModel.DataAnnotations;

namespace DataAccess.Model
{
    public class FileDescription
    {
        public int Id { get; set; }
        public string FileName { get; set; }
        public string Description { get; set; }
        public DateTime CreatedTimestamp { get; set; }
        public DateTime UpdatedTimestamp { get; set; }
        public string ContentType { get; set; }
    }
}

The connection string needs to be added to the config file which is used in the context. This is required for migrations and also running the application.

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=...."
  },
  "ApplicationConfiguration": {
    "ServerUploadFolder": "\\\\..."
  }
}

The migrations can be created and the database can be updated. Open the application using the command line in the src folder where the project is defined. The migrations are executed from the project containing the Startup file where the DbContext is used. The migrations needs to be set to this project then in the AddDbContext method.

>
> dotnet restore
>
> dotnet ef migrations add testMigration
>
> dotnet ef database update
>
>

If you don’t want to use EF7 migrations, you could just create the SQL table using plain TSQL.

USE [WebApiFileTable]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[FileDescriptions](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[FileName] [nvarchar](max) NULL,
	[Description] [nvarchar](max) NULL,
	[CreatedTimestamp] [datetime] NOT NULL,
	[UpdatedTimestamp] [datetime] NOT NULL,
	[ContentType] [nvarchar](max) NULL,
 CONSTRAINT [PK_dbo.FileDescription] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

Step 3: ASP.NET Core MVC Single or Multiple file upload and download

The ASP.NET Core MVC application is a simple project with razor views and a FileUpload ASP.NET Core MVC controller to upload and download the files. The data access project is added as a reference in the csproj file in the dependencies. It does not matter if the dependencies uses sources from NuGet or from local projects.

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
  </PropertyGroup>
  
  <ItemGroup>
    <ProjectReference Include="..\DataAccess\DataAccess.csproj" />
  </ItemGroup>
  
  <ItemGroup>
    <PackageReference Include="BuildBundlerMinifier" Version="3.2.435" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
  </ItemGroup>
  
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
  </ItemGroup>
  
  <ItemGroup>
    <Folder Include="Migrations\" />
  </ItemGroup>
  
  <Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('node_modules') ">
    <!-- Ensure Node.js is installed -->
    <Exec Command="node --version" ContinueOnError="true">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
    </Exec>
    <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
    <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
    <Exec WorkingDirectory="" Command="npm install" />
  </Target>
</Project>

ASP.NET 5 provides the IFormFile class for file upload. This class is used inside the FileDescriptionShort, which is used for single or multiple file uploads.

public class FileDescriptionShort
{
	public int Id { get; set; }

	public string Description { get; set; }

	public string Name { get; set; }

	public ICollection<IFormFile> File { get; set; }
}

The FileUploadController has two action methods. The controller uses the default DI with constructor injection to add the dependencies. The UploadFiles action method uses the FileDescriptionShort class as a parameter. The method takes all the files and saves each file directly to the MS SQL Server FileTable. Then the file descriptions are saved to the database. The descriptions are used to list and download to files.

The file upload logic was built using the following two blogs:

http://www.mikesdotnetting.com/article/288/asp-net-5-uploading-files-with-asp-net-mvc-6

http://dotnetthoughts.net/file-upload-in-asp-net-5-and-mvc-6/

Thanks for these articles.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using DataAccess;
using DataAccess.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
    
using FileResult = DataAccess.Model.FileResult;

namespace AspNet5FileUploadFileTable.Controllers
{
    [Route("api/test")]
    public class FileUploadController : Controller
    {
        private readonly IFileRepository _fileRepository;
        private readonly IOptions<ApplicationConfiguration> _optionsApplicationConfiguration;

        public FileUploadController(IFileRepository fileRepository, IOptions<ApplicationConfiguration> o)
        {
            _fileRepository = fileRepository;
            _optionsApplicationConfiguration = o;
        }

        [Route("files")]
        [HttpPost]
        [ServiceFilter(typeof(ValidateMimeMultipartContentFilter))]
        public async Task<IActionResult> UploadFiles(FileDescriptionShort fileDescriptionShort)
        {
            var names = new List<string>();
            var contentTypes = new List<string>();
            if (ModelState.IsValid)
            {
                // http://www.mikesdotnetting.com/article/288/asp-net-5-uploading-files-with-asp-net-mvc-6
                // http://dotnetthoughts.net/file-upload-in-asp-net-5-and-mvc-6/
                foreach (var file in fileDescriptionShort.File)
                {
                    if (file.Length > 0)
                    {
                        var fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
                        contentTypes.Add(file.ContentType);
                        names.Add(fileName);

                        // Extension method update RC2 has removed this 
                        await file.SaveAsAsync(Path.Combine(_optionsApplicationConfiguration.Value.ServerUploadFolder, fileName));
                    }
                }
            }

            var files = new FileResult
                            {
                                FileNames = names,
                                ContentTypes = contentTypes,
                                Description = fileDescriptionShort.Description,
                                CreatedTimestamp = DateTime.UtcNow,
                                UpdatedTimestamp = DateTime.UtcNow,
                            };

            _fileRepository.AddFileDescriptions(files);

            return RedirectToAction("ViewAllFiles", "FileClient");
        }

        [Route("download/{id}")]
        [HttpGet]
        public FileStreamResult Download(int id)
        {
            var fileDescription = _fileRepository.GetFileDescription(id);

            var path = _optionsApplicationConfiguration.Value.ServerUploadFolder + "\\" + fileDescription.FileName;
            var stream = new FileStream(path, FileMode.Open);
            return  File(stream, fileDescription.ContentType);
        }
    }
}


The IFormFile SaveAsAsync extension needs to be added.

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace AspNetCoreFileUploadFileTable
{
    public static class FormFileExtensions
    {
        private static int DefaultBufferSize = 80 * 1024;
        /// <summary>
        /// Asynchronously saves the contents of an uploaded file.
        /// </summary>
        /// <param name="formFile">The <see cref="IFormFile"/>.</param>
        /// <param name="filename">The name of the file to create.</param>
        public async static Task SaveAsAsync(
            this IFormFile formFile,
            string filename,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (formFile == null)
            {
                throw new ArgumentNullException(nameof(formFile));
            }

            using (var fileStream = new FileStream(filename, FileMode.Create))
            {
                var inputStream = formFile.OpenReadStream();
                await inputStream.CopyToAsync(fileStream, DefaultBufferSize, cancellationToken);
            }
        }
    }
}

The upload method also uses a service filter to validate the mime type. The ValidateMimeMultipartContentFilter class implements the ActionFilterAttribute which provides virtual methods which can be overridden. This attribute throws an exception, if the mime type is incorrect. A file upload requires a multipart content type.

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;

namespace AspNetCoreFileUploadFileTable
{

    public class ValidateMimeMultipartContentFilter : ActionFilterAttribute
    {
        private readonly ILogger _logger;

        public ValidateMimeMultipartContentFilter(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger("ctor ValidateMimeMultipartContentFilter");
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (!IsMultipartContentType(context.HttpContext.Request.ContentType))
            {
                context.Result = new StatusCodeResult(415);
                return;
            }

            base.OnActionExecuting(context);
        }

        private static bool IsMultipartContentType(string contentType)
        {
            return !string.IsNullOrEmpty(contentType) && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
        }
    }
}

All the required application configurations are implemented in the Startup class. Entity Framework, configuration, attribute, and class dependencies are defined here.

using DataAccess;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace AspNetCoreFileUploadFileTable
{
    public class Startup
    {
        private readonly IWebHostEnvironment _environment;

        public Startup(IWebHostEnvironment env, IConfiguration configuration)
        {
            Configuration = configuration;
            _environment = env;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<ApplicationConfiguration>(Configuration.GetSection("ApplicationConfiguration"));

            var sqlConnectionString = Configuration.GetConnectionString("DefaultConnection");

            services.AddDbContext<FileContext>(options =>
                options.UseSqlServer(
                    sqlConnectionString,
                    b => b.MigrationsAssembly("AspNetCoreFileUploadFileTable")
                )
            );

            services.AddControllersWithViews()
                .SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

            services.AddScoped<IFileRepository, FileRepository>();
            services.AddScoped<ValidateMimeMultipartContentFilter>();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=FileClient}/{action=ViewAllFiles}/{id?}");
            });
        }
    }
}

The razor view implements a HTML form which is used to upload the files. The form attributes enctype and method are important for file upload, these should be defined as follows: enctype=”multipart/form-data” method=”post”

<!doctype html>
<html>
    <head>
        <title>Test</title>
    </head>
    <body>
        <form enctype="multipart/form-data" method="post" action="http://localhost:20828/api/test/files" id="ajaxUploadForm" novalidate="novalidate">

            <fieldset>
                <legend style="padding-top: 10px; padding-bottom: 10px;">Uploaded Form</legend>

                <div class="col-xs-12" style="padding: 10px;">
                    <div class="col-xs-4">
                        <label>Description</label>
                    </div>
                    <div class="col-xs-7">
                        <textarea rows="2" placeholder="Description" class="form-control" name="description" id="description"></textarea>
                    </div>
                </div>

                <div class="col-xs-12" style="padding: 10px;">
                    <div class="col-xs-4">
                        <label>Upload</label>
                    </div>
                    <div class="col-xs-7">
                        <input type="file" id="fileInput" name="file" multiple>
                    </div>
                </div>

                <div class="col-xs-12" style="padding: 10px;">
                    <div class="col-xs-4">
                        <input type="submit" value="Upload" id="ajaxUploadButton" class="btn">
                    </div>
                    <div class="col-xs-7">

                    </div>
                </div>

            </fieldset>

        </form>
    </body>
</html>

Testing the application

The application displays all the existing files which where uploaded when started.
aspnet5upload_01

If clicked, the file can be downloaded:

http://localhost:20828/api/test/download/id

The files can be uploaded as follows:
aspnet5upload_02

Conclusion

The application is relatively easy to implement and is simple to understand. This is a big improvement compared to the same application implemented in .NET 4.5 with Web API. One problem which exists is the configuration when using separate projects. The paths depends on where the project is run. The config file can be saved to a common path to all the projects and the application will work. (Might be a problem with deployment.)

Links:

https://docs.microsoft.com/en-us/sql/relational-databases/blob/filestream-sql-server?view=sql-server-2017

https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.1

http://dotnetthoughts.net/file-upload-in-asp-net-5-and-mvc-6/

http://www.mikesdotnetting.com/article/288/asp-net-5-uploading-files-with-asp-net-mvc-6

Web API File Upload with MS SQL SERVER FileTable

http://senvichet.com/how-to-upload-file-from-web-form-in-asp-net-5-mvc-6/

38 comments

  1. […] ASP.NET 5 MVC 6 File Upload with MS SQL SERVER FileTable // Software Engineering […]

  2. […] ASP.NET 5 MVC 6 file upload with Sql Server FileTable by Damien Bod […]

  3. […] ASP.NET Core 1.0 MVC 6 file upload with Sql Server FileTable : Damien Bod 는 ASP.NET Core 1.0 MVC 6 기반의 환경에서 SQL 서버의 파일 테이블에 직접 파일을 올리는 방법을 설명합니다. […]

  4. xavier Watson · · Reply

    upon trying to run your solution I’m getting the following error: System.Data.SqlClient.SqlException was unhandled by user code
    Class=16
    HResult=-2146232060
    LineNumber=1
    Message=Invalid object name ‘FileDescriptions’.
    Number=208
    Procedure=””
    Server=DESKTOP-NT3CUN7\XWDESIGNS
    Source=Core .Net SqlClient Data Provider
    State=1
    StackTrace:
    at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
    at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
    at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
    at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
    at System.Data.SqlClient.SqlDataReader.get_MetaData()
    at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
    at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
    at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)
    at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.Execute(IRelationalConnection connection, String executeMethod, IReadOnlyDictionary`2 parameterValues, Boolean openConnection, Boolean closeConnection)
    at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteReader(IRelationalConnection connection, IReadOnlyDictionary`2 parameterValues, Boolean manageConnection)
    at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable.Enumerator.MoveNext()
    at Microsoft.EntityFrameworkCore.Query.QueryMethodProvider.d__3`1.MoveNext()
    at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
    at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
    at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
    at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
    at AspNet5FileUploadFileTable.Controllers.FileClientController.ViewAllFiles() in C:\Users\Xavier\Documents\Visual Studio 2015\Projects\AspNet5FileUploadFileTable-master\src\AspNet5FileUploadFileTable\Controllers\FileClientController.cs:line 47
    at lambda_method(Closure , Object , Object[] )
    at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__28.MoveNext()
    InnerException:

  5. Does the FileDescriptions table exist in your Database?

    Greetings Damien

    1. xavier Watson · · Reply

      No it wasn’t. That was the issue after making the migration and committing it to the database everything worked

  6. Martha Dibenedetto · · Reply

    Good comments ! I was fascinated by the info – Does someone know if I would be able to find a template Form A version to fill in ?

  7. Hi Damien,

    Great article! I would love another demo by you implementing this with the jQuery File Upload plugin. https://blueimp.github.io/jQuery-File-Upload/angularjs.html

    1. nice, thanks

  8. Thanks for the example!

    Anyway, you don’t shot the FileResult class in the post, so I was turning crazy looking for, until I donwload the entire project.

  9. I’m using VS2015.
    I get confused at Step 2.
    I installed EF 1.1.0
    I installed sdk “version”: “1.0.0-preview2-003131” and referenced from global.json
    In order to include ‘dependencies’ and add ‘the ef commands’, I:
    Install-Package Microsoft.EntityFrameworkCore.SqlServer
    Install-Package Microsoft.EntityFrameworkCore.Tools -Version 1.1.0-msbuild3-final
    Install-Package Microsoft.EntityFrameworkCore.SqlServer.Design
    per https://docs.microsoft.com/en-us/ef/core/get-started/aspnetcore/existing-db
    Q: Am I on the wrong track?

  10. Hi Daniel, in VS2015 you should use the project.json and not the msbuild tooling. “Microsoft.EntityFrameworkCore.Tools”: “1.1.0-preview4-final”,
    and
    “tools”: {
    “Microsoft.AspNetCore.Razor.Tools”: “1.1.0-preview4-final”,
    “Microsoft.EntityFrameworkCore.Tools.DotNet”: “1.1.0-preview4”,

    It is very confusing at the moment because everything is being switched back to msbuild and VS2017 is only in RC and still not bug free or missing features from the project.json.

    Greetings Damien

  11. Thanks Damien, for the quick reply!

    I found those tools already listed in my project.json file, among others.

    In global.json I changed my sdk thus:

    “projects”: [ “src”, “test” ],
    “sdk”: {
    //”version”: “1.0.0-preview2-1-003177”
    “version”: “1.0.0-preview2-003131”

    I added these commands to project.json, per a book I just got:

    “commands”: {
    “web”: “Microsoft.AspNet.Server.Kestrel”,
    “ef”: “EntityFramework.Commands”
    }

    That got the thing to build, at least.

    I created the database tables directly because I couldn’t get migration to work.

    I edited the connection string in config.json

    But when I debug, the project doesn’t start.

    Any suggestions?

    Thanks again!

    1. seems like something is not installed on your PC, check that the VS version is correct and that the command line is working.

      $ dotnet restore
      $ dotnet run
      Does this work?
      Then VS 15.0.0-RC.3+26127.3

  12. Hi Damienbod,

    Thanks again for your help with this.

    I ran dotnet restore successfully.

    But dotnet run failed because I have framework versions 1.0.0 and 1.0.1 installed, but not 1.1.0.

    I thought EF 1.1.0 would require VS 2017. Should I (somehow) install EF 1.1?

    I am using VS Community 2015; 14.0.25431.01 Update 3

    Should I get a later version?

    Thanks again!!

    1. I would install 1.1 because it has a lot of features and fixes with 1.0 does not have. Install it from the .NET Core downloads (Click the LTS button first)

      Greetings Damien

  13. Thanks Damien,

    I went to the downloads page: https://www.microsoft.com/net/download/core
    I saw the LTS ‘Button’, but it was just an AJAX message when I hover over it, it wasn’t a real button. Clicking it did nothing. It only showed a description of that LTS means.

    It looks like I can download .NET Core 1.0.3 SDK – Installer from that page, but not EF 1.1.0.

    Earlier, I did install EF 1.1.0 from the NuGet package manager, but like you said before, that didn’t work.
    https://www.nuget.org/packages/Microsoft.EntityFrameworkCore/

    Do you have a URL for a download page for EF 1.1.0?

    Do I need VS 2017?

    Thanks again!

    1. sorry, you need to click the current button.
      LTS: 1.0.3
      Current 1.1

  14. Woohoo!

    That did it!

    In debug, I was able to step through the code and then see a web page render in the browser!

    This is exciting!!

    Thanks again for your work on this and for your patience with me!!!

    Dan

  15. Wow!!
    And it works!
    It’s hitting my database and doing everything I would hope it would do!
    This is so wonderful!
    Now I just gotta learn how you did this.
    Thanks again *soo* much!!!

  16. Shoot. Spoke too soon.

    The app inserts to the FileDescriptions table and the page renders with a link to the file, but clicking on it breaks. The file has not actually been saved to the WebApiUploads table.

    I am able to insert/retrieve files to/from the WebApiUploads table via T-SQL and via the File Explorer, as you explained. But the app isn’t touching that table.

    I’m looking at \IISExpress\TraceLogFiles\AspNet5FileUploadFileTable for clues.

    Suggestions?

    Thanks!

    Dan

  17. I seem to have found my problem.

    It’s in the FileUploadController, with the line:

    await file.SaveAsAsync(Path.Combine(_optionsApplicationConfiguration.Value.ServerUploadFolder, fileName));

    It’s trying to combine the path to my database table with the full path to the uploaded file, including “C:\…etc.” where clearly we want only the file name and extension.

    .. still looking into the details.

    I’ll be checking back!

    Thanks again!!

  18. When i try to import project VS15 , program gives error.

    error is : “one or more solutions were not loaded correctly”

    how can i solve this problem? i have no any idea :/

    maybe i need to upgrade my VS15 to VS17, right?

  19. Thanks for the great example. I’m wondering if anyone has ideas on how to handle delete. It would be nice if the FileDescription was deleted when the file was deleted and vice versa. One idea is using a foreign key in FileDescription to path_locator column in the file table and cascading delete. However I’m not sure how to set this up in EF Core. It appears that sql column type HierarchyID is not supported in core.

  20. I’m struggling to see where the FileTable dbo.WebApiUploads is actually being referenced? The fileshare created is used in your config.json but other than that the FileTable isn’t actually being accessed and the information is stored in a separate table, so this would still work without FileStream/FileTable?

  21. Keith · · Reply

    I’m with Mark (1/9), where does this use the benefits of FileStream/FileTable?

  22. Hi Mark, Keith

    The example uses the folder created by the FileTable. To upload, download, the files are saved directly to the SQL FileTable. If you wanted, you can access the files through the FileTable with SQL. What benefits of upload/download persistence are missing?

    Greetings Damien

  23. Mark Heptinstall · · Reply

    For me it’s that the actual FileTable you see and query in the database isn’t actually used and you have the FileDescriptions table instead. If you were to allow non-transactional access to the FileTable then the FileDescriptions table would be incorrect the files were modified. Perhaps you could put a trigger on the FileTable to ensure the FileDescriptions table is in sync.

    1. Hi Mark thanks for your feedback. Can we continue this discussion here?

      https://github.com/damienbod/AspNetCoreFileUploadFileTable/issues/4

      My main reason for doing this was performance. At first I used the FileTable through SQL queries but had problems. Would be very grateful for improvements, feedback.

      Greeetings Damien

  24. I have downloaded the .net core 2.0 and can make it work. I have opened the solution in Visual Studio 2017 and when I do Update-Database I get the error
    Unable to create an object of type ‘FileContext’. Add an implementation of ‘IDesignTimeDbContextFactory’ to the project, or see https://go.microsoft.com/fwlink/?linkid=851728 for additional patterns supported at design time. —> System.MissingMethodException: No parameterless constructor defined for this object.

    If I do your suggestion it says: “No executable found matching command “dotnet-ef””

    What should I do to make your project runnable?

    1. now .NET Core 3.0 VS 2019

  25. paco fmar · · Reply

    Thank you for pointing me to FileTable, saved me lots of headaches :D. Just to add my 2 cents, I am using Path_locator property instead of the name to avoid duplicities since it’s not unique in the FileTable. By using GetPathLocator() SQL Server function you could update this property passing the full file path where it was stored.

  26. Alex Godoroja · · Reply

    I left an issue on your project on GitHub I was hoping you could address it
    Many thanks
    https://github.com/damienbod/AspNetCoreFileUploadFileTable/issues

      1. Hi, I would like to know if it’s possible to see all files in file table.
        If yes, how can I adapt your code to do that? So your code show only the downloaded files
        Thanks in advance

  27. Yu can do a DB query on the Files table which shows which files are in the db

Leave a reply to Dan Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.