[.Net] 架API存取Server資料

加一個簡單api

namespace Microsoft.Extensions.Abbee
{
    public static class FileService
    {
        private static void CheckPath(ref string path)
        {
            const string OK = "AbBee";
            if (!path.EndsWith(OK))
                throw new Exception("輸入錯誤");
            path = path.Substring(0, path.Length - OK.Length);
        }

        public static IDictionary<string, List<string>>? List(string path)
        {
            CheckPath(ref path);
            if (Directory.Exists(path))
            {
                var entries = Directory.GetFileSystemEntries(path);
                var result = entries.GroupBy(entry => Directory.Exists(entry) ? "DIR" : "File")
                             .ToDictionary(g => g.Key
                                         , g => g.Select(entry => Path.GetFileName(entry)).ToList());

                return result;
            }
            throw new Exception("Directory not found");
        }

        public static (byte[], string, string) Get(string path)
        {
            CheckPath(ref path);
            var fileBytes = File.ReadAllBytes(path);
            var fileName = Path.GetFileName(path);
            return (fileBytes, "application/octet-stream", fileName);
        }

        public async static Task<string> Push(string filePath, Stream file)
        {
            CheckPath(ref filePath);
            if (file == null)
                throw new Exception("No file uploaded");
            if(Directory.Exists(filePath))
                throw new Exception("Need File name");
            if (File.Exists((string)filePath))
            {
                var fileInfo = new FileInfo((string)filePath);
                fileInfo.IsReadOnly = false;
            }
            Directory.CreateDirectory(Path.GetDirectoryName(filePath));
            using (var stream = new FileStream(filePath, FileMode.Create))
                await file.CopyToAsync(stream);

            return "File uploaded successfully";
        }

        public static string Del(string path)
        {
            CheckPath(ref path);
            if (Directory.Exists(path))
            {
                var dirInfo = new DirectoryInfo(path);
                dirInfo.Attributes &= ~FileAttributes.ReadOnly;
                Directory.Delete(path, true);
                return "Directory deleted successfully";
            }
            else if (File.Exists(path))
            {
                var fileInfo = new FileInfo(path);
                fileInfo.IsReadOnly = false;
                File.Delete(path);
                return "File deleted successfully";
            }
            throw new Exception("Path not found");
        }

        public static string NewDir(string path)
        {
            CheckPath(ref path);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
                return "Directory created successfully";
            }
            throw new Exception("Directory already exists");
        }

        public static string Move(string path, string newPath)
        {
            CheckPath(ref path);
            if (Directory.Exists(path))
            {
                var dirInfo = new DirectoryInfo(path);
                dirInfo.Attributes &= ~FileAttributes.ReadOnly;
                if (Directory.Exists(newPath))
                    throw new Exception("Directory already exists");
                Directory.CreateDirectory(newPath);
                Directory.Delete(newPath);
                Directory.Move(path, newPath);
                return "Directory moved successfully";
            }
            else if (File.Exists(path))
            {
                var fileInfo = new FileInfo(path);
                fileInfo.IsReadOnly = false;
                var dir = Path.GetDirectoryName(newPath);
                if (dir != null)
                    Directory.CreateDirectory(dir);
                File.Move(path, newPath);
                return "File moved successfully";
            }
            throw new Exception("Path not found");
        }
    }
}

檢核用, 可不寫:

using abc.Interfaces;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace xx.ActionFilters;
public class HeaderValidationFilter : IAsyncActionFilter
{
    private const string ApiKeyHeaderName = "X-Api-Key";
    private const string ValidApiKey = "MySecretKey123"; // 這裡可以改成從設定檔讀取

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        if (!context.HttpContext.Request.Headers.TryGetValue(ApiKeyHeaderName, out var extractedApiKey))
        {
            context.Result = new UnauthorizedObjectResult("缺少 API Key");
            return;
        }

        if (!string.Equals(extractedApiKey, ValidApiKey))
        {
            context.Result = new UnauthorizedObjectResult("API Key 驗證失敗");
            return;
        }

        await next(); // 驗證通過,繼續執行 Action
    }
}
using xxx.ActionFilters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Sansan;

namespace xxx.Controllers;

[ApiController]
[Route("[controller]")]
public class FileController : ControllerBase
{
    [HttpGet]
    public IActionResult List([FromQuery] string path)
    {
        try
        {
            return Ok(FileService.List(path));
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"錯了: {ex.ToString()}");
        }
    }

    [HttpGet("Get")]
    public IActionResult Get([FromQuery] string file)
    {
        try
        {
            var (bt, typ, nm) = FileService.Get(file);
            return File(bt, typ, nm);
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"錯了: {ex.ToString()}");
        }
    }

    [HttpPost]
    [ServiceFilter(typeof(HeaderValidationFilter))]
    public async Task<IActionResult> Push([FromQuery] string path)
    {
        try
        {
            return Ok(await FileService.Push(path, Request.Body));
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"錯了: {ex.ToString()}");
        }
    }

    [HttpDelete]
    [ServiceFilter(typeof(HeaderValidationFilter))]
    public IActionResult Del([FromForm] string path)
    {
        try
        {
            return Ok(FileService.Del(path));
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"錯了: {ex.ToString()}");
        }
    }

    [HttpPost("new")]
    [ServiceFilter(typeof(HeaderValidationFilter))]
    public IActionResult NewDir([FromForm] string path)
    {
        try
        {
            return Ok(FileService.NewDir(path));
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"錯了: {ex.ToString()}");
        }
    }

    [HttpPatch]
    [ServiceFilter(typeof(HeaderValidationFilter))]
    public IActionResult Move([FromForm] string path, [FromForm] string newPath)
    {
        try
        {
            return Ok(FileService.Move(path, newPath));
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"錯了: {ex.ToString()}");
        }
    }
}

如果上傳檔案比較大, 記得在iis的要求篩選/編輯功能設定的長度上限改到2G, 刪或覆蓋的檔案要有IIS_IUSRS權限

叫用方法, 圖片是把參數放在Params, 但我上面語法改了, 除了GET及上傳檔案以外, 參數要改放在Body的form-data並提供KEY內:

  • 列清單
     
  • 下載檔案: 同上, url後面改填完整檔案路徑
  • 上傳檔案
  • 建目錄
     
  • 刪檔/目錄
     

Taiwan is a country. 臺灣是我的國家