基于ArcGIS API 4.12 for JS 的开发实践(六)——JavaScript调用WebAPI

在前面的文章JavaScript调用WebService请求 中,我们演示了如何使用JavaScript来调用ASP.NET的WebService服务,这次我们看一看使用使用JavaScript来调用WebAPI。至于WebAPI和WebService的优缺点以及区别,不是本文的重点,读者可以自行查询。

1、创建WebAPI项目

我这里使用的环境是Windows 7 sp1,开发环境是Visual Studio 2017。VS2017有一样好处就是,你项目使用的.Net Framework的版本如果高于你部署环境的版本,VS2017会把所有依赖的程序集打包在一起。如下图所示,在VS2017中创建一个ASP.NET Web应用程序。框架选4.5,因为随后要解决跨域问题的时候,需要引用4.5的程序包。

然后在弹出的“新建ASP.NET Web应用程序”窗口中,选择Web API 并确定,如下图所示:

稍等片刻,VS2017自动为你创建一系列文件夹和程序,如下图所示:

                                                           

2、创建Model类和Controller类

(1)创建Model类

在解决方案中,右键“Models”文件夹,添加一个空的类,然后你就可以改造这个空类。这里重点提醒一下,如果你这个空类是跟你数据库中某个表一致的,那你这个空类的属性名最好跟数据库中的字段名保持一致,这样后面可以省去很多麻烦。如果名称一致,可以让系统自动把数据库中查询到的数据集转换成你的自定义类。下面贴一下,我的这个改造后的空类,也就是自定义类(Device.cs):

public class Device
    {
        private string _deviceCode;
        /// <summary>
        /// 设备编码
        /// </summary>
        public string DeviceCode
        {
            get { return _deviceCode; }
            set { _deviceCode = value; }
        }
        private int _devicePower;
        /// <summary>
        /// 设备是否低压
        /// </summary>
        public int Power
        {
            get { return _devicePower; }
            set { _devicePower = value; }
        }
        private int _deviceOnline;
        /// <summary>
        /// 设备是否在线
        /// </summary>
        public int Online
        {
            get { return _deviceOnline; }
            set { _deviceOnline = value; }
        }
        private int _deviceOpen;
        /// <summary>
        /// 设备是否打开
        /// </summary>
        public int Open
        {
            get { return _deviceOpen; }
            set { _deviceOpen = value; }
        }
        private float _deviceDegree;
        /// <summary>
        /// 设备倾斜角度
        /// </summary>
        public float Degree
        {
            get { return _deviceDegree; }
            set { _deviceDegree = value; }
        }
        private double _lat;
        /// <summary>
        /// 设备纬度
        /// </summary>
        public double Latitude
        {
            get { return _lat; }
            set { _lat = value; }
        }
        private double _log;
        /// <summary>
        /// 设备经度
        /// </summary>
        public double Longitude
        {
            get { return _log; }
            set { _log = value; }
        }
        private DateTime _updateTime;
        /// <summary>
        /// 设备数据更新时间
        /// </summary>
        public DateTime UpdateTime
        {
            get { return _updateTime; }
            set { _updateTime = value; }
        }

        public Device()
        {
            _deviceCode = string.Empty;
            _deviceOpen = -1;
            _devicePower = -1;
            _deviceOnline = -1;
            _deviceDegree = -1;
            _lat = 0;
            _log = 0;
            _updateTime = DateTime.Parse("1990/1/1 00:00:00");
        }
    }

再提醒一下,自定义类的属性名,最好跟数据库中的字段名保持一致,顺序也一致!这样这个Model类就创建好了。

(2)创建Controller类

Controller类是用来操作Model类,并且向外提供访问接口的。我们依然在解决方案下面的“Controller”文件夹右键单击,选择添加,然后点击“控制器(Controllers)”,在弹出的“添加基架”的窗口中选择“Web API 2 控制器 - 空”,然后点击确定,如下图所示:

