ASP.NET MVC Registering custom culture and internationalization - asp.net

I have an ASP.NET MVC 4 project and I've registered the custom culture in it because I want to have a client-specific translation.
I call the following helper method with parameters like RegisterCulture("en-GB-CustA", "English (Customer A)", "en-GB"); This call is done in Application_Start event handler of the MvcApplication : HttpApplication class.
private static void RegisterCulture(string cultureCode, string cultureName, string baseCultureCode)
{
var ci = new CultureInfo(baseCultureCode);
var ri = new RegionInfo(ci.Name);
var builder = new CultureAndRegionInfoBuilder(cultureCode, CultureAndRegionModifiers.None);
builder.LoadDataFromCultureInfo(ci);
builder.LoadDataFromRegionInfo(ri);
builder.CultureEnglishName = cultureName;
builder.CultureNativeName = cultureName;
try
{
builder.Register();
}
catch (InvalidOperationException)
{
}
}
The method is fairy simple, it basically creates new culture based on existing one and replaces it's name.
Now in my Global.asax just for the testing purposes I've put the following code to MvcApplication class to switch current thread for the custom one.
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
var ci = new CultureInfo("en-GB-CustA");
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
}
I've also included two resources files. One is called Test.resx which is for the default texts, the second one is Test.en-GB-CustA.resx. I've added a simple string resource there called Title with two different values.
Test.resx => "Hello World!"
Test.en-GB-CustA => "Hello from custom culture!"
I've also put on one of my view the code to display this title (I've added ViewRes as s CustomToolNamespace for both resource files for simplification).
#ViewRes.Test.Title
Unfortunatelly even though I've set the custom culture as descibed before I'm getting the detault "Hello world" value all the time. What am I missing here?

I know it sounds really simple, and this is an old-ish thread but have you tried rebooting?
Your code looks fine.
I had exactly the same problem, the new custom culture (locale) was being created correctly, but not being applied against the resource file of the correct name. Restarting was the only thing that applied the setting changes.
The only other thing to check, is that you are running with administrator privileges - as you need these to create a new locale.
See here:
http://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo.aspx

I ended up renaming the code form my custom culture so something like en-XX. I've also have to define both TwoLetterISOLanguageName and ThreeLetterISOLanguageName and it registered properly.

Related

MVVM Light 5 - Navigation Service passing wrong parameter

I have upgraded to MVVM Light 5 and I changed my navigation methods from:
Messenger.Default.Send(new NavigateToPageMessage() { PageName = "UserDetailsPage", Parameter = id });
To the following:
_navigationService.NavigateTo(ViewModelLocator.UserDetailsPageKey, id);
The parameter I am passing does not seem to make it's way to the OnNavigatedTo event of the view anymore, the parameter is completely different, am I missing something?
EDIT:
This new method seems to give me the parameter I need:
GlobalNavigation.GetAndRemoveParameter(NavigationContext)
Although now, when the app is tombstoned, I lose that parameter entirely. Before, when the app was restored I would still have that parameter in the NavigatedTo args, this allowed me to re-hit the server with that ID and get fresh data. Why have I lost this capability
use this
protected override void OnNavigatedTo(NavigationEventArgs e)
{
GalaSoft.MvvmLight.Views.NavigationService navigationService = new GalaSoft.MvvmLight.Views.NavigationService();
var param = navigationService.GetAndRemoveParameter(this.NavigationContext);
base.OnNavigatedTo(e);
}

ASP.Net WebForms Routing Single Route for Multiple Destinations

