英文:
Best way to change field value before it reaches the webapi controller
问题
[ApiController]
Route("v1/[controller]/[action]")]
public class AccountController : Controller
{
private readonly IClient _client;
public AccountController(IClient client)
{
_client = client;
}
[HttpPost]
public async Task<IActionResult> SomeAction([FromBody] SomeRequestModel request)
{
if (_client.SomeId == null) return BadRequest("Something could not be resolved");
request.SomeId = _client.SomeId.Value;
// Do further processing
}
}
SomeRequestModel.cs
public class SomeRequestModel
{
public long SomeId;
// other fields
}
有大约100个不同的操作,每个操作都有大约100个不同的请求模型。但是这些模型不继承自具有 SomeId
字段的单个类。由于它们来自另一个项目,更改请求模型是不可行的。
我需要检查 SomeId
是否已解析并返回BadRequest或设置该字段。但我不想在每个请求上都写这两行代码。如何处理这种情况的最佳方法?或者只写这两行是否可以?
我考虑过使用 System.Web.Http.AuthorizeAttribute
并解析 JSON 体并在那里更新值,但这似乎非常低效。
<details>
<summary>英文:</summary>
```csharp
[ApiController]
Route("v1/[controller]/[action]")]
public class AccountController : Controller
{
private readonly IClient _client;
public AccountController(IClient client,)
{
_client = client;
}
[HttpPost]
public async Task<IActionResult> SomeAction([FromBody] SomeRequestModel request)
{
if (_client.SomeId == null) return BadRequest("Something could not be resolved");
request.SomeId = _client.SomeId.Value;
// Do further processing
}
}
SomeRequestModel.cs
public class SomeRequestModel
{
public long SomeId;
// other fields
}
There are about 100 different actions that take in about 100 different request models. But those models don't inherit from a single class that has SomeId
field. Changing the requests is not feasible since they are from another project.
I need to check if SomeId
is resolved and return BadRequest or set the field. But I dont wnat to write these 2 lines on every request. What would be the best way of handling this. Or just writing the lines okay.
I thought of using System.Web.Http.AuthorizeAttribute
and parsing the body as json and updating the value there but that seems extremely inefficient.
答案1
得分: 1
以下是翻译好的代码部分:
这是一个示例
[ApiController]
[Route("v1/[controller]/[action]")]
public class AccountController : Controller
{
public AccountController()
{
}
[HttpPost]
public async Task<IActionResult> SomeAction([FromBody] SomeRequestModel request)
{
return Ok(request.SomeId);
}
}
[ModelBinder(BinderType = typeof(SomeRequestModelBinder))]
public class SomeRequestModel
{
public long SomeId { get; set; }
// other fields
}
public class SomeRequestModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
var json = await reader.ReadToEndAsync();
var model = JsonSerializer.Deserialize<SomeRequestModel>(json);
model.SomeId = model.SomeId + 100;
bindingContext.Result = ModelBindingResult.Success(model);
}
}
}
更新 1:
Peter注意到你需要更新100个模型。也可以通过实现IModelBinderProvider来实现。以下是更新后的版本。
[ApiController]
[Route("v1/[controller]/[action]")]
public class AccountController : Controller
{
public AccountController()
{
}
[HttpPost]
public async Task<IActionResult> SomeAction([FromBody] SomeRequestModel request)
{
return Ok(request.SomeId);
}
}
public interface IHaveSomeId
{
long SomeId { get; set; }
}
public class SomeRequestModel : IHaveSomeId
{
public long SomeId { get; set; }
// other fields
}
public class MyModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
var json = await reader.ReadToEndAsync();
var model = JsonSerializer.Deserialize(json, bindingContext.ModelType);
if (model is IHaveSomeId someIdModel)
someIdModel.SomeId = someIdModel.SomeId + 100;
bindingContext.Result = ModelBindingResult.Success(model);
}
}
}
public class MyModelBinderProvider : IModelBinderProvider
{
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
return new MyModelBinder();
}
}
更新 2:
如果没有一个所有模型都可以实现的接口,可以使用反射或表达式树来修改值。以下示例使用反射。
[ApiController]
[Route("v1/[controller]/[action]")]
public class AccountController : Controller
{
public AccountController()
{
}
[HttpPost]
public async Task<IActionResult> SomeAction([FromBody] SomeRequestModel request)
{
return Ok(request.SomeId);
}
}
public class SomeRequestModel
{
public long SomeId { get; set; }
// other fields
}
public class MyModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
var json = await reader.ReadToEndAsync();
var doc = JsonSerializer.Deserialize<JsonDocument>(json);
if (!doc.RootElement.TryGetProperty("SomeId", out JsonElement someIdElement))
throw new BadHttpRequestException("No SomeId in the request.");
var model = doc.Deserialize(bindingContext.ModelType);
TryToUpdateSomeId(model);
bindingContext.Result = ModelBindingResult.Success(model);
}
}
private void TryToUpdateSomeId(object? model)
{
if (model == null) return;
var propInfo = model.GetType().GetProperty("SomeId");
if (propInfo == null)
return;
var getMethod = propInfo.GetGetMethod();
var setMethod = propInfo.GetSetMethod();
if (getMethod == null || setMethod == null)
return;
var currentValue = (long)getMethod.Invoke(model, null);
setMethod?.Invoke(model, new object[] { currentValue + 100 });
}
}
public class MyModelBinderProvider : IModelBinderProvider
{
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
return new MyModelBinder();
}
}
不要忘记注册提供程序。
builder.Services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
});
英文:
It is quite easy. You need to apply a model binder to SomeRequestModel. Here is an example
[ApiController]
[Route("v1/[controller]/[action]")]
public class AccountController : Controller
{
public AccountController()
{
}
[HttpPost]
public async Task<IActionResult> SomeAction([FromBody] SomeRequestModel request)
{
return Ok(request.SomeId);
}
}
[ModelBinder(BinderType = typeof(SomeRequestModelBinder))]
public class SomeRequestModel
{
public long SomeId { get; set; }
// other fields
}
public class SomeRequestModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
var json = await reader.ReadToEndAsync();
var model = JsonSerializer.Deserialize<SomeRequestModel>(json);
model.SomeId = model.SomeId + 100;
bindingContext.Result = ModelBindingResult.Success(model);
}
}
}
UPDATE 1:
Peter paid attention that you need to update 100 models. It is also can be achieved by implementing IModelBinderProvider. Here is an updated version.
[ApiController]
[Route("v1/[controller]/[action]")]
public class AccountController : Controller
{
public AccountController()
{
}
[HttpPost]
public async Task<IActionResult> SomeAction([FromBody] SomeRequestModel request)
{
return Ok(request.SomeId);
}
}
public interface IHaveSomeId
{
long SomeId { get; set; }
}
public class SomeRequestModel : IHaveSomeId
{
public long SomeId { get; set; }
// other fields
}
public class MyModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
var json = await reader.ReadToEndAsync();
var model = JsonSerializer.Deserialize(json, bindingContext.ModelType);
if (model is IHaveSomeId someIdModel)
someIdModel.SomeId = someIdModel.SomeId + 100;
bindingContext.Result = ModelBindingResult.Success(model);
}
}
}
public class MyModelBinderProvider : IModelBinderProvider
{
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
return new MyModelBinder();
}
}
UPDATE 2:
If you do not have an interface that all models can implement, the value can be modified with reflection or expression trees. The example below uses reflection.
[ApiController]
[Route("v1/[controller]/[action]")]
public class AccountController : Controller
{
public AccountController()
{
}
[HttpPost]
public async Task<IActionResult> SomeAction([FromBody] SomeRequestModel request)
{
return Ok(request.SomeId);
}
}
public class SomeRequestModel
{
public long SomeId { get; set; }
// other fields
}
public class MyModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
var json = await reader.ReadToEndAsync();
var doc = JsonSerializer.Deserialize<JsonDocument>(json);
if (!doc.RootElement.TryGetProperty("SomeId", out JsonElement someIdElement))
throw new BadHttpRequestException("No SomeId in the request.");
var model = doc.Deserialize(bindingContext.ModelType);
TryToUpdateSomeId(model);
bindingContext.Result = ModelBindingResult.Success(model);
}
}
private void TryToUpdateSomeId(object? model)
{
if (model == null) return;
var propInfo = model.GetType().GetProperty("SomeId");
if (propInfo == null)
return;
var getMethod = propInfo.GetGetMethod();
var setMethod = propInfo.GetSetMethod();
if (getMethod == null || setMethod == null)
return;
var currentValue = (long)getMethod.Invoke(model, null);
setMethod?.Invoke(model, new object[] { currentValue + 100 });
}
}
public class MyModelBinderProvider : IModelBinderProvider
{
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
return new MyModelBinder();
}
}
Do not forget to register the provider.
builder.Services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
});
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论