Development Practice Based on ArcGIS API 4.12 for JS-JavaScript Calling Web API

In the previous article JavaScript calls Web Service requests In this article, we demonstrated how to use JavaScript to invoke the Web Service service of ASP.NET. Let's take a look at using JavaScript to invoke the Web API. As for the advantages, disadvantages and differences between Web API and Web Service, it is not the focus of this article. Readers can query by themselves.

1. Create a Web API project

The environment I use here is Windows 7 sp1, and the development environment is Visual Studio 2017. One advantage of VS2017 is that if the version of the. Net Framework used by your project is higher than the version of your deployment environment, VS2017 packages all dependent assemblies together. As shown in the figure below, create an ASP.NET Web application in VS2017. Framework 4.5, because subsequently to solve cross-domain problems, you need to refer to 4.5 packages.

Then in the pop-up "New ASP.NET Web Application" window, select the Web API and determine, as shown in the following figure:

 

Just a moment, VS2017 automatically creates a series of folders and programs for you, as shown in the following figure:

                                                           

2. Create Model Classes and Controller Classes

(1) Create Model classes

In the solution, right-click the "Models" folder, add an empty class, and then you can modify the empty class. Here's a key reminder that the attribute name of your empty class should be consistent with the field name in the database, which can save a lot of trouble later. If the names are identical, the system can automatically convert the data set queried in the database into your custom class. Next, I'll post my modified empty class, Device.cs:

public class Device
    {
        private string _deviceCode;
        /// <summary>
        /// Equipment Coding
        /// </summary>
        public string DeviceCode
        {
            get { return _deviceCode; }
            set { _deviceCode = value; }
        }
        private int _devicePower;
        /// <summary>
        /// Is the Equipment Low Voltage
        /// </summary>
        public int Power
        {
            get { return _devicePower; }
            set { _devicePower = value; }
        }
        private int _deviceOnline;
        /// <summary>
        /// Is the device online?
        /// </summary>
        public int Online
        {
            get { return _deviceOnline; }
            set { _deviceOnline = value; }
        }
        private int _deviceOpen;
        /// <summary>
        /// Whether the device is open or not
        /// </summary>
        public int Open
        {
            get { return _deviceOpen; }
            set { _deviceOpen = value; }
        }
        private float _deviceDegree;
        /// <summary>
        /// Equipment tilt angle
        /// </summary>
        public float Degree
        {
            get { return _deviceDegree; }
            set { _deviceDegree = value; }
        }
        private double _lat;
        /// <summary>
        /// Latitude of equipment
        /// </summary>
        public double Latitude
        {
            get { return _lat; }
            set { _lat = value; }
        }
        private double _log;
        /// <summary>
        /// Equipment longitude
        /// </summary>
        public double Longitude
        {
            get { return _log; }
            set { _log = value; }
        }
        private DateTime _updateTime;
        /// <summary>
        /// Equipment data update time
        /// </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");
        }
    }

Again, the attribute name of the custom class should be consistent with the field name in the database. So the Model class is created.

(2) Create Controller classes

The Controller class is used to manipulate the Model class and provide an access interface to the outside world. We still right-click the "Controller" folder under the solution, select Add, then click "Controllers", select "Web API 2 Controller - Empty" in the pop-up "Add Base" window, and then click OK, as shown in the following figure:

Then in the naming window that pops up later, the name here can't be arbitrary, because the name of the Model class just created is Device, so the name of the Controller class here should be DeviceController, which must correspond to the name of the Model class, otherwise the system can't map. The "DeviceController.cs" created is also an empty class, which needs to be modified to control the Model class. The following code:

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

