- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
有没有办法在目标模型之外以流畅的方式定义绑定(bind)属性(FromBody
、FromQuery
等)?类似于 FluentValidation vs [Required]
、[MaxLength]
等属性。
背景故事:
我想使用命令模型作为 Controller Action 参数:
[HttpPost]
public async Task<ActionResult<int>> Create(UpdateTodoListCommand command)
{
return await Mediator.Send(command);
}
更重要的是,我希望模型从多个来源(路线、 body 、atc.)绑定(bind):
[HttpPut("{id}")]
public async Task<ActionResult> Update(UpdateTodoListCommand command)
{
// command.Id is bound from the route, the rest is from the request body
}
这应该是可能的(https://josef.codes/model-bind-multiple-sources-to-a-single-class-in-asp-net-core/,https://github.com/ardalis/RouteAndBodyModelBinding),但需要在命令的属性上绑定(bind)属性,这应该避免。
最佳答案
AspNetCore modelBinding 过程可以在没有属性的情况下使用自定义 IModelBinderProvider 进行自定义。
对于这样的请求,我将解释一种实现以下结果的方法:
Header: PUT
URL: /TodoList/testid?Title=mytitle&Index=2
BODY: { "Description": "mydesc" }
预期的响应主体:
{"Id":"testid","Title":"mytitle","Description":"mydesc","Index":2}
因此 Controller 应该将来自路由、查询和正文的所有数据混合为一个模型,然后返回序列化模型(我们只是想检查示例中的自定义绑定(bind)结果)。
C# POCO 可以是:
public class UpdateTodoListCommand
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
// string properties are too easy to bind, so we add an extra property of another type for the demo
public int Index { get; set; }
}
Controller :
[Route("[controller]")]
public class TodoListController : Controller
{
[HttpPut("{id}")]
public IActionResult Update(UpdateTodoListCommand command
{
// return the serialized model so we can check all query and route data are merged as expected in the command instance
return Ok(JsonSerializer.Serialize(command));
}
}
我们需要一些样板代码来声明关于我们的命令的元数据,并定义应该绑定(bind)哪个属性来查询或路由数据。我说得很简单,因为这不是主题的目的:
public class CommandBindingModel
{
public HashSet<string> FromQuery { get; } = new HashSet<string>();
public HashSet<string> FromPath { get; } = new HashSet<string>();
}
public class CommandBindingModelStore
{
private readonly Dictionary<Type, CommandBindingModel> _inner = new ();
public CommandBindingModel? Get(Type type, bool createIfNotExists)
{
if (_inner.TryGetValue(type, out var model))
return model;
if (createIfNotExists)
{
model = new CommandBindingModel();
_inner.Add(type, model);
}
return model;
}
}
存储将包含您希望与自定义进程绑定(bind)的所有命令的元数据快照。
store 的 fluent builder 可能是这样的(我再次尝试简单):
public class CommandBindingModelBuilder
{
public CommandBindingModelStore Store { get; } = new CommandBindingModelStore();
public CommandBindingModelBuilder Configure<TModel>(Action<Step<TModel>> configure)
{
var model = Store.Get(typeof(TModel), true);
configure(new Step<TModel>(model ?? throw new Exception()));
return this;
}
public class Step<TModel>
{
private readonly CommandBindingModel _model;
public Step(CommandBindingModel model)
{
_model = model;
}
public Step<TModel> FromQuery<TProperty>(Expression<Func<TModel, TProperty>> property
{
if (property.Body is not MemberExpression me)
throw new NotImplementedException();
_model.FromQuery.Add(me.Member.Name);
return this;
}
public Step<TModel> FromPath<TProperty>(Expression<Func<TModel, TProperty>> property)
{
if (property.Body is not MemberExpression me)
throw new NotImplementedException();
_model.FromPath.Add(me.Member.Name);
return this;
}
}
}
现在我们可以创建一个自定义实现 IModelBinderProvider
.这个有责任给定制IModelBinder
为我们商店的每个命令添加 MVC。我们的命令是复杂类型,因此我们必须获取一些元数据(来自 MVC api)以简化属性绑定(bind):
public class CommandModelBinderProvider : IModelBinderProvider
{
private readonly CommandBindingModelStore _store;
public CommandModelBinderProvider(CommandBindingModelStore store)
{
_store = store;
}
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
var model = _store.Get(context.Metadata.ModelType, false);
if (model != null)
{
var binders = new Dictionary<ModelMetadata, IModelBinder>();
foreach(var property in model.FromQuery.Concat(model.FromPath))
{
var metadata = context.Metadata.GetMetadataForProperty(context.Metadata.ModelType, property);
var binder = context.CreateBinder(metadata);
binders.Add(metadata, binder);
}
return new CommandModelBinder(model, binders);
}
return null;
}
}
自定义 Binder 将读取请求正文(示例中为 JSON,但您可以以任何需要的格式读取和解析):
public class CommandModelBinder : IModelBinder
{
private readonly CommandBindingModel _commandBindingModel;
private readonly Dictionary<ModelMetadata, IModelBinder> _binders;
public CommandModelBinder(CommandBindingModel commandBindingModel, Dictionary<ModelMetadata, IModelBinder> binders)
{
_commandBindingModel = commandBindingModel;
_binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var value = await bindingContext.HttpContext.Request.ReadFromJsonAsync(bindingContext.ModelType);
if (value == null)
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
bindingContext.Model = value;
/* CUSTOM BINDING HERE */
bindingContext.Result = ModelBindingResult.Success(value);
}
}
如果我们现在执行代码(假设 MVC 知道自定义提供程序,在我的示例的这个阶段这不是真的),只有属性 Description 按预期绑定(bind)。所以我们必须绑定(bind) QueryString 的属性。在 MVC 绑定(bind)哲学中,ValueProvider 负责从请求中获取原始值:QueryStringValueProvider
是 QueryString 的那个。所以我们可以使用它:
var queryStringValueProvider = new QueryStringValueProvider(BindingSource.Query, bindingContext.HttpContext.Request.Query, CultureInfo.CurrentCulture);
foreach (var fq in _commandBindingModel.FromQuery)
{
var r = queryStringValueProvider.GetValue(fq);
bindingContext.ModelState.SetModelValue(fq, r);
if (r == ValueProviderResult.None) continue;
/* we have to bind the value to our command */
}
这里很容易使用反射来设置我们的命令的属性,但是 MVC 给了我们一些工具,所以我认为最好使用它们。此外,我们只得到一个类型为 StringValues
的原始值。 ,因此将其转换为预期的属性类型可能会很痛苦(想想我们的 Index
的属性 UpdateTodoListCommand
)。现在是使用在自定义 IModelProvider 中创建的绑定(bind)器的时候了:
var m = bindingContext.ModelMetadata.GetMetadataForProperty(bindingContext.ModelType, fq);
using (bindingContext.EnterNestedScope(m, fq, fq, m.PropertyGetter(value)))
{
bindingContext.Result = ModelBindingResult.Success(r);
var binder = _binders[m];
await binder.BindModelAsync(bindingContext);
var result = bindingContext.Result;
m.PropertySetter(value, result.Model);
}
现在,在我们的示例中,Title 和 Index 将按预期绑定(bind)。 Id 属性可以与 RouteValueProvider
绑定(bind):
var routeValueProvider = new RouteValueProvider(BindingSource.Path, bindingContext.ActionContext.RouteData.Values);
foreach (var fp in _commandBindingModel.FromPath)
{
var r = routeValueProvider.GetValue(fp);
bindingContext.ModelState.SetModelValue(fp, r);
if (r == ValueProviderResult.None) continue;
var m = bindingContext.ModelMetadata.GetMetadataForProperty(bindingContext.ModelType, fp);
using (bindingContext.EnterNestedScope(m, fp, fp, m.PropertyGetter(value)))
{
bindingContext.Result = ModelBindingResult.Success(r);
var binder = _binders[m];
await binder.BindModelAsync(bindingContext);
var result = bindingContext.Result;
m.PropertySetter(value, result.Model);
}
}
最后要做的是告诉 MVC 我们的自定义 IModelBinderProvider
, 应该在 MvcOptions
中完成:
var store = new CommandBindingModelBuilder()
.Configure<UpdateTodoListCommand>(e => e
.FromPath(c => c.Id)
.FromQuery(c => c.Title)
.FromQuery(c => c.Index)
)
.Store;
builder.Services.AddControllers().AddMvcOptions(options =>
{
options.ModelBinderProviders.Insert(0, new CommandModelBinderProvider(store));
});
这里有一个完整的要点:https://gist.github.com/thomasouvre/e5438816af1a0ad81bddf106432cfa7d
编辑:当然,您可以使用自定义 IOperationProcessor
自定义 NSwag 操作生成像这样:
public class NSwagCommandOperationProcessor : IOperationProcessor
{
private readonly CommandBindingModelStore _store;
public NSwagCommandOperationProcessor(CommandBindingModelStore store)
{
_store = store;
}
public bool Process(OperationProcessorContext context)
{
ParameterInfo? pinfo = null;
CommandBindingModel? model = null;
// check if there is a command parameter in the action
foreach (var p in context.MethodInfo.GetParameters())
{
pinfo = p;
model = _store.Get(pinfo.ParameterType, false);
if (model != null) break;
}
if (model == null || pinfo == null) return true; // false will exclude the action
var jsonSchema = JsonSchema.FromType(pinfo.ParameterType); // create a full schema from the command type
if (jsonSchema.Type != JsonObjectType.Object) return false;
var bodyParameter = new OpenApiParameter() { IsRequired = true, Kind = OpenApiParameterKind.Body, Schema = jsonSchema, Name = pinfo.Name };
foreach (var prop in jsonSchema.Properties.Keys.ToList())
{
if (model.FromQuery.Contains(prop) || model.FromPath.Contains(prop))
{
// then excludes some properties from the schema
jsonSchema.Properties.Remove(prop);
continue;
}
bodyParameter.Properties.Add(prop, jsonSchema.Properties[prop]);
// if the property is not excluded, the property should be binded from the body
// so we have to delete existing parameters generated by NSwag (probably binded as from query)
var operationParameter = context.OperationDescription.Operation.Parameters.FirstOrDefault(p => p.Name == prop);
if (operationParameter != null)
context.OperationDescription.Operation.Parameters.Remove(operationParameter);
}
if (bodyParameter.Properties.Count > 0)
context.OperationDescription.Operation.Parameters.Add(bodyParameter);
return true;
}
}
关于c# - ASP.net 中的流畅模型绑定(bind),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74136993/
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!