我正在尝试为我的ASP MVC项目提供一个简单的RESTful API。我将无法控制此API的客户端,他们将通过POST方法传递XML,该方法将包含在服务器端执行某些操作所需的信息,并提供带有操作结果的XML。发回XML时没有问题,问题是通过POST接收XML。我已经看到了一些JSON示例,但由于我不会控制我的客户端(从我的角度来看它甚至可能是一个telnet)我不认为JSON会起作用。我对么?
我见过客户只是构造正确的表单格式作为请求主体的一部分然后ASP解析消息的例子,数据可用作FormCollection(?param1 = value1& param2 = value2&等) 。但是,我想将纯XML作为消息体的一部分传递。
感谢您的帮助,
答案 0 :(得分:9)
@Freddy - 喜欢你的方法,并使用以下代码对其进行了改进,以简化流阅读:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContextBase httpContext = filterContext.HttpContext;
if (!httpContext.IsPostNotification)
{
throw new InvalidOperationException("Only POST messages allowed on this resource");
}
Stream httpBodyStream = httpContext.Request.InputStream;
if (httpBodyStream.Length > int.MaxValue)
{
throw new ArgumentException("HTTP InputStream too large.");
}
StreamReader reader = new StreamReader(httpBodyStream, Encoding.UTF8);
string xmlBody = reader.ReadToEnd();
reader.Close();
filterContext.ActionParameters["message"] = xmlBody;
// Sends XML Data To Model so it could be available on the ActionResult
base.OnActionExecuting(filterContext);
}
然后在Controller中,您可以以字符串形式访问xml:
[RestAPIAttribute]
public ActionResult MyActionResult(string message)
{
}
答案 1 :(得分:7)
这可以通过使用ActionFilterAttribute来完成。操作过滤器基本上与操作结果之前或之后的请求相交。所以我刚为POST Action Result构建了一个自定义动作过滤器属性。这是我做的:
public class RestAPIAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContextBase httpContext = filterContext.HttpContext;
if (!httpContext.IsPostNotification)
{
throw new InvalidOperationException("Only POST messages allowed on this resource");
}
Stream httpBodyStream = httpContext.Request.InputStream;
if (httpBodyStream.Length > int.MaxValue)
{
throw new ArgumentException("HTTP InputStream too large.");
}
int streamLength = Convert.ToInt32(httpBodyStream.Length);
byte[] byteArray = new byte[streamLength];
const int startAt = 0;
/*
* Copies the stream into a byte array
*/
httpBodyStream.Read(byteArray, startAt, streamLength);
/*
* Convert the byte array into a string
*/
StringBuilder sb = new StringBuilder();
for (int i = 0; i < streamLength; i++)
{
sb.Append(Convert.ToChar(byteArray[i]));
}
string xmlBody = sb.ToString();
//Sends XML Data To Model so it could be available on the ActionResult
base.OnActionExecuting(filterContext);
}
}
然后在控制器上的动作结果方法中,您应该执行以下操作:
[RestAPIAttribute]
public ActionResult MyActionResult()
{
//Gets XML Data From Model and do whatever you want to do with it
}
希望这有助于其他人,如果您认为有更优雅的方法可以做到,请告诉我。
答案 2 :(得分:3)
为什么他们不能在表格帖子中将xml作为字符串传递?
示例:
public ActionResult SendMeXml(string xml)
{
//Parse into a XDocument or something else if you want, and return whatever you want.
XDocument xmlDocument = XDocument.Parse(xml);
return View();
}
您可以创建表单帖子并将其发送到单个表单字段中。
答案 3 :(得分:3)
我知道您可以创建自定义值提供程序工厂。这样,您还可以在发布模型之前验证模型,然后再尝试保存模型。 Phil Haack有一篇关于同一概念的JSON版本的博客文章。唯一的问题是我不知道如何为XML实现同样的东西。
答案 4 :(得分:3)
IMO实现此目的的最佳方法是编写自定义值提供程序,这是一个处理请求到表单字典映射的工厂。您只需继承ValueProviderFactory并处理请求,如果它是“text / xml”或“application / xml”类型。
更多信息:
protected override void OnApplicationStarted()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
ValueProviderFactories.Factories.Add(new XmlValueProviderFactory());
}
<强> XmlValueProviderFactory 强>
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Web.Mvc;
using System.Xml;
using System.Xml.Linq;
public class XmlValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
var deserializedXml = GetDeserializedXml(controllerContext);
if (deserializedXml == null) return null;
var backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
AddToBackingStore(backingStore, string.Empty, deserializedXml.Root);
return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
}
private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, XElement xmlDoc)
{
// Check the keys to see if this is an array or an object
var uniqueElements = new List<String>();
var totalElments = 0;
foreach (XElement element in xmlDoc.Elements())
{
if (!uniqueElements.Contains(element.Name.LocalName))
uniqueElements.Add(element.Name.LocalName);
totalElments++;
}
var isArray = (uniqueElements.Count == 1 && totalElments > 1);
// Add the elements to the backing store
var elementCount = 0;
foreach (XElement element in xmlDoc.Elements())
{
if (element.HasElements)
{
if (isArray)
AddToBackingStore(backingStore, MakeArrayKey(prefix, elementCount), element);
else
AddToBackingStore(backingStore, MakePropertyKey(prefix, element.Name.LocalName), element);
}
else
{
backingStore.Add(MakePropertyKey(prefix, element.Name.LocalName), element.Value);
}
elementCount++;
}
}
private static string MakeArrayKey(string prefix, int index)
{
return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
}
private static string MakePropertyKey(string prefix, string propertyName)
{
if (!string.IsNullOrEmpty(prefix))
return prefix + "." + propertyName;
return propertyName;
}
private XDocument GetDeserializedXml(ControllerContext controllerContext)
{
var contentType = controllerContext.HttpContext.Request.ContentType;
if (!contentType.StartsWith("text/xml", StringComparison.OrdinalIgnoreCase) &&
!contentType.StartsWith("application/xml", StringComparison.OrdinalIgnoreCase))
return null;
XDocument xml;
try
{
var xmlReader = new XmlTextReader(controllerContext.HttpContext.Request.InputStream);
xml = XDocument.Load(xmlReader);
}
catch (Exception)
{
return null;
}
if (xml.FirstNode == null)//no xml.
return null;
return xml;
}
}
答案 5 :(得分:2)
我喜欢@Freddy的回答和@Bowerm的改进。它简洁并保留了基于表单的操作的格式。
但IsPostNotification检查在生产代码中不起作用。它不会检查HTTP动词,因为错误消息似乎暗示,并且当编译调试标志设置为false时,它会从HTTP上下文中删除。这在这里解释: HttpContext.IsPostNotification is false when Compilation debug is false
由于这个问题,我希望这能节省一半半的调试路由。这是没有检查的解决方案:
public class XmlApiAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContextBase httpContext = filterContext.HttpContext;
// Note: for release code IsPostNotification stripped away, so don't check it!
// https://stackoverflow.com/questions/28877619/httpcontext-ispostnotification-is-false-when-compilation-debug-is-false
Stream httpBodyStream = httpContext.Request.InputStream;
if (httpBodyStream.Length > int.MaxValue)
{
throw new ArgumentException("HTTP InputStream too large.");
}
StreamReader reader = new StreamReader(httpBodyStream, Encoding.UTF8);
string xmlBody = reader.ReadToEnd();
reader.Close();
filterContext.ActionParameters["xmlDoc"] = xmlBody;
// Sends XML Data To Model so it could be available on the ActionResult
base.OnActionExecuting(filterContext);
}
}
...
public class MyXmlController
{ ...
[XmlApiAttribute]
public JsonResult PostXml(string xmlDoc)
{
...
答案 6 :(得分:1)
尼斯!,
我在控制器方法中操作Xml的对象是什么?
我正在使用这种方式:
在actionFilter上,我用:
填充模型 .
.
string xmlBody = sb.ToString();
filterContext.Controller.ViewData.Model = xmlBody;
在我的控制器方法上,我得到了模型:
string xmlUserResult = ViewData.Model as string;
XmlSerializer ser = new XmlSerializer(typeof(UserDTO));
StringReader stringReader = new StringReader(xmlUserResult);
XmlTextReader xmlReader = new XmlTextReader(stringReader);
UserDTO userToUpdate = ser.Deserialize(xmlReader) as UserDTO;
xmlReader.Close();
stringReader.Close();
这是正确的实施吗?
感谢。