5 min read

Integrating Server-Side Code

The code for this sample is contained in the same sample repository, in a WebForm called RouterWithServerPage.aspx. Links to applicable Gists are located throughout.

Nuget Updates

Axios (https://github.com/axios/axios) is a commonly used package with many different SPA frameworks. At the time of writing, the current version of Axios is 0.19.2 (https://github.com/axios/axios/tree/v0.19.2). A nuget search did not yield any promising option for a package that was actively maintained (note to self: add one).

In order to continue writing the article, I downloaded the release from the dist folder and added it to the Scripts folder in my application. That will suffice for the purposes of this article.

ScriptManager Configuration

References:

The ScriptManager element must be configured to be able to call/invoke the page methods that this technique relies on. Without configuration, the script does not process correct.

Axios Configuration

WebForms returns data in a d key in the object response. In order to have the data return properly, the following axios interceptor is needed to extract the response properly:

axios.interceptors.response.use(function (response) {
                // Any status code that lie within the range of 2xx cause this function to trigger
                // Do something with response data
                // Adjust the response here. ASP.NET seems to output this as the d of the response field.
                if (response.data && response.data["d"] !== undefined && response.data["d"] !== null) {
                    response.data = response.data["d"]; // Transform the object now.
                }
                return response;
            }, function (error) {
                // Any status codes that falls outside the range of 2xx cause this function to trigger
                // Do something with response error
                return Promise.reject(error);
            });
Axios Interceptor for WebForms ScriptMethod Response

This code simply examines the server response and adjusts it to the required format.

Data Model

Requests

The most basic request for this example is to just get the list of data from the previous example, but pulling it from the server instead.

The basic class used to act as an object model is as follows:

public class SampleDataModel
    {
        static int idGenerator = 1;

        public int Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
    }
Class used for the sample data in the example
public partial class RouterWithServerData : System.Web.UI.Page
    {
        static List<SampleDataModel> sampleData = new List<SampleDataModel>
        {
            new SampleDataModel{ Id = 1, Title="Item 1", Description = "Description 1" },
            new SampleDataModel {Id =2, Title="Item 2", Description= "Description 2" },
            new SampleDataModel {Id=3, Title="Item 3", Description="Description 3" }
        };

        protected void Page_Load(object sender, EventArgs e)
        {

        }

        [WebMethod(EnableSession = true)]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public static object GetData()
        {
            return sampleData;
        }

        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public static SampleDataModel CreateItem(SampleDataModel data)
        {
            // Figure out a new ID of course
            var lastIdentifier = sampleData.OrderBy(x => x.Id).LastOrDefault();

            if (lastIdentifier == null)
            {
                data.Id = 1;
            }
            else
            {
                data.Id = lastIdentifier.Id + 1;
            }

            sampleData.Add(data);

            return data;
        }

        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public static bool UpdateItem(SampleDataModel data)
        {
            bool result = false;

            if (data != null)
            {
                var updateObject = sampleData.FirstOrDefault(x => x.Id == data.Id);
                if (updateObject != null)
                {
                    updateObject.Title = data.Title;
                    updateObject.Description = data.Description;
                    result = true;
                }
            }

            return result;
        }

        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        public static bool DeleteItem(DeleteRequestObject data)
        {
            var deletedCount = sampleData.RemoveAll(x => x.Id == data.Id);
            return deletedCount > 0;
        }
    }
Code Behind

The code behind listing above the CRUD request WebMethods along with a static array sampleData that is used to demonstrate the concept (and serve as a non-sophisticated stand-in for persistent data).

The router configuration is the same as the previous post. It references ListComponent, DeleteComponent, EditComponent and CreateComponent to get the job done. Before delving into the specifics of the components, axios needs a bit of assistance extracting the data from the API calls that it makes to the page methods.

ScriptMethod Response Processing

Processing the response with the axios package requires an interceptor to extract the data. The response is returned in the d key of the result/response when it completes successfully. Extracting it is straightforward:

// Add a response interceptor
            axios.interceptors.response.use(function (response) {
                // Any status code that lie within the range of 2xx cause this function to trigger
                // Do something with response data
                // Adjust the response here. ASP.NET seems to output this as the d of the response field.
                if (response.data && response.data["d"] !== undefined && response.data["d"] !== null) {
                    response.data = response.data["d"]; // Transform the object now.
                }
                return response;
            }, function (error) {
                // Any status codes that falls outside the range of 2xx cause this function to trigger
                // Do something with response error
                return Promise.reject(error);
            });
Axios Interceptor for ASP.NET WebForms

This code simply checks for the response d key for a successful response, and if a status code is not successful it will reject the Promise.

The ListComponent is simple:

const ListComponent = {
                data() {
                    return {
                        items: []
                    }
                },
                mounted() {
                    var url = '<%= ResolveUrl("~/RouterWithServerData.aspx/GetData") %>'
                    axios.post(url, {
                        headers: [
                            { 'Content-Type': "application/json; charset=utf-8"}
                        ]
                    }).then((response) => {
                        // Ensure there's something to actually assign here.
                        if (response.data) {
                            this.items = response.data;
                        }
                    });
                },
                template: `
                    <div>
                        <h2>Item List</h2>
                        
                        <router-link :to="{name: 'create'}" class="btn btn-link" role="button">Add New Record</router-link>

                        <table class="table">
                            <tr>
                                <th>Record ID</th>
                                <th>Title</th>
                                <th>Actions</th>
                            </tr>
                            <tr v-for="item in items">
                                <td>{{item.Id}}</td>
                                <td>{{item.Title}}</td>
                                <td><router-link :to="{name: 'edit', params: { id: item.Id }}" class="btn btn-link">Edit</router-link> <router-link :to="{name: 'delete', params: { id: item.Id }}" class="btn btn-link">Delete</router-link> </td>
                            </tr>
                        </table>
                        
                    </div>
                `
            };

The call to the server's GetData function is conducted in the mounted method, using axios.post. The ScriptMethod does allow a get request as long as the attribute is set on the method in the code-behind.

The network dev tools demonstrates this call to the server (note the 200 response):

Exploring the response shows the data returned:

GetData Response Preview

As shown, the response is serialized into JSON and stored in the d key of the response object. Once extracted by the interceptor, it is populated onto the table as shown:

Populating the data into the table (ListComponent)

The data is properly displayed as in the previous article's example for the sample route.

Remaining Operations

In the interest of brevity, I won't go through the level of detail (see the previous article in the series for more on this topic). The implementation is similar (axios call to the same WebForm's other ScriptMethods), and the code is available on github (see the RouterWithServerData.aspx set of files for the complete details). URL: https://github.com/mathewgrabau/vue-aspnet-webforms

Closing Remarks

The next step will likely be figuring out an integration for a Vue UI library (there are several nice options) to further modernize the app. Code organization is also very important so that the solution improves both the user experience and maintenance (developer) perspectives.

UPDATE: 19 Apr 2020 - fixed a formatting error (left an editing/draft part in the closing remarks section, removed it)