Technical articles on AI agents, Azure, .NET, architecture, and EV charging systems from Sydney.

Tag: .NET Page 1 of 2

MCP Transport Types Explained: stdio vs Streamable HTTP vs SSE in C#

Nobody tells you this when you start building MCP servers, but picking the wrong MCP transport is the kind of mistake that doesn’t hurt immediately. It hurts three months later when you’re trying to deploy to Azure and realise your architecture assumes a single process on a single machine. I’ve been there. This post is what I wish I’d read before that happened.

The MCP C# SDK gives you four transport options: stdio, Streamable HTTP, SSE, and in-memory. They all carry the same JSON-RPC 2.0 messages between your AI client and your server — the difference is in where your server lives, how connections are managed, and what happens under production load. I’ll walk through each one honestly, with working C# code and the stuff the official docs gloss over.

If you haven’t read my post on what MCP servers actually are, start there first — this one assumes you already know the basics.

What Is an MCP Transport, Exactly?

The simplest way I can put it: the MCP transport is the pipe that carries messages between the AI model and your server. The protocol itself — tool calls, capability manifests, responses — is identical regardless of which transport you use. What changes is the physical channel those messages travel through.

Think of it like this. The conversation between you and a colleague is the same whether you’re in the same room, on a video call, or texting. The transport just determines who can initiate, how fast messages arrive, and what happens when the connection drops.

The Four Transports at a Glance

Before I go into detail on each one, here’s the map. I’ll refer back to this table throughout.

Transport Direction Sessions Scales horizontally? Best for
stdio Bidirectional Implicit — one per process N/A Local tools, Claude Desktop, IDE integrations
Streamable HTTP (stateless) Request-response None ✅ No constraints Production APIs, Azure Container Apps
Streamable HTTP (stateful) Bidirectional Mcp-Session-Id header ⚠️ Needs sticky sessions Long-running agents, server push notifications
SSE (legacy) Server→client stream + POST Query string session ID ⚠️ Needs sticky sessions Legacy client compatibility only
In-memory Bidirectional Implicit — one per pipe N/A Unit tests, same-process embedding

1. stdio — Start Here, Always

stdio is the one that surprised me the most when I first encountered MCP. The client literally launches your server as a child process and the two talk through stdin and stdout. No HTTP, no ports, no certificates. Your server is just a program reading from one pipe and writing to another.

This is how Claude Desktop connects to local MCP servers. You add your server to claude_desktop_config.json, Claude spawns the process, and from that point on the two are talking JSON-RPC over standard I/O. It’s almost offensively simple for something that enables quite sophisticated agent behaviour.

What I like about it

  • It just works, everywhere. No networking, no firewall rules, no TLS ceremony. If you can run the binary, you have a working MCP server. I’ve got stdio servers running in environments where I’m not allowed to open any network ports.
  • Bidirectional by default. The server can push notifications back to the client at any time — stdin/stdout flow control gives you natural backpressure with zero configuration.
  • Clean isolation. Each client connection gets its own process. If the server crashes, the client knows immediately and only that session is affected. Nothing shared, nothing leaked.
  • Easiest to debug. You can literally run the server in a terminal and type JSON at it. I’ve done this more times than I’d like to admit.

What to watch out for

  • Local only. The server has to live on the same machine as the client. This rules it out for any cloud or API-style deployment.
  • One client per process. Not a problem for dev tools, but if you’re imagining dozens of agents sharing one server instance — that’s not stdio’s job.
  • Secret leakage is a real risk. This one caught me off guard. By default, your child process inherits every environment variable from the parent — which means GITHUB_TOKEN, OPENAI_API_KEY, AWS_SECRET_ACCESS_KEY, all of it flows straight to your server (or a third-party server you’re connecting to). The fix is one option, but you have to know to set it.

Server — the code is three lines

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<MyTools>();

// stdout belongs to the protocol — logs must go to stderr
builder.Logging.AddConsole(options =>
{
    options.LogToStandardErrorThreshold = LogLevel.Trace;
});

await builder.Build().RunAsync();

Client

var transport = new StdioClientTransport(new StdioClientTransportOptions
{
    Command = "dotnet",
    Arguments = ["run", "--project", "MyMcpServer"],
    ShutdownTimeout = TimeSpan.FromSeconds(10)
});

await using var client = await McpClient.CreateAsync(transport);

The environment variable thing — actually fix this

Don’t skip this, especially if you’re connecting to any third-party MCP server. The safe pattern is to start from the SDK’s curated allowlist and add only what your specific server needs:

// GetDefaultEnvironmentVariables() gives you PATH, HOME, and standard
// system dirs — nothing sensitive
var env = StdioClientTransportOptions.GetDefaultEnvironmentVariables();
env["MY_SERVER_API_KEY"] = apiKey; // add only what's needed

var transport = new StdioClientTransport(new StdioClientTransportOptions
{
    Command = "my-mcp-server",
    InheritEnvironmentVariables = false, // <-- this is the important bit
    EnvironmentVariables = env,
});

If you’re writing the server yourself and you know exactly what it needs, this feels like overkill. If you’re connecting to someone else’s server — treat it like any other third-party code and don’t hand it your credentials by default.

2. Streamable HTTP — What You Want in Production

Once your MCP server needs to live somewhere other than the developer’s laptop, Streamable HTTP is the answer. It’s the transport I reach for on every Azure deployment, and the one the SDK team clearly considers the future of the protocol.

Here’s how it works: the client sends an HTTP POST. The server holds that POST response body open as an SSE stream and writes the JSON-RPC response through it — along with any intermediate messages like progress updates. So you get the reliability and auth ecosystem of HTTP, with real-time streaming baked in. Clever.

It comes in two flavours and the choice matters a lot for your deployment architecture.

Stateless mode — what I use by default

Every request is independent. No session tracking. No in-memory state between calls. This is gloriously simple to operate — deploy three instances behind Azure Front Door and every request can land on any instance. No sticky session configuration, no session replication, no “which node has this user’s state” debugging at 2am.

The trade-off: the server can’t send unsolicited messages to the client. If your tools are pure request-response — client asks, server answers — stateless is perfect and I’d argue it’s the right default for most tool-use scenarios.

Stateful mode — when you need server push

If your server needs to push notifications mid-conversation — progress updates on a long-running job, real-time alerts, streaming intermediate results — you need stateful mode. The server issues an Mcp-Session-Id header after the first request and tracks per-session state in memory. Clients can also open long-lived GET requests to receive unsolicited notifications.

The cost is operational: you need session affinity at your load balancer. On Azure Container Apps this means pinning the session to a specific replica. Not complicated, but it’s a constraint you need to plan for.

Server code

dotnet add package ModelContextProtocol.AspNetCore

Stateless (the default I recommend):

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMcpServer()
    .WithHttpTransport(options =>
    {
        options.Stateless = true;
    })
    .WithTools<MyTools>();

