65.9K
CodeProject 正在变化。 阅读更多。
Home

JSON API

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (101投票s)

2011年10月10日

CPOL

11分钟阅读

viewsIcon

339195

downloadIcon

7263

简单介绍如何将JSON API共享给Web和桌面。

目录

引言

大家好,好久不见,是吧?嗯,我没有闲着,实际上我正在为一个大型的开源开发者工具工作;它是一个组织工具,占用了我所有的时间。事实上,不仅是我,它也占用了 fellow CodeProjecter Pete O'Hanlon 的不少时间,他愚蠢地、呃,好心地自愿帮助我创建这个工具。

这个工具进展顺利,它实际上是一个 ASP MVC 3 网站,并且我认为其中一些东西非常有用,所以我将创建几篇文章介绍它使用的一些内容,当然我也会写一两篇关于它是如何工作的文章。

那么这篇文章是关于什么的呢?嗯,Pete O'Hanlon 和我正在开发的这个工具需要做的一件事就是公开一个 JSON(Java Script Object Notation)API,供人们使用。这个相同的 API 也需要能够从标准的 .NET 应用程序中调用。这实际上就是这篇文章要讲的。它展示了一种(有很多种方法,这只是一种)方法,你可以用它来公开一个 JSON API,既可以从 Web 调用,也可以通过基于 Web 的 API 从标准的桌面 .NET 客户端应用调用,而且麻烦最少,没有配置,除了 .NET 安装自带的标准配置。

不过,它确实使用了 ASP MVC 3,所以这是假定的,.NET 4 和 VS2010 也是如此。

可用框架

乍一看,你可能会认为这个问题非常适合某种 WCF 服务,这种服务可以同时被 JavaScript 和标准的 .NET 代码调用。所以这给了我们两种选择:

RESTful WCF

这个功能在 .NET 3.5 SP1 中出现,它允许你编写一个普通的 WCF 服务,你可以用特殊的 Web 属性进行标记,例如:

[ServiceContract]
public interface ICalculator
{
    [OperationContract]
    [WebInvoke(UriTemplate = "add?x={x}&y={y}")]
    long Add(long x, long y);

    [OperationContract]
    [WebInvoke(UriTemplate = "sub?x={x}&y={y}")]
    long Subtract(long x, long y);

    [OperationContract]
    [WebInvoke(UriTemplate = "mult?x={x}&y={y}")]
    long Multiply(long x, long y);

    [OperationContract]
    [WebInvoke(UriTemplate = "div?x={x}&y={y}")]
    long Divide(long x, long y);

    [OperationContract]
    [WebGet(UriTemplate = "hello?name={name}")]
    string SayHello(string name);
}

public class CalcService : ICalculator
{
    public long Add(long x, long y)
    {
        return x + y;
    }

    public long Subtract(long x, long y)
    {
        return x - y;
    }

    public long Multiply(long x, long y)
    {
        return x * y;
    }

    public long Divide(long x, long y)
    {
        return x / y;
    }

    public string SayHello(string name)
    {
        return "Hello " + name;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Uri baseAddress = new Uri("https://:8000/");

        WebServiceHost svcHost = 
           new WebServiceHost(typeof(CalcService), baseAddress);

        try
        {
            svcHost.Open();

            Console.WriteLine("Service is running");
            Console.WriteLine("Press enter to quit...");
            Console.ReadLine();

            svcHost.Close();
        }
        catch (CommunicationException cex)
        {
            Console.WriteLine("An exception occurred: {0}", cex.Message);
            svcHost.Abort();
        }
    }
}

但是你还需要使用一个新的 WebServiceHost 来托管这个服务,并且你还必须为服务使用一个特定的端口。

我很久以前就写过一篇关于这个的相当不错的文章;如果你感兴趣,可以在 https://codeproject.org.cn/KB/smart/GeoPlaces.aspx 阅读。

WCF Web API

