I understand that it is good practice to break up your application's code into separate blocks - so for example you would not just have your entire code in a button click event, you would break up the parts of the procedure into methods that call each other. Normally I follow this practice but in my current little project (creating an SMTP server) I am finding that difficult. I'll explain...

I have a method that is called each time a remote server connects to my SMTP server and tries to pass a message to it, this method needs to read the data that the remote server has sent and then respond to the remote server appropriately and store certain parts of the data the remote server sent (ie the sender address of the email message, the recipient address, the body of the email etc). Now you would think that this is quite easy to separate into different methods as you would just have one function that reads the data, then a quick IF statement to see what the command was that the remote server sent, and then a separate procedure for each of the possible commands that takes an appropriate action.

However, because the actions of each of these parts all need to access so many things that are declared in the original method, it seems pointless just passing these items around through parameters in functions/subs just for the sake of the code looking a bit neater. Its kind of hard to explain but it seems to me that without either declaring all these things at class level (which looks pretty naff to me) or passing loads of object references around between each method, then there isnt really any obvious way of separating this code.

Maybe its just me - maybe this method is no where near what you would consider "too long" and maybe the amount of objects I would need to pass to each method if I separated it all would not be considered anything out of the ordinary but just have a look at the code below and see what you think... As you can see, I have defined separate procedures for some things, such as the WriteToClientStream and SMTPSendMessage but I'm struggling to find any more things that are self contained enough to split out

