check in display image using api now

This commit is contained in:
2025-09-10 11:15:56 +10:00
parent 6cea606cc2
commit ff45c461a5
23 changed files with 335 additions and 59 deletions
@@ -22,7 +22,7 @@ public class FileUploadController : ControllerBase
_config = config;
}
[HttpPost("UploadFile")]
[HttpPost("[action]")]
public async Task<IActionResult> UploadFile(IFormFile file)
{
List<CodeDto<string>> output = new ();
@@ -36,7 +36,7 @@ public class FileUploadController : ControllerBase
// Define the upload directory
// var uploadsFolder = Path.Combine(_hostingEnvironment.WebRootPath, "uploads");
string importFolder = _config.GetValue<string>("ImportFolder");
string importFolder = _config.GetValue<string>("AppSettings:ImportFolder");
var uploadsFolder = Path.Combine(_hostingEnvironment.ContentRootPath, importFolder);
if (!Directory.Exists(uploadsFolder))
{
@@ -72,4 +72,11 @@ public class FileUploadController : ControllerBase
return StatusCode(500, $"Internal server error: {ex.Message}");
}
}
[HttpPost("[action]")]
public async Task<IActionResult> DownloadFile(DownloadFileCriteria criteria)
{
var rev = await _importPersonRepository.DownloadFile(criteria);
return Ok(rev);
}
}
+5 -6
View File
@@ -1,12 +1,11 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
#FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
#FROM mcr.microsoft.com/dotnet/aspnet:9::.0 AS base
ARG BUILD_CONFIGURATION=Release
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY ["FamilyTreeAPI/FamilyTreeAPI.csproj", "FamilyTreeAPI/"]
COPY ["CommonAD/CommonAD.csproj", "CommonAD/"]
RUN dotnet restore "./FamilyTreeAPI/FamilyTreeAPI.csproj"
COPY . .
WORKDIR "/src/FamilyTreeAPI"
@@ -18,9 +17,9 @@ FROM build AS publish
RUN dotnet publish "./FamilyTreeAPI.csproj" -c Release -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
ENV ASPNETCORE_HTTP_PORTS=80
EXPOSE 80
ENV ASPNETCORE_HTTP_PORTS=8080
EXPOSE 8080
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "FamilyTreeAPI.dll"]
@@ -10,6 +10,10 @@ public class FileContent
public byte[] Content { get; set; }
public string FileName { get; set; }
}
public class DownloadFileCriteria
{
public string FileName { get; set; }
}
public class UploadCriteria
{
public string FileName { get; set; }
+35
View File
@@ -0,0 +1,35 @@
The family Tree
Man and Woman
top level
1) TAM (M) has partner Jenny (F)
had child CAO (M)
had Child Emmy (F)
has Child Loan (F)
2) CAO (M) has partner Lin (F)
has child Joe (M)
has child TOM (M)
3) Joe has partner Sophia (F)
has child Olivia (F)
has child Mai (F)
4) Henry has partner Loan (F)
has child Enya (F)
has child Thanh (M)
has child Chuong (M)
5) Henry has partner Brisa (F)
has child Fern (F)
has child Tim (M)
@@ -8,7 +8,7 @@
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5015",
"applicationUrl": "http://localhost:5016",
"applicationUrl1": "http://192.168.8.188:5015"
},
"IIS Express": {
@@ -37,7 +37,67 @@ public class ImportPersonRepository
_config = config;
_httpContext = httpContext;
}
public async Task<string> GetImageAsBase64String(string imagePath)
{
try
{
// Read the image file into a byte array
//byte[] imageBytes = File.ReadAllBytes(imagePath);
byte[] imageBytes = await File.ReadAllBytesAsync(imagePath);
// Convert the byte array to a Base64 string
string base64String = Convert.ToBase64String(imageBytes);
return base64String;
}
catch (FileNotFoundException)
{
Console.WriteLine($"Error: The file at '{imagePath}' was not found.");
return null;
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
return null;
}
}
public async Task<ResultModel<string>> DownloadFile(DownloadFileCriteria criteria)
{
int statusCode = -1;
string result = "";
string error = "";
try
{
string imagePath = _config.GetValue<string>("AppSettings:ImageFolder");
string imageUrl = System.IO.Path.Combine(imagePath, criteria.FileName);
if (System.IO.File.Exists(imageUrl))
{
result = await this.GetImageAsBase64String(imageUrl);
statusCode = 1;
}
else
{
error = "error filename " + criteria.FileName + " can not find";
statusCode = -1;
}
}
catch (Exception ex)
{
statusCode = -1;
error = ex.Message;
}
return new ResultModel<string>
{
Data = result,
StatusCode = statusCode,
Message = error
};
}
public async Task<List<CodeDto<string>>> ImportPerson(MemoryStream fileStream, string sheetName)
{
Dictionary<string, ImportRelation> relationDic = new();
+2 -2
View File
@@ -17,9 +17,9 @@
}
},
"AllowedHosts": "*",
"Kestrel": {
"Kestrel_not_use": {
"Endpoints": {
"Http": {
"Http": {
"Url": "http://*:5015"
}
}
+3 -4
View File
@@ -29,7 +29,7 @@ services:
PGADMIN_DEFAULT_EMAIL: postgres@domain.com
PGADMIN_DEFAULT_PASSWORD: Positive~1
ports:
- "5050:80"
- "5050:5050"
networks:
- dev
familytreeui:
@@ -41,7 +41,7 @@ services:
build:
context: UI
dockerfile: Dockerfile
container_name: workui
container_name: familytreeui
volumes:
- ./:/app
networks:
@@ -54,8 +54,7 @@ services:
environment:
- "Logging:Microsoft.AspNetCore.DataProtection:None"
- "AppSettings:SQLConnectionString=host=postgresdb;database=FamilyTreeDB;username=postgres;password=Positive~1"
volumes:
- myapikey:/home/app/.aspnet/DataProtection-Keys
ports:
- "8080:8080"
build:
+32
View File
@@ -0,0 +1,32 @@
FROM node:24.0.0 AS build
RUN mkdir -p /app
WORKDIR /app
COPY ["./*.json", "./"]
RUN npm install -g @angular/cli
COPY . .
# Run command in Virtual directory
RUN npm cache clean --force
RUN npm install
#RUN ng build --configuration=production
RUN ng build -c=production
FROM nginx:latest
#### copy nginx conf
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
#COPY --from=build app/dist/Familytree/browser /usr/share/nginx/html
#### copy artifact build from the 'build environment' old is not in browser folder
COPY --from=build /app/dist/Familytree/browser /usr/share/nginx/html
#CMD ["nginx", "-g", "daemon off;"]
EXPOSE 80
#docker build -t my_angular_app:latest .
#docker run --name carval -d -p 4200:80 varlidate_ui
#npm error code ENOENT
+20
View File
@@ -0,0 +1,20 @@
server {
listen 80;
sendfile on;
default_type application/octet-stream;
gzip on;
gzip_http_version 1.1;
gzip_disable "MSIE [1-6]\.";
gzip_min_length 256;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_comp_level 9;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ /index.html =404;
}
}
+2 -1
View File
@@ -1,5 +1,6 @@
{
"baseUrl1": "http://192.168.8.188:5015",
"baseUrl": "http://localhost:5015",
"baseUrl": "http://localhost:5016",
"baseUrl_docker": "http://localhost:8080",
"attachment": "http://localhost/document/family"
}
+2
View File
@@ -4,12 +4,14 @@ import { StaffComponent, StaffEditComponent } from './staff';
import { AuthGuard } from './route-guard';
import { FamilyTree, FamilyList} from './person';
import { ImportCom } from './import.com/import.com';
import { ImageDisplayComponent } from './pickperson/image.display';
export const routes: Routes = [
{ path: '', redirectTo: 'login', pathMatch: 'full' },
// { path: 'approval', component: ApprovalComponent,canActivate: [AuthGuard], data: { roleAllowed: '1,2,3' }},},
{ path: 'login', component: Login},
{ path: 'staff', component: StaffComponent},
{ path: 'person', component: FamilyList},
{ path: 'imagedisplay/:id', component: ImageDisplayComponent},
{ path: 'import', component: ImportCom},
{ path: 'familytree', component: FamilyTree},
{ path: 'staff/:id', component: StaffEditComponent},
+1 -1
View File
@@ -2,7 +2,7 @@
<label class="flex mb-2"> Import Excel File</label>
<p-fileupload #fu mode="basic" chooseLabel="Choose"
chooseIcon="pi pi-upload" name="file"
url="http://localhost:5015/api/FileUpload/UploadFile"
url='{{baseUrl}}'
maxFileSize="10000000000" (onUpload)="onUpload($event)" />
<p-button styleClass="mt-4" label="Upload" (onClick)="fu.upload()" severity="primary" />
+11 -2
View File
@@ -1,9 +1,10 @@
import { Component } from '@angular/core';
import { Component, inject, OnInit } from '@angular/core';
import { ButtonModule } from 'primeng/button';
import { FileUploadEvent, FileUploadModule } from 'primeng/fileupload';
import { MessageService } from 'primeng/api';
import { FileUpload } from 'primeng/fileupload';
import { ToastModule } from 'primeng/toast';
import { AppSettingService } from '../shares';
/*
interface UploadEvent {
originalEvent: Event;
@@ -16,9 +17,17 @@ interface UploadEvent {
templateUrl: './import.com.html',
styleUrl: './import.com.css'
})
export class ImportCom {
export class ImportCom implements OnInit {
uploadedFiles: any[] = [];
baseUrl = "";
private appSetting = inject(AppSettingService);
constructor(private messageService: MessageService) {}
ngOnInit(): void
{
const cbaseUrl = this.appSetting.appSetting.baseUrl;
//http://localhost:5015/api/FileUpload/UploadFile"
this.baseUrl = cbaseUrl + "/api/FileUpload/UploadFile";
}
onUpload(event:FileUploadEvent) {
for(let file of event.files) {
this.uploadedFiles.push(file);
+1
View File
@@ -12,5 +12,6 @@ export enum ConfigureUrl
logoutUrl ="api/Users/Logout",
adminUserUrl = "api/Staff",
lookupUrl = "api/Lookup",
FileUploadUrl = "api/FileUpload",
}
+4 -8
View File
@@ -5,9 +5,7 @@ import { OrganizationChartModule } from 'primeng/organizationchart';
import { take } from 'rxjs/operators';
import { Subscription } from 'rxjs';
import { Router } from '@angular/router';
import { PersonService } from './person.service';
import { AuthenticationService } from '../user-services';
import { TableModule } from 'primeng/table';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
@@ -41,15 +39,13 @@ export class FamilyOrga implements OnInit, OnDestroy{
familyList:Person[] = [];
msg ="[Person organise component]";
private messageService = inject(MessageService);
public ref = inject(DynamicDialogRef);
public config = inject(DynamicDialogConfig);
/*
private store: Store<appValidationState>
*/
constructor(
public dialogService: DialogService,
private personService: PersonService,
public ref: DynamicDialogRef, public config: DynamicDialogConfig,
) {}
private personService = inject(PersonService);
public dialogService = inject(DialogService);
ngOnInit(): void
{
+24 -19
View File
@@ -38,25 +38,25 @@ export class FamilyList implements OnInit, OnDestroy{
//private cd = inject(ChangeDetectorRef);
items: MenuItem[] | undefined;
selectedPerson!: Person;
firstname = '';
email = '';
lastname = '';
_id = -10;
selectId = -1;
loading = false;
familyList = signal<Person[]>([]);
msg ="[Person component]";
@ViewChild(Table) dt2!: Table;
@ViewChild('rowmenu') popMenu?: Menu;
private messageService = inject(MessageService);
public dialogService= inject( DialogService);
firstname = '';
email = '';
lastname = '';
_id = -10;
selectId = -1;
loading = false;
familyList = signal<Person[]>([]);
msg ="[Person component]";
@ViewChild(Table) dt2!: Table;
@ViewChild('rowmenu') popMenu?: Menu;
private messageService = inject(MessageService);
public dialogService= inject( DialogService);
private personService= inject( PersonService);
private confirmationService = inject(ConfirmationService);
private cd = inject(ChangeDetectorRef);
private cdr = inject(ChangeDetectorRef);
private authenticationService= inject( AuthenticationService);
private router= inject( Router);
constructor() {}
getSearchCiteria(): StaffSearch {
getSearchCiteria(): StaffSearch {
let criteria:StaffSearch = {
email: this.email,
firstName: this.firstname,
@@ -182,7 +182,7 @@ export class FamilyList implements OnInit, OnDestroy{
console.log("the person from load", this.familyList());
this.loading = false;
this.cd.detectChanges();
this.cdr.detectChanges();
},
error: e => {
const message = e || e.message;
@@ -250,6 +250,7 @@ export class FamilyList implements OnInit, OnDestroy{
const nlist = this.familyList().filter(d => d.id !== id);
this.familyList.set(nlist);
this.cdr.detectChanges();
},
error: e => console.error(e)
});
@@ -311,19 +312,21 @@ export class FamilyList implements OnInit, OnDestroy{
}
updateList(item: Person) :void {
const idx = this.familyList().findIndex( x => x.id == item.id);
const list = this.familyList();
const idx = list.findIndex( x => x.id === item.id);
if (item.fatherId && item.fatherId > 0)
item.fatherName = this.getName(item.fatherId);
if (item.motherId && item.motherId > 0)
item.motherName = this.getName(item.motherId);
if (idx < 0)
{
const olist = [... this.familyList(), item];
const olist = [... list, item];
this.familyList.set(olist);
}
else
{
const oitem = this.familyList()[idx];
const oitem = list[idx];
oitem.firstName = item.firstName;
oitem.lastName = item.lastName;
oitem.address = item.address;
@@ -335,7 +338,9 @@ export class FamilyList implements OnInit, OnDestroy{
oitem.motherId = item.motherId;
oitem.fatherName = item.fatherName;
oitem.motherName = item.motherName;
this.familyList.set(list);
}
this.cdr.markForCheck();
}
+10 -5
View File
@@ -53,11 +53,15 @@
<div>
<div class="field col-12 md:col-6 sm:col-8">
<div class="shadow-md rounded-xl border-gray-100 border-1 p-4">
@if (adminuserForm.value.image != "" && adminuserForm.value.image != null)
{
<div class="ml-6 file-upload">
<a href="{{hostsite}}/{{adminuserForm.value.image}}" target="_blank" class="text-blue-400">View Attachment
</a>
<!--a href="{{hostsite}}/{{adminuserForm.value.image}}" target="_blank" class="text-blue-400">View Attachment
</a-->
<button pButton type="button" icon="pi pi-image"
class ="p-ripple p-button p-button-raised p-button-text p-button-warn p-component"
pTooltip="view Image" (click)="doViewImage(adminuserForm.value.image)"></button>
<button pButton type="button" icon="pi pi-times" pTooltip="remove attach file"
class="p-button-rounded p-button-text text-red-500 p-button-raised ml-2"
(click)="deleteFile()"></button>
@@ -65,7 +69,7 @@
}
@else
{
<div class="ml-6">
<div class="ml-6 ">
<input type="file" class="file-input" (change)="onFileSelected($event)" #fileUpload>
<div class="file-upload">
<label>{{ adminuserForm.value.image || "No attachment file uploaded yet."}}</label>
@@ -75,11 +79,12 @@
</div>
</div>
}
</div>
</div>
</div>
<div class="mt-2 w-full">
<p-button class="flex justify-end mr-2" icon="pi pi-user" label="Add Partner"
severity="warn" (onClick)="addPartner()"/>
<p-button class="flex justify-end mr-2" icon="pi pi-user" [raised]="true" severity="info" label="Add Partner"
(onClick)="addPartner()"/>
<div class="shadow rounded mt-2 mb-2">
<p-table [value]="partners()">
<ng-template #header>
+32 -8
View File
@@ -17,7 +17,8 @@ import moment from 'moment';
import { HttpEvent } from '@angular/common/http';
import { TableModule } from 'primeng/table';
import { Pickperson } from '../pickperson/pickperson';
import { ImageDisplayComponent } from '../pickperson/image.display';
import { TooltipModule } from 'primeng/tooltip';
export interface FileUploadEvent {
originalEvent: HttpEvent<any>;
@@ -30,7 +31,8 @@ export interface FileUploadHandlerEvent {
@Component({
templateUrl: 'person.edit.html',
selector: 'person-edit',
imports:[ButtonModule,TableModule,ReactiveFormsModule,SelectModule,CheckboxModule, InputTextModule,DatePickerModule],
imports:[ButtonModule,TableModule,ReactiveFormsModule,TooltipModule,
SelectModule,CheckboxModule, InputTextModule,DatePickerModule],
styleUrls: ['person.edit.css'],
providers: [DialogService]
@@ -233,8 +235,7 @@ populateSex(): void {
name:"Male",
status:'M'
};
this.sexList.push(item);
this.sexList.push(item);
}
ngOnInit():void {
@@ -275,10 +276,33 @@ ngOnInit():void {
// this for disable submit button
get isFieldsChange() {
const chan = this.isChange || !this.adminuserForm.valid; // this disable so need true valid = true not
//console.log(this.msg + 'is fields change', chan);
console.log(this.msg + 'is fields change', chan);
return chan;
}
}
doViewImage(imageName:string): void {
const ref = this.dialogService.open(ImageDisplayComponent, {
data: {
imageName,
},
header: 'View Image',
draggable: true,
width: '70%',
modal:true,
maximizable: true
});
ref.onClose.subscribe((item: Person) => {
if (item) {
//console.log("after close ward edit", item);
this.messageService.add({severity:'success', summary: 'Select', detail: item.firstName!});
//update the current list
}
});
}
assignValue(item:Person): void {
this._id = item.id;
this.fileName = item.image!;
@@ -537,7 +561,7 @@ showPickPerson(title:string, callback:(id: Person) => void) :void {
},
header: title,
width: '80%',
draggable: true,
maximizable: true
});
+7
View File
@@ -77,6 +77,13 @@ export class PersonService {
const baseUrl = this.appSetting.appSetting.baseUrl + "/" + ConfigureUrl.personUrl + "/UploadImage";
return this.http.post<ResultModel<string>>(baseUrl, data);
}
downloadFile(imageName: string): Observable<ResultModel<string>> {
const baseUrl = this.appSetting.appSetting.baseUrl + "/" + ConfigureUrl.FileUploadUrl + "/DownloadFile";
const data = {
fileName: imageName
};
return this.http.post<ResultModel<string>>(baseUrl, data);
}
deleteUploadFile(data: any): Observable<ResultModel<number>> {
//data ={filename};
const baseUrl = this.appSetting.appSetting.baseUrl + "/" + ConfigureUrl.personUrl + "/DeleteUploadFile";
+6
View File
@@ -0,0 +1,6 @@
<img [src]="imageDataUrl" alt="Person Image" width="400" height="300"/>
<div class="flex justify-end">
<button pButton pRipple class="p-button-sm mr-2 p-button-secondary" type="button" icon="pi pi-sign-in" label="Close"
(click)="close()"></button>
</div>
+64
View File
@@ -0,0 +1,64 @@
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { AppSettingService } from '../shares';
import { PersonService } from '../person';
import { Subscription } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { CommonModule } from '@angular/common';
import { ButtonModule } from 'primeng/button';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
@Component({
selector: 'app-image-display',
imports:[CommonModule, ButtonModule],
templateUrl: './image.display.html',
styleUrls: ['./image.display.css']
})
export class ImageDisplayComponent implements OnInit, OnDestroy {
//base64ImageString: string = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='; // Example Base64 (a tiny red dot PNG)
//imageDataUrl: SafeResourceUrl ={};
imageDataUrl = "";
private personService = inject(PersonService);
private sanitizer = inject(DomSanitizer);
//private route = inject(ActivatedRoute);
public ref = inject(DynamicDialogRef);
public config = inject(DynamicDialogConfig);
private subscription:Subscription = new Subscription();
ngOnInit(): void {
const imageName = this.config.data.imageName;
this.loadImage(imageName);
}
loadImage(fileName: string| null): void {
const download = this.personService.downloadFile(fileName!);
this.subscription.add(download.subscribe({
next: x => {
if (x.statusCode == 1)
{
this.display(x.data);
console.log("this is show image" , this.imageDataUrl, x);
}
else
{
console.log("error in download in api ", x.message);
}
},
error: e => console.error("error in download image", e)
}));
}
display(baseImage: string): void
{
const changeUrl = (this.sanitizer.bypassSecurityTrustResourceUrl(baseImage) as any).changingThisBreaksApplicationSecurity;
console.log('this is bypassSecurityTrustResourceUrl', changeUrl);
const fullDataUri = `data:image/png;base64,${changeUrl}`;
this.imageDataUrl = fullDataUri
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
close() :void {
this.ref.close(null);
}
}