Change the Views location - asp.net

I am developing a website in MVC 2.0. I want to change the View folder location in my website. I wanted to keep the views folder inside other folders, When I try to do so i am getting following errors
The view 'Index' or its master was not found. The following locations were searched:
~/Views/Search/Index.aspx
~/Views/Search/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
My Views folder will be in ~/XYZ/ABC/Views instead of ~/Views. Please solve my problem. Will I get any problems If I change the default Views folder location. Do I need to change anything in HTML Helper classes because I don't know anything in MVC as this is my starting project i dont want to risk..Please help me out...

You'll need to create a custom view engine and use that instead. Fortunately you can just inherit from the default one and change the locations on the constructor. Here's a guide to creating your own view engine: http://www.singingeels.com/Articles/Creating_a_Custom_View_Engine_in_ASPNET_MVC.aspx
From the article:
protected void Application_Start()
{
//... other things up here.
// I want to REMOVE the ASP.NET ViewEngine...
ViewEngines.Engines.Clear();
// and then add my own :)
ViewEngines.Engines.Add(new HoTMeaTViewEngine());
}
public class HoTMeaTViewEngine : VirtualPathProviderViewEngine
{
public HoTMeaTViewEngine()
{
// This is where we tell MVC where to look for our files. This says
// to look for a file at "Views/Controller/Action.html"
base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.html" };
base.PartialViewLocationFormats = base.ViewLocationFormats;
}
}

Check this place out. How to change default view location scheme in ASP.NET MVC?
base.ViewLocationFormats = new string[] {
"~/Views/{1}/{2}/{0}.aspx",
"~/Views/{1}/{2}/{0}.ascx",
"~/Views/Shared/{2}/{0}.aspx",
"~/Views/Shared/{2}/{0}.ascx" ,
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx"
Even easier is this one Can I specify a custom location to “search for views” in ASP.NET MVC?

As an alternative, you can override the view engine locations for a specific controller without affecting the view engines for the other controllers.
These are some snippets from a product I am developing, but it shows the constructor for one of my controllers, and a view engine I made specificially for controllers that inherit from KBRenderMvcController.
So any controller based off KBRenderMvcController will also have my view engine.
However at no point did I clear the view engine collection, which is relevant. Because I wanted the views my product is using to fall back to default locations.
In short, if you delete \App_plugins\Product\Views\MyView And instead create a \Views\MyView it will still render from \Views\MyView instead.
Also in the ViewEngine I demonstrate code that determines the type of controller being used and if it's not a target controller I return empty view locations so they don't get used for other controllers.
#region Constructor
public KBRenderMvcController()
: base()
{
viewEngine = new KBFrontEndViewEngine();
if (!this.ViewEngineCollection.Contains(viewEngine))
this.ViewEngineCollection.Insert(0, viewEngine);
}
#endregion
public class KBFrontEndViewEngine : RazorViewEngine
{
#region Fields
private static bool _Initialized = false;
private static string[] viewLocationFormats = null;
private static string[] partialViewLocationFormats = null;
private static string[] viewEngineFileExtensions = new string[] { "cshtml" };
#endregion
#region Constructor
public KBFrontEndViewEngine()
{
if (!_Initialized)
{
viewLocationFormats = new string[]
{
string.Concat(KBApplicationCore.PluginRelUrl, "/Views/{1}/{0}.cshtml"),
string.Concat(KBApplicationCore.PluginRelUrl, "/Views/Partials/{0}.cshtml")
};
partialViewLocationFormats = new string[]
{
string.Concat(KBApplicationCore.PluginRelUrl, "/Views/{1}/Partials/_partial{0}.cshtml"),
string.Concat(KBApplicationCore.PluginRelUrl, "/Views/Partials/_partial{0}.cshtml"),
string.Concat(KBApplicationCore.PluginRelUrl, "/Views/{1}/Dialogs/_dialog{1}.cshtml"),
string.Concat(KBApplicationCore.PluginRelUrl, "/Views/Dialogs/_dialog{1}.cshtml"),
};
_Initialized = true;
}
base.ViewLocationFormats = viewLocationFormats;
base.PartialViewLocationFormats = partialViewLocationFormats;
base.MasterLocationFormats = viewLocationFormats;
base.FileExtensions = viewEngineFileExtensions;
}
#endregion
#region Methods
//Don't run on requests that are not for our hijacked controllers
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
Type controllerType = controllerContext.Controller.GetType();
Type baseType = controllerType.BaseType;
if ((baseType != null) && (baseType.Name == "KBRenderMvcController`1") || (baseType.Name == "KBFrontEndBaseSurfaceController"))
return base.FindPartialView(controllerContext, partialViewName, useCache);
else
return new ViewEngineResult(new List<string>());
}
#endregion
}