The above code is DeviceController, and you can see that there is a line of code modification on the DeviceController class: [RoutePrefix("apis/device")], which means that the interfaces in my custom route, DeviceController, are all the URLs that have been successfully published later,'http://localhost/WebTes'. T/apis/device/XXX is accessed, and "XXX" is the name of each interface. In this limited space, I will talk about the first interface "GetAllDevice()", which is decorated with a [Route("getall"). When my Web API is published successfully, it can be accessed through the URL "http://localhost/WebTest/apis/device/getall". The second modification "[HttpGet, HttpPost]" means that our "getall" interface allows "GET, POST" to be invoked in two ways. Notice that my "GetAllDevice" return value is a list of custom classes, List < Device >, which is the feature of the Web API, which can convert custom classes to JSON format.

It is also important to note that there is such a line of code in the "GetAllDevice" interface:

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

This code converts the DataTable from a database query into a list of custom classes. The focus is on the "ToList" function. Here's the code for this function:

        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) //There are matched headers
                            {
                                value = row[pro.Name].ToString().Replace("$", "").Replace(",", ""); //Extracting data from dataRow
                                pro.SetValue(objT, Convert.ChangeType(value, Type.GetType(Nullable.GetUnderlyingType(pro.PropertyType).ToString())), null); //Assignment
                            }
                            else
                            {
                                value = row[pro.Name].ToString().Replace("%", ""); //There are matched headers
                                pro.SetValue(objT, Convert.ChangeType(value, Type.GetType(pro.PropertyType.ToString())), null);//Assignment
                            }
                        }
                    }
                }

                return objT;
            }).ToList();
        }

This function first takes the column name of the Datatable queried by the database into an array, and then puts the attribute name of the custom class into an array. Traverse the attribute names in the custom classes, determine whether the attribute names in each custom class are consistent with the column names of the current DataTable, and assign values accordingly. So as mentioned earlier, when creating Model classes, we must pay attention to keeping the same order with the database, and the best way is to use tools to directly generate the corresponding classes of the database.

With these steps, our first Web API was created successfully.

3. Running and Browsing

In the first part, we created a Web API application, but if you choose "empty", VS2017 will not generate browsing content for you, so I recommend that you choose a Web API application, as shown below.

Assuming you choose the Web API in the figure above, you can start debugging directly in VS2017 (you can not deploy it on IIS for the time being). Direct debugging with the default IIS Express of VS2017:

                         

After startup, your browser opens and the following interface pops up (note that if you choose an empty application, the following content will not be available):

This interface is automatically generated for you by VS2017. In the red box above, you can find your custom interface:

The process above is to publish a method of customizing the interface, and so on, you can then add other Model s and Controller classes to enrich your project.

4. Publishing and deploying

(1) Publication

Under the solution, right-click your application, I am "WebTest" here, not the right-click solution, find the "Publish" menu, as follows:

After clicking on the "Publish" menu, we first need to select the publishing target. Here we choose to publish to IIS, and then we need to configure the publishing options, as follows:

    

Then we click "Create Profiles" in the lower right corner, then "Publish Method" in the "Connect" option to select "File System" and "Target Location" to select a local folder (random location, actually the location of the publish package), as follows:

Next, in the Settings option, we configure the Release publishing option and check the first two items:

  

Finally, the release file will be generated at the "target location" after saving the startup. This release file can be deployed on IIS of other machines or on IIS of the local machine.

(2) Deployment to IIS

Deployment to IIS is not much to say here. You can use the default application pool and start creating a new application pool. I usually like to create a new application pool and then deploy the release file above to the bottom of the new application pool. Just remember to distinguish the port from the default port.

5. JavaScript calls Web API

(1) Calls without parameters

JavaScript calling Web API is not very different from calling Web Service. You can use the xhr mode of Dojo or the $. Ajax mode of jQuery. Here I use the xhr mode of dojo. JQuery's $.ajax approach allows readers to test themselves. Here is the code:

require([
"dojo/_base/xhr"
function(xhr) 
{
    xhr.post({
        //Request self-built Web API
        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);
            }
    });
}
);

Careful readers can see that in the Model Device Controller of ASP.NET Web API, GetAllDevice() returns a list of custom classes. Called through JavaScript, it returns a list, and can be converted into a JSON object, which is much more convenient than Web Service.

Think about it or paste it in jQuery's $. ajax mode. Readers will debug it to see the difference.

$.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) Calls with parameters

