Location>code7788 >text

C# Web Programming: Core Skills for .NET Developers

Popularity:492 ℃/2024-07-22 10:14:52

preamble

In the digital age, network programming has become an indispensable part of software development, and for .NET developers in particular, mastering network programming skills in C# is a necessary step to the next level. Whether you're building high-performance web applications or implementing complex distributed systems, network programming is the cornerstone that holds it all together.

This is a comprehensive and concise introduction to C# network programming for .NET developers, from the basics to advanced topics, dissecting them one by one to help you build a solid network programming foundation that will take you on a coding journey through the network world.

I. HTTP Requests

HTTP (Hypertext Transfer Protocol) is one of the most widely used network protocols on the Internet, mainly used for transferring hypertext from World Wide Web servers to local browsers.

In C#, there are several ways to handle HTTP requests, ranging from the traditionalnamespace to the modernHttpClientclass, each method has its own application scenario.

1. UseHttpClient Send HTTP request

HttpClientis the recommended class for sending HTTP requests in C#, which provides an asynchronous API to better handle long-running operations and avoid blocking UI threads.

Below is an example of a simple GET request:

using System;
using ;
using ;

public class HttpClientExample
{
    public static async Task Main()
    {
        using var client = new HttpClient();
        var response = await ("/data");
        
        if ()
        {
            var content = await ();
            (content);
        }
        else
        {
            ($"Failed to retrieve data: {}");
        }
    }
}

 2. UseWebClient Send HTTP request

(go ahead and do it) without hesitatingWebClientclass still exists in the .NET Framework, but in .NET Core and subsequent versions it has been marked as obsolete and it is recommended to use theHttpClient

However, for simple synchronization requests, theWebClientStill available:

using System;
using ;
using ;

class WebClientExample
{
    public static void Main()
    {
        using (var client = new WebClient())
        {
            try
            {
                string result = ("/info");
                (result);
            }
            catch (Exception ex)
            {
                ($"Error: {}");
            }
        }
    }
}

3. UseHttpRequestMessage cap (a poem)HttpMessageHandler

For more complex HTTP requests, such as those requiring custom request headers or handling authentication, you can use theHttpRequestMessagecap (a poem)HttpMessageHandler

This approach provides more flexibility and control:

using System;
using ;
using ;

class HttpRequestMessageExample
{
    public static async Task Main()
    {
        using var client = new HttpClient();
        var request = new HttpRequestMessage(, "/info");
        ("Authorization", "Bearer your-access-token");

        var response = await (request);

        if ()
        {
            var content = await ();
            (content);
        }
        else
        {
            ($"Failed to retrieve data: {}");
        }
    }
}

4. Cautions

  • Safety and performance: utilizationHttpClientWhen doing so, make sure that you reuse the same instance for the lifetime of an application, rather than creating a new instance for each request.
  • Error handling: Always checks the result of an HTTP request and handles possible exceptions and non-successful HTTP status codes.
  • Timeouts and Cancellations: utilizationHttpClientWhen theCancellationTokento control the timeout and cancelation of requests.

By mastering these points, you will be able to effectively handle a variety of HTTP requests in C#, from simple GET requests to complex POST requests, including authentication and error handling.

WebSocket communication

WebSocket is a protocol for full-duplex communication over a single TCP connection, which provides lower latency and higher efficiency than the traditional HTTP request/response model, and is ideal for scenarios such as real-time data streaming, chat applications, and online games. In C#, both server-side and client-side, you can use WebSocket to communicate.

1、Client use WebSocket

In C#, you can use theunder the namespaceClientWebSocketclass to create a WebSocket client. The following is a simple example showing how to connect to a WebSocket server and send and receive messages:

using System;
using ;
using ;
using ;
using ;
using ;

/// <summary>
/// WebSocket client class for establishing a connection and communicating with a WebSocket server.
/// </summary>
public class WebSocketClient
{
    /// <summary>
    /// Client WebSocket instance.
/// </summary>
    private readonly ClientWebSocket _webSocket = new ClientWebSocket();
    
    /// <summary>
    /// CancellationTokenSource used to cancel the operation.
/// </summary>
    private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

