使用 JSONP 将复杂对象发送到 WCF Web API
感谢 Alex Zeitler 的文章 我的 WCF Web API 服务接受 JSONP 请求。
这对于简单的请求非常有效,但我有一个问题。
我需要能够访问的功能之一是搜索功能,目前该功能通过 http post 获取复杂的对象。显然我无法通过 JSONP 发布,所以我试图考虑如何将其转换为 get 请求。
现有函数如下所示:
[WebInvoke(UriTemplate = "", Method = "POST")]
public HttpResponseMessage<List<Models.Payload>> FindPayloads(Models.AimiRequest requestValues)
{
// do stuff here
return new HttpResponseMessage<List<searchResult>>(results);
}
传入的请求对象定义如下:
public class AimiRequest
{
public MetadataQueryParameter[] Metadata { get; set; }
public string ContentType { get; set; }
public string RuleTypes { get; set; }
}
public class MetadataQueryParameter
{
public string Name { get; set; }
public string Value { get; set; }
}
棘手的部分是元数据参数的数量未知,并且名称和值事先未知。
我尝试简单地将对象序列化为字符串并传递它,但服务抛出 400 Bad Request (从客户端检测到潜在危险的 Request.Path 值 (:)。)
有人有什么好主意吗?
编辑:肮脏的黑客警报!
好吧,我有一个工作功能,但我不喜欢它。必须有一种方法可以实现这一目标,而无需我手动从查询字符串中提取数据并亲自对其进行取消编码。
客户端脚本(请记住这是测试代码而不是生产代码)
function findPayload() {
var paramName = $("#ddlMetaType option:selected").val();
var paramValue = $("#txtValue").val();
// build query object
var AimiRequest = new Object();
AimiRequest.ContentType = null;
AimiRequest.RuleTypes = null;
var MetadataQueryParameter = new Object();
MetadataQueryParameter.Name = paramName;
MetadataQueryParameter.Value = paramValue;
AimiRequest.Metadata = new Array();
AimiRequest.Metadata.push(MetadataQueryParameter);
// NB. In production there may be multiple params to push into the array.
// send query to service
$.ajax({
cache: false,
contentType: "application/json",
data: {},
dataType: "jsonp",
error: function (xhr, textStatus, errorThrown) {
switch (xhr.status) {
case 404:
displayNotFound();
break;
default:
alert(xhr.status);
break;
}
},
success: function (response) {
var resultsPane = $("#resultsPane");
$(resultsPane).empty();
$("#payloadTemplate").tmpl(response).appendTo(resultsPane);
},
type: "GET",
url: "http://localhost:63908/search/json?data=" + encodeURIComponent(JSON.stringify(AimiRequest))
});
}
以及接收它的服务器端函数:
[ServiceContract]
public class SearchResource
{
private IPayloadService _payloadService;
public SearchResource(IPayloadService pService)
{
this._payloadService = pService;
}
[WebGet(UriTemplate = "")]
public HttpResponseMessage<List<Payload>> Search()
{
// find input in querystring
var qString = HttpContext.Current.Request.QueryString["data"];
// Unencode it
var unenc = HttpUtility.UrlDecode(qString);
// deserialise back to the object
var jsSerialiser = new System.Web.Script.Serialization.JavaScriptSerializer();
var myObj = jsSerialiser.Deserialize<AimiRequest>(unenc);
// do search
var metadataParams = new List<KeyValuePair<string, string>>();
foreach (MetadataQueryParameter param in myObj.Metadata)
{
metadataParams.Add(new KeyValuePair<string, string>(param.Name, param.Value));
}
List<Data.Payload> data = _payloadService.FindPayloads(metadataParams, myObj.ContentType, myObj.RuleTypes);
// Map to "viewmodel"
var retVal = AutoMapper.Mapper.Map<List<Data.Payload>, List<Payload>>(data);
// return results
return new HttpResponseMessage<List<Payload>>(retVal);
}
}
Thanks to Alex Zeitler's article I have my WCF Web API service accepting JSONP requests.
This works really well for simple requests but I have a problem.
One of the functions I need to be able to make accessible is a search function that currently takes a complex object via an http post. Obviously I can't post via JSONP so I'm trying to think of how I can convert this to a get request.
The existing function looks like:
[WebInvoke(UriTemplate = "", Method = "POST")]
public HttpResponseMessage<List<Models.Payload>> FindPayloads(Models.AimiRequest requestValues)
{
// do stuff here
return new HttpResponseMessage<List<searchResult>>(results);
}
The request object that's being passed in is defined as follows:
public class AimiRequest
{
public MetadataQueryParameter[] Metadata { get; set; }
public string ContentType { get; set; }
public string RuleTypes { get; set; }
}
public class MetadataQueryParameter
{
public string Name { get; set; }
public string Value { get; set; }
}
The tricky part is there is an unknown number of metadata parameters and the names and values are not known in advance.
I've tried simply serialising the object to a string and passing that but the service throws a 400 Bad Request (A potentially dangerous Request.Path value was detected from the client (:).)
Anyone out there got any bright ideas?
EDIT: DIRTY HACK ALERT!
Ok, I have a working function but I don't like it. There must be a way of getting this achieved without me having to manually pull the data out of the querystring and unencode it myself.
The client side script (bear in mind this is test code not production)
function findPayload() {
var paramName = $("#ddlMetaType option:selected").val();
var paramValue = $("#txtValue").val();
// build query object
var AimiRequest = new Object();
AimiRequest.ContentType = null;
AimiRequest.RuleTypes = null;
var MetadataQueryParameter = new Object();
MetadataQueryParameter.Name = paramName;
MetadataQueryParameter.Value = paramValue;
AimiRequest.Metadata = new Array();
AimiRequest.Metadata.push(MetadataQueryParameter);
// NB. In production there may be multiple params to push into the array.
// send query to service
$.ajax({
cache: false,
contentType: "application/json",
data: {},
dataType: "jsonp",
error: function (xhr, textStatus, errorThrown) {
switch (xhr.status) {
case 404:
displayNotFound();
break;
default:
alert(xhr.status);
break;
}
},
success: function (response) {
var resultsPane = $("#resultsPane");
$(resultsPane).empty();
$("#payloadTemplate").tmpl(response).appendTo(resultsPane);
},
type: "GET",
url: "http://localhost:63908/search/json?data=" + encodeURIComponent(JSON.stringify(AimiRequest))
});
}
And the server side function that receives it:
[ServiceContract]
public class SearchResource
{
private IPayloadService _payloadService;
public SearchResource(IPayloadService pService)
{
this._payloadService = pService;
}
[WebGet(UriTemplate = "")]
public HttpResponseMessage<List<Payload>> Search()
{
// find input in querystring
var qString = HttpContext.Current.Request.QueryString["data"];
// Unencode it
var unenc = HttpUtility.UrlDecode(qString);
// deserialise back to the object
var jsSerialiser = new System.Web.Script.Serialization.JavaScriptSerializer();
var myObj = jsSerialiser.Deserialize<AimiRequest>(unenc);
// do search
var metadataParams = new List<KeyValuePair<string, string>>();
foreach (MetadataQueryParameter param in myObj.Metadata)
{
metadataParams.Add(new KeyValuePair<string, string>(param.Name, param.Value));
}
List<Data.Payload> data = _payloadService.FindPayloads(metadataParams, myObj.ContentType, myObj.RuleTypes);
// Map to "viewmodel"
var retVal = AutoMapper.Mapper.Map<List<Data.Payload>, List<Payload>>(data);
// return results
return new HttpResponseMessage<List<Payload>>(retVal);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
1) WCF Web API 具有格式化程序的概念,用于在通过 Stream 访问的 HTTP 内容和对象之间进行转换。可以注册多个格式化程序,选择过程不仅会考虑目标对象类型,还会考虑内容的媒体类型。
2) 但是,格式化程序不用于转换 URI 和查询字符串参数。相反,这个过程是由不可扩展的 HttpParameterValueConverter 完成的。
3)所以总而言之,你必须自己“解密”数据。但是,您可以将该代码从操作中分离出来。只需创建一个操作处理程序,以字符串形式接收(输入参数)查询字符串参数并返回(输出参数)强类型“未编码”对象 (
Models.AimiRequest
)。操作参数的类型应为Models.AimiRequest
。1) WCF Web API has the concept of formatters for converting between a HTTP content, accessed via a Stream, and an object. There can be multiple formatters registered and the selection process will take into consideration not only the target object type but also the content's media-type.
2) However, formatters are not used to convert URI and query string parameters. Instead, this process is done by HttpParameterValueConverter(s),which are not extensible.
3) So in conclusion, you have to "unencode" the data yourself. However you can factor out this code out of the operation. Just create an operation handler that receives (input parameter) the query string parameter as a string and returns (output parameter) the strongly typed "unencoded" object (
Models.AimiRequest
). The operation parameter's should be of typeModels.AimiRequest
.