Skip to content

.NET Widget Configuration

Custom configuration settings and features for .NET-based widget projects.

Overview

.NET widgets are hybrid widget projects where ASP.NET Core backend and TypeScript frontend come together. The backend provides API services while the frontend runs as a widget.

Project Structure

my-dotnet-widget/
├── my-dotnet-widget.sln              # Visual Studio solution
├── my-dotnet-widget/
│   ├── .xcon/
│   │   └── config.json               # Widget configuration
│   ├── Controllers/
│   │   └── ApiController.cs          # Backend API
│   ├── Models/
│   │   └── DataModel.cs              # Data models
│   ├── Services/
│   │   └── DataService.cs            # Business logic
│   ├── src/
│   │   ├── widget.ts                 # Frontend widget
│   │   ├── widget.html
│   │   └── widget.css
│   ├── wwwroot/
│   │   └── dist/
│   │       └── widget.min.js         # Built widget
│   ├── my-dotnet-widget.csproj
│   ├── package.json
│   ├── tsconfig.json
│   ├── Program.cs
│   └── appsettings.json

Project Type Definition

json
{
  "configurations": {
    "provider": "web",
    "id": "my-dotnet-widget",
    "type": "latest"
  },
  "projectType": "dotnet",
  "version": "1.0"
}

Important: projectType must be "dotnet".

.NET Widget Configuration

Minimal Configuration

json
{
  "configurations": {
    "provider": "web",
    "id": "dotnet-widget",
    "type": "latest"
  },
  "build": {
    "entry": "widget.ts",
    "sourceRoot": "src",
    "outputFilename": "widget.min.js",
    "outputPath": "wwwroot/dist",
    "tsConfig": "tsconfig.json"
  },
  "projectType": "dotnet",
  "version": "1.0"
}

Full Configuration

json
{
  "configurations": {
    "provider": "web",
    "id": "advanced-dotnet-widget",
    "type": "latest",
    "fqn": "com.example.DotNetWidget"
  },
  "build": {
    "debug": false,
    "entry": "widget.ts",
    "sourceRoot": "src",
    "outputFilename": "widget.min.js",
    "outputPath": "wwwroot/dist",
    "tsConfig": "tsconfig.json",
    "externals": {
      "axios": "axios",
      "chart.js": "Chart"
    }
  },
  "data": {
    "dataKeys": [],
    "additionalTypeParameters": {
      "apiEndpoint": "/api/data",
      "authRequired": true,
      "refreshInterval": 5000
    },
    "defaultConfig": {
      "title": ".NET Widget",
      "backgroundColor": "#ffffff",
      "showLegend": true,
      "apiUrl": "https://localhost:5001/api"
    }
  },
  "projectType": "dotnet",
  "version": "1.0"
}

Build Paths (.NET Specific)

Output Path

In .NET widgets, output must be under wwwroot:

json
{
  "build": {
    "outputPath": "wwwroot/dist"        // Specific for .NET
  }
}

Why wwwroot?

  • ASP.NET Core static files middleware
  • Automatically included in publish process
  • IIS deployment compatibility

Source Root

json
{
  "build": {
    "sourceRoot": "src"                 // Frontend sources
  }
}

Directory structure:

my-dotnet-widget/
├── src/                    # Frontend sources
│   ├── widget.ts
│   ├── widget.html
│   └── widget.css
├── Controllers/            # Backend API
├── Models/                 # Data models
└── Services/              # Business logic

Backend API Integration

API Controller Configuration

appsettings.json:

json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  },
  "AllowedHosts": "*",
  "WidgetSettings": {
    "ApiEndpoint": "/api/widget",
    "CorsOrigins": ["http://localhost:4201"]
  }
}

Controllers/WidgetApiController.cs:

csharp
[ApiController]
[Route("api/[controller]")]
public class WidgetApiController : ControllerBase
{
    [HttpGet("data")]
    public IActionResult GetData()
    {
        var data = new
        {
            timestamp = DateTime.UtcNow,
            value = Random.Shared.Next(0, 100)
        };
        return Ok(data);
    }
    