    /// <summary>
    /// Connects to the specified WebSocket server.
/// </summary>
    /// <param name="uri">URI of the WebSocket server.</param>
    public async Task Connect(string uri)
    {
        // Connect to the WebSocket server using the URI provided
        await _webSocket.ConnectAsync(new Uri(uri), _cancellationTokenSource.Token);
    }

    /// <summary>
    /// Sends a message to the WebSocket server.
/// </summary>
    /// <param name="message">The message string to be sent.</param>
    public async Task SendMessage(string message)
    {
        // Convert message to UTF8 encoded bytes
        byte[] buffer = Encoding.(message);
        
        // Create an ArraySegment that encapsulates the buffer of bytes to be sent
        ArraySegment<byte> segment = new ArraySegment<byte>(buffer);
        
        // Send a message to the WebSocket server
        await _webSocket.SendAsync(segment, , true, _cancellationTokenSource.Token);
    }

    /// <summary>
    /// Receives messages sent by the WebSocket server.
/// </summary>
    /// <param name="onMessageReceived">A callback function that is called when a message is received.</param>
    public async Task ReceiveMessage(Action<string> onMessageReceived)
    {
        // Continuously receive messages while the WebSocket connection is open
        while (_webSocket.State == )
        {
            var buffer = new byte[1024];
            
            // Receiving data from a WebSocket server
            var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationTokenSource.Token);
            
            // If the type received is closed, the connection is closed
            if ( == )
            {
                await _webSocket.CloseAsync(, "Closing", );
                break;
            }
            
            // Converts the received bytes to strings and handles them with the callback function
            var receivedMessage = Encoding.(buffer, 0, );
            onMessageReceived(receivedMessage);
        }
    }

    /// <summary>
    /// Disconnects from the WebSocket server.
/// </summary>
    public async Task Disconnect()
    {
        // Cancel receive and send operations
        _cancellationTokenSource.Cancel();
        
        // Close the WebSocket connection
        await _webSocket.CloseAsync(, "Closing", );
    }
}

2、Server side using WebSocket

On the server side, you can use theto support WebSocket.

The following is a simple example of a WebSocket service endpoint configuration:

using ;
using ;
using ;
using ;
using System;
using ;

public class Startup
{
    /// <summary>
    /// Configure the service container.
/// </summary>
    /// <param name="services">Service Collection.</param>
    public void ConfigureServices(IServiceCollection services)
    {
        // Adding Controller Services
        ();
    }

    /// <summary>
    /// Configure the application pipeline.
/// </summary>
    /// <param name="app">Application builder.</param>
    /// <param name="env">Host environment.</param>
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // Enabling exception pages in the development environment
        if (())
        {
            ();
        }

        // Enabling Routing
        ();

        // Enabling WebSocket Middleware
        ();

        // Configuring the Endpoint Processor
        (endpoints =>
        {
            // Mapping the default GET request handler
            ("/", async context =>
            {
                await ("Hello World!");
            });

            // Mapping WebSocket Request Handlers
            ("/ws", async context =>
            {
                // Check if the current request is a WebSocket request
                if ()
                {
                    // Accepting WebSocket Connections
                    using var webSocket = await ();

                    // Continuously listening for WebSocket messages
                    while (true)
                    {
                        // Prepare receive buffer
                        var buffer = new byte[1024 * 4];
                        
                        // Receiving WebSocket messages
                        var result = await (new ArraySegment<byte>(buffer), );

                        // If the type received is a close message, close the connection
                        if ( == )
                        {
                            await (, "Closing", );
                            break;
                        }

                        // Decode incoming messages
                        var message = Encoding.(buffer, 0, );
                        ($"Received: {message}");

                        // Reply message to client
                        await (
                            new ArraySegment<byte>(Encoding.($"Echo: {message}")),
                            ,
                            ,
                            );
                    }
                }
                else
                {
                    // If it is not a WebSocket request, a 400 error is returned
                     = 400;
                }
            });
        });
    }
}

In the server-side code above, the WebSocket middleware is first enabled and then maps a/wsendpoint to handle WebSocket connections.

