0

我从我的前任那里继承了一个项目,该项目使用 OpenRasta 为我的 OpenSource 同事托管一个 Web 服务,以访问他们的应用程序。这是我第一次涉足 OpenRasta,我添加了许多附加功能,所有这些功能都通过手动浏览器请求工作,虽然不是 100% 可靠,但这可能是稍后的另一个问题。所以我已经着手创建一组单元测试来测试功能,无论如何我都应该这样做。我已经成功地为每个 GET 请求创建了一个或两个单元测试,所有这些请求都通过了,但是我被困在项目中的单个 POST 的测试中。

我收到 HTTP 415 错误“8-[2012-12-07 11:23:19Z] Information(0) Executing OperationResult OperationResult: type=RequestM ediaTypeUnsupported, statusCode=415。” 从输出窗口。我从 Nate Taylor 的帖子http://taylonr.com/integration-testing-openrasta中获得灵感,并问了他同样的问题,他很友好地回答了这个问题。我仍在尝试破译他的答案,也许有人可以扩展并填补我的理解空白?这是我一直在尝试的代码:

[Test]
public void AuthenticateUserJSONPOSTTest()
    {
        object content = new AuthenticationStructure { Username = "matthew.radford", Password = "obviously not going to tell you that bit and will replace with a domain test user when the time comes", AppId = 4 };

        OpenRastaJSONTestMehods.POST<AuthenticationResult, AuthenticationStructure>("http://localhost/user", content);
    }

