Compiling/Embedding ASCX templated UserControls for reuse in multiple web applications - asp.net

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!

Related

MVC conversion project, fully qualified classes are not available unless 'Global' is added or System... is removed

My question is similar to here: ASP.NET System.Anything is not defined
I am in the process of converting a website project to an MVC 4 project. To do this, I created a new MVC project and imported all of my content from the previous website. Both projects are in VB, and I'm using Visual Studio 2010 SP1, with both the MVC 4 update and TFS 2012 update applied.
I now have errors popping up when I build the MVC project. Things like "Type 'System.Web.UI.Webcontrol' is not defined." When I hover over the error, two of the prompts I get to fix it are
1) Change 'System.Web.UI.WebControl' to 'Global.System.Web.UI.WebControl'
2) Change 'System.Web.UI.WebControl' to 'WebControl'
Both of these seem to fix it, but does anyone know why I can't use "System.Web.UI.WebControl" to refer to this class? I'd rather not change all of my code... there are 100s of thousands lines in there.
Update: Outside of the System and System.Web.UI namespaces, I can also drop the 'System.'. So 'System.Drawing.Color' would become 'Drawing.Color'.
There exists another namespace containing System in your solution somewhere (e.g. Abc.System.def) - probably in a referenced library. After the conversion, the project file probably imported the prefix to System in said namespace (e.g. Abc), so when you type in System, it resolves to Abc.System.
I posted this question before I had whittled down as much of the compile errors as possible.
It appears that after I had removed all the System. references from the previous App_Code classes, the real errors started appearing. There were all sorts of messages about aspx controls not existing.
Ultimately, it seemed that I needed to right-click on the .aspx pages from my website project and choose 'Convert to Web Application.' This generated a .aspx.designer.vb file with the asp.net control declarations in yet another partial class.
After doing that for all pages, I am now able to use System. throughout the app.
To others who've asked, this System. error was only affecting the non-page code. .aspx, .aspx.vb, .ascx, .ascx.vb, .ashx, etc., files were not affected.
This does make some sense, anyhow, as I had previously pulled in all the App_Code libraries prior to pulling in the pages, and the site compiled. After importing the pages and their code behinds, the order of the errors was just odd. But I stuck with it and found the underlying cause.
Thanks for the willingness to help. If you have any other questions, I'd be glad to respond.

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 ;-)

What makes an ASP.NET project an application vs library?

