Localization of Custom Controls in a redistributable assembly - asp.net

I'm trying to build a redistributable assembly containing several custom controls (CommonControls).
My environment: MSVC 2010, ASP.NET (WebForms) .NET 2.0/3.0/3.5
The problem: Compiling everything with a Web Deployment Project won't work if I localize CommonControls via App_GlobalResources.
Here is how I build the CommonControls assembly:
I use a WebSite containing the .ascx and .ascx.cs files:
and a Web Deployment Project with the following settings:
This will create "CommonControls.dll".
That assembly is to be used in a different ASP.NET WebApplication as follows:
web.config:
First (minor) problem: Adding CommonControls as a dependency will not automatically copy the satellite assemblies for the languages. Copying them manually to the correct output path seems to work though (for DEBUGGING).
Main problem: The Main web application is also localized via App_GlobalResources and built with a Web Deployment Project:
That build process will fail with
ASPNETCOMPILER : error ASPRUNTIME: Object reference not set to an
instance of an object.
Both deployment projects create a file named "bin\App_GlobalResources.compiled" and I guess those 2 files cannot coexist peacefully within the same output-project.
Is there any elegant solution to localize both CommonContols AND Main using ASP.NET built-in localization?
Note: The project I'm working on has to be compatible with Apache+Mono, so my project-settings (screenshots above) must be exactly like this to work correctly (already tested all other variations).