I am looking into setting database routing up for a new website I plan to create. I have been looking at the following tutorial with regards to utilising friendlyUrls from a database:
http://www.asp.net/web-forms/tutorials/aspnet-45/getting-started-with-aspnet-45-web-forms/url-routing
However, I would like to use the same route structure for multiple entities. Meaning:
mysite.com/{PlayerName} goes to player.aspx
mysite.com/{TeamName} goes to team.aspx
… and so on …
Could somebody point in the right direction of achieving this with asp.net. Is it possible using the built in routing engine, or should I be looking to code my own HTTPModule for this?
Thanks
David
I'm not sure why so many people say that this cannot be done with routing - maybe I'm not getting something, but the same logic that apparently makes the accepted answer a valid option should be perfectly applicable to a custom route handler, e.g. IRouteHandler or something derived from System.Web.Routing.RouteBase.
You can add "managers" to your RouteCollection (RouteTable.Routes) in the manner of:
routes.Add("MyRoutName", new MyCustomRouteBaseThing())
... Or:
routes.Add(new Route("whatever/{possiblySomething}", new RouteValueDictionary {
{"whatever", null}
}, new MyImplementationOfIRouteHandler()));
... Etcetera, depending on your needs.
If you go with the RouteBase alternative for example, override GetRouteData(), GetVirtualPath() and whatnot. I'm not saying it's necessarily a better option than the accepted answer, I just don't see why routing should be deemed not viable. (What am I missing?)
EDIT: At the time I wrote the above, the "accepted answer" was the one about URL rewriting posted by Tasos K, to whom the bounty was also rewarded. The accepted answer has since been reassigned.
Write two constraints which return boolean whether segment is a team or not / a player or not.
public class IsTeamConstraint : IRouteConstraint
{
public bool Match
(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection
)
{
return SomeService.IsTeam(values["teamName"]);
}
}
public class IsPlayerConstraint : IRouteConstraint
{
public bool Match
(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection
)
{
return SomeService.IsPlayer(values["playerName"]);
}
}
Set constraint in page route.
void RegisterCustomRoutes(RouteCollection routes)
{
routes.MapPageRoute(
"Team",
"{teamName}",
"~/Team.aspx",
false,
null,
new RouteValueDictionary { { "isTeam", new IsTeamConstraint() } }
);
routes.MapPageRoute(
"Player",
"{playerName}",
"~/Player.aspx",
false,
null,
new RouteValueDictionary { { "isPlayer", new IsPlayerConstraint() } }
);
}
Now when a page is requested registered page routes will use constraint to check that the route is valid and execute page if it is.
I haven't tried this in ASP.Net Forms but I've applications running with constraints developed in ASP.Net MVC. Both type of application (Forms and MVC) shared common routing logic.
I also don't know how this can be done using routing. But one way to achieve this is using URL rewriting instead. The whole process has a few steps and it is rather simple to make.
Applying the URL rewriting
You add at the Global.asax the following function.
void Application_BeginRequest(object sender, EventArgs e)
{
//Here you will get exception 'Index was outside the bounds of the array' when loading home page, handle accordingly
string currentsegment = Request.Url.Segments[1];
string RewritePath = "";
if (IsTeam(currentsegment))
{
RewritePath = "~/team.aspx?team=" + currentsegment;
}
if (IsPlayer(currentsegment))
{
RewritePath = "~/player.aspx?player=" + currentsegment;
}
if (RewritePath != "") {
// Adding all query string items to the new URL
for (int I = 0; I <= Request.QueryString.Count - 1; I++)
{
RewritePath = RewritePath + "&" + Request.QueryString.Keys[I] + "=" + Request.QueryString[I];
}
Context.RewritePath(RewritePath);
}
}
So if the URL has is /some-title-here you can get the some-title-here part using the Request.Url.Segments array.
Then based on that your code detects if this title is a team or a player. In any case you change internally the URL by calling the Context.RewritePath(...).
One important thing is that you need to add all the query string items manually in order to pass them to your pages.
Also, inside your code the Request.Url will know the rewritten URL, not the original.
A quick way to test it is to implement the IsTeam(...) and IsPlayer(...) function as below. With only this code when hitting /player-tasos the ~/player.aspx?player=player-tasos page loads and when hitting /team-stackoverflow the ~/team.aspx?team=team-stackoverflow page loads.
private bool IsTeam(string segment)
{
return segment.StartsWith("team");
}
private bool IsPlayer(string segment)
{
return segment.StartsWith("player");
}
So far this approach works but it has one main issue. When there is a PostBack the URL changes to the one you have set in the Context.RewritePath(...)
Avoiding PostBack issue
To avoid this issue you need to add to your projects two ASP.NET folders
App_Browsers
App_Code
In the App_Code folder you create a file FormRewriter.cs and add the following code (In my demo the root namespace is WebFormsRewriting)
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.UI;
namespace WebFormsRewriting
{
public class FormRewriterControlAdapter : System.Web.UI.Adapters.ControlAdapter
{
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
base.Render(new RewriteFormHtmlTextWriter(writer));
}
}
public class RewriteFormHtmlTextWriter : System.Web.UI.HtmlTextWriter
{
public RewriteFormHtmlTextWriter(HtmlTextWriter writer)
: base(writer)
{
this.InnerWriter = writer.InnerWriter;
}
public RewriteFormHtmlTextWriter(System.IO.TextWriter writer)
: base(writer)
{
base.InnerWriter = writer;
}
public override void WriteAttribute(string name, string value, bool fEncode)
{
// If the attribute we are writing is the "action" attribute, and we are not on a sub-control,
// then replace the value to write with the raw URL of the request - which ensures that we'll
// preserve the PathInfo value on postback scenarios
if ((name == "action"))
{
HttpContext Context = default(HttpContext);
Context = HttpContext.Current;
if (Context.Items["ActionAlreadyWritten"] == null)
{
// Because we are using the UrlRewriting.net HttpModule, we will use the
// Request.RawUrl property within ASP.NET to retrieve the origional URL
// before it was re-written. You'll want to change the line of code below
// if you use a different URL rewriting implementation.
value = Context.Request.RawUrl;
// Indicate that we've already rewritten the <form>'s action attribute to prevent
// us from rewriting a sub-control under the <form> control
Context.Items["ActionAlreadyWritten"] = true;
}
}
base.WriteAttribute(name, value, fEncode);
}
}
}
In the App_Browsers folder you create a file Form.browser and add the following snippet. Note here to put the class name of the Adapter with its namespace.
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.HtmlControls.HtmlForm"
adapterType="WebFormsRewriting.FormRewriterControlAdapter" />
</controlAdapters>
</browser>
</browsers>
And that's it. Adding those two files will handle the PostBack issue. If you put the FormRewriter.cs outside the App_Code folder it will not work. Also those two folders must be uploaded to the production server.
I have used this approach for years in .NET 3.5 and .NET 4.0 without any problems. Today I also tested it in a .NET 4.5 Web Forms project and it works with no issues.
All of the above are based on a ScottGu's article about the subject
As others have pointed out... it would be much better NOT to use this route for both Players and Teams.
It would be preferable to setup two routes...
mysite.com/player/{PlayerName}
mysite.com/team/{TeamName}
In this way you can drive all "player" traffic to Player.aspx, and "team" traffic to Team.aspx, nice and easy.
However... If you really have to support a single route, I recommend that you add it as a third option, and use a 301 Redirect to one of the two above routes.
mysite.com/{PlayerOrTeamName} -> Route.aspx
Let Route.aspx handle requests that don't map to physical files.
Then your Route.aspx code needs to function as a 404 Error handler, but with a catch.. It will check the Players data and the Teams data for an exact match. If it finds one it should do a 301 permanent redirect to the correct /player/ or /team/ route.
Using...
string strCorrectURL = RouteTable.Routes.GetVirtualPath(null, "player", new RouteValueDictionary { { "Name", strValue }});
Response.StatusCode = 301;
Response.Status = "301 Moved Permanently";
Response.AddHeader("Location", strCorrectURL);
Response.End();
This will give you the functionality of a single path, but tell search engines to index the more precise path.
You could skip the RouteTable altogether and just put this code into your default 404 handler.