    [HttpPost("update")]
    public IActionResult UpdateData([FromBody] DataModel model)
    {
        // Update logic
        return Ok(new { success = true });
    }
}

Frontend API Integration

widget.ts:

typescript
import axios from 'axios';

@Widget({
  // configuration
})
export class DotNetWidget {
  private apiUrl = '/api/widgetapi';
  
  async onInit() {
    await this.loadData();
  }
  
  private async loadData() {
    try {
      const response = await axios.get(`${this.apiUrl}/data`);
      console.log('Data:', response.data);
    } catch (error) {
      console.error('API Error:', error);
    }
  }
}

External API Configuration

json
{
  "build": {
    "externals": {
      "axios": "axios"
    }
  },
  "data": {
    "additionalTypeParameters": {
      "apiEndpoint": "/api/widgetapi",
      "apiTimeout": 30000
    }
  }
}

Widget resources:

typescript
@Widget({
  resources: [
    {
      id: 'axios',
      name: 'Axios',
      type: 'js',
      extension: true,
      url: 'https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js',
      order: 1
    }
  ]
})

.NET Build Workflow

1. Development Build

bash
# Run backend
cd my-dotnet-widget
dotnet run

# Frontend build (development)
npm run dev

package.json:

json
{
  "scripts": {
    "dev": "webpack --mode development --watch",
    "build": "webpack --mode production"
  }
}

2. Production Build

bash
# Frontend build
npm run build

# .NET publish
dotnet publish -c Release -o ./publish

3. CLI Build Integration

bash
# Widget build (CLI)
xcons dotnet build --production

# Or from project directory
cd my-dotnet-widget
xcons widget build --production

CORS Configuration

Program.cs

csharp
var builder = WebApplication.CreateBuilder(args);

// Add CORS
builder.Services.AddCors(options =>
{
    options.AddPolicy("WidgetPolicy", policy =>
    {
        policy.WithOrigins("http://localhost:4201")
              .AllowAnyMethod()
              .AllowAnyHeader();
    });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseCors("WidgetPolicy");
app.UseStaticFiles();
app.MapControllers();

app.Run();

Database Integration

Entity Framework Setup

.csproj:

xml
<ItemGroup>
  <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
  <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
</ItemGroup>

appsettings.json:

json
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=WidgetDb;Trusted_Connection=true;"
  }
}

Data/ApplicationDbContext.cs:

csharp
public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options) { }
    
    public DbSet<WidgetData> WidgetData { get; set; }
}

Program.cs:

csharp
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
);

Authentication & Authorization

JWT Authentication

.csproj:

xml
<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
</ItemGroup>

Program.cs:

csharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])
            )
        };
    });

Controller:

csharp
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class SecureWidgetController : ControllerBase
{
    [HttpGet("data")]
    public IActionResult GetSecureData()
    {
        var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        // Secure data logic
        return Ok(data);
    }
}

Widget config:

json
{
  "data": {
    "additionalTypeParameters": {
      "authRequired": true,
      "authType": "jwt"
    }
  }
}

Custom Scenarios

1. Real-time Data Widget

SignalR Integration:

.csproj:

xml
<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="8.0.0" />
</ItemGroup>

Hubs/WidgetHub.cs:

csharp
public class WidgetHub : Hub
{
    public async Task SendData(object data)
    {
        await Clients.All.SendAsync("ReceiveData", data);
    }
}

Program.cs:

csharp
builder.Services.AddSignalR();
app.MapHub<WidgetHub>("/widgetHub");

widget.ts:

typescript
import * as signalR from '@microsoft/signalr';

@Widget({
  // configuration
})
export class RealtimeWidget {
  private connection: signalR.HubConnection;
  
  async onInit() {
    this.connection = new signalR.HubConnectionBuilder()
      .withUrl("/widgetHub")
      .build();
    
    this.connection.on("ReceiveData", (data) => {
      console.log('Real-time data:', data);
      this.updateView(data);
    });
    
    await this.connection.start();
  }
  
  onDestroy() {
    this.connection.stop();
  }
}

2. File Upload Widget

Controller:

