Skip to content

Gufalagupagup/raml-dotnet-tools

 
 

Repository files navigation

RAML Tools for .NET

The RAML Tools for .NET allows developers to easily integrate and consume APIs that expose a RAML definition, or create a new ASP.NET Web API implementation using a contract-first approach from a previously created RAML definition. See http://raml.org for information on RAML (RESTful API Markup Language).

The tools are provided as a Visual Studio extension, allowing simple and natural integration of RAML into a normal development workflow.

A single installation package provides support for both client and service code-generation scenarios.

Supported Scenarios

API Client Code from a RAML Definition

You can generate API client code from a RAML definition, which lets client applications consume existing APIs from a RAML definition.

In Visual Studio .NET, you can add a reference to a RAML definitio, and a strongly-typed client proxy is generated in the project. A local copy of the RAML definition stores as a file within the Visual Studio project, which is kept in sync with the original RAML definition.

If the remote API does not provide a RAML definition, you can use a self-managed local definition to allow rapid generation of the client code in a declarative manner.

ASP .NET Web API Implementation from a RAML Definition

You can generate an ASP .NET Web API implementation from a RAML definition, which lets you create a new ASP .NET Web API from a new or existing RAML definition.

From within Visual Studio .NET, you can add a RAML definition from which the tool generates an initial ASP .NET Web API implementation. This implementation includes controller classes and route registrations that map to the resources exposed in the RAML definition and also includes model classes for their representation. The model classes are generated from the available JSON schemas.

In code, the Web API definition and implementation are logically separated. The generated code representing the definition is driven by the RAML definition allowing you to focus exclusively on the implementation classes. This separation of concerns allows iterative evolution of the API with non-destructive forward engineering of the code-based definition.

As a convenience, a local endpoint is registered to provides a browser-accessible API Console for the RAML-enabled implementation. This gives an easily navigable view of the API, including full documentation of routes, resource schema, and includes examples.

Prerequisites

  • Visual Studio 2013 Update 3

  • .NET Framework 4.5 or higher

  • RAML 0.8 compatible endpoint

  • Supported languages: C# (other languages indirectly)

Installation

  1. Run the RAML tools for Visual Studio Tools extension (VSIX) included in this package (ensure that Visual Studio 2013 is closed).

  2. On the initial screen select Visual Studio 2013 and click Install.

    RAML NET VSIXInstaller
  3. Wait for the installer to complete and click the Close button.

Generating an API Client

  1. Start Visual Studio 2013 and create a new project that consumes the API, or open an existing project.

  2. In the Solution Explorer right-click the References node for the selected project and select the Add RAML Reference command.

    RAML NET SolutionExplorer
  3. Specify the URL of the RAML definition and click the Go button, or use the Browse button to select the file from the local filesystem.

    RAML NET AddRAMLReference
  4. The RAML definition is presented together a preview of the available resources. When ready, click the OK button to begin generating the API client. Optionally change the filename or namespace for the generated code.

    A folder called API References containing the generated assets is added to the project. These assets include the original RAML file as well as any include dependencies, generated code, and a hidden .ref file with metadata for the code-generation tools.

    RAML NET APIRef

    The RAML.Api.Core, Newtonsoft.Json and Microsoft.AspNet.WebApi.Client NuGet packages are installed and referenced by the project.

  5. The C# classes nested beneath the parent RAML file contain the generated code to consume the Web API. At this point, the generated code is ready to be used.

Updating the API Reference

If the referenced RAML definition changes, the client code can be easily regenerated by right-clicking the parent RAML file and selecting Update RAML Reference.

RAML NET RunTests

Using the API Client with the Movies Sample

The RAML .NET installation package includes a sample project for a Movies API, which is a fictitious video library service where users browse a movie catalog, rent or return movies, and add movies to a wishlist for future watching.

The main constructor of the project’s MoviesClient client uses an endpoint URI. The overload for the constructor allows a custom HttpClient implementation to be injected, such as an HttpClient instance configured with a MessageHandler. You can use this instance for unit testing.

C#: Consuming an API

The MoviesClient model object replicates the same structure as the RAML definition through available resources and actions. The methods in this object model are asynchronous and based on the Task Parallel Library (TPL), so they can executed with the new async and await syntax in C# version 5.

var api = new MoviesClient("http://movies.com/api/");

// GET /movies
var moviesResponse = await api.Movies.Get();

