我正在为第三方API创建一个回调端点。调用方将向其POST多部分表单数据。
类似这样的东西:
public SomeApiController : ApiController {
public AModel Post(AModel input) {
return input; //for demonstration
}
}
它将发布的某些字段的名称中有短划线,不能是实际的.NET属性名称。因此,我使用[DataContract]和[DataMember(name="blah")]来定义序列化规则。
型号:
//input model class
[DataContract]
public class AModel {
[DataMember]
public string NormalProperty {get; set;} //value set appropriately
[DataMember(Name="abnormal-property")]
public string AbnormalProperty {get; set;} //always null (not serializing)
}
对于标准的XML和JSON帖子,这可以很好地工作。设置了正常和异常属性,我可以继续我的业务。
然而,对于表单数据的任何变体(表单数据、多报警/表单数据、x-urlended-form-data),异常属性都不能正确地反序列化到模型中,并且将始终为null。
有没有我遗漏的指示?
我已经尝试过您的示例,我的结论是ASP.NET MVC中的DefaultModelBinder
不支持POST变量的命名。
一个显而易见的解决方案是不要在名字中使用破折号。
如果这不是一个选项,那么您可以为该特定模型实现自己的模型绑定器,以处理发送到MVC控制器的异常名称。下面是一个自定义模型活页夹的例子:
public class AModelDataBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(AModel))
{
var request = controllerContext.HttpContext.Request;
string normal = request.Form.Get("normalproperty");
string abnormal = request.Form.Get("abnormal-property");
return new AModel
{
NormalProperty = normal,
AbnormalProperty = abnormal
};
}
return base.BindModel(controllerContext, bindingContext);
}
}
然后您必须在Global.asax:中注册此自定义活页夹
ModelBinders.Binders.Add(typeof(AModel), new AModelDataBinder());
最后,您可以在控制器中使用自定义活页夹:
[HttpPost]
public ActionResult Index([ModelBinder(typeof(AModelDataBinder))]AModel input)
{
// Handle POST
return View();
}
我认为DataContract
的主要问题是它需要像XML这样的原始数据。您尝试POST的是HTML。
数据协定序列化程序支持的类型
经过一番抨击,我们或多或少地做了两件事的结合。
对于url编码的表单,我们遵循了The Zen Coder的示例,它完美地工作。
然而,对于流式传输到服务器的传统多部分表单(它们可以包含文件数据),我们创建了一个简单的解决方案来从请求中读取键/值,然后手动序列化。
在我们的特定用例中,我们不必担心文件数据或其他任何东西,所以我们可以假设所有东西都是字符串,并从那里进行序列化工作。但是,明智的做法可能是为实际文件添加对ContentDisposition标头的简单检查。
示例:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
namespace WebApplication1.Controllers
{
public class ValuesController : ApiController
{
public async Task<Dictionary<string,string>> Post()
{
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
var provider = new MultipartMemoryStreamProvider();
var formData = new Dictionary<string,string>();
try
{
await Request.Content.ReadAsMultipartAsync(provider);
foreach (var item in provider.Contents)
{
formData.Add(item.Headers.ContentDisposition.Name.Replace(""",""), await item.ReadAsStringAsync());
}
return formData;
}
catch (Exception e)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
}
}
}