武裝你的WEBAPI-OData與DTO

2023-05-08 12:01:09

本文屬於OData系列文章

Intro

前面寫了很多有關OData使用的文章,很多讀者會有疑問,直接將實體物件暴露給終端使用者會不會有風險?$expand在預設設定的情況下,資料會不會有洩露風險?

答案是肯定的,由於OData的特性,提供給我們便捷同時也會帶來一些風險。很多地方推薦使用DTO模式來隔離實體類與終端使用者使用到類的關係,從而解決以上兩個問題,OData同樣也適用。

DTO

DTO代表Data Transfer Object,是一種設計模式,用於在不同層之間傳輸資料。它通常用於將資料從一個應用程式的邏輯層傳輸到另一個應用程式的介面層或持久化層,以及在分散式系統中傳輸資料。

DTO物件是純資料物件,它包含要從一個應用程式傳輸到另一個應用程式的資料。它不包含業務邏輯或資料存取程式碼,因此它們不能直接與資料庫互動或執行任何操作,而只是簡單地儲存資料。

DTO物件通常由開發人員建立,並且可以根據需要進行擴充套件。它們可以包含各種屬性和方法,以提供使用方便和更好的可讀性。使用DTO物件可以降低耦合度,使不同層之間的資料傳輸更加簡單和安全。

AutoMapper

我們需要將實體物件與DTO進行轉換,對於需要轉換數量不是很多的情況,直接編寫一個轉換函數就方便了。

    public static class DeviceDataExtension
    {
        public static DeviceDataDto ToDeviceDataDto(this Datum deviceData)
        {
            if (deviceData == null) return null;
            DeviceDataDto deviceDataDto = new()
            {
                DataArray = deviceData.DataArray,
                DeviceId = deviceData.DeviceId,
                Timestamp = deviceData.Timestamp,
                Id = Guid.NewGuid().ToString()
            };
            return deviceDataDto;
        }
    }

但是如果需要對映的屬性很多,或者有很多物件的情況,建議使用物件對映工具:AutoMapper。基礎用法不詳細說了,講講對OData的支援。

首先安裝對OData支援的包,由於我使用預設的DI,還需要安裝DI支援的包:

Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection
Install-Package AutoMapper.AspNetCore.OData.EFCore

然後有三個要求:

  • 一定要對物件宣告顯示展開(explicit expansion)。
  • 呼叫IMapper的GetAsync()或者GetQueryAsync()方法。
  • 不能在Controller或者方法上使用[EnableQuery]特性:這個我熟,因為GetQueryAsync()函數需要利用ODataQueryOptions引數,如果同時使用[EnableQuery]會導致對結果再進行一次篩選,導致返回資料錯誤。

程式碼:

            services.AddAutoMapper(option =>
            {
                option.CreateMap<Datum, DeviceDataDto>()
                .ForMember(dest => dest.Id, opt => opt.MapFrom(src => Guid.NewGuid().ToString()))
                .ForPath(dest => dest.DataArray, opt => opt.MapFrom(src => src.DataArray))
                .ForAllMembers(w => w.ExplicitExpansion());
            });

        public DeviceDatasController(IMapper mapper)
        {
            _mapper = mapper;
        }
        
        [HttpGet]
        [ProducesResponseType(typeof(IEnumerable<DeviceDataDto>), Status200OK)]
        public async Task<IActionResult> GetAsync(string key, ODataQueryOptions<DeviceDataDto> options)
        {
            var insp = await _context.DeviceData.Where(w => w.DeviceId == key).GetQueryAsync(_mapper, options);
            return Ok(insp);
        }

這樣,我們就可以正常使用 OData,同時也享受了的 DTO 的好處,即可以對 DeviceDataDto 使用的 OData 查詢。使用的時候要注意,如果有導航屬性,導航屬性也需要設定對映。

參考資料