vb.net Code:
  1. ''' <summary>
  2. ''' Handles the session and command processing for each connected client
  3. ''' </summary>
  4. ''' <param name="AcceptedTcpClient">The TcpClient object that represents the connected client</param>
  5. Private Sub ManageSession(ByVal AcceptedTcpClient As Object)
  6.  
  7.         Dim RemoteClient As TcpClient = DirectCast(AcceptedTcpClient, TcpClient)
  8.         Dim RemoteIP As String = RemoteClient.Client.RemoteEndPoint.ToString
  9.         Dim ClientStream As NetworkStream = RemoteClient.GetStream
  10.  
  11.         'Respond to the connection request and raise the ClientConnected event
  12.         WriteToClientStream(ClientStream, "220 Prototype SMTP Server , ready at " & Now.ToString)
  13.         RaiseEvent ClientConnected(New SMTPSessionEventArgs(RemoteIP, String.Empty, String.Empty))
  14.  
  15.  
  16.         'Flags that are used to track the current progress of the session
  17.         Dim QuitCommandReceived As Boolean = False
  18.         Dim InDataCommand As Boolean = False
  19.         Dim MessageSent As Boolean = False
  20.  
  21.         'Email attributes populated by received SMTP commands
  22.         Dim SenderAddress As String = String.Empty
  23.         Dim RecipientAddresses As New List(Of String)
  24.         Dim Subject As String = String.Empty
  25.         Dim MessageBody As String = String.Empty
  26.  
  27.         'Will contain the string of data received from the remote client
  28.         Dim ReceivedStringBuilder As New StringBuilder
  29.  
  30.         Do While QuitCommandReceived = False
  31.             Do
  32.                 Dim size As Integer = 0
  33.                 Dim DataAsBytes(1024) As Byte
  34.                 Do While ClientStream.DataAvailable
  35.                     size = ClientStream.Read(DataAsBytes, 0, DataAsBytes.Length)
  36.                     Dim ShortDataAsString As String = System.Text.Encoding.ASCII.GetString(DataAsBytes, 0, size)
  37.                     ReceivedStringBuilder.Append(ShortDataAsString)
  38.                 Loop
  39.  
  40.                 If InDataCommand Then
  41.                     If ReceivedStringBuilder.ToString.EndsWith(vbCrLf & "." & vbCrLf) Then
  42.                         WriteToClientStream(ClientStream, "250 Message queued for delivery.")
  43.                         MessageBody = ReceivedStringBuilder.ToString.Remove(ReceivedStringBuilder.Length - 3, 3)
  44.                         SMTPSendMessage(SenderAddress, RecipientAddresses, Subject, MessageBody, RemoteIP)
  45.                         InDataCommand = False
  46.                         SenderAddress = String.Empty
  47.                         RecipientAddresses.Clear()
  48.                         Subject = String.Empty
  49.                         MessageBody = String.Empty
  50.                         MessageSent = True
  51.                         Exit Do
  52.                     End If
  53.                 ElseIf InDataCommand = False Then
  54.                     If ReceivedStringBuilder.ToString.EndsWith(vbCrLf) Then
  55.                         Exit Do
  56.                     End If
  57.                 End If
  58.             Loop 'Start listening for new data on the stream again
  59.  
  60.             If MessageSent Then
  61.                 MessageSent = False
  62.             Else
  63.                 Dim CommandEntered As String = String.Empty
  64.                 CommandEntered = ReceivedStringBuilder.ToString
  65.                 RaiseEvent CommandReceived(New SMTPSessionEventArgs(RemoteIP, CommandEntered, String.Empty))
  66.  
  67.                 '-------- HELO Command ---------
  68.                 If String.Compare(CommandEntered, "HELO" & vbCrLf, True) = 0 Then
  69.                     WriteToClientStream(ClientStream, "250 HELO From " & RemoteClient.Client.LocalEndPoint.ToString)
  70.  
  71.                     '--- EHLO Command ---
  72.                 ElseIf CommandEntered.StartsWith("EHLO ", StringComparison.CurrentCultureIgnoreCase) Or String.Compare(CommandEntered, "EHLO" & vbCrLf, True) = 0 Then
  73.                     WriteToClientStream(ClientStream, "250 OK")
  74.  
  75.                     '--- MAIL FROM Command ---
  76.                 ElseIf CommandEntered.StartsWith("MAIL FROM:", StringComparison.CurrentCultureIgnoreCase) Then
  77.                     If Not SenderAddress = String.Empty Then
  78.                         WriteToClientStream(ClientStream, "503 5.5.2 Sender already specified")
  79.                     Else
  80.                         Dim RequestedSenderAddress As String = CommandEntered.Substring(CommandEntered.IndexOf(":") + 1).Trim
  81.                         If RequestedSenderAddress.Contains("@") = False Then
  82.                             WriteToClientStream(ClientStream, "501 5.5.4 Invalid Address")
  83.                         Else
  84.                             SenderAddress = RequestedSenderAddress
  85.                             WriteToClientStream(ClientStream, "250 2.1.0 " & SenderAddress & "....Sender OK")
  86.                         End If
  87.                     End If
  88.  
  89.                     '--- RCPT TO Command ---
  90.                 ElseIf CommandEntered.StartsWith("RCPT TO:", StringComparison.CurrentCultureIgnoreCase) Then
  91.                     If SenderAddress = String.Empty Then
  92.                         WriteToClientStream(ClientStream, "503 5.5.2 Need Mail From: first")
  93.                     Else
  94.                         Dim RequestedRecipientAddress As String = CommandEntered.Substring(CommandEntered.IndexOf(":") + 1).Trim
  95.                         If RequestedRecipientAddress.Contains("@") = False Then
  96.                             WriteToClientStream(ClientStream, "501 5.5.4 Invalid Address")
  97.                         Else
  98.                             RecipientAddresses.Add(RequestedRecipientAddress)
  99.                             WriteToClientStream(ClientStream, "250 2.1.5 " & RequestedRecipientAddress)
  100.                         End If
  101.                     End If
  102.  
  103.                     '--- DATA Command ---
  104.                 ElseIf String.Compare(CommandEntered, "DATA" & vbCrLf, True) = 0 Then
  105.                     If SenderAddress = String.Empty Then
  106.                         WriteToClientStream(ClientStream, "503 5.5.2 Need mail command.")
  107.                     ElseIf RecipientAddresses.Count < 1 Then
  108.                         WriteToClientStream(ClientStream, "503 5.5.2 Need Rcpt command.")
  109.                     Else
  110.                         WriteToClientStream(ClientStream, "354 Start mail input; end with <CRLF>.<CRLF>")
  111.                         InDataCommand = True
  112.                     End If
  113.  
  114.                     '--- AUTH LOGIN Command ---
  115.                     ElseIf String.Compare(CommandEntered, "AUTH LOGIN" & vbCrLf, True) = 0 Then
  116.                     'Havent got this far yet...
  117.  
  118.                     '--- RSET Command ---
  119.                 ElseIf String.Compare(CommandEntered, "RSET" & vbCrLf, True) = 0 Then
  120.                     SenderAddress = String.Empty
  121.                     RecipientAddresses.Clear()
  122.                     Subject = String.Empty
  123.                     MessageBody = String.Empty
  124.                     InDataCommand = False
  125.                     WriteToClientStream(ClientStream, "250 2.0.0 Resetting")
  126.                     '--- QUIT Command ---
  127.                 ElseIf String.Compare(CommandEntered, "QUIT" & vbCrLf, True) = 0 Then
  128.                     QuitCommandReceived = True
  129.                     RemoteClient.Close()
  130.                     '--- Unknown Command ---
  131.                 Else
  132.                     WriteToClientStream(ClientStream, "500 5.3.3 Unknown Command: " & CommandEntered)
  133.                 End If
  134.             End If
  135.  
  136.             'Clear out the command string ready for next command
  137.             ReceivedStringBuilder.Remove(0, ReceivedStringBuilder.Length)
  138.  
  139.         Loop 'Start processing commands that are sent from the client again (unless QUIT has been sent)
  140. End Sub