var app = builder.Build();
app.MapMcp(); // registers POST /mcp and GET /mcp
app.Run();

Stateful (when you need server push):

builder.Services.AddMcpServer()
    .WithHttpTransport(options =>
    {
        options.Stateless = false;
    })
    .WithTools<MyTools>();

Client

// AutoDetect tries Streamable HTTP first, falls back to SSE if needed
var transport = new HttpClientTransport(new HttpClientTransportOptions
{
    Endpoint = new Uri("https://my-mcp-server.example.com/mcp")
    // TransportMode defaults to AutoDetect
});

await using var client = await McpClient.CreateAsync(transport);

Session resumption — more useful than it sounds

In stateful mode, if the connection drops mid-conversation, you don’t have to start from scratch. Store the session ID and server info after connecting, then resume:

// Save these after first connection
string savedSessionId = client.SessionId;
McpServerCapabilities savedCaps = client.ServerCapabilities;
McpImplementation savedServerInfo = client.ServerInfo;

// On reconnect
var transport = new HttpClientTransport(new HttpClientTransportOptions
{
    Endpoint = new Uri("https://my-mcp-server.example.com/mcp"),
    KnownSessionId = savedSessionId
});

await using var client = await McpClient.ResumeSessionAsync(transport, new ResumeClientSessionOptions
{
    ServerCapabilities = savedCaps,
    ServerInfo = savedServerInfo
});

For long-running agent workflows — the kind where the model is orchestrating a multi-step task over several minutes — this is the difference between a graceful reconnect and a broken session that kills the job.

If you have browser clients (CORS)

Most MCP clients aren’t browsers, but if yours is, you’ll need CORS. One thing worth emphasising: CORS is not a substitute for host name validation — you need both. The CORS config tells browsers which origins are allowed; host name validation protects against DNS rebinding attacks from non-browser clients.

var allowedOrigins = builder.Configuration
    .GetSection("Mcp:AllowedOrigins")
    .Get<string[]>() ?? ["http://localhost:5173"];

builder.Services.AddCors(options =>
{
    options.AddPolicy("McpBrowserClient", policy =>
    {
        policy.WithOrigins(allowedOrigins)
            .WithMethods("POST", "GET", "DELETE")
            .WithHeaders("Content-Type", "Authorization",
                         "MCP-Protocol-Version", "Mcp-Session-Id")
            .WithExposedHeaders("Mcp-Session-Id");
    });
});

app.UseCors();
app.MapMcp("/mcp").RequireCors("McpBrowserClient");

3. SSE — Use It Only If You Have To

I’ll be straight with you: I wouldn’t choose SSE for anything new. The SDK marks EnableLegacySse as obsolete with diagnostic MCP9004, and that label exists for a reason.

SSE splits communication across two endpoints: your server streams to the client over a /sse connection, and the client sends messages back via HTTP POST to a /message endpoint. It was the original remote transport before Streamable HTTP arrived, and it has a structural flaw that’s hard to work around: the POST endpoint returns HTTP 202 before your handler even runs, which means there’s no backpressure. Under load, requests pile up and you can’t signal to the client that the server is overwhelmed.

The only reason to use SSE today is compatibility. Some older MCP clients — early Claude Desktop builds, some third-party tools — only speak SSE. If you need to support them alongside newer clients, run SSE alongside Streamable HTTP and let clients self-select.

Client

var transport = new HttpClientTransport(new HttpClientTransportOptions
{
    Endpoint = new Uri("https://my-mcp-server.example.com/sse"),
    TransportMode = HttpTransportMode.Sse,
    MaxReconnectionAttempts = 5,
    DefaultReconnectionInterval = TimeSpan.FromSeconds(1)
});

Server — note the pragma

builder.Services.AddMcpServer()
    .WithHttpTransport(options =>
    {
        options.Stateless = false; // SSE requires stateful mode

#pragma warning disable MCP9004
        options.EnableLegacySse = true;
#pragma warning restore MCP9004
    })
    .WithTools<MyTools>();

The fact that you need a pragma suppression to enable it should tell you everything about the SDK team’s intentions here.

4. In-Memory — Your Testing Best Friend

This one doesn’t get talked about enough. The in-memory transport connects a client and server using System.IO.Pipelines inside the same process — no network, no serialisation overhead, no infrastructure. For unit and integration tests it’s genuinely great.

What I like about it: your tests exercise the real MCP protocol, not mocks. Tool registration, schema generation, capability negotiation — all of it runs for real, just without a network between the two sides. I caught a schema mismatch bug in a tool’s [Description] attribute using an in-memory test that I’d completely missed in manual testing.

using System.IO.Pipelines;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;

Pipe clientToServer = new(), serverToClient = new();

await using var server = McpServer.Create(
    new StreamServerTransport(
        clientToServer.Reader.AsStream(),
        serverToClient.Writer.AsStream()),
    new McpServerOptions
    {
        ToolCollection =
        [
            McpServerTool.Create(
                (string message) => $"Echo: {message}",
                new() { Name = "echo" })
        ]
    });

_ = server.RunAsync();

await using var client = await McpClient.CreateAsync(
    new StreamClientTransport(
        clientToServer.Writer.AsStream(),
        serverToClient.Reader.AsStream()));

var tools = await client.ListToolsAsync();
var echo = tools.First(t => t.Name == "echo");
Console.WriteLine(await echo.InvokeAsync(new() { ["arg"] = "Hello, MCP!" }));
// Output: Echo: Hello, MCP!

One limitation worth knowing: in-memory tests won’t catch network-specific failure modes — timeouts, dropped connections, serialisation edge cases from real socket buffers. Use in-memory for protocol correctness tests, and add a handful of real HTTP integration tests for your production transport paths.

Which MCP Transport Should You Use?

Here’s the decision in plain terms. I’ve seen people overthink this — pick the one that fits your deployment and move on.

Your situation Use this
Connecting to Claude Desktop, VS Code, or any local AI client stdio
Remote server on Azure / AWS, tools are pure request-response Streamable HTTP — stateless
Remote server, needs server push or long-running job tracking Streamable HTTP — stateful
Supporting a legacy client that only speaks SSE SSE (but plan to migrate)
Writing unit or integration tests In-memory
Not sure — want the client to handle it automatically HttpTransportMode.AutoDetect on the client

One More Thing: Enterprise SSO with the ID-JAG Flow

If you’re deploying an MCP server inside a corporate environment with Okta or Entra ID, you’ll likely hit the question of how agents authenticate without requiring users to log in every time. The SDK has a built-in solution for this: the Identity Assertion Grant flow.

In short: it exchanges an OIDC ID token from your identity provider for an MCP access token via a two-step RFC-standard token exchange. Your agent stays authenticated, the access token is cached until expiry, and you call InvalidateCache() if you get a 401 and need to refresh.