csharp
[ApiController]
[Route("api/[controller]")]
public class FileController : ControllerBase
{
    [HttpPost("upload")]
    public async Task<IActionResult> Upload(IFormFile file)
    {
        if (file.Length > 0)
        {
            var path = Path.Combine("wwwroot/uploads", file.FileName);
            using var stream = new FileStream(path, FileMode.Create);
            await file.CopyToAsync(stream);
            
            return Ok(new { fileName = file.FileName });
        }
        return BadRequest();
    }
}

widget.ts:

typescript
@Widget({
  // configuration
})
export class FileUploadWidget {
  
  async uploadFile(file: File) {
    const formData = new FormData();
    formData.append('file', file);
    
    try {
      const response = await axios.post('/api/file/upload', formData, {
        headers: { 'Content-Type': 'multipart/form-data' }
      });
      console.log('Uploaded:', response.data);
    } catch (error) {
      console.error('Upload failed:', error);
    }
  }
}

3. Dashboard with Background Jobs

Hangfire Integration:

.csproj:

xml
<ItemGroup>
  <PackageReference Include="Hangfire" Version="1.8.0" />
  <PackageReference Include="Hangfire.SqlServer" Version="1.8.0" />
</ItemGroup>

Program.cs:

csharp
builder.Services.AddHangfire(config =>
    config.UseSqlServerStorage(builder.Configuration.GetConnectionString("DefaultConnection"))
);
builder.Services.AddHangfireServer();

app.UseHangfireDashboard();

// Recurring job
RecurringJob.AddOrUpdate(
    "update-widget-data",
    () => UpdateWidgetData(),
    Cron.Minutely
);

Build Profiles (.NET)

Development Profile

json
{
  "configurations": {
    "provider": "web",
    "id": "dotnet-widget"
  },
  "build": {
    "debug": true,
    "entry": "widget.ts",
    "sourceRoot": "src",
    "outputFilename": "widget.js",
    "outputPath": "wwwroot/dist",
    "tsConfig": "tsconfig.json"
  },
  "data": {
    "additionalTypeParameters": {
      "apiEndpoint": "https://localhost:5001/api",
      "enableDebugConsole": true
    }
  },
  "projectType": "dotnet",
  "version": "1.0"
}

Production Profile

json
{
  "configurations": {
    "provider": "web",
    "id": "dotnet-widget"
  },
  "build": {
    "debug": false,
    "entry": "widget.ts",
    "sourceRoot": "src",
    "outputFilename": "widget.min.js",
    "outputPath": "wwwroot/dist",
    "tsConfig": "tsconfig.json",
    "externals": {
      "axios": "axios",
      "chart.js": "Chart"
    }
  },
  "data": {
    "additionalTypeParameters": {
      "apiEndpoint": "https://api.production.com/api",
      "enableDebugConsole": false
    }
  },
  "projectType": "dotnet",
  "version": "1.0"
}

Deployment

IIS Deployment

web.config:

xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" />
    </handlers>
    <aspNetCore processPath="dotnet" 
                arguments=".\MyDotNetWidget.dll" 
                stdoutLogEnabled="false" 
                hostingModel="inprocess" />
  </system.webServer>
</configuration>

Docker Deployment

Dockerfile:

dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyDotNetWidget.csproj", "./"]
RUN dotnet restore
COPY . .
RUN dotnet build -c Release -o /app/build

FROM build AS publish
RUN dotnet publish -c Release -o /app/publish

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

docker-compose.yml:

yaml
version: '3.8'
services:
  widget:
    build: .
    ports:
      - "5000:80"
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
    volumes:
      - ./wwwroot:/app/wwwroot

Azure Deployment

azure-pipelines.yml:

yaml
trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: UseDotNet@2
  inputs:
    version: '8.x'

- task: DotNetCoreCLI@2
  inputs:
    command: 'restore'

- task: DotNetCoreCLI@2
  inputs:
    command: 'build'
    arguments: '--configuration Release'

- task: DotNetCoreCLI@2
  inputs:
    command: 'publish'
    publishWebProjects: true
    arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)'

