ASP.NET Core 백 엔드 서버 SDK를 사용하는 방법
메모
이 제품은 사용 중지되었습니다. .NET 8 이상을 사용하는 프로젝트를 대체하려면 Community Toolkit Datasync 라이브러리참조하세요.
이 문서에서는 ASP.NET Core 백 엔드 서버 SDK를 구성하고 사용하여 데이터 동기화 서버를 생성해야 합니다.
지원되는 플랫폼
ASP.NET Core 백 엔드 서버는 ASP.NET 6.0 이상을 지원합니다.
데이터베이스 서버는 밀리초 정확도로 저장되는 DateTime
또는 Timestamp
형식 필드가 다음 조건을 충족해야 합니다. 리포지토리 구현은 Entity Framework Core 및 LiteDb제공됩니다.
특정 데이터베이스 지원은 다음 섹션을 참조하세요.
- Azure SQL 및 SQL Server
- Azure Cosmos DB
- PostgreSQL
- SqLite
- LiteDb
새 데이터 동기화 서버 만들기
데이터 동기화 서버는 일반 ASP.NET Core 메커니즘을 사용하여 서버를 만듭니다. 다음 세 단계로 구성됩니다.
- ASP.NET 6.0 이상 서버 프로젝트를 만듭니다.
- Entity Framework Core 추가
- 데이터 동기화 서비스 추가
Entity Framework Core를 사용하여 ASP.NET Core 서비스를 만드는 방법에 대한 자세한 내용은 자습서
데이터 동기화 서비스를 사용하도록 설정하려면 다음 NuGet 라이브러리를 추가해야 합니다.
- Microsoft.AspNetCore.Datasync
- Entity Framework Core 기반 테이블용 Microsoft.AspNetCore.Datasync.EFCore
. - 메모리 내 테이블에 대해 Microsoft.AspNetCore.Datasync.InMemory
.
Program.cs
파일을 수정합니다. 다른 모든 서비스 정의 아래에 다음 줄을 추가합니다.
builder.Services.AddDatasyncControllers();
ASP.NET Core datasync-server
템플릿을 사용할 수도 있습니다.
# This only needs to be done once
dotnet new -i Microsoft.AspNetCore.Datasync.Template.CSharp
mkdir My.Datasync.Server
cd My.Datasync.Server
dotnet new datasync-server
템플릿에는 샘플 모델 및 컨트롤러가 포함됩니다.
SQL 테이블에 대한 테이블 컨트롤러 만들기
기본 리포지토리는 Entity Framework Core를 사용합니다. 테이블 컨트롤러를 만드는 것은 3단계 프로세스입니다.
- 데이터 모델에 대한 모델 클래스를 만듭니다.
- 애플리케이션의
DbContext
모델 클래스를 추가합니다. - 모델을 노출하는 새
TableController<T>
클래스를 만듭니다.
모델 클래스 만들기
모든 모델 클래스는 ITableData
구현해야 합니다. 각 리포지토리 형식에는 ITableData
구현하는 추상 클래스가 있습니다. Entity Framework Core 리포지토리는 EntityTableData
사용합니다.
public class TodoItem : EntityTableData
{
/// <summary>
/// Text of the Todo Item
/// </summary>
public string Text { get; set; }
/// <summary>
/// Is the item complete?
/// </summary>
public bool Complete { get; set; }
}
ITableData
인터페이스는 데이터 동기화 서비스를 처리하기 위한 추가 속성과 함께 레코드의 ID를 제공합니다.
-
UpdatedAt
(DateTimeOffset?
)는 레코드가 마지막으로 업데이트된 날짜를 제공합니다. -
Version
(byte[]
)는 모든 쓰기에서 변경되는 불투명 값을 제공합니다. - 레코드가 삭제로 표시되었지만 아직 제거되지 않은 경우
Deleted
(bool
)는 true입니다.
데이터 동기화 라이브러리는 이러한 속성을 유지 관리합니다. 사용자 고유의 코드에서 이러한 속성을 수정하지 마세요.
DbContext
업데이트
데이터베이스의 각 모델은 DbContext
등록해야 합니다. 예를 들어:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; }
}
테이블 컨트롤러 만들기
테이블 컨트롤러는 특수한 ApiController
. 다음은 최소 테이블 컨트롤러입니다.
[Route("tables/[controller]")]
public class TodoItemController : TableController<TodoItem>
{
public TodoItemController(AppDbContext context) : base()
{
Repository = new EntityTableRepository<TodoItem>(context);
}
}
메모
- 컨트롤러에는 경로가 있어야 합니다. 규칙에 따라 테이블은
/tables
하위 경로에 노출되지만 어디에나 배치할 수 있습니다. v5.0.0 이전의 클라이언트 라이브러리를 사용하는 경우 테이블은/tables
하위 경로여야 합니다. - 컨트롤러는
TableController<T>
상속해야 합니다. 여기서<T>
리포지토리 형식에 대한ITableData
구현의 구현입니다. - 모델과 동일한 형식을 기반으로 리포지토리를 할당합니다.
메모리 내 리포지토리 구현
영구 스토리지가 없는 메모리 내 리포지토리를 사용할 수도 있습니다.
Program.cs
리포지토리에 대한 싱글톤 서비스를 추가합니다.
IEnumerable<Model> seedData = GenerateSeedData();
builder.Services.AddSingleton<IRepository<Model>>(new InMemoryRepository<Model>(seedData));
다음과 같이 테이블 컨트롤러를 설정합니다.
[Route("tables/[controller]")]
public class ModelController : TableController<Model>
{
public MovieController(IRepository<Model> repository) : base(repository)
{
}
}
테이블 컨트롤러 옵션 구성
TableControllerOptions
사용하여 컨트롤러의 특정 측면을 구성할 수 있습니다.
[Route("tables/[controller]")]
public class MoodelController : TableController<Model>
{
public ModelController(IRepository<Model> repository) : base(repository)
{
Options = new TableControllerOptions { PageSize = 25 };
}
}
설정할 수 있는 옵션은 다음과 같습니다.
-
PageSize
(int
, 기본값: 100)은 쿼리 작업이 단일 페이지에서 반환되는 최대 항목 수입니다. -
MaxTop
(int
, 기본값: 512000)는 페이징 없이 쿼리 작업에서 반환되는 최대 항목 수입니다. -
EnableSoftDelete
(bool
, 기본값: false)를 사용하면 일시 삭제가 가능하며, 이 경우 항목을 데이터베이스에서 삭제하는 대신 삭제된 것으로 표시합니다. 일시 삭제를 사용하면 클라이언트가 오프라인 캐시를 업데이트할 수 있지만 삭제된 항목은 데이터베이스에서 별도로 제거해야 합니다. -
UnauthorizedStatusCode
(int
, 기본값: 401 권한 없음)은 사용자가 작업을 수행할 수 없을 때 반환되는 상태 코드입니다.
액세스 권한 구성
기본적으로 사용자는 모든 레코드를 만들고 읽고 업데이트하고 삭제하는 테이블 내에서 엔터티를 원하는 모든 작업을 수행할 수 있습니다. 권한 부여를 보다 세밀하게 제어하려면 IAccessControlProvider
구현하는 클래스를 만듭니다.
IAccessControlProvider
다음 세 가지 방법을 사용하여 권한 부여를 구현합니다.
-
GetDataView()
연결된 사용자가 볼 수 있는 것을 제한하는 람다를 반환합니다. -
IsAuthorizedAsync()
연결된 사용자가 요청되는 특정 엔터티에 대해 작업을 수행할 수 있는지 여부를 결정합니다. -
PreCommitHookAsync()
리포지토리에 기록되기 직전에 엔터티를 조정합니다.
세 가지 방법 사이에서 대부분의 액세스 제어 사례를 효과적으로 처리할 수 있습니다.
예를 들어 다음에서는 사용자가 자신의 레코드만 볼 수 있는 개인 테이블을 구현합니다.
public class PrivateAccessControlProvider<T>: IAccessControlProvider<T>
where T : ITableData
where T : IUserId
{
private readonly IHttpContextAccessor _accessor;
public PrivateAccessControlProvider(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
private string UserId { get => _accessor.HttpContext.User?.Identity?.Name; }
public Expression<Func<T,bool>> GetDataView()
{
return (UserId == null)
? _ => false
: model => model.UserId == UserId;
}
public Task<bool> IsAuthorizedAsync(TableOperation op, T entity, CancellationToken token = default)
{
if (op == TableOperation.Create || op == TableOperation.Query)
{
return Task.FromResult(true);
}
else
{
return Task.FromResult(entity?.UserId != null && entity?.UserId == UserId);
}
}
public virtual Task PreCommitHookAsync(TableOperation operation, T entity, CancellationToken token = default)
{
entity.UserId == UserId;
return Task.CompletedTask;
}
}
메서드는 정답을 얻기 위해 추가 데이터베이스 조회를 수행해야 하는 경우에 비동기입니다. 컨트롤러에서 IAccessControlProvider<T>
인터페이스를 구현할 수 있지만 스레드로부터 안전한 방식으로 HttpContext
액세스하려면 IHttpContextAccessor
전달해야 합니다.
이 액세스 제어 공급자를 사용하려면 다음과 같이 TableController
업데이트합니다.
[Authorize]
[Route("tables/[controller]")]
public class ModelController : TableController<Model>
{
public ModelsController(AppDbContext context, IHttpContextAccessor accessor) : base()
{
AccessControlProvider = new PrivateAccessControlProvider<Model>(accessor);
Repository = new EntityTableRepository<Model>(context);
}
}
테이블에 대한 인증되지 않은 액세스와 인증된 액세스를 모두 허용하려면 [Authorize]
대신 [AllowAnonymous]
데코레이트합니다.
로깅 구성
로깅은 ASP.NET Core에 대한 일반적인 로깅 메커니즘 ILogger
개체를 Logger
속성에 할당합니다.
[Authorize]
[Route("tables/[controller]")]
public class ModelController : TableController<Model>
{
public ModelController(AppDbContext context, Ilogger<ModelController> logger) : base()
{
Repository = new EntityTableRepository<Model>(context);
Logger = logger;
}
}
리포지토리 변경 내용 모니터링
리포지토리가 변경되면 워크플로를 트리거하거나, 클라이언트에 응답을 기록하거나, 다음 두 가지 방법 중 하나로 다른 작업을 수행할 수 있습니다.
옵션 1: PostCommitHookAsync 구현
IAccessControlProvider<T>
인터페이스는 PostCommitHookAsync()
메서드를 제공합니다. Th PostCommitHookAsync()
메서드는 데이터가 리포지토리에 기록된 후 클라이언트에 데이터를 반환하기 전에 호출됩니다. 클라이언트로 반환되는 데이터가 이 메서드에서 변경되지 않도록 주의해야 합니다.
public class MyAccessControlProvider<T> : AccessControlProvider<T> where T : ITableData
{
public override async Task PostCommitHookAsync(TableOperation op, T entity, CancellationToken cancellationToken = default)
{
// Do any work you need to here.
// Make sure you await any asynchronous operations.
}
}
후크의 일부로 비동기 작업을 실행하는 경우 이 옵션을 사용합니다.
옵션 2: RepositoryUpdated 이벤트 처리기 사용
TableController<T>
기본 클래스에는 PostCommitHookAsync()
메서드와 동시에 호출되는 이벤트 처리기가 포함되어 있습니다.
[Authorize]
[Route(tables/[controller])]
public class ModelController : TableController<Model>
{
public ModelController(AppDbContext context) : base()
{
Repository = new EntityTableRepository<Model>(context);
RepositoryUpdated += OnRepositoryUpdated;
}
internal void OnRepositoryUpdated(object sender, RepositoryUpdatedEventArgs e)
{
// The RepositoryUpdatedEventArgs contains Operation, Entity, EntityName
}
}
Azure App Service ID 사용
ASP.NET Core 데이터 동기화 서버는 ASP.NET 핵심 ID또는 지원하려는 다른 인증 및 권한 부여 체계를 지원합니다. 이전 버전의 Azure Mobile Apps의 업그레이드를 지원하기 위해 Azure App Service IDProgram.cs
편집합니다.
builder.Services.AddAuthentication(AzureAppServiceAuthentication.AuthenticationScheme)
.AddAzureAppServiceAuthentication(options => options.ForceEnable = true);
// Then later, after you have created the app
app.UseAuthentication();
app.UseAuthorization();
데이터베이스 지원
Entity Framework Core는 날짜/시간 열에 대한 값 생성을 설정하지 않습니다. (날짜/시간 값 생성참조). Entity Framework Core용 Azure Mobile Apps 리포지토리는 자동으로 UpdatedAt
필드를 업데이트합니다. 그러나 데이터베이스가 리포지토리 외부에서 업데이트되는 경우 업데이트할 UpdatedAt
및 Version
필드를 정렬해야 합니다.
Azure SQL
각 엔터티에 대한 트리거를 만듭니다.
CREATE OR ALTER TRIGGER [dbo].[TodoItems_UpdatedAt] ON [dbo].[TodoItems]
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE
[dbo].[TodoItems]
SET
[UpdatedAt] = GETUTCDATE()
WHERE
[Id] IN (SELECT [Id] FROM INSERTED);
END
마이그레이션을 사용하거나 EnsureCreated()
직후에 이 트리거를 설치하여 데이터베이스를 만들 수 있습니다.
Azure Cosmos DB
Azure Cosmos DB는 모든 크기 또는 규모의 고성능 애플리케이션을 위한 완전 관리형 NoSQL 데이터베이스입니다. Entity Framework Core에서 Azure Cosmos DB를 사용하는 방법에 대한 자세한 내용은 Azure Cosmos DB 공급자 참조하세요. Azure Mobile Apps에서 Azure Cosmos DB를 사용하는 경우:
UpdatedAt
및Id
필드를 지정하는 복합 인덱스를 사용하여 Cosmos 컨테이너를 설정합니다. 복합 인덱스는 Azure Portal, ARM, Bicep, Terraform 또는 코드 내에서 컨테이너에 추가할 수 있습니다. 다음은 bicep 리소스 정의에예입니다. resource cosmosContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-04-15' = { name: 'TodoItems' parent: cosmosDatabase properties: { resource: { id: 'TodoItems' partitionKey: { paths: [ '/Id' ] kind: 'Hash' } indexingPolicy: { indexingMode: 'consistent' automatic: true includedPaths: [ { path: '/*' } ] excludedPaths: [ { path: '/"_etag"/?' } ] compositeIndexes: [ [ { path: '/UpdatedAt' order: 'ascending' } { path: '/Id' order: 'ascending' } ] ] } } } }
테이블에 있는 항목의 하위 집합을 끌어오는 경우 쿼리에 관련된 모든 속성을 지정해야 합니다.
ETagEntityTableData
클래스에서 모델을 파생합니다.public class TodoItem : ETagEntityTableData { public string Title { get; set; } public bool Completed { get; set; } }
DbContext
OnModelCreating(ModelBuilder)
메서드를 추가합니다. Entity Framework용 Cosmos DB 드라이버는 기본적으로 모든 엔터티를 동일한 컨테이너에 배치합니다. 최소한 적절한 파티션 키를 선택하고EntityTag
속성이 동시성 태그로 표시되는지 확인해야 합니다. 예를 들어 다음 코드 조각은 Azure Mobile Apps에 대한 적절한 설정을 사용하여TodoItem
엔터티를 자체 컨테이너에 저장합니다.protected override void OnModelCreating(ModelBuilder builder) { builder.Entity<TodoItem>(builder => { // Store this model in a specific container. builder.ToContainer("TodoItems"); // Do not include a discriminator for the model in the partition key. builder.HasNoDiscriminator(); // Set the partition key to the Id of the record. builder.HasPartitionKey(model => model.Id); // Set the concurrency tag to the EntityTag property. builder.Property(model => model.EntityTag).IsETagConcurrency(); }); base.OnModelCreating(builder); }
Azure Cosmos DB는 v5.0.11 이후 Microsoft.AspNetCore.Datasync.EFCore
NuGet 패키지에서 지원됩니다. 자세한 내용은 다음 링크를 검토하세요.
- Cosmos DB 샘플.
- EF Core Azure Cosmos DB 공급자 설명서.
- Cosmos DB 인덱스 정책 설명서입니다.
PostgreSQL
각 엔터티에 대한 트리거를 만듭니다.
CREATE OR REPLACE FUNCTION todoitems_datasync() RETURNS trigger AS $$
BEGIN
NEW."UpdatedAt" = NOW() AT TIME ZONE 'UTC';
NEW."Version" = convert_to(gen_random_uuid()::text, 'UTF8');
RETURN NEW
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER
todoitems_datasync
BEFORE INSERT OR UPDATE ON
"TodoItems"
FOR EACH ROW EXECUTE PROCEDURE
todoitems_datasync();
마이그레이션을 사용하거나 EnsureCreated()
직후에 이 트리거를 설치하여 데이터베이스를 만들 수 있습니다.
SqLite
경고
프로덕션 서비스에는 SqLite를 사용하지 마세요. SqLite는 프로덕션 환경에서 클라이언트 쪽 사용에만 적합합니다.
SqLite에는 밀리초 정확도를 지원하는 날짜/시간 필드가 없습니다. 따라서 테스트 외에는 적합하지 않습니다. SqLite를 사용하려면 날짜/시간 속성에 대해 각 모델에서 값 변환기 및 값 비교자를 구현해야 합니다. 값 변환기 및 비교자를 구현하는 가장 쉬운 방법은 DbContext
OnModelCreating(ModelBuilder)
메서드에 있습니다.
protected override void OnModelCreating(ModelBuilder builder)
{
var timestampProps = builder.Model.GetEntityTypes().SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(byte[]) && p.ValueGenerated == ValueGenerated.OnAddOrUpdate);
var converter = new ValueConverter<byte[], string>(
v => Encoding.UTF8.GetString(v),
v => Encoding.UTF8.GetBytes(v)
);
foreach (var property in timestampProps)
{
property.SetValueConverter(converter);
property.SetDefaultValueSql("STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')");
}
base.OnModelCreating(builder);
}
데이터베이스를 초기화할 때 업데이트 트리거를 설치합니다.
internal static void InstallUpdateTriggers(DbContext context)
{
foreach (var table in context.Model.GetEntityTypes())
{
var props = table.GetProperties().Where(prop => prop.ClrType == typeof(byte[]) && prop.ValueGenerated == ValueGenerated.OnAddOrUpdate);
foreach (var property in props)
{
var sql = $@"
CREATE TRIGGER s_{table.GetTableName()}_{prop.Name}_UPDATE AFTER UPDATE ON {table.GetTableName()}
BEGIN
UPDATE {table.GetTableName()}
SET {prop.Name} = STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')
WHERE rowid = NEW.rowid;
END
";
context.Database.ExecuteSqlRaw(sql);
}
}
}
데이터베이스를 초기화하는 동안 InstallUpdateTriggers
메서드가 한 번만 호출되었는지 확인합니다.
public void InitializeDatabase(DbContext context)
{
bool created = context.Database.EnsureCreated();
if (created && context.Database.IsSqlite())
{
InstallUpdateTriggers(context);
}
context.Database.SaveChanges();
}
LiteDB
LiteDB .NET C# 관리 코드로 작성된 단일 작은 DLL로 전달되는 서버리스 데이터베이스입니다. 독립 실행형 애플리케이션을 위한 간단하고 빠른 NoSQL 데이터베이스 솔루션입니다. 디스크 내 영구 스토리지에서 LiteDb를 사용하려면 다음을 수행합니다.
NuGet에서
Microsoft.AspNetCore.Datasync.LiteDb
패키지를 설치합니다.Program.cs
LiteDatabase
싱글톤을 추가합니다.const connectionString = builder.Configuration.GetValue<string>("LiteDb:ConnectionString"); builder.Services.AddSingleton<LiteDatabase>(new LiteDatabase(connectionString));
LiteDbTableData
모델을 파생합니다.public class TodoItem : LiteDbTableData { public string Title { get; set; } public bool Completed { get; set; } }
LiteDb NuGet 패키지와 함께 제공되는
BsonMapper
특성을 사용할 수 있습니다.LiteDbRepository
사용하여 컨트롤러를 만듭니다.[Route("tables/[controller]")] public class TodoItemController : TableController<TodoItem> { public TodoItemController(LiteDatabase db) : base() { Repository = new LiteDbRepository<TodoItem>(db, "todoitems"); } }
OpenAPI 지원
NSwag
NSwag 통합에 대한 기본 지침을 따르고 다음과 같이 수정합니다.
NSwag를 지원하기 위해 프로젝트에 패키지를 추가합니다. 다음 패키지가 필요합니다.
- NSwag.AspNetCore
. - Microsoft.AspNetCore.Datasync.NSwag
.
- NSwag.AspNetCore
Program.cs
파일의 맨 위에 다음을 추가합니다.using Microsoft.AspNetCore.Datasync.NSwag;
Program.cs
파일에 OpenAPI 정의를 생성하는 서비스를 추가합니다.builder.Services.AddOpenApiDocument(options => { options.AddDatasyncProcessors(); });
Program.cs
생성된 JSON 문서 및 Swagger UI를 제공하기 위해 미들웨어를 사용하도록 설정합니다.if (app.Environment.IsDevelopment()) { app.UseOpenApi(); app.UseSwaggerUI3(); }
웹 서비스의 /swagger
엔드포인트로 이동하면 API를 찾아볼 수 있습니다. 그런 다음 OpenAPI 정의를 다른 서비스(예: Azure API Management)로 가져올 수 있습니다. NSwag 구성에 대한 자세한 내용은 NSwag 및 ASP.NET Core시작하기를 참조하세요.
Swashbuckle
Swashbuckle 통합에 대한 기본 지침을 따르고 다음과 같이 수정합니다.
프로젝트에 패키지를 추가하여 Swashbuckle을 지원합니다. 다음 패키지가 필요합니다.
- Swashbuckle.AspNetCore.
- Swashbuckle.AspNetCore.Newtonsoft.
- Microsoft.AspNetCore.Datasync.Swashbuckle
.
Program.cs
파일에 OpenAPI 정의를 생성하는 서비스를 추가합니다.builder.Services.AddSwaggerGen(options => { options.AddDatasyncControllers(); }); builder.Services.AddSwaggerGenNewtonsoftSupport();
메모
AddDatasyncControllers()
메서드는 테이블 컨트롤러를 포함하는 어셈블리에 해당하는 선택적Assembly
사용합니다.Assembly
매개 변수는 테이블 컨트롤러가 서비스와 다른 프로젝트에 있는 경우에만 필요합니다.Program.cs
생성된 JSON 문서 및 Swagger UI를 제공하기 위해 미들웨어를 사용하도록 설정합니다.if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); options.RoutePrefix = string.Empty; }); }
이 구성을 사용하면 웹 서비스의 루트로 이동하여 API를 찾아볼 수 있습니다. 그런 다음 OpenAPI 정의를 다른 서비스(예: Azure API Management)로 가져올 수 있습니다. Swashbuckle 구성에 대한 자세한 내용은 Swashbuckle 및 ASP.NET Core시작하기를 참조하세요.
제한
서비스 라이브러리의 ASP.NET Core 버전은 목록 작업에 OData v4를 구현합니다. 서버가 이전 버전과의 호환 모드에서 실행되는 경우 부분 문자열에 대한 필터링은 지원되지 않습니다.