In MVC Project I dynamically render a view to string using the following code.
public string RenderViewToString(string viewName, object model, RequestContext requestContext){
ViewData.Model = model;
var cContext = ControllerContext;
if (cContext == null)
{
var ctrlFactory = ControllerBuilder.Current.GetControllerFactory();
var ctrl = ctrlFactory.CreateController(requestContext, "Documents") as Controller;
var newCContext = new ControllerContext(requestContext, ctrl);
cContext = newCContext;
var ctrlDesc = new ReflectedControllerDescriptor(ctrl.GetType());
}
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(cContext, viewName);
var viewContext = new ViewContext(cContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(cContext, viewResult.View);
return sw.GetStringBuilder().ToString();
}
}
This works fine until multiple users run the code at the same time. It then gets each users' view data mixed up.
Any suggestions why? Thanks
I had the same problem with similar code. I spend a couple of days figuring this one out.
After reading loads around this, containing suggestions like 'write your own viewRenderer' and so on, it all seemed to complex to solve this problem.
After diving into the .net code itself, I found a dirty trick that that fixes this
Add a couple of lines to your ConfigureServices (startup):
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddTransient<IViewRenderer, ViewRenderer>();
// reason for this registration https://github.com/aspnet/Mvc/issues/5106 . A scoped buffer causes overflow.
var memoryPoolViewBufferScopeType = Type.GetType("Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers.MemoryPoolViewBufferScope, Microsoft.AspNetCore.Mvc.ViewFeatures");
if (memoryPoolViewBufferScopeType != null)
{
services.AddTransient(typeof(IViewBufferScope), memoryPoolViewBufferScopeType);
}
It makes the IViewRenderer and IViewBufferScope transient, which solves the problem.
The reason the IViewBufferScope is added this way, is because it's an internal, so some trickery is needed to get to it.
Related
I'm trying to test a custom model binder. As part of that process I am creating a new ModelBindingContext to pass into the BindModel method of the custom binder. The problem is that I need to set the MetaData property of the ModelBindingContext as a CachedDataAnnotationsModelMetadata object, but honestly am not sure how to instantiate the object.
The signature for the CachedDataAnnotationsModelMetadata object is:
public CachedDataAnnotationsModelMetadata(
CachedDataAnnotationsModelMetadata prototype,
Func<object> modelAccessor
)
Does anyone have an example of what the prototype and modelAccessor parameters are supposed to be?
Here's a snippet of the non-functional code.
// Assemble
var formCollection = new List<KeyValuePair<string, string>> {
new KeyValuePair<string, string> ("SomeRequiredProperty", "SomeValue")
};
var valueProvider = new System.Web.Http.ValueProviders.Providers.NameValuePairsValueProvider(formCollection, null);
var metadata = new System.Web.Http.Metadata.Providers.CachedDataAnnotationsModelMetadata(????, ????)
var bindingContext = new ModelBindingContext {
ModelName = "ClaimsModelBinderInputModel",
ValueProvider = valueProvider,
ModelMetadata = metadata
};
var actionContext = new HttpActionContext();
var httpControllerContext = new HttpControllerContext();
httpControllerContext.Request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/someUri");
actionContext.ControllerContext = httpControllerContext;
var cmb = new ClaimsModelBinder();
// Act
cmb.BindModel(actionContext, bindingContext);
// Assert...
I've found a few examples around the net and SO of people interacting with this class, but no concrete example of implementing it.
Thanks in advance!
Update
Figured out ModelAccessor, it's just used to delay accessing the actual model until the model property is accessed. https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http/Metadata/ModelMetadata.cs#L103
I'm still working on supplying a prototype object if anyone can help with that.
So I ended up sidestepping this issue entirely.
After inspecting the inheritance model, I realized that in this case I can get away with using the DataAnnotationsModelMetadataProvider rather than the CachedDataAnnotationsModelMetadata. The latter of these doesn't require the parameters necessary to setup caching, and since I don't need caching to test what I am testing, the standard DataAnnotationsModelMetadataProvider will suffice.
I was able to identify that I could take a different approach after reading through the source code for the metadata namespace.
https://github.com/ASP-NET-MVC/aspnetwebstack/tree/master/src/System.Web.Http/Metadata
That said, if anyone else can provide some clues to my original question, I would prefer to use the cached model object if possible.
I have found a few other questions like this, however every single one has not been able to fix this problem I am having. I continuously get the same problem.
I have checked out this and that and multiple others! Every single thing I get the same problem.
I get the following exception:
The view at '~/Views/Email/Ticket.cshtml' must derive from WebViewPage, or WebViewPage<TModel>.
The code I am using is:
public static string RenderViewToString(string controllerName, string viewName, object viewModel)
{
HttpContextWrapper context = new HttpContextWrapper(HttpContext.Current);
RouteData routeData = new System.Web.Routing.RouteData();
routeData.Values.Add("controller", controllerName);
ControllerContext controllerContext = new ControllerContext(context, routeData, new RenderController());
RazorViewEngine razorViewEngine = new RazorViewEngine();
ViewEngineResult razorViewResult = razorViewEngine.FindView(controllerContext, viewName, string.Empty, false);
using (StringWriter writer = new StringWriter())
{
ViewDataDictionary viewData = new ViewDataDictionary(viewModel);
var viewContext = new ViewContext(controllerContext, razorViewResult.View, viewData, new TempDataDictionary(), writer);
razorViewResult.View.Render(viewContext, writer);
writer.Flush();
return writer.GetStringBuilder().ToString();
}
}
The RenderController is just:
private class RenderController : ControllerBase
{
protected override void ExecuteCore()
{
}
}
No matter what I have done, or different methods I have tried nothing seems to make this error go away, I have tried making the view inherit the objects but you can't have #model and #inherit in the same thing. Getting quite aggravated over this as I have followed pretty much everyone's instructions on other questions/posts but hasn't been able to help me.
Thanks to #JustinRusso he helped figure this out!
What seemed to happen was, my refernce to MVC 5.0.0.0 in my actual web project was different to my MVC version that I was referencing in my Common library which was at 4.0.0.1.
After I updated the version it worked. I feel stupid now looking back on it but that is what learning is all about!
Thanks to all for the help and suggestions!
I don't think you can mark comments as answers (at least from what I can tell) so just making it a bigger item so future people can find it.
I am trying to pass my model List< Models.Statement > statementList from one action to another but i am receiving null value in the 2nd controller. Please suggest what is wrong here. Even tried with:
return RedirectToAction("WriteInTemplate", new { statementList = statementList });
Please help.
public ActionResult SendPdfStatement(string InvoiceNumber)
{
try
{
InvoiceNumber = InvoiceNumber.Trim();
ObjectParameter[] parameters = new ObjectParameter[1];
parameters[0] = new ObjectParameter("InvoiceNumber", InvoiceNumber);
List<Models.Statement> statementList = new List<Models.Statement>();
statementList = _db.ExecuteFunction<Models.Statement>("uspInvoiceStatement", parameters).ToList<Models.Statement>();
//WriteInTemplate(statementList);
return RedirectToAction("WriteInTemplate", statementList );
}
catch (Exception e)
{
InvoiceSearchTool.Models.udtExceptionTable exception = new udtExceptionTable();
exception.MethodName = "SendPdfStatement";
exception.Exception = e.ToString();
exception.Date = DateTime.Now;
DYNAMICS_EXTEntities db = new DYNAMICS_EXTEntities();
db.AddToudtExceptionTables(exception);
db.SaveChanges();
return View("Error");
}
}
public ActionResult WriteInTemplate(List<Models.Statement> statementList)
{
try
{
string invoiceNumber = statementList.FirstOrDefault().Invoice.ToString().Trim();
...................snip..........
return RedirectToAction("CreateMessageWithAttachment", "email", invoiceNumber);
}
catch (Exception e)
{
InvoiceSearchTool.Models.udtExceptionTable exception = new udtExceptionTable();
exception.MethodName = "WriteInTemplate";
exception.Exception = e.ToString();
exception.Date = DateTime.Now;
DYNAMICS_EXTEntities db = new DYNAMICS_EXTEntities();
db.AddToudtExceptionTables(exception);
db.SaveChanges();
return View("Error");
}
}
Please take a look here to pass your Model
you are not passing "statementList" , instead you are passing new { statementList= statementList} just pass the model and you should be fine .
return RedirectToAction("WriteInTemplate", statementList);
Answer by sino
RedirectToAction() writes a redirect command to the browser, making it start a brand new request to WriteInTemplate(). Your model object is therefore lost.
Is WriteInTemplate() an independent action which will sometimes be responsible for an entire request from a user or a partial request from a view? If not, you should just call it as a regular method instead of using RedirectToAction().
This is because you had spefified wrong route parameters.
while thinking about this.. did you check that the data are not null?
you are using
return RedirectToAction("WriteInTemplate", statementList );
instead you should use
return RedirectToAction("WriteInTemplate","controllerName", new{"statementList"=stetementList});
see reference here
The way you call the RedirectToAction() method may not be your issue.
For me, the solutions presented above did not work because the RedirectToAction() method builds a RouteValueDictionary by using the .ToString() value of each property in the model. This will only work if all the properties in the model are simple properties and it fails if any properties are complex objects, lists, collections, etc.
because this method does not use recursion.
If for example, a model called MymodelOrganization contained a property List Employees, then that property would result in a query string value of
....&Employees=System.Collections.Generic.List'1[System.String]
and binding would fail, and you would end up (as was my case) with ... null
I had this problem, so I created a copy of my model containing only the elements of the form, stripping my Lists and passed that inside RedirectToAction().
Once on the other action method, I was able to re-assemble my Lists and added them to my Model before calling the last return. Good luck. Here is the idea in my code:
[HttpPost]
public ActionResult ItemSubmissionForm(CombinedModelContent membervalues)
{ ...
ItemSubmissionsDBFields aFieldsList = membervalues.FieldsList; //Stripping other objects
return RedirectToAction("ItemSubmissionConfirm", aFieldsList);
}
[HttpGet]
public ActionResult ItemSubmissionConfirm(ItemSubmissionsDBFields aFieldsList)
{ ...
List<SomeArea> SomeAreaitems = new List<SomeArea>();
SomeAreaitems.Add ...
CombinedModelContent copymembervalues = new CombinedModelContent();
copymembervalues.SomeCodeLists = SomeAreaitems;
copymembervalues.FieldsList = aFieldsList;
return View("SomeConfirmPage", copymembervalues);
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.
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