- task: AzureWebApp@1
  inputs:
    azureSubscription: '<subscription>'
    appName: 'my-dotnet-widget'
    package: $(Build.ArtifactStagingDirectory)/**/*.zip

Troubleshooting (.NET Specific)

Build Output Not Found

Problem: widget.min.js not found in wwwroot/dist

Solution:

json
{
  "build": {
    "outputPath": "wwwroot/dist"  // Must be under wwwroot
  }
}

API CORS Error

Problem: CORS policy blocked

Solution - Program.cs:

csharp
builder.Services.AddCors(options =>
{
    options.AddPolicy("WidgetPolicy", policy =>
    {
        policy.AllowAnyOrigin()
              .AllowAnyMethod()
              .AllowAnyHeader();
    });
});

app.UseCors("WidgetPolicy");

Static Files Not Served

Problem: Widget JS not loading

Solution - Program.cs:

csharp
app.UseStaticFiles();  // MUST add

Entity Framework Migration

Problem: Database schema not up to date

Solution:

bash
# Create migration
dotnet ef migrations add InitialCreate

# Update database
dotnet ef database update

Best Practices (.NET)

1. Output Path

Correct:

json
{
  "build": {
    "outputPath": "wwwroot/dist"  // Standard for .NET
  }
}

Wrong:

json
{
  "build": {
    "outputPath": "dist"  // ASP.NET static files middleware won't work
  }
}

2. API Endpoint Configuration

Correct:

json
{
  "data": {
    "additionalTypeParameters": {
      "apiEndpoint": "/api/widget",  // Relative path
      "apiBaseUrl": "https://api.example.com"  // Absolute URL
    }
  }
}

3. External Dependencies

Correct:

json
{
  "build": {
    "externals": {
      "axios": "axios",
      "@microsoft/signalr": "signalR"
    }
  }
}

Example Configurations

Minimal .NET Widget

json
{
  "configurations": {
    "provider": "web",
    "id": "simple-dotnet-widget"
  },
  "build": {
    "entry": "widget.ts",
    "sourceRoot": "src",
    "outputFilename": "widget.min.js",
    "outputPath": "wwwroot/dist",
    "tsConfig": "tsconfig.json"
  },
  "projectType": "dotnet",
  "version": "1.0"
}

Advanced .NET Widget

json
{
  "configurations": {
    "provider": "web",
    "id": "advanced-dotnet-widget",
    "type": "latest",
    "fqn": "com.example.AdvancedDotNetWidget"
  },
  "build": {
    "debug": false,
    "entry": "widget.ts",
    "sourceRoot": "src",
    "outputFilename": "widget.min.js",
    "outputPath": "wwwroot/dist",
    "tsConfig": "tsconfig.json",
    "externals": {
      "@xcons/common": "XConsCommon",
      "@xcons/widget": "XConWidgetCore",
      "axios": "axios",
      "chart.js": "Chart",
      "@microsoft/signalr": "signalR"
    }
  },
  "data": {
    "additionalTypeParameters": {
      "apiEndpoint": "/api/widget",
      "apiBaseUrl": "https://api.example.com",
      "authRequired": true,
      "authType": "jwt",
      "refreshInterval": 5000,
      "enableSignalR": true,
      "signalRHubUrl": "/widgetHub"
    },
    "defaultConfig": {
      "title": "Advanced .NET Widget",
      "backgroundColor": "#ffffff",
      "showLegend": true,
      "apiTimeout": 30000,
      "retryAttempts": 3
    }
  },
  "projectType": "dotnet",
  "version": "1.0"
}

Migration Guide

Migration from Web to .NET

Web widget config:

json
{
  "configurations": {
    "provider": "web"
  },
  "build": {
    "outputPath": "dist"
  },
  "projectType": "widget"
}

In Visual Studio:

  1. Create ASP.NET Core Web Application
  2. Move widget files to src/ folder
  3. Update config file:
json
{
  "configurations": {
    "provider": "web"
  },
  "build": {
    "outputPath": "wwwroot/dist"  // Changed
  },
  "projectType": "dotnet"  // Changed
}
  1. Add backend APIs
  2. Configure Program.cs

Your .NET widget configuration is ready!