Are que getting null or empty with some Request.ServerVariables
When you convert your ASP application to run on Windows Azure it is a good
to put attention to the methods that are used to get the user IP Address.
Normally the recommendation will be to use Request.UserHostAddress however
our friend Alex has found that this property can return null or empty.
After some research Alex found that there are several scenarios under which
you must check both the REMOTE_ADDR and the HTTP_X_FORWARD_FOR server variables:
More info:
http://forums.asp.net/t/1138908.aspx and
http://meatballwiki.org/wiki/AnonymousProxy
A possible code snipped that can provide a value for the client address can be:
public static string ReturnIP()
{
var request = System.Web.HttpContext.Current.Request;
var ServerVariables_HTTP_X_FORWARDED_FOR = (String)request.ServerVariables["HTTP_X_FORWARDED_FOR"];
var ServerVariables_REMOTE_ADDR = (String)request.ServerVariables["REMOTE_ADDR"];
string ip = "127.0.0.1";
if (!string.IsNullOrEmpty(ServerVariables_HTTP_X_FORWARDED_FOR) &&
!ServerVariables_HTTP_X_FORWARDED_FOR.ToLower().Contains("unknown"))
{
ServerVariables_HTTP_X_FORWARDED_FOR = ServerVariables_HTTP_X_FORWARDED_FOR.Trim();
string[] ipRange = ServerVariables_HTTP_X_FORWARDED_FOR.Split(',');
ip = ipRange[0];
}
else if (!string.IsNullOrEmpty(ServerVariables_REMOTE_ADDR))
{
ServerVariables_REMOTE_ADDR = ServerVariables_REMOTE_ADDR.Trim();
ip = ServerVariables_REMOTE_ADDR;
}
return ip;
}
In the previous code the HTTP_X_FORWARDED_FOR value is examined first and if it is not null or unknown then ip address of the client
is gotten from there.
Windows Azure is a great platform and the escalatity oportunities are great,
and deployment time is also great.
You can have all your website up and running in just 10-15minutes.
But… and yes there is always a but.
Sometimes you can have a WebSite that is not that static, that as a matter of fact
you are changing its views constantly. Specially if some ideas are not finished.
And yes you can test locally, but there is also a situation where you might want to have that flexibility.
Well looking around I found a very interesting solution by
Maatern Balliauw. http://blog.maartenballiauw.be/post/2009/06/09/A-view-from-the-cloud-(or-locate-your-ASPNET-MVC-views-on-Windows-Azure-Blob-Storage).aspx
What he proposes is to use windows azure storage as a virtual file system, so you can with simple tools
like the Windows Azure Explorer modify your web pages without the need of going through a lengthy republish process.
So go ahead and keep enyoing Azure
Typical ASP applications were built as a layer of simple ASP with some
COM+ components that did the heavy lifting.
Now, when you migrate your ASP application to ASP.NET and you also migrate your
COM+ components to .NET then you might encounter some issues with security.
One common issue is impersonation.
Sometimes the COM+ were created to use the current user account.
And there is a slight
difference between ASP and ASP.NET:
“Impersonation is when ASP.NET executes code in the context of an authenticated and authorized client. By default, ASP.NET does not use impersonation and instead executes all code using the same user account as the ASP.NET process, which is typically the ASPNET account. This is contrary to the default behavior of ASP, which uses impersonation by default. In Internet Information Services (IIS) 6, the default identity is the NetworkService account.”
That will cause errors in your ASP.NET application like:
To solve this issue you must use ASP.NET Impersonation, and to enable impersonation go to the web.config file and add:
<identity impersonate=”true”/>
For more info on impersonation see: http://msdn.microsoft.com/en-us/library/aa292118(v=vs.71).aspx
I had a situation where the IIS reported that the targetFramework=4.0 attribute
was not supported.
I’m not sure why it started reporting it, but I fixed it with this:
%windir%\Microsoft.NET\Framework\v4.0.21006\aspnet_regiis.exe -i
During a migration from ASP to ASP.NET one of the biggest hurdles is finding a way to deal with the include files.
ASP was a interpreted environment whether ASP.NET is compiled and this difference is very important because you need to find ways more natural in .NET to do some things you used to do in ASP.
For example in ASP you used to have a set of common functionality that was included in your files. What will you do with that?
For ASP ASP.NET in VB.NET is a lot easier. One of the things you can do is move all those common subs and functions to a Module.
Now if what you have is a ASP.NET Web Site, then just your new modules to the App_Code folder and voila your pages are now able to see that code.
For a ASP.NET Web Application is just a little differente. What you have to do is move your common variables, constants, subs and functions to a Module, but that is not enough to make that code reachable from your mark up, so you have two alternatives:
1. Add an %@import Namespace=”QualfiedNamespaces.Module1” statement for each of your modules.
2. Modify your web.config file and add under system.web something like:
<system.web>
<pages>
<namespaces>
<add namespace="WebApplication1.Module1"/>
</namespaces>
</pages>
That will add an implicit import for all your pages.
For C# it can be a little more complicated. Because you do not have modules like in VB.NET, what you can do is use extension methods, to have a similiar syntax.
ASP.NET has gone a long way. And it might be the right time to start moving your sites to ASP.NET.
But what happens if you have a big investment in JSP. Will I lose all my JSP skills?
Well ASP.NET is not the same as JSP but the MVC view engine concept can help you retain some of the JSP flavor.
A good MVC View Engine project is SharpTiles this project provides a partial implementation of JSTL and Tiles.
Just take a look at the the SharpTiles. Just as they say SharpTiles if for you if “You are a Java Developer and you don’t like .aspx”
Now all we need to do is setup the VB6 code that we will use for migration. To do that follow these steps:
1. On the Source Code Explorer toolback click on the Add Files button:
2. Click the Add Folder button and select the folder with your VB6 files
3. After you select the folder, a list of files found will be shown. Just remove any unneccesary files. For example files like MSSCCPRJ.SCC should be removed. And press OK
Now you have to commit your code the Source Code Repository
4. On the Source code Explorer Right click on Source Control Folder (for this example is MergeExample) and select Check In Pending Changes..
5. Write an apropiate comment and press Check In
As vb6 migration experts in our company we deal everyday with a lot of issues around Interop and serialization.
One important thing to note is the concept of “Bittable Types”. I’m not making up terms. Those terms actually exist. Just see this link in MSDN.
In a few words, a bittable type is a type that has the same representation in managed and unmanaged code.
Why in earth is that important at all?
Because if you are calling that great C++ DLL implemented some years ago that just works ok, you won’t be able to pass a NON-Bittable type because that DLL will expect a binary representation different from that in the .NET virtual machine.
This is also an issue in other scenarios like:
- Serializing content to files
- Sending messages through messaging mechanisms like named-pipes or sockets.
Well, we have just introduced the problem so now let’s think on a nice solution for this problem.
Well Bittable Types are:
The following types from the System namespace are blittable types:
So now let’s look at a couple of non-BITTABLE types
DateTime
To test this differences let’s make a small test in VB6 and write a Date value to a file:
Private Sub SaveDateToFile()
Open "C:\test1.bin" For Binary Access Write As #1
Dim d1 As Date
d1 = "1/1/2009"
Put #1, , d1
Close #1
End Sub
Now let’s make a quick program in Vb.NET
Sub Main()
Dim f As System.IO.FileStream = System.IO.File.Open("C:\test2.bin", IO.FileMode.Create, IO.FileAccess.Write)
Dim fw As New System.IO.BinaryWriter(f)
Dim d As Date
d = Convert.ToDateTime("1/1/2009")
Dim val As Long = d.ToBinary()
fw.Write(val)
fw.Close()
Main2()
End Sub
If we compare these files we will have:
So the values are obviously different. This is because VB6 Date are stores with the OLE Automation DateFormat
So let’s change the C# code for something like:
Sub Main2()
Dim f As System.IO.FileStream = System.IO.File.Open("C:\test3.bin", IO.FileMode.Create, IO.FileAccess.Write)
Dim fw As New System.IO.BinaryWriter(f)
Dim d As Date
d = Convert.ToDateTime("1/1/2009")
fw.Write(d.ToOADate())
fw.Close()
End Sub
And now when we compare the files we will have:
So to make your Date values compatible with VB6 format you must user the DateTime method .ToOADate. Now if you are calling a DLL that expects a Date value in the same format used by VB6 then you will have to do this:
Dim d As Date
d = Convert.ToDateTime("1/1/2009")
Dim handle As System.Runtime.InteropServices.GCHandle = System.Runtime.InteropServices.GCHandle.Alloc(d.ToOADate(), Runtime.InteropServices.GCHandleType.Pinned)
Dim memory_address As IntPtr = handle.AddrOfPinnedObject()
Try
APICall(memory_address)
Finally
d = DateTime.FromOADate(System.Runtime.InteropServices.Marshal.ReadInt64(memory_address))
handle.Free()
End Try
String
Most of the time you wont have to deal with String marshalling because adding marshaling tags to your API call solves most of the problems, but if you arent that luckyly then you might do something like:
IntPtr ptrToStringVar = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(strVar);
try
{
APICall(ptrToStringVar);
}
finally
{
strVar = System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptrToStringVar);
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptrToStringVar);
}
NOTE: if you have an API that might return an string with /0 characters you must call the API with System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptrToStringVar,size), if you do that the Framework will take in consideration the size bytes at the ptrToStringVar memory address.
Double and Singles
At least between VB6 and VB.NET the double and single types follows the same format. Well, at least, that is the result of my tests.
Try it yourself, the following shows a simple test for double variables:
VB6
Private Sub SaveDoubleToFile()
Open "C:\test1.bin" For Binary Access Write As #1
Dim d1 As Double
d1 = 1.123
Put #1, , d1
Close #1
End Sub
Sub Main()
SaveDoubleToFile
End Sub
.NET
Module Module1
Sub Main()
Dim f As System.IO.FileStream = System.IO.File.Open("C:\test2.bin", IO.FileMode.Create, IO.FileAccess.Write)
Dim fw As New System.IO.BinaryWriter(f)
Dim d As Double
d = 1.123
fw.Write(d)
fw.Close()
End Sub
End Module
So you could make an api call in those cases with something like:
Dim handle As System.Runtime.InteropServices.GCHandle = System.Runtime.InteropServices.GCHandle.Alloc(d, System.Runtime.InteropServices.GCHandleType.Pinned)
Dim ptr As System.IntPtr = handle.AddrOfPinnedObject()
Try
APICall(ptr)
Finally
handle.Free()
End Try
One of our clients wanted to change the CreateObject function migration for a function of their own. So they wanted all cases like:
Dim x As Object
Set x = CreateObject("Excel.Application")
To be migrated to something like:
Excel.Application x = (Excel.Application) Utils.MyCreateObject("Excel.Application", "");
Our migratio vb6migration tool provides a new cool feature called CustomMaps. This feature allows you to provide some simple but useful changes to the way things get migrated.
For this case follow these steps:
1. Open the Visual Basic Upgrade Companion.
2. In the Tools Menu choose:
3. Create a new CustomMaps File and an an entry like the following:
Notice the Source name is VBA.Interaction.CreateObject. To find out this name you can look in your VB6 IDE, right click on the CreateObject and select goto Definition.
and for the target name just put the implementation that you what, for example you can write a function like:
class Utils
{
public static object MyCreateObject(string className,params object[] ignoreRestParams)
{
return Activator.CreateInstance(Type.GetType(className));
}
}
and set the SourceName to Utils.MyCreateObject (or NameSpace.Utils.MyCreateObject to use the fully qualified name). You just need to set the New Reference Name column because we will not change the definition of the function.
If you have your ASP.NET application for example in c:\inetpub\wwwroot\WebApplication1 and you want to programmatically get that path just use something like:
string AppPath = Request.PhysicalApplicationPath;
I saw this with Francisco and this is one possible solution:
ASP Source
rs.Save Response, adPersistXML
rs is an ADODB.RecordSet variable, and its result is being written to the ASP Response
Wrong Migration
rs.Save(Response <-- The ASP.NET Response is not COM, ADODB.Recordset is a COM object, ADODB.PersistFormatEnum.adPersistXML);
So we cannot write directly to the ASP.NET response. We need a COM Stream object
Solution
ADODB.Stream s = new ADODB.Stream();
rs.Save(s, ADODB.PersistFormatEnum.adPersistXML);
Response.Write(s.ReadText(-1));
In this example an ADODB.Stream object is created, data is written into it and the it is flushed to the ASP.NET response
When people decide to migrate their VB6 applications they eventually end up questioning where they should go. Is VB.NET or C# a good choice?
I have my personal preference, but my emphasis is in developing the technology to take where YOU want to go.
VB.NET is a VB dialect very similar to VB6. It supports several constructs and it makes the changes easier.
C# has several differences from VB6, but it has it a growing language with lots of enthusiasts in its community.
Obviously migrating VB6 to VB dialect is a task far more easier than migrating to a different language.
However we are a research company with years of work in this area and challenges is just what we love.
Let's use a methaphor here.
My beautiful wife, was born in Moscow, Russia. Like her, I really enjoy reading a good book. Some of my favorite authors are
russian authors like Dostoievsky, Tolstoi and Chejov. However I still do not speak russian. I have tried, and I will keep trying but
I still don't know russian. I have read only translations of their books, and I really enjoy them.
As a native speaker my wife always tells me, that it is not the same to read those books in another language besides russian.
And they are phrases (specially in Chejov books that I might not completely understand) but I really got the author
message and enjoyed it.
Translating a book from russian to a more similar language like Ucranian is easier than translating it to English or Spanish.
But I think everybody agrees that is a task than can be done.
You can use terrible works case scenarios, but these scenarios must be analized.
Let see (I took these example from the link in that Francesco put in my previous post http://blogs.artinsoft.net/mrojas/archive/2008/08/07/vb-migration-not-for-the-weak-of-mind.aspx)
If you have code like this:
Sub CopyFiles(ByVal throwIfError As Boolean)
If Not throwIfError Then On Error Resume Next
Dim fso As New FileSystemObject
fso.CopyFile "sourcefile1", "destfile1"
fso.CopyFile "sourcefile2", "destfile2"
fso.CopyFile "sourcefile3", "destfile3"
' seven more CopyFile method calls …
End Sub
and you translate it to:
void CopyFiles(bool throwIfError)
{
Scripting.FileSystemObject fso = new Scripting.FileSystemObjectClass();
try
{
fso.CopyFile("sourcefile1", "destfile1", true);
}
catch
{
if (throwIfError)
{
throw;
}
}
try
{
fso.CopyFile("sourcefile1", "destfile1", true);
}
catch
{
if (throwIfError)
{
throw;
}
}
try
{
fso.CopyFile("sourcefile1", "destfile1", true);
}
catch
{
if (throwIfError)
{
throw;
}
}
// seven more try-catch blocks
}
I think that the russian is really keep in this translation.
First of all. When you do a translation, you should try to make it as native as possible. So why will you keep using a COM function when there is an
equivalent in .NET. So why not use System.IO.File.CopyFile("sourcefile1", "destfile1", true); instead?
Second of all. The On Error Resume Next, I agree is a not a natural statement in C#. I really think that using it could provide results that are less predictable.
Why? Becuase after executing it, are you sure that all the CopyFile occurred successfully? I would prefer wrapping the whole code inside a try-catch instead of trying
to provide an implementation that is not natural in C#, will Aspect Oriented programming provide a clean solution for this cases. Maybe?
RPG and COBOL to Object Oriented Programming, PowerBuilder to C#, Hierarquical Databases to Relational Databases are just the kind of challenges we have faced in our research project.
Not everything is easy, and we might not be able to automate all the tasks (commonly due to the cost of implementing the automation not becuase of feasability).
But at the end Could you understand the whole novel?, even if you didn't understand the joke in the one of the paragraphs in the page?
My years of reading make be belive that you can.
Motivation:
I hate to be disappointed. Specially if it is by a person you had respect for. And that's exactly what Francisco Balena from VB Migration Partner, has done. I have respected him for his books and all his VB6 experience. In terms of legacy VB6 code he is a monster. He is the man.
But in terms of code migration...
I'm not saying this because I work on code migration or maybe I'm biased a little by the fact that I work almost 10 years with a company that has done source code migration research on a big number of legacy languages such as COBOL, RPG, PL\1, Algol, Progress, PowerBuilder and also VB6.
I can tell the difference between a "compiler" and a system that rewrites a expression to the closest equivalent in the target language. We are aware of limitations. We are aware of paradigm differences and functional equivalence, but we talk from experience. We talk about our results. And we have proven those things we talk about.
Let's says you create a Cobol Library with a "MOVE" function, and a COBOLPicture Type and add minus, divide, and add operators. And I write something like:
CobolPicture x = new CobolPicture("XXXX");
x.move(10);
x.add(10);
We have things like that, and it works. It's feasible and maybe there are cases where that is a solution. But we are also proud of have researchers that have been able to detect pattern to rewrite something like that like:
int x = 0;
x = 10;
x+=10;
And saying, that something is not possible just because you can't or you dont like it, just seem uneducated to me.
All of this has motivated me to start a series of chapters I for a small blog book I will call VB Migration (not for the weak of mind).
For those of you, who really are tecnology savvy and are in the process of a VB Migration, this is YOUR book.
AJAX applications are great
Their coolness factor is high, and your clients
will be trilled by the new interactivity of your pages
But as a developer when you have a problem with
AJAX it could be hard to track. Because there is
a lot going on behind the scenes
Some of our costumers migrate their ASP applications to
to ASP.NET and it's natural that they want
some AJAX in it.
So I found a GREAT GREAT application for AJAX debugging
It's called fiddler. I cannot describe it all for you so go its site and watch the videos
The tool is just amazing :)
It really can see everything that the browser
receives and sends and more.
A definitive must
Fiddler
Localize a VB6 application can be cumbersome, specially if it was not even originally planned to be localized.
Nowadays is common that you're clients might demand support for different languages.
While a localization task is still difficult, we have found excellent results performing it during a VB Migration.
Our tools allow us to easily externalize all your strings. After that the traslation task becomes easy, and you can even use the help
of nice projets like
Automatically Translate your .NET resource files with Google Translate
This is the migration of a snippet of ASP classic to ASP.Net
Checking that a File Exists ASP Classic
<%
Dim strExists
Dim strNotExists
Dim objFileSystemObjectVar
``
strExists = "exists.txt"
strNotExists = "error.txt"
Set objFileSystemObjectVar = Server.CreateObject("Scripting.FileSystemObject")
' The FileExists method expects a fully qualified path and
' use Server.MapPath
%>
<p>
"<%= strExists %>" exists:
<b><%=objFileSystemObjectVar.FileExists(Server.MapPath(strExists)) %></b>
</p>
<p>
"<%= strNotExists %>" exists:
<b><%= objFileSystemObjectVar.FileExists(Server.MapPath(strNotExists)) %></b>
</p>
Checking that a file exists ASP.NET
<%@ Page Language="VB" %>
<%@ Import Namespace="System.IO" %>
<script language="VB" runat="server">
Dim strExists As String = "exists.txt"
Dim strNotExists As String = "error.txt"
</script>
<p>
"<%= strExists %>" exists:
<b><%=File.Exists(Server.MapPath(strExists))%></b>
</p>
<p>
"<%= strNotExists %>" exists:
<b><%= File.Exists(Server.MapPath(strNotExists))%></b>
</p>