.NET 开发者可以选择的第二个选项是使用 WCF Web API,这是我最喜欢的微软开发人员之一 Glenn Block 团队在推广的。它可以被看作是 RESTful WCF 的一种进步。它相当新,并且(还没有)包含在 .NET Framework 中。

下面是一个关于如何使用 WCF Web API 配置服务的简单示例,这通常会放在一个网站中(例如 ASP MVC 网站):

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Net;
using System.Net.Http;
using Microsoft.ApplicationServer.Http.Dispatcher;


namespace RESTFul_WCF
{
    /// <summary>
    /// Demo service using the new WEB WCF APIs
    /// </summary>
    [ServiceContract]
    public class EFResource
    {
        int dummyCounter = 0;

        [WebGet(UriTemplate = "", 
            RequestFormat = WebMessageFormat.Json, 
            ResponseFormat = WebMessageFormat.Json)]
        public IQueryable<Models.Author> Get()
        {
            //dummy code, this would hit database normally
            List<Models.Author> authors = new List<Models.Author>();
            for (int i = 0; i < 5; i++)
            {
              authors.Add(new Models.Author(
                i,string.Format("Some Author_{0}", i.ToString())));
            }

            return authors.AsQueryable();
        }

        [WebInvoke(UriTemplate = "", Method = "POST", 
            RequestFormat = WebMessageFormat.Json, 
            ResponseFormat = WebMessageFormat.Json)]
        public Models.Author Post(Models.Author author)
        {
            if (author == null)
            {
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            }

            //dummy code, this would hit database normally
            author.Id = dummyCounter++;
            return author;
        }
    }
}

你可能会在你的 Global.asax.cs 文件中看到这个(这是 ASP MVC 特有的,我确信如果没有使用 ASP MVC,也会有等效的设置)

private void Application_Start(object sender, EventArgs e)
{
    // setting up contacts services
    RouteTable.Routes.MapServiceRoute<EFResource>("ef");
}

注意:文章的一位读者实际上指出,你可以让 WCF Web API 与 ASP MVC 及其路由功能良好配合。虽然这没有包含在附加的演示应用中,但你可以这样做。

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.Add(new ServiceRoute("api/resource", 
               new HttpServiceHostFactory(), typeof(EFResource)));

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new { controller = "Home", action = "Index", 
              id = UrlParameter.Optional } // Parameter defaults
    );
}

这就是服务部分的一切(好吧,还有一小部分配置,但比旧版本的 WCF 少了很多)。

那么消费客户端呢?它看起来会是什么样子?嗯,它看起来就像这样,这是它的全部内容,完全不需要配置

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Xml.Serialization;
using Microsoft.ApplicationServer.Http;

using Models;

namespace RestFul_Test
{
    class Program
    {

        private enum MimeFormat { JSON, Xml };

        static void Main(string[] args)
        {
            string uri = "https://:8300/ef";

            //GET Using JSON
            HttpClient client = GetClient(uri, MimeFormat.JSON);
            var response = client.Get(uri);

            Console.WriteLine("=========GET==============");
            foreach (Author author in response.Content.ReadAs<List<Author>>())
            {
                LogAuthorResultToConsole(author);
            }


            //POST using JSON
            Console.WriteLine("\r\n");
            Console.WriteLine("=========POST==============");


            var newAuthor = new Author { Name = "Xml Barber" };
            var request = new HttpRequestMessage(HttpMethod.Post, new Uri(uri));
            request.Content = new ObjectContent<Author>(newAuthor, "application/json");
            request.Headers.Accept.Add(
              new MediaTypeWithQualityHeaderValue("application/json"));
            response = client.Send(request);
            var receivedAuthor = response.Content.ReadAs<Author>();
            LogAuthorResultToConsole(receivedAuthor);
            Console.ReadLine();
        }

