Is their a solution to generate an email template using an ASP.NET MVC View without having to jump through hoops.
Let me elaborate jumping through hoops.
var fakeContext = new HttpContext(HttpContext.Current.Request, fakeResponse);
var oldContext = HttpContext.Current;
HttpContext.Current = fakeContext;
var html = new HtmlHelper(new ViewContext(fakeControllerContext,
new FakeView(), viewDataDictionary, new TempDataDictionary()),
new ViewPage());
html.RenderPartial(viewName, viewData, viewDataDictionary);
HttpContext.Current = oldContext;
The above code is using the current HttpContext to fake a new Context and render the page with RenderPartial, we shouldn't have to do this.
Another very detailed solution using ControllerContext and .Render:
(IEmailTemplateService, Headers/Postback WorkAround) but pretty much doing the same thing with a lot more code.
I on the other hand, am looking for something that would just render a View without having to POST/GET and generates me a simple string that I can send off through my Email code.
Something that doesn't run into errors such as posting headers twice or faking some piece of data.
EX:
//code which does not fire Render, RenderPartial... etc
var email = emailFramework.Create(viewData, view);
See my solution bellow or follow this link:
My Solution using spark: (12/30/2009) ASP.NET MVC Email Template Solution
This is what I wanted the ASP.NET MVC ViewEngine to do, but it's in Spark, just follow the latest link right bellow,
Update (12/30/2009) Cleaner Version: ASP.NET MVC Email Template Solution
(11/16/2009) Or, Louis DeJardin Console Application Version:
using System;
using Spark;
using Spark.FileSystem;
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public abstract class EmailView : AbstractSparkView
{
public User user { get; set; }
}
class Program
{
static void Main(string[] args)
{
// following are one-time steps
// create engine
var settings = new SparkSettings()
.SetPageBaseType(typeof(EmailView));
var templates = new InMemoryViewFolder();
var engine = new SparkViewEngine(settings)
{
ViewFolder = templates
};
// add templates
templates.Add("sample.spark", #"Dear ${user.Name}, This is an email.Sincerely, Spark View Engine http://constanto.org/unsubscribe/${user.Id}");
// following are per-render steps
// render template
var descriptor = new SparkViewDescriptor()
.AddTemplate("sample.spark");
var view = (EmailView)engine.CreateInstance(descriptor);
view.user = new User { Id = 655321, Name = "Alex" };
view.RenderView(Console.Out);
Console.ReadLine();
}
}
I decided to use this method because it seems to be the one that does everything right, it:
Does not use any HttpContext/ControllerContext or mess with routing data!
It can implement Header/Footer to allow templates!
You can use loops, conditionals, etc...
It's clean, light weight especially if you plan to move entirely to spark view engine!
Please, make sure to read these posts. All credit to Louis DeJardin see his tutorials :):
Using Spark as a general purpose template engine!, Email Templates Revisited
Why do you need to create the email from a view? Why not use a plain old template file? I do this all the time - I make a template and use the NVelocity engine from the castle project (not to be confused with an nvelocity VIEW engine) to render the template.
Example:
var nvEngine = new NVelocityEngine();
nvEngine.Context.Add("FullName", fullName);
nvEngine.Context.Add("MallName", voucher.Mall.Name);
nvEngine.Context.Add("ConfirmationCode", voucher.ConfirmationCode);
nvEngine.Context.Add("BasePath", basePath);
nvEngine.Context.Add("TermsLink", termsLink);
nvEngine.Context.Add("LogoFilename", voucher.Mall.LogoFilename);
var htmlTemplate = System.IO.File.ReadAllText(
Request.MapPath("~/App_Data/Templates/Voucher.html"));
var email = nvEngine.Render(htmlTemplate);
The NVelocityEngine class is a wrapper I wrote around the NVelocity port provided by the Castle project as shown below:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using NVelocity;
using NVelocity.App;
namespace MyProgram
{
/// <summary>
/// A wrapper for the NVelocity template processor
/// </summary>
public class NVelocityEngine : VelocityEngine
{
Hashtable context = new Hashtable();
/// <summary>
/// A list of values to be merged with the template
/// </summary>
public Hashtable Context
{
get { return context; }
}
/// <summary>
/// Default constructor
/// </summary>
public NVelocityEngine()
{
base.Init();
}
/// <summary>
/// Renders a template by merging it with the context items
/// </summary>
public string Render(string template)
{
VelocityContext nvContext;
nvContext = new VelocityContext(context);
using (StringWriter writer = new StringWriter())
{
this.Evaluate(nvContext, writer, "template", template);
return writer.ToString();
}
}
}
}
In this way, you don't have to meddle with the view engine at all, and you can theoretically chain this with the ASP.NET view engine if you wanted, like I have done in the following controller method:
public ActionResult ViewVoucher(string e)
{
e = e.Replace(' ', '+');
var decryptedEmail = CryptoHelper.Decrypt(e);
var voucher = Voucher.FindByEmail(decryptedEmail);
if (voucher == null) return View("Error", new Exception("Voucher not found."));
var basePath = new Uri(Request.Url, Url.Content("~/")).ToString();
var termsLink = new Uri(Request.Url, Url.Action("TermsGC", "Legal")).ToString();
basePath = basePath.Substring(0, basePath.Length - 1);
var fullName = voucher.FirstName;
if (!string.IsNullOrEmpty(voucher.LastName))
fullName += " " + voucher.LastName;
var nvEngine = new NVelocityEngine();
nvEngine.Context.Add("FullName", fullName);
nvEngine.Context.Add("MallName", voucher.Mall.Name);
nvEngine.Context.Add("ConfirmationCode", voucher.ConfirmationCode);
nvEngine.Context.Add("BasePath", basePath);
nvEngine.Context.Add("TermsLink", termsLink);
nvEngine.Context.Add("LogoFilename", voucher.Mall.LogoFilename);
var htmlTemplate = System.IO.File.ReadAllText(
Request.MapPath("~/App_Data/Templates/Voucher.html"));
return Content(nvEngine.Render(htmlTemplate));
}
Try using spark view engine (http://www.sparkviewengine.com/). It is easy to use, nicer than standard engine and doesn't require to fake context.
You can also use function from this answer Render a view as a string , but it requires faking context. This is the way standard view engine works and you can do nothing about that.
This is my extension class that is used to generate views to string. First is for standard view engine, second for Spark:
public static class ControllerHelper
{
/// <summary>Renders a view to string.</summary>
public static string RenderViewToString(this Controller controller,
string viewName, object viewData)
{
//Getting current response
var response = HttpContext.Current.Response;
//Flushing
response.Flush();
//Finding rendered view
var view = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName).View;
//Creating view context
var viewContext = new ViewContext(controller.ControllerContext, view,
controller.ViewData, controller.TempData);
//Since RenderView goes straight to HttpContext.Current, we have to filter and cut out our view
var oldFilter = response.Filter;
Stream filter = new MemoryStream(); ;
try
{
response.Filter = filter;
viewContext.View.Render(viewContext, null);
response.Flush();
filter.Position = 0;
var reader = new StreamReader(filter, response.ContentEncoding);
return reader.ReadToEnd();
}
finally
{
filter.Dispose();
response.Filter = oldFilter;
}
}
/// <summary>Renders a view to string.</summary>
public static string RenderSparkToString(this Controller controller,
string viewName, object viewData)
{
var view = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName).View;
//Creating view context
var viewContext = new ViewContext(controller.ControllerContext, view,
controller.ViewData, controller.TempData);
var sb = new StringBuilder();
var writer = new StringWriter(sb);
viewContext.View.Render(viewContext, writer);
writer.Flush();
return sb.ToString();
}
}
If you want simple text replacements, .NET has something for that:
ListDictionary replacements = new ListDictionary();
// Replace hard coded values with objects values
replacements.Add("{USERNAME}", "NewUser");
replacements.Add("{SITE_URL}", "http://yourwebsite.com");
replacements.Add("{SITE_NAME}", "My site's name");
string FromEmail= "from#yourwebsite.com";
string ToEmail = "newuser#gmail.com";
//Create MailDefinition
MailDefinition md = new MailDefinition();
//specify the location of template
md.BodyFileName = "~/Templates/Email/Welcome.txt";
md.IsBodyHtml = true;
md.From = FromEmail;
md.Subject = "Welcome to youwebsite.com ";
System.Web.UI.Control ctrl = new System.Web.UI.Control { ID = "IDontKnowWhyThisIsRequiredButItWorks" };
MailMessage message = md.CreateMailMessage(ToEmail , replacements, ctrl);
//Send the message
SmtpClient client = new SmtpClient();
client.Send(message);
And the Welcome.txt file
Welcome - {SITE_NAME}<br />
<br />
Thank you for registering at {SITE_NAME}<br />
<br />
Your account is activated and ready to go! <br />
To login, visit {SITE_NAME} and use the following credentials:
<br />
username: <b>{USERNAME}</b><br />
password: use the password you registered with
<br />
<br />
- {SITE_NAME} Team
Again, this is only good for simple string replacements. If you plan emailing more data, you would need to format it properly then replace it.
You can consider using MvcMailer NuGet - it does just what you are looking for and does it cleanly. See the NuGet package here and the project documentation
Hope it helps!
I created an overload to LukLed's RenderSparkToString method that allows you to use a spark layout along with your view:
public static string RenderSparkToString(this Controller controller,
string viewName, string masterName, object viewData)
{
var view = ViewEngines.Engines.FindView(controller.ControllerContext, viewName, masterName).View;
//Creating view context
var viewContext = new ViewContext(controller.ControllerContext, view,
controller.ViewData, controller.TempData);
var sb = new StringBuilder();
var writer = new StringWriter(sb);
viewContext.View.Render(viewContext, writer);
writer.Flush();
return sb.ToString();
}
I agree with Andrew though. I wish there was an easier way to do this with the web forms view engine.
Although this is a little old thread, I would encourage if you to take a look at the MvcMailer NuGet package - it simplifies the whole thing greatly and makes the mailer behave li
Related
I have following problem: I wrote a HTML-Code which shows a variable List as table when I press the Button. I did so far, that the table get showed in PDF in a Print Service. But the table has some sort functions and on a PDF one can't use them. So I try to show the HTML as a Website in Webview in the App, where one can use the function. The code for the code behind is the following:
public ICommand PrintCommand => new AsyncCommand(Print);
private async Task Print()
{
// New up the Razor template and set the model property
var printTemplate = new ListPrintTemplate
{
Model = FilteredList,
};
// Generate the HTML
var htmlString = printTemplate.GenerateString();
// Create a source for the webview
var htmlSource = new HtmlWebViewSource{ Html = htmlString };
// Create and populate the Xamarin.Forms.WebView
var browser = new WebView { Source = htmlSource };
var printService = Xamarin.Forms.DependencyService.Get<IPrintService>();
printService.Print(browser, $"{Res.Probe}-{FilteredList}");
}
I have still the printService in the last lines. I looked over the libraries and the Microsoft documentation but can't help myself.
In my ElasticSearch server, I have an existing index template, which contains some settings and some mappings.
I want to add a mapping for a new type to the template, but since it's not possible to update templates, I need to delete the existing one and recreate it.
Since I would like to keep all the existing settings, I tried getting the existing definition, add the mapping to it and then delete/recreate, like in this example:
using Nest;
using System;
public class SomeNewType {
[ElasticProperty(Index = FieldIndexOption.NotAnalyzed)]
public string SomeField { get; set; }
[ElasticProperty(Index = FieldIndexOption.Analyzed)]
public string AnotherField { get; set; }
}
class Program {
static void Main(string[] args) {
var settings = new ConnectionSettings(uri: new Uri("http://localhost:9200/"));
var client = new ElasticClient(settings);
var templateResponse = client.GetTemplate("sometemplate");
var template = templateResponse.TemplateMapping;
client.DeleteTemplate("sometemplate");
// Add a mapping to the template somehow...
template.Mappings.Add( ... );
var putTemplateRequest = new PutTemplateRequest("sometemplate") {
TemplateMapping = template
};
client.PutTemplate(putTemplateRequest);
}
}
However, I cannot find a way to add a mapping to the template definition using the ElasticProperty attributes, like in
client.Map<SomeNewType>(m => m.MapFromAttributes());
Is it possible to create a RootObjectMapping to add to the Mappings collection with something similar to MapFromAttributes?
You can do this by using the more robust PutMappingDescriptor to get a new RootObjectMapping, then add that into the collection returned by your GET _template request, like so:
var settings = new ConnectionSettings(uri: new Uri("http://localhost:9200/"));
var client = new ElasticClient(settings);
var templateResponse = client.GetTemplate("sometemplate");
var template = templateResponse.TemplateMapping;
// Don't delete, this way other settings will stay intact and the PUT will override ONLY the mappings.
// client.DeleteTemplate("sometemplate");
// Add a mapping to the template like this...
PutMappingDescriptor<SomeNewType> mapper = new PutMappingDescriptor<SomeNewType>(settings);
mapper.MapFromAttributes();
RootObjectMapping newmap = ((IPutMappingRequest) mapper).Mapping;
TypeNameResolver r = new TypeNameResolver(settings);
string mappingName = r.GetTypeNameFor(typeof(SomeNewType));
template.Mappings.Add(mappingName, newmap);
var putTemplateRequest = new PutTemplateRequest("sometemplate")
{
TemplateMapping = template
};
var result = client.PutTemplate(putTemplateRequest);
Note: TypeNameResolver is in the Nest.Resolvers namespace
As noted in the comment above, I recommend that you NOT delete the old template if the mappings are the only thing that needs updated, otherwise you will need to copy all of the other relevant settings into your new request.
I'm trying to send parameters with ActionAsPdf using the Rotativa Library (1.6.4), unfortunately, the function is called but the parameter trainee in it is always null.
Here's my code:
List<T_Trainee> trainee= new List<T_Trainee>();
foreach (int f in foo)
{
T_Trainee t = new T_Trainee();
t.email = (string)Session["Mail"];
t.phone = (string)Session["Phone"];
trainee.Add(t);
}
//code to get the PDF
ActionAsPdf pdf = new ActionAsPdf("Index", trainee) { FileName = "Bulletin.pdf" };
Trainee var is a list of object T_Trainee not null -> seen in debug:
//function that return the PDF
public ActionResult Index(List<T_Trainee> trainee)
{
ViewModelFoo vmc = new ViewModelFoo();
vmc.trainee = trainee;
return View(vmc);
}
When the function is call in debug mode, I can clearly see that the parameter "trainee" is null but I still don't understand why.
Can anyone help me? Thanks!
ActionAsPdf seems to be a deprecated function in the last version of Rotativa.
I changed it by ViewAsPdf and now it works. The difference between the two function is that you have to send directly the view model inside the Index method call with ViewAsPdf.
Here's my code, I hope that it will help someone :
Code to call the index and send the viewModel
ViewModelFoo vmc = new ViewModelFoo();
List<T_Trainee> trainees= new List<T_Trainee>();
foreach (int f in foo)
{
T_Trainee t = new T_Trainee();
t.email = (string)Session["Mail"];
t.phone = (string)Session["Phone"];
trainees.Add(t);
}
vmc.trainees = trainees;
//code to get the PDF
ViewAsPdf pdf = new ViewAsPdf("Index", vmc)
{
FileName = "File.pdf",
PageSize = Rotativa.Options.Size.A4,
PageMargins = { Left = 0, Right = 0 }
};
Index that generate the view
public ActionResult Index()
{
return View(vmc);
}
Is foo populated?
Try sample code...
List trainee= new List();
trainee.Add(new T_Trainee {email = "sample#email.com", phone = "555-1212"});
Does that work?
You can also try to bind Action to a model
public ActionResult Index(ViewModelFoo vmc)
The second parameter of ActionAsPdf() is type of RouteValueDictionary, which is a dictionary of key and value. You passed in a custom type thereby it converts it to null. It should work if you pass a RouteValueDictionary instead.
ViewAsPdf() receives an object parameter and treats it as a model for view binding that's why it works.
You can have a look at its source code here:
https://github.com/webgio/Rotativa/blob/master/Rotativa/ActionAsPdf.cs
I am writing code to test an C# MVC3 application. I can test the controllers but how do I test the code in the views? This includes javascript and razor styled code.
Are there any tools available which can mock out views or test views and javascript in C# in them?
The following is about testing the rendered output of the view. This textual output can for instance be loaded into a DOM for further analysis with XPath (using XmlReader for XHTML or HtmlAgilityPack for SGML-style HTML). With some nice helper methods this allows easy check for checking specific parts of the view, such as testing //a[#href='#'] or anything else which you want to test. This helps making the unit tests more stable.
One would expect that this is easy when using Razor instead of the "blown-up" WebForms engine, but it turned out to be quite the contrary, due to many inner workings of the Razor view engine and views using parts (HtmlHelper especially) of the HTTP request lifecycle. In fact, properly testing the generated output requires a lot of running code to get reliable and appropriate results, even more so if you use exotic stuff such as portable areas (from the MVCContrib project) and the like in the mix.
The HTML helpers for actions and URLs require that the routing is properly initialized, the route dictionary is properly set up, the controller must also exist, and there are other "gotchas" related to loading data for the view, such as setting up the view data dictionary...
We ended up creating a ViewRenderer class which will actually instantiate an application host on the physical path of your web to be tested (can be statically cached, re-initialization for each single test is impractical due to the initialization lag):
host = (ApplicationHost)System.Web.Hosting.ApplicationHost.CreateApplicationHost(typeof(ApplicationHost), "/", physicalDir.FullName);
The ApplicationHost class in turn inherits from MarshalByRefObject since the host will be loaded in a separate application domain. The host performs all sorts of unholy initialization stuff in order to properly initialize the HttpApplication (the code in global.asax.cs which registers routes etc.) while disabling some aspects (such as authentication and authorization). Be warned, serious hacking ahead. Use at your own risk.
public ApplicationHost() {
ApplicationMode.UnitTesting = true; // set a flag on a global helper class to indicate what mode we're running in; this flag can be evaluated in the global.asax.cs code to skip code which shall not run when unit testing
// first we need to tweak the configuration to successfully perform requests and initialization later
AuthenticationSection authenticationSection = (AuthenticationSection)WebConfigurationManager.GetSection("system.web/authentication");
ClearReadOnly(authenticationSection);
authenticationSection.Mode = AuthenticationMode.None;
AuthorizationSection authorizationSection = (AuthorizationSection)WebConfigurationManager.GetSection("system.web/authorization");
ClearReadOnly(authorizationSection);
AuthorizationRuleCollection authorizationRules = authorizationSection.Rules;
ClearReadOnly(authorizationRules);
authorizationRules.Clear();
AuthorizationRule rule = new AuthorizationRule(AuthorizationRuleAction.Allow);
rule.Users.Add("*");
authorizationRules.Add(rule);
// now we execute a bogus request to fully initialize the application
ApplicationCatcher catcher = new ApplicationCatcher();
HttpRuntime.ProcessRequest(new SimpleWorkerRequest("/404.axd", "", catcher));
if (catcher.ApplicationInstance == null) {
throw new InvalidOperationException("Initialization failed, could not get application type");
}
applicationType = catcher.ApplicationInstance.GetType().BaseType;
}
The ClearReadOnly method uses reflection to make the in-memory web configuration mutable:
private static void ClearReadOnly(ConfigurationElement element) {
for (Type type = element.GetType(); type != null; type = type.BaseType) {
foreach (FieldInfo field in type.GetFields(BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.DeclaredOnly).Where(f => typeof(bool).IsAssignableFrom(f.FieldType) && f.Name.EndsWith("ReadOnly", StringComparison.OrdinalIgnoreCase))) {
field.SetValue(element, false);
}
}
}
The ApplicationCatcher is a "null" TextWriter which stores the application instance. I couldn't find another way to initialize the application instance and get it. The core of it is pretty simple.
public override void Close() {
Flush();
}
public override void Flush() {
if ((applicationInstance == null) && (HttpContext.Current != null)) {
applicationInstance = HttpContext.Current.ApplicationInstance;
}
}
This now enables us to render out almost any (Razor) view as if it were hosted in a real web server, creating almost a full HTTP lifecycle for rendering it:
private static readonly Regex rxControllerParser = new Regex(#"^(?<areans>.+?)\.Controllers\.(?<controller>[^\.]+)Controller$", RegexOptions.CultureInvariant|RegexOptions.IgnorePatternWhitespace|RegexOptions.ExplicitCapture);
public string RenderViewToString<TController, TModel>(string viewName, bool partial, Dictionary<string, object> viewData, TModel model) where TController: ControllerBase {
if (viewName == null) {
throw new ArgumentNullException("viewName");
}
using (StringWriter sw = new StringWriter()) {
SimpleWorkerRequest workerRequest = new SimpleWorkerRequest("/", "", sw);
HttpContextBase httpContext = new HttpContextWrapper(HttpContext.Current = new HttpContext(workerRequest));
RouteData routeData = new RouteData();
Match match = rxControllerParser.Match(typeof(TController).FullName);
if (!match.Success) {
throw new InvalidOperationException(string.Format("The controller {0} doesn't follow the common name pattern", typeof(TController).FullName));
}
string areaName;
if (TryResolveAreaNameByNamespace<TController>(match.Groups["areans"].Value, out areaName)) {
routeData.DataTokens.Add("area", areaName);
}
routeData.Values.Add("controller", match.Groups["controller"].Value);
ControllerContext controllerContext = new ControllerContext(httpContext, routeData, (ControllerBase)FormatterServices.GetUninitializedObject(typeof(TController)));
ViewEngineResult engineResult = partial ? ViewEngines.Engines.FindPartialView(controllerContext, viewName) : ViewEngines.Engines.FindView(controllerContext, viewName, null);
if (engineResult.View == null) {
throw new FileNotFoundException(string.Format("The view '{0}' was not found", viewName));
}
ViewDataDictionary<TModel> viewDataDictionary = new ViewDataDictionary<TModel>(model);
if (viewData != null) {
foreach (KeyValuePair<string, object> pair in viewData) {
viewDataDictionary.Add(pair.Key, pair.Value);
}
}
ViewContext viewContext = new ViewContext(controllerContext, engineResult.View, viewDataDictionary, new TempDataDictionary(), sw);
engineResult.View.Render(viewContext, sw);
return sw.ToString();
}
}
Maybe this helps you to get to some results. In general many people say that the hassle of testing views is not worth the effort. I'll let you be the judge of that.
Check out vantheshark's article, it describes how to mock the ASP.NET MVC View engine using NSubstitute.
I have an aspx page that contains regular html, some uicomponents, and multiple tokens of the form {tokenname} .
When the page loads, I want to parse the page content and replace these tokens with the correct content. The idea is that there will be multiple template pages using the same codebehind.
I've no trouble parsing the string data itself, (see named string formatting, replace tokens in template) my trouble lies in when to read, and how to write the data back to the page...
What's the best way for me to rewrite the page content? I've been using a streamreader, and the replacing the page with Response.Write, but this is no good - a page containing other .net components does not render correctly.
Any suggestions would be greatly appreciated!
Take a look at System.Web.UI.Adapters.PageAdapter method TransformText - generally it is used for multi device support, but you can postprocess your page with this.
I'm not sure if I'm answering your question, but...
If you can change your notation from
{tokenname}
to something like
<%$ ZeusExpression:tokenname %>
you could consider creating your System.Web.Compilation.ExpressionBuilder.
After reading your comment...
There are other ways of getting access to the current page using ExpressionBuilder: just... create an expression. ;-)
Changing just a bit the sample from MSDN and supposing the code of your pages contain a method like this
public object GetData(string token);
you could implement something like this
public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
{
Type type1 = entry.DeclaringType;
PropertyDescriptor descriptor1 = TypeDescriptor.GetProperties(type1)[entry.PropertyInfo.Name];
CodeExpression[] expressionArray1 = new CodeExpression[1];
expressionArray1[0] = new CodePrimitiveExpression(entry.Expression.Trim());
return new CodeCastExpression(
descriptor1.PropertyType,
new CodeMethodInvokeExpression(
new CodeThisReferenceExpression(),
"GetData",
expressionArray1));
}
This replaces your placeholder with a call like this
(string)this.GetData("tokenname");
Of course you can elaborate much more on this, perhaps using a "utility method" to simplify and "protect" access to data (access to properties, no special method involved, error handling, etc.).
Something that replaces instead with (e.g.)
(string)Utilities.GetData(this, "tokenname");
Hope this helps.
Many thanks to those that contributed to this question, however I ended up using a different solution -
Overriding the render function as per this page, except I parsed the page content for multiple different tags using regular expressions.
protected override void Render(HtmlTextWriter writer)
{
if (!Page.IsPostBack)
{
using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
{
using (System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(stream))
{
HtmlTextWriter htmlWriter = new HtmlTextWriter(streamWriter);
base.Render(htmlWriter);
htmlWriter.Flush();
stream.Position = 0;
using (System.IO.StreamReader oReader = new System.IO.StreamReader(stream))
{
string pageContent = oReader.ReadToEnd();
pageContent = ParseTagsFromPage(pageContent);
writer.Write(pageContent);
oReader.Close();
}
}
}
}
else
{
base.Render(writer);
}
}
Here's the regex tag parser
private string ParseTagsFromPage(string pageContent)
{
string regexPattern = "{zeus:(.*?)}"; //matches {zeus:anytagname}
string tagName = "";
string fieldName = "";
string replacement = "";
MatchCollection tagMatches = Regex.Matches(pageContent, regexPattern);
foreach (Match match in tagMatches)
{
tagName = match.ToString();
fieldName = tagName.Replace("{zeus:", "").Replace("}", "");
//get data based on my found field name, using some other function call
replacement = GetFieldValue(fieldName);
pageContent = pageContent.Replace(tagName, replacement);
}
return pageContent;
}
Seems to work quite well, as within the GetFieldValue function you can use your field name in any way you wish.