ASP.NET MVC load Razor view from database - asp.net-mvc

ScottGu mentioned that we should be able to load a Razor view from a database (check the comments section), so does anybody have an example on how to do that?
Thanks.

You might want to check Pulling a View from a database rather than a file or Using VirtualPathProvider to load ASP.NET MVC views from DLLs
Taking the code from my previous question on the subject.
In your FileExists() method on the other page you replace the test code I have there with some db code that actually checks to see if the virtualPath has an entry in your database. Your database would look something like:
Views --tablename
Path --view's virtual path
SomeOtherValue
...and your call would then be something like
public class DbPathProvider : VirtualPathProvider {
public DbPathProvider() : base() {
}
public override bool FileExists(string virtualPath) {
Database db = new Database();
return db.Views.Any(w => w.Path == virtualPath);
}
public override VirtualFile GetFile(string virtualPath) {
return new DbVirtualFile(virtualPath);
}
}
And now we modify the DbVirtualFile
public class DbVirtualFile : System.Web.Hosting.VirtualFile {
public DbVirtualFile(string path) : base (path) {
}
public override System.IO.Stream Open() {
Database db = new Database();
return new System.IO.MemoryStream(
db.Views.Single(v => v.Path == this.VirtualPath));
}
}
The virtualPath doesn't have to correspond to a real filesystem if you don't want it to. You can override the functionality by implementing these two classes.
You can then register your new VirtualPathProvider in the global.asax like so
HostingEnvironment.RegisterVirtualPathProvider(new DbPathProvider());
I hope this better answers your question.

Related

I want to use session in the asp.net mvc controller constructor

I'm new to Mvc.
Sorry to my english. ^^
I have some question about asp.net MVC session in the controller.
The Scenario things that I want to do is like follows..
First of all, My development circumstance is entityframework and mvc3.
When Someone logged in each one has different database. So, Each has connect different database.
So, Each person has his own session value which is database connection string. So far so good.
I have simple database Repository and at the each repository's constructor can change database connection.
At controller which calls Repository class, I need session value. But As I know Controller's construction can't keep session value. right?
I want your good advice. Thanks in advance.
Code samples are below:
public class MasterRepository
{
DBEntities _db;
public MasterRepository(string con)
{
_db = new DBEntities(con);
}
}
public class TestController : Controller
{
private string con;
MasterRepository _db;
public TestController()
{
_db = new MasterRepository(Session["conn"].ToString()); // Session is null I want to solve this Part...
}
public ActionResult Index()
{
string con = Session["conn"].ToString(); // Session is assigned.
return View();
}
}
These should explain what's happening to cause Session to be null, and give you a few possible solution options:
Is ASP.NET MVC Session available at any point durign controller construction
Why my session variables are not available at construction of a Controller?
Session null in ASP.Net MVC Controller Constructors
I think you have missed out the "service" part of the controller - service - repository pattern:
http://weblogs.asp.net/fredriknormen/archive/2008/04/24/what-purpose-does-the-repository-pattern-have.aspx
But when you go down this path you will probably also need to learn IoC as well.
Then your code would look more like:
public class MasterRepository
{
public Foo GetAllFoo()
{
return ObjectContextManager.GetObjectContext().AsQueryable().ToList();
}
}
public class MasterService
{
MasterRepository _repository;
public MasterService(MasterRepository repository) // use IoC
{
_repository = repository;
}
public Foo GetAllFoo()
{
return _repository.GetAllFoo();
}
}
public class TestController : Controller
{
MasterService _service;
public TestController(MasterService service) // use IoC
{
_service = service;
}
public ActionResult Index()
{
var model _service.GetAllFoo();
return View(model);
}
}

Compile error with T4MVC generated code in a MVC 3 project

