I have an ASP.NET app which needs to have multiple layouts for the views. There's a normal web layout, and a "static" layout for a self-contained page with no dependencies (to be used as a display template in a doc management system).
It's easy enough to have a view switch its layout.
So the all-in-one-page layout needs to include all of its CSS in a <style> tag inside the page. I found a way to find out what files are in a bundle and wrote a method like so:
#MyCode.IncludeCSS("~/StaticLayout/css")
It reads the CSS files in the bundle and emits their content into the page inside a <style> tag.
But I'd like to minify the CSS.
I haven't been able to find any documentation for System.Web.Optimization.CssMinify. You need to call the Process method, which needs a BundleContext. So... find one, create one? MSDN isn't much use:
public BundleContext(
HttpContextBase context,
BundleCollection collection,
string bundleVirtualPath
)
Parameters
context
Type: System.Web.HttpContextBase
The context.
Well, that's definitely useful. I had no way of knowing that context (of type context) was the context until some bulging-brained colossus of the intellect in Redmond took some time out from his busy nap to push a definite article in front of it with his fat little paws before passing out in a puddle of warm drool.
Nevertheless, I still don't know what it is or where you get one from, and neither does Google. HttpContext.Current is not derived from it and has no properties (that I can find) which are.
This page has some good information about normal bundling, but it assumes that the framework will be doing the bundling and serving the bundle content in the conventional way, as a separately requested resource.
Has anybody done this?
Ed Plunkett,
If you are using this as a HTML helper the HttpContextBase can be grabbed from the ViewContext.HttpContext property. This can be created such as.
public static IHtmlString BundleCss(this HtmlHelper html, string outputPath, params string[] cssFilePaths)
{
var collection = new BundleCollection();
foreach (var cssFilePath in cssFilePaths)
collection.Add(new Bundle(cssFilePath));
var bundler = new BundleContext(html.ViewContext.HttpContext, collection, outputPath);
//... ommitted code
System.Web.Optimization.CssMinify minify = new CssMinify();
minify.Process(bundler, response);
//... TODO: Grab the response content and return
}
Yes this is a basic example using a HTML helper. Let me know if this does't answer your question.
EDIT: After re-reading the question I am clarifying my response. So the above helps find the HttpContextBase property from the question. However I think the question is how you can actually read the content of the files, minify them and out put them into a <style> tag on the page.
I took a stab at writing my own interpretation of your requirement and came up with the following set of classes
My CssOutputBundler class:
public class CssOutputBundler
{
static readonly Dictionary<string, string> cachedOutput = new Dictionary<string, string>();
readonly string tempFileOutputPath;
readonly HtmlHelper helper;
readonly IList<string> virtualFilePaths;
public CssOutputBundler(HtmlHelper helper, string tempFileOutputPath)
{
if (helper == null)
throw new ArgumentNullException("helper null");
if (string.IsNullOrWhiteSpace(tempFileOutputPath))
this.tempFileOutputPath = tempFileOutputPath;
this.helper = helper;
this.virtualFilePaths = new List<string>();
this.tempFileOutputPath = tempFileOutputPath;
}
public CssOutputBundler Add(string cssFilePath)
{
if (!this.virtualFilePaths.Contains(cssFilePath))
this.virtualFilePaths.Add(cssFilePath);
return this;
}
public IHtmlString Minify()
{
if (helper == null)
throw new ArgumentNullException("helper null");
string cache_string = string.Join(",", this.virtualFilePaths);
if(cachedOutput.ContainsKey(cache_string))
return formatResponse(File.ReadAllText(cachedOutput[cache_string]));
var bundle = new StyleBundle(this.tempFileOutputPath).Include(this.virtualFilePaths.ToArray());
var collection = new BundleCollection();
collection.Add(bundle);
var context = new BundleContext(helper.ViewContext.HttpContext, collection, "");
var response = bundle.GenerateBundleResponse(context);
System.Web.Optimization.CssMinify minify = new CssMinify();
minify.Process(context, response);
string serverPath = helper.ViewContext.HttpContext.Server.MapPath(this.tempFileOutputPath);
string outputPath = Guid.NewGuid().ToString() + ".css";
while(File.Exists(Path.Combine(serverPath, outputPath)))
outputPath = Guid.NewGuid().ToString() + ".css";
File.WriteAllText(outputPath, response.Content);
cachedOutput[cache_string] = outputPath;
return formatResponse(response.Content);
}
IHtmlString formatResponse(string responseContent)
{
StringBuilder responseBuilder = new StringBuilder();
responseBuilder.AppendLine("<style type=\"text/css\">");
responseBuilder.Append(responseContent);
responseBuilder.AppendLine("</style>");
return helper.Raw(responseBuilder.ToString());
}
}
Ok the above class looks complex but all it does is creates an internal list of paths for the bundler to to bundle and minify. When the Minify method is called it formats the virtual paths into a string which is used as a cache key. This is how we determine if we have already bundled and minified these scripts.
If we have we read from the temporary bundled script that is stored on the HDD.
Note: I used hard drive storage as opposed to in memory storage. This can be easily changed by stuffing the full content into a static dictionary or other cacheing medium.
If this file set has not been bundled, minified etc then we proceed to bundle and minify the set. Finally we store the file onto the HDD and return the response. So this is pretty easy.
Now to implement the class I just wrote a quick HTML Helper as the Minification class supports chaining for Add files methods can be implemented nicely.
The HTML Helper:
public static CssOutputBundler BundleCss(this HtmlHelper helper, string outputVirtualPath)
{
return new CssOutputBundler(helper, outputVirtualPath);
}
Ok basic implementation for the bundler. Now this helper takes the string outputVirtualPath this is the virtual PATH to store the temporary files. If you do decide to eliminate saving to HDD and use a cache system you could remove this parameter from the helper and the class.
Finally this can be used in your view as...
#Html.BundleCss("~/Content/").Add("~/Content/Site.css").Add("~/Content/themes/base/jquery-ui.css").Minify();
Where the ~/Content/ is the virtual path on the HDD to store the minified files. Then we add two files "~/Content/Site.css" and ~/Content/themes/base/jquery-ui.css. Finally call the minify method which returns the html string. Such as...
<style type="text/css">
.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)} ....more css
</style>
Cheers.
Related
I'm trying to implement an RSS/Atom feed aggregator in spring-integration and I am primarily using the Java DSL to write my IntegrationFlow. A requirement of this aggregator is that feeds can be added / removed during runtime. That is to say, the feeds are not known at design time.
I found it simple to use the basic Feed.inboundAdapter() with a test url and extract the links out of the feed with a transformer and then pass it on to an outbound-file-adapter to save the links to a file. However, I have gotten very stuck when trying to read the (thousands) of feed urls from an inbound-file-adapter run the file through a FileSplitter and then pass each resulting Message<String> containing the feed url to then register a new Feed.inboundAdapter(). Is this not possible with the Java DSL?
Ideally I would love it if I could do the following:
#Bean
public IntegrationFlow getFeedsFromFile() throws MalformedURLException {
return IntegrationFlows.from(inboundFileChannel(), e -> e.poller(Pollers.fixedDelay(10000)))
.handle(new FileSplitter())
//register new Feed.inboundAdapter(payload.toString()) foreach Message<String> containing feed url coming from FileSplitter
.transform(extractLinkFromFeedEntry())
.handle(appendLinkToFile())
.get();
}
Though after reading through the spring integration java DSL code multiple times (and learning a tonne of stuff along the way) I just can't see that it's possible to do it this way. So... A) is it? B) should it be? C) Suggestions?
It almost feels like I should be able to take the output of .handle(new FileSplitter()) and pass that into .handleWithAdapter(Feed.inboundAdapter(/*stuff here*/)) but the DSL only references outbound-adapters there. Inbound adapters are really just a subclass of AbstractMessageSource and it seems the only place you can specify one of those is as an argument to the IntegrationFlows.from(/*stuff here*/) method.
I would have thought it would be possible to take the input from a file, split it line by line, use that output to register inbound feed adapters, poll those feeds, extract the new links from feeds as they appear and append them to a file. It appears as though it's not.
Is there some clever subclassing I can do to make this work??
Failing that... and I suspect this is going to be the answer, I found the spring integration Dynamic Ftp Channel Resolver Example and this answer on how to adapt it dynamically register stuff for the inbound case...
So is this the way to go? Any help/guidance appreciated. After pouring over the DSL code and reading documentation for days, I think I'll have a go at implementing the dynamic ftp example and adapting it to work with FeedEntryMessageSource... in which case my question is... that dynamic ftp example works with XML configuration, but is it possible to do it with either Java config or the Java DSL?
Update
I've implemented the solution as follows:
#SpringBootApplication
class MonsterFeedApplication {
public static void main(String[] args) throws IOException {
ConfigurableApplicationContext parent = SpringApplication.run(MonsterFeedApplication.class, args);
parent.setId("parent");
String[] feedUrls = {
"https://1nichi.wordpress.com/feed/",
"http://jcmuofficialblog.com/feed/"};
List<ConfigurableApplicationContext> children = new ArrayList<>();
int n = 0;
for(String feedUrl : feedUrls) {
AnnotationConfigApplicationContext child = new AnnotationConfigApplicationContext();
child.setId("child" + ++n);
children.add(child);
child.setParent(parent);
child.register(DynamicFeedAdapter.class);
StandardEnvironment env = new StandardEnvironment();
Properties props = new Properties();
props.setProperty("feed.url", feedUrl);
PropertiesPropertySource pps = new PropertiesPropertySource("feed", props);
env.getPropertySources().addLast(pps);
child.setEnvironment(env);
child.refresh();
}
System.out.println("Press any key to exit...");
System.in.read();
for (ConfigurableApplicationContext child : children) {
child.close();
}
parent.close();
}
#Bean
public IntegrationFlow aggregateFeeds() {
return IntegrationFlows.from("feedChannel")
.transform(extractLinkFromFeed())
.handle(System.out::println)
.get();
}
#Bean
public MessageChannel feedChannel() {
return new DirectChannel();
}
#Bean
public AbstractPayloadTransformer<SyndEntry, String> extractLinkFromFeed() {
return new AbstractPayloadTransformer<SyndEntry, String>() {
#Override
protected String transformPayload(SyndEntry payload) throws Exception {
return payload.getLink();
}
};
}
}
DynamicFeedAdapter.java
#Configuration
#EnableIntegration
public class DynamicFeedAdapter {
#Value("${feed.url}")
public String feedUrl;
#Bean
public static PropertySourcesPlaceholderConfigurer pspc() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public IntegrationFlow feedAdapter() throws MalformedURLException {
URL url = new URL(feedUrl);
return IntegrationFlows
.from(s -> s.feed(url, "feedTest"),
e -> e.poller(p -> p.fixedDelay(10000)))
.channel("feedChannel")
.get();
}
}
And this works IF and only IF I have one of the urls defined in application.properties as feed.url=[insert url here]. Otherwise it fails telling me 'unable to resolve property {feed.url}'. I suspect what is happening there is that the #Beans defined in DynamicFeedAdapter.java all get singletons eagerly initialized, so aside from the beans being manually created in our for loop in the main method (which work fine because they have feed.url property injected) we have a stray singleton that has been eagerly initialized and if there is no feed.url defined in application.properties then it can't resolve the property and everything goes bang. Now from what I know of Spring, I know it should be possible to #Lazy initialize the beans in DynamicFeedAdapter.java so we don't wind up with this one unwanted stray singleton problem-child. The problem is now...if I just mark the feedAdapter() #Lazy then the beans never get initialized. How do I initialize them myself?
Update - problem solved
Without having tested it, I think the problem is that boot is finding
the DynamicFeedAdapter during its component scan. A simple solution is
to move it to a sibling package. If MonsterFeedApplication is in
com.acme.foo, then put the adapter config class in com.acme.bar. That
way, boot won't consider it "part" of the application
This was indeed the problem. After implementing Gary's suggestion, everything works perfect.
See the answer to this question and its follow up for a similar question about inbound mail adapters.
In essence, each feed adapter is created in a child context that is parameterized.
In that case the child contexts are created in a main() method but there's no reason it couldn't be done in a service invoked by .handle().
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.
I am just learning actionscript, so come across the problem
In my application I often call to different web services, and because I don't want to hardcode urls to them in my code, I am passing urls to the services as flashvars.
Currently I am doing it this way:
public var siteUrl:String;
public var gameId:String;
public function main():void
{
siteUrl = Application.application.parameters.siteurl;
gameId = Application.application.parameters.gameid;
Where main is a function, which is called on application's creation complete event.
This way I can call both variables from main file of the application but I want to access them from other files. (other as classes)
So is there a way to create class with constants and init values there with flashvars so I can use them everywhere (after importing of course)
The parameters are just stored in that Application.application.parameters object, and that's static. There's no reason you couldn't access that from other classes in your code.
If you want to write a class that wraps the parameters (maybe validates them or something) you could do that fairly easily. You can use a for each loop to loop over all the parameters. Something like:
var params:Object = Application.application.parameters
for(var name:String in params) {
var value:String = params[name] as String;
/* do something with the param */
}
If you want your class to actually verify things then it could just check for each parameter it expects and store it in a local variable.
It really just depends on your own preferences. Some people are fine with accessing the parameters object when they need it. Some people like having the extra code-completion by having a config class that actually defines all the expected config variables.
Update in response to comment:
Instead of having one module declare the variable and have other modules have to depend on that one to access the property it would be cleaner to have a single config module that everything that needs it would all use.
You could use a static class or singleton or some IoC stuff. Just for simplicity I'll show you a way you can do it with a static class.
class MyConfig {
private static var _infoService:String;
private static var _someOtherParam:int;
public static function get infoService():String { return _infoService; }
public static function get someOtherParam():int { return _someOtherParam; }
public static function initParams():Void {
var params:Object = Application.application.parameters;
_infoService = params.infoservice;
// just assuming you have a method to convert here. don't remember the
// code off the top of my head
_someOtherParam = convertToInt(params.someOtherParam);
}
}
Make sure when your app initializes it calls MyConfig.initParams(). You can have that method actually validate that it gets everything it expects and throw exceptions (or return an error) if there's a failure if you want.
Then wherever you need to use that config within your code you just import your config class and access the param. So getting infoService would just be:
var infoService:String = MyConfig.infoService;
Personally I wouldn't use a static class, but it was the easiest to show.
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();
}
}
How would one get resx resource strings into javascript code stored in a .js file?
If your javascript is in a script block in the markup, you can use this syntax:
<%$Resources:Resource, FieldName %>
and it will parse the resource value in as it renders the page... Unfortunately, that will only be parsed if the javascript appears in the body of the page. In an external .js file referenced in a <script> tag, those server tags obviously never get parsed.
I don't want to have to write a ScriptService to return those resources or anything like that, since they don't change after the page is rendered so it's a waste to have something that active.
One possibility could be to write an ashx handler and point the <script> tags to that, but I'm still not sure how I would read in the .js files and parse any server tags like that before streaming the text to the client. Is there a line of code I can run that will do that task similarly to the ASP.NET parser?
Or does anyone have any other suggestions?
Here is my solution for now. I am sure I will need to make it more versatile in the future... but so far this is good.
using System.Collections;
using System.Linq;
using System.Resources;
using System.Web.Mvc;
using System.Web.Script.Serialization;
public class ResourcesController : Controller
{
private static readonly JavaScriptSerializer Serializer = new JavaScriptSerializer();
public ActionResult GetResourcesJavaScript(string resxFileName)
{
var resourceDictionary = new ResXResourceReader(Server.MapPath("~/App_GlobalResources/" + resxFileName + ".resx"))
.Cast<DictionaryEntry>()
.ToDictionary(entry => entry.Key.ToString(), entry => entry.Value.ToString());
var json = Serializer.Serialize(resourceDictionary);
var javaScript = string.Format("window.Resources = window.Resources || {{}}; window.Resources.{0} = {1};", resxFileName, json);
return JavaScript(javaScript);
}
}
// In the RegisterRoutes method in Global.asax:
routes.MapRoute("Resources", "resources/{resxFileName}.js", new { controller = "Resources", action = "GetResourcesJavaScript" });
So I can do
<script src="/resources/Foo.js"></script>
and then my scripts can reference e.g. window.Resources.Foo.Bar and get a string.
There's no native support for this.
I built a JavaScriptResourceHandler a while ago that can serve Serverside resources into the client page via objects where each property on the object represents a localization resource id and its value. You can check this out and download it from this blog post:
http://www.west-wind.com/Weblog/posts/698097.aspx
I've been using this extensively in a number of apps and it works well. The main win on this is that you can localize your resources in one place (Resx or in my case a custom ResourceProvider using a database) rather than having to have multiple localization schemes.
whereas "Common" is the name of the resource file and Msg1 is the fieldname. This also works for culture changes.
Partial Javascript...:
messages:
{
<%=txtRequiredField.UniqueID %>:{
required: "<%=Resources.Common.Msg1 %>",
maxlength: "Only 50 character allowed in required field."
}
}
In a nutshell, make ASP.NET serve javascript rather than HTML for a specific page. Cleanest if done as a custom IHttpHandler, but in a pinch a page will do, just remember to:
1) Clear out all the ASP.NET stuff and make it look like a JS file.
2) Set the content-type to "text/javascript" in the codebehind.
Once you have a script like this setup, you can then create a client-side copy of your resources that other client-side scripts can reference from your app.
If you have your resources in a separate assembly you can use the ResourceSet instead of the filename. Building on #Domenics great answer:
public class ResourcesController : Controller
{
private static readonly JavaScriptSerializer Serializer = new JavaScriptSerializer();
public ActionResult GetResourcesJavaScript()
{
// This avoids the file path dependency.
ResourceSet resourceSet = MyResource.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
// Create dictionary.
var resourceDictionary = resourceSet
.Cast<DictionaryEntry>()
.ToDictionary(entry => entry.Key.ToString(), entry => entry.Value.ToString());
var json = Serializer.Serialize(resourceDictionary);
var javaScript = string.Format("window.Resources = window.Resources || {{}}; window.Resources.resources = {1};", json);
return JavaScript(javaScript);
}
}
The downside is that this will not enable more than one resource-file per action. In that way #Domenics answer is more generic and reusable.
You may also consider using OutputCache, since the resource won't change a lot between requests.
[OutputCache(Duration = 3600, Location = OutputCacheLocation.ServerAndClient)]
public ActionResult GetResourcesJavaScript()
{
// Logic here...
}
http://www.asp.net/mvc/overview/older-versions-1/controllers-and-routing/improving-performance-with-output-caching-cs
I usually pass the resource string as a parameter to whatever javascript function I'm calling, that way I can continue to use the expression syntax in the HTML.
I the brown field application I'm working on we have an xslt that transforms the resx file into a javascript file as part of the build process. This works well since this is a web application. I'm not sure if the original question is a web application.
use a hidden field to hold the resource string value and then access the field value in javascript
for example :
" />
var todayString= $("input[name=TodayString][type=hidden]").val();
Add the function in the BasePage class:
protected string GetLanguageText(string _key)
{
System.Resources.ResourceManager _resourceTemp = new System.Resources.ResourceManager("Resources.Language", System.Reflection.Assembly.Load("App_GlobalResources"));
return _resourceTemp.GetString(_key);
}
Javascript:
var _resurceValue = "<%=GetLanguageText("UserName")%>";
or direct use:
var _resurceValue = "<%= Resources.Language.UserName %>";
Note:
The Language is my resouce name. Exam: Language.resx and Language.en-US.resx