I am inheriting some ASP.NET code (I am an OS guy, not a web dev (yet ;-)). The solution has been re-factored and there are multiple projects (libraries and asp.net sites) in it. Aside from the libraries, there are two asp.net projects (called MAINSITE and SUBSITE). Only MAINSITE is being used as the official site (as an asp.net site), and MAINSITE has a depency on the code in the SUBSITE asp.net site, but doesn't use the site itself. I am trying to figure out how to clean this up and convert SUBSITE into a library.
My quick question is, whenever I debug the MAINSITE (set as default), it runs two asp.net processes: MAINSITE and SUBSITE. And so, at the very least, how can I avoid this? Is there a quick/temporary solution to this?
My detailed question is this:
What makes an asp.net site an asp.net site? For instance, in C the difference between an dll and exe could be defined (superficially anyway) as the presence of a main, and potential export information for the library (among other things, of course). If I were to convert an exe to dll I might:
1. remove the main code
2. make sure the public interface was correct (and exported correctly)
3. convert the makefile to build a dll rather than an exe.
Can someone point me to some similar steps for asp.net to .net lib?
Maybe:
1. get rid of index.aspx
2. get rid of web.config
3. any *.cs files to remove?
4. how do I change the properties?
5. any gotchas?
Thanks so much for your help.
Details: Visual Studio 2008/.NET 3.5
There are many, many components to make an application run as an ASP.Net application. However, in terms of your actual Web Application project, there's really not that much difference between it and generic library code except for the fact that much of your code relies on the existence of the HttpApplication runtime.
Any code that utilizes the System.Web (especially System.Web.UI) is going to be suspect in terms of having this dependency. For example, all the code in page or webcontrol event handlers (Init, Load, PreRender, etc.) relies on the fact that there is an HttpHandler (running inside an HttpApplication) raising these events. If you run the same WebControl out of a library that's not in an ASP.Net project, none of this will ever happen and the control will be useless. However, that exact same library would be quite functional if executed in the context of an ASP.Net process.
It really boils down to what process you're running the library in. In most cases, ASP.Net processes are spawned by IIS, although it is possible to host an ASP.Net process in other types of programs as well.
There isn't a simple 5-step process for converting a web project to a library unfortunately. But as a rule of thumb, webcontrols, .aspx and .ascx codebehind aren't going to convert.
For a more detailed look at what makes code into an ASP.Net program, see Rick Strahl's "A Low level look at ASP.Net".
If you go to "File" > "New" > "New Project..." and then click on the (assuming you're using C#) "Visual C#" in the list on the left, you're given the ability to create a "Class Library" project. You can extract all the relevant code to one of these and then reference in in your "MAINSITE".
You will need to reference it in the "References" section of your MAINSITE project and may need to import your library project using the import keyword.

ASP.NET page or control marked as internal doesn't work and gives "inaccessible due to its protection level" error

I know why this happens and I want to find a workaround it.
Example: a user control that exist in 2 assemblies, loaded at the same time.
namespace MyNamespace
{
internal class MyUserControl : UserControl
{
}
}
My situation - I want to be able to share user interface control between two web applications which are loaded at the same time in the same application pool. If my user interface class is marked public, then ASP.NET will not like it because it will be duplicated into memory ( CLR uses namespaces as full qualifier for class, and if the same class and namespace is met it more than one assembly, exception is thrown ). I do then mark the class as internal and of course I forget how UI compilation occurs, and I expect it to work. Of course it doesn't because ASP.NET compiles the UI into separate assembly which is dynamic and since I marked the class as internal, it is not visible to the main assembly...
The question that follows is: How can I make dynamic compiled assemblies that ASP.NET generates to view the main application assembly internals? It is very unfortunate that classes and methods I write into my UI controls must always be public! Has anyone met this obstacle in his daily work and found a workaround?
EDIT:
Precompilation is not possible here due to other circumstances.
You can share User Control markup between apps using a Virtual Path Provider (see
http://weblogs.asp.net/scottgu/archive/2005/11/27/431650.aspx and http://msdn.microsoft.com/en-us/library/system.web.hosting.virtualpathprovider.aspx).
You can pull the markup for the control from anywhere - SharePoint uses VPP to get markup from the database, and I use it in some projects to pull from a non-standard location on disk (which is shared between projects).
If your base class is internal, the derived class that ASP.NET generates at runtime will not be able to extend it.
However, I don't understand the issue that you're running into in the first place. Two distinct web applications will always run in different AppDomains (even if they're in the same app pool), and there should be no type conflicts between the AppDomains.
I probably need more details about what you are doing. e.g. what assembly are you compiling your base class into, and why does it end up in two different assemblies?

Does App_Code in ASP.Net compile new code to a secondary temp AppDomain?

I am trying to better understand how the App-Code folder operates, and in particular I am curious how new objects that a new class in the App-Code folder are included in the current AppDomain. Is a second, temporary AppDomain created in the same manner as when I compile new objects with the CodeDom?
In many ways the capability of moving new classes / objects into a Web Application is very compelling. Rob Connery's MVC Storefront is a good illustration. If you have used the App-code folder to deploy new functionality were there any trade-offs or gotchas that should be considered?
UPDATE:
I found an article in CoDe Magazine with this interesting passage:
Your application-specific code can go
inline of the ASPX page or control, it
can go into a CodeBeside partial
class, or you can create completely
autonomous classes in the APP_CODE
folder. The APP_CODE folder is a
special folder in an ASP.NET 2.0
project and any non-page or
control-related source code in your
Web project must go into this folder.
ASP.NET treats the content of APP_CODE
like a library project and compiles
the content into a separate assembly.
This assembly is then referenced by
all of the page or directory-level
assemblies that ASP.NET creates from
your ASPX/ASCX pages that use any of
the classes defined in APP_CODE.
My question still stands - is a second AppDomain created that supports this libary, and if so are the impacts on performance minimal?
I don't know the details, but it will certainly be in different dynamic assemblies. A class can't be in another AppDomain. Only an instance of a class can be in another AppDomain, and then it's really a hassle to access from another AppDomain (only through remoting)

Resources