using ModelContextProtocol.Authentication;

var provider = new IdentityAssertionGrantProvider(
    new IdentityAssertionGrantProviderOptions
    {
        ClientId = "mcp-client-id",
        IdpTokenEndpoint = "https://company.okta.com/oauth2/token",
        IdpClientId = "idp-client-id",
        // This callback retrieves the current user's ID token from your SSO client
        IdTokenCallback = (context, cancellationToken) =>
            mySsoClient.GetIdTokenAsync(cancellationToken)
    },
    new HttpClient());

var tokens = await provider.GetAccessTokenAsync(
    resourceUrl: new Uri("https://mcp-server.example.com"),
    authorizationServerUrl: new Uri("https://auth.mcp-server.example.com"),
    cancellationToken: ct);

// On 401: provider.InvalidateCache() forces a fresh exchange next call

I’ll cover enterprise auth in more depth in a dedicated post — there’s enough there to warrant its own treatment, especially for Azure-hosted scenarios with Entra ID.

Frequently Asked Questions

What is the default MCP transport in the C# SDK?

There isn’t one — you choose explicitly when configuring the server. The most common starting points are WithStdioServerTransport() for local tools and WithHttpTransport() from ModelContextProtocol.AspNetCore for anything deployed remotely.

Can I switch transports without rewriting my tools?

Yes, completely. The transport is configured in Program.cs; your [McpServerTool] methods don’t know or care which transport is active. I’ve migrated projects from stdio to Streamable HTTP in under 10 minutes — it’s genuinely just a config change.

What is the difference between stateless and stateful Streamable HTTP?

Stateless means each HTTP POST is a standalone request — no session, no shared state, infinitely scalable. Stateful means the server issues an Mcp-Session-Id and tracks per-session state in memory, which enables server-to-client push notifications and session resumption but requires sticky sessions at your load balancer.

Is SSE still supported in the MCP C# SDK?

Yes, but it’s marked obsolete (diagnostic MCP9004). The SDK team recommends Streamable HTTP for all new work. SSE is there for backwards compatibility with older clients and will likely be removed in a future major version.

What transport does Claude Desktop use?

stdio. Claude Desktop launches your server as a child process using the command defined in claude_desktop_config.json and communicates over stdin/stdout. This is why local MCP servers for Claude are so simple to set up — no networking involved at all.

Can I run stdio and Streamable HTTP on the same server?

Not in a single AddMcpServer() setup, but the practical pattern is to put your tool logic in a shared class library and have two thin host projects — one with stdio for local dev tooling, one with Streamable HTTP for production. The tool code is identical; only the entry point differs.


Transport decisions look simple on paper and get complicated fast when you’re deploying to Azure with sticky session requirements and corporate SSO in the way. If you’re navigating that and want a second opinion, find me on LinkedIn — I’m in Sydney and happy to talk it through.

This is part of my MCP server series: What Are MCP Servers? · Real-World MCP Case Study · Production MCP Deployment on Azure.

What Are MCP Servers? A Complete Guide for AI Engineers

If you’re building AI agents that need to interact with real systems — databases, APIs, internal tools — you’ve probably run into the same wall I did. I’m talking about the mess of hardcoded JSON schemas in system prompts, brittle API wrappers, and production incidents that happen when the LLM invents a parameter name that doesn’t exist. An MCP server (Model Context Protocol server) is the structured solution to that problem, and as an AI engineer working in Sydney, it’s now a core part of how I build agent systems.

Let me walk you through what MCP servers are, why they matter, and how to build one in C# using the official SDK — with real, working code.

The Problem an MCP Server Solves

When you build an AI agent that needs to do things — query a database, call an API, read a file — you have to teach the model what tools exist, what they accept, and what they return. The old approach was: write it into the system prompt and pray.

The problem is that system prompts don’t have a schema. If your tool expects a date in ISO 8601 format and the model sends "tomorrow", your app crashes. If you rename a parameter, you need to update prompts scattered across multiple deployments. There’s no contract between the AI and your code.

MCP fixes this by defining a standard protocol — a contract — between AI models (clients) and the external capabilities they need (servers). An MCP server advertises what tools it offers, what inputs each tool expects, and what it returns. The model calls tools through MCP the same way a browser calls APIs through HTTP: with a defined interface that both sides agree on in advance.

This isn’t just theoretical tidiness. It’s what makes AI agents maintainable in production.

How an MCP Server Works

Think of MCP like a USB-C standard for AI tools. Before USB-C, every device had its own charger. After, one cable works everywhere. MCP does the same thing for AI capabilities.

Here’s the flow when an AI agent uses an MCP tool:

  1. The agent (MCP client) connects to your MCP server at startup.
  2. The server sends a capabilities manifest — a list of tools it provides, with their names, descriptions, and input schemas.
  3. The agent’s host (Claude, ChatGPT, your own orchestrator) reads this manifest and makes the tools available to the model.
  4. When the model decides to call a tool, it sends a structured request through the client.
  5. Your server executes the tool and returns a structured response.
  6. The model reads the result and continues reasoning.

The beauty of step 2 is that the model now has a machine-readable description of your tools — not a paragraph of English in a system prompt, but a typed schema. Fewer hallucinations. Cleaner architecture. Tools you can version and test independently.

The Three Things an MCP Server Can Expose

MCP servers can expose three types of capabilities:

Tools — Functions the model can call. Think of these as your API endpoints. A tool takes typed inputs and returns a result. This is what most people think of when they hear “MCP server.”

Resources — Read-only data the model can access. Like files, database records, or live feeds. The model can browse available resources and read their contents.

Prompts — Reusable prompt templates the model can invoke. Useful for standardising how certain tasks are framed across your application.

For most enterprise use cases, you’ll mostly be building Tools. Resources matter when you want the model to explore data rather than query it directly. Prompts are useful in multi-agent workflows.

Architecture Overview

