0

The question: is it possible to automatically generate a TypeScript client that defines data transfer between a React component and an ASP.NET MVC controller?

The context is: we have a TypeScript React app that consumes an ASP.NET Core Web API and use NSwag to automatically generate a TypeScript client for interaction between the two. We also have a legacy ASP.NET MVC Web app with Razor pages, and have been asked to integrate TypeScript React components into it.

Initially it was only one component so we used a fairly simple solution whereby the React component uses fetch to call an ASP.NET MVC controller action method that returns a JsonResult. The issue is that we are being asked to embed more and more React components, and our current solution requires manual definition of everything that NSwag handles automatically e.g. a C# dto on the ASP.NET MVC side, an equivalent .ts class on the React side, and the mapping of non-trivial properties from C# to .ts e.g. other classes, dates, etc.

For example, in order to get data from a FooController with action method GetFoo:

[HttpGet]
public async Task<IActionResult> GetFoo(int fooId)
{
    // get details from service
    // ...

    var foo = new FooDto
    {
        // translate service data for views
        // ...
    };

    return Json(foo);
}

We need to define the model-equivalent dtos:

public class FooDto
{
    public int FooId { get; set; }
    public string FooName { get; set; }
    public DateTime FooDate { get; set; }
    public BarDto Bar { get; set; }
}

public class BarDto
{
    public int BarId { get; set; }
    public string BarName { get; set; }
}

and then the equivalent TypeScript classes in React to be initialised with the response.json from the fetch:

export interface IBarDto {
    barId: number;
    barName?: string | undefined;
}

export class BarDto implements IBarDto {
    barId!: number;
    barName?: string | undefined;

    constructor(data?: IBarDto) {
        if (data) {
            this.barId = data["barId"];
            this.barName = data["barName"];
        }
    }    
}

export interface IFooDto {
    fooId: number;
    fooName?: string | undefined;
    fooDate: Date;
    bar?: BarDto;
}

export class FooDto implements IFooDto {
    fooId!: number;
    fooName?: string | undefined;
    fooDate!: Date;
    bar?: BarDto;

    constructor(data?: IFooDto) {
        if (data) {
            this.fooId = data["fooId"];
            this.fooName = data["fooName"];
            this.fooDate = data["fooDate"] ? new Date(data["fooDate"].toString()) : <any>undefined;
            this.bar = data["bar"] ? new BarDto(data["bar"]) : <any>undefined;
        }
    }    
}

This is a very simple example of a Get action only; we have an increasingly large number of more complex Get and Post actions being incorporated into the system. Ideally I would like to use NSwag for consistency so have started by generating a json schema of the C# dtos and using Nswag on that, but it's still a manual process at this stage.

Does anyone have any experience in or suggestions for automating generation of the necessary TypeScript classes? I'm open to changing our approach if it means being able to better manage the creation of classes to define the interaction between ASP.NET MVC and React components, but at this stage don't have the option of deprecating ASP.NET MVC entirely - the legacy app and ASP.NET MVC Controllers must be the interface for the React components.

kelsny
  • 23,009
  • 3
  • 19
  • 48
Dave
  • 457
  • 4
  • 15

1 Answers1

0

If anyone's interested, I added separate React-specific actions with request and response dtos to existing MVC controllers and decorated them with a custom Route attribute - as per the Swashbuckle documentation you "must use attribute routing for any controllers that you want represented in your Swagger document(s)". I added a convention in Startup so that actions marked with the attribute would be included in the Swagger document. This allowed me to use NSwag to generate the required TypeScript clients and models.

Dave
  • 457
  • 4
  • 15