        private static void LogAuthorResultToConsole(Author author)
        {
            Console.WriteLine(string.Format(CultureInfo.InvariantCulture,
                "\r\n Author Name: {0}, Author Id: {1}", author.Name, author.Id));
            
        }

        private static HttpClient GetClient(string uri, MimeFormat format)
        {
            var client = new HttpClient(new Uri(uri));
            switch (format)
            {
                case MimeFormat.Xml:
                    client.DefaultRequestHeaders.Accept.Add(
                        new MediaTypeWithQualityHeaderValue("application/xml"));
                    break;
                case MimeFormat.JSON:
                    client.DefaultRequestHeaders.Accept.Add(
                        new MediaTypeWithQualityHeaderValue("application/json"));
                    break;
            }
            return client;
        }
    }
}

我很喜欢这个,但它需要使用很多额外的 DLL,即使是客户端也需要一些额外的 DLL。

注意:我在文章的开头包含了一个小的演示应用,因为我认为它可能会引起一些人的兴趣。

这些框架存在的问题

有几个问题需要考虑,我将逐一在下面介绍

RESTful WCF

使用 RESTful WCF(在 .NET 3.5 SP1 中可用)本可以奏效,但有几个障碍

  • 它需要使用额外的端口。现在,由于这是网站的一部分,我已经为实际的网站使用了一个端口。使用 RESTful WCF 之后,我将需要维护另一个端口。
  • .NET 客户端需要 WCF DLL 以及一些特殊的 REST WCF DLL。
  • 需要创建客户端代理。
  • 为客户端和服务器维护一个 App.Config 用于所有的 WCF 配置。
  • 如果正确操作,则使用 eTags。
  • 也许需要深入 MessageInspector 来更改 HttpRequestContextType;JSON 是支持的,所以那应该是可以的,但你明白我的意思吗?

WCF Web API

我非常喜欢 Glenn Block 和他的团队正在开发的 WCF Web API;然而,对我来说,它也有一些问题,即:

  • 阻止我使用 WCF Web API 的原因是服务器上需要大量的额外 DLL;当我安装 WCF Web APInuget 包时,我感到非常震惊。
  • 即使是客户端也需要额外的 DLL(HttpClient 等)。

好处是无需任何配置,它就能工作。好吧,你确实需要一些额外的 WCF Contrib 东西才能让它与 DataContract 序列化良好工作,但这还可以容忍。

尽管如此,ASP MVC 对 WCF Web API 有相当好的支持,你可以在这里阅读:http://wcf.codeplex.com/wikipage?title=Getting%20started:%20Building%20a%20simple%20web%20api。正如我所说,我真的很喜欢 WCF Web API,如果不是因为需要那么多 DLL,我可能就会选择它了。

一个可能的解决方案(至少对我来说是有效的)

现在我已经概述了现有解决方案中让我不满的地方,我真的还有什么别的选择吗?我真正想要的是没有额外的端口需要维护/打开,没有配置,没有额外的 DLL,代码应该在通过 JavaScript 调用时工作,例如通过 jQuery 调用,并且从标准的 .NET 客户端应用程序使用标准的 .NET Web API 调用。

使用 ASP MVC

我得出的逻辑结论是直接使用 ASP MVC。这有很多好处,即:

  • ASP MVC 控制器可以被任何 JavaScript 调用,包括 jQuery,当然。
  • ASP MVC 的路由已经存在。
  • 网站已经在一个端口上可用,不需要再开放另一个端口。
  • ASP MVC 控制器就是一个 URL,因此可以轻松地被标准的 .NET Web API 调用。
  • ASP MVC 3(我正在使用的是这个)实际上自带了某些 ValueProviderFactory 对象,其中 JsonValueProviderFactory 是其中之一。

基本上,ASP MVC 为你做了很多工作。

那么这通常会是什么样子呢?好吧,我们来看看,好吗?