// GET /movies/available
var availableMoviesResponse = await api.Movies.Available.Get();

If your API requires authentication, you can specify the access token as per this example of an authenticated Post.

C#: Calling an Authenticated API with OAuth

If your API is secured with OAuth, you can specify the access token before making a call as it is shown in this example:

var api = new MoviesApi("http://movies.con/api/");
var postMovie = new PostMovies
{
  Name = "Big Fish",
  Director = "Tim Burton",
  Cast = "Ewan McGregor, Albert Finney, Billy Crudup",
  Language = "English",
  Genre = "Drama, Fantasy"
};

// Set OAuth access token
moviesApi.OAuthAccessToken = "<OAuth_Token>";

// POST /movies
var response = await moviesApi.Movies.Post(postMovie);

Replace the <OAuth_Token> with your OAuth token received from your OAuth authorization service.

C#: Consuming the HTTP Response

All methods in the generated class return an instance of ApiResponse or of a subclass of it. This class provides access to the HTTP status codes, raw headers, and content. The following code fragment illustrates how to use those:

var statusCode = response.StatusCode;
var rawHeaders = response.RawHeaders;
var rawContent = response.RawContent;
var stream = await response.RawContent.ReadAsStreamAsync();

When the RAML specifies a JSON contract for a response, the tool generates a strongly typed object with an equivalent structure. This object is accessible through the Content property in the response.

var moviesResponse = await api.Movies.Get();
MoviesGetOKResponseContent[] movies = moviesResponse.Content;
var director = movies.First().Director;

For more advanced scenarios in which several JSON schemas are associated with a response, the Content property provides a different typed object for each schema.

var okContent = movieResponse.Content.IdGetOKResponseContent;
var badReqContent = movieResponse.Content.IdGetBadRequestResponseContent;
var notFoundContent = movieResponse.Content.IdGetNotFoundResponseContent;

Depending on the HTTP status code, each property has a value or is null. For example, if the status code is OK (200), only the IdGetOKResponseContent has a value, and the other properties are null.

The response also provides access to typed headers in case they were included in the RAML definition:

GetByIdMoviesOKResponseHeader headers = movieResponse.Headers;
var created = headers.Created;
var code = headers.Code;

Implementing an ASP.NET Web API

To implement an ASP.NET Web API:

  1. Start Visual Studio 2013 and create a new ASP.NET Web project.

  2. In the New ASP.NET Project menu, click Web API:

    RAML NET NewASPProject
  3. In the Solution Explorer, right-click the project node and click the Add RAML Contract command.

    RAML NETAddRAMLContract
  4. The dialog lets you create a RAML definition or import an existing one. If you import an existing one, click the Go button to download the RAML definition from an URL, or browse to use a local copy from your file system. Optionally change the filename or namespace for the generated code.

    RAML NETAddRAMLContractScreen
  5. A Contracts folder is added to the project containing the generated assets. These assets include a local copy of the RAML definition, the generated model classes (inferred from the JSON schemas in the RAML definition), and .NET interfaces representing the contracts for the ASP.NET Web API Controllers. The controllers are generated in the Controllers folder, and implement the contract interfaces.

Updating the ASP.NET Web API

The tool also supports updating the generated ASP.NET Web API when a change is made to the RAML definition. This lets you keep the contract definition in a RAML file with the implementation, so that both stay in sync. Right-click the RAML contract file under Contracts, and select the option Implement RAML Contract. This command only affects the existing .NET contract interfaces and adds ASP.NET Web API controller implementations for any new resource in the RAML definition. The existing controller implementations remain untouched.

Implementing a Controller in ASP.NET Web API

The generated controllers provide the starting point for the implementation. The tool generates a class that implements the .NET interface or contract for the resource defined in RAML. The following example illustrates the controller Movies for the Movies RAML file:

public partial class MoviesController : IMoviesController
{

    /// <summary>
    /// Gets all movies in the catalogue
    /// </summary>
    /// <returns>IList<MoviesGetOKResponseContent></returns>
    public IHttpActionResult Get()
    {
        // TODO: implement Get - route: movies/
        // var result = new IList<MoviesGetOKResponseContent>();
        // return Ok(result);
        return Ok();
    }