When a connection request is received, we accept the connection and enter a loop that listens for messages sent by the client and then simply returns a postback message.

3. Description

WebSocket provides C# developers with powerful real-time communication capabilities. Whether you're building complex real-time data streaming applications or simple chat rooms, WebSocket is an option worth considering. By mastering the details of client-side and server-side implementations, you can take full advantage of WebSocket to create high-performance and low-latency real-time applications.

Socket Programming

Socket programming is a fundamental concept in computer network communications that provides the ability to send and receive data between different computers.

In C#, socket programming is done primarily through theunder the namespaceSocketclass to implement. sockets can be used to create the two main types of network connections, TCP/IP and UDP, corresponding to Stream Sockets and Datagram Sockets, respectively.

1. Socket Basics

Socket address family: Specifies the type of network protocol, such asfor IPv4.

Socket type:for TCP.for UDP.

Socket Protocol:maybe, respectively, for TCP and UDP.

2、TCP Socket Client

TCP Socket clients are typically used to establish persistent connections and to send and receive data over streams.

The following is a simple TCP client example:

using System;
using ;
using ;
using ;

public class TcpClientExample
{
    public static void Main()
    {
        try
        {
            // Create a new Socket instance
            using (Socket socket = new Socket(, , ))
            {
                // Connecting to the server
                IPAddress ipAddress = ("127.0.0.1");
                IPEndPoint remoteEP = new IPEndPoint(ipAddress, 11000);
                (remoteEP);

                // Send data
                string message = "Hello Server!";
                byte[] data = (message);
                (data);

                // Receive server response
                data = new byte[1024];
                int bytes = (data);
                ("Received: {0}", (data, 0, bytes));
            }
        }
        catch (Exception e)
        {
            ("Error: {0}", ());
        }
    }
}

3、TCP Socket Server

The TCP Socket Server is responsible for listening for connection requests from clients and processing data from them.

The following is a simple example of a TCP server:

using System;
using ;
using ;
using ;

public class TcpServerExample
{
    public static void Main()
    {
        try
        {
            // Create a new Socket instance
            using (Socket listener = new Socket(, , ))
            {
                // Bind to local port
                IPAddress ipAddress = ;
                IPEndPoint localEP = new IPEndPoint(ipAddress, 11000);
                (localEP);

                // monitor a connection
                (10);

                // Accepting client connections
                ("Waiting for a connection...");
                Socket handler = ();

                // receive data
                byte[] data = new byte[1024];
                int bytes = (data);
                ("Text received: {0}", (data, 0, bytes));

                // Send Response
                string response = "Hello Client!";
                byte[] responseData = (response);
                (responseData);
            }
        }
        catch (Exception e)
        {
            ("Error: {0}", ());
        }
    }
}

4、UDP Socket

UDP sockets are used for connectionless, unreliable network communication and are typically used for real-time data transfer, such as video streaming or gaming.

The following is a simple UDP client and server example:

UDP client

using System;
using ;
using ;
using ;

public class UdpClientExample
{
    public static void Main()
    {
        try
        {
            // Create a new Socket instance
            using (UdpClient client = new UdpClient())
            {
                // Send data
                string message = "Hello UDP Server!";
                byte[] data = (message);
                IPEndPoint server = new IPEndPoint(("127.0.0.1"), 11000);
                (data, , server);

                // Receive server response
                data = (ref server);
                ("Received: {0}", (data));
            }
        }
        catch (Exception e)
        {
            ("Error: {0}", ());
        }
    }
}

UDP server

using System;
using ;
using ;
using ;

public class UdpServerExample
{
    public static void Main()
    {
        try
        {
            // Create a new Socket instance
            using (UdpClient listener = new UdpClient(11000))
            {
                // receive data
                IPEndPoint client = new IPEndPoint(, 0);
                byte[] data = (ref client);
                ("Text received: {0}", (data));

                // Send Response
                string response = "Hello UDP Client!";
                byte[] responseData = (response);
                (responseData, , client);
            }
        }
        catch (Exception e)
        {
            ("Error: {0}", ());
        }
    }
}