这是一个完整的 ASP MVC 3 控制器,它接受 JSON 并返回 JSON。那里有三个不同的操作,我们将在本文后面讨论它们。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Common;
using System.IO;
using System.Text;
using System.Runtime.Serialization.Json;
using CodeStash.Filters;
using System.Json;
using RestfulWebSite.ModelBinders;

namespace RestfulWebSite.Controllers
{
    /// <summary>
    /// This controller shows 3 different methods of accepting JSON values
    /// 1. Accepts JSON from JavaScript in the browser,
    /// and also from .NET client app using standard web APIs
    /// 2. Accepts JSON from JavaScript in the browser
    /// 3. Uses JSONValue API to accept JSON as dynamic object
    /// </summary>
    public class RestController : Controller
    {

        /// <summary>
        /// Uses special ActionFilter to convert desktop JSON data
        /// into acceptable action value. Returns standard JSON result
        /// </summary>
        [JSONFilter(Param = "person", RootType = typeof(Person))]
        public ActionResult Index(Person person)
        {
            int age = person.Age;

            List<Person> people = new List<Person>();
            for (int i = 0; i < 5; i++)
            {
                string s = string.Format("{0}_{1}", "hardCoded", i.ToString());
                people.Add(new Person(i, s, new Parent(1, "parent")));
            }
            return Json(people);
        }

        /// <summary>
        /// Accepts standard JSON, from JavaScript call. 
        /// Returns standard JSON result
        /// </summary>
        public ActionResult NoFilterJSON(Person person)
        {
            int age = person.Age;

            List<Person> people = new List<Person>();
            for (int i = 0; i < 5; i++)
            {
                string s = string.Format("{0}_{1}", "hardCoded", i.ToString());
                people.Add(new Person(i, s, new Parent(1, "parent")));
            }
            return Json(people);
        }

        /// <summary>
        /// Uses JSONValue to convert to dynamic object. 
        /// Returns standard JSON result
        /// </summary>
        public ActionResult JsonValue(

            [DynamicJson(typeof(Person))]
            JsonValue person
        )
        {
            var x = person.AsDynamic();
            int age = x.Age;

            List<Person> people = new List<Person>();
            for (int i = 0; i < 5; i++)
            {
                string s = string.Format("{0}_{1}", "hardCoded", i.ToString());
                people.Add(new Person(i, s, new Parent(1, "parent")));
            }
            return Json(people);
        }
    }
}

自定义 MVC 行为

对于上面显示的第一种操作,当直接从标准的 .NET 客户端(至少是本文附带的客户端代码)调用此 ASP MVC 控制器代码时,必须执行的一件事是使用自定义的 ActionFilterAttribute 来提供控制器操作模型数据。

下面展示了这一点

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Text;
using System.Json;

namespace CodeStash.Filters
{
    public class JSONFilter : ActionFilterAttribute
    {
        public string Param { get; set; }

        public Type RootType { get; set; }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            try
            {
                string json = filterContext.HttpContext.Request.Form[Param];
                string contentType = filterContext.HttpContext.Request.ContentType;
                
                switch (contentType)
                {
                    //Which is what you get if you use the WebClient in your code.
                    //Which is what the .NET client code is using
                    case "application/x-www-form-urlencoded":
                        if (json == "[]" || json == "\",\"" || String.IsNullOrEmpty(json))
                        {
                            filterContext.ActionParameters[Param] = null;
                        }
                        else
                        {
                            using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
                            {
                                filterContext.ActionParameters[Param] = 
                                    new DataContractJsonSerializer(RootType).ReadObject(ms);
                            }
                        }
                        break;
                    case "application/json":
                        //allow standard Controller/ASP MVC JSONValueProvider to do its work
                        //we can't do a better job than it, so let it deal with it
                        return;
                    default:
                        filterContext.ActionParameters[Param] = null;
                        break;
                }
            }
            catch
            {
                filterContext.ActionParameters[Param] = null;
            }
        }

    }
}

这段代码运行,这要归功于我们如下在附件的演示应用中使用这个专门的 ActionFilterAttribute

