I am trying to load a MVC view from a dll (Asp.Net MVC 5).
Set up
I have a MVC web application project and a class library project (Custom.Views) which I have /CustomViews/Views/MyView/CustomView1.cshtml
CustomView1.cshtml which is an embedded resource,
#inherits System.Web.Mvc.WebViewPage
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div>
Helloooo Custom view in action!!!!!
</div>
In my Custom.Views project, I have a controller called MyView controller,
public ActionResult Shop()
{
return View("~/ClientView/Custom.Views.DLL/Custom.Views.CustomViews.Views.MyView.CustomView1.cshtml");
}
Which returns a virtual view path pointing to the embedded resource. I configured Route config to check this namespace "Custom.Views.CustomViews.." controllers.
I implemented a VirtualPathProvider,
public class AssemblyResourceProvider : System.Web.Hosting.VirtualPathProvider
{
public AssemblyResourceProvider() { }
private bool IsAppResourcePath(string virtualPath)
{
String checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
return checkPath.StartsWith("~/ClientView/", StringComparison.InvariantCultureIgnoreCase);
}
public override bool FileExists(string virtualPath)
{
return (IsAppResourcePath(virtualPath) ||
base.FileExists(virtualPath));
}
public override VirtualFile GetFile(string virtualPath)
{
if (IsAppResourcePath(virtualPath))
return new AssemblyResourceVirtualFile(virtualPath);
else
return base.GetFile(virtualPath);
}
public override CacheDependency GetCacheDependency(string virtualPath,
IEnumerable virtualPathDependencies, DateTime utcStart)
{
if (IsAppResourcePath(virtualPath))
return null;
else
return base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
}
class AssemblyResourceVirtualFile : VirtualFile
{
string path;
public AssemblyResourceVirtualFile(string virtualPath)
: base(virtualPath)
{
path = VirtualPathUtility.ToAppRelative(virtualPath);
}
public override System.IO.Stream Open()
{
string[] parts = path.Split('/');
string assemblyName = parts[2];
string resourceName = parts[3];
assemblyName = Path.Combine(HttpRuntime.BinDirectory, assemblyName);
var assembly = Assembly.LoadFile(assemblyName);
if (assembly != null)
{
return assembly.GetManifestResourceStream(resourceName);
}
return null;
}
}
Problem
Question 1. When I type url "httlp://localhost:1234/myview/shop" it hits the virtual path and returns finds the file stream. That part works fine. But soon after that Virtual Path provider gets another request looking for a _ViewStart.cshtml in "~/ClientView/Custom.Views.DLL/_ViewStart.cshtml". Why is this happening?
Why it's looking for a _ViewStart.cshtml in that path.
You can try to define layout page in _ViewStart.cshtml:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
If you use different layout pages for several Views, you can also define corresponding layout pages for every View as shown below:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
Related
I am getting the title error. Here are the full error shows
'HttpRequest' does not contain a definition for 'Browser' and no accessible extension method 'Browser' accepting a first argument of type 'HttpRequest' could be found(are you missing a using directive or an assembly reference?)
Now, this error shows me when I write the code for check the site is working in computer or mobile.
I try to give the reference also for HTTPRequest but I didn't find in nuget package
Here is my controller code,
public ActionResult MobileBrowser()
{
var browser = Request.Browser;
System.Web.HttpBrowserCapabilitiesBase myBrowserCaps = browser;
if (((System.Web.HttpBrowserCapabilitiesBase)myBrowserCaps).IsMobileDevice)
{
ViewBag.Message = "mob";
}
else
{
ViewBag.Message = "web";
}
return PartialView();
}
Now, I get the redline in var browser = Request.Browser;, System.Web.HttpBrowserCapabilitiesBase myBrowserCaps = browser; and if (((System.Web.HttpBrowserCapabilitiesBase)myBrowserCaps).IsMobileDevice)
In nopCommerce they are providing that site used in a mobile device or not using the below service
/// <summary>
/// Get a value indicating whether the request is made by mobile device
/// </summary>
/// <returns></returns>
public virtual bool IsMobileDevice()
{
if (_httpContextAccessor?.HttpContext == null)
return false;
//we put required logic in try-catch block
//more info: https://www.nopcommerce.com/boards/t/17711/unhandled-exception-request-is-not-available-in-this-context.aspx
try
{
//we don't parse browscap library here
//in 99% of cases it's enough to use the approach suggested by http://detectmobilebrowsers.com/
var userAgent = _httpContextAccessor.HttpContext.Request.Headers[HeaderNames.UserAgent].ToString();
var mobile = _firstMobileDeviceRegex.IsMatch(userAgent) || _secondMobileDeviceRegex.IsMatch(userAgent.Substring(0, 4));
return mobile;
}
catch
{
// ignored
}
return false;
}
Or instead of request browser, you can use this
var userAgent = _httpContextAccessor.HttpContext.Request.Headers[HeaderNames.UserAgent].ToString();
So this will sure helpful
Hope this will be helpful to you
Thank you
You can try this -
Without Injection: Controller has Request object which is HttpRequest type it contains IHeaderDictionary type Header from where you will have browser information.
public class UploadFileController : Controller
{
[HttpGet]
public IActionResult Photo()
{
var brows = Request.Headers["User-Agent"].ToString();
return View();
}
[HttpPost]
public IActionResult Photo(UserViewModel userViewModel)
{
if (ModelState.IsValid)
{
}
return View(userViewModel);
}
}
IHttpContextAccessor Inject in controller:
You need to add singleton service of the type specified in Startup class, Configuration method services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();. Now you are ready to inject in controller -
public class UploadFileController : Controller
{
private readonly IHttpContextAccessor httpContextAccessor;
public UploadFileController(IHttpContextAccessor httpContext)
{
this.httpContextAccessor = httpContext;
}
[HttpGet]
public IActionResult Photo()
{
var browser = httpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
return View();
}
[HttpPost]
public IActionResult Photo(UserViewModel userViewModel)
{
if (ModelState.IsValid)
{
}
return View(userViewModel);
}
}
IHttpContextAccessor Inject in View: If you want have browser information in the view you need to add in Startup class, Configuration method services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); and inject IHttpContextAccessor.
#inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContextAccessor
#{
ViewData["Title"] = "Upload Picture";
var browser = HttpContextAccessor.HttpContext.Request.Headers["User-Agent"].ToString();
}
I would like to pass the data to the view, I created a viewmodel inheriting from RenderModel but when I run to the error "Element" Umbraco.Web.Models.RenderModel "does not contain the definition" Topic "
ViewModel:
namespace Umbraco12.Models
{
public class Home : RenderModel
{
public Home(IPublishedContent content, CultureInfo culture) : base(content, culture)
{
}
public string Topic { get; set; }
}
}
Controller:
public class HomeController : Umbraco.Web.Mvc.RenderMvcController
{
// GET: Home
public ActionResult Home(RenderModel model)
{
var home = new Home(model.Content, model.CurrentCulture);
home.Topic = "aloha";
//Do some stuff here, then return the base method
return View("Home", home);
}
}
View:
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage<Home>
#using ContentModels = Umbraco.Web.PublishedContentModels;
#{
Layout = "Master.cshtml";
}
<h1>#Umbraco.Field("topic") : #Model.Topic</h1>
Make sure your DocumentType Alias is "Home".
I would also call your model something different to just Home as it could clash and give an ambiguous error.
Controller:
public class HomeController : Umbraco.Web.Mvc.RenderMvcController
{
// GET: Home
public ActionResult Index(RenderModel model)
{
var home = new Home(model.Content, model.CurrentCulture);
home.Topic = "aloha";
//Do some stuff here, then return the base method
return View("Home", home);
}
}
View:
#inherits UmbracoViewPage<Umbraco12.Models.Home>
#{
Layout = "Master.cshtml";
}
<h1>#Model.Topic</h1>
Some explanation about scenario but please be patient to end!!!
I have Implemented a pluggable MVC application which can register plugins which exist in Areas folder of main Project.
each plugin have some views and controller
I want to set layout for views in plugins(the plugins don't know anything about master layout in Main application)
So I investigated some ways to render views where I want to be rendered...
In Main Application my PluginBootstrapper will register all plugins in Areas folder is as:
public class PluginBootstrapper
{
public static readonly List<Assembly> PluginAssemblies = new List<Assembly>();
public static void Init()
{
var fullPluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Areas");
foreach (var file in Directory.EnumerateFiles(fullPluginPath, "*Plugin*.dll", SearchOption.AllDirectories))
PluginAssemblies.Add(Assembly.LoadFile(file));
PluginAssemblies.ForEach(BuildManager.AddReferencedAssembly);
// Add assembly handler for strongly-typed view models
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
}
private static Assembly AssemblyResolve(object sender, ResolveEventArgs resolveArgs)
{
var currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();
// Check we don't already have the assembly loaded
foreach (var assembly in currentAssemblies)
{
if (assembly.FullName == resolveArgs.Name || assembly.GetName().Name == resolveArgs.Name)
{
return assembly;
}
}
return null;
}
}
To call Init() and register plugin in assembly file:
[assembly: PreApplicationStartMethod(
typeof(PluginBootstrapper), "Init")]
In the other side each plugin can be developed in separate solution so developed plugin have it's own AreaRegistration implementation for example for one of them I have:
public class SettingPluginAreaRegistration : AreaRegistration
{
public override string AreaName
{
get { return "SettingPlugin"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"SettingPlugin",
"SettingPlugin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
I know how we can set layout in View but this is proper way for when all the views are in same project and Views knows where the main layout is, but in plugin views must know where the main Layout located in main application?
For example in plugin view:
#{
ViewBag.Title = "Plugin View Title";
Layout = "the address of main layout in main application";
}
As this link mentioned the other way is _ViewStart but this is also not proper way because each plugin have it's own _ViewStart.
So is there a good pattern to do this such as implementing WebViewPage to override Layout :
public abstract class SitePage<T> : System.Web.Mvc.WebViewPage<T>
{
public override string Layout
{
get
{
return base.Layout;
}
set
{
base.Layout = value;
}
}
}
or make a interface to set Layout and force the plugins view to implement that interface and change the layout by the reflections in Init() in PluginBootstrapper or something else ?
UPDATE1:
Is it possible or a good way to load all WebPageBase types while registering plugins in Init() method and set Layout for each of them by reflection ?
UPDATE2:
The bad way
public class BaseController : Controller
{
private string _masterName;
public string MasterLayout
{
get
{
return _masterName;
}
set
{
_masterName = value;
}
}
}
for controller:
public class SettingController : BaseController
{
public ActionResult Index()
{
var myView = View();
myView.MasterName = MasterLayout;
return myView;
}
}
and in Init() in PluginBootstrapper something like this:
var fullPluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Areas");
foreach (var file in Directory.EnumerateFiles(fullPluginPath, "*Plugin*.dll", SearchOption.AllDirectories))
PluginAssemblies.Add(Assembly.LoadFile(file));
foreach (Assembly plugin in PluginAssemblies)
{
BuildManager.AddReferencedAssembly(plugin);
var controllers = plugin.DefinedTypes.Where(x => x.BaseType.Name == "BaseController");
foreach (Type t in controllers)
{
// t.InvokeMember("MasterLayout", BindingFlags.SetProperty);
PropertyInfo propertyInfo = t.GetProperty("MasterLayout");
if (propertyInfo != null)
propertyInfo.SetValue(t, /*Convert.ChangeType(*/"~/Views/Shared/_Wrapper.cshtml"/*, propertyInfo.PropertyType)*/, null);
}
}
or hard code MasterLayout to always return specific layout address.
I believe UPDATE2 is not proper way...
thanks in advance.
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.
Is it possible to determine if a specific view name exists from within a controller before rendering the view?
I have a requirement to dynamically determine the name of the view to render. If a view exists with that name then I need to render that view. If there is no view by the custom name then I need to render a default view.
I'd like to do something similar to the following code within my controller:
public ActionResult Index()
{
var name = SomeMethodToGetViewName();
// The 'ViewExists' method is what I've been unable to find.
if (ViewExists(name))
{
retun View(name);
}
else
{
return View();
}
}
private bool ViewExists(string name)
{
ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, name, null);
return (result.View != null);
}
For those looking for a copy/paste extension method:
public static class ControllerExtensions
{
public static bool ViewExists(this Controller controller, string name)
{
ViewEngineResult result = ViewEngines.Engines.FindView(controller.ControllerContext, name, null);
return (result.View != null);
}
}
What about trying something like the following assuming you are using only one view engine:
bool viewExists = ViewEngines.Engines[0].FindView(ControllerContext, "ViewName", "MasterName", false) != null;
Here's another [not necessarily recommended] way of doing it
try
{
#Html.Partial("Category/SearchPanel/" + Model.CategoryKey)
}
catch (InvalidOperationException) { }
In asp.net core 2.x and aspnet6 the ViewEngines property no longer exists so we have to use the ICompositeViewEngine service. This a variant of the accepted answer using dependency injection:
public class DemoController : Controller
{
private readonly IViewEngine _viewEngine;
public DemoController(ICompositeViewEngine viewEngine)
{
_viewEngine = viewEngine;
}
private bool ViewExists(string name)
{
ViewEngineResult viewEngineResult = _viewEngine.FindView(ControllerContext, name, true);
return viewEngineResult?.View != null;
}
public ActionResult Index() ...
}
For the curious: The base interface IViewEngine is not registered as a service so we must inject ICompositeViewEngine instead. The FindView() method however is provided by IViewEngine so the member variable may use the base interface.
If you want to re-use this across multiple controller actions, building on the solution given by Dave, you can define a custom view result as follows:
public class CustomViewResult : ViewResult
{
protected override ViewEngineResult FindView(ControllerContext context)
{
string name = SomeMethodToGetViewName();
ViewEngineResult result = ViewEngines.Engines.FindView(context, name, null);
if (result.View != null)
{
return result;
}
return base.FindView(context);
}
...
}
Then in your action simply return an instance of your custom view:
public ActionResult Index()
{
return new CustomViewResult();
}
ViewEngines.Engines.FindView(ViewContext.Controller.ControllerContext, "View Name").View != null
My 2 cents.
Here's how to do it in Razor for Core 2.2 etc. Note that the call is "GetView", not "Find View)
#using Microsoft.AspNetCore.Mvc.ViewEngines
#inject ICompositeViewEngine Engine
...
#if (Engine.GetView(scriptName, scriptName, isMainPage: false).Success)
{
#await Html.PartialAsync(scriptName)
}