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

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

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(&quot;v1/[controller]/[action]&quot;)]
public class AccountController : Controller
{
        private readonly IClient _client;
        public AccountController(IClient client,)
        {
            _client = client;
        }

        [HttpPost]
        public async Task&lt;IActionResult&gt; SomeAction([FromBody] SomeRequestModel request)
        {
            if (_client.SomeId == null) return BadRequest(&quot;Something could not be resolved&quot;);
            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(&quot;v1/[controller]/[action]&quot;)]
public class AccountController : Controller
{
public AccountController()
{
}
[HttpPost]
public async Task&lt;IActionResult&gt; 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&lt;SomeRequestModel&gt;(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(&quot;v1/[controller]/[action]&quot;)]
public class AccountController : Controller
{
public AccountController()
{
}
[HttpPost]
public async Task&lt;IActionResult&gt; 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(&quot;v1/[controller]/[action]&quot;)]
public class AccountController : Controller
{
public AccountController()
{
}
[HttpPost]
public async Task&lt;IActionResult&gt; 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&lt;JsonDocument&gt;(json);
if (!doc.RootElement.TryGetProperty(&quot;SomeId&quot;, out JsonElement someIdElement))
throw new BadHttpRequestException(&quot;No SomeId in the request.&quot;);
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(&quot;SomeId&quot;);
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 =&gt;
{
options.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
});

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:

确定