Appearance
.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.jsonProject 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 logicBackend 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 devpackage.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 ./publish3. CLI Build Integration
bash
# Widget build (CLI)
xcons dotnet build --production
# Or from project directory
cd my-dotnet-widget
xcons widget build --productionCORS 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/wwwrootAzure 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)/**/*.zipTroubleshooting (.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 addEntity Framework Migration
Problem: Database schema not up to date
Solution:
bash
# Create migration
dotnet ef migrations add InitialCreate
# Update database
dotnet ef database updateBest 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:
- Create ASP.NET Core Web Application
- Move widget files to
src/folder - Update config file:
json
{
"configurations": {
"provider": "web"
},
"build": {
"outputPath": "wwwroot/dist" // Changed
},
"projectType": "dotnet" // Changed
}- Add backend APIs
- Configure Program.cs
Your .NET widget configuration is ready!