Related

How to create viewmodels instance when app start time in xamarin forms MVVM

My aim is to access bindable property across the the App. But My current framework ViewModel Instance create multiple time
My Requirement : I have the cart count in the bottomTray(CheckuoutViewModel) i want to increase the cart count any where in the app page, but in this cart count not update when back click, its only working on forward navigation, the reason behind CheckoutViewModel instance create each and every time. so that i'm try to instant creation at earlier.
Here I'm list out sample ViewModel and calling method
Login ViewModel
Checkuout ViewModel(This view model common for all page)
BaseNavigationViewModel(Its BaseViewModel)
As of now i'm calling when BindinContext each and every time like,
new LoginViewMode(navigation)
new CheckoutViewModel(navigation)
what will do to create all ViewModel instance when app start time like ViewModel Locator?
Im tried
public static ViewModelLocator Locator
{
get { return locator ?? (locator = new ViewModelLocator()); }
}
And ViewModel Locator
public ViewModelLocator()
{
navigation = App.Current.MainPage.Navigation;
}
internal CustomTabBarViewModel CustomTabBarVM
{
get
{
return customTabBarVM ?? (customTabBarVM = new CustomTabBarViewModel(navigation));
}
}
And CustomTabBar.xaml.cs
public CustomTabBar()
{
viewModel = App.Locator.CustomTabBarVM;
InitializeComponent();
BindingContext = viewModel;
}
and Expectation
App.Locator.CustomTabBarVM.BadgeCartCount = OrderObject.Instance.ORDER_OBJECT.Items.Count;
This approach is working fine but it's create some navigation issues
A singleton instance is a common feature of virtually all MVVM frameworks (Prism, FreshMVVM etc). If you aren't using a framework (if you aren't, I would STRONGLY advise you consider using one), below is a solution.
To obtain a single instance of a ViewModel you can use the App class to host the object and access it whenever you need.
Create a public static property of your ViewModel:
public static MyViewModel MyViewModelInstance { get; }
Create an instance in the constructor of the app
public App()
{
InitializeComponent();
MyViewModelInstance = new MyViewModel();
var myPage = new MyPage()
{
BindingContext = MyViewModelInstance
};
var navPage = new NavigationPage(myPage);
MainPage = navPage;
}
Whenever you create a new page, access the shared instance
// This method is just an example of how you might create a new page and wire up the view model
async void GoNextClicked(System.Object sender, System.EventArgs e)
{
var myPage = new MyPage()
{
BindingContext = App.MyViewModelInstance
};
await this.Navigation.PushAsync(myPage);
}
This approach comes with a few caveats, you are creating instances when the app loads not when they are needed (Eagerly loading). So a performance optimisation would be to use Lazy<T> to handle the creation of these objects. However this is logic that has already been written for you in MVVM frameworks, they are there to help you and you should be using them.
Lazy Load
You can save memory and performance at startup by lazy loading the viewmodel, here is this example rewritten to support this pattern:
public static MyViewModel MyViewModelInstance
{
get => _myViewModelInstanceFactory.Value;
}
private static Lazy<MyViewModel> _myViewModelInstanceFactory = new Lazy<MyViewModel>(() => new MyViewModel(), true);
public App()
{
InitializeComponent();
var myPage = new MyPage()
{
BindingContext = MyViewModelInstance
};
var navPage = new NavigationPage(myPage);
MainPage = navPage;
}
Now this object won't be created until it is accessed by your code, and once it has been accessed once it has already been created and will go on to live in memory for the rest of your apps lifecycle.
Axemasta has good answer about re-use of a shared view model instance.
I'll give an alternative approach to the underlying need given in one comment: how to have a static property (so the value is common), and Bind to it when Binding to an instance.
Use this approach if you do want a different CheckoutViewModel for each new page. For example, if there are other properties that should be set up differently, depending on the page.
public class CheckoutViewModel : : INotifyPropertyChanged // or your MVVM library's base class for ViewModels.
{
public static int SharedCount { get; set; }
public void IncrementCount()
{
Count = Count + 1;
}
public int Count {
get => SharedCount;
set {
// Exact code might be simpler if using an MVVM library.
if (SharedCount != value)
{
SharedCount = value;
OnPropertyChanged("Count");
}
}
}
}
}
LIMITATION: This assumes that only the current instance of CheckoutViewModel is visible; if you need to "notify" OTHER Views (update other CheckoutViewModel instances), then you'll need a "publish/subscribe" solution. In Xamarin Forms, one such solution is MessagingCenter.