I fixed it. It was (probably) caused by outdated assemblies in the Bin folder used by the WDP (seems to be a good idea to manually clean that folder from time to time).
I also copied files from "CommonControls\Bin" to the final output (via pre-build event) which is not necessary and causes everything to break (satellite assemblies for translations are automatically copied by MSVC).
While my problem+solution might not be that helpful to others, it's at least a tutorial how to build a re-distributable assembly with some custom-controls out of a bunch of user-controls.
Notes:
All outputs of the custom-control assembly must be merged. Otherwise we will end up with 2 "App_GlobalResources.dll" files (won't work)
It only works with a "Web-site project", not "Web application"
User-controls must use the "CodeFile/Inherit" tags, not the deprecated "CodeBehind" tag (get rid of all those ".designer.cs" files!)
User-controls must have a "ClassName" tag that differs from the control's name (I appended "Internal" and renamed the class(es) in code-behind)
Embedded resources (images, scripts...) are not directly supported in a "Web-site project". I use an additional LIB for that

Related

Provide an Explicit Assembly Name for a Dynamically Compiled ASP.NET Website's App_Code Folder?

In a dynamically compiled ASP.NET Website project, can the assembly for the App_Code folder be explicitly named?
For example, under regular circumstances when I run an ASP.NET website the assembly name generated into the Temporary ASP.NET Files\ folder is partially randomized like App_Code.neizakfo.dll where neizakfo is the portion that can differ. Can I explicitly provide a name for the assembly like App_Code_Web1.dll?
Clarification
By business requirements, the website cannot be precompiled/deployed. Therefore I'm seeking a solution in context of the Temporary ASP.NET Files folder and dynamically compiled assemblies as noted above.
Background:
I came across this question while looking for a way to perform dynamic type instantiation on a class in the App_Code folder of a website using an assembly-qualified name stored in configuration, but instantiated from the web page, thus crossing an assembly boundary. Because the web page and app_code code are compiled into two different assemblies by default, the Type.GetType(..) method's default behaviour of searching for the Type name either in the current executing assembly (the web page) or in mscorlib doesn't suffice for picking any Type from the App_Code assembly. Being randomized, the app_code assembly name is not known for me to include in the assembly-qualified string.
I can put the data Type in a class library (because that does have an predefined/exact name) to get rid of this problem, however I'd like to know how to do this inside the website itself without creating a class library project for the purpose.
You can sort of do this in a WebSite project.
There's an MSDN article on using the -fixednames flag when compiling the project.
This effectively creates an assembly for each page - default.aspx.dll. However, this is only marginally more useful to you as you still need to know the name of the control or page you are looking for when you are loading - so you have to ensure your types and naming is consistent. It should, however, respect the name of the classes in app_code so this may work for you.
One other thing you could do is move all of the code in app_code out into it's own assembly, and then add that as a project reference. That would also simplify this problem.
Lastly, you could enumerate all of the dll's in the bin directory, and search each one for the type you are looking for. As this is fairly expensive, do it once, and cache the result somewhere so you don't keep doing it everytime you look that type up. This is probably the worst solution.
This is trivial to do in a WebApplication project, but I assume you are stuck with the WebSite one?
EDIT: As an update for the comments; if I use the Publish Web Tool, then all of the code in app_code goes in the bin directory in a dll called App_Code.dll - this behaviour does not change even if I use fixed naming (all fixed naming effects the naming of the dll's for each page, usercontrol). If I use ILSpy on this file, I can see my classes in there. So I know the name of the assembly, and it's location - I should be able to get at the types in it with minimal effort. I wonder why I'm seeing different behavior to you!
I created a simple class called "Person" with an Id and Name, put it in App_Code, compiled the site, and then ran the following code:
Type myType = Assembly.LoadFrom(Server.MapPath("~/bin/App_Code.dll")).GetType("Person", true);
Response.Write(myType.ToString());
It wrote out "Person", as expected.
Further Edit
The penny drops! If I then do:
object myObject= Activator.CreateInstance("App_Code.dll", "Person");
And try to cast myObject to person, I get the following message:
The type 'Person' exists in both 'App_Code.dll' and 'App_Code.jydjsaaa.dll'
So it's time to be devious.
in Global.asax, on Application_OnStart, do the following:
Application["App_Code_Assembly"] = Assembly.GetAssembly(typeof(Person));
In my test default page, I then did:
Assembly app_Code = Application["App_Code_Assembly"] as Assembly;
Response.Write(app_Code.FullName);
Which gave me the randomly named app_code it is actually running with in Temporary ASP.Net Files.
This is why I hate Web Site Projects ;-)

Satellite assembly is not picked up by ASP.NET app

I have a web project called "TestResourceApp" with Labels.resx in App_GlobalResources folder. I want to add another language by creating a satellite assembly.
Here are the steps I took to create the satellite assembly. The default text always get displayed. What did I do wrong ?
1) Create Labels.fr.resx in a different folder.
2) Generate resource file:
Resgen Labels.fr.resx TestResourceApp.App_GlobalResources.Labels.fr.resources
3) Generate satellite assembly:
AL /t:lib /embed:TestResourceApp.App_GlobalResources.Labels.fr.resources /out:french.dll /c:fr
4) Copy french.dll to TestResourceApp/bin/fr
I have uiculture set to auto in web.config and I have change the language on the browser.
I was able to use this page to solve some satellite assembly issues I was having. I'll throw in a few more things to check.
It's helpful to decompile the "neutral" assembly and see how it's put together. A tool like ILDASM.exe is helpful for this purpose. Once you get it decompiled, look through the text output for ".mresource", and you should see one with your naming. For example, if you add a resource to a Visual Studio project, they're named MyAssemblyName + ".Properties.Resources" + a language (if any) + ".resources" Examples:
MyAssembly.Properties.Resources.resources (neutral language)
MyAssembly.Properties.Resources.en-US.resources (English (US))
In my case, I had the file named properly, and in the appropriate folder (such as Bin\en-US). I was able to verify that much by using ProcMon.exe (by the SysInternals guys) and could see the worker process finding and reading in my DLL file (instead of just saying "PATH NOT FOUND"). However, it was not finding the resource by the name that it expected it to. That's when some disassembly helped to get to the bottom of the naming problem.
So, use ProcMon.exe to narrow down the kind of problem you might have. Hopefully that's helpful to someone.
It's complicated but here are a few tips for those who run into this problem:
Try to include the resx in the web project and let VS do the job for you.
Reflector is your friend. Compare satellite assemblies you created and those created by VS.
If you web app is targetting ASP.NET 2.0, you should use Resgex and AL that come with .net 2.0. Open the assemblies in Reflector and check the "references". It should reference mscorlib version 2.0.
If you deploy your web app using web deployment project, make sure the namespace for the resources in your satellite assemblies is correct. Again, compare with what VS creates. In my case, I used the wrong tool to generate the designer.cs file because I wanted them to be accessible from a different assembly. Make sure you are using GlobalResourceProxyGenerator. Otherwise, the namespaces won't match and the deployment code will not be able to find your resource. The namespace in the designer.cs should simply be "Resources", not "XXXX.App_GlobalResources"
Did you have set enableClientBasedCulture to true in globalization ?