We are developing a web application with ASP.Net 4 & MVC 3 Framework. I've installed T4MVC through NuGet and all the Views, Controllers and static content are succesfully generated as strong types.
But, when I try to compile the project, it raises an error at generated file T4MVC.cs, which is:
'T4MVC_ViewResultBase.FindView(System.Web.Mvc.ControllerContext)':
return type must be 'System.Web.Mvc.ViewEngineResult' to match overridden member
'System.Web.Mvc.ViewResultBase.FindView(System.Web.Mvc.ControllerContext)'
This is the source code generated:
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class T4MVC_ViewResultBase : System.Web.Mvc.ViewResultBase,
IT4MVCActionResult
{
public T4MVC_ViewResultBase(string area, string controller, string action):
base() {
this.InitMVCT4Result(area, controller, action);
}
protected override void FindView(System.Web.Mvc.ControllerContext context){}
public string Controller { get; set; }
public string Action { get; set; }
public RouteValueDictionary RouteValueDictionary { get; set; }
}
The error says that:
protected override void FindView(System.Web.Mvc.ControllerContext context) { }
should be:
protected override ViewEngineResult
FindView(System.Web.Mvc.ControllerContext context) { }
But then it raises another compiling error, as this method should return code.
If we check the base class it inherits from, System.Web.Mvc.ViewResultBase, it actually declares FindView() with ViewEngineResult return type:
public abstract class ViewResultBase : ActionResult
{
...
protected abstract ViewEngineResult FindView(ControllerContext context);
}
Has anyone got this error? Has it something to do with MVC version, are we are using MVC 3?
Thanks a lot!
Sergi
I think I see the problem, and it is a T4MVC bug. But hopefully it's easy to work around.
Do you have a controller action that is declared to return a ViewResultBase? If so, can you change the return type to be ActionResult? Or alternatively you can change the return type to be whatever the concrete type is that you're returning (e.g. is it ViewResult)?
The T4MVC bug is that it doesn't correctly override non-void methods in ActionResult types.

Cannot resolve view of the parent controller

Create a controller:
public abstract class MyBaseController : Controller
{
public ActionResult MyAction(string id)
{
return View();
}
}
Than create another specific controller that inherit from MyBaseController:
public class MyController : MyBaseController
{
}
There is a view called MyAction.aspx in the Views/MyBaseController folder
Then, call MyController/MyAction method. Following exception will be generated:
The view 'MyAction' or its master
could not be found. The following
locations were searched:
~/Views/MyController/MyAction.aspx
~/Views/MyController/MyAction.ascx
~/Views/Shared/MyAction.aspx
~/Views/Shared/MyAction.ascx
Can I make MVC.NET to use the view from Views/MyBaseController folder?
you should wait for a more finesse answer but this work:
Create a new view engine based on the default one and override the FindViewMethod this way:
public class MyNewViewEngine : WebFormViewEngine
{
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
var type = controllerContext.Controller.GetType();
//Retrieve all the applicable views.
var applicableViews = from m in type.GetMethods()
where typeof(ActionResult).IsAssignableFrom(m.ReturnType) & m.Name == viewName
select m;
//Save the original location formats.
var cacheLocations = ViewLocationFormats;
var tempLocations = cacheLocations.ToList();
//Iterate over applicable views and check if they have been declared in the given controller.
foreach(var view in applicableViews)
{
//If not, add a new format location to the ones at the default engine.
if (view.DeclaringType != type)
{
var newLocation = "~/Views/" + view.DeclaringType.Name.Substring(0, view.DeclaringType.Name.LastIndexOf("Controller")) + "/{0}.aspx";
if (!tempLocations.Contains(newLocation))
tempLocations.Add(newLocation);
}
}
//Change the location formats.
ViewLocationFormats = tempLocations.ToArray();
//Redirected to the default implementation
var result = base.FindView(controllerContext, viewName, masterName, useCache);
//Restore the location formats
ViewLocationFormats = cacheLocations;
return result;
}
}
Add the new view engine:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new MyNewViewEngine());
RegisterRoutes(RouteTable.Routes);
}
}
hope this helps
You need to add it to shared because you are in the context of the subcontroller. If you want different behavior for different controllers, then you'll want to put a MyAction view in each of your subcontroller view folders.
To answer your question though, you probably could make it look in base controller folder, but it would require you to write your own request handler which looks in base controller folders. The default implementation only looks in the view folder for the current controller context, then it looks in the shared folder. It sounds like your view is shared however, so the shared folder seems like a good place for it anyway.
It is possible, but not very clean.
public class MyController : MyBaseController
{
public ActionResult MyAction(string id)
{
return View("~/Views/MyBaseController/MyAction.aspx");
}
}
However if your View (MyAction.aspx) contains a reference to a Partial View, ASP.NET MVC will look for it in the Views/MyController folder (and not find it there!).
If your view is shared across controllers, its best to place it in the Views/Shared folder as recommended by NickLarsen.

Reference problems when using VirtualPathProvider to dynamically load a view

