문제

ASP MVC 프로젝트에 간단한 편안한 API를 제공하려고합니다. 이 API의 클라이언트를 제어하지 않으며 서버 측에서 일부 작업을 수행하는 데 필요한 정보를 포함하고 조치 결과를 XML을 다시 제공하는 게시물을 통해 XML을 전달합니다. XML을 다시 보내는 데 문제가 없으며 문제는 게시물을 통해 XML을받는 것입니다. 나는 JSON 예제를 보았지만 고객을 제어하지 않기 때문에 (내 관점에서 텔넷 일 수도 있습니다) JSON이 작동하지 않을 것이라고 생각합니다. 제가 맞습니까?

클라이언트가 단순히 요청 본문의 일부로 올바른 형식 형식을 구성한 다음 ASP를 메시지를 구문 분석하는 예를 보았으며 데이터는 FormCollection으로 사용할 수 있습니다 (? param1 = value1¶m2 = value2 & 등). 그러나 메시지 본문의 일부로 순수한 XML을 통과하고 싶습니다.

당신의 도움을 주셔서 감사합니다,

도움이 되었습니까?

해결책 2

이것은 ActionFilterattribute를 사용하여 달성 할 수 있습니다. 작업 필터는 기본적으로 작업 결과 전후에 요청을 교차합니다. 그래서 저는 사후 조치 결과를위한 사용자 정의 액션 필터 속성을 만들었습니다. 여기에 내가 한 일은 다음과 같습니다.

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
    }

이것이 다른 사람에게 도움이되기를 바랍니다. 더 우아한 방법이 있다고 생각한다면 알려주세요.

다른 팁

@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);
    }

그런 다음 컨트롤러에서 XML에 문자열로 액세스 할 수 있습니다.

[RestAPIAttribute]    
public ActionResult MyActionResult(string message)    
{         

}

사용자 정의 가치 공급자 공장을 만들 수 있다는 것을 알고 있습니다. 이렇게하면 저장을 시도하기 전에 게시 될 때 모델을 검증 할 수 있습니다. Phil Haack 이 같은 개념의 JSON 버전에 대한 블로그 게시물이 있습니다. 유일한 문제는 XML과 같은 종류의 것을 구현하는 방법을 모른다는 것입니다.

IMO이를 달성하는 가장 좋은 방법은 사용자 정의 가치 제공 업체를 작성하는 것입니다. 이것은 요청을 Forms Dictionary에 매핑하는 것을 처리하는 공장입니다. ValueProviderFactory에서 상속하고 "Text/XML"또는 "Application/XML"유형 인 경우 요청을 처리합니다.

더 많은 정보:

Phil Haack

내 블로그

MSDN

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;
    }
}

양식 게시물에서 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();
}

양식 게시물을 작성하여 단일 양식 필드로 보낼 수 있습니다.

@freddy의 답변과 @Bowerm의 개선이 마음에 듭니다. 그것은 간결하고 형식 기반 행동의 형식을 보존합니다.

그러나 iSpostNotification 확인은 생산 코드에서는 작동하지 않습니다. 오류 메시지가 암시하는 것처럼 보이기 때문에 HTTP 동사를 확인하지 않으며 컴파일 디버그 플래그가 False로 설정되면 HTTP 컨텍스트에서 벗어납니다. 이것은 여기에 설명됩니다.httpcontext.ispostnotification은 컴파일 디버그가 false 일 때 거짓입니다

이 문제로 인해 누군가가 1/2 일의 디버깅 경로를 절약 할 수 있기를 바랍니다. 다음은 확인이없는 해결책입니다.

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)
    {
...

멋진!,

컨트롤러 방법에서 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();

이것이 올바른 구현입니까?

감사.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top