    /// <summary>
    /// Adds a movie to the catalog
    /// </summary>
    /// <param name="moviespostrequestcontent"></param>
    /// <param name="access_token">Sends a valid OAuth v2 access token. Do not use together with the &quot;Authorization&quot; header </param>
    public IHttpActionResult Post(Models.MoviesPostRequestContent moviespostrequestcontent,[FromUri] string access_token = null)
    {
        // TODO: implement Post - route: movies/
        return Ok();
    }

    /// <summary>
    /// Get the info of a movie
    /// </summary>
    /// <param name="id"></param>
    /// <returns>IdGetOKResponseContent</returns>
    public IHttpActionResult GetById([FromUri] string id)
    {
        // TODO: implement GetById - route: movies/{id}
        // var result = new IdGetOKResponseContent();
        // return Ok(result);
        return Ok();
    }

    /// <summary>
    /// Update the info of a movie
    /// </summary>
    /// <param name="idputrequestcontent"></param>
    /// <param name="id"></param>
    public IHttpActionResult Put(Models.IdPutRequestContent idputrequestcontent,[FromUri] string id)
    {
        // TODO: implement Put - route: movies/{id}
        return Ok();
    }

    /// <summary>
    /// Remove a movie from the catalog
    /// </summary>
    /// <param name="id"></param>
    public IHttpActionResult Delete([FromUri] string id)
    {
        // TODO: implement Delete - route: movies/{id}
        return Ok();
    }

    /// <summary>
    /// Rent a movie
    /// </summary>
    /// <param name="json"></param>
    /// <param name="id"></param>
    /// <param name="access_token">Sends a valid OAuth 2 access token. Do not use together with the &quot;Authorization&quot; header </param>
    public IHttpActionResult PutRent(string json,[FromUri] string id,[FromUri] string access_token = null)
    {
        // TODO: implement PutRent - route: movies/{id}/rent
        return Ok();
    }

    /// <summary>
    /// return a movie
    /// </summary>
    /// <param name="json"></param>
    /// <param name="id"></param>
    /// <param name="access_token">Sends a valid OAuth v2 access token. Do not use together with the &quot;Authorization&quot; header </param>
    public IHttpActionResult PutReturn(string json,[FromUri] string id,[FromUri] string access_token = null)
    {
        // TODO: implement PutReturn - route: movies/{id}/return
        return Ok();
    }

    /// <summary>
    /// gets the current user movies wishlist
    /// </summary>
    /// <param name="access_token">Sends a valid OAuth v2 access token. Do not use together with the &quot;Authorization&quot; header </param>
    /// <returns>IList<WishlistGetOKResponseContent></returns>
    public IHttpActionResult GetWishlist([FromUri] string access_token = null)
    {
        // TODO: implement GetWishlist - route: movies/wishlist
        // var result = new IList<WishlistGetOKResponseContent>();
        // return Ok(result);
        return Ok();
    }

    /// <summary>
    /// Add a movie to the current user movies wishlist
    /// </summary>
    /// <param name="json"></param>
    /// <param name="id"></param>
    /// <param name="access_token">Sends a valid OAuth 2 access token. Do not use together with the &quot;Authorization&quot; header </param>
    public IHttpActionResult PostById(string json,[FromUri] string id,[FromUri] string access_token = null)
    {
        // TODO: implement PostById - route: movies/wishlist/{id}
        return Ok();
    }

    /// <summary>
    /// Removes a movie from the current user movies wishlist
    /// </summary>
    /// <param name="id"></param>
    /// <param name="access_token">Sends a valid OAuth v2 access token. Do not use together with the &quot;Authorization&quot; header </param>
    public IHttpActionResult DeleteById([FromUri] string id,[FromUri] string access_token = null)
    {
        // TODO: implement DeleteById - route: movies/wishlist/{id}
        return Ok();
    }

    /// <summary>
    /// Gets the user rented movies
    /// </summary>
    /// <returns>IList<RentedGetOKResponseContent></returns>
    public IHttpActionResult GetRented()
    {
        // TODO: implement GetRented - route: movies/rented
        // var result = new IList<RentedGetOKResponseContent>();
        // return Ok(result);
        return Ok();
    }

    /// <summary>
    /// Get all movies that are not currently rented
    /// </summary>
    /// <returns>IList<AvailableGetOKResponseContent></returns>
    public IHttpActionResult GetAvailable()
    {
        // TODO: implement GetAvailable - route: movies/available
        // var result = new IList<AvailableGetOKResponseContent>();
        // return Ok(result);
        return Ok();
    }

}