_ViewStart not used after Custom ViewEngine

Does anyone know if there is a reason why _ViewStart.cshtml wouldn't get picked up with a Custom ViewEngine in MVC 3?
My Views now live at
~\UI\Views\
~\UI\Views\Shared\
with ViewStart being at ~\UI\Views_ViewStart.cshtml.
I've cleared out the existing RazorViewEngine and replaced it with mine in the global.asax and all the views resolve properly except none of the layout pages get applied unless I specify it individually in each view.
My engine path format code is:
this.ViewLocationFormats = new[]
{
"~/UI/Views/{1}/{0}.cshtml",
"~/UI/Views/Shared/{0}.cshtml"
};
this.PartialViewLocationFormats = new[]
{
"~/UI/Views/Shared/{0}.cshtml",
"~/UI/Views/Shared/Partial/{0}.cshtml",
"~/UI/Views/{1}/Partial/{0}.cshtml"
};
this.AreaMasterLocationFormats = new[]
{
"~/UI/Views/Admin/Shared/{0}.cshtml"
};
this.AreaPartialViewLocationFormats = new[]
{
"~/UI/Views/Admin/Shared/{0}.cshtml",
"~/UI/Views/Admin/Shared/Partial/{0}.cshtml"
};
this.AreaViewLocationFormats = new[] { "~/UI/Views/Admin/{1}/{0}.cshtml" };
this.MasterLocationFormats = new[]
{
"~/UI/Views/{1}/{0}.cshtml",
"~/UI/Views/Shared/{0}.cshtml"
};
Thanks in advance,
Scott
Stupidity won this time, unfortunately. I had based my custom ViewEngine off some code I referenced from an article. Within the article, they detailed the override for CreateView. It had one of the boolean parameters (runViewStartPages) set to false but since it wasn't a named argument, I missed over it.
public class XyzViewEngine : RazorViewEngine
{
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
return new RazorView(
controllerContext,
viewPath,
masterPath,
true, //<--- this drives whether to use _ViewStart pages. It was set to false
FileExtensions,
ViewPageActivator
);
}
}

Adding sub-directory to "View/Shared" folder in ASP.Net MVC and calling the view