[Test]
    public static void POST<T, U>(string uri, object content)
    {
        const string LocalHost = "http://localhost/";
        if (uri.Contains(LocalHost))
            POST<T, U>(new Uri(uri), content);
        else
            throw new UriFormatException(string.Format("The uri doesn't contain {0}", LocalHost));
    }
    [Test, WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    public static void POST<T,U>(Uri serviceuri, object content)
    {
        using (var host = new InMemoryHost(new Configuration()))
        {
            var request = new InMemoryRequest()
            {
                Uri = serviceuri,
                HttpMethod = "POST"
            };

            request.Entity.ContentType = MediaType.Json;
            request.Entity.Headers["Accept"] = "application/json";

            var serializer = new DataContractJsonSerializer(typeof(T), new [] { typeof(AuthenticationStructure) });
            serializer.WriteObject(request.Entity.Stream, content); 
            request.Entity.Stream.Seek(0, SeekOrigin.Begin); 
            request.Entity.ContentLength = request.Entity.Stream.Length;

            //Just a read test, not necessary for the output
            byte[] readbyte = new byte[(int)request.Entity.ContentLength];
            request.Entity.Stream.Read(readbyte, 0, (int)request.Entity.ContentLength);
            request.Entity.Stream.Seek(0, SeekOrigin.Begin);

            U readObject = (U)serializer.ReadObject(request.Entity.Stream);
            request.Entity.Stream.Seek(0, SeekOrigin.Begin);

            NUnit.Framework.Assert.AreEqual(content, readObject);

            var response = new InMemoryResponse();

            response.Entity.ContentType = MediaType.Json;
            response.Entity.Headers["Accept"] = "application/json";

            response = (InMemoryResponse)host.ProcessRequest(request);
            int statusCode = response.StatusCode;

//this is where the test fails because the above response isn't correct and gives the 415 statusCode
            NUnit.Framework.Assert.AreEqual(201, statusCode, string.Format("Http StatusCode Error: {0}", statusCode));

            object returnedObject;
            if (response.Entity.ContentLength > 0)
            {
                response.Entity.Stream.Seek(0, SeekOrigin.Begin);

                //Just a read test, not necessary for the output
                readbyte = new byte[(int)response.Entity.ContentLength];
                response.Entity.Stream.Read(readbyte, 0, (int)response.Entity.ContentLength);
                response.Entity.Stream.Seek(0, SeekOrigin.Begin);

                returnedObject = serializer.ReadObject(response.Entity.Stream);
                //return returnedObject;
            }
        }
    }

提前致谢。

4

1 回答 1

1

今天早上我尝试了很多不同的东西来尝试让它发挥作用。通过尝试将 JSON 流作为字符串读取以实际查看对象被序列化的内容,这是我向前迈出的第一步。

为此,我发现如何在 C# 中将流转换为字节 []?这让我朝着正确的方向前进,将流读取到字符串中。因此,我想出了这一行来将其写入输出窗口:

Debug.WriteLine(Encoding.UTF8.GetString(StreamHelper.ReadToEnd(request.Entity.Stream), 0, (int)request.Entity.Stream.Length).ToString());

结果如下: {"__type":"AuthenticationStructure","username":"matthew.radford","appid":4,"password":"###########"}

我意识到这个输出有两个问题。首先很简单,密码应该在 appid 之前,这在 AuthenticationStructure 类中很容易修复,我犯了一个错误。AppId 的 DataMember Order 需要等于 3

[DataMember(Name="appid", Order=3)]
    public int AppId { get; set; }

其次,默认的序列化在符号的开头包含一个 '__type' 成员。这显然与我的处理程序的 POST 方法上的参数不匹配:

[HttpOperation(HttpMethod.POST)] 
    public OperationResult Post(string username, string password, int appid)

在这一点上,我试图从 JSON 字符串中删除类型表示法。我找到了一个很好的网站Deserialize array values to .NET properties using DataContractJsonSerializer这向我展示了如何编写构造函数以包含他们设置为 false 的 alwaysEmitTypeInformation,但我想 Emit 类型信息,所以将其更改为 true。它还向我展示了如何基于 IDataContractSurrogate 创建一个代理,我称之为 AuthenticationTypeSurrogate。

public class AuthenticationTypeSurrogate : IDataContractSurrogate
{
    public Type GetDataContractType(Type type)
    {
        // "Book" will be serialized as an object array
        // This method is called during serialization, deserialization, and schema export. 
        if (typeof(AuthenticationStructure).IsAssignableFrom(type))
        {
            return typeof(object[]);
        }
        return type;
    }
    public object GetObjectToSerialize(object obj, Type targetType)
    {
        // This method is called on serialization.
        if (obj is AuthenticationStructure)
        {
            AuthenticationStructure authenticationStructure = (AuthenticationStructure)obj;
            return new object[] { authenticationStructure.Username, authenticationStructure.Password, authenticationStructure.AppId };
        }
        return obj;
    }
    public object GetDeserializedObject(object obj, Type targetType)
    {
        // This method is called on deserialization.
        if (obj is object[])
        {
            object[] arr = (object[])obj;
            AuthenticationStructure authenticationStructure = new AuthenticationStructure { Username = (string)arr[0], Password = (string)arr[1], AppId = (int)arr[2] };
            return authenticationStructure;
        }
        return obj;
    }
    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        return null; // not used
    }
    public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
    {
        return typeDeclaration; // Not used
    }
    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        return null; // not used
    }
    public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
    {
        return null; // not used
    }
    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
    {
        return; // not used
    }
}

序列化为此工作,但现在反序列化,我从不费心调试并正确处理,因为它仍然无法正常工作,因为它不是创建 JSON,而是序列化对象数组,这是您所期望的,因为这就是 GetObjectoSerialize 方法方法正在返回。因此,除非我能弄清楚如何将其更改为 JSON,否则我会遇到另一面砖墙。

所以最后我想好了,我只需将 __type 参数添加到重载的 POST 方法中,并将其他参数传递给原始 POST 方法。

[HttpOperation(HttpMethod.POST)]
    public OperationResult Post(Type __type, string username, string password, int appid)
    {
        return Post(username, password, appid);
    }

这几乎是正确的,但它仍然不起作用,所以最后我创建了另一个重载方法并传递了整个类型:

[HttpOperation(HttpMethod.POST)]
    public OperationResult Post(AuthenticationStructure astruct)
    {
        return Post(astruct.Username, astruct.Password, astruct.AppId);
    }

最后这奏效了。我对此并不完全满意,并且希望直接链接到我现有的方法中,但它确实有效。

感谢所有看过的人,尤其是 Nate,感谢您最初的回复,希望这对未来的人们有所帮助。

于 2012-12-10T15:57:44.207 回答