[JSONFilter(Param = "person", RootType = typeof(Person))]
public ActionResult Index(Person person)
{
    .....
}

一些更有眼光的你会注意到我们在这里做了两件事:

  1. 如果 HTTP ContentType 是 "application/x-www-form-urlencoded",我们会尝试使用 DataContractJsonSerializer 来为控制器操作模型提供数据。为什么要这样做?嗯,在附件的演示代码中,.NET 客户端应用程序使用 WebClient Web 类,它使用 "application/x-www-form-urlencoded" HTTP ContentType。它不允许你更改,这很公平,因为 WebClient 暴露了一个值集合,可以接受任何东西,所以它必须使用这个 ContentType。你将在本文后面看到更多关于这一点的内容。
  2. 如果 HTTP ContentType 是 "application/json",我们允许 ASP MVC 提供 JSON 数据,因为它内置了对这个的支持,这要归功于内置的 JsonValueProviderFactory。它做得很好,所以让它去做吧。

JSONValue

在继续查看 .NET 客户端和 jQuery 演示之前,我想稍微偏离一下主题,谈谈一个有趣的 API,它来自于 WCF Web API 工作,但也可以作为独立的 nuget 包使用。

这个独立包名为 JSONValue,可以通过在 Visual Studio 中添加包引用来获得。

演示控制器操作的第三个使用了 JSONValue

JSONValue 带来的好处是它提供了一些 JavaScript 对象周围的包装器/转换器,它还支持动态属性。我认为最好的方式之一就是看看它的例子。

所以,就像我们之前通过自定义 ActionFilterAttribute 扩展 ASP MVC 一样,ASP MVC 也通过使用专门的模型绑定器来支持可扩展性,所以让我们看一个例子,好吗?

所以,在我们的控制器中,我们有这个,看它是如何简单地接受一个 JsonValue,并且在实际控制器的操作中,它能够使用 AsDynamic() 方法来获取接收到的类型的正确属性值。

public ActionResult JsonValue(

    [DynamicJson(typeof(Person))]
    JsonValue person
)
{

    var x = person.AsDynamic();
    int age = x.Age;
}

在这里可以看到我正在使用一个特殊的 DynamicJsonAttribute,它看起来像这样:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace RestfulWebSite.ModelBinders
{
    public class DynamicJsonAttribute : CustomModelBinderAttribute
    {
        private Type rootType;

        public DynamicJsonAttribute(Type rootType)
        {
            this.rootType = rootType;
        }

        public override IModelBinder GetBinder()
        {
            return new DynamicJsonBinder(rootType);
        }
    }
}

这个属性负责创建作为被标记对象模型值的数据。它通过使用一个 DynamicJsonBinder 类的内部实例来做到这一点,该实例看起来像这样:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.IO;
using System.Json;
using System.Runtime.Serialization.Json;
using System.Text;

namespace RestfulWebSite.ModelBinders
{
    public class DynamicJsonBinder : IModelBinder
    {
        private Type rootType;
        public DynamicJsonBinder(Type rootType)
        {
            this.rootType = rootType;
        }