然后在随后弹出的命名窗口中,这里的名字不能随意,因为刚才创建的Model类的名字是“Device”,那么这里的Controller类的名字就应该是“DeviceController”,一定要跟Model类名称对应起来,不然系统映射不过去。创建好的“DeviceController.cs”也是一个空类,需要我们改造一下用来控制Model类。如下代码:

    [RoutePrefix("apis/device")]
    public class DeviceController : ApiController
    {
        [Route("getall")]
        [HttpGet, HttpPost]
        public IEnumerable<Device> GetAllDevice()
        {
            DataTable dt = MySqlHelper.getRows("SELECT * FROM smartcover.smc ");
            List<Device> devices = JsonHelper.ToList<Device>(dt);
            return devices;
        }
        [Route("getbycode/{code}")]
        [HttpGet]
        public Device GetDeviceByCode(string code)
        {
            string sql = "SELECT * FROM smartcover.smc where deviceCode = '" + code + "'";
            DataTable dt = MySqlHelper.getRows(sql);
            List<Device> results = JsonHelper.ToList<Device>(dt);
            var product = results.FirstOrDefault((p) => p.DeviceCode == code);
            if (product == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            return product;
        }
        [Route("getbytime/{datetime}")]
        [HttpGet, HttpPost]
        public IEnumerable<Device> GetDeviceOneDay(string datetime)
        {
            if (datetime == null || datetime == "") return null;
            DateTime result;
            bool isRight = DateTime.TryParse(datetime, out result);

            if (!isRight) return null;
            string s = MySqlHelper.getDayStart(datetime);
            string e = MySqlHelper.getDayEnd(datetime);

            string sql = "SELECT * FROM smartcover.smc where updatetime between '" + s + "' and '" + e + "'";
            DataTable dt = MySqlHelper.getRows(sql);
            List<Device> results = JsonHelper.ToList<Device>(dt);
            return results;
        }
    }

 上面这段代码就是“DeviceController”,可以看到,在“DeviceController”类的上面有一行代码修饰:

                        [RoutePrefix("apis/device")]

这里的意思是,我自定义的路由,“DeviceController”里面的接口都是后面发布成功后的URL:“http://localhost/WebTest/apis/device/XXX”进行访问,而“XXX”则是每一个接口的名称。这里篇幅所限,我就说一下第一个接口“GetAllDevice()”,在这个接口上面有一个[Route("getall")]进行修饰,

                        [Route("getall")]

那么这个[Route("getall")]的意思是,我的WebAPI发布成功后,则可以通过URL:“http://localhost/WebTest/apis/device/getall”进行访问。接着第二个修饰“[HttpGet, HttpPost]”,这表示,我们这个“getall”接口允许“GET、POST”两种方式进行调用。大家注意我这个“GetAllDevice”的返回值,是一个自定义类的列表List<Device>,这就是WebAPI的特性,可以把自定义类转为JSON格式。

另外需要注意的是“GetAllDevice”这个接口里面有这么一行代码:

            List<Device> devices = JsonHelper.ToList<Device>(dt);

这句代码就是把从数据库查询得到的DataTable转换为自定义类列表。重点是“ToList”这个函数,下面是这个函数的代码:

        public static List<T> ToList<T>(this DataTable dt)
        {
            var columnNames = dt.Columns.Cast<DataColumn>()
                .Select(c => c.ColumnName.ToUpper())
                .ToList();

            var properties = typeof(T).GetProperties();

            return dt.AsEnumerable().Select(row =>
            {
                var objT = Activator.CreateInstance<T>();
                string value = "";
                foreach (var pro in properties)
                {
                    if (columnNames.Contains(pro.Name.ToUpper()))
                    {
                        value = row[pro.Name].ToString();
                        if (!string.IsNullOrEmpty(value))
                        {
                            if (Nullable.GetUnderlyingType(pro.PropertyType) != null) //存在匹配的表头
                            {
                                value = row[pro.Name].ToString().Replace("$", "").Replace(",", ""); //从dataRow中提取数据
                                pro.SetValue(objT, Convert.ChangeType(value, Type.GetType(Nullable.GetUnderlyingType(pro.PropertyType).ToString())), null); //赋值操作
                            }
                            else
                            {
                                value = row[pro.Name].ToString().Replace("%", ""); //存在匹配的表头
                                pro.SetValue(objT, Convert.ChangeType(value, Type.GetType(pro.PropertyType.ToString())), null);//赋值操作
                            }
                        }
                    }
                }

                return objT;
            }).ToList();
        }

这个函数,首先把数据库查询到的Datatable的列名取到数组中,然后再把自定义类的属性名也放到一个数组中。遍历自定义类中的属性名,判断每一个自定义类的中的属性名是否跟当前DataTable的列名一致,一致则进行赋值。所以前面讲到,创建Model类的时候一定要注意跟数据库保持一致,并且顺序也要一致,最好是使用工具直接生成数据库对应的类。

通过上述这些步骤,我们的第一个WebAPI也就创建成功了。

3、运行和浏览

在第一部分内容中,我们创建的是WebAPI应用程序,但是如果你选择的是“空”,VS2017不会为你生成浏览内容,所以我这里建议还是选择WebAPI应用程序,如下图

假设你上图中选择的是Web API 则你可以在VS2017中直接启动调试了(可以暂时不用部署在IIS上)。直接用VS2017默认的IIS Express进行调试:

                         

启动后则会打开你的浏览器并弹出如下界面(注意,如果你选择的是空的应用程序,则不会下面的内容):

这个界面就是VS2017自动为你生成的,在上图红框中,你可以找到你自定义的接口:

上面的过程是发布一个自定义接口的方法,如此类推,你可以接着加入其它的Model和Controller类,来丰富你的工程项目。

4、发布和部署 

(1)发布

在解决方案下面,右键你的应用程序,我这里是“WebTest”,不是右键解决方案,找到“发布”菜单,如下图:

点击“发布”菜单后,首先需要选择发布的目标,这里我们选择发布到IIS,然后需要配置一下发布的选项,如下图:

    

然后我们点击右下角的“创建配置文件”,然后在“连接”选项里面的“发布方法”选择“文件系统”,并在“目标位置”选择一个本地的文件夹(随便位置,其实就是发布包的位置),如下图:

再接着,我们在“设置”选项中,配置Release发布选项,并勾选前两项:

  

最后,保存启动之后会在“目标位置”生成发布文件,这个发布文件你可以部署到其他机器的IIS上,也可以部署到本机的IIS上。

(2)部署到IIS

部署到IIS这里就不多讲了,发布后的文件,你可以拷贝到电脑任何位置上,然后你可以使用默认的IIS应用程序池装载你发布的WebAPI,也开始重新创建一个应用程序池装载你发布的WebAPI。我一般喜欢创建一个新的应用程序池,然后把上面的发布文件,部署到这个新的应用程序池下面。端口记得跟默认的端口区别开来就行。

5、JavaScript调用WebAPI

(1)无参数的调用

JavaScript调用WebAPI其实跟调用WebService差别不是很大,可以用dojo的xhr方式,也可以用jQuery的$.ajax方式,这里我就用dojo的xhr方式。jQuery的$.ajax方式读者可以自行测试。下面是代码:

require([
"dojo/_base/xhr"
function(xhr) 
{
    xhr.post({
        //请求自建的WebAPI
        url:"http://localhost:8088/WebTest/apis/device/getall",
        headers: { "Content-Type": "application/json; charset=utf-8" },
        crossdomin:true,
        handleAs : "json",
        load : function(data) 
            {
                //dosomting
                data.forEach(function (json,index)
                {
                   console.log(json);
                });
                console.log("success");
            },
        error:function (error) 
            {
                console.log("error"+error);
            }
    });
}
);

细心地读者可以看到,我在ASP.NET WebAPI的Model“DeviceController”中,GetAllDevice()这个函数返回的是一个自定义类列表,通过JavaScript调用,返回的也是一个列表,并且可以通过转换成一个JSON对象,相比WebService确实方便了很多。

想了想还是贴一下用jQuery的$.ajax方式,解读就不细说了,读者自行调试一下看看区别。

$.ajax({
        type: "POST",
        url: "http://localhost:8088/WebTest/apis/device/getall",
        contentType: "application/json; charset=utf-8",
        dataType: "json"
       }).done(function(res)
              {
                //dosomting
                res.forEach(function (json,index)
                {
                   console.log(json);
                });
                console.log("success");
       }).fail(function(a, b, c, d)
       {
                alert("Failure: " 
                    + JSON.stringify(a) + " " 
                    + JSON.stringify(b) + " " 
                    + JSON.stringify(c) + " " 
                    + JSON.stringify(d) );
        });

(2)带参数的调用

这里的带参数调用,我说的是WebAPI中的参数是自定义参数,而不是常规的string、int、double等这一类的参数,下面是我自定义的一个接口,,这个接口的功能就是根据客户端传入的用户名和密码,服务器端验证用户名和密码是否正确。带的参数也是自定义的类:

        [Route("checkout")]
        [HttpGet, HttpPost]
        public int UserCheck(User jsonUser)
        {
            if (jsonUser == null )
            {
                return -1;
            }
            else
            {
                try
                {
                    string username = jsonUser.UserName;
                    string userpassword = jsonUser.UserPassword;
                    //查询用户
                    string userSql = "select * from smartcover.myusers where username='" + username + "'";
                    DataTable dt = MySqlHelper.getRows(userSql);
                    if (dt == null || dt.Rows.Count < 1)
                    {
                        return 2;//用户不存在
                    }
                    else
                    {
                        string encryptdPassword = dt.Rows[0]["userpassword"].ToString();
                        if (encryptdPassword == userpassword)
                        {
                            return 1;//用户验证成功
                        }
                        else if (userpassword == Decrypt(encryptdPassword))
                        {
                            return 0;//用户验证成功
                        }
                        else
                        {
                            return 3;//用户密码不正确
                        }
                    }
                }
                catch
                {
                    return -1; //创建失败
                }
            }
        }

上面代码是ASP.NET端的WebAPI代码,这个“UserCheck()”函数被命名为“checkout”接口,参数是一个自定义类User。下面我们看看JavaScript端怎么来调用这个接口的:

var userJson={
    UserName:myusername,
    UserPassword:myuserpassword
};
require(["dojo/_base/xhr"],
    function(xhr) 
    {
        xhr.post({
        //请求自建的WebAPI
        url:"http://localhost:8088/WebTest/apis/user/checkout",
        //headers: { "Content-Type": "application/json" },
        postData:userJson,
        handleAs : "json",
        load : function(data) 
        {
            //dosomting
            var jb=JSON.parse(data);
            console.log(jb);
        },
        error:function (error) 
        {
            console.log("error"+error);
        }
        });
    }
);

上面的代码userJson,是根据用户输入的用户名和密码创建的一个JSON对象,注意,这里并没有把这个JSON对象转换成JSON字符串。这里多说几句,我这里用的请求全部是POST,读者也可以试试GET请求,对于没有参数的WebAPI两者没什么区别,而对于有参数的WebAPI,两者的区别就大了,就是get请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),而post请求则是放在http协议包的包体中。如果是带的是自定义类参数,上面代码“headers: { "Content-Type": "application/json" }”这一行,就需要把它给注释掉,因为如果你加上这行代码,这个请求会把参数转换问JSON字符串,但是你的WebAPI中的参数是实体类,系统并不会自动帮你转换,就会出错。如果非要加上这行代码,第一需要把你的WebAPI参数改为String类型,在你的WebAPI中使用“JsonDeserialize”进行序列化,重新把你传过来的JSON字符串序列化成你的实体类。具体详情,请看JavaScript调用WebService请求 。这方面内容我参考了WebApi 接口参数:传参详解

也就是说,如果你的WebAPI的参数是实体类,那么在JavaScript请求的时候,参数应该是对应的JSON对象。对于基础类型的参数读者也可以自行测试一下。

6、WebAPI的跨域问题

JavaScript调用WebService存在跨域问题,我在JavaScript调用WebService请求中介绍了一些解决办法,同样WebAPI中我也同样碰到了跨域问题,相对于WebService的跨域,WebAPI的跨域问题解决的更方便一些。不需要在IIS服务器的“HTTP响应标头”上添加一些跨域的属性。特别是使用VS2017后,使得问题解决的更方便。

通过NuGet插件,可以加载System.Web.Http.Cors文件,如下图所示:

如上图所示,在你的解决方案下面,右键“引用”,然后点击“管理NuGet程序包”,弹出如下窗体:

    

在搜索框中搜索“cors”,系统会搜索相关的程序包,找到“Microsoft.AspNet.WebApi.Cors”,然后下载安装,安装完毕后,会自动添加System.Web.Http.Cors引用,如下图所示:

         

添加好这个System.Web.Http.Cors引用后,就可以在解决方案的“App_Start”文件夹中找到“webApiConfig.cs”这个文件,然后双击进入这个文件的代码,在命名空间上加入“using System.Web.Http.Cors;”这句代码。如下图所示

然后在函数“Register()”里面加入如下代码:

                 //配置解决js跨域访问的问题
                var cors = new EnableCorsAttribute(ConfigurationManager.AppSettings["origins"], "*", "*");
                config.EnableCors(cors);

上面这两行代码把跨域属性注册的WebAPI中,而“ConfigurationManager.AppSettings["origins"]”这句代码,其实就是从“Web.congfig”文件中读取配置的值,这样我们部署的时候不用修改源码了。那么,在项目的“Web.congfig”文件中就要把跨域的属性值配置好,如下代码。这里假设你的WebAPI部署的是“localhost:8080”端口,而你的JavaScript代码是部署在“localhost:8088”这个端口上,虽然在同一台机器上,但是分属不同端口,这就跨域了。所以,在“Web.congfig”文件中我们要允许JavaScript项目部署的“localhost:8088”这个端口,能够跨域访问WebAPI部署的“localhost:8080”端口。

<appSettings>
    <add key="origins" value="http://localhost:8088,http://192.168.1.108:9012" />
  </appSettings>

在上面“Web.congfig”文件中,你可以设置多个域值,中间用逗号分开。这时候,有细心的读者就问了,干嘛设置一个个的域值啊,我干脆设置成“*”多好啊。您还别说,我开始就是设置的“*”,可是一调试还是会报“blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.”这个错误。但是我单独设置成一个个URL,就可以解决了,你说这怪不怪。希望有人能帮我解答这个疑惑。

另外,CORS的浏览器支持情况,对IE8、9是部分支持的。网上说的解决方案都是Internet Explorer 8 、9使用 XDomainRequest 对象实现CORS。这样似乎有些复杂了,于是网上一顿搜索。最后发现在调用处指定 jQuery.support.cors = true; 这一句就能解决IE8、9的问题了。

调试运行,我这里是可以解决了,也希望能解决你的一部分问题,大家有什么疑惑的可以讨论讨论。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值