feat: add file storage service with content-addressed blob store
Add FileStorageService for DXF/PDF storage using content hashing, FileStorageOptions config, FilesController for uploads, and FileBrowserController for browsing stored files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
9
FabWorks.Api/Configuration/FileStorageOptions.cs
Normal file
9
FabWorks.Api/Configuration/FileStorageOptions.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace FabWorks.Api.Configuration
|
||||||
|
{
|
||||||
|
public class FileStorageOptions
|
||||||
|
{
|
||||||
|
public const string SectionName = "FileStorage";
|
||||||
|
|
||||||
|
public string OutputFolder { get; set; } = @"C:\ExportDXF\Output";
|
||||||
|
}
|
||||||
|
}
|
||||||
184
FabWorks.Api/Controllers/FileBrowserController.cs
Normal file
184
FabWorks.Api/Controllers/FileBrowserController.cs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
using FabWorks.Api.Services;
|
||||||
|
using FabWorks.Core.Data;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace FabWorks.Api.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class FileBrowserController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IFileStorageService _fileStorage;
|
||||||
|
private readonly FabWorksDbContext _db;
|
||||||
|
private readonly FileExtensionContentTypeProvider _contentTypeProvider = new();
|
||||||
|
|
||||||
|
public FileBrowserController(IFileStorageService fileStorage, FabWorksDbContext db)
|
||||||
|
{
|
||||||
|
_fileStorage = fileStorage;
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("files")]
|
||||||
|
public async Task<ActionResult<FileListResult>> ListFiles(
|
||||||
|
[FromQuery] string search = null,
|
||||||
|
[FromQuery] string type = null)
|
||||||
|
{
|
||||||
|
var files = new List<StoredFileEntry>();
|
||||||
|
|
||||||
|
// Query DXF files from CutTemplates
|
||||||
|
if (type == null || type.Equals("dxf", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var dxfQuery = _db.CutTemplates
|
||||||
|
.Where(c => c.ContentHash != null)
|
||||||
|
.Select(c => new
|
||||||
|
{
|
||||||
|
c.Id,
|
||||||
|
c.DxfFilePath,
|
||||||
|
c.ContentHash,
|
||||||
|
c.Thickness,
|
||||||
|
DrawingNumber = c.BomItem.ExportRecord.DrawingNumber,
|
||||||
|
CreatedAt = c.BomItem.ExportRecord.ExportedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
var term = search.Trim().ToLower();
|
||||||
|
dxfQuery = dxfQuery.Where(c =>
|
||||||
|
c.DxfFilePath.ToLower().Contains(term) ||
|
||||||
|
c.DrawingNumber.ToLower().Contains(term));
|
||||||
|
}
|
||||||
|
|
||||||
|
var dxfResults = await dxfQuery
|
||||||
|
.OrderByDescending(c => c.CreatedAt)
|
||||||
|
.Take(500)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// Deduplicate by content hash (keep latest)
|
||||||
|
var seenDxf = new HashSet<string>();
|
||||||
|
foreach (var c in dxfResults)
|
||||||
|
{
|
||||||
|
if (seenDxf.Contains(c.ContentHash)) continue;
|
||||||
|
seenDxf.Add(c.ContentHash);
|
||||||
|
|
||||||
|
var fileName = c.DxfFilePath?.Split(new[] { '/', '\\' }).LastOrDefault() ?? c.DxfFilePath;
|
||||||
|
files.Add(new StoredFileEntry
|
||||||
|
{
|
||||||
|
FileName = fileName,
|
||||||
|
ContentHash = c.ContentHash,
|
||||||
|
FileType = "dxf",
|
||||||
|
DrawingNumber = c.DrawingNumber,
|
||||||
|
Thickness = c.Thickness,
|
||||||
|
CreatedAt = c.CreatedAt
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query PDF files from ExportRecords
|
||||||
|
if (type == null || type.Equals("pdf", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var pdfQuery = _db.ExportRecords
|
||||||
|
.Where(r => r.PdfContentHash != null)
|
||||||
|
.Select(r => new
|
||||||
|
{
|
||||||
|
r.Id,
|
||||||
|
r.DrawingNumber,
|
||||||
|
r.PdfContentHash,
|
||||||
|
r.ExportedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
var term = search.Trim().ToLower();
|
||||||
|
pdfQuery = pdfQuery.Where(r =>
|
||||||
|
r.DrawingNumber.ToLower().Contains(term));
|
||||||
|
}
|
||||||
|
|
||||||
|
var pdfResults = await pdfQuery
|
||||||
|
.OrderByDescending(r => r.ExportedAt)
|
||||||
|
.Take(500)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// Deduplicate by content hash
|
||||||
|
var seenPdf = new HashSet<string>();
|
||||||
|
foreach (var r in pdfResults)
|
||||||
|
{
|
||||||
|
if (seenPdf.Contains(r.PdfContentHash)) continue;
|
||||||
|
seenPdf.Add(r.PdfContentHash);
|
||||||
|
|
||||||
|
files.Add(new StoredFileEntry
|
||||||
|
{
|
||||||
|
FileName = $"{r.DrawingNumber}.pdf",
|
||||||
|
ContentHash = r.PdfContentHash,
|
||||||
|
FileType = "pdf",
|
||||||
|
DrawingNumber = r.DrawingNumber,
|
||||||
|
CreatedAt = r.ExportedAt
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FileListResult
|
||||||
|
{
|
||||||
|
Total = files.Count,
|
||||||
|
Files = files.OrderByDescending(f => f.CreatedAt).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("preview")]
|
||||||
|
public IActionResult PreviewFile([FromQuery] string hash, [FromQuery] string ext = "dxf")
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(hash) || hash.Length < 4)
|
||||||
|
return BadRequest("Invalid hash.");
|
||||||
|
|
||||||
|
if (!_fileStorage.BlobExists(hash, ext))
|
||||||
|
return NotFound("File not found.");
|
||||||
|
|
||||||
|
var stream = _fileStorage.OpenBlob(hash, ext);
|
||||||
|
if (stream == null)
|
||||||
|
return NotFound("File not found.");
|
||||||
|
|
||||||
|
var virtualName = $"file.{ext}";
|
||||||
|
if (!_contentTypeProvider.TryGetContentType(virtualName, out var contentType))
|
||||||
|
contentType = "application/octet-stream";
|
||||||
|
|
||||||
|
return File(stream, contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("download")]
|
||||||
|
public IActionResult DownloadFile([FromQuery] string hash, [FromQuery] string ext = "dxf", [FromQuery] string name = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(hash) || hash.Length < 4)
|
||||||
|
return BadRequest("Invalid hash.");
|
||||||
|
|
||||||
|
if (!_fileStorage.BlobExists(hash, ext))
|
||||||
|
return NotFound("File not found.");
|
||||||
|
|
||||||
|
var stream = _fileStorage.OpenBlob(hash, ext);
|
||||||
|
if (stream == null)
|
||||||
|
return NotFound("File not found.");
|
||||||
|
|
||||||
|
var fileName = name ?? $"{hash[..8]}.{ext}";
|
||||||
|
if (!_contentTypeProvider.TryGetContentType(fileName, out var contentType))
|
||||||
|
contentType = "application/octet-stream";
|
||||||
|
|
||||||
|
return File(stream, contentType, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileListResult
|
||||||
|
{
|
||||||
|
public int Total { get; set; }
|
||||||
|
public List<StoredFileEntry> Files { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StoredFileEntry
|
||||||
|
{
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public string ContentHash { get; set; }
|
||||||
|
public string FileType { get; set; }
|
||||||
|
public string DrawingNumber { get; set; }
|
||||||
|
public double? Thickness { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
93
FabWorks.Api/Controllers/FilesController.cs
Normal file
93
FabWorks.Api/Controllers/FilesController.cs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
using FabWorks.Api.DTOs;
|
||||||
|
using FabWorks.Api.Services;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
|
|
||||||
|
namespace FabWorks.Api.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class FilesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IFileStorageService _fileStorage;
|
||||||
|
private readonly FileExtensionContentTypeProvider _contentTypeProvider = new();
|
||||||
|
|
||||||
|
public FilesController(IFileStorageService fileStorage)
|
||||||
|
{
|
||||||
|
_fileStorage = fileStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("dxf")]
|
||||||
|
[RequestSizeLimit(50_000_000)] // 50 MB
|
||||||
|
public async Task<ActionResult<FileUploadResponse>> UploadDxf(
|
||||||
|
IFormFile file,
|
||||||
|
[FromForm] string equipment,
|
||||||
|
[FromForm] string drawingNo,
|
||||||
|
[FromForm] string itemNo,
|
||||||
|
[FromForm] string contentHash)
|
||||||
|
{
|
||||||
|
if (file == null || file.Length == 0)
|
||||||
|
return BadRequest("No file uploaded.");
|
||||||
|
|
||||||
|
using var stream = file.OpenReadStream();
|
||||||
|
var result = await _fileStorage.StoreDxfAsync(stream, equipment, drawingNo, itemNo, contentHash);
|
||||||
|
|
||||||
|
return Ok(new FileUploadResponse
|
||||||
|
{
|
||||||
|
StoredFilePath = result.FileName,
|
||||||
|
ContentHash = result.ContentHash,
|
||||||
|
FileName = result.FileName,
|
||||||
|
WasUnchanged = result.WasUnchanged,
|
||||||
|
IsNewFile = result.IsNewFile
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("pdf")]
|
||||||
|
[RequestSizeLimit(100_000_000)] // 100 MB
|
||||||
|
public async Task<ActionResult<FileUploadResponse>> UploadPdf(
|
||||||
|
IFormFile file,
|
||||||
|
[FromForm] string equipment,
|
||||||
|
[FromForm] string drawingNo,
|
||||||
|
[FromForm] string contentHash,
|
||||||
|
[FromForm] int? exportRecordId = null)
|
||||||
|
{
|
||||||
|
if (file == null || file.Length == 0)
|
||||||
|
return BadRequest("No file uploaded.");
|
||||||
|
|
||||||
|
using var stream = file.OpenReadStream();
|
||||||
|
var result = await _fileStorage.StorePdfAsync(stream, equipment, drawingNo, contentHash, exportRecordId);
|
||||||
|
|
||||||
|
return Ok(new FileUploadResponse
|
||||||
|
{
|
||||||
|
StoredFilePath = result.FileName,
|
||||||
|
ContentHash = result.ContentHash,
|
||||||
|
FileName = result.FileName,
|
||||||
|
WasUnchanged = result.WasUnchanged,
|
||||||
|
IsNewFile = result.IsNewFile
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("blob/{hash}")]
|
||||||
|
public IActionResult GetBlob(string hash, [FromQuery] string ext = "dxf", [FromQuery] bool download = false, [FromQuery] string name = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(hash) || hash.Length < 4)
|
||||||
|
return BadRequest("Invalid hash.");
|
||||||
|
|
||||||
|
if (!_fileStorage.BlobExists(hash, ext))
|
||||||
|
return NotFound("Blob not found.");
|
||||||
|
|
||||||
|
var stream = _fileStorage.OpenBlob(hash, ext);
|
||||||
|
if (stream == null)
|
||||||
|
return NotFound("Blob not found.");
|
||||||
|
|
||||||
|
var fileName = !string.IsNullOrEmpty(name) ? name : $"{hash[..8]}.{ext}";
|
||||||
|
if (!_contentTypeProvider.TryGetContentType(fileName, out var contentType))
|
||||||
|
contentType = "application/octet-stream";
|
||||||
|
|
||||||
|
if (download)
|
||||||
|
return File(stream, contentType, fileName);
|
||||||
|
|
||||||
|
return File(stream, contentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
FabWorks.Api/DTOs/FileUploadResponse.cs
Normal file
11
FabWorks.Api/DTOs/FileUploadResponse.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace FabWorks.Api.DTOs
|
||||||
|
{
|
||||||
|
public class FileUploadResponse
|
||||||
|
{
|
||||||
|
public string StoredFilePath { get; set; } // kept for client compat, contains logical filename
|
||||||
|
public string ContentHash { get; set; }
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public bool WasUnchanged { get; set; }
|
||||||
|
public bool IsNewFile { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "weatherforecast",
|
"launchUrl": "",
|
||||||
"applicationUrl": "http://localhost:5206",
|
"applicationUrl": "http://localhost:5206",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "weatherforecast",
|
"launchUrl": "",
|
||||||
"applicationUrl": "https://localhost:7182;http://localhost:5206",
|
"applicationUrl": "https://localhost:7182;http://localhost:5206",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"IIS Express": {
|
"IIS Express": {
|
||||||
"commandName": "IISExpress",
|
"commandName": "IISExpress",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "weatherforecast",
|
"launchUrl": "",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
|
|||||||
148
FabWorks.Api/Services/FileStorageService.cs
Normal file
148
FabWorks.Api/Services/FileStorageService.cs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
using FabWorks.Api.Configuration;
|
||||||
|
using FabWorks.Core.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace FabWorks.Api.Services
|
||||||
|
{
|
||||||
|
public class FileUploadResult
|
||||||
|
{
|
||||||
|
public string ContentHash { get; set; }
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public bool WasUnchanged { get; set; }
|
||||||
|
public bool IsNewFile { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IFileStorageService
|
||||||
|
{
|
||||||
|
string OutputFolder { get; }
|
||||||
|
Task<FileUploadResult> StoreDxfAsync(Stream stream, string equipment, string drawingNo, string itemNo, string contentHash);
|
||||||
|
Task<FileUploadResult> StorePdfAsync(Stream stream, string equipment, string drawingNo, string contentHash, int? exportRecordId = null);
|
||||||
|
Stream OpenBlob(string contentHash, string extension);
|
||||||
|
bool BlobExists(string contentHash, string extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileStorageService : IFileStorageService
|
||||||
|
{
|
||||||
|
private readonly FileStorageOptions _options;
|
||||||
|
private readonly FabWorksDbContext _db;
|
||||||
|
|
||||||
|
public string OutputFolder => _options.OutputFolder;
|
||||||
|
|
||||||
|
public FileStorageService(IOptions<FileStorageOptions> options, FabWorksDbContext db)
|
||||||
|
{
|
||||||
|
_options = options.Value;
|
||||||
|
_db = db;
|
||||||
|
|
||||||
|
var blobRoot = Path.Combine(_options.OutputFolder, "blobs");
|
||||||
|
if (!Directory.Exists(blobRoot))
|
||||||
|
Directory.CreateDirectory(blobRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FileUploadResult> StoreDxfAsync(Stream stream, string equipment, string drawingNo, string itemNo, string contentHash)
|
||||||
|
{
|
||||||
|
var fileName = BuildDxfFileName(drawingNo, equipment, itemNo);
|
||||||
|
|
||||||
|
// Look up previous hash by drawing number + item number
|
||||||
|
var drawingNumber = BuildDrawingNumber(equipment, drawingNo);
|
||||||
|
var previousHash = await _db.CutTemplates
|
||||||
|
.Where(c => c.BomItem.ExportRecord.DrawingNumber == drawingNumber
|
||||||
|
&& c.BomItem.ItemNo == itemNo
|
||||||
|
&& c.ContentHash != null)
|
||||||
|
.OrderByDescending(c => c.Id)
|
||||||
|
.Select(c => c.ContentHash)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
var wasUnchanged = previousHash != null && previousHash == contentHash;
|
||||||
|
var isNewFile = await StoreBlobAsync(stream, contentHash, "dxf");
|
||||||
|
|
||||||
|
return new FileUploadResult
|
||||||
|
{
|
||||||
|
ContentHash = contentHash,
|
||||||
|
FileName = fileName,
|
||||||
|
WasUnchanged = wasUnchanged,
|
||||||
|
IsNewFile = isNewFile
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FileUploadResult> StorePdfAsync(Stream stream, string equipment, string drawingNo, string contentHash, int? exportRecordId = null)
|
||||||
|
{
|
||||||
|
var drawingNumber = BuildDrawingNumber(equipment, drawingNo);
|
||||||
|
var fileName = $"{drawingNumber}.pdf";
|
||||||
|
|
||||||
|
// Look up previous PDF hash
|
||||||
|
var previousHash = await _db.ExportRecords
|
||||||
|
.Where(r => r.DrawingNumber == drawingNumber
|
||||||
|
&& r.PdfContentHash != null
|
||||||
|
&& (exportRecordId == null || r.Id != exportRecordId))
|
||||||
|
.OrderByDescending(r => r.Id)
|
||||||
|
.Select(r => r.PdfContentHash)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
var wasUnchanged = previousHash != null && previousHash == contentHash;
|
||||||
|
var isNewFile = await StoreBlobAsync(stream, contentHash, "pdf");
|
||||||
|
|
||||||
|
return new FileUploadResult
|
||||||
|
{
|
||||||
|
ContentHash = contentHash,
|
||||||
|
FileName = fileName,
|
||||||
|
WasUnchanged = wasUnchanged,
|
||||||
|
IsNewFile = isNewFile
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream OpenBlob(string contentHash, string extension)
|
||||||
|
{
|
||||||
|
var path = GetBlobPath(contentHash, extension);
|
||||||
|
if (!File.Exists(path))
|
||||||
|
return null;
|
||||||
|
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BlobExists(string contentHash, string extension)
|
||||||
|
{
|
||||||
|
return File.Exists(GetBlobPath(contentHash, extension));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> StoreBlobAsync(Stream stream, string contentHash, string extension)
|
||||||
|
{
|
||||||
|
var blobPath = GetBlobPath(contentHash, extension);
|
||||||
|
|
||||||
|
if (File.Exists(blobPath))
|
||||||
|
return false; // blob already exists (dedup)
|
||||||
|
|
||||||
|
var dir = Path.GetDirectoryName(blobPath);
|
||||||
|
if (!Directory.Exists(dir))
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
|
using var fileStream = new FileStream(blobPath, FileMode.Create, FileAccess.Write);
|
||||||
|
await stream.CopyToAsync(fileStream);
|
||||||
|
return true; // new blob written
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetBlobPath(string contentHash, string extension)
|
||||||
|
{
|
||||||
|
var prefix1 = contentHash[..2];
|
||||||
|
var prefix2 = contentHash[2..4];
|
||||||
|
return Path.Combine(_options.OutputFolder, "blobs", prefix1, prefix2, $"{contentHash}.{extension}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildDrawingNumber(string equipment, string drawingNo)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(equipment) && !string.IsNullOrEmpty(drawingNo))
|
||||||
|
return $"{equipment} {drawingNo}";
|
||||||
|
if (!string.IsNullOrEmpty(equipment))
|
||||||
|
return equipment;
|
||||||
|
return drawingNo ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildDxfFileName(string drawingNo, string equipment, string itemNo)
|
||||||
|
{
|
||||||
|
var drawingNumber = BuildDrawingNumber(equipment, drawingNo);
|
||||||
|
var paddedItem = (itemNo ?? "").PadLeft(2, '0');
|
||||||
|
if (!string.IsNullOrEmpty(drawingNumber) && !string.IsNullOrEmpty(itemNo))
|
||||||
|
return $"{drawingNumber} PT{paddedItem}.dxf";
|
||||||
|
return $"PT{paddedItem}.dxf";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,5 +8,8 @@
|
|||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"FabWorksDb": "Server=localhost;Database=ExportDxfDb;Trusted_Connection=True;TrustServerCertificate=True;"
|
"FabWorksDb": "Server=localhost;Database=ExportDxfDb;Trusted_Connection=True;TrustServerCertificate=True;"
|
||||||
|
},
|
||||||
|
"FileStorage": {
|
||||||
|
"OutputFolder": "C:\\ExportDXF\\Output"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user