Sunday, September 11, 2011

Remoting Servers 101

Before I jump into this, I just want to explain that Remoting is an old .Net v1.1 technology and it is no longer supported. However it still gets the job done if that is what you are using already or if you need a really old code to talk to a really new code and you are not at liberty to use WCF. The following example I am going to provide shows .Net v2.0 talking to .Net v4.0. If you have the opportunity to design something from scratch with the time to learn how to do it correctly; please use WCF instead of Remoting. There is no point in using old coding if you can use the new stuff.

Introduction to Remoting
In this example you need to understand that there are two parts to Remoting always, there is the Client or Consumer and the Proxy or Remoting Server. Remoting is great for exposing parts of a dll to an older .Net version or to avoid dll hell in a sense. You can have a centralized location for your main or base dll and just have other components point to it. The benefit here is to not have to make sure that all locations have an exact copy of the latest dll (hence the mention of dll hell).

Remoting Anatomy
There are some basic parts that are involved in remoting and keep in mind a lot of these same concepts are now used in WCF. Remoting requires the following to work:

  1. You must use Remoting locally, it will not work across the net and it isn't safe to attempt it either. Not to mention that it would kill performance.
  2. You must have an Interface Library and that library must be identical for the client and the proxy.
  3. You must have a Remoting Project (you could probably get away without having it, but for simplicity's sake just have one).
  4. You must be able to host these projects in IIS. I have done this with IIS 5, 6 and 7. Trying to use casini in this case is just a pain in the ass, just use IIS.
Here is a picture of the projects I am used to using and seeing when I work with remoting:
Project Descriptions:
  1. BaseWebApp - ASP.NET v4.0 - This is the application or dll we want to expose. Please be aware that Session and other web related objects are not completely available during remoting access. This application is oblivious to the fact that the Remoting Server exists (kind of). Think of the Remoting Server as a parasite and the BaseWebApp as the host. You are going to need to put the RemotingServer and RSInterfaceLibrary into the BaseWebApp's bin folder, this will be explained more later.
  2. PublicWebService - ASP.NET v2.0 - This is the client project that will be consuming the remoting server or utilizing it in order to use the exposed dll. Notice that it is in an older .Net version and would not be able to use the target dll as a reference even if we wanted to. Unless you down graded, which is not suggested or good design.
  3. RemotingServer - ASP.NET v4.0 - This remoting server project will do all of the accessing of the target dll that we are exposing, hence being called the proxy. It is a proxy between the consumer and the exposed dll. This is necessary because we don't want to just allow the web service to have access to everything, only what is allowed by the remoting server.
  4. RSInterfaceLibrary - ASP.NET v2.0 - This remoting server interface library is .Net v2.0 because it can be used by the RemotingServer and the PublicWebService. I did this for simplicity, but if this cannot be done, such as using a .Net v1.1 web service, then you will have to make an exact duplicate of this interface in both flavors of .Net and maintain two separate projects. It sucks, but sometimes if you don't have a choice due to poor code management or a lack of manager foresight you might be stuck in this predicament. One thing to keep in mind is since you are using an older version of .Net for the Interface Library it is important to keep backwards compatibility in mind. So in other words, if you are maintaining a .Net v1.1 interface library and a .Net v4.0 interface library; then it is safer to make your changes in .Net v1.1 and port them over to .Net v4.0. Do NOT go backwards because it can cause very difficult to troubleshoot problems. Just don't do it.
So to recapitulate, the right way to think about this is to think about these projects in pairs... kind of. Lets have two groups:
  • Group A - PublicWebService and RSInterfaceLibrary. Consumer/Client. 
  • Group B - BaseWebApp, RemotingServer and RSInterfaceLibrary. Remoting Server/ Proxy.
Groups A and B speak to each other via the RemotingServer, but must have the RSInterfaceLibrary in common because it is their Contract with each other. This is an implicit concept in Remoting. For WCF it is an explicit concept now, which helps in my opinion. I can't say this enough, MAKE SURE THE INTERFACE LIBRARIES ARE IDENTICAL!

On to the Code. How does this all work?
I like to think of all this as creating pipelines, because essentially that is what this all is. You are creating a tunnel from point A to point B or rather from Group A to Group B. Follow these steps:
  1. Create the BaseWebApp web application project which is in ASP.NET v4.0
  2. Create the PublicWebService web service project which is in ASP.NET v2.0 (or could be .Net v1.1, what ever you require).
  3. Create the RemotingServer class library which is in ASP.NET v4.0
  4. Create the RSInterfaceLibrary class library which is in ASP.NET v2.0. You only need to create one of these projects because .Net v4.0 can take .Net v2.0 as a reference with no problem. If you needed to create one as .Net v1.1, then make two of these projects, one in .Net v1.1 and one in .Net v4.0. You will then have to have two version of visual studio open, which is not a problem, but a little more complicated.
Code Snippets


BaseWebApp
  1. Create a new folder called "BusinessObjects"
  2. Add a new class to it called "BaseObject.cs"
  3. Include the following code:
    public static string UsefulData()
    {
        return "BaseWebApp.BusinessObjects.UsefulData() : " + DateTime.Now.ToString();
    }
    
  4. Host this project in IIS using the same name as the project.

I like returning the current time because it is something that is constantly updating without me having to do it myself and it lets you know if something is working right now.

RSInterfaceLibrary
  1. Take the stock class and rename it to "InterfaceLibrary.cs" (for this example I am not using the proper naming convention of IClassName to be more clear)
  2. Use the following code:
public interface InterfaceLibrary
{
    string GetUsefulData();
}

RemotingServer
  1. Take the stock class and rename it to "Gateway.cs"
  2. Make a project reference or dll reference to the "BaseWebApp.dll"
  3. Make a project reference to the "RSInterfaceLibraray" project
  4. Open "Gateway.cs" and replace the code with this code:

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

namespace RemotingServer
{
    public class Gateway : MarshalByRefObject, RSInterfaceLibrary.InterfaceLibrary
    {
        public string GetUsefulData()
        {
            return BaseObject.UsefulData();
        }
    }
}

PublicWebService
  1. Make a project reference to the "RSInterfaceLibraray" project
  2. Add a class called "RemotingLogic.cs"
  3. Add the following code to this class:
    public static InterfaceLibrary GetProxyObject()
    {
        string strURL = System.Configuration.ConfigurationManager.AppSettings["remotingServerURL"];
    
        return (InterfaceLibrary)Activator.GetObject(typeof(InterfaceLibrary), strURL);
    }
    
  4. Add the following code to the "Service1.asmx.cs" code behind:
    [WebMethod]
    public string GetUsefulInfoViaRemoting()
    {
        InterfaceLibrary proxy = RemotingLogic.GetProxyObject();
    
        return proxy.GetUsefulData();
    }
    
  5. Host this web service in IIS using what ever name you want.
Configuration Files

  • Client or Consumer Side - You can use this configuration in any kind of consumer that I know of. Web.Config or App.Config. Here is the configuration value for the Web Service side:
    
      
        
      
    
    

    This indicates the direction or location of the remoting server. If you navigate to it you will get an HTTP 500 error, so don't bother it doesn't work that way.
  • Proxy or Remoting Server Side - I have only ever used this configuration with a web application, I don't know what else this could be used with.
    
        
          
            
          
        
     
    

    This is what exposes the remoting server on the web application. It piggy backs off of it, like a parasite on its host.
After implementing all of the above, make sure to compile everything and most importantly for the BaseWebApp and the RemotingServer you need to move the "RemotingServer.dll" and "RSInterfaceLibrary.dll" to the BaseWebApp's bin folder. If you did everything right then this should work right off the bat.

One of the most confusing parts about the configuration is the Web Application's side. Here is the explanation:



I got this information from here: http://social.msdn.microsoft.com/Forums/eu/netfxremoting/thread/cb2681e7-2b06-4dcd-af65-fed5981b93d8

There is ton's more that can be done with this stuff. I recommend that if you are going to have multiple remoting servers for the same application don't bother creating multiple projects, you can just use the same projects and just make sure to configure them properly. Here is an example:


    
      
        
        
      
    


This indicates that you have two additional classes "Gateway2.cs" in the remoting server project and "InterfaceLibrary2.cs" in the interface library project. I used the number 2 just to indicate that it is a second pipeline, but you can call it what ever you want.

Enjoy.

Friday, September 9, 2011

Adventures In Web Service and Remoting Server Craziness

I don't think there are that many people out there that still have to deal with more than one .Net Framework, but I have to deal with 3 simultaneously constantly. That means I use 3 different versions of Visual Studio simultaneously. The best part is everything I have to deal with has to talk to each other.

On to the topic at hand though. I learned a valuable and obscure lesson about getting a Remoting Server that is written in .Net v4.0 to send a custom object to a Web Service written in .Net v1.1.

All of the finer details aside here is the lesson I learned. If you are going to make a higher version of .Net - anything greater than v1.1 - then you need to make sure that you write the class in .Net v1.1. I am not referring to unsupported objects or technology such as Linq, I am referring to unsupported structure.

So for example if you get an error like so:
"...<PropertyName> k__backingfield were not found."

There is a good possibility that you might have tried to use a structure that isn't backwards compatible with .Net v1.1 and here is an example why:


//.Net v4.0 Side
[Serializable]
public class SerializableTestObject
{
    public SerializableTestObject()
    { 
    
    }

    [XmlElement]
    public bool BoolField { get; set; }
     
    [XmlElement]
    public bool StringField { get; }
}

Versus
//.Net v1.1 Side
[Serializable]
public class SerializableTestObject
{
    public SerializableTestObject()
    { 
    
    }

    private bool _boolField;
    [XmlElement]
    public bool BoolField { get {return _boolField; } set { _boolField = value; } }
     
    private string _stringField;
    [XmlElement]
    public bool StringField { get { return _stringField; } }
}

Even though those classes are technically identical, the difference is that .Net v1.1 doesn't understand what get; or set; means. That was introduced in .Net v3.0, so you will get the weird error described above if you try to pass the .Net v4.0 class version to the .Net v1.1 class version.

Thursday, September 8, 2011

Remoting Server Troubleshooting

If you are having trouble with getting remoting to function properly and you know you have everything configured properly then it is more than likely caused by a bad or incomplete dll. Here is a list of things to try before giving up on trying to get this to work.
 
1. Remoting errors are never helpful and usually will be a 500 Internal Server Error:
 

This will occur when the proxy object attempts to access the remoting server and fails. Literally what happens is an error occurs on the remoting side (proxy side) and instead of return something useful it will return the following block of text:
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/> <title>500 - Internal server error.</title> <style type="text/css"> <!-- body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;} fieldset{padding:0 15px 10px 15px;} h1{font-size:2.4em;margin:0;color:#FFF;} h2{font-size:1.7em;margin:0;color:#CC0000;} h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;} #header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF; background-color:#555555;} #content{margin:0 0 0 2%;position:relative;} .content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;} --> </style> </head> <body> <div id="header"><h1>Server Error</h1></div> <div id="content"> <div class="content-container"><fieldset> <h2>500 - Internal server error.</h2> <h3>There is a problem with the resource you are looking for, and it cannot be displayed.</h3> </fieldset></div> </div> </body> </html>
 
This block of text is the html of the image directly above. The one thing to keep in mind is if you see this, then you need to make sure that everything is configured properly first, make sure the remoting URL is correct. Pay attention to the errors returned though because sometimes they are not the 500 error. If it is a 404 error then you know that the URL you provided was wrong. If the configuration is fine then go through the steps listed below.
 
2. Manually delete all dlls and recompile everything. You must make sure to move your base project dll to the remoting project’s bin folder. After successfully recompiling the remoting server dll (and respective interface library), should you move the remoting dlls to the base project’s bin folder. No part of this exercise should fail. Make it a habit to periodically move the base project dll to the remoting project’s bin folder to give it an update copy to work with. That is where the remoting server gets its meta-data from.
 
3. Make sure that the interface library looks exactly the same on the consumer side and the remoting side (proxy side). If the interface differs at all, then you will have failures.
 
4. Sometimes, during debug, .Net can get all mucked up with bad cache and you will see a message like this:
 

In the path that is provided in the picture you can see the directory name, this directory name is the same name as the virtual directory used in IIS. This is where all of the cached information for the web application is stored. The problem with this picture is I was getting this error when I was running a different virtual directory which has nothing to do with that other virtual directory. The solution here is to reset IIS by running the following command at cmd:
iisreset /restart
 
If you continue to have this problem then you should do the following:
a.       Stop IIS via cmd: iisreset /stop
b.      Open up the affected path, in this case open: “C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\”
c.       Locate the virtual directory that is giving you trouble and delete it. This is safe to do because it will just be re-cached when you run the application again.
d.      Start IIS via cmd: iisreset /start
e.      Run your application to re-cache it. This could take longer than usual so be patient.
 
This error message can also appear if you are trying to attach to a process with an old debug file. The way to fix that is to simply rebuild your files and make sure you are in debug mode. If you attempt this during release mode you will get this error message too.

Thursday, September 1, 2011

SQL Server: Tips on Declaring a Decimal Data Type

One of my pet peeves about SQL Server is the decimal data type. Declaring it once in a table where you don't have to do any math on it is normally okay, but I get very confused when I have to take two unknown values, do a mathematical operation on them and then store them into a table on the fly. This usually generates that oh so annoying: "Arithmetic overflow error converting [type] to data type numeric."
Here is an example:

SQL:
CREATE TABLE tblDecimalTest
(
    DecimalColumn dec(5,2) NOT NULL
);

INSERT INTO tblDecimalTest ( DecimalColumn ) VALUES(1.1);     -- OK
INSERT INTO tblDecimalTest ( DecimalColumn ) VALUES(5);       -- OK 
INSERT INTO tblDecimalTest ( DecimalColumn ) VALUES(5.1234);  -- OK
INSERT INTO tblDecimalTest ( DecimalColumn ) VALUES(99999);   -- Int Conversion Exception
INSERT INTO tblDecimalTest ( DecimalColumn ) VALUES(99999.0); -- Numeric Conversion Exception

Messages:
(1 row(s) affected)

(1 row(s) affected)

(1 row(s) affected)
Msg 8115, Level 16, State 8, Line 4
Arithmetic overflow error converting int to data type numeric.

The statement has been terminated.
Msg 8115, Level 16, State 8, Line 5
Arithmetic overflow error converting numeric to data type numeric.

The statement has been terminated.

I found a trick to sort of avoid this kind of problem, it won't always work if you are going to use it to preset precision, but it is a start to something better than just randomly deciding or trying to estimate the amount of precision that you will require. Take your inputs, typically a query, and store the virtual table into a new table. After the table is created highlight its name and press Alt + F1, this will give you the complete listing of a lot of information about the table that was just created. Most interestingly and most valuable it will tell you the dimensions of the decimal column that it has appropriated for you.
Example 2:

--This is just an example, it is taking all of the rows from the previous example and multiplying them by 20
SELECT
	decUnknownSize = DecimalColumn*20
INTO tblDecimalTest2 --Create a new table and NOT a temporary table because the trick won't work with a temp table
FROM tblDecimalTest;

Screen Shot of Alt + F1


As we can see in the screen shot above the precision has been determined for us which helps you if you need to manually create the table for what ever reason. There is only one draw back to this scenario, this will only work if you know that the values you are working with will never be bigger than what has been specified already. Otherwise you are going to find yourself fudging with the numbers a lot, in which case you might be better off "SELECT INTO" a real temporary table (#/##) or a Common Table Expression because it will be created for you on the fly. This won't work if you are declaring the schema of a table, temporary table or a table variable.

MSDN Link for Decimal and Numeric Data Type
http://msdn.microsoft.com/en-us/library/aa258832(SQL.80).aspx