Before we touch code, here’s how the pieces connect. The diagram shows the three zones: the AI client (Claude Desktop or your app), the MCP server (our C# application), and the backend systems it talks to. Use the zoom controls inside the diagram to explore it in detail.

MCP Server architecture: AI client → JSON-RPC protocol → C# server → backend systems. Pan and zoom inside the diagram, or open full-screen in draw.io ↗

Building a Real MCP Server in C#

Enough theory. Let me show you how this looks in code using the official MCP C# SDK (ModelContextProtocol), which Microsoft co-maintains with Anthropic. This is production-ready code — not a toy example.

Step 1: Install the packages

dotnet add package ModelContextProtocol
dotnet add package Microsoft.Extensions.Hosting

This gives you the full MCP server runtime with the .NET generic host, dependency injection, and logging baked in.

Step 2: Project structure

MyMcpServer/
├── Program.cs
├── Tools/
│   └── ProductTools.cs
└── MyMcpServer.csproj

Step 3: Wire up the host in Program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MyMcpServer.Tools;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddMcpServer()
    .WithStdioServerTransport()   // Claude Desktop and most MCP clients use stdio
    .WithTools<ProductTools>();   // Register your tool class

// Log to stderr — stdout must stay clean for the MCP protocol
builder.Logging.AddConsole(options =>
{
    options.LogToStandardErrorThreshold = LogLevel.Trace;
});

// Your tools get full DI — inject anything here
builder.Services.AddSingleton<IProductRepository, SqlProductRepository>();

await builder.Build().RunAsync();

Three things worth noting. First, AddMcpServer() wires up the entire MCP runtime — you write zero protocol handling. Second, WithStdioServerTransport() is for local Claude Desktop use; for Azure deployments you swap it for the ModelContextProtocol.AspNetCore HTTP transport. Third, your tool classes get full dependency injection — inject your database, HTTP clients, loggers, anything.

Step 4: Define your tools with attributes

using ModelContextProtocol;
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace MyMcpServer.Tools;

[McpServerToolType]
public sealed class ProductTools
{
    private readonly IProductRepository _repo;

    public ProductTools(IProductRepository repo)
    {
        _repo = repo;
    }

    [McpServerTool, Description("Search for products by name or SKU. Returns matching products with current stock levels.")]
    public async Task<string> SearchProducts(
        [Description("The product name or SKU to search for.")] string query,
        [Description("Maximum number of results to return. Defaults to 10.")] int limit = 10)
    {
        var results = await _repo.SearchAsync(query, limit);

        if (!results.Any())
            return $"No products found matching '{query}'.";

        return string.Join("\n---\n", results.Select(p =>
            $"SKU: {p.Sku}\nName: {p.Name}\nStock: {p.StockLevel}\nPrice: {p.Price:C}"));
    }

    [McpServerTool, Description("Get detailed information about a specific product by its SKU.")]
    public async Task<string> GetProductDetails(
        [Description("The product SKU (e.g. PROD-12345).")] string sku)
    {
        var product = await _repo.GetBySkuAsync(sku);

        if (product is null)
            return $"No product found with SKU '{sku}'.";

        return $"""
            SKU: {product.Sku}
            Name: {product.Name}
            Description: {product.Description}
            Stock Level: {product.StockLevel} units
            Price: {product.Price:C}
            Category: {product.Category}
            Last Updated: {product.UpdatedAt:yyyy-MM-dd HH:mm} UTC
            """;
    }
}

The [McpServerTool] attribute registers the method as an MCP tool. The [Description] attributes on both the method and its parameters become the typed schema that the AI model reads — this replaces the fragile English paragraphs in your system prompt. If the description is precise, the model uses the tool correctly. If the parameter description specifies the expected format, the model sends the right format. Every time.

What the AI Model Actually Sees

When Claude or any MCP client connects to this server, it automatically receives a capabilities manifest generated from your C# code:

{
  "tools": [
    {
      "name": "SearchProducts",
      "description": "Search for products by name or SKU. Returns matching products with current stock levels.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "description": "The product name or SKU to search for."
          },
          "limit": {
            "type": "integer",
            "description": "Maximum number of results to return. Defaults to 10.",
            "default": 10
          }
        },
        "required": ["query"]
      }
    }
  ]
}

The SDK generates this JSON schema from your C# method signatures automatically. No manual schema writing. No JSON maintenance. Rename a parameter in C#, the schema updates. Add a new tool method, it appears in the manifest. This is the developer experience that was missing before MCP.

Running It Locally with Claude Desktop

To test your MCP server with Claude Desktop, add it to your claude_desktop_config.json:

{
  "mcpServers": {
    "product-server": {
      "command": "dotnet",
      "args": ["run", "--project", "/path/to/MyMcpServer"]
    }
  }
}

Restart Claude Desktop, open a conversation, and ask: “What products do we have in the PROD-1 range?” Claude will automatically call SearchProducts, read the result, and answer from real data — not a hallucination.

That moment when you see the AI reach into your actual database and return a real answer is quietly satisfying in a way that’s hard to describe until you’ve experienced it.

When Should You Build an MCP Server?

Build one when:

  • You want the same capabilities available to multiple AI clients (Claude, Copilot, your internal tools) without duplicating integration code.
  • You’re building an AI agent that needs to interact with internal systems — databases, ERPs, CRMs — that aren’t publicly accessible.
  • You want to give your tools a proper versioned schema rather than relying on natural language in prompts.
  • You’re building a multi-agent system and need a reliable way for agents to share capabilities.

Don’t bother when you’re prototyping with a single model and a single capability. Use function calling directly. MCP pays off when you’re thinking about the long run — multiple clients, multiple tools, real maintainability.

The Bigger Picture

MCP is becoming the USB-C of AI tooling. Claude supports it natively. GitHub Copilot is adding support. OpenAI’s agent framework is converging on it. If you’re building AI systems that need to interact with the real world, this is the protocol worth learning now.

More importantly for us as engineers: it gives us a way to reason about AI tool integrations the same way we reason about APIs. Contract-first, typed, testable, versioned. That’s the kind of discipline that makes enterprise AI systems actually work in production.

I’ve been working with MCP servers on Azure-hosted agent systems, and the difference in maintainability compared to prompt-embedded tool descriptions is night and day. If you’re building anything beyond a demo, start here.


Working on an AI integration project in Sydney and want to talk through the architecture? Connect with me on LinkedIn — I’m always up for a conversation about what’s actually working in production.

Next up: How I Built an MCP Server for Enterprise Data — a Real-World Case Study, where I go deeper on authentication, error handling, and deploying to Azure Container Apps.

Mastering SOLID Principles in C# Development

SOLID Pattern Object Oriented Design and How to Use It in C#

  • Enhances maintainability and scalability of applications.
  • Guides developers in crafting robust software systems.
  • Encourages extensible software architectures.
  • Improves reliability and promotes clean design.
  • Facilitates easier testing and mocking through abstraction.

Table of Contents

Understanding SOLID Principles

The SOLID acronym comprises five principles:

  1. Single Responsibility Principle (SRP)
  2. Open/Closed Principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)

While these principles are applicable across various programming languages, they align exceptionally well with C# due to its robust type system and object-oriented capabilities. Let’s delve into each principle in detail.

Single Responsibility Principle (SRP)

Definition: A class should have only one reason to change, meaning it should only have one job or responsibility.

Implementation in C#:

Consider the following implementation where a class violates SRP by performing multiple roles:


// Bad example - multiple responsibilities
public class UserService
{
    public void RegisterUser(string email, string password)
    {
        // Register user logic
        // Send email logic
        // Log activity
    }
}

In contrast, adhering to the Single Responsibility Principle leads to a more maintainable structure:


// Better example - single responsibility
public class UserRegistration
{
    private readonly EmailService _emailService;
    private readonly LoggingService _loggingService;
    