I'm currently developing a site using ASP.Net MVC3 with Razor. Inside the "View/Shared" folder, I want to add a subfolder called "Partials" where I can place all of my partial views (for the sake of organizing the site better.
I can do this without a problem as long as I always reference the "Partials" folder when calling the views (using Razor):
#Html.Partial("Partials/{ViewName}")
My question is if there is a way to add the "Partials" folder to the list that .Net goes through when searching for a view, this way I can call my view without having to reference the "Partials" folder, like so:
#Html.Partial("{ViewName}")
Thanks for the help!
Solved this. To add the "Shared/Partials" sub directory I created to the list of locations searched when trying to locate a Partial View in Razor using:
#Html.Partial("{NameOfView}")
First create a view engine with RazorViewEngine as its base class and add your view locations as follows. Again, I wanted to store all of my partial views in a "Partials" subdirectory that I created within the default "Views/Shared" directory created by MVC.
public class RDDBViewEngine : RazorViewEngine
{
private static readonly string[] NewPartialViewFormats =
{
"~/Views/{1}/Partials/{0}.cshtml",
"~/Views/Shared/Partials/{0}.cshtml"
};
public RDDBViewEngine()
{
base.PartialViewLocationFormats = base.PartialViewLocationFormats.Union(NewPartialViewFormats).ToArray();
}
}
Note that {1} in the location format is the Controller name and {0} is the name of the view.
Then add that view engine to the MVC ViewEngines.Engines Collection in the Application_Start() method in your global.asax:
ViewEngines.Engines.Add(new RDDBViewEngine());
Thank you for your answers. This has organized my Shared folder, but why do you create a new type of view engine? I just made a new RazorViewEngine, set it's PartialViewLocationFormats and added it to the list of ViewEngines.
ViewEngines.Engines.Add(new RazorViewEngine
{
PartialViewLocationFormats = new string[]
{
"~/Views/{1}/Partials/{0}.cshtml",
"~/Views/Shared/Partials/{0}.cshtml"
}
});
It´s nice to custom the view engine, but if you just want to have a subfolder por partials you don´t need that much...
Just use the full path to the partial view, as done for the Layout View:
#Html.Partial("/Views/Shared/Partial/myPartial.cshtml")
Hope it helps someone...
If you are doing this in ASP.NET Core, simple go to the Startup class, under ConfigureServices method, and put
services.AddMvc()
.AddRazorOptions(opt => {
opt.ViewLocationFormats.Add("/Views/{1}/Partials/{0}.cshtml");
opt.ViewLocationFormats.Add("/Views/Shared/Partials/{0}.cshtml");
});
I've updated lamarant's excellent answer to include Areas:
public class RDDBViewEngine : RazorViewEngine
{
private static readonly string[] NewPartialViewFormats =
{
"~/Views/{1}/Partials/{0}.cshtml",
"~/Views/Shared/Partials/{0}.cshtml"
};
private static List<string> AreaRegistrations;
public RDDBViewEngine()
{
AreaRegistrations = new List<string>();
BuildAreaRegistrations();
base.PartialViewLocationFormats = base.PartialViewLocationFormats.Union(NewPartialViewFormats).ToArray();
base.PartialViewLocationFormats = base.PartialViewLocationFormats.Union(areaRegistrations).ToArray();
}
private static void BuildAreaRegistrations()
{
string[] areaNames = RouteTable.Routes.OfType<Route>()
.Where(d => d.DataTokens != null && d.DataTokens.ContainsKey("area"))
.Select(r => r.DataTokens["area"].ToString()).ToArray();
foreach (string areaName in areaNames)
{
AreaRegistrations.Add("~/Areas/" + areaName + "/Views/Shared/Partials/{0}.cshtml");
AreaRegistrations.Add("~/Areas/" + areaName + "/Views/{1}/Partials/{0}.cshtml");
}
}
}
Then don't forget to include in your application start:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
...
ViewEngines.Engines.Add(new RDDBViewEngine());
}
}
You can also update the partialview-location-formats of the registered RazorViewEngine.
Place the below code in Application_Start in Global.asax.
RazorViewEngine razorEngine = ViewEngines.Engines.OfType<RazorViewEngine>().FirstOrDefault();
if (razorEngine != null)
{
string[] newPartialViewFormats = new[] {
"~/Views/{1}/Partials/{0}.cshtml",
"~/Views/Shared/Partials/{0}.cshtml"
};
razorEngine.PartialViewLocationFormats =
razorEngine.PartialViewLocationFormats.Union(newPartialViewFormats).ToArray();
}
You can create register your own view engine that derives from whatever view engine your are using (Webforms/Razor) and specify whatever locations you want in the constructor or just add them to the list of already existing locations:
this.PartialViewLocationFormats = viewLocations;
Then in application start you would add your view engine like so:
ViewEngines.Engines.Add(new MyViewEngineWithPartialPath());

