We are developing an e-commerce system that multiple affiliate partners will use. We would like to tailor the portal for each partner and be able to accommodate slight variations in content from page to page. Our current technique has been to create a copy of a .cshtml view for each partner and make the customization to each view. Our designer is groaning because may of these views only have slight variations in wording. We only plan to have 10 or so partners (it cannot expand beyond that because of the size of our industry) so a full blown CMS system is overkill.
I would like to use resx files manage content strings for each partner the way one would use them to manage content strings for different languages. The end result would be the ability to do something like this in a view.
Please contact customer service at #Properties.Resources.PartnerCustomerServiceEmail
at not have to worry about which resource file is used to resolve the string PartnerCustomerServiceEmail.
Thank you in advance for your help
First idea that comes to my mind is to save resource file's name in question into viewdata (or Session) and use a helper to get the value.
Say you have two partners: Foo Logistics and Bar Solutions. Have a resource file for each of them: PartnerFoo.resx and PartnerBar.resx.
In your controller, store the resource file you want to use into ViewData as in:
public ActionResult About()
{
...
ViewData["Resource"] = "MyMVCAppNamespace.Resources.PartnerFoo";
return View();
}
Include the namespace into the string too.
Then code in the helper to retrieve the resource with viewdata.
Helpers/Helper.cs:
namespace MyMVCAppNamespace.MvcHtmlHelpers
{
public static class HtmlHelpersExtensions
{
public static ResourceManager PartnerResource(this HtmlHelper helper)
{
// Get resource filename from viewdata
string res = helper.ViewContext.ViewData["Resource"].ToString();
// Load the resource from assembly
ResourceManager resourceManager = new ResourceManager(res, Assembly.GetExecutingAssembly());
return resourceManager;
}
}
}
Now in the view, we are gonna use this helper to retrieve the string we want to write:
About.cshtml:
#Html.PartnerResource().GetString("PartnerName")
#Html.PartnerResource().GetString("PartnerCustomerServiceEmail")
Which gets rendered as:
Foo Logistics service#foologistics.com
or with PartnerBar
Bar Solutions service#barsolutions.com
We determine the resource file to use before the view is loaded. Then in view it gets dynamically rendered according to what resource is stored in to the viewdata. You can even store the resource filename into web.config and load the string in helper from there if you want.
What's even more cool is that if you have localized resx file, say PartnerFoo.fi.resx and then use different culture (fi-FI in this case), the resourcemanager automatically looks up the localized version.
You can even do simple branding by storing image URLs and whatnot in the resource file.
It's simple really, but I hope it gets you started.
Related
So I want to have a status page that will show a different layout of the equipment depending on who's using it which will be a variable defined in the web.config. Should I be creating a separate controller per view? Some of the background functions should be similar but some will probably be different in the future. Or should I have the same cshtml file and hide html markup depending on who's using it or not?
I was thinking of doing something like:
#if(System.Configuration.ConfigurationManager.AppSettings["IsSuperUser"] == "true")
{
Status
}
else {
Status
}
Or is this a bad idea?
There are several options, it all depends on your needs and preferences.
Your code will work, however you must also double check permission in your controller! For example, your url will be "/SuperUser/Status" and "/User/Status". Now, what's stopping non-super user to type in "/SuperUser/Status" to the address bar?
One important rule, never trust the end users! Assume that they will not do what you intend them to do.
Given all, my preference would be to include a variable in your Model to identify the user level (super vs non super), then use that to determine the layout in your views. Remember, you can also change the layout of the view based on variable/expression.
#Layout = Model.IsSuperUser ? "_SuperLayout.cshtml" : "_RegularLayout.cshtml";
Sounds like a view concern. I would pass the config data through a dependency in the controller and render partials:
#if (Model.IsSuperUser)
{
#Html.Partial("_SuperUser")
}
else
{
#Html.Partial("_User")
}
The controller can then do something like:
public ActionResult Index()
{
var vm = new MyViewModel();
vm.IsSuperUser = _config.GetSuperUser();
return View(vm);
}
I have a C# MVC Razor site. Typically, Controllers load views from the Views folder. However, I have a special circumstance where I need to render a view outside of the Views folder. How do I do that?
Controller will load /Views/Random/Index.cshtml
Can't load /Random/Index.cshtml
/Random/test.aspx loads with no issues, but can't change cshtml files to aspx files, they need to be built regularly.
I have tried return Redirect("/Random/Index.cshtml") in the Controller, and currently have no controller at all.
The weird thing is it works on my Production environment, but not in localhost. In localhost I get:
The type of page you have requested is not served because it has been explicitly forbidden. The extension '.cshtml' may be incorrect. Please review the URL below and make sure that it is spelled correctly.
Requested URL: /Random/Index.cshtml
You can definitely do this. For doing this you need to create one new custom view engine like
public class MyViewEngine : RazorViewEngine
{
private static string[] AdditionalViewLocations = new[]{
"~/Random/{0}.cshtml"
};
public MyViewEngine()
{
base.PartialViewLocationFormats = base.PartialViewLocationFormats.Union(AdditionalViewLocations).ToArray();
base.ViewLocationFormats = base.ViewLocationFormats.Union(AdditionalViewLocations).ToArray();
base.MasterLocationFormats = base.MasterLocationFormats.Union(AdditionalViewLocations).ToArray();
}
}
Then in you global.asax's Application_Start method register this view engine like this-
ViewEngines.Engines.Add(new MyViewEngine ());
If you want your viewengine to take precedence then insert this at 0th position. like this -
ViewEngines.Engines.Insert(0, new MyViewEngine());
return View("~/AnotherFolder/Index.cshtml")` should work for you.
Do not forget to indicate the Layout in your index view:
#{
Layout="~/Views/Shared/Layout.cshtml";
}
I was wondering what's best practice for a javascript file's location that is view-specific.
Some of my views have 1700+ lines of code, 70% of it being javascript, so I'm putting them into a .js file to take advantage of bundling/minifying.
I know core script files (eg. jquery) typically go into the root /Scripts folder, but there are a LOT of views that i need to do this to (50+), so I'm wondering if I should put them in their respective view folders (eg: /Views/Account/ViewName.js) or should i dump them all into the /Scripts folder at the root.
In my practices, I've basically made the following static assets structure:
public
-- css
-- javascript
---- controllers
---- lib
-- otherAssets
And using some controller logic, I add a file per controller to /public/javascript/controllers so that way my controller specific code is all bundled together.
You could just as easily rig it to be per model, and then have object oriented methods in your js files like this:
myModel = {
pageA: function() {
//stuff
},
pageB: function() {
//stuff
}
};
doing it this way would mean that you'd need a parent javascript file that basically finds the proper javascript model object (i.e. myModel), then controller method (pageA) and then calls that function.
Anything more complex than that really starts bringing you into the territory of you needing a javascript assets manager like require.js
I'm looking to create a common ASP.MVC (c# and razor) base project containing common controllers, _Layout.cshtml, css and js for many of our webapps to extend from.
I thought that using MvcContrib and creating Portable Areas is my best bet
So the folder setup is roughly like this
BaseProj
Content
js
plugins
misc
images
foo
css
Controllers
Views
MainProj
Content
Controllers
Views
I am registering the BaseProj area by extending PortableAreaRegistration class (as per MvcContrib docs) so this url works...
htttp://localhost/MainProj/BaseProj/
Looking in the MvcContrib code for PortableAreaRegistration it also registers 3 static routes too (images, styles, scripts) under Content.
Therefore this url works fine too...
htttp://localhost/MainProj/BaseProj/images/bar.jpg
also subfolders work fine only if i use a dot instead of a slash...
htttp://localhost/MainProj/BaseProj/images/foo.bar.jpg
However its really useful to have subfolders under images, css, js etc for organisation purposes. These subfolders will now 404 though
ie this will not work....
htttp://localhost/MainProj/BaseProj/images/foo/bar.jpg
So the question is how do i map subfolders under images (without the dot notation)?
ie so this works....
htttp://localhost/MainProj/BaseProj/images/foo/bar.jpg
Thanks
EDIT:
Well a bit of thinking and I found a solution. I wonder if this kind of thing might be included in MvcContrib.Portable Areas by default.
First create a catch all route in your BaseProjRegistration.cs to catch all subfolders of Content
public override void RegisterArea(AreaRegistrationContext context, IApplicationBus bus)
{
...
//static controller with catch all for all subfolders of content
context.MapRoute(
"BaseProjContent",
"BaseProj/Content/{*resourceName}",
new { controller = "Content", action = "LoadContent", resourcePath = "Content" }
);
...
}
Then Create a ContentController class in your BaseProj which will extend MvcContrib.PortableAreas.EmbeddedResourceController.
This will convert slashes to dots to load the BaseProj content from the DLL
ie /BaseProj/Content/images/foo/bar.jpg => /BaseProj/Content/images.foo.bar.jpg
public class ContentController : MvcContrib.PortableAreas.EmbeddedResourceController
{
public ActionResult LoadContent(string resourceName, string resourcePath)
{
string actualResourceName = resourceName.Replace("/", ".");
return base.Index(actualResourceName, resourcePath);
}
}
This worked for me, but any other recommnedations of how to setup a common base project for lots of webapps to extend are welcome
In Controller it's easy to access the virtual path you need to access like:
Server.MapPath(#"~\App_Data\blah\blah")
This give you access to AppData folder, but if I want to access them in Models, how can I acccess the virtual path in MVC 3?
How can I access my app_data folder in Models of my application ?
If i were you, rather than figuring out how to access the current execution path, I wouldn't break my App layers and pass it as an argument to my model
Your model should not access it - get the controller to provide the data needed.
As Aliostad said, you should have the Controller access it, the model should only hold model data. So here are 2 ways to use the controller to access it.
If virtual folder is in the root of the web application. (If you have to drill down further, just add more parameters to the path combine until you get to your folders location.)
string folderPath = Server.MapPath(System.IO.Path.Combine(Request.ApplicationPath, "VirtualFolderName"));
For a more re-usable solution i created a Extension for the Controller class:
using System.IO;
using System.Web.Mvc;
namespace Extensions {
public static class ControllerExtensions {
public static string ResolveVirtualFolderPath(this Controller controller, string folder_name) => controller?.HttpContext?.Server?.MapPath(Path.Combine(controller?.HttpContext?.Request?.ApplicationPath, folder_name));
}
}
Then in the Controller put the using statement so you can access the extension
using static Extensions.ControllerExtensions;
then you can do this in the controller:
string folderPath = this.ResolveVirtualFolderPath("VirtualFolderNameHere");
You could break the extension down to not use the null check operator "?" and do if null and then handle each situation like the folder does not exist, or maybe access to location is not allowed or whatever else your needs may be.