How do QueryString parameters get bound to Action method parameters?

I have a webforms project, and am attempting to run some code that allows me to make a call to an MVC route and then render the result within the body of the web forms page.
There are a couple of HttpResponse/Request/Context wrappers which I use to execute a call to an MVC route, e.g.:
private static string RenderInternal(string path)
{
var responseWriter = new StringWriter();
var mvcResponse = new MvcPlayerHttpResponseWrapper(responseWriter, PageRenderer.CurrentPageId);
var mvcRequest = new MvcPlayerHttpRequestWrapper(Request, path);
var mvcContext = new MvcPlayerHttpContextWrapper(Context, mvcResponse, mvcRequest);
lock (HttpContext.Current)
{
new MvcHttpHandlerWrapper().PublicProcessRequest(mvcContext);
}
...
The code works fine for executing simple MVC routes, for e.g. "/Home/Index". But I can't specify any query string parameters (e.g. "/Home/Index?foo=bar") as they simply get ignored. I have tried to set the QueryString directly within the RequestWrapper instance, like so:
public class MvcPlayerHttpRequestWrapper : HttpRequestWrapper
{
private readonly string _path;
private readonly NameValueCollection query = new NameValueCollection();
public MvcPlayerHttpRequestWrapper(HttpRequest httpRequest, string path)
: base(httpRequest)
{
var parts = path.Split('?');
if (parts.Length > 1)
{
query = ExtractQueryString(parts[1]);
}
_path = parts[0];
}
public override string Path
{
get
{
return _path;
}
}
public override NameValueCollection QueryString
{
get
{
return query;
}
}
...
When debugging I can see the correct values are in the "request.QueryString", but the values never get bound to the method parameter.
Does anyone know how QueryString values are used and bound from an http request to an MVC controller action?
It seems like the handling of the QueryString value is more complex than I anticipated. I have a limited knowledge of the internals of the MVC Request pipeline.
I have been trying to research the internals myself and will continue to do so. If I find anything I will update this post appropriately.
I have also created a very simple web forms project containing only the code needed to produce this problem and have shared it via dropbox: https://www.dropbox.com/s/vi6erzw24813zq1/StackMvcGetQuestion.zip
The project simply contains one Default.aspx page, a Controller, and the MvcWrapper class used to render out the result of an MVC path. If you look at the Default.aspx.cs you will see a route path containing a querystring parameter is passed in, but it never binds against the parameter on the action.
As a quick reference, here are some extracts from that web project.
The controller:
public class HomeController : Controller
{
public ActionResult Index(string foo)
{
return Content(string.Format("<p>foo = {0}</p>", foo));
}
}
The Default.aspx page:
protected void Page_Load(object sender, EventArgs e)
{
string path = "/Home/Index?foo=baz";
divMvcOutput.InnerHtml = MvcWrapper.MvcPlayerFunctions.Render(path);
}
I have been struggling with this for quite a while now, so would appreciate any advice in any form. :)
MVC framework will try to fill the values of the parameters of the action method from the query string (and other available data such as posted form fields, etc.), that part you got right. The part you missed is that it does so by matching the name of the parameter with the value names passed in. So if you have a method MyMethod in Controller MyController with the signature:
public ActionResult MyMethod(string Path)
{
//Some code goes here
}
The query string (or one of the other sources of variables) must contain a variable named "Path" for the framework to be able to detect it. The query string should be /MyController/MyMethod?Path=Baz
Ok. This was a long debugging session :) and this will be a long response, so bear with me :)
First how MVC works. When you call an action method with input parameters, the framework will call a class called "DefaultModelBinder" that will try and provide a value for each basic type (int, long, etc.) and instance of complex types (objects). This model binder will depend on something called the ValueProvider collection to look for variable names in query string, submitted forms, etc. One of the ValueProviders that interests us the most is the QueryStringValueProvider. As you can guess, it gets the variables defined in the query string. Deep inside the framework, this class calls HttpContext.Current to retrieve the values of the query string instead of relying on the ones being passed to it. In your setup this is causing it to see the original request with localhost:xxxx/Default.aspx as the underlying request causing it to see an empty query string. In fact inside the Action method (Bar in your case) you can get the value this.QueryString["variable"] and it will have the right value.
I modified the Player.cs file to use a web client to make a call to an MVC application running in a separate copy of VS and it worked perfectly. So I suggest you run your mvc application separately and call into it and it should work fine.