ASP .NET MVC VirtualPathProvider

I am writing a VirtualPathProvider to dynamically load my MVC views, which are located in a different directory. I successfully intercept the call before MVC (in FileExists), but in my VirtualPathProvider, I get the raw, pre-routed url like:
~/Apps/Administration/Account/LogOn
Personally, I know that MVC will look for
~/Apps/Administration/Views/Account/LogOn.aspx
and that I should be reading the file contents from
D:\SomeOtherNonWebRootDirectory\Apps\Administration\Views\Account\LogOn.aspx
but I'd rather not hard code the logic to "add the directory named Views and add aspx to the end".
Where is this logic stored and how can I get it into my virtual path provider?
Thanks. Sorry if I'm not being clear.
Edited
You need to make a class that inherits WebFormViewEngine and sets the ViewLocationFormats property (inherited from VirtualPathProviderViewEngine).
The default values can be found in the MVC source code:
public WebFormViewEngine() {
MasterLocationFormats = new[] {
"~/Views/{1}/{0}.master",
"~/Views/Shared/{0}.master"
};
AreaMasterLocationFormats = new[] {
"~/Areas/{2}/Views/{1}/{0}.master",
"~/Areas/{2}/Views/Shared/{0}.master",
};
ViewLocationFormats = new[] {
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx"
};
AreaViewLocationFormats = new[] {
"~/Areas/{2}/Views/{1}/{0}.aspx",
"~/Areas/{2}/Views/{1}/{0}.ascx",
"~/Areas/{2}/Views/Shared/{0}.aspx",
"~/Areas/{2}/Views/Shared/{0}.ascx",
};
PartialViewLocationFormats = ViewLocationFormats;
AreaPartialViewLocationFormats = AreaViewLocationFormats;
}
You should then clear the ViewEngines.Engines collection and add your ViewEngine instance to it.
As SLaks mentioned above, you need to create a Custom View Engine and add your view-finding logic in the FindView method.
public class CustomViewEngine : VirtualPathProviderViewEngine
{
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
//Set view path
string viewPath = GetCurrentViewPath();
//Set master path (if need be)
string masterPath = GetCurrentMasterPath();
return base.FindView(controllerContext, viewPath, masterPath, useCache);
}
}
In the Application_Start, you can register your View Engine like this:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomViewEngine());
The answer was that MVC was not finding my controller properly. If MVC does in fact find your controller properly, there should be two requests processed by the VirtualPathProvider:
An initial request with the acutal url requested (ie. http://.../Account/LogOn).
A subsequent FileExists check for http://.../Views/Account/LogOn.aspx, after the request in 1. returns false calling FileExists. This actually retuns the aspx content.

ASP.NET - Avoid hardcoding paths

I'm looking for a best practice solution that aims to reduce the amount of URLs that are hard-coded in an ASP.NET application.
For example, when viewing a product details screen, performing an edit on these details, and then submitting the changes, the user is redirected back to the product listing screen. Instead of coding the following:
Response.Redirect("~/products/list.aspx?category=books");
I would like to have a solution in place that allows me to do something like this:
Pages.GotoProductList("books");
where Pages is a member of the common base class.
I'm just spit-balling here, and would love to hear any other way in which anyone has managed their application redirects.
EDIT
I ended up creating the following solution: I already had a common base class, to which I added a Pages enum (thanks Mark), with each item having a System.ComponentModel.DescriptionAttribute attribute containing the page's URL:
public enum Pages
{
[Description("~/secure/default.aspx")]
Landing,
[Description("~/secure/modelling/default.aspx")]
ModellingHome,
[Description("~/secure/reports/default.aspx")]
ReportsHome,
[Description("~/error.aspx")]
Error
}
Then I created a few overloaded methods to handle different scenarios. I used reflection to get the URL of the page through it's Description attribute, and I pass query-string parameters as an anonymous type (also using reflection to add each property as a query-string parameter):
private string GetEnumDescription(Enum value)
{
Type type = value.GetType();
string name = Enum.GetName(type, value);
if (name != null)
{
FieldInfo field = type.GetField(name);
if (field != null)
{
DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attr != null)
return attr.Description;
}
}
return null;
}
protected string GetPageUrl(Enums.Pages target, object variables)
{
var sb = new StringBuilder();
sb.Append(UrlHelper.ResolveUrl(Helper.GetEnumDescription(target)));
if (variables != null)
{
sb.Append("?");
var properties = (variables.GetType()).GetProperties();
foreach (var property in properties)
sb.Append(string.Format("{0}={1}&", property.Name, property.GetValue(variables, null)));
}
return sb.ToString();
}
protected void GotoPage(Enums.Pages target, object variables, bool useTransfer)
{
if(useTransfer)
HttpContext.Current.Server.Transfer(GetPageUrl(target, variables));
else
HttpContext.Current.Response.Redirect(GetPageUrl(target, variables));
}
A typical call would then look like so:
GotoPage(Enums.Pages.Landing, new {id = 12, category = "books"});
Comments?
I'd suggest that you derive your own class ("MyPageClass") from the Page class and include this method there:
public class MyPageClass : Page
{
private const string productListPagePath = "~/products/list.aspx?category=";
protected void GotoProductList(string category)
{
Response.Redirect(productListPagePath + category);
}
}
Then, in your codebehind, make sure that your page derives from this class:
public partial class Default : MyPageClass
{
...
}
within that, you can redirect just by using:
GotoProductList("Books");
Now, this is a bit limited as is since you'll undoubtedly have a variety of other pages like the ProductList page. You could give each one of them its own method in your page class but this is kind of grody and not smoothly extensible.
I solve a problem kind of like this by keeping a db table with a page name/file name mapping in it (I'm calling external, dynamically added HTML files, not ASPX files so my needs are a bit different but I think the principles apply). Your call would then use either a string or, better yet, an enum to redirect:
protected void GoToPage(PageTypeEnum pgType, string category)
{
//Get the enum-to-page mapping from a table or a dictionary object stored in the Application space on startup
Response.Redirect(GetPageString(pgType) + category); // *something* like this
}
From your page your call would be: GoToPage(enumProductList, "Books");
The nice thing is that the call is to a function defined in an ancestor class (no need to pass around or create manager objects) and the path is pretty obvious (intellisense will limit your ranges if you use an enum).
Good luck!
You have a wealth of options availible, and they all start with creating a mapping dictionary, whereas you can reference a keyword to a hard URL. Whether you chose to store it in a configuration file or database lookup table, your options are endless.
You have a huge number of options available here. Database table or XML file are probably the most commonly used examples.
// Please note i have not included any error handling code.
public class RoutingHelper
{
private NameValueCollecton routes;
private void LoadRoutes()
{
//Get your routes from db or config file
routes = /* what ever your source is*/
}
public void RedirectToSection(string section)
{
if(routes == null) LoadRoutes();
Response.Redirect(routes[section]);
}
}
This is just sample code, and it can be implemented any way you wish. The main question you need to think about is where you want to store the mappings. A simple xml file could do it:
`<mappings>
<map name="Books" value="/products.aspx/section=books"/>
...
</mappings>`
and then just load that into your routes collection.
public class BasePage : Page
{
public virtual string GetVirtualUrl()
{
throw new NotImplementedException();
}
public void PageRedirect<T>() where T : BasePage, new()
{
T page = new T();
Response.Redirect(page.GetVirtualUrl());
}
}
public partial class SomePage1 : BasePage
{
protected void Page_Load()
{
// Redirect to SomePage2.aspx
PageRedirect<SomePage2>();
}
}
public partial class SomePage2 : BasePage
{
public override string GetVirtualUrl()
{
return "~/Folder/SomePage2.aspx";
}
}

Resources