The IMoviesController interface implemented by the controller represents the contract. You can provide, for example, the implementation code for the Get method and return a list of available movies in the catalog.

Customizing the Generated Code

RAML Tools for .NET uses T4 templates for code generation of client and service implementation. The T4 templates are now placed in your project folder to be able to easily customize them.

If you customize a template, be sure to add this file to your VCS repository.

Each template has a header with the title, version, and hash. Do not modify this information as it’s used to check for customization and compatibility with new versions.

Compatibility With New Versions of the Templates

When upgrading the tool if the template has changed, a compatibility check is performed. If you have customized the template and the new version of the template is compatible with your current one, you are given the option to override or continue using your customized template.

In case your customized template is no longer compatible, you are given the choice to override the template or stop the process. In the latter, you must uninstall the new version of the tool and reinstall the previous one.

Customizing the Generated Code for the Client

For the client there is a single template containing all the generated code, the RAMLClient.t4 file is placed under "API References/Templates".

Customizing the Generated cCode for the Asp.Net Web API

For the Web API there are a several templates under "Contracts/Templates":

  • ApiControllerImplementation.t4: Generates the skeleton of the controller, this is the place where you implement your code.

  • ApiControllerBase.t4: This class delegates the to the methods on the controller implementation class (ApiControllerImplementation).

  • ApiControllerInterface.t4: The interface that the controller implements.

  • ApiModel.t4: Template for the request and response content models.

Metadata

RAML metadata output lets you extract a RAML definition for your Web API app. To enable metadata output, right-click your project and choose Enable RAML metadata output command. This adds a RamlController, start up configurations, a razor view and other required files (css, js, etc.). The next sections list the three ways you can access the information about your API.

Api Console

Run the web application and navigate to /raml to see the API Console.

RAML NET ApiConsole

You can navigate by clicking the buttons, you can see the request and responses, and try the available methods for each resource.

Viewing the Raw RAML

If you wish to view the RAML that is generated from your API, run your web app and navigate to /raml/raw.

RAML NET RAML

Downloading the RAML

If you wish to download the RAML as a file, run your web app and navigate to /raml/download. This prompts you to choose the location and file name.

Customizing the Generated RAML

Some aspects of your API like security are not automatically detected. You can customize the RAML generation process and further adjust it to your API.

To do this, modify the GetRamlContents method of your RamlController class.

    private static string GetRamlContents()
        {
            var config = GlobalConfiguration.Configuration;
            var apiExplorer = config.Services.GetApiExplorer();
            var apiExplorerService = new ApiExplorerService(apiExplorer, config.VirtualPathRoot);
            var ramlDocument = apiExplorerService.GetRaml();
            var ramlContents = new RamlSerializer().Serialize(ramlDocument);

            return ramlContents;
        }

You can set the security schemes of your API, this is an example for OAuth v2. First it creates a SecuritySchemeDescriptor where you can set the query parameters, headers, and responses. In this case it defines a single query parameter called "access_token".

Then it calls the UseOAuth2 method, which sets the endpoints, grants, scopes, and the previously created security scheme descriptor.

In this example the authorization URL is /oauth/authorize, the access token URL is /oauth/access_token. There are two authorization grants code and token, and a single scope all.

    // Set OAuth security scheme descriptor:  headers, query parameters, and responses
    var descriptor = new SecuritySchemeDescriptor
    {
        QueryParameters = new Dictionary<string, Parameter>
          {
              {
                  "access_token",
                  new Parameter
                  {
                      Type = "string",
                      Required = true
                  }
              }
          }
    };

    // Set OAuth v2 endpoints, grants, scopes and descriptor
    apiExplorerService.UseOAuth2("/oauth/authorize", "/oauth/access_token",
                new[] {"code", "token"}, new[] {"all"}, descriptor);

You can set the protocols for the web API by setting the Protocols property of the ApiExplorerService instance. For example for using HTTPS only in all of your API you would do this:

    apiExplorerService.Protocols = new[] { Protocol.HTTPS };

In a similar fashion if you want to set all of your resources to be accessed with OAuth v2, you can set the SecuredBy property of the ApiExplorerService instance.

    apiExplorerService.SecuredBy = new[] { "oauth_2_0" };

