ASP.NET MVC Render View to a string for emailing - asp.net-mvc

I want to use MVC views to create the body for an email and I have come across this (http://www.brightmix.com/blog/renderpartial-to-string-in-asp-net-mvc/) but it doesn't seem to work with strongly typed views (ViewContext is null). But I was after something the would render a full view including a masterpage.
I thought that if there was a way of just invoking a view with out redirecting and just writing to a different stream and sending the email within the controller that might do it, but I can't work out how to invoke a view.
Any suggestions would be great!
Thanks in advance.
Gifster

The question has been asked (and answered) already:
Render View as a String
This is the bit that I use:
protected string RenderViewToString<T>(string viewPath, T model, System.Web.Mvc.ControllerContext controllerContext) {
using (var writer = new StringWriter()) {
var view = new WebFormView(viewPath);
var vdd = new ViewDataDictionary<T>(model);
var viewCxt = new ViewContext(controllerContext, view, vdd, new TempDataDictionary(), writer);
viewCxt.View.Render(viewCxt, writer);
return writer.ToString();
}
}
Best place for this method is in a class library project that your mvc project has a reference to. Mainly because that way you can easily reuse it in all your apps. But also because it is neither Application logic (so doesn't belong in the controller) nor does it belong in the model. Some things are just utilities.
Note that to get this to work, the viewPath parameter HAS to be the PHYSICAL PATH to the file, complete with the .aspx extension. You can't use a route as WebFormView class requires a physical path in its constructor.
This will render the full view and take account of the master page.
HEALTH WARNING FOR HTML EMAILS:
HTML emails and the devices where you read them are even more difficult and restrictive to design for than websites and browsers. What works in one, will not work in another. So with html emails, you really have to Keep It Simple! Your lovely page with menus and relative images and whatever else, JUST WON'T WORK in all email devices. Just as an example, the images src attribute needs to be absolute and include the domain:
This won't work:
<img src="/Images/MyImage.gif" ... />
Bit this will:
<img src="http://www.mywebsite.com/Images/MyImage.gif" ... />
With those caveats, it works fine and I use it. Just don't try to send them the full gimmickry of your website, cos that won't work!
Even more important:
All CSS must be INLINE and just for basic styling: colours, borders, padding. But no floating and positioning. CSS layouts won't work consistently across devices!

You can use MvcView NuGet package to do this! Its simple, just install using install-package MvcMailer and you are done! Use full power of view engine to template, master page, view model and send html emails neatly!

Related

dynamically create partial view (CSHTML)

Is it possible in ASP.NET MVC 5 to dynamically create a partial view (cshtml) in the /Views/Shared directory? I have a situation where people are going to upload a bunch of HTML as strings and was thinking it would be better for performance to store them on the file system.
Is it as simple as creating a new file, steaming a string and saving?
Yes it is possible
Simply make a view like DynamicView.cshtml
#model DynamicView
#Html.Raw(Model.HTMLString)
Now the method you will use to store both the HTML and the pointer to it is a different story. You can either store the sanitized HTML in a data-base and retrieve it with a call to the Controller like
public ActionResult DynamicView(ind id)
{
DynamicView model = new DynamicView();
DynamicView.HTMLString = dbContext.HTMLViews.Where(v => v.id == id);
return View(model);
}
If you wish to write the submitted HTML to files instead, you can instead do
public ActionResult DynamicView(string filePath)
{
DynamicView model = new DynamicView();
DynamicView.HTMLString = ...code that reads file
return View(model);
}
See this related post Writing/outputting HTML strings unescaped
Quick Answer: No (you cannot/should not modify Views Folder at RunTime.)
ASP.NET MVC Views and Shared Views are meant to be compiled and run. Modifying them or Adding new ones as the Application is running is not at all advisable or practical.
What would make more sense is for you to store the uploaded html blobs in a database, file system, storage blob (cloud). Then code your Shared View or Specific Views to retrieve specific html blobs from the stored location depending upon which user is logged in.
There are a whole lot of Extension Functions in MVC that Enable you to insert partial html in views. take a look at PartialExtensions as an starting point.
I had a requirement to create top menu which will generate from DB. I tried a lot to create it using partial view. But i found most of the cases partial view is use for static content. at last i fond some helpful tutorial please find the following i hope it will help you too.
click here to see how to create Dynamic Partial view

How to Use ViewData and ViewBag with Umbraco Surface Controllers

I have just spent 2 hours trying to work out why when I put a string in to View.Bag/ViewData inside my Surface controller, when I try and get the string back in the view I get null.
In the end I have solved the problem by putting the string in to a session variable insted.
Would like to know though why it wasn't working, and how to fix it.
Thanks in advance.
Update: Are you posting and redirecting? When you refresh the form does it prompt you about posting again? If not, it's because you have accidentally followed the best practice of 302ing from a form post (prevents a user refreshing and reposting form data). The examples I was following for login surface controllers all used return RedirectToCurrentUmbracoPage() which I blindly followed. But, as the name implies that really is doing a redirect and it is really two requests! (I stubbornly had to verify in Fiddler before I believed it). ViewData and ViewBag are only good for one request--so they are fundamentally broken in a POST 302. Session is good for multiple requests which is why it worked for you. TempData will work for you too, because as it turns out, TempData is a construct that is built on top of session and was specifically designed to carry state between two posts (removed on retrieve). I read somewhere that TempData would have been better named RedirectData and that helped it click for me.
So when you're dealing with Surface Controllers and POSTing you have three options that I know work:
Session (which you proved worked)
TempData (which is built on session, and from what I've read is both best practice and built specifically for this situation)
Use return CurrentUmbracoPage(); in your form post. I just verified in Fiddler that this is exactly one request (refreshing in the browser prompts a repost warning). I also verified that ViewData works this way. But, because the surface controller is rendered as a Child Action using #Html.Action(...) you have to use ParentActionViewContext to get at the right ViewData (my first answer which I'll leave for others that find this question).
Original answer is still useful when there is no redirect involved (GET or a POST that returns CurrentUmbracoPage())...
In many cases you're actually making a child action. Usually you're only one level deep but if you mix macros and partials you can actually get multiple levels deep. There is a ViewData for each level and you have to walk your way up the stack with ParentActionViewContext to get to the top ViewData that you populated in your controller.
See this comment from Shannon in answer to a question about surface controllers and viewdata (Shannon is a core contributor on the HQ team and has a lot of great content out there). Quoting here:
If you want to access the ViewData that you've set on the master ViewContext's on a ChildAction being rendered from the master's ViewContext then you need to use #ViewContext.ParentActionViewContext.ViewData["ErrorMessage"]
The ParentActionViewContext in this example is the ViewContext that is rendering the Umbraco template, not the ChildAction. That is because when you POST (whether inside of Umbraco or normal MVC), you are posting to a new Action and the rendering process starts from scratch, when you validate your model, update the ViewData, etc... this all happens on what will become the 'master' ViewContext when the view renders. This view then will render your ChildAction.
Twamley's answer above is excellent, in addition to this, I have found that using TempData.Add(key, value) works nicely.
An bare bones would look like:
SurfaceController
public class MyController : Umbraco.Web.Mvc.SurfaceController
{
public MyController()
{}
public ActionResult DoSomething()
{
// surface controller does something
// get a page by it's document/model type alias
var umbracoHelper = new UmbracoHelper(UmbracoContext.Current);
var node = umbracoHelper.TypedContentSingleAtXPath("//" + "Home")
TempData.Add("Message", "This value will be passed through");
return redirectToUmbracoPage(node);
}
}
View
#inherits UmbracoTemplatePage
#{
Layout = null;
}
#if (TempData.ContainsKey("Message"))
{
<p>#TempData["Message"]</p>
}
http://localhost/umbraco/Surface/My/DoSomething

Find MVC template view by name

I am writing an HtmlHelper extension and I need to search for the existence of a template by name. The template in question may be a display or editor template depending on the context. My initial thought was to use ViewEngines.Engines.FindPartialView method. However, it appears that this method is not searching the ~/Views/Shared/DisplayTemplates and ~/Views/Shared/EditorTemplates directories.
I suppose this is for good reason. After all, how would the ViewEngine know whether to return the display or editor template without some additional information of context?
So, that leads to the question: how can I search for a specific EditorTemplate/DisplayTemplate I've considered adding a custom view engine to the ViewEngines collection to include these locations. I'm concerned, however, that this might be problematic.
My main concern is that the DisplayTemplate/EditorTemplate view might be served up for something unintended. Does anyone else see this as a problem?
Is it a better idea just to new up a specific DisplayTemplateViewEngine/EditorTemplateViewEngine instance when necessary and keep the ViewEngines collection clear of this specific functionality?
Is there something else I'm missing?
I absolutely love that the MVC framework is open source! I was able to determine, from the TemplateHelpers class (internal to the MVC Runtime) that the DataBoundControlMode is considered when rendering a template. The answer was simple! All I have to do is prefix the template name with the appropriate template director. So, to find a display template:
var metadata = ModelMetadata.FromLambdaExpression(expression, HtmlHelper.ViewData);
ViewEngines.Engines.FindPartialView(
_controllerContext,
string.Format("DisplayTemplates/{0}", metadata.TemplateHint))
No additional view engines or routing required! In case you're interested in the application, my helper is auto-generating UI components for a given model. I wanted to enable the existence of a custom template to bypass the automated rendering.
A WebFormViewEngine has a few properties that define (patterns for) locations to search for views.
You either follow the convention of the view engine you use, or create a custom view engine (that for examlpe extends Razor) with custom view paths.
The latter is explained here:
public class CustomViewEngine : RazorViewEngine
{
public CustomViewEngine()
{
var viewLocations = new[] {
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/DisplayTemplates/{0}.cshtml",
"~/Views/Shared/DisplayTemplates/{1}/{0}.cshtml",
// etc
};
this.PartialViewLocationFormats = viewLocations;
this.ViewLocationFormats = viewLocations;
}
}
So I guess in your helper you should look up the current view engine and look up its view location paths and search them in order. Doesn't an Html helper have a method or property for getting the view you're currently running in?
Why you just map the relative path
string path = Server.MapPath("~/View/");
And then check if the file exits base on the .cshtml exit's in that specific directory
string fileName = "MyView.cshtml";
if (File.Exists(path + fileName))
//do somethings
else
//do another things

Serving HTML or ASPX files with ASP.NET MVC

I want to implemented URL like :
www.domain.com/Business/Manufacturing/Category/Product1
This will give the details of specific product(such as specifications).
Since the list of Categories & Products in each Category are limited, I thought it is not worth to use database for products.
So I want to implement it using the HTML or ASPX files. Also I want the URL to be without the files extention(i.e. URL's should not have .html or .aspx extension)
How can I implement it with ASP.NET MVC? Should I use nested folder structure with HTML/ASPX files in it, so that it corresponds to URL? Then how to avoid extensions in URL?
I am confused with these thoughts
Asp.net Mvc uses the routing library from Microsoft. So it is very easy to get this kind of structure without thinking about the folder structure or the file extensions. With asp.new mvc you do not point a request at a specific file. Instead you point at a action that handles the request and use the parameters to determine what to render and send to the client. To implement your example you can do something like this:
_routes.MapRoute(
"Product",
"Business/Manufacturing/Category/Product{id}",
new {controller = "Product", action = "Details", id = ""}
);
This route will match the url you described and execute the action named "Details" on a controller named "ProductController" (if you are using the default settings). That action can look something like this:
public ActionResult Details(int id) {
return View(string.Format("Product{0}", id);
}
This action will then render views depending on what id the product have (the number after "Product" in the end of your example url). This view should be located in the Views/Product folder if you use the default settings. Then if you add a view named "Product1.aspx" in that folder, that is the view that will be rendered when you visit the url in your example.
All tough it is very possible to do it that way I would strongly recommend against it. You will have to do a lot of duplicated work even if you only have a few products and use partial views in a good way to minimize the ui duplications. I would recommend you use a database or some other kind of storage for you products and use a single view as template to render the product. You can use the same route for that. You just edit your action a little bit. It can look something like this:
public ActionResult Details(int id) {
var product = //get product by id from database or something else
return View(product);
}
This way you can strongly type your view to a product object and you will not have that much duplication.
The routing engine is very flexible, and when you have played around with it and learned how it works you will be able to change your url in just about any way you want without changing any other code or moving any files.
If you're not ready to dive into ASP.Net MVC, you can still get the nice URLs by using URL Rewriting for ASP.Net. That'd be simpler if you're already familiar with ASP.Net WebForms. The MSDN article on URL Rewriting should be a good start:
http://msdn.microsoft.com/en-us/library/ms972974.aspx
I'd be really sure you won't eventually have more products before deciding not to use a database. Both MVC and WebForms would allow you to make one page that dyamically shows a product and still have a nice URL- plus you might save yourself development time down the road. Something to think about.

Where should the browser types be segregated in an ASP.NET mobile MVC application?

I am building what will primarily be a mobile browser targeted ASP.NET MVC application. Although it will target the desktop as well in a smaller capacity. (I'm fairly new to both MVC and mobile apps.)
I'm wondering what is the best practice for segregating mobile vs. desktop users in an MVC application.
Should the controller be in charge of checking for browser type? Or, should this type of functionality be reserved for the View?
If checked in the view, can & should a masterpage do the checking? Do you know of any good examples online?
Update:
I just discovered an overload of the View method that accepts a string argument specifying the Masterpage to be used.
For example:
public ActionResult Index()
{
if (isMobile())
return View("Index", "Mobile", myObject);
else
return View("Index", myObject);
}
To me this suggests that at least a few people on the Microsoft team expect major distinctions (such as mobile vs. desktop) to be carried out in the controller. (There's a good chance I'm highly confused about this.)
I think the controller must know the plataform, because you can get a lot of views in distint languages, Some view for browsers (mobile) another view in Desktop App, another view can be a web service, and all views can have different needs.
If you have a few views, you can call views with parameters to mark the type of view:
Index(mobile) and Index(Desktop) as it:
Index(string typeOfApp){
//prepare data, do querys, etc
if (typeOfApp=='Mobile'){
redirectoAction('IndexMobile',ds);
//or
return view('IndexMobile',ds)
}
return View('IndexDesktop',ds);
}
IndexMobile(DataSet ds){}
IndexDesktop(DataSet ds){}
You can get a general method for your action() and another action for every type,
Index -> Index4Mobile & Index4Browser & Index4Desktop
And in all of this methods prepare or do something special for every plataform, or a Single Action with multiple views(1 for plataform).
Any code dealing with rendering of your code should exist on the page itself via CSS and javascript. Your controllers should know nothing about how your data will be rendered on screen. The views shouldn't really even know anything about it either - they only expose the data that your CSS will render.
The HTML your View spits out describes your data and how it is organized. Only the CSS should know how to make it look appropriate for whatever device is rendering it.
This link, chock full of javascript should help determine which mobile browser is running.

Resources