        public object BindModel(ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
        {
            try
            {
                var inpStream = controllerContext.HttpContext.Request.InputStream;
                inpStream.Seek(0, SeekOrigin.Begin);
                string bodyText;

                using (StreamReader reader = new StreamReader(
                    controllerContext.HttpContext.Request.InputStream))
                {
                    bodyText = reader.ReadToEnd();
                }

                string contentType = controllerContext.HttpContext.Request.ContentType;
                object result=null;

                switch(contentType)
                {
                    case "application/json":
                    case "application/json; charset=UTF-8":
                        if (!String.IsNullOrEmpty(bodyText))
                        {
                            result= JsonValue.Parse(bodyText);
                        }
                        break;
                    default:
                        result= null;
                        break;
  
                }
                return result;

            }
            catch(Exception ex)
            {
                return null;
            }
        }
    }
}

现在这可能听起来有点多,但希望当你看到调用代码时,一切都会变得清晰。让我们继续看看吧,好吗?

演示:浏览器支持

演示控制器操作中的所有三个都接受由以下三个 jQuery 片段生成的 JSON,它们实际上都相同,除了它们调用的实际 ASP MVC 控制器操作之外。

$(document).ready(function () {
    var person = { Age: 1, Name: "person1" };

    $("#btnActionFilter").click(function () {
        $.ajax({
            url: "/Rest/Index",
            type: "POST",
            dataType: "json",
            contentType: 'application/json',
            data: JSON.stringify(person),
            success: function (response) {
                alert(response);
                $("#personContainer").empty()
                $("#personTemplate").tmpl(response).appendTo("#personContainer");
            }
        });
    });

    $("#btnNoFilterJSON").click(function () {
        $.ajax({
            url: "/Rest/NoFilterJSON",
            type: "POST",
            dataType: "json",
            contentType: 'application/json',
            data: JSON.stringify(person),
            success: function (response) {
                alert(response);
                $("#personContainer").empty()
                $("#personTemplate").tmpl(response).appendTo("#personContainer");
            }
        });
    });

    $("#btnJSONValue").click(function () {
        $.ajax({
            url: "/Rest/JsonValue",
            type: "POST",
            dataType: "json",
            contentType: 'application/json',
            data: JSON.stringify(person),
            success: function (response) {
                alert(response);
                $("#personContainer").empty()
                $("#personTemplate").tmpl(response).appendTo("#personContainer");
            }
        });
    });
});

我还包含了一些(纯粹为了好玩)——使用 jQuery Templates,它允许你重复使用带有占位符的 HTML 片段,占位符将使用 JSON 对象填充。

这是演示代码中的一个 jQuery Template

<div>
    <h1>Results</h1>
    <div id="personContainer"></div>
</div>
    
<script id="personTemplate" type="text/x-jQuery-tmpl">
    <div>
        <p><strong>Person</strong>
        <br/>
        Age:${Age}
        <br/>
        Name:${Name}
        <br/>
        TimeStamp:${TimeStamp}
        <br/>
    </div>
</script>

这基本上就是用来显示控制器操作方法调用结果的。很不错,对吧?jQuery Template 还有更多功能,如果你有时间,值得深入研究。

演示:桌面 .NET 客户端支持

剩下要做的就是向你展示如何使用一个标准的 .NET 客户端来调用 ASP MVC 控制器,该客户端使用标准的 .NET Web API。

对于演示应用程序,客户端代码看起来像这样(我们从这段代码中调用 REST 控制器的 Index 操作)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Collections.Specialized;
using System.Web.Script.Serialization;
using Common;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;
using System.Xml;

namespace ConsoleApplication1
{
    class Program
    {
        internal static DataContractJsonSerializer jss;
        private static WebClient client = new WebClient();
        private static NameValueCollection values = 
                new System.Collections.Specialized.NameValueCollection();

        static void Main(string[] args)
        {
            //Use ModelBinding in WebSite to resolve types to Controller Actions
            Person pers = new Person(1, "name", null);
            AddValue(values,"person", pers);

            //NOTE : WebClient doesn't allow you to set ContentType header to "application/json" when using the UploadValues() method
            //       which is a shame as it would be very nice. But since the WebClient allows
            //       you to add anything to its collection of values, it makes sense to restrict the
            //       WebClient ContentType header to "application/x-www-form-urlencoded" (the default)
            //       as you can't really know what data you are sending, could be anything being sent up
            //       in one go, so what ContentType could be reasonably provided except 
            //       "application/x-www-form-urlencoded"
            //       Also the WebClient is kind of a dumbed down HttpRequest, 
            //       so its more limited, but also more convenient

            Byte[] results = client.UploadValues("https://:8300/Rest/Index", values);
            //get results
            List<Person> people = GetValue<List<Person>>(results);

            Console.ReadLine();
        }