Combining all this together, your RAML action should look like:

    private static string GetRamlContents()
    {
        var config = GlobalConfiguration.Configuration;
        var apiExplorer = config.Services.GetApiExplorer();
        var apiExplorerService = new ApiExplorerService(apiExplorer, config.VirtualPathRoot);

        // Use HTTPS only
        apiExplorerService.Protocols = new[] { Protocol.HTTPS };

        // Use OAuth 2 for all resources
        apiExplorerService.SecuredBy = new[] { "oauth_2_0" };

        // Set OAuth security scheme descriptor: headers, query parameters, and responses
        var descriptor = new SecuritySchemeDescriptor
        {
            QueryParameters = new Dictionary<string, Parameter>
                {
                    {
                        "access_token",
                        new Parameter
                        {
                            Type = "string",
                            Required = true
                        }
                    }
                }
        };

        // Set OAuth v2 endpoints, grants, scopes, and descriptor
        apiExplorerService.UseOAuth2("https://api.movies.com/oauth/authorize",
            "https://api.movies.com/oauth/access_token", new[] {"code", "token"}, new[] {"all"}, descriptor);

        var ramlDocument = apiExplorerService.GetRaml();
        var ramlContents = new RamlSerializer().Serialize(ramlDocument);

        return ramlContents;
    }

If using OAuth v1, you can use the UseOAuth1 method. For other security schemes or further customization, you can use the SetSecurityScheme method or the SecuritySchemes property.

Other global properties can be set using the SetRamlProperties action. For example, to set the root level documentation:

    apiExplorerService.SetRamlProperties = raml =>
        {
            raml.Documentation = "Documentation is availabe at http://documentation.org"
        }

For customizing your RAML only for specific resources, you have three action available: SetMethodProperties, SetResourceProperties, SetResourcePropertiesByAction, and SetResourcePropertiesByController.

For example for setting OAuth v2 for the movies POST action, you can do this:

apiExplorerService.SetMethodProperties = (apiDescription, method) =>
    {
      if (apiDescription.RelativePath == "movies" && method.Verb == "post")
        {
            method.SecuredBy = new [] {"oauth_2_0"};
        }

    };

You can also modify the Body or the Responses using the same strategy.

XML Schemas

When using XML shcemas please note that there is no root type. So you need to specify the schema to be used exactly as it appears on the XML.