ASP.NET precompile fails to merge a single user control

I have rather large third party ASP.NET WebSite project that I precompile and merge using MSBuild and the Microsoft Web Deployment targets. I don't precompile the .aspx and .ascx files, only the codefiles.
During the precompilation phase, the user control is compiled into an assembly that is not merged into the final WebSite.dll assembly. The final output contains:
WebSite.dll
App_Web_selectsinglepath.ascx.73690ebc.dll
All other user controls were compiled into assemblies with names like App_Web_abcdefgh.dll and these assemblies were merged into WebSIte.dll, just this one control was not.
What could be the cause of this behavior?
I had a similar issue trying to use aspnet_compile and aspnet_merge from the command line for a Web App project. Two controls would always have named assemblies instead of App_Web_[hex].dll, then would fail to load after the merge.
In my case I was able to add the -fixednames flag to the aspnet_compile call, which generated all of the control libraries as App_Web_[control_name].[hex].dll, which made them all visible to aspnet_merge, which removed the assembly loading error.
I still don't understand why those controls would consistently be generated with a different file name pattern (no duplicate page or control names in the hierarchy), but HTH someone.
User controls are compiled into separate assemblies because this allows you to treat your user controls as web controls. You can use ILMerge utility to merge multiple assemblies into one. Here is a good page explaining this.

Compiling/Embedding ASCX templated UserControls for reuse in multiple web applications

I'm onto a real head scratcher here ... and it appears to be one of the more frustrating topics of ASP.NET.
I've got an assembly that implements a-lot of custom Linq stuff, which at it's core has zero web functionality. I have an additional assembly that extends this assembly with web specific behaviour.
The web specific behaviour comes with a couple of user controls marked up inside ASCX templated UserControls.
I'm having trouble putting a nice finish on this assembly so that it is simple to redeploy for use in other applications. Let me run through what I've tried so far:
Copied the ASCX files to the consuming web application using build events; far from ideal and quite a deployment nightmare.
Implemented a custom VirtualPathProvider and embedded the ASCX templates within the assembly as embedded resources. Unfortunately when using the Register directive in the consuming application it creates the designer declaration as a UserControl, where I would require a declaration of the actual control type; unforeseen (typically) and undesirable.
Created a Web Deployment Project to compile the UserControls, but the compiled user controls then become part of another assembly, and no longer descend from the class definitions in my web assembly--the assembly needs to instantiate them dependent on the request context.
So number 1 is just crap, number 2 doesn't give me the type support I desire and number 3 I think I'm about to produce a reasonable solution with:
Lump all non-control classes into the App_Code folder, prepare a factory class that will construct an object of the desired control type using reflection and the expectation that the type being reflected will be present in the deployment output (hopefully guaranteed by the presence of the ClassName attribute in the Control directive).
Theres also always the other option of rewriting the ASCX controls into custom controls, but just don't have the resources to consider it at the moment, and we've got no expertise in doing that, and they work fine as UserControls.
Am I missing something obvious, something maybe much simpler, or is this just purposefully difficult? I've read stories of the ASP.NET compilation process being very unfortunate in it's design on my travels across this topic.
Well I think I've done it ... by being mindful of a few of a few annoying pitfalls with my last approach, I recommend the following when compiling ASCX user controls in a Web Application Project using a Web Deployment Project:
Avoid putting classes in App_Code unless they're standalone or helper classes, ASP.NET treats this as a speshul folder, the meaning of which is lost on me, mayhem, confusion and chaos follows. Code in this folder does get output in the Web Deployment Project, though.
Pay close attention to your assembly names, their root namespaces and deployment output assembly name- you'll get access is denied errors if you have any naming conflicts during the aspnet_merge process.
Ultimately you'll most likely end up deploying 2 assemblies, I tried to create only one but the deployment output was still pointing to type definitions in the source assembly. This isn't a problem if you don't have any other types in your Web Application Project--I have so it was a problem for me. In my case, my final output was:
<Organisation>.<TechnologyName>.Web.DLL - Compiled Web Application Assembly (containing the ASCX templates)
<Organisation>.<TechnologyName>.Web.UI.DLL - ASP.NET Compiled UserControl assembly, created by Web Deployment Project
Clean often, and check that the Web Application Project's bin and obj paths are cleared of any previous junk built when you perhaps hadn't finalised your namespace or assembly naming scheme--the Web Deployment Project will be quite keen to include these, causing a fine mess.
Check your imported namespaces, the ASP.NET compiler likes to refer to the Import directive in the ASCX template, and it also considers imported namespaces present in web.config's <configuration><system.web><pages><namespaces> element, tweak if you get unknown definitions appearing during the deployment process.
Have some patience spare, it's quite tricky! But you do get some nice re distributable UserControls at the end of it!
Phew!