Problem with dotnetopenauth client.ProcessUserAuthorization()

I downloaded DotNetOpenAuth-3.5.0.10259 and tried to run the samples, specifically the OAuthClient sample and I managed to get it to work with facebook (VS2010). I can see "Welcome, [my name]" after allowing access in facebook.
The problem comes in when I try to use it in another project. I get a "No overload for method 'ProcessUserAuthorization' takes '0' arguments" and "No overload for method 'RequestUserAuthorization' takes '0' arguments".
Its basically the same code, which I find very weird since it works on the included sample but won't compile in the other project.
What did I miss?
protected void Page_Load(object sender, EventArgs e)
{
IAuthorizationState authorization = client.ProcessUserAuthorization();
if (authorization == null)
{
// Kick off authorization request
client.RequestUserAuthorization();
}
private static readonly FacebookClient client = new FacebookClient
{
ClientIdentifier = ConfigurationManager.AppSettings["facebookAppID"],
ClientSecret = ConfigurationManager.AppSettings["facebookAppSecret"],
};
The FacebookClient class came from the DotNetOpenAuth.ApplicationBlock project in the samples included in the 3.5.0.10259 download.
The only thing I can guess is that there is missing overload definitions within the libraries. I experienced the same issue you are describing, but in my case I couldn't get the samples to compile at all.
The trick though, is to simply pass in a NULL for the request parameter, which seems to work:
IAuthorizationState authorization = client.ProcessUserAuthorization(null);
Also note that you may run into the same missing overload issue with the "RequestUserAuthorization" method. Likewise, you can also pass in null values for each of the three parameters if you don't want to send them along:
client.RequestUserAuthorization(null, null, null);
Good luck!

Create Instance Aspx Page of Ascx Control In a Back End Class without Loading FilePath

Question: Is it possible in back end code (not in the code behind but in an actual back end class) to load and render a page or control defined in a .aspx or .ascx without having to use Load(path) and instead just create an instance of the page/control class?
I want to be able to do this (from a back end class NOT a code behind):
MyControl myCtl = new MyApp.Views.Shared.MyControl();
String html = Util.ControlToString(myCtl); //I get an empty string & hidden errors
instead of this
public static string ControlToString(string path)
{
Page pageHolder = new Page();
MyControl myCtl = (MyControl)pageHolder.LoadControl(path);
pageHolder.Controls.Add(myCtl);
StringWriter output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
Details:
In a Asp.net WebApp I occasionally need to render a user control (.ascx) or page (.aspx) as a HTML string. When a page or control inherits from a code behind, its class shows up in intellisense in my back end code and I can create an instance and set properties without getting compile time or run time errors. However, when I try to render the page or control I always get an empty string and upon inspection the page or control shows suppressed internal rendering errors unless I load the page or control using its physical file path.
I think the key issue has to do with when & how the .aspx / .ascx files are runtime compiled. I don't want to create a pre compiled class library of user controls because that would make the design process awkward and I really like the designer features offered by the .aspx / .ascx pages and so I'd love to find a way to make the pages compile in the solution so that they are usable like any other back end class but can still be created using the designer. I want the best of both worlds (1) to be able to edit pages and controls in the designer and (2) create instances and set their properties using back end classes.
Here is an approach that may help in situations like this.
The "back-end" code may not know where the user control is located, but the User Control does know where it is.
So, in the User Control, add a static method like this:
public partial class MyControl : UserControl
{
...
public static MyControl LoadControl(CustomDto initialData)
{
var myControl =
(MyControl)
((Page) HttpContext.Current.Handler)
.LoadControl("~\\UserControlsSecretPath\\MyControl.ascx");
myControl._initialData = initialData;
return myControl;
}
...
private CustomDto _initialData;
}
(The CustomDto is included to illustrate how initial data can be passed to the User Control. If you don't need to do that, take it out!)
With this, the code that loads the user control does not need to know the path to where the user control is physically located. If that location ever changes, then update this one location. All other code that uses this UserControl is unchanged.
In your back-end code, or anywhere else, you can do something this:
var control = MyControl.LoadControl(customDto);
PlaceHolder1.Controls.Add(control);
Generally speaking: no.
As far as I know, ASP.NET inherits from your classes to combine the .aspx/.ascx template with your code. This is why your controls show up empty: the code to combine the template with your code is missing. This is usually done by ASP.NET the first time you access a page or user control (that's precisely why the first hit is a little slow: it's actually generating and compiling the hookup-code).
For precompiled websites ASP.NET generates this code as part of your precompiled website's .dll in advance, which is why such sites load quicker. However, IIRC you'll still need to instantiate the generated classes rather than your original classes.
It's a pretty common request, but so far MS has not provided the tools to do this.
Edit: Although I fail to see why you'd want to render a control to an in-memory string, I might have a solution to the build problems.
If you stick to non-compiled .ascx files (using the web site model rather than the web application model), you can actually develop them separately by placing them physically in subfolder of your main project, and treat them as content files only. Then, you can make a separate project with this subfolder as the root folder. You only need to treat the files in this subfolder as web site files, the main project can still be a web application. (Actually recommended, 'cause you don't want the .csproj files included in the main project.)
However, shared code (that is, shared between the controls project and the main project) should be put in a separate library project, so you can compile each project separately without interdependencies.
Using LoadControl within the main project will compile them on the fly (code behind is possible); if you need to set properties, you must however define interfaces in the shared project, implement them on the appropriate user controls and cast the control created by LoadControl to the appropriate interface.
I developed a solution that solves my problem in VS 2008:
Create Main Site Solution: Create a MVC 1 Website solution in
VS 2008
Create Model Class Library: Add a Class Library for the Model Code
Create View Code: Add an "Empty Website" to hold the .ascx pages, and add a reference the model library
Create Deployment Site: Add a deployment project that compiles the "Empty Website" goto the "properties page" and Check: "Merge All outputs into a single assembly" and "Treat as library component" and be sure to UnCheck: "Allow this precompiled site to be updatable"
Reference Deployment Output: In the main project add a reference to the output of the Deployment site.
ASP. - Compiled Controls: Controls show up under the ASP. namespace and are named in two ways
A. if the .ascx / aspx page did not declare a "ClassName" then they are named using their folder and file name with underscores ex. <%# Control Language="C#" ClassName="Admin_Index" %>
B. if they did declare a class name then that is their name
List item
Usage: Example code is below
Here is an example usage
public ActionResult Index()
{
var ctl = new ASP.Directory_FirmProfile(); //create an instance
ctl.Setup(new MyDataModel); //assign data
//string test = CompiledControl.Render(ctl); //output to string
return new HtmlCtl.StrongView(ctl); //output to response
}
public class CompiledControl
{
public static string Render(Control c)
{
Page pageHolder = new Page();
pageHolder.Controls.Add(c);
StringWriter output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
public static void Render(Control c, StringWriter output)
{
Page pageHolder = new Page();
pageHolder.Controls.Add(c);
HttpContext.Current.Server.Execute(pageHolder, output, false);
}
public static void Render(Control c, HttpResponseBase r)
{
Page pageHolder = new Page();
pageHolder.Controls.Add(c);
HttpContext.Current.Server.Execute(pageHolder, r.Output, false);
}
}
public class StrongView : ActionResult
{
private Control ctl;
public StrongView(Control ctl)
{
this.ctl = ctl;
}
public string VirtualPath{get;set;}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
HtmlCtl.CompiledControl.Render(ctl, context.HttpContext.Response);
}
}
I've come up with a simpler solution along the lines of Ruben's advice.
It has worked without problems for about a month:
//Example usage
//reference the control
var emailCTL = new HtmlCtl.ControlOnDisk<MyControlType>(#"~\Views\EmailTemplates\MyControlType.ascx");
//if you have a code behind you will get intellisense allowing you to set these properties
// and re-factoring support works most places except the template file.
emailCTL.c.title = "Hello World "; //title is a property in the code behind
emailCTL.c.data = data; //data is a property in the code behind
string emailBody = emailCTL.RenderStateless();
//Helper Class
public class ControlOnDisk<ControlType> where ControlType : UserControl
{
public ControlType c;
Page pageHolder = new Page();
public ControlOnDisk(string path)
{
var o = pageHolder.LoadControl(path);
c = (ControlType)o;
pageHolder.Controls.Add(c);
}
public string RenderStateless()
{
StringWriter output = new StringWriter();
// set up dumby context for use in rendering to email stream
StringBuilder emailMessage = new StringBuilder();
TextWriter tw = new StringWriter(emailMessage);
HttpResponse dumbyResponse = new HttpResponse(tw);
HttpRequest dumbyRequest = new HttpRequest("", "http://InsertURL.com/", ""); //dummy url requierd for context but not used
HttpContext dumbyContext = new HttpContext(dumbyRequest, dumbyResponse);
//HttpContextBase dumbyContextBase = new HttpContextWrapper2(dumbyContext);
dumbyContext.Server.Execute(pageHolder, output, false);
return output.ToString();
}
}

Resources