#%RAML 0.8
title: XML Schemas API
version: v1
baseUri: /
mediaType: application/xml
schemas:
  - xmlschema: |
        <xsd:schema targetNamespace="http://www.example.com/IPO"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:ipo="http://www.example.com/IPO">

         <xsd:element name="purchaseOrder" type="ipo:PurchaseOrderType"/>

         <xsd:element name="comment" type="xsd:string"/>
         <xsd:element name="shipComment" type="xsd:string"
          substitutionGroup="ipo:comment"/>
         <xsd:element name="customerComment" type="xsd:string"
          substitutionGroup="ipo:comment"/>

         <xsd:complexType name="PurchaseOrderType">
          <xsd:sequence>
           <xsd:choice>
            <xsd:group   ref="ipo:shipAndBill"/>
            <xsd:element name="singleAddress" type="ipo:AddressType"/>
           </xsd:choice>
           <xsd:element ref="ipo:comment" minOccurs="0"/>
           <xsd:element name="items"      type="ipo:ItemsType"/>
          </xsd:sequence>
          <xsd:attribute name="orderDate" type="xsd:date"/>
         </xsd:complexType>

         <xsd:group name="shipAndBill">
          <xsd:sequence>
           <xsd:element name="shipTo"     type="ipo:AddressType"/>
           <xsd:element name="billTo"     type="ipo:AddressType"/>
          </xsd:sequence>
         </xsd:group>


         <xsd:complexType name="AddressType">
          <xsd:sequence>
           <xsd:element name="name"   type="xsd:string"/>
           <xsd:element name="street" type="xsd:string"/>
           <xsd:element name="city"   type="xsd:string"/>
          </xsd:sequence>
         </xsd:complexType>

         <xsd:complexType name="USAddress">
          <xsd:complexContent>
           <xsd:extension base="ipo:AddressType">
            <xsd:sequence>
             <xsd:element name="state" type="ipo:USState"/>
             <xsd:element name="zip"   type="xsd:positiveInteger"/>
            </xsd:sequence>
           </xsd:extension>
          </xsd:complexContent>
         </xsd:complexType>

         <xsd:complexType name="UKAddress">
          <xsd:complexContent>
           <xsd:extension base="ipo:AddressType">
            <xsd:sequence>
             <xsd:element name="postcode" type="ipo:UKPostcode"/>
            </xsd:sequence>
            <xsd:attribute name="exportCode" type="xsd:positiveInteger" fixed="1"/>
           </xsd:extension>
          </xsd:complexContent>
         </xsd:complexType>

         <!-- other Address derivations for more countries -->
         <xsd:simpleType name="USState">
          <xsd:restriction base="xsd:string">
           <xsd:enumeration value="AK"/>
           <xsd:enumeration value="AL"/>
           <xsd:enumeration value="AR"/>
           <xsd:enumeration value="CA"/>
           <!-- and so on ... -->
           <xsd:enumeration value="PA"/>
          </xsd:restriction>
         </xsd:simpleType>

         <!-- simple type definition for UKPostcode -->
         <xsd:simpleType name="UKPostcode">
          <xsd:restriction base="xsd:string">
           <xsd:pattern value="[A-Z]{2}\d\s\d[A-Z]{2}"/>
          </xsd:restriction>
         </xsd:simpleType>

        <xsd:complexType name="ItemsType" mixed="true">
         <xsd:sequence>
         <xsd:element name="item" minOccurs="0" maxOccurs="unbounded">
          <xsd:complexType>
            <xsd:sequence>
              <xsd:element name="productName" type="xsd:string"/>
              <xsd:element name="quantity">
                <xsd:simpleType>
                  <xsd:restriction base="xsd:positiveInteger">
                    <xsd:maxExclusive value="100"/>
                  </xsd:restriction>
                </xsd:simpleType>
              </xsd:element>
              <xsd:element name="USPrice"  type="xsd:decimal"/>
              <xsd:element ref="ipo:comment"   minOccurs="0" maxOccurs="2"/>
              <xsd:element name="shipDate" type="xsd:date" minOccurs="0"/>
            </xsd:sequence>
            <!-- attributeGroup replaces individual declarations -->
            <xsd:attributeGroup ref="ipo:ItemDelivery"/>
          </xsd:complexType>
         </xsd:element>
         </xsd:sequence>
        </xsd:complexType>

        <xsd:attributeGroup name="ItemDelivery">
          <xsd:attribute name="partNum"  type="ipo:SKU" use="required"/>
          <xsd:attribute name="weightKg" type="xsd:decimal"/>
          <xsd:attribute name="shipBy">
            <xsd:simpleType>
              <xsd:restriction base="xsd:string">
                <xsd:enumeration value="air"/>
                <xsd:enumeration value="land"/>
                <xsd:enumeration value="any"/>
              </xsd:restriction>
            </xsd:simpleType>
          </xsd:attribute>
        </xsd:attributeGroup>

         <xsd:simpleType name="SKU">
          <xsd:restriction base="xsd:string">
           <xsd:pattern value="\d{3}-[A-Z]{2}"/>
          </xsd:restriction>
         </xsd:simpleType>

        </xsd:schema>
/contacts:
  displayName: Contacts
  get:
    responses:
      200:
        body:
          schema: PurchaseOrderType

Note that the schema does not use the key on the schemas section ("xmlschema") but "PurchaseOrderType" which is the name of a type as it appears on the XML.

FAQ

What are the differences between the RAML Parser for .NET and RAML Tools for .NET?

The RAML Parser takes a text based RAML definition and returns an Abstract Syntax Tree (An object model representing the resources/methods in the RAML definition). The RAML Tools leverage this model in code generation templates to provide strongly typed classes for the consumption or implementation of the API itself.

Which languages can the tools generate code for?

Currently, C# is the only output language supported. This generated code can however simply be contained within a separate assembly, and the types exposed then consumed from any CLR language.

Can I customize the code-generation templates?

Yes, RAML Tools for .NET uses T4 templates for code generation of client and service implementation. See the appropriate sections for guidance on where and how to customize templates.

I already have an API built using ASP.NET WebApi - how do I adopt RAML for my project?

To extract a RAML definition for an existing WebApi project, simply enable RAML Metadata output from the project context menu.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C++ 65.2%
  • RAML 26.9%
  • C 4.2%
  • C# 2.9%
  • Objective-C 0.6%
  • Other 0.1%
  • Other 0.1%