How to convert ASP.NET website to ASP.NET web application

I have an ASP.NET 3.5 Website (visual studio lingo), but the site continues to grow and is looking rather cowboyish among other things. I'd like to see this get converted into a Web Application (namespaces and all).
Is this something that can be easily done in Visual Studio? If not, are there any other tools out there that could create all of the namespaces, etc. automagically?
Well, it turns out that the option "Convert to web application" does NOT exist for "websites". The option "Convert to web application" does exist only for "web applications" !!!!
[emphasis mine]
So, here's the deal, to do the
conversion, you need to:
Add a new "Web Application" to your VS 2008 solution (File->Add->New
Project->C#->Web->ASP.NET Web
Application).
Afterwards, you copy all the files in the old "website" to your newly
created "web application", and
override any files created in it by
default
The next step is the most ugly, you need to "manually" add the references
in your "website" to the new "web
application". I thought the VS 2008
PowerCommands toy would do this for me
as it does copy references from other
project types, but it didn't. You have
to do it by yourself, manually, and
you have to be cautious in this step
if you have multiple versions of the
same assembly (like AJAXToolkit in my
case) or assemblies that have both GAC
and local versions or so.
Keep repeating the last step and trying to build the "web application".
You'll keep getting errors like "
'....' is unknown namespace. Are you
missing an assembly reference? ". Make
sure you have none of those except the
ones where '....' is replaced by the
IDs of the server controls you use. In
other words, keep adding references
and building the project until only
the errors that exist because of
missing .DESIGNER.CS or .DESIGNER.VB
files.
Afterwards, go to the "web application" root project node in VS
2008 solution explorer, and right
click it, then you WILL find the
option "Convert to web application".
What this option does is actually
making small changes to the "#Page"
and "#Control" directives of pages and
controls, and creating the required
.DESIGNER.CS or .DESIGNER.VB files.
Try building the "web application" again. If you get errors, see what
references may be missing and/or go
click the "Convert to web application"
again. Sometimes, if there's any error
other than those caused of missing
DESIGNER files, not all the
pages/controls will have those
DESIGNER files created for them.
Fixing the non DESIGNER problem and
clicking "Convert to web application"
again should do the job for this.
Once you are done successful VS build, you should be ready to go.
Start testing your web application.
Optionally, you can right click the
"web application" root project node in
VS 2008 Solution Explorer and click
"Properties" then go to the tab "Web"
to set the "web application" to a
virtual folder in IIS (you can create
new virtual directory from there in
VS). If you want to use the IIS
virtual directory that the old
"website" used, you need to remove
that from IIS first.
Update: When testing your pages, pay MOST ATTENTION to classes in
"App_Code" folder, especially those
with NO NAMESPACE. Those can be a big
trap. We had a problem with two
extension method overloads in the same
static class that had no namespace,one
extends DateTime? (Nullable)
and calls another overload that
extends DateTime itself. Calling the
other overload as extension method
passed VS 2008 compilation and gave us
a compilation error ONLY IN RUNTIME
(With IIS). Changing the call to the
other overload from calling it as
extension method to calling it as
normal static method (only changing
the call in the same class, calls from
other classes remained extension
method calls) did solve this one, but
clearly, it's not as safe as it used
to be in VS 2005. Especially with
classes with no namespaces.
Update2: During the conversion, VS 2008 renames your "App_Code" to
"Old_App_Code". This new name sounds
ugly, but DO NOT RENAME IT BACK. In
the "web application" model, all code
will be in one assembly. In runtime,
the web server does not know what web
project type you are using. It does
take all code in "App_Code" folder and
create a new assembly for it. This
way, if you have code in folder named
"App_Code", you'll end up with RUNTIME
compilation errors that the same types
exist in two assemblies, the one
created by VS, and the one created by
IIS / ASP.NET Development Server. To
avoid that. leave the "Old_App_Code"
with the same name, or rename it to
ANYTHING EXCEPT: "App_Code". Do not
place any code in such "App_Code"
folder and prefereably do NOT have a
folder with such name in your "web
application" at all.
I know this since before but forgot it
now as I have not used "website" model
for long :(.
Walkthrough: Converting a Web Site Project to a Web Application Project in Visual Studio at MSDN
If your website application grows.. it's better to split it into several projects. Conversion from Web Site project to Web Application project won't help much.
If you're having problems getting your new Web Application Project to build check the File Properties in Visual Studio of all 'helper' classes. For a project I was converting the Build Action was set to Content whereas it should have been Compile.
I've now successfully migrated one Website project to a web application and there is quiet a few gotchas to look out for.
Having ReSharper at your disposal helps a lot in refactoring the aspx files.
Set up your solution and create an empty WebApplication
Copy all file over
aspx files in website projects don't have a namspace. Wrap your classes in the appropriate namespaces
During copying, all my pages in subfolders got renamed to my project name and the foldername, so I got 40ish public partial class FolderName_Projectname : Page If neccessary rename all files using Resharper or manually.
If you encounter multiple errors like "There is already a member Page_Load() defined", this is most likely due to incorrect class names und duplication
After adding a namespace
Replace CodeFile in all aspx pages with Codebehind and especially pay attention to files i your subfolder. Make sure Inhertis="" doesn't contain the relative path. Your namespaces take care of everything. So the correct format is Inherits="Namespace.classname".
If your class has a namespace NaSpa and a filename foo.cs it would be Inherits="NaSpa.foo"
After you have prepared all your files (don't forget your master pages), run "Convert to web application". If you encounter errors afterwards, rinse and repeat.
If you encounter errors of the sort "TextBoxName can't be found are you missing a reference", make sure you did not forget to sanitize your aspx pages. A good indicator is to check the automatically generated designer files. If TextBoxName does not appear in there, the conversion did not succeed completely.
Resolve any missing dependencies.
Build
Create a New Web Application in VS 2010.
1. Using Windows Explorer copy all your files into you project folder.
2. In VS 2010 solution explorer show all files.
3. Select the files and folders - right click include in project.
4. Right click the project solution explorer and select Convert to Web Application.
There are quite a few small differences, such as the App_Code folder will get renamed to old_app_code - that surprisingly doesn't cause any errors. The TypeName on your object data sources and the inherits on the #Page tag might need the [ProjectName]. prefix appended globally. For example if your type name was "BusinessLogic.OrderManager" and your project name is InventorySystem you would need to change it to InventorySystem.BusinessLogic.OrderManager. Also a few display changes, such as required field validators don't default to red font anymore, they default to black.
I was facing the same problems initially. After following the Wrox Professional ASP.NET 4.0 book, I found the following solution for my case.
I first created a new web application. Copied all the website files into the web application folder. Right click on the application, and click conver to web application.
You might ask why you need to convert a web app into a web app. The answer is, that when you create a website, you simply code the .cs file where-ever required.
A web application, however declares .design.cs (or .vb) and a .cs file for the code and design section automatically.
NEXT: Remove all manual references, like 'Inherits' attribute in the PAGE directive, to other files in your website, since name spaces WILL take care of referencing the classes centrally.
I also faced a problem, since I had not included OBJ and BIN folder in my project.
If you think you are missing your BIN and OBJ folders, simply click the 'Show All Files' icon in the Solution Explorer and then right click on the missing folders and add to project. (to make sure they compile with the project.)
UPDATE:
As #deadlychambers points out in the comments: You can search everywhere by doing a "Ctrl + Shift + F" and then search for Inherits="(.*?)". This will find all occurrences and probably save you some time!
the default ASP name space does not seem to work anymore. So I cannot seem to call my User Controls.ascx pages from outside the page. Giving them a namespace and changing the default from ASP to my namespace seemed to work.

Resources