The above example shows how to use C#'sSocketclass to implement TCP and UDP client-server communication.

In practice, issues such as concurrent connections, error handling and resource management may also need to be addressed.

In addition, for TCP communication, an asynchronous programming model is often recommended for performance and resource usage considerations.

IV. C# Network security

Network security is a crucial aspect when programming for the web in C# and involves confidentiality, integrity and availability of data transmission. Here are some key network security points that are essential for building secure web applications:

1、SSL/TLS encryption

Use in C#HttpClientWhen theHttpClientHandlerclass to configure SSL/TLS related options to secure HTTPS requests.

Here's an example that demonstrates how to use theHttpClientHandlerto configure SSL/TLS settings:

using System;
using ;
using ;
using .X509Certificates;
using ;

class Program
{
    static async Task Main()
    {
        // Creating an Instance of HttpClientHandler
        var handler = new HttpClientHandler();

        // Configuring SSL/TLS Settings
// Setting up a delegate for checking server certificates
         = ;

        // Set whether to redirect automatically
         = true;

        // Setting up a proxy
//  = true;
        //  = new WebProxy(":8080");

        // Creating an HttpClient Instance
        using var httpClient = new HttpClient(handler);

        // Setting the request header
        ();
        (new MediaTypeWithQualityHeaderValue("application/json"));

        // Send HTTPS request
        var response = await ("/data");

        // Checking the response status
        if ()
        {
            var content = await ();
            (content);
        }
        else
        {
            ($"Failed to retrieve data: {}");
        }
    }
}

account for

  • ServerCertificateCustomValidationCallback: This attribute allows you to specify a delegate that will be used to validate the server's SSL certificate. In this example, we use the, it will accept any certificate, which may be useful in a test environment, but it is highly recommended to use stricter certificate validation logic in a production environment.
  • AllowAutoRedirect: This property controls whether or not to allowHttpClientAutomatically handle redirects. By default, it is turned on.
  • UseProxy cap (a poem)Proxy: You can configure these two properties if you need to send requests through a proxy server.
  • DefaultRequestHeaders: Used to set the default header for requests, such asAcceptto specify the desired response format.

Caveats:

  • In practice, it is not recommended to useDangerousAcceptAnyServerCertificateValidator, because it bypasses normal certificate validation and may expose the application to man-in-the-middle attacks. In a production environment, you should implement your own certificate validation logic to ensure that only valid and trusted certificates are accepted.
  • Additionally, if the application needs to handle a specific SSL/TLS protocol version or encryption suite, this can also be handled through theSslProtocolsAttributes are further customizedHttpClientHandlerThe SSL/TLS settings of the
  • For example, it can be set toSslProtocols.Tls12maybeSslProtocols.Tls13, to limit the version of the protocol used.

2、Password security storage

Securely storing passwords in C# is a crucial practice, especially when it comes to user accounts and sensitive information. To protect passwords from being leaked or cracked, avoid storing passwords in plaintext and instead use encryption or hashing.

Here are some recommended practices:

  • Using Hash Functions

Using a secure hash function, such as SHA-256 or SHA-512, it is possible to convert a password into a fixed-length digest. However, simple hashes are vulnerable to rainbow table attacks and therefore require the addition of a salt value (salt).

Sample code:

using System;
using ;
using ;

public static class PasswordHasher
{
    public static string HashPassword(string password, byte[] salt)
    {
        using (var sha256 = ())
        {
            var passwordSalted = Encoding.(password + Encoding.(salt));
            var hash = (passwordSalted);
            return Convert.ToBase64String(hash);
        }
    }

    public static byte[] GenerateSalt()
    {
        using (var rng = new RNGCryptoServiceProvider())
        {
            var salt = new byte[32];
            (salt);
            return salt;
        }
    }
}

// usage example
byte[] salt = ();
string hashedPassword = ("password123", salt);
  • Using salted hashes

A randomly generated salt value is combined with the password before hashing it. This prevents rainbow table attacks and brute force cracking.

  • Using slow hash functions

