Skip to Content
Technical Articles
Author's profile photo Gunter Albrecht

SAP S/4HANA Cloud and Microsoft .NET 6: Deployment on SAP BTP


In a customer meeting this week I was asked: “We develop Microsoft .NET applications. Can we run them on SAP BTP?”.

I found this a fascinating challenge – particularly since I never worked with MS .NET as a developer before. Why not try and see if and how it would work? Let’s go!

Proof of concept scenario

In this blog we develop and deploy a .NET Blazor application on SAP BTP Kyma which is SAP’s Kubernetes runtime. I decided to connect to S/4HANA Cloud, retrieve some data and display it in the application to prove integration to SAP solutions, too.


Image 1: High level architecture of .NET deployment


As said, I’m not well-versed on .NET and I didn’t want to install Visual Studio. I used VSCode with the C# extension of Omnisharp installed. That works very well for me. Next I downloaded .NET 6.0 SDK from Microsoft.

I then followed the tutorial of building a Blazor application. Finally I added an additional page to the app to load and display data from S/4HANA Cloud.

For those of you like me that never worked with C# here’s my masterpiece of integration. 😅 If we have an experienced C#/ Blazor developer among the readers I would love to hear how to avoid the interim step to remove the unwanted JSON hierarchy. Also I didn’t manage to convert the JSON date into a DateTime variable. For that there are JsonConverters in .NET. But I didn’t make it work.

@code {
    private IEnumerable<SupplierInvoices> invoices = Array.Empty<SupplierInvoices>();
    private bool getInvoicesError;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    protected override async Task OnInitializedAsync()
        var request = new HttpRequestMessage(HttpMethod.Get,"https://<url of service>");
        request.Headers.Add("Accept", "application/json");
        request.Headers.Add("User-Agent", "Blazor-http-client");
        request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("<username>:<password>")));

        var client = ClientFactory.CreateClient();

        var response = await client.SendAsync(request);
        if (response.IsSuccessStatusCode)
            using var responseStream = await response.Content.ReadAsStreamAsync();
            JsonNode odataSet = await JsonSerializer.DeserializeAsync<JsonNode>(responseStream);
            var jsonString = odataSet["d"]["results"].ToString();
            invoices = JsonSerializer.Deserialize<IEnumerable<SupplierInvoices>>(jsonString);
            getInvoicesError = true;

        shouldRender = true;

    public class SupplierInvoices
        public string? SupplierInvoice { get; set; }
        public string? FiscalYear { get; set; }
        public string? CompanyCode { get; set; }
        public string? AccountingDocumentType { get; set; }
        public string? InvoicingParty { get; set; }
        public string? DocumentCurrency { get; set; }
        public string? InvoiceGrossAmount { get; set; }
        public string? DocumentHeaderText { get; set; }
        public string? PaymentTerms { get; set; }
        public string? DueCalculationBaseDate { get; set; }


With that code the invoice data is moved to invoices and can be displayed on the page in such a way:

@if (getInvoicesError)
    <p>Unable to get invoices from S/4HANA Cloud. Sorry for that.</p>
    <table class="table">
                <th>Invoice #</th>
                <th>Document type</th>
                <th>Fiscal year</th>
                <th>Invoicing party</th>
                <th>Gross amount</th>
                <th>Company code</th>
                <th>Header text</th>
                <th>Payment terms</th>
            @foreach (var invoice in invoices)
                    <td>@invoice.DocumentCurrency @invoice.InvoiceGrossAmount</td>

Now that we are through with the code, next step is deployment of the application.


Deployment happens in two steps: First, build a docker image, second deploy on SAP BTP Kyma.

1. Docker image creation

Make sure in your Program.cs you have commented or removed the line


We don’t want the application to use https since the security is completely handled by Kyma’s API Rules. As you might know communication among the Kubernetes pods is always http (unless we go a level deeper and work with sockets).

Microsoft publishes official Docker images which you can find here. As for the Dockerfile it looks for me like below.

# Create docker image to be used on SAP BTP Kyma for .NET web server
FROM AS base
# Port number of server

FROM AS build
WORKDIR /usr/src

COPY ["BlazorApp.csproj", "."]
RUN dotnet restore "BlazorApp.csproj"
# Copy the code into the workdir
COPY . .
RUN dotnet build "BlazorApp.csproj" -c Release -o /usr/app/build

FROM build AS publish
RUN dotnet publish "BlazorApp.csproj" -c Release -o /usr/app/publish

FROM base AS final
WORKDIR /usr/app
COPY --from=publish /usr/app/publish .
ENTRYPOINT ["dotnet", "BlazorApp.dll"]

You need to adjust for your application name. Run the usual docker build like so:

docker build --tag <your docker id>/<your image name>:<your tag version> .

and push it to the docker hub with this command:

docker push <your docker id>/<your image name>:<your tag version>

Now we are good to deploy on SAP BTP Kyma.

2. Kyma deployment

That part is easy, we need the usual deployment yaml which you can copy from below. Adjust it to your namespace and hostname and don’t forget to update the image name.

apiVersion: v1
kind: Namespace
  name: dotnet-blazor
    istio-injection: enabled
apiVersion: apps/v1
kind: Deployment
  name: dotnet-blazor
  namespace: dotnet-blazor
  replicas: 1
      app: dotnet-blazor-app
        app: dotnet-blazor-app
        - name: dotnet-blazor-image
          image: <your docker id>/<your image name>:<your tag version> 
          imagePullPolicy: Always  
          - containerPort: 80
              memory: "256Mi"
              cpu: "250m"
              memory: "512Mi"
              cpu: "500m"
apiVersion: v1
kind: Service
    app: dotnet-blazor-app
  name: dotnet-blazor-service
  namespace: dotnet-blazor
  - name: "webserverport"
    port: 8080
    targetPort: 80
  type: ClusterIP
    app: dotnet-blazor-app
kind: APIRule
  labels: dotnet-blazor-apirule
  name: dotnet-blazor-apirule
  namespace: dotnet-blazor
  gateway: kyma-gateway.kyma-system.svc.cluster.local
  - accessStrategies:
    - config: {}
      handler: allow
    - GET
    - POST
    - PUT
    - DELETE
    - PATCH
    - HEAD
    path: /.*
    host: myblazorapp
    name: dotnet-blazor-service
    port: 8080

And with that we are good to test it:


Image 2: Walk through the running application


And here it ends, our little excursion. While this blog was focussing on front-end application development it runs a .NET web server in the backend. Certainly there should be no concerns to integrate with SAP or Non-SAP applications when developing with Microsoft .NET on SAP BTP. Hope it was useful or interesting and would be happy to hear your feedback.

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.