feat(api): add CutFab API client and configuration\n\n- Add ICutFabApiClient + CutFabApiClient HTTP client\n- Wire base URL via appSettings (CutFab.ApiBaseUrl)\n- Register client in Program and inject into services\n- Add required System.Net.Http and compression references
This commit is contained in:
@@ -26,7 +26,7 @@
|
|||||||
<PublisherName>Rogers Engineering</PublisherName>
|
<PublisherName>Rogers Engineering</PublisherName>
|
||||||
<CreateWebPageOnPublish>true</CreateWebPageOnPublish>
|
<CreateWebPageOnPublish>true</CreateWebPageOnPublish>
|
||||||
<WebPage>publish.htm</WebPage>
|
<WebPage>publish.htm</WebPage>
|
||||||
<ApplicationRevision>7</ApplicationRevision>
|
<ApplicationRevision>8</ApplicationRevision>
|
||||||
<ApplicationVersion>1.6.0.%2a</ApplicationVersion>
|
<ApplicationVersion>1.6.0.%2a</ApplicationVersion>
|
||||||
<UseApplicationTrust>false</UseApplicationTrust>
|
<UseApplicationTrust>false</UseApplicationTrust>
|
||||||
<PublishWizardCompleted>true</PublishWizardCompleted>
|
<PublishWizardCompleted>true</PublishWizardCompleted>
|
||||||
@@ -70,6 +70,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="PresentationCore" />
|
<Reference Include="PresentationCore" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.IO.Compression" />
|
||||||
|
<Reference Include="System.IO.Compression.FileSystem" />
|
||||||
<Reference Include="SolidWorks.Interop.sldworks, Version=24.1.0.45, Culture=neutral, PublicKeyToken=7c4797c3e4eeac03, processorArchitecture=MSIL">
|
<Reference Include="SolidWorks.Interop.sldworks, Version=24.1.0.45, Culture=neutral, PublicKeyToken=7c4797c3e4eeac03, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||||
@@ -85,6 +88,7 @@
|
|||||||
<Reference Include="System.Data" />
|
<Reference Include="System.Data" />
|
||||||
<Reference Include="System.Drawing" />
|
<Reference Include="System.Drawing" />
|
||||||
<Reference Include="System.Security" />
|
<Reference Include="System.Security" />
|
||||||
|
<Reference Include="System.Web.Extensions" />
|
||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -119,6 +123,7 @@
|
|||||||
<Compile Include="Services\BomExcelSettings.cs" />
|
<Compile Include="Services\BomExcelSettings.cs" />
|
||||||
<Compile Include="Services\DrawingExporter.cs" />
|
<Compile Include="Services\DrawingExporter.cs" />
|
||||||
<Compile Include="Services\DxfExportService.cs" />
|
<Compile Include="Services\DxfExportService.cs" />
|
||||||
|
<Compile Include="Services\CutFabApiClient.cs" />
|
||||||
<Compile Include="Services\PartExporter.cs" />
|
<Compile Include="Services\PartExporter.cs" />
|
||||||
<Compile Include="Services\SolidWorksService.cs" />
|
<Compile Include="Services\SolidWorksService.cs" />
|
||||||
<Compile Include="Utilities\SheetMetalProperties.cs" />
|
<Compile Include="Utilities\SheetMetalProperties.cs" />
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using ExportDXF.Forms;
|
using ExportDXF.Forms;
|
||||||
using ExportDXF.Services;
|
using ExportDXF.Services;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Configuration;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
namespace ExportDXF
|
namespace ExportDXF
|
||||||
@@ -39,16 +40,17 @@ namespace ExportDXF
|
|||||||
var bomExtractor = new BomExtractor();
|
var bomExtractor = new BomExtractor();
|
||||||
var partExporter = new PartExporter();
|
var partExporter = new PartExporter();
|
||||||
var drawingExporter = new DrawingExporter();
|
var drawingExporter = new DrawingExporter();
|
||||||
var bomExcelExporter = new BomExcelExporter();
|
var baseUrl = ConfigurationManager.AppSettings["CutFab.ApiBaseUrl"] ?? "http://localhost:7027";
|
||||||
|
var apiClient = new CutFabApiClient(baseUrl);
|
||||||
|
|
||||||
var exportService = new DxfExportService(
|
var exportService = new DxfExportService(
|
||||||
solidWorksService,
|
solidWorksService,
|
||||||
bomExtractor,
|
bomExtractor,
|
||||||
partExporter,
|
partExporter,
|
||||||
drawingExporter,
|
drawingExporter,
|
||||||
bomExcelExporter);
|
apiClient);
|
||||||
|
|
||||||
return new MainForm(solidWorksService, exportService);
|
return new MainForm(solidWorksService, exportService, apiClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
319
ExportDXF/Services/CutFabApiClient.cs
Normal file
319
ExportDXF/Services/CutFabApiClient.cs
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Configuration;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Web.Script.Serialization;
|
||||||
|
using static ExportDXF.Services.CutFabApiClient;
|
||||||
|
|
||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
public interface ICutFabApiClient
|
||||||
|
{
|
||||||
|
string BaseUrl { get; }
|
||||||
|
Task<int?> ResolveDrawingIdAsync(string drawingNumber);
|
||||||
|
Task<int?> CreateDrawingAsync(int equipmentId, string drawingNumber);
|
||||||
|
Task<CutFabApiClient.ApiResponse<int?>> CreateDrawingWithInfoAsync(int equipmentId, string drawingNumber);
|
||||||
|
Task<bool> UploadDrawingPdfAsync(string drawingNumber, string pdfPath, string uploadedBy = null, string notes = null);
|
||||||
|
Task<bool> UploadDxfZipAsync(int drawingId, string zipPath);
|
||||||
|
Task<int?> CreateBomItemAsync(object upsertBomItemDto);
|
||||||
|
Task<bool> AutoLinkTemplatesAsync(int drawingId);
|
||||||
|
Task<List<ApiEquipment>> GetEquipmentAsync();
|
||||||
|
Task<List<ApiDrawingSummary>> GetDrawingsForEquipmentAsync(int equipmentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CutFabApiClient : ICutFabApiClient, IDisposable
|
||||||
|
{
|
||||||
|
private readonly HttpClient _http;
|
||||||
|
private readonly string _baseUrl;
|
||||||
|
public string BaseUrl => _baseUrl;
|
||||||
|
public class ApiResponse<T>
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public int StatusCode { get; set; }
|
||||||
|
public T Data { get; set; }
|
||||||
|
public string RawBody { get; set; }
|
||||||
|
public string Error { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public CutFabApiClient(string baseUrl)
|
||||||
|
{
|
||||||
|
_baseUrl = (baseUrl ?? string.Empty).TrimEnd('/');
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(_baseUrl))
|
||||||
|
{
|
||||||
|
// Default to deployed API port from deployment script
|
||||||
|
_baseUrl = "http://localhost:7027";
|
||||||
|
}
|
||||||
|
|
||||||
|
_http = new HttpClient
|
||||||
|
{
|
||||||
|
Timeout = TimeSpan.FromSeconds(100)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int?> ResolveDrawingIdAsync(string drawingNumber)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var url = $"{_baseUrl}/api/Drawings/resolve?drawingNumber={Uri.EscapeDataString(drawingNumber)}";
|
||||||
|
var resp = await _http.GetAsync(url).ConfigureAwait(false);
|
||||||
|
if (!resp.IsSuccessStatusCode) return null;
|
||||||
|
var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
var dict = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(json);
|
||||||
|
if (dict != null && dict.ContainsKey("ID"))
|
||||||
|
{
|
||||||
|
var idObj = dict["ID"]; // serializer returns int or double depending
|
||||||
|
if (idObj is int i) return i;
|
||||||
|
if (idObj is double d) return (int)d;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int?> CreateDrawingAsync(int equipmentId, string drawingNumber)
|
||||||
|
{
|
||||||
|
var payload = new { DrawingNumber = drawingNumber, Description = (string)null, Qty = 1, EquipmentID = equipmentId };
|
||||||
|
var json = new JavaScriptSerializer().Serialize(payload);
|
||||||
|
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
var resp = await _http.PostAsync($"{_baseUrl}/api/Drawings", content).ConfigureAwait(false);
|
||||||
|
if (!resp.IsSuccessStatusCode) return null;
|
||||||
|
var body = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dict = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(body);
|
||||||
|
if (dict != null && dict.ContainsKey("ID"))
|
||||||
|
{
|
||||||
|
var idObj = dict["ID"]; if (idObj is int i) return i; if (idObj is double d) return (int)d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApiResponse<int?>> CreateDrawingWithInfoAsync(int equipmentId, string drawingNumber)
|
||||||
|
{
|
||||||
|
var result = new ApiResponse<int?> { Success = false, StatusCode = 0, Data = null, RawBody = null, Error = null };
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var payload = new { DrawingNumber = drawingNumber, Description = (string)null, Qty = 1, EquipmentID = equipmentId };
|
||||||
|
var json = new JavaScriptSerializer().Serialize(payload);
|
||||||
|
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
var resp = await _http.PostAsync($"{_baseUrl}/api/Drawings", content).ConfigureAwait(false);
|
||||||
|
result.StatusCode = (int)resp.StatusCode;
|
||||||
|
result.Success = resp.IsSuccessStatusCode;
|
||||||
|
var body = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
result.RawBody = body;
|
||||||
|
if (!resp.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
result.Error = "HTTP " + ((int)resp.StatusCode) + " " + resp.ReasonPhrase;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dict = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(body);
|
||||||
|
if (dict != null)
|
||||||
|
{
|
||||||
|
object v;
|
||||||
|
if (TryGetCI(dict, new[] { "ID", "id" }, out v))
|
||||||
|
{
|
||||||
|
if (v is int i) { result.Data = i; return result; }
|
||||||
|
if (v is double d) { result.Data = (int)d; return result; }
|
||||||
|
int parsed; if (int.TryParse(Convert.ToString(v), out parsed)) { result.Data = parsed; return result; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Error = ex.Message;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Error = ex.Message;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UploadDrawingPdfAsync(string drawingNumber, string pdfPath, string uploadedBy = null, string notes = null)
|
||||||
|
{
|
||||||
|
if (!File.Exists(pdfPath)) return false;
|
||||||
|
using (var form = new MultipartFormDataContent())
|
||||||
|
{
|
||||||
|
form.Add(new StringContent(drawingNumber ?? string.Empty), "drawingNumber");
|
||||||
|
if (!string.IsNullOrWhiteSpace(uploadedBy)) form.Add(new StringContent(uploadedBy), "uploadedBy");
|
||||||
|
if (!string.IsNullOrWhiteSpace(notes)) form.Add(new StringContent(notes), "notes");
|
||||||
|
|
||||||
|
var fileContent = new StreamContent(File.OpenRead(pdfPath));
|
||||||
|
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/pdf");
|
||||||
|
var fileName = Path.GetFileName(pdfPath);
|
||||||
|
form.Add(fileContent, "file", fileName);
|
||||||
|
|
||||||
|
var resp = await _http.PostAsync($"{_baseUrl}/api/DrawingRevisions/upload", form).ConfigureAwait(false);
|
||||||
|
return resp.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UploadDxfZipAsync(int drawingId, string zipPath)
|
||||||
|
{
|
||||||
|
if (!File.Exists(zipPath)) return false;
|
||||||
|
using (var form = new MultipartFormDataContent())
|
||||||
|
{
|
||||||
|
var fileContent = new StreamContent(File.OpenRead(zipPath));
|
||||||
|
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/zip");
|
||||||
|
var fileName = Path.GetFileName(zipPath);
|
||||||
|
form.Add(fileContent, "file", fileName);
|
||||||
|
|
||||||
|
var resp = await _http.PostAsync($"{_baseUrl}/api/Drawings/{drawingId}/upload-dxf-templates", form).ConfigureAwait(false);
|
||||||
|
return resp.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int?> CreateBomItemAsync(object upsertBomItemDto)
|
||||||
|
{
|
||||||
|
var json = new JavaScriptSerializer().Serialize(upsertBomItemDto);
|
||||||
|
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
var resp = await _http.PostAsync($"{_baseUrl}/api/BomItems", content).ConfigureAwait(false);
|
||||||
|
if (!resp.IsSuccessStatusCode) return null;
|
||||||
|
var body = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dict = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(body);
|
||||||
|
if (dict != null)
|
||||||
|
{
|
||||||
|
object v;
|
||||||
|
if (TryGetCI(dict, new[] { "ID", "id" }, out v))
|
||||||
|
{
|
||||||
|
if (v is int i) return i;
|
||||||
|
if (v is double d) return (int)d;
|
||||||
|
int parsed; if (int.TryParse(Convert.ToString(v), out parsed)) return parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
// Successful HTTP with empty/minimal body: treat as success
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> AutoLinkTemplatesAsync(int drawingId)
|
||||||
|
{
|
||||||
|
var url = $"{_baseUrl}/api/Drawings/{drawingId}/auto-link-templates";
|
||||||
|
var resp = await _http.PostAsync(url, new ByteArrayContent(new byte[0])).ConfigureAwait(false);
|
||||||
|
return resp.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_http?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lightweight DTOs for UI binding
|
||||||
|
public class ApiEquipment
|
||||||
|
{
|
||||||
|
public int ID { get; set; }
|
||||||
|
public string EquipmentNumber { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public override string ToString() => EquipmentNumber ?? base.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ApiDrawingSummary
|
||||||
|
{
|
||||||
|
public int ID { get; set; }
|
||||||
|
public string DrawingNumber { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public override string ToString() => DrawingNumber ?? base.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<ApiEquipment>> GetEquipmentAsync()
|
||||||
|
{
|
||||||
|
var url = $"{_baseUrl}/api/Equipment";
|
||||||
|
var req = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
|
var resp = await _http.SendAsync(req).ConfigureAwait(false);
|
||||||
|
resp.EnsureSuccessStatusCode();
|
||||||
|
var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var serializer = new JavaScriptSerializer();
|
||||||
|
var raw = serializer.DeserializeObject(json);
|
||||||
|
var result = new List<ApiEquipment>();
|
||||||
|
|
||||||
|
if (raw is System.Collections.IEnumerable enumerable && !(raw is string))
|
||||||
|
{
|
||||||
|
foreach (var item in enumerable)
|
||||||
|
{
|
||||||
|
var dict = item as Dictionary<string, object>;
|
||||||
|
if (dict == null) continue;
|
||||||
|
var eq = new ApiEquipment();
|
||||||
|
object v;
|
||||||
|
if (TryGetCI(dict, new[] { "ID", "id" }, out v)) eq.ID = ToInt(v);
|
||||||
|
if (TryGetCI(dict, new[] { "EquipmentNumber", "equipmentNumber", "equipmentNo" }, out v)) eq.EquipmentNumber = v?.ToString();
|
||||||
|
if (TryGetCI(dict, new[] { "Description", "description" }, out v)) eq.Description = v?.ToString();
|
||||||
|
result.Add(eq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<ApiDrawingSummary>> GetDrawingsForEquipmentAsync(int equipmentId)
|
||||||
|
{
|
||||||
|
var url = $"{_baseUrl}/api/Equipment/{equipmentId}";
|
||||||
|
var resp = await _http.GetAsync(url).ConfigureAwait(false);
|
||||||
|
if (!resp.IsSuccessStatusCode) return new List<ApiDrawingSummary>();
|
||||||
|
var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
|
var serializer = new JavaScriptSerializer();
|
||||||
|
var root = serializer.DeserializeObject(json) as Dictionary<string, object>;
|
||||||
|
var results = new List<ApiDrawingSummary>();
|
||||||
|
if (root != null)
|
||||||
|
{
|
||||||
|
object dval;
|
||||||
|
if (!TryGetCI(root, new[] { "Drawings", "drawings" }, out dval)) return results;
|
||||||
|
if (dval is System.Collections.IEnumerable arr && !(dval is string))
|
||||||
|
{
|
||||||
|
foreach (var item in arr)
|
||||||
|
{
|
||||||
|
var d = item as Dictionary<string, object>;
|
||||||
|
if (d == null) continue;
|
||||||
|
var summary = new ApiDrawingSummary();
|
||||||
|
object v;
|
||||||
|
if (TryGetCI(d, new[] { "ID", "id" }, out v)) summary.ID = ToInt(v);
|
||||||
|
if (TryGetCI(d, new[] { "DrawingNumber", "drawingNumber" }, out v)) summary.DrawingNumber = v?.ToString();
|
||||||
|
if (TryGetCI(d, new[] { "Description", "description" }, out v)) summary.Description = v?.ToString();
|
||||||
|
results.Add(summary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetCI(Dictionary<string, object> dict, IEnumerable<string> keys, out object value)
|
||||||
|
{
|
||||||
|
foreach (var k in keys)
|
||||||
|
{
|
||||||
|
if (dict.ContainsKey(k)) { value = dict[k]; return true; }
|
||||||
|
foreach (var dk in dict.Keys)
|
||||||
|
{
|
||||||
|
if (string.Equals(dk, k, StringComparison.OrdinalIgnoreCase)) { value = dict[dk]; return true; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = null; return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ToInt(object v)
|
||||||
|
{
|
||||||
|
if (v == null) return 0;
|
||||||
|
if (v is int i) return i;
|
||||||
|
if (v is long l) return (int)l;
|
||||||
|
if (v is double d) return (int)d;
|
||||||
|
int parsed; if (int.TryParse(v.ToString(), out parsed)) return parsed; return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,5 +5,7 @@
|
|||||||
</startup>
|
</startup>
|
||||||
<appSettings>
|
<appSettings>
|
||||||
<add key="MaxBendRadius" value="2.0"/>
|
<add key="MaxBendRadius" value="2.0"/>
|
||||||
|
<!-- Deployed API base URL (default port from Deploy-CutFabApi.ps1) -->
|
||||||
|
<add key="CutFab.ApiBaseUrl" value="http://localhost:7027"/>
|
||||||
</appSettings>
|
</appSettings>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
Reference in New Issue
Block a user