Generating a JS-client based on a ASP.NET WebAPI Controller

后端 未结 3 1581
忘掉有多难
忘掉有多难 2021-01-30 11:41

In modern web-projects that use RESTful API\'s we often see AJAX-calls like the one below littered around our JavaScript-files.

$.ajax({
    type: \"POST\",
             


        
相关标签:
3条回答
  • 2021-01-30 12:05

    Just found a project called: ProxyApi

    ProxyApi is a library that automatically creates JavaScript proxy objects for your ASP.NET MVC and WebApi Controllers.

    GitHub: https://github.com/stevegreatrex/ProxyApi

    Blog: http://blog.greatrexpectations.com/2012/11/06/proxyapi-automatic-javascript-proxies-for-webapi-and-mvc/

    ProxyApi generated invalid JavaScript for my solution which contained over a hundred separate WebAPI actions. This is probably because ProxyApi does not cover all WebApi features such as custom ActionName attributes. Moreover the ProxyApi library is a bit on the bulky side to my taste. There has to be a more efficient way to do this...

    So I decided to take a look at the ASP.NET WebAPI source code and it turns out WebAPI has self-describing functionality built into it. You can use the following code from anywhere in your ASP.NET solution to access WebAPI metadata:

    var apiExplorer = GlobalConfiguration.Configuration.Services.GetApiExplorer();
    

    Based on the output from apiExplorer.ApiDescriptions, I rolled my own metadata provider:

    public class MetadataController : Controller
    {
        public virtual PartialViewResult WebApiDescription()
        {
            var apiExplorer = GlobalConfiguration.Configuration.Services.GetApiExplorer();
            var apiMethods = apiExplorer.ApiDescriptions.Select(ad => new ApiMethodModel(ad)).ToList();
            return PartialView(apiMethods);
        }
    
        public class ApiMethodModel
        {
            public string Method { get; set; }
            public string Url { get; set; }
            public string ControllerName { get; set; }
            public string ActionName { get; set; }
            public IEnumerable<ApiParameterModel> Parameters { get; set; }
    
            public ApiMethodModel(ApiDescription apiDescription)
            {
                Method = apiDescription.HttpMethod.Method;
                Url = apiDescription.RelativePath;
                ControllerName = apiDescription.ActionDescriptor.ControllerDescriptor.ControllerName;
                ActionName = apiDescription.ActionDescriptor.ActionName;
                Parameters = apiDescription.ParameterDescriptions.Select(pd => new ApiParameterModel(pd));
            }
        }
    
        public class ApiParameterModel
        {
            public string Name { get; set; }
            public bool IsUriParameter { get; set; }
    
            public ApiParameterModel(ApiParameterDescription apiParameterDescription)
            {
                Name = apiParameterDescription.Name;
                IsUriParameter = apiParameterDescription.Source == ApiParameterSource.FromUri;
            }
        }
    }
    

    Use this controller in conjunction with the following view:

    @model IEnumerable<Awesome.Controllers.MetadataController.ApiMethodModel>
    <script type="text/javascript">
        var awesome = awesome || {};
    
        awesome.api = {
            metadata: @Html.Raw(Json.Encode(Model))
        };
    
        $.each(awesome.api.metadata, function (i, action) {
            if (!awesome.api[action.ControllerName]) {
                awesome.api[action.ControllerName] = {};
            }
            awesome.api[action.ControllerName][action.ActionName] = function (parameters) {
                var url = '/' + action.Url;
                var data;
                $.each(action.Parameters, function (j, parameter) {
                    if (parameters[parameter.Name] === undefined) {
                        console.log('Missing parameter: ' + parameter.Name + ' for API: ' + action.ControllerName + '/' + action.ActionName);
                    } else if (parameter.IsUriParameter) {
                        url = url.replace("{" + parameter.Name + "}", parameters[parameter.Name]);
                    } else if (data === undefined) {
                        data = parameters[parameter.Name];
                    } else {
                        console.log('Detected multiple body-parameters for API: ' + action.ControllerName + '/' + action.ActionName);
                    }
                });
                return $.ajax({
                    type: action.Method,
                    url: url,
                    data: data,
                    contentType: 'application/json'
                });
            };
        });
    </script>
    

    The controller will use the ApiExplorer to generate metadata about all available WebAPI actions. The view will render this data as JSON and then execute some JavaScript to transform this data to actual executable JavaScript functions.

    To use this little bit of magic, insert the following line in the head of your Layout page after your jQuery reference.

    @Html.Action(MVC.Metadata.WebApiDescription())
    

    From now on, you can make your WebAPI calls look like this:

    // GET: /Api/Notes?id={id}
    awesome.api.Notes.Get({ id: id }).done(function () {
        // .. do something cool       
    });
    
    // POST: /Api/Notes
    awesome.api.Notes.Post({ form: formData }).done(function () {
        // .. do something cool       
    });
    

    This simple proxy will automatically distinguish query string parameters from request body parameters. Missing parameters or multiple body-parameters will generate an error to prevent typo's or other common WebAPI development errors.

    0 讨论(0)
  • 2021-01-30 12:10

    This excellent another project allows you to do what you asked for. This poject auto-generates JavaScript proxies for MVC and WebApi controllers. And, this project covers WebApi features such as custom ActionName attributes. With this project, you will have also the Intellisense.
    http://jsnet.codeplex.com/

    Example of Intellisense

    window.test = function test() {
    /// <summary>
    ///This example works.
    ///You have the Intellisense. It's great!!!
    ///No hard coded url.
    ///</summary>
    
    //-- settings of ajax request.
    var a = $dpUrlSet.Customer.Create.$action0.$AjaxSettings();
    
    //-- your parameters of action method
    a.data.name = "Scott Gu";
    a.data.address = "Somewhere in Redmond";
    
    //-- stringify
    a.data = JSON.stringify(a.data);
    
    //-- send ajax request
    var xhr = $.ajax(a);
    
    xhr.success(function (id) {
        /// <summary>Response of ajax request</summary>
    
        //-- settings of ajax request.
        var a = $dpUrlSet.Customer.Update.$action0.$AjaxSettings();
    
        //-- your parameters of action method
        a.data.id = id;
        a.data.name = "Scott Gu";
        a.data.address = "Somewhere in Seattle";
    
        //-- stringify
        a.data = JSON.stringify(a.data);
    
        //-- send ajax request
        var xhr = $.ajax(a);
    
    });
    }
    
    0 讨论(0)
  • 2021-01-30 12:12

    I'm working on the Swagger open-source toolchain NSwag for .NET: With this tool you can generate the TypeScript client for a single or multiple Web API controllers.

    In the UI just

    1. Select the Web API DLL
    2. Select the controller classes
    3. Generate TypeScript client code (in your case, select the JQueryCallbacks or JQueryPromises template)

    Have a look at http://nswag.org

    FYI: TypeScript is a language which gets transpiled to JavaScript

    0 讨论(0)
提交回复
热议问题