    public UserRegistration(EmailService emailService, LoggingService loggingService)
    {
        _emailService = emailService;
        _loggingService = loggingService;
    }
    
    public void RegisterUser(string email, string password)
    {
        // Only handle user registration
        var user = new User(email, password);
        SaveUserToDatabase(user);
        
        _emailService.SendWelcomeEmail(email);
        _loggingService.LogActivity("User registered: " + email);
    }
}

Benefits of SRP:

  • Improved maintainability as each class has a distinct responsibility.
  • Easier collaboration; team members can work on separate functionalities with minimal overlap.

Open/Closed Principle (OCP)

Definition: Software entities should be open for extension but closed for modification.

Implementation in C#:

Let’s assess a traditional approach that violates the OCP:


// Bad approach
public class AreaCalculator
{
    public double CalculateArea(object shape)
    {
        if (shape is Rectangle rectangle)
            return rectangle.Width * rectangle.Height;
        else if (shape is Circle circle)
            return Math.PI * circle.Radius * circle.Radius;
        
        throw new NotSupportedException("Shape not supported");
    }
}

By implementing the OCP, we can extend functionality without altering existing code:


// Better approach using OCP
public interface IShape
{
    double CalculateArea();
}

public class Rectangle : IShape
{
    public double Width { get; set; }
    public double Height { get; set; }
    
    public double CalculateArea()
    {
        return Width * Height;
    }
}

public class Circle : IShape
{
    public double Radius { get; set; }
    
    public double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

// Now we can add new shapes without modifying existing code

Benefits of OCP:

  • Encourages the development of extensible software architectures.
  • Reduces the risk of introducing bugs to existing functionalities.

Liskov Substitution Principle (LSP)

Definition: Objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.

Implementation in C#:

Let’s critique this implementation which violates LSP:


// Violation of LSP
public class Rectangle
{
    public virtual int Width { get; set; }
    public virtual int Height { get; set; }
    
    public virtual int GetArea()
    {
        return Width * Height;
    }
}

public class Square : Rectangle
{
    public override int Width 
    { 
        get { return base.Width; }
        set { 
            base.Width = value;
            base.Height = value; // This breaks LSP
        }
    }
}

To adhere to LSP, we separate shape behavior into correct implementations:


// Better approach adhering to LSP
public interface IShape
{
    int GetArea();
}

public class Rectangle : IShape
{
    public int Width { get; set; }
    public int Height { get; set; }
    
    public int GetArea()
    {
        return Width * Height;
    }
}

public class Square : IShape
{
    public int Side { get; set; }
    
    public int GetArea()
    {
        return Side * Side;
    }
}

Benefits of LSP:

  • Promotes a reliable hierarchy, ensuring placeholder objects work seamlessly in place of base class instances.

Interface Segregation Principle (ISP)

Definition: Clients should not be forced to depend on interfaces they do not use.

Implementation in C#:

This example showcases a common mistake by violating ISP:


// Violation of ISP
public interface IWorker
{
    void Work();
    void Eat();
    void Sleep();
}

// Better approach with segregated interfaces
public interface IWorkable
{
    void Work();
}

public interface IEatable
{
    void Eat();
}

public interface ISleepable
{
    void Sleep();
}

Benefits of ISP:

  • Reduces side effects and promotes clean design, enhancing modularity.
  • Developers work with specific interfaces relevant to their implementations.

Dependency Inversion Principle (DIP)

Definition: High-level modules should not depend on low-level modules; both should depend on abstractions.

Implementation in C#:

Examine this flawed approach under DIP:


// Violation of DIP
public class NotificationService
{
    private readonly EmailSender _emailSender;
    
    public NotificationService()
    {
        _emailSender = new EmailSender();
    }
    
    public void SendNotification(string message, string recipient)
    {
        _emailSender.SendEmail(message, recipient);
    }
}

Implementing DIP effectively allows for a more flexible design:


// Better approach using DIP
public interface IMessageSender
{
    void SendMessage(string message, string recipient);
}

public class EmailSender : IMessageSender
{
    public void SendMessage(string message, string recipient)
    {
        // Email sending logic
    }
}

public class SMSSender : IMessageSender
{
    public void SendMessage(string message, string recipient)
    {
        // SMS sending logic
    }
}

public class NotificationService
{
    private readonly IMessageSender _messageSender;
    
    public NotificationService(IMessageSender messageSender)
    {
        _messageSender = messageSender;
    }
    
    public void SendNotification(string message, string recipient)
    {
        _messageSender.SendMessage(message, recipient);
    }
}

Benefits of DIP:

  • Enhances the flexibility and reusability of code.
  • Facilitates easier testing and mocking through abstraction.

Conclusion

Incorporating the SOLID principles in C# development results in several benefits, such as improved maintainability, enhanced testability, increased flexibility, better code organization, and reduced technical debt. As applications grow in scale and complexity, consciously applying these principles will contribute to producing robust, maintainable, and adaptable software systems.

By prioritizing SOLID principles in your coding practices, you won’t just write C# code— you’ll create software that stands the test of time.

If you’re interested in exploring further implementation examples, feel free to connect with me on LinkedIn or check out my GitHub. Happy coding!

FAQ

What are the SOLID principles?

The SOLID principles are five design principles that help software developers create more maintainable and flexible systems.

How does SRP improve code quality?

SRP enhances code quality by ensuring that a class has only one reason to change, making it easier to manage and understand.

What advantages does OCP provide?

OCP allows developers to extend functionalities without changing existing code, reducing bugs and improving code safety.

Can LSP help avoid bugs?

Yes, adhering to LSP promotes a reliable class hierarchy and helps to avoid bugs that can arise from unexpected behavior in subclasses.

Why is Dependency Inversion important?

DIP is crucial for reducing coupling and enhancing flexibility, making it easier to change or replace components without affecting high-level modules.

Azure Service Bus Peek-Lock: A Comprehensive Guide to Reliable Message Processing

Working with Peek-Lock in Azure Service Bus: A Practical Guide

In many distributed systems, reliable message handling is a top priority. When I first started building an order processing application, I learned very quickly that losing even one message could cause major headaches. That’s exactly where Azure Service Bus and its Peek-Lock mode came to the rescue. By using Peek-Lock, you don’t remove the message from the queue as soon as you receive it. Instead, you lock it for a certain period, process it, and then decide what to do next—complete, abandon, dead-letter, or defer. Here’s how it all fits together.

Why Peek-Lock Matters

Peek-Lock is one of the two receiving modes offered by Azure Service Bus. The other is Receive and Delete, which automatically removes messages from the queue upon receipt. While that might be fine for scenarios where occasional message loss is acceptable, many real-world applications need stronger guarantees.