Using slow hash functions like PBKDF2, bcrypt, scrypt, or Argon2 can be significantly more difficult to crack because they are designed with preventing brute force cracking in mind.

Sample code:

using System;
using ;
using ;
using ;

public static class PasswordHasher
{
    public static string HashPasswordUsingBcrypt(string password)
    {
        using (var bcrypt = new Rfc2898DeriveBytes(password, 16, 10000)) // 16 bytes of salt, 10000 iterations
        {
            return Convert.ToBase64String((24)); // 24 bytes of hash
        }
    }
}

// usage example
string hashedPassword = ("password123");
  • Storing Hash and Salt Values

In the database, in addition to storing the hashed password, the salt value used for that password should also be stored so that the hash can be recalculated using the same salt value during verification.

  • Verify Password

When a user logs in, the hash and salt values are retrieved from the database, the entered password is hashed using the same hash function and salt value, and then compared to the stored hash value.

Sample code:

public static bool VerifyPassword(string inputPassword, string storedHash, byte[] storedSalt)
{
    string hashOfInput = (inputPassword, storedSalt);
    return hashOfInput == storedHash;
}
  • Do not store answers to password reset questions

Answers to password reset questions should be handled as securely as passwords, avoiding storage in clear text.

Core provides built-in methods for password hashing and validation, and using these frameworks is often more secure than implementing them manually. In summary, storing passwords securely involves the use of strong hashing algorithms, salting, appropriate number of iterations, and storage mechanisms. Also, stay on top of the latest security practices and update your code regularly to address new threats.

3、Prevent SQL injection

Use parameterized queries or ORM tools, etc. to prevent SQL injection attacks.

string query = "SELECT * FROM SystemUser WHERE Username = @username";
SqlCommand command = new SqlCommand(query, connection);
("@username", inputUsername);

4. Prevention of cross-site scripting attacks (XSS)

Proper encoding and validation of user input to prevent malicious script injection.

string userContent = "<script>alert('XSS');</script>";
string encodedContent = (userContent);

5. Prevention of cross-site request forgery (CSRF)

MVC can use mechanisms such as Anti-Forgery Token to prevent CSRF attacks.

@()

6. Authentication and authorization

Use more advanced authentication mechanisms such as JWT (JSON Web Token) and implement appropriate authorization policies in your application.

[Authorize]
public ActionResult SecureAction()
{
    // safe operation
}

7. Judgment document security

In C#, determining whether a file is "safe" can be done from a number of perspectives, usually involving the source of the file, its contents, permissions, and whether it contains potentially malicious code.

Below I'll describe a few possible ways to check the security of a file:

  • Checking the origin of documents

Ensure that the file is downloaded or fetched from a trusted source. In web applications, you can use theContent-Dispositionresponse header to check that the file is provided as an attachment and that the filename is as expected.

  • Verify the type and extension of the file

Determine if the file type is what is expected by checking the file's extension or MIME type, e.g., if an image file is expected, then only accept the.jpg, .pngetc. extensions.

private bool IsFileSafeByExtension(string filePath)
{
    string[] allowedExtensions = { ".jpg", ".png", ".gif" };
    string extension = (filePath).ToLower();
    return (extension);
}
  • Checking the contents of the document

Use file signatures or magic numbers to verify that the actual type of a file matches the declared type and to prevent extension spoofing.

private bool IsFileSafeByContent(string filePath)
{
    byte[] magicNumbers = (filePath);
    if ( >= 2 && magicNumbers[0] == 0xFF && magicNumbers[1] == 0xD8) // JPEG
    {
        return true;
    }
    // Add checks for other formats...
    return false;
}
  • Scan for viruses and malware

Use anti-virus software or online APIs to check files for viruses or malware.

private bool IsFileSafeFromVirus(string filePath)
{
    // Example: Using an antivirus API to scan the file.
    // This is just a placeholder and you should replace it with actual antivirus software integration.
    return (filePath);
}
  • Checking file permissions

Ensure that files have the correct permissions to prevent unauthorized access.

private bool IsFileSafeByPermissions(string filePath)
{
    var fileInfo = new FileInfo(filePath);
    var security = ();
    // Check permissions here...
    return true; // Placeholder logic
}
  • File Size Check

