Thursday, January 24, 2008

share ASP.NET user controls between applications (aspnet_merge)

You want to create some shared ASP.NET controls for use in multiple applications.
Probably the only way you think of is custom control:

the only way to share the user control between applications is to put a separate copy in each application, which takes more maintenance if you make changes to the control (MSDN)

But it isn't actually true.

You can use "Web Deployment Projects" add-in to compile your user controls project into dll which can be referenced from any web application.

Now let's take it step by step:

(make sure you have installed the "Web Deployment Projects" add-in)

  1. Create a new web project (SharedUserControls.csproj).
  2. Remove the "Default.aspx" page and web.config file.
  3. Add a user control (let's call it MyControl.ascx).
  4. Add what ever web controls you want to your user control.

    image 
  5. Add Web Deployment Project by standing on the project and selecting "Add Web Project Project..." from the Build menu

    image 
  6. Keep the default name, and click OK.

     image
    A new project was created with out any file inside

    image 
  7. Double click the new project to get the project property pages.

     image
  8. Uncheck the last check box - "Allow this precompiled site to be updatable".

    This is an important step - If you'll skip it, you'll get: ASPNETMERGE : warning 1013: Cannot find any assemblies that can be merged in the application bin folder.
  9. Go to the "Output Assemblies" tab
  10. Select "Merge all pages and control outputs to a single assembly"
  11. Call the assembly name: "SharedUserControlMerged"

    image 
  12. Build the solution, and see what you get in the Output:

    aspnet_compiler.exe...
    Running aspnet_merge.exe ...
    aspnet_merge.exe...
    Successfully merged...

    What happens is that the "Deployment Web Project" actually compiles the aspx and ascx files - using aspnet_compiler.

    Then merge all dlls (currently we have only one - because we have only one control) using aspnet_merge (which uses ILMerge behind the scenes).
  13. Now add another Web Project (WebApp.csproj).
  14. Add reference to the SharedUserControlMerged.dll from the bin folder of the SharedUserControls.csproj_deploy project.

    image 

  15. Register this assembly inside a web page on WebApp project.

    <%@Register
    tagprefix="xyz"
    namespace="ASP" 
    assembly="SharedUserControlMerged"%>

  16. Now you can use your control on this page:

    You need first to find out what is the name of the control in the merged dll.
    I have used Reflector to do it:

    image
    and just put it in the page:

    <xyz:mycontrol_ascx runat="Server"></xyz:mycontrol_ascx>


    So what we have is a reusable dll contains our User Control.


    Next time we will find out how to create user control with images and JavaScript files.

Sunday, January 13, 2008

13 months a year?

Do you believe in "Unlucky 13"?

Today (13th) we had a bug on production.
Yesterday it worked... Today it doesn't....

After short debugging I have analyzed it to be a date parsing error - so I switched the date on the server back to yesterday, and it worked again.

So finally I found out that the culture was Hebrew while the Dataset was comparing based on Invariant culture. so:

12/1/2008 can be translated both as:

  • 12th JAN
  • 1st DEC

But 13/1/2008 can only be translated as:

  • 13th JAN

but it would crash if you try to translate it as 1st to ??? (the 13th month...)

It just remind be a great book of Erich Kästner - The 35th of May...

Sunday, January 06, 2008

Referencing different versions of an assembly - Part 3 (ILMerge)

(if you missed the first and the second parts...)

We have succeed build our solution using ILMerge with the flag /internalize.

But now lets go a step further:

image

A.dll + infra V1 => MergedA.dll (using /internalize)
B.dll + infra V2 => MergedB.dll (using /internalize)

App1 + MergedA.dll + MergedB.dll => MergedApp1.dll

 

Will it work?

At first look, the answer should be it won't.

Why? remember that the reason it worked before was because infra1 & infra2 were declared Internal.

But now they both on the same Assembly (MergedApp1) so we should get an error from ILMerge about the same type declared more than once...

 

But if you try it, you'll find out it works...

How?

A another look with reflector will reveal the secret:

image

What happened? Where come from those MergedA850 and MergerdB1071 ?

The magic sits inside ILMerge. it has noticed the conflict between the two types with the same name (which now inside the same assembly - so "Internal"won't help) and just made each of them a new name!

read the following section from ILMerge help file:

The normal behavior of ILMerge is to not allow there to be more than one public type with the same name. If such a duplicate is found, then an exception is thrown. However, ILMerge can just rename the type so that it no longer causes a conflict. For private types, this is not a problem since no outside client can see it anyway, so ILMerge just does the renaming by default.

So, because Infra1 & Infra2 weren't public - they just got new names.

(Actually, you can do it for public types as well - using the /allowDup flag).

 

Is it a good solution?

 

In my opinion (or better phrased: with my situation) it isn't.

  1. Renamed types can break your application:

    If you use reflection in your code you may plan getting a type by its name.
    But now it has a different name...
  2. Multiple copies of the same type is loaded into memory:

    guess what happens if you have a singleton class in the Infra.dll.
    You can end up with 3 instance of one singleton... (ouch).

 

So back to square one -
How to reference multiple versions of the same assembly?

We have left with the two original options:

  1. Put the two DLLs into different folders, and tell the application to probe those folders.
  2. Install the DLLs into the GAC.
    This way they won't be copied into the Bin at all.
  3. Add the DLL's version to their name (e.g. Infra-1.dll)

Which one? Let's continue next time.

Thursday, January 03, 2008

Referencing different versions of an assembly - Part 2 (ILMerge)

This is where we stopped in Part 1:

image28

We have ended up with two merged assemblies (MergedA & MergedB) which are referenced by App1.

Now let's complicate things a little:

image

All I wanted to do is to use infra.dll (the version isn't critical for this discussion) inside my main application (App1).

 

If you try to use any type from infra.dll in App1 code and build the solution, you'll get:

The type "infra.xyz" exists in both 'MergedA.dll' and 'MergeB.dll'

 

What is it all about?

When I used infra.xyz type, the compiler tries to find where is it declared, but find at least two places (actually there are three). So it can't decide which one you want...

I got a question about this in a comment on Part 1 from Kevin Berridge, and actually I have crashed into the same problem few hours before, too.

 

The solution - ILMerge with /internalize switch:

The problem was created because all types inside infra.dll were exposed both by MergedA & by MergedB.

If we can make the infra part in the merged assembly internal only, our main application wouldn't be able to see it.

And this is exactly what /internalize does.
image
Only the first dll file in the merge list is left public (in yellow), and all the rest assemblies are converted to be internal (in turquoise).

You can see it using .Net Reflector:

clip_image001

Originally, Class1 was public (inside Infra)

clip_image001[6]

After merging with /internalize it is now Internal (inside MergedA)

clip_image001[4]

 

Now App1 knows of only one infra.xyz, and the build succeed!

 

Question for the next part:

What will happen if you merge App1 too?

 

Next: Part 3