  1. Reliability: With Peek-Lock, if processing fails, you can abandon the message. This makes it visible again for another attempt, reducing the risk of data loss.
  2. Explicit Control: You decide when a message is removed. After you successfully handle the message (e.g., update a database or complete a transaction), you explicitly mark it as complete.
  3. Error Handling: If the same message repeatedly fails, you can dead-letter it for investigation. This helps avoid getting stuck in an endless processing loop.

What Happens If the Lock Expires?

By default, the lock is held for a certain period (often 30 seconds, which can be adjusted). If your code doesn’t complete or abandon the message before the lock expires, the message becomes visible to other receivers. To handle potentially lengthy processes, you can renew the lock programmatically, although that introduces additional complexity. The key takeaway is that you should design your service to either complete or abandon messages quickly, or renew the lock if more time is truly necessary.

Default Peek-Lock in Azure Functions

When you use Azure Service Bus triggers in Azure Functions, you generally don’t need to configure or manage the Peek-Lock behavior yourself. According to the official documentation, the default behavior in Azure Functions is already set to Peek-Lock. This means you can focus on your function’s core logic without explicitly dealing with message locking or completion in most scenarios.

Don’t Swallow Exceptions

One important detail to note is that in Azure Functions, any unhandled exceptions in your function code will signal to the runtime that message processing failed. This prevents the function from automatically completing the message, allowing the Service Bus to retry later. However, if you wrap your logic in a try/catch block and inadvertently swallow the exception—meaning you catch the error without rethrowing or handling it properly—you might unintentionally signal success. That would lead to the message being completed even though a downstream service might have failed.

Recommendation:

  • If you must use a try/catch, make sure errors are re-thrown or handled in a way that indicates failure if the message truly hasn’t been processed successfully. Otherwise, you’ll end up completing the message and losing valuable information about the error.

Typical Use Cases

  1. Financial Transactions: Losing a message that represents a monetary transaction is not an option. Peek-Lock ensures messages remain available until your code confirms it was successfully processed.
  2. Critical Notifications: If you have an alerting system that notifies users about important events, you don’t want those notifications disappearing in case of a crash.
  3. Order Processing: In ecommerce or supply chain scenarios, every order message has to be accounted for. Peek-Lock helps avoid partial or lost orders due to transient errors.

Example in C#

Here’s a short snippet that demonstrates how you can receive messages in Peek-Lock mode using the Azure.Messaging.ServiceBus library:

using System;
using System.Threading.Tasks;
using Azure.Messaging.ServiceBus;

public class PeekLockExample
{
    private const string ConnectionString = "<YOUR_SERVICE_BUS_CONNECTION_STRING>";
    private const string QueueName = "<YOUR_QUEUE_NAME>";

    public async Task RunPeekLockSample()
    {
        // Create a Service Bus client
        var client = new ServiceBusClient(ConnectionString);

        // Create a receiver in Peek-Lock mode
        var receiver = client.CreateReceiver(
            QueueName, 
            new ServiceBusReceiverOptions 
            { 
                ReceiveMode = ServiceBusReceiveMode.PeekLock 
            }
        );

        try
        {
            // Attempt to receive a single message
            ServiceBusReceivedMessage message = await receiver.ReceiveMessageAsync(TimeSpan.FromSeconds(10));

            if (message != null)
            {
                // Process the message
                string body = message.Body.ToString();
                Console.WriteLine($"Processing message: {body}");

                // If processing is successful, complete the message
                await receiver.CompleteMessageAsync(message);
                Console.WriteLine("Message completed and removed from the queue.");
            }
            else
            {
                Console.WriteLine("No messages were available to receive.");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An error occurred: {ex.Message}");
            // Optionally handle or log the exception
        }
        finally
        {
            // Clean up resources
            await receiver.CloseAsync();
            await client.DisposeAsync();
        }
    }
}

What’s Happening Here?

  • We create a ServiceBusClient to connect to Azure Service Bus.
  • We specify ServiceBusReceiveMode.PeekLock when creating the receiver.
  • The code then attempts to receive one message and processes it.
  • If everything goes smoothly, we call CompleteMessageAsync to remove it from the queue. If something goes wrong, the message remains locked until the lock expires or until we choose to abandon it.

Final Thoughts

Peek-Lock strikes a balance between reliability and performance. It ensures you won’t lose critical data while giving you the flexibility to handle errors gracefully. Whether you’re dealing with financial operations, critical user notifications, or any scenario where each message must be processed correctly, Peek-Lock is an indispensable tool in your Azure Service Bus arsenal.

In Azure Functions, you get this benefit without having to manage the locking details, so long as you don’t accidentally swallow your exceptions. For other applications, adopting Peek-Lock might demand a bit more coding, but it’s well worth it if you need guaranteed, at-least-once message delivery.

Whether you’re building a simple queue-based workflow or a complex event-driven system, Peek-Lock ensures your messages remain safe until you decide they’re processed successfully. It’s a powerful approach that balances performance with reliability, which is why it’s a must-know feature for developers relying on Azure Service Bus.

Tag Replacement using Regex in .NET

This snippet of code shows how to do Synchronisation or HTML tag replacement

-My problem is i want to synchronise of tags between 2 HTML document

Issue

-Source.htm

my source.htm has this tag

<xml id=STATIC_CRITERIA_>

  <fields>

    <field fieldID=OPEN_FLAG displayType=checkboxGroup searchType=checkboxGroup underlyingType=int />

    <field fieldID=PARTITION displayType=dropdown searchType=profile profileID=SU_PARTITIONS underlyingType=int />

    <field fieldID=TEMPLATE_IND displayType=checkbox searchType=checkbox underlyingType=int />

    <field fieldID=INCLUDE_DELETED displayType=checkbox searchType=checkbox underlyingType=string />

  </fields>

</xml>

 -Target.htm has this tag

<xml id=STATIC_CRITERIA_>

</xml>

So now the problem is how do I fill the gap in XML tag in target.htm with the value from mySource.htm. We can do this using regex and it’s very simple

 

  string originalDocument = Load(“c:\\MySource.htm”);

                string syncDocument = Load(“c:\\Target.htm”);

 

                MatchCollection mc = Regex.Matches(originalDocument, “<xml([ ]?.*?)>(.*?)</xml>”, RegexOptions.Singleline | RegexOptions.IgnoreCase);

 

                foreach (Match m in mc)

                {

                    string token = “<xml{0}>”;

                    syncDocument = Regex.Replace(syncDocument, String.Format(token, m.Groups[1].Value) + “(.*?)</xml>”,

                                                               String.Format(token, m.Groups[1].Value) + m.Groups[2].Value + “</xml>”,

                                                               RegexOptions.Singleline | RegexOptions.IgnoreCase);

                }

 MatchCollection is used to return all the instances of that regular expression (e.g you might have multiple XML tags with different ID)

What does this tag means

“<xml([ ]?.*?)>(.*?)</xml>”

