更改字段值以在达到Web API 控制器之前的最佳方法

huangapple go评论94阅读模式
英文:

Best way to change field value before it reaches the webapi controller

问题

  1. [ApiController]
  2. Route("v1/[controller]/[action]")]
  3. public class AccountController : Controller
  4. {
  5. private readonly IClient _client;
  6. public AccountController(IClient client)
  7. {
  8. _client = client;
  9. }
  10. [HttpPost]
  11. public async Task<IActionResult> SomeAction([FromBody] SomeRequestModel request)
  12. {
  13. if (_client.SomeId == null) return BadRequest("Something could not be resolved");
  14. request.SomeId = _client.SomeId.Value;
  15. // Do further processing
  16. }
  17. }

SomeRequestModel.cs

  1. public class SomeRequestModel
  2. {
  3. public long SomeId;
  4. // other fields
  5. }

有大约100个不同的操作,每个操作都有大约100个不同的请求模型。但是这些模型不继承自具有 SomeId 字段的单个类。由于它们来自另一个项目,更改请求模型是不可行的。

我需要检查 SomeId 是否已解析并返回BadRequest或设置该字段。但我不想在每个请求上都写这两行代码。如何处理这种情况的最佳方法?或者只写这两行是否可以?

我考虑过使用 System.Web.Http.AuthorizeAttribute 并解析 JSON 体并在那里更新值,但这似乎非常低效。

  1. <details>
  2. <summary>英文:</summary>
  3. ```csharp
  4. [ApiController]
  5. Route(&quot;v1/[controller]/[action]&quot;)]
  6. public class AccountController : Controller
  7. {
  8. private readonly IClient _client;
  9. public AccountController(IClient client,)
  10. {
  11. _client = client;
  12. }
  13. [HttpPost]
  14. public async Task&lt;IActionResult&gt; SomeAction([FromBody] SomeRequestModel request)
  15. {
  16. if (_client.SomeId == null) return BadRequest(&quot;Something could not be resolved&quot;);
  17. request.SomeId = _client.SomeId.Value;
  18. // Do further processing
  19. }
  20. }

SomeRequestModel.cs

  1. public class SomeRequestModel
  2. {
  3. public long SomeId;
  4. // other fields
  5. }

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

以下是翻译好的代码部分:

  1. 这是一个示例
  2. [ApiController]
  3. [Route("v1/[controller]/[action]")]
  4. public class AccountController : Controller
  5. {
  6. public AccountController()
  7. {
  8. }
  9. [HttpPost]
  10. public async Task<IActionResult> SomeAction([FromBody] SomeRequestModel request)
  11. {
  12. return Ok(request.SomeId);
  13. }
  14. }
  15. [ModelBinder(BinderType = typeof(SomeRequestModelBinder))]
  16. public class SomeRequestModel
  17. {
  18. public long SomeId { get; set; }
  19. // other fields
  20. }
  21. public class SomeRequestModelBinder : IModelBinder
  22. {
  23. public async Task BindModelAsync(ModelBindingContext bindingContext)
  24. {
  25. using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
  26. {
  27. var json = await reader.ReadToEndAsync();
  28. var model = JsonSerializer.Deserialize<SomeRequestModel>(json);
  29. model.SomeId = model.SomeId + 100;
  30. bindingContext.Result = ModelBindingResult.Success(model);
  31. }
  32. }
  33. }
  34. 更新 1:
  35. Peter注意到你需要更新100个模型。也可以通过实现IModelBinderProvider来实现。以下是更新后的版本。
  36. [ApiController]
  37. [Route("v1/[controller]/[action]")]
  38. public class AccountController : Controller
  39. {
  40. public AccountController()
  41. {
  42. }
  43. [HttpPost]
  44. public async Task<IActionResult> SomeAction([FromBody] SomeRequestModel request)
  45. {
  46. return Ok(request.SomeId);
  47. }
  48. }
  49. public interface IHaveSomeId
  50. {
  51. long SomeId { get; set; }
  52. }
  53. public class SomeRequestModel : IHaveSomeId
  54. {
  55. public long SomeId { get; set; }
  56. // other fields
  57. }
  58. public class MyModelBinder : IModelBinder
  59. {
  60. public async Task BindModelAsync(ModelBindingContext bindingContext)
  61. {
  62. using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
  63. {
  64. var json = await reader.ReadToEndAsync();
  65. var model = JsonSerializer.Deserialize(json, bindingContext.ModelType);
  66. if (model is IHaveSomeId someIdModel)
  67. someIdModel.SomeId = someIdModel.SomeId + 100;
  68. bindingContext.Result = ModelBindingResult.Success(model);
  69. }
  70. }
  71. }
  72. public class MyModelBinderProvider : IModelBinderProvider
  73. {
  74. public IModelBinder? GetBinder(ModelBinderProviderContext context)
  75. {
  76. return new MyModelBinder();
  77. }
  78. }
  79. 更新 2:
  80. 如果没有一个所有模型都可以实现的接口,可以使用反射或表达式树来修改值。以下示例使用反射。
  81. [ApiController]
  82. [Route("v1/[controller]/[action]")]
  83. public class AccountController : Controller
  84. {
  85. public AccountController()
  86. {
  87. }
  88. [HttpPost]
  89. public async Task<IActionResult> SomeAction([FromBody] SomeRequestModel request)
  90. {
  91. return Ok(request.SomeId);
  92. }
  93. }
  94. public class SomeRequestModel
  95. {
  96. public long SomeId { get; set; }
  97. // other fields
  98. }
  99. public class MyModelBinder : IModelBinder
  100. {
  101. public async Task BindModelAsync(ModelBindingContext bindingContext)
  102. {
  103. using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
  104. {
  105. var json = await reader.ReadToEndAsync();
  106. var doc = JsonSerializer.Deserialize<JsonDocument>(json);
  107. if (!doc.RootElement.TryGetProperty("SomeId", out JsonElement someIdElement))
  108. throw new BadHttpRequestException("No SomeId in the request.");
  109. var model = doc.Deserialize(bindingContext.ModelType);
  110. TryToUpdateSomeId(model);
  111. bindingContext.Result = ModelBindingResult.Success(model);
  112. }
  113. }
  114. private void TryToUpdateSomeId(object? model)
  115. {
  116. if (model == null) return;
  117. var propInfo = model.GetType().GetProperty("SomeId");
  118. if (propInfo == null)
  119. return;
  120. var getMethod = propInfo.GetGetMethod();
  121. var setMethod = propInfo.GetSetMethod();
  122. if (getMethod == null || setMethod == null)
  123. return;
  124. var currentValue = (long)getMethod.Invoke(model, null);
  125. setMethod?.Invoke(model, new object[] { currentValue + 100 });
  126. }
  127. }
  128. public class MyModelBinderProvider : IModelBinderProvider
  129. {
  130. public IModelBinder? GetBinder(ModelBinderProviderContext context)
  131. {
  132. return new MyModelBinder();
  133. }
  134. }
  135. 不要忘记注册提供程序。
  136. builder.Services.AddControllers(options =>
  137. {
  138. options.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
  139. });
英文:

It is quite easy. You need to apply a model binder to SomeRequestModel. Here is an example

  1. [ApiController]
  2. [Route(&quot;v1/[controller]/[action]&quot;)]
  3. public class AccountController : Controller
  4. {
  5. public AccountController()
  6. {
  7. }
  8. [HttpPost]
  9. public async Task&lt;IActionResult&gt; SomeAction([FromBody] SomeRequestModel request)
  10. {
  11. return Ok(request.SomeId);
  12. }
  13. }
  14. [ModelBinder(BinderType = typeof(SomeRequestModelBinder))]
  15. public class SomeRequestModel
  16. {
  17. public long SomeId { get; set; }
  18. // other fields
  19. }
  20. public class SomeRequestModelBinder : IModelBinder
  21. {
  22. public async Task BindModelAsync(ModelBindingContext bindingContext)
  23. {
  24. using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
  25. {
  26. var json = await reader.ReadToEndAsync();
  27. var model = JsonSerializer.Deserialize&lt;SomeRequestModel&gt;(json);
  28. model.SomeId = model.SomeId + 100;
  29. bindingContext.Result = ModelBindingResult.Success(model);
  30. }
  31. }
  32. }

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.

  1. [ApiController]
  2. [Route(&quot;v1/[controller]/[action]&quot;)]
  3. public class AccountController : Controller
  4. {
  5. public AccountController()
  6. {
  7. }
  8. [HttpPost]
  9. public async Task&lt;IActionResult&gt; SomeAction([FromBody] SomeRequestModel request)
  10. {
  11. return Ok(request.SomeId);
  12. }
  13. }
  14. public interface IHaveSomeId
  15. {
  16. long SomeId { get; set; }
  17. }
  18. public class SomeRequestModel : IHaveSomeId
  19. {
  20. public long SomeId { get; set; }
  21. // other fields
  22. }
  23. public class MyModelBinder : IModelBinder
  24. {
  25. public async Task BindModelAsync(ModelBindingContext bindingContext)
  26. {
  27. using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
  28. {
  29. var json = await reader.ReadToEndAsync();
  30. var model = JsonSerializer.Deserialize(json, bindingContext.ModelType);
  31. if (model is IHaveSomeId someIdModel)
  32. someIdModel.SomeId = someIdModel.SomeId + 100;
  33. bindingContext.Result = ModelBindingResult.Success(model);
  34. }
  35. }
  36. }
  37. public class MyModelBinderProvider : IModelBinderProvider
  38. {
  39. public IModelBinder? GetBinder(ModelBinderProviderContext context)
  40. {
  41. return new MyModelBinder();
  42. }
  43. }

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.

  1. [ApiController]
  2. [Route(&quot;v1/[controller]/[action]&quot;)]
  3. public class AccountController : Controller
  4. {
  5. public AccountController()
  6. {
  7. }
  8. [HttpPost]
  9. public async Task&lt;IActionResult&gt; SomeAction([FromBody] SomeRequestModel request)
  10. {
  11. return Ok(request.SomeId);
  12. }
  13. }
  14. public class SomeRequestModel
  15. {
  16. public long SomeId { get; set; }
  17. // other fields
  18. }
  19. public class MyModelBinder : IModelBinder
  20. {
  21. public async Task BindModelAsync(ModelBindingContext bindingContext)
  22. {
  23. using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
  24. {
  25. var json = await reader.ReadToEndAsync();
  26. var doc = JsonSerializer.Deserialize&lt;JsonDocument&gt;(json);
  27. if (!doc.RootElement.TryGetProperty(&quot;SomeId&quot;, out JsonElement someIdElement))
  28. throw new BadHttpRequestException(&quot;No SomeId in the request.&quot;);
  29. var model = doc.Deserialize(bindingContext.ModelType);
  30. TryToUpdateSomeId(model);
  31. bindingContext.Result = ModelBindingResult.Success(model);
  32. }
  33. }
  34. private void TryToUpdateSomeId(object? model)
  35. {
  36. if (model == null) return;
  37. var propInfo = model.GetType().GetProperty(&quot;SomeId&quot;);
  38. if (propInfo == null)
  39. return;
  40. var getMethod = propInfo.GetGetMethod();
  41. var setMethod = propInfo.GetSetMethod();
  42. if (getMethod == null || setMethod == null)
  43. return;
  44. var currentValue = (long)getMethod.Invoke(model, null);
  45. setMethod?.Invoke(model, new object[] { currentValue + 100 });
  46. }
  47. }
  48. public class MyModelBinderProvider : IModelBinderProvider
  49. {
  50. public IModelBinder? GetBinder(ModelBinderProviderContext context)
  51. {
  52. return new MyModelBinder();
  53. }
  54. }

Do not forget to register the provider.

  1. builder.Services.AddControllers(options =&gt;
  2. {
  3. options.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
  4. });

huangapple
  • 本文由 发表于 2023年7月13日 17:14:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/76677748.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定