Limit the file size to avoid consuming too much disk space or memory.

private bool IsFileSafeBySize(string filePath, long maxSizeInBytes)
{
    var fileInfo = new FileInfo(filePath);
    return  <= maxSizeInBytes;
}
  • Content Security Policy (CSP)

In web applications, CSPs are used to restrict the types and sources of resources loaded to prevent attacks such as XSS.

  • Example of synthesized check function
private bool IsFileSafe(string filePath)
{
    return IsFileSafeByExtension(filePath) &&
           IsFileSafeByContent(filePath) &&
           IsFileSafeFromVirus(filePath) &&
           IsFileSafeByPermissions(filePath) &&
           IsFileSafeBySize(filePath, 1024 * 1024); // Limit to 1MB
}

Please note that the above code snippets are intended as examples only, and real-world applications may require adjustments and additions to specific implementation details, such as the introduction of actual virus scanning libraries or APIs, as well as more complex permission and content checking logic.

Security checks are multi-layered and need to be integrated with specific application scenarios and requirements.

8、Safe Cookie Handling

Cookies are a common mechanism used in Web development to store user information. They can save small amounts of data in the client browser so that the server can track information such as user preferences, login status, and so on. However, if cookies are not handled properly, they can cause serious security issues such as data leakage, session hijacking and cross-site scripting attacks (XSS). Therefore, it is crucial to ensure that cookies are handled securely.

Here are some best practices that should be followed when dealing with cookies:

  • Use HTTPS:When transferring cookies, be sure to encrypt the connection using HTTPS, which prevents Man-in-the-Middle Attacks and protects cookie data from eavesdropping.
  • Set the HttpOnly flag:Marking cookies as HttpOnly prevents JavaScript scripts from accessing cookies, thus reducing the risk of cross-site scripting attacks (XSS).
  • Set the Secure flag:When cookies are marked as Secure, they will only be sent over an HTTPS connection, ensuring that the data is secure in transit.
  • Limit the valid paths and domains for cookies:By setting the Path and Domain attributes of cookies, you can control which pages can access specific cookies, reducing the attack surface.
  • Use the SameSite property:The SameSite property controls whether cookies are sent with cross-site requests, reducing the possibility of cross-site request forgery (CSRF) attacks. One of three modes can be selected: Strict, Lax or None.
  • Set a reasonable expiration time:Setting an appropriate expiration time for cookies avoids the security risks posed by permanent cookies and makes it easier to clean up user information that is no longer needed.
  • Regularly review and update cookie policies:Regularly check cookie usage to ensure that all cookie settings comply with the latest security standards and privacy regulations.

By following these best practices, application security can be greatly enhanced to protect user data from malicious attacks. Secure cookie handling in web development is not only a technical requirement, but also a demonstration of responsibility for user privacy and data security.

using System;
using ;

public class CookieHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        // Create a new cookie object
        HttpCookie cookie = new HttpCookie("UserSession");

        // Setting Cookie Values
         = "123456"; // Assuming this is a unique identifier for the user

// Setting the cookie expiration time
         = (1); // Setting cookies to expire after one day

// Setting the HttpOnly property for added security
         = true;

        // If your site supports HTTPS, set the Secure attribute
        if ()
             = true;

        // Adding a Cookie to the Response
        (cookie);
    }

    public bool IsReusable
    {
        get { return false; }
    }
}

In .NET Core or .NET 6+, different APIs are used to handle cookies, for exampleunder the namespaceIResponseCookiesInterface.

V. Summary

With the article's comprehensive introduction to C# network programming, I believe that I have gained knowledge and understanding of this piece of content. From simple HTTP requests to complex socket communications, from asynchronous programming models to the application of security protocols, each step lays a solid foundation for us to build modern network applications. In real projects, in-depth learning and practicing these knowledge points according to the needs will help to improve the .NET developers' ability in the field of network programming. Continuous learning and practicing is a sure way to become a good .NET developer.

If you find this article useful, welcome to join WeChat [DotNet technician] community to share ideas and grow with other tech-loving peers.