 ([ ]?.*) means that I don’t care whatever after it (e.g it can be ID or attributes etc). this is the first parameter that we stored on variable

(.*?) means that whatever inside the tag are captured into the second parameter that we stored on variable

You can access the first variable (e.g any attributes after the XML) by doing

m.Groups[1].Value

you can access the value inside the xml bracket by using

m.Groups[2].Value

so what  contains in

m.Groups[0].Value

it contains the whole xml tag that we extracted using regex

 then we can use regex.replace method to replace the tag in target html. When you replace the tag you need to replace the whole xml tag. You can’t just replace the inner part of it

   foreach (Match m in mc)

                {

                    string token = “<xml{0}>”;

                    syncDocument = Regex.Replace(syncDocument, String.Format(token, m.Groups[1].Value) + “(.*?)</xml>”,

                                                               String.Format(token, m.Groups[1].Value) + m.Groups[2].Value + “</xml>”,

                                                               RegexOptions.Singleline | RegexOptions.IgnoreCase);

                }

Send message in Twitter using .NET

This code snippet is used to wrap Twitter API to send message. It’s not a complete Twitter wrapper but It can send message using Twitter easily. There are two overloaded constructors where both of them requires user name and password to be passed and proxy server is used as the overloaded parameter.

using System;
using System.IO;
using System.Net;
using System.Xml;
using System.Web;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Helpers
{
    public class TwitterHelpers
    {
        private string _twitterJsonUrl = "http://twitter.com/statuses/update.json";

        public string TwitterJsonUrl
        {
            get { return _twitterJsonUrl; }
            set { _twitterJsonUrl = value; }
        }

        private string _twitterUser = string.Empty;

        public string TwitterUser
        {
            get { return _twitterUser; }
            set { _twitterUser = value; }
        }

        private string _twitterPass = string.Empty;

        public string TwitterPass
        {
            get { return _twitterPass; }
            set { _twitterPass = value; }
        }

        private string _proxyServer = string.Empty;

        public string ProxyServer
        {
            get { return _proxyServer; }
            set { _proxyServer = value; }
        }

        public string SendTwitterMessage(string message)
        {
            try
            {
                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(TwitterJsonUrl);
                System.Net.ServicePointManager.Expect100Continue = false;

                string post = string.Empty;
                using (TextWriter writer = new StringWriter())
                {
                    writer.Write("status={0}", HttpUtility.UrlEncode(message.Substring(0,140)));
                    post = writer.ToString();
                }

                SetRequestParams(request);

                request.Credentials = new NetworkCredential(TwitterUser, TwitterPass);

                using (Stream requestStream = request.GetRequestStream())
                {
                    using (StreamWriter writer = new StreamWriter(requestStream))
                    {
                        writer.Write(post);
                    }
                }

                WebResponse response = request.GetResponse();
                string content;

                using (Stream responseStream = response.GetResponseStream())
                {
                    using (StreamReader reader = new StreamReader(responseStream))
                    {
                        content = reader.ReadToEnd();
                    }
                }

                return content;

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void SetRequestParams(HttpWebRequest request)
        {
            request.Timeout = 500000;
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.UserAgent = "My Twitter Application";

            if (!string.IsNullOrEmpty(_proxyServer))
            {
                request.Proxy = new WebProxy(_proxyServer, false);
            }

        }

        public TwitterHelpers(string userName, string userPassword, string proxyServer)
        {
            _twitterUser = userName;
            _twitterPass = userPassword;
            _proxyServer = proxyServer;
        }

        public TwitterHelpers(string userName, string userPassword)
        {
            _twitterUser = userName;
            _twitterPass = userPassword;
        }

    }
}

Usage:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleAppTest
{
    class Program
    {
        private static TwitterHelpers _twitterHelpers = null;

        private static TwitterHelpers TwitterHelpers
        {
            get
            {
                if (_twitterHelpers == null)
                {
                    _twitterHelpers = new TwitterHelpers("twitteruser", "twitterpassword");
                }

                return _twitterHelpers;
            }
        }

        static void Main(string[] args)
        {
            TwitterHelpers.SendTwitterMessage("Hello World!!");
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }
    }
}

SQL Server Function using CLR

UPDATED: I’ve added one function to write from BLOB in SQL Server table to the disk straight away

I thought this article might be useful for anyone that wants to implement .NET code to SQL server level. In this case I really need CLR because I want to do compression of images and I believe it’s not possible to do that using pure SQL server stored procedure and I’m trying to avoid creating a .NET application just for compression of images through row by row processing.

Here we start:

This is your C#/.NET code, you need to declare it as SQL function or SQL stored procedure

using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using Zip = ICSharpCode.SharpZipLib.Zip.Compression;

namespace CLRCompressionFunctions
{
    public partial class CompressionCore
    {
        [SqlFunction()]
        public static SqlBytes fn_Compress(SqlBytes uncompressedBytes)
        {
            if (uncompressedBytes.IsNull)
                return uncompressedBytes;

            MemoryStream memory = new MemoryStream();

            ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream stream =
                new ICSharpCode.SharpZipLib.Zip.Compression.Streams.DeflaterOutputStream(memory, new Zip.Deflater(Zip.Deflater.BEST_COMPRESSION), 131072);

            stream.Write(uncompressedBytes.Buffer, 0, Convert.ToInt32(uncompressedBytes.Length));
            stream.Flush();
            stream.Close();
            stream = null;

            return new SqlBytes(memory.ToArray());
        }

        [SqlFunction()]
        public static SqlBytes fn_Decompress(SqlBytes compressedBytes)
        {
            if (compressedBytes.IsNull)
                return compressedBytes;

            ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream stream =
                new ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream(new MemoryStream(compressedBytes.Buffer));
            MemoryStream memory = new MemoryStream();
            byte[] writeData = new byte[4096];
            int size;

            while (true)
            {
                size = stream.Read(writeData, 0, writeData.Length);
                if (size > 0)
                {
                    memory.Write(writeData, 0, size);
                }
                else break;
            }
            stream.Flush();
            stream.Close();
            stream = null;

            return new SqlBytes(memory.ToArray());
        }

[SqlFunction()]
        public static SqlString fn_WriteFile(SqlString path, SqlBytes bytesFile, SqlBoolean isCompressed)
        {
            string returnString = string.Empty;

            try
            {
                //check if the file exists or not
                FileStream myFStream = new FileStream(path.ToString(), FileMode.OpenOrCreate, FileAccess.ReadWrite);

                SqlBytes fileBytes = bytesFile;

                if (isCompressed)
                {
                    fileBytes = fn_Decompress(bytesFile);
                }

                int Length = 256;
                Byte[] buffer = new Byte[Length];

                Stream readStream = fileBytes.Stream;

                int bytesRead = readStream.Read(buffer, 0, Length);

                // write the required bytes
                while (bytesRead > 0)
                {
                    myFStream.Write(buffer, 0, bytesRead);
                    bytesRead = readStream.Read(buffer, 0, Length);
                }

                readStream.Close();
                myFStream.Close();
                returnString = "File is written successfully";
            }
            catch (Exception ex)
            {
                returnString = ex.ToString();
            }

            return new SqlString(returnString);
        }
    }
}

Installation time to your SQL Server, You need to register your assembly(.dll) as well as referenced Assembly to SQL Server

ALTER DATABASE TestAssembly SET TRUSTWORTHY ON
GO
EXEC sp_configure 'clr enabled', 1;
RECONFIGURE WITH OVERRIDE;
GO
CREATE ASSEMBLY [ICSharpCode.SharpZipLib.dll]
                  FROM 'D:\Applications\FileCompressorApp\CLRCompressionFunctions\Deployment\ICSharpCode.SharpZipLib.dll'
                  WITH PERMISSION_SET = UNSAFE
GO
CREATE ASSEMBLY [CLRCompressionFunctions]
                  FROM 'D:\Applications\FileCompressorApp\CLRCompressionFunctions\Deployment\CLRCompressionFunctions.dll'
                  WITH PERMISSION_SET = EXTERNAL_ACCESS
GO

PERMISSION_SET = SAFE only if you don’t want the assembly accessing external resources such as writing to disk, but in this case the function is used to write into the disk
e.g How about if you want to use/register System.Drawing to your assembly? Yes you can do it by using

CREATE ASSEMBLY [System.Drawing.dll]
FROM 'C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll'
WITH PERMISSION_SET = UNSAFE

You need to enable the CLR on SQL server in order to use your function

Now you need to create your function based on your assembly

CREATE FUNCTION [fn_Compress]           (
                 @uncompressedBytes varbinary(MAX))
            RETURNS varbinary(MAX)
            AS    EXTERNAL NAME CLRCompressionFunctions.[CLRCompressionFunctions.CompressionCore].fn_Compress
GO
CREATE FUNCTION [fn_Decompress]           (
                 @uncompressedBytes varbinary(MAX))
            RETURNS varbinary(MAX)
            AS    EXTERNAL NAME CLRCompressionFunctions.[CLRCompressionFunctions.CompressionCore].fn_Decompress
GO
CREATE FUNCTION [fn_WriteFile]           (
                 @path nvarchar(4000),
				 @bytesFile varbinary(MAX),
				 @bitCompressed bit)
			RETURNS nvarchar(4000)
            AS    EXTERNAL NAME CLRCompressionFunctions.[CLRCompressionFunctions.CompressionCore].fn_WriteFile
GO

Usage, It’s the same as you call a function in SQL Server

SELECT dbo.[fn_Compress](testimage) FROM tblImages
SELECT dbo.[fn_Decompress](imgFileContent) FROM TABLE_NAME
GO
SELECT dbo.[fn_WriteFile]('c:\test.pdf',imgFileContent, 0) FROM TABLE_NAME
GO 

Linq.Binary(SQL Image Data Type) Type to File Stream

I got a column with Data Type of Image in SQL Server. What I would like to do is to omit this data to a FileStream or to a file. I tried to read the data using LINQ and I found the data type detected for that particular column is System.Data.Linq.Binary. I was expecting it to be Byte Data type. So I need to convert the Binary to byte then to File Stream. But I found a simpler way by using “ToArray” properties and cast it back to byte solves my problem.

    foreach (tblExpReceiptFile file in ExpReceiptFactory.tblExpReceiptFileSelect())
            {
                string fileName = file.vcFileName.Replace(" ","_");
                FileStream fileStream = File.Create(DirectoryPath + @"\" + fileName + ".pdf");
                fileStream.Write((byte[])file.imgFileContent.ToArray(), 0, ((byte[])file.imgFileContent.ToArray()).Length);
                fileStream.Close();
            }

Generic Logging Class in .NET

This is a snippet code which is useful in creating your own logging class. You can compile this as a library and use it accross the projects.

using System;
using System.Configuration;
using System.Diagnostics;
using System.Reflection;

namespace MyLogging
{
    /// 
    /// provides event logging capabilities
    ///
    /// requires a LogSource setting in app/web.config, which must match the name of the event log
    /// 
    public class Logger
    {
        #region constants

        static readonly string ERROR_NOSOURCE = "MyLogging - cannot write to event log - please specify a LogSource in app/web.config";

        #endregion

        #region static ctor

        static Logger()
        {
            if (ConfigurationManager.AppSettings["LogSource"] == null)
            {
                throw new ApplicationException(ERROR_NOSOURCE);
            }
            else
            {
                _source = ConfigurationManager.AppSettings["LogSource"].ToString();

                if (!EventLog.SourceExists(_source))
                {
                    EventLog.CreateEventSource(_source, "Application");
                    EventLog.WriteEntry(_source, "log source created");
                }
            }
        }

        #endregion

        #region properties - LogSource

        private static string _source = null;

        public static string LogSource
        {
            get { return _source; }
            set { _source = value; }
        }

        #endregion

        #region public logging methods

        /// 
        /// logs an exception, using reflection to determine calling method and module
        /// 
        /// 
        public static void LogException(Exception ex)
        {
            MethodBase  method = ex.TargetSite;
            Module      module = method.Module;

            string      msg = module.Name + "." + method.Name
                            + " - " + ex.Message
                            + Environment.NewLine
                            + "Stack Trace - " + ex.StackTrace;

            LogMessage(msg, EventLogEntryType.Error);
        }

        /// 
        /// logs a (non-error) message
        /// 
        /// 
        public static void LogMessage(string message)
        {
            LogMessage(message, EventLogEntryType.Information);
        }

        /// 
        /// logs a message, with specified EventLogEntryType
        /// 
        /// 
        /// 
        private static void LogMessage(string message, EventLogEntryType type)
        {
            message = Assembly.GetExecutingAssembly().FullName + " - " + message;

            //if (_source == null)
            //{
            //    throw new ApplicationException(ERROR_NOSOURCE);
            //}

            EventLog.WriteEntry(_source, message, type);
        }

        #endregion
    }
}

webform_dopostbackwithoptions is undefined

A few days back I’ve got the error message “webform_dopostbackwithoptions is undefined” on the project that i’m working on.
The strange thing it happened only when I activated the securepagemodule, when i deactivated the module it works perfectly. I tried to debug it with HTTP debugger (fiddler tool) and i found that on that particular page, there is a request from webresources.axd but the request is not into https but into http and what i believe since the page is on secure mode therefore it discards the “webresources.axd” since it’s not secure. The workaround for this issue is by adding entry of “webresources.axd” under securepage and the problem is solved.

This is the sample of web.config for it

 
        
         
   </secureWebPages

NOTE:This is resolved in the new version of securepage module

Page 1 of 2

Powered by WordPress & Theme by Anders Norén