I have the following set of classes that I used to dynamically load in a View. The code below works well when called with .RenderPartial.
public class VirtFile:VirtualFile
{
public VirtFile(string virtualPath) : base(virtualPath)
{
}
public override Stream Open()
{
string path = this.VirtualPath;
Stream str = new MemoryStream();
StreamWriter writer = new StreamWriter(str);
writer.Write(#"<%# Control Language=""C#"" Inherits=""System.Web.Mvc.ViewUserControl"" %>
<%="Test"%>
");
writer.Flush();
str.Position = 0;
return str;
}
}
public class Provider:VirtualPathProvider
{
public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
{
return null;
var dependency = new System.Web.Caching.CacheDependency(virtualPath);
return dependency;// base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
public override bool DirectoryExists(string virtualDir)
{
if (IsVirtual(virtualDir))
{
return true;
}
return base.DirectoryExists(virtualDir);
}
public override bool FileExists(string virtualPath)
{
if (IsVirtual(virtualPath))
{
return true;
}
return base.FileExists(virtualPath);
}
public override VirtualFile GetFile(string virtualPath)
{
if(IsVirtual(virtualPath))
{
return new VirtFile(virtualPath);
}
return base.GetFile(virtualPath);
}
private bool IsVirtual(string virtualPath)
{
return virtualPath.Contains("Database");
}
But if I try to change the <%="Test"%> to <%=new Model.Category()%>, of create a typed View I get an error stating that "The type or namespace name 'Model' could not be found (are you missing a using directive or an assembly reference?)". However, the same code works if it is simply placed in an .ascx file.
Am I missing something, it seems like wether the stream comes from the file system or a custom VirtualPathProvider it should have the same loaded assemblies, since <%=AppDomain.CurrentDomain.ApplicationIdentity%> returns the same value from either the file system or the custom provider.
Try adding
<%# Import Namespace="MyApp.Model" %>
to your dynamic user control string.
EDIT:
Of course, you could also use the fully qualified name for the type, changing Model.Category() to MyApp.Model.Category(). Most of the time, I import the namespace. Just a style preference.
How your Model class look like? Is it wrapped within some namespace? VPP is pretty amazing thing and can do a lot of magic, just make sure when you passing 'string' with your virtual asp.net 'page' content you provide full path to you classes, it's safer this way. or, another option, use your web.config to link your namespaces, so app will find your classes.

Chose to render different View (ASPX) file in ASP.NET MVC

I have this
public class HomeController{
public ActionResult Index()
{
//do stuff
return View();
}
Obviously this choses and renders Index.aspx in the Home folder.
What we really want is to chose another file - Index.ar.aspx - if the CurrentCulture is ar-AE. I don't want IF statements on every return View() call. Anyone help me find the best place to override the name of the view file that is selected?
Note, please don't tell me off :) I know that separate files are a bit hacky, and we ARE using RESX files, DIR directives and routes to change languages etc. But we need seperate files for layout reasons.
You should create your own ViewEngine. If you are using the WebFormViewEngine that is the default one with MVC, you could easily subclass it and then override the FindView(...) method.
In your overridden FindView(...) method you could easily look for a file based on a convention that includes the name of the current culture.
Take a look at Scott Hanselmans post about a ViewEngine that looks for different view files if the site is browsed using a mobile device.
Perhaps something like:
public class ExampleViewEngine : WebFormViewEngine
{
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
ViewEngineResult result = null;
string conventionViewName = string.Format("{0}.{1}", viewName, System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName);
result = base.FindView(controllerContext, conventionViewName, masterName, useCache);
if (result == null || result.View == null)
{
result = base.FindView(controllerContext, viewName, masterName, useCache);
}
return result;
}
}
It sounds like you really want the View engine to be able to decide which view to return, rather than having the controllers be responsible for it.
Take a look at this tutorial, and google around for some others. It's pretty simple to override the default view engine, and you can add the language choosing logic there, removing the need for it at the controller level.
public class BaseController{
// Don't remember parameter type exactly
public void OnActionExecuted(ActionExecutedContext context)
{
// if view is returned, add culture suffix to its name
// also may need to do so for PartialViewResult
// One problem is if view is not named; Name is "";
// in this case use context.ActionContext.Name or RouteData["action"] for view name
if (context.Result is ViewResult)
{
var view = context.Result as ViewResult;
view.Name = view.Name + CurrentCultureSuffix;
}
}
}
public class HomeController: BaseController{
public ActionResult Index()
{
//do stuff
return View();
}
}

Resources