Here is the call with parameters. What I said is that the parameters in the Web API are custom parameters, not parameters like string, int, double and so on. Here is an interface I defined. The function of this interface is to verify the username and password on the server side according to the username and password passed in by the client side. Correct. The parameters are also custom classes:

        [Route("checkout")]
        [HttpGet, HttpPost]
        public int UserCheck(User jsonUser)
        {
            if (jsonUser == null )
            {
                return -1;
            }
            else
            {
                try
                {
                    string username = jsonUser.UserName;
                    string userpassword = jsonUser.UserPassword;
                    //Query users
                    string userSql = "select * from smartcover.myusers where username='" + username + "'";
                    DataTable dt = MySqlHelper.getRows(userSql);
                    if (dt == null || dt.Rows.Count < 1)
                    {
                        return 2;//user does not exist
                    }
                    else
                    {
                        string encryptdPassword = dt.Rows[0]["userpassword"].ToString();
                        if (encryptdPassword == userpassword)
                        {
                            return 1;//User Authentication Successful
                        }
                        else if (userpassword == Decrypt(encryptdPassword))
                        {
                            return 0;//User Authentication Successful
                        }
                        else
                        {
                            return 3;//Incorrect user password
                        }
                    }
                }
                catch
                {
                    return -1; //Create failure
                }
            }
        }

The above code is the Web API code on ASP.NET side. This "UserCheck()" function is named "checkout" interface, and the parameter is a custom class User. Let's look at how the JavaScript side calls this interface:

var userJson={
    UserName:myusername,
    UserPassword:myuserpassword
};
require(["dojo/_base/xhr"],
    function(xhr) 
    {
        xhr.post({
        //Request self-built Web API
        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);
        }
        });
    }
);

The code userJson above is a JSON object created based on the user's input username and password. Note that this JSON object is not converted into a JSON string here. Let me say a few more words. All the requests I use here are POST requests. Readers can also try GET requests. There is no difference between Web APIs without parameters. For Web APIs with parameters, there is a big difference between the two. The data requested by get will be attached to the URL (that is, put the data in the HTTP header), while the data requested by post will be attached to the URL. The request is placed in the package of the HTTP protocol package. If you have a custom class parameter, you need to comment out this line in the code headers: {"Content-Type": "application/json"} above, because if you add this line of code, the request will convert the parameter to a JSON string, but the parameter in your Web API is an entity class, and the system will not automatically. Help you change, and you'll make mistakes. If you have to add this line of code, you need to serialize it in your Web API using "Json Deserialize" to serialize the JSON string you passed back into your entity class. For more details, see JavaScript calls Web Service requests  . I refer to this aspect. WebApi Interface Parameters: Detailed Parametric Explanation.

That is, if the parameters of your Web API are entity classes, then the parameters should be the corresponding JSON objects when JavaScript requests. Readers of basic types of parameters can also test themselves.

6. Cross-domain issues of Web API

There is a cross-domain problem with JavaScript calling Web Service. I am JavaScript calls Web Service requests Some solutions are introduced. Similarly, I also encounter cross-domain problems in Web API. Compared with cross-domain problems in Web Service, cross-domain problems in Web API are more convenient to solve. There is no need to add some cross-domain attributes to the HTTP response header of the IIS server. Especially after using VS2017, it makes the problem solving more convenient.

Through the NuGet plug-in, you can load the System.Web.Http.Cors file, as shown in the following figure:

As shown in the figure above, under your solution, right-click "Reference" and then click "Manage NuGet Package" to pop up the following form:

Search for "cors" in the search box, the system will search for relevant packages, find "Microsoft.AspNet.WebApi.Cors", then download the installation, after installation, automatically add System.Web.Http.Cors reference, as shown in the following figure:

         

After adding the System.Web.Http.Cors reference, you can find the file "webApiConfig.cs" in the "App_Start" folder of the solution, then double-click the code entering the file, and add the code "using System.Web.Http.Cors;" in the namespace. As shown in the following figure

Then add the following code into the function "Register()":

                 //Configuration Solves the Problem of js Cross-Domain Access
                var cors = new EnableCorsAttribute(ConfigurationManager.AppSettings["origins"], "*", "*");
                config.EnableCors(cors);

The above two lines of code register cross-domain attributes in the Web API, while the code "Configuration Manager. AppSettings ["origins"]" actually reads the configuration values from the "Web.congfig" file, so that we do not need to modify the source code when deploying. Then, in the project's "Web. congfig" file, you need to configure the cross-domain attribute values, as follows. Suppose your Web API uses the "localhost: 8080" port, and your JavaScript code is deployed on the "localhost: 8088" port. Although on the same machine, it belongs to different ports, which is cross-domain. So, in the "Web.congfig" file, we allow "localhost:8088" to access the Web API across domains using the "localhost:8080" port.

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

When debugging and running, cross-domain problems should be solved.

Tags: JSON Javascript IIS Database

Posted on Tue, 10 Sep 2019 22:41:32 -0700 by buluk21