        internal static T GetValue<T>(Byte[] results) where T : class
        {
            using (MemoryStream ms = new MemoryStream(results))
            {
                jss = new DataContractJsonSerializer(typeof(T));
                return (T)jss.ReadObject(ms);
            }
        }

        internal static void AddValue(NameValueCollection values, string key, object value)
        {
            jss = new DataContractJsonSerializer(value.GetType());
            using (MemoryStream ms = new MemoryStream())
            {
                jss.WriteObject(ms, value);
                string json = Encoding.UTF8.GetString(ms.ToArray());
                values.Add(key, json);
            }
        }
    }
}

这都是相当简单的事情。我们只需要使用 WebClientDataContractJsonSerializer 来形成我们的 NameValueCollection 并向网站发出请求。所有都是标准操作,但要让所有这些部分协同工作确实花了我不少时间,所以我认为这篇文章可能会让其他人受益。

正如我一直在强调的,这只需要标准的 .NET 类,零配置,没有特定于调用的端口,只需要网站 URL,我们就自己进行非常精简的序列化。实际上,使用 JSON 作为序列化格式相当不错,因为它与其他序列化格式相比非常轻量,而且也非常易于人类阅读,这是 SOAP 格式有点受苦的地方,我觉得。

更多

自 1. 我写这篇文章以来,一位 codeproject 用户“Simonotti Paolo”也在做和我同样的事情,我们都经历了一个顿悟的时刻,哇,我们不孤单。无论如何,Simonotti 展示了一种可以使用 ContentType application/jsonWebClient 的方法,他好心地同意我在这里包含它。所以如果你真的想使用 ContentType application/json。方法如下:

你的桌面客户端代码将如下所示,而不是我之前的代码:

public static class JsonExtensionMethods
{
    public static T JsonInvoke<T>(this WebClient client, string methodUri, object argument) where T : class
    {
        T res = default(T);
        client.Headers.Add("Content-Type", "application/json");
        byte[] jsonRequest = Encoding.UTF8.GetBytes(argument.ToJson());
        byte[] jsonResponse = client.UploadData(methodUri, jsonRequest);
        res = jsonResponse.JsonDeserializeTo<T>();
        return res;
    }
 
    public static string ToJson(this object obj)
    {
        JavaScriptSerializer jss = new JavaScriptSerializer();
        return jss.Serialize(obj);
    }
 
    public static T JsonDeserializeTo<T>(this byte[] bytes) where T:class
    {
        T res = null;
        DataContractJsonSerializer jss = new DataContractJsonSerializer(typeof(T));
        using (MemoryStream ms = new MemoryStream(bytes))
        {
            res=(T)jss.ReadObject(ms);
        }
        return res;
    }
}

你将在你的桌面应用程序中这样使用它:

string uri="https://:8300/Rest/NoFilterJSON";
 
Person pers1 = new Person(1, "àèìòù", null);
Person pers2 = new Person(2, "àèìòù2", null);
 
Dictionary<string, object> values = new Dictionary<string, object>()
{
    { "person", pers1 },
    { "person2", pers2 }
};
 
//with dictionary
List<Person> people1 = client.JsonInvoke<List<Person>>(uri, values);
 
//with anonymous type
List<Person> people2 = client.JsonInvoke<List<Person>>(uri, new { person=pers1, person2=pers2 } );

我认为这两种方法都有其优点,我将让你选择,你拥有工具,尽情享受吧。

就这样

总之,这就是我现在想说的全部内容。我意识到这不是我通常的 WPF 类型文章,但我认为它仍然有用,并且向你展示了如何创建一些可以从 Web/桌面调用、零配置且代码量很少的代码。

如果你喜欢这篇文章,请花点时间给我一个投票/评论,我将不胜感激。

© . All rights reserved.