모델 바인딩을 사용하면 컨트롤러 작업이 HTTP 요청이 아닌 모델 형식(메서드 인수로 전달됨)에서 직접 작업할 수 있습니다. 들어오는 요청 데이터와 애플리케이션 모델 간의 매핑은 모델 바인더에서 처리됩니다. 개발자는 사용자 지정 모델 바인더를 구현하여 기본 제공 모델 바인딩 기능을 확장할 수 있습니다(일반적으로 고유한 공급자를 작성할 필요는 없지만).
기본 모델 바인더 제한 사항
기본 모델 바인더는 대부분의 일반적인 .NET Core 데이터 형식을 지원하며 대부분의 개발자 요구 사항을 충족해야 합니다. 요청의 텍스트 기반 입력을 모델 형식에 직접 바인딩해야 합니다. 바인딩하기 전에 입력을 변환해야 할 수 있습니다. 예를 들어 모델 데이터를 조회하는 데 사용할 수 있는 키가 있는 경우입니다. 사용자 지정 모델 바인더를 사용하여 키를 기반으로 데이터를 가져올 수 있습니다.
단순 및 복합 형식 모델 바인딩
모델 바인딩은 작업을 수행하는 형식에 대한 특정 정의를 사용합니다.
단순 형식은 TypeConverter 또는 TryParse
메서드를 사용하여 단일 문자열에서 변환됩니다.
복합 형식은 여러 입력 값에서 변환됩니다. 프레임워크는 TypeConverter
또는 TryParse
존재 여부에 따라 차이를 결정합니다. 형식 변환기를 만들거나 외부 리소스 또는 여러 입력이 필요하지 않은 TryParse
을 string
로 변환하는 데 SomeType
를 사용하는 것을 권장합니다.
모델 바인더가 문자열에서 변환할 수 있는 형식 목록은 단순 형식을 참조하세요.
사용자 지정 모델 바인더를 만들기 전에 기존 모델 바인더가 구현되는 방식을 검토할 가치가 있습니다. ByteArrayModelBinder base64로 인코딩된 문자열을 바이트 배열로 변환하는 데 사용할 수 있는 것을 고려합니다. 바이트 배열은 종종 파일 또는 데이터베이스 BLOB 필드로 저장됩니다.
ByteArrayModelBinder와 함께 작업하기
Base64로 인코딩된 문자열을 사용하여 이진 데이터를 나타낼 수 있습니다. 예를 들어 이미지를 문자열로 인코딩할 수 있습니다. 샘플은 Base64String.txtbase64로 인코딩된 문자열로 이미지를 포함합니다.
ASP.NET Core MVC는 base64로 인코딩된 문자열을 ByteArrayModelBinder
사용하여 바이트 배열로 변환할 수 있습니다.
ByteArrayModelBinderProvider는 byte[]
인수를 ByteArrayModelBinder
로 매핑합니다.
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(byte[]))
{
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return new ByteArrayModelBinder(loggerFactory);
}
return null;
}
사용자 고유의 사용자 지정 모델 바인더를 만들 때, 고유한 IModelBinderProvider
형식을 구현하거나 ModelBinderAttribute을 사용할 수 있습니다.
다음 예제에서는 base64로 인코딩된 문자열을 a byte[]
로 변환하고 결과를 파일에 저장하는 방법을 ByteArrayModelBinder
보여줍니다.
[HttpPost]
public void Post([FromForm] byte[] file, string filename)
{
// Don't trust the file name sent by the client. Use
// Path.GetRandomFileName to generate a safe random
// file name. _targetFilePath receives a value
// from configuration (the appsettings.json file in
// the sample app).
var trustedFileName = Path.GetRandomFileName();
var filePath = Path.Combine(_targetFilePath, trustedFileName);
if (System.IO.File.Exists(filePath))
{
return;
}
System.IO.File.WriteAllBytes(filePath, file);
}
영어 이외의 언어로 번역된 코드 주석을 보려면 이 GitHub 토론 이슈에서 알려주세요.
curl과 같은 도구를 사용하여 base64로 인코딩된 문자열을 이전 api 메서드에 게시할 수 있습니다.
바인더가 요청 데이터를 적절하게 명명된 속성 또는 인수에 바인딩할 수 있는 한 모델 바인딩은 성공합니다. 다음 예제에서는 뷰 모델에서 사용하는 ByteArrayModelBinder
방법을 보여 있습니다.
[HttpPost("Profile")]
public void SaveProfile([FromForm] ProfileViewModel model)
{
// Don't trust the file name sent by the client. Use
// Path.GetRandomFileName to generate a safe random
// file name. _targetFilePath receives a value
// from configuration (the appsettings.json file in
// the sample app).
var trustedFileName = Path.GetRandomFileName();
var filePath = Path.Combine(_targetFilePath, trustedFileName);
if (System.IO.File.Exists(filePath))
{
return;
}
System.IO.File.WriteAllBytes(filePath, model.File);
}
public class ProfileViewModel
{
public byte[] File { get; set; }
public string FileName { get; set; }
}
사용자 지정 모델 바인더 샘플
이 섹션에서는 다음과 같은 사용자 지정 모델 바인더를 구현합니다.
- 들어오는 요청 데이터를 강력한 형식의 키 인수로 변환합니다.
- Entity Framework Core를 사용하여 연결된 엔터티를 가져옵니다.
- 연결된 엔터티를 작업 메서드에 인수로 전달합니다.
다음 샘플은 ModelBinder
속성을 Author
모델에 사용합니다.
using CustomModelBindingSample.Binders;
using Microsoft.AspNetCore.Mvc;
namespace CustomModelBindingSample.Data
{
[ModelBinder(BinderType = typeof(AuthorEntityBinder))]
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public string GitHub { get; set; }
public string Twitter { get; set; }
public string BlogUrl { get; set; }
}
}
이전 코드에서 ModelBinder
특성은 Author
작업 매개 변수를 바인딩하는 데 사용해야 하는 형식인 IModelBinder
을 지정합니다.
다음 AuthorEntityBinder
클래스는 Entity Framework Core와 authorId
를 사용하여 데이터 원본에서 엔터티를 페치함으로써 Author
매개 변수를 바인딩합니다.
public class AuthorEntityBinder : IModelBinder
{
private readonly AuthorContext _context;
public AuthorEntityBinder(AuthorContext context)
{
_context = context;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
// Try to fetch the value of the argument by name
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
if (!int.TryParse(value, out var id))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
modelName, "Author Id must be an integer.");
return Task.CompletedTask;
}
// Model will be null if not found, including for
// out of range id values (0, -3, etc.)
var model = _context.Authors.Find(id);
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
비고
앞의 AuthorEntityBinder
클래스는 사용자 지정 모델 바인더를 설명하기 위한 것입니다. 클래스는 조회 시나리오에 대한 모범 사례를 설명하기 위한 것이 아닙니다. 조회의 경우 작업 메서드에서 데이터베이스를 authorId
바인딩하고 쿼리합니다. 이 접근 방식은 모델 바인딩 오류를 NotFound
의 경우와 구분합니다.
다음 코드는 작업 메서드에서 사용하는 AuthorEntityBinder
방법을 보여 줍니다.
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
ModelBinder
특성은 기본 규칙을 사용하지 않는 매개 변수에 AuthorEntityBinder
를 적용하는 데 사용할 수 있습니다.
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
이 예제에서는 인수 이름이 기본값 authorId
이 아니므로 특성을 사용하여 ModelBinder
매개 변수에 지정됩니다. 컨트롤러와 작업 메서드는 모두 작업 메서드에서 엔터티를 조회하는 것과 비교하여 간소화됩니다. 작성자를 가져오는 Entity Framework Core의 논리가 모델 바인더로 이동되었습니다. 모델에 바인딩 Author
하는 여러 메서드가 있는 경우 이는 상당히 간소화될 수 있습니다.
특성은 ModelBinder
개별 모델 속성(예: viewmodel) 또는 작업 메서드 매개 변수에 적용하여 해당 형식 또는 작업에 대한 특정 모델 바인더 또는 모델 이름을 지정할 수 있습니다.
ModelBinderProvider 구현
특성을 적용하는 대신 구현할 수 있습니다 IModelBinderProvider
. 기본 제공 프레임워크 바인더를 구현하는 방법입니다. 바인더가 동작하는 형식을 지정할 때, 바인더가 수용하는 입력이 아니라 생성된 인수의 형식을 지정합니다. 다음 바인더 공급자는 AuthorEntityBinder
와 함께 작동합니다. MVC의 공급자 컬렉션에 추가되면, ModelBinder
또는 Author
형식의 매개 변수에 Author
특성을 사용할 필요가 없습니다.
using CustomModelBindingSample.Data;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;
namespace CustomModelBindingSample.Binders
{
public class AuthorEntityBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(Author))
{
return new BinderTypeModelBinder(typeof(AuthorEntityBinder));
}
return null;
}
}
}
참고: 앞의 코드는
BinderTypeModelBinder
를 반환합니다.BinderTypeModelBinder
는 모델 바인더의 팩터리 역할을 하며 DI(종속성 주입)를 제공합니다. DI는AuthorEntityBinder
을 통해 EF Core에 액세스하는 데 필요합니다. 모델 바인더에 DI의 서비스가 필요한 경우 사용합니다BinderTypeModelBinder
.
사용자 지정 모델 바인더 공급자를 사용하려면 ConfigureServices
에 추가하십시오.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AuthorContext>(options => options.UseInMemoryDatabase("Authors"));
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
});
}
모델 바인더를 평가할 때 공급자 컬렉션은 순서대로 검사됩니다. 입력 모델과 일치하는 바인더를 반환하는 첫 번째 공급자가 사용됩니다. 따라서 컬렉션의 끝에 공급자를 추가하면 사용자 지정 바인더가 실행되기 전에 기본 제공 모델 바인더가 호출될 수 있습니다. 이 예제에서는 사용자 지정 공급자가 항상 작업 인수에 사용 Author
되도록 컬렉션의 시작 부분에 추가됩니다.
다형 모델 바인딩
파생 형식의 여러 모델에 대한 바인딩을 다형 모델 바인딩이라고 합니다. 요청 값을 특정 파생 모델 형식에 바인딩해야 하는 경우 다형 사용자 지정 모델 바인딩이 필요합니다. 다형 모델 바인딩:
- 모든 언어와 상호 운용하도록 설계된 API에서는 일반적이지 않습니다.
- 바인딩된 모델에 대해 추론하기 어렵게 만듭니다.
그러나 앱에 다형 모델 바인딩이 필요한 경우 구현은 다음 코드와 같이 표시될 수 있습니다.
public abstract class Device
{
public string Kind { get; set; }
}
public class Laptop : Device
{
public string CPUIndex { get; set; }
}
public class SmartPhone : Device
{
public string ScreenSize { get; set; }
}
public class DeviceModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Device))
{
return null;
}
var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new DeviceModelBinder(binders);
}
}
public class DeviceModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Device.Kind));
var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (modelTypeValue == "Laptop")
{
(modelMetadata, modelBinder) = binders[typeof(Laptop)];
}
else if (modelTypeValue == "SmartPhone")
{
(modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
권장 사항 및 모범 사례
사용자 지정 모델 바인더:
- 상태 코드를 설정하거나 결과를 반환하지 않아야 합니다(예: 404 찾을 수 없음). 모델 바인딩이 실패하면 작업 메서드 자체 내의 작업 필터 또는 논리가 오류를 처리해야 합니다.
- 반복 코드를 제거하고 작업 메서드에서 교차 절단 문제를 제거하는 데 가장 유용합니다.
- 일반적으로 문자열을 사용자 지정 형식으로 변환하는 데 사용하면 안 되며, TypeConverter 일반적으로 더 나은 옵션입니다.
작성자 Steve Smith
모델 바인딩을 사용하면 컨트롤러 작업이 HTTP 요청이 아닌 모델 형식(메서드 인수로 전달됨)에서 직접 작업할 수 있습니다. 들어오는 요청 데이터와 애플리케이션 모델 간의 매핑은 모델 바인더에서 처리됩니다. 개발자는 사용자 지정 모델 바인더를 구현하여 기본 제공 모델 바인딩 기능을 확장할 수 있습니다(일반적으로 고유한 공급자를 작성할 필요는 없지만).
기본 모델 바인더 제한 사항
기본 모델 바인더는 대부분의 일반적인 .NET Core 데이터 형식을 지원하며 대부분의 개발자 요구 사항을 충족해야 합니다. 요청의 텍스트 기반 입력을 모델 형식에 직접 바인딩해야 합니다. 바인딩하기 전에 입력을 변환해야 할 수 있습니다. 예를 들어 모델 데이터를 조회하는 데 사용할 수 있는 키가 있는 경우입니다. 사용자 지정 모델 바인더를 사용하여 키를 기반으로 데이터를 가져올 수 있습니다.
모델 바인딩 검토
모델 바인딩은 작업을 수행하는 형식에 대한 특정 정의를 사용합니다.
단순 형식은 입력의 단일 문자열에서 변환됩니다.
복합 형식은 여러 입력 값에서 변환됩니다. 프레임워크는 .의 TypeConverter
존재 여부에 따라 차이를 결정합니다. 외부 리소스가 필요하지 않은 간단한 string
>SomeType
매핑이 있는 경우 형식 변환기를 만드는 것이 좋습니다.
사용자 지정 모델 바인더를 만들기 전에 기존 모델 바인더가 구현되는 방식을 검토할 가치가 있습니다. ByteArrayModelBinder base64로 인코딩된 문자열을 바이트 배열로 변환하는 데 사용할 수 있는 것을 고려합니다. 바이트 배열은 종종 파일 또는 데이터베이스 BLOB 필드로 저장됩니다.
ByteArrayModelBinder을 다루기
Base64로 인코딩된 문자열을 사용하여 이진 데이터를 나타낼 수 있습니다. 예를 들어 이미지를 문자열로 인코딩할 수 있습니다. 샘플은 Base64String.txtbase64로 인코딩된 문자열로 이미지를 포함합니다.
ASP.NET Core MVC는 base64로 인코딩된 문자열을 ByteArrayModelBinder
사용하여 바이트 배열로 변환할 수 있습니다.
ByteArrayModelBinderProvider은 byte[]
인수를 ByteArrayModelBinder
으로 매핑합니다.
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(byte[]))
{
return new ByteArrayModelBinder();
}
return null;
}
사용자 정의 모델 바인더를 만들 때 고유의 IModelBinderProvider
형식을 구현하거나 ModelBinderAttribute을 사용할 수 있습니다.
다음 예제에서는 base64로 인코딩된 문자열을 a byte[]
로 변환하고 결과를 파일에 저장하는 방법을 ByteArrayModelBinder
보여줍니다.
[HttpPost]
public void Post([FromForm] byte[] file, string filename)
{
// Don't trust the file name sent by the client. Use
// Path.GetRandomFileName to generate a safe random
// file name. _targetFilePath receives a value
// from configuration (the appsettings.json file in
// the sample app).
var trustedFileName = Path.GetRandomFileName();
var filePath = Path.Combine(_targetFilePath, trustedFileName);
if (System.IO.File.Exists(filePath))
{
return;
}
System.IO.File.WriteAllBytes(filePath, file);
}
curl과 같은 도구를 사용하여 base64로 인코딩된 문자열을 이전 api 메서드에 게시할 수 있습니다.
바인더가 요청 데이터를 적절하게 명명된 속성 또는 인수에 바인딩할 수 있는 한 모델 바인딩은 성공합니다. 다음 예제에서는 뷰 모델에서 사용하는 ByteArrayModelBinder
방법을 보여 있습니다.
[HttpPost("Profile")]
public void SaveProfile([FromForm] ProfileViewModel model)
{
// Don't trust the file name sent by the client. Use
// Path.GetRandomFileName to generate a safe random
// file name. _targetFilePath receives a value
// from configuration (the appsettings.json file in
// the sample app).
var trustedFileName = Path.GetRandomFileName();
var filePath = Path.Combine(_targetFilePath, trustedFileName);
if (System.IO.File.Exists(filePath))
{
return;
}
System.IO.File.WriteAllBytes(filePath, model.File);
}
public class ProfileViewModel
{
public byte[] File { get; set; }
public string FileName { get; set; }
}
사용자 지정 모델 바인더 샘플
이 섹션에서는 다음과 같은 사용자 지정 모델 바인더를 구현합니다.
- 들어오는 요청 데이터를 강력한 형식의 키 인수로 변환합니다.
- Entity Framework Core를 사용하여 연결된 엔터티를 가져옵니다.
- 연결된 엔터티를 작업 메서드에 인수로 전달합니다.
다음 샘플에서는 ModelBinder
특성을 Author
모델에 사용합니다.
using CustomModelBindingSample.Binders;
using Microsoft.AspNetCore.Mvc;
namespace CustomModelBindingSample.Data
{
[ModelBinder(BinderType = typeof(AuthorEntityBinder))]
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public string GitHub { get; set; }
public string Twitter { get; set; }
public string BlogUrl { get; set; }
}
}
이전 코드에서 ModelBinder
속성은 Author
작업 매개 변수를 바인딩하는 데 사용해야 하는 IModelBinder
의 유형을 지정합니다.
다음 AuthorEntityBinder
클래스는 Entity Framework Core와 authorId
를 사용하여 데이터 원본에서 엔터티를 가져와 Author
매개 변수를 바인딩합니다.
public class AuthorEntityBinder : IModelBinder
{
private readonly AppDbContext _db;
public AuthorEntityBinder(AppDbContext db)
{
_db = db;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var modelName = bindingContext.ModelName;
// Try to fetch the value of the argument by name
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
if (!int.TryParse(value, out var id))
{
// Non-integer arguments result in model state errors
bindingContext.ModelState.TryAddModelError(
modelName, "Author Id must be an integer.");
return Task.CompletedTask;
}
// Model will be null if not found, including for
// out of range id values (0, -3, etc.)
var model = _db.Authors.Find(id);
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
비고
앞의 AuthorEntityBinder
클래스는 사용자 지정 모델 바인더를 설명하기 위한 것입니다. 클래스는 조회 시나리오에 대한 모범 사례를 설명하기 위한 것이 아닙니다. 조회의 경우 작업 메서드에서 데이터베이스를 authorId
바인딩하고 쿼리합니다. 이 접근 방식은 모델 바인딩 실패를 NotFound
경우와 구분합니다.
다음 코드는 작업 메서드에서 사용하는 AuthorEntityBinder
방법을 보여 줍니다.
[HttpGet("get/{author}")]
public IActionResult Get(Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
기본 규칙을 사용하지 않는 매개 변수에 ModelBinder
특성을 적용하여 AuthorEntityBinder
을 사용할 수 있습니다.
[HttpGet("{id}")]
public IActionResult GetById([ModelBinder(Name = "id")] Author author)
{
if (author == null)
{
return NotFound();
}
return Ok(author);
}
이 예제에서는 인수 이름이 기본값 authorId
이 아니므로 특성을 사용하여 ModelBinder
매개 변수에 지정됩니다. 컨트롤러와 작업 메서드는 모두 작업 메서드에서 엔터티를 조회하는 것과 비교하여 간소화됩니다. 작성자를 가져오는 로직을 Entity Framework Core를 통해 모델 바인더로 이동시켰습니다. 모델에 바인딩 Author
하는 여러 메서드가 있는 경우 이는 상당히 간소화될 수 있습니다.
특성은 ModelBinder
개별 모델 속성(예: viewmodel) 또는 작업 메서드 매개 변수에 적용하여 해당 형식 또는 작업에 대한 특정 모델 바인더 또는 모델 이름을 지정할 수 있습니다.
ModelBinderProvider 구현
특성을 적용하는 대신 구현할 수 있습니다 IModelBinderProvider
. 기본 제공 프레임워크 바인더를 구현하는 방법입니다. 바인더가 운영되는 형식을 지정한다는 것은 바인더로 생성되는 인수의 형식을 지정하는 것이며, 바인더가 받아들이는 입력의 형식을 지정하는 것이 아닙니다. 다음 바인더 공급자는 AuthorEntityBinder
와 함께 작동합니다. MVC의 공급자 컬렉션에 추가되면, Author
또는 Author
형식의 매개 변수에 ModelBinder
속성을 사용할 필요가 없습니다.
using CustomModelBindingSample.Data;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;
namespace CustomModelBindingSample.Binders
{
public class AuthorEntityBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(Author))
{
return new BinderTypeModelBinder(typeof(AuthorEntityBinder));
}
return null;
}
}
}
참고: 앞의 코드는
BinderTypeModelBinder
를 반환합니다.BinderTypeModelBinder
는 모델 바인더의 팩터리 역할을 하며 DI(종속성 주입)를 제공합니다.AuthorEntityBinder
는 EF Core에 액세스하기 위해 DI가 필요합니다. 모델 바인더에 DI의 서비스가 필요한 경우 사용합니다BinderTypeModelBinder
.
사용자 지정 모델 바인더 공급자를 사용하려면 ConfigureServices
에 추가하십시오.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase("App"));
services.AddMvc(options =>
{
// add custom binder to beginning of collection
options.ModelBinderProviders.Insert(0, new AuthorEntityBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
모델 바인더를 평가할 때 공급자 컬렉션은 순서대로 검사됩니다. 바인더를 반환하는 첫 번째 공급자가 사용됩니다. 컬렉션의 끝에 공급자를 추가하면 사용자 지정 바인더가 실행되기 전에 기본 제공 모델 바인더가 호출될 수 있습니다. 이 예제에서는 사용자 지정 공급자가 동작 인수에 사용되는 Author
지 확인하기 위해 컬렉션의 시작 부분에 추가됩니다.
다형 모델 바인딩
파생 형식의 여러 모델에 대한 바인딩을 다형 모델 바인딩이라고 합니다. 요청 값을 특정 파생 모델 형식에 바인딩해야 하는 경우 다형 사용자 지정 모델 바인딩이 필요합니다. 다형 모델 바인딩:
- 모든 언어와 상호 운용하도록 설계된 API에서는 일반적이지 않습니다 REST.
- 바인딩된 모델에 대해 추론하기 어렵게 만듭니다.
그러나 앱에 다형 모델 바인딩이 필요한 경우 구현은 다음 코드와 같이 표시될 수 있습니다.
public abstract class Device
{
public string Kind { get; set; }
}
public class Laptop : Device
{
public string CPUIndex { get; set; }
}
public class SmartPhone : Device
{
public string ScreenSize { get; set; }
}
public class DeviceModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(Device))
{
return null;
}
var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new DeviceModelBinder(binders);
}
}
public class DeviceModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
this.binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Device.Kind));
var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;
IModelBinder modelBinder;
ModelMetadata modelMetadata;
if (modelTypeValue == "Laptop")
{
(modelMetadata, modelBinder) = binders[typeof(Laptop)];
}
else if (modelTypeValue == "SmartPhone")
{
(modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result.Model] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
}
권장 사항 및 모범 사례
사용자 지정 모델 바인더:
- 상태 코드를 설정하거나 결과를 반환하지 않아야 합니다(예: 404 찾을 수 없음). 모델 바인딩이 실패하면 작업 메서드 자체 내의 작업 필터 또는 논리가 오류를 처리해야 합니다.
- 반복 코드를 제거하고 작업 메서드에서 교차 절단 문제를 제거하는 데 가장 유용합니다.
- 일반적으로 문자열을 사용자 지정 형식으로 변환하는 데 사용하면 안 되며, TypeConverter 일반적으로 더 나은 옵션입니다.
ASP.NET Core