Copying raw html from a file into a razor page, in aspnet core? - asp.net

I'm moving an existing ASP.NET/MVC app to aspnet core, and there's a bit where I'm not sure of the cleanest solution.
The issue is that we have a shared view that is called from a number of controllers. There's a chunk of html that is loaded from a file by the controller, and then is inserted into the page by the view using #Html.Raw().
The existing code in .NET Framework is using an extension method on Controller to get the contents of the file:
public static class ControllerExtension
{
public static string GetContents(this Controller controller, string path)
{
var filepath = controller.Server.MapPath(path);
var contents = System.IO.File.ReadAllText(filepath);
return contents;
}
}
This, of course, does not work in aspnet core. There is no Server.MapPath().
Googling around I found that the recommended approach is to use IWebHostEnvironment. I could do that - inject it into my controller, and pass it to my GetContents() extension, but that's starting to have a bit of a whiff about it.
So I'm wondering, is there some other mechanism for doing the basic task?
Assume I have a number of files in a directory under wwwroot, each containing plain html.
What would be the cleanest way to have a controller include the contents of one of these files in a .cshtml view?

I've used the WebRootPath property of the environment to get to wwwroot and use relative paths from there. Example:
public class MyController
{
private readonly IWebHostEnvironment environment;
public MyController(IWebHostEnvironment environment)
{
this.environment = environment ?? throw new ArgumentNullException(nameof(environment));
}
public IActionResult GetContents(string path)
{
var contentPath = Path.Combine(environment.WebRootPath, path);
var content = System.IO.File.ReadAllText(contentPath);
// Do something with content and return
}
}
You would of course want more checks to validate the user-provided path before attempting to read the file. Also, would likely make this async unless you cache the content and normally serve the memory-cached data.

Related

How to dump entire appsettings.json file in ASP.NET Core Web API

I am trying to create a simple GET API endpoint which will return the JSON of the current environment appsettings.json file, for example for the development environment it will return the contents of appsettings.Development.json file and for production environment it will return the contents of appsettings.Production.json file.
I don't know a nice way to dump the entire config file. What I know though is ways to read single config values through the injected config["Key"] or read a section through
config.GetSection("SectionName").Get<MyCustomSectionClass>()
approach. These options are not feasible as the file is big and the content may change.
This is an ASP.NET Core 6 Core Web API application, created through the default Visual Studio template.
If you want to return entire appsettings.xxx.json file, You can try this simple demo. I not sure if there is a better method, But this method works well in my project.
using Newtonsoft.Json;
private readonly IWebHostEnvironment _env;
public WeatherForecastController(IWebHostEnvironment env)
{
_env = env;
}
[HttpGet]
public IActionResult Get()
{
if (_env.IsDevelopment())
{
var path = Path.Combine(_env.ContentRootPath, "appsettings.Development.json");
string json = System.IO.File.ReadAllText(path);
object jsonObject = JsonConvert.DeserializeObject(json);
return Ok(jsonObject);
}else if (_env.IsProduction())
{
//read from appsettings.Production.json
return Ok();
}
else
{
//..........
return Ok();
}
}

Can't handle routing correctly in mixed VS environment

I am creating a new project in Visual Studio using the SPA template. My goal is to have an Angular/SPA application that will "host"/contain some legacy applications that will eventually be modernized/migrated. So, I have an iframe on a page in my SPA app, and when a menu item is clicked, I want to load one of the legacy ASP.NET apps in that iframe (it has to be in an iframe, as the legacy site used them, and its architecture relies on them).
I am having trouble getting the routing right. The SPA template defines a DefaultRoute class like this (I changed RouteExistingFiles to true):
public class DefaultRoute : Route
{
public DefaultRoute()
: base("{*path}", new DefaultRouteHandler()) {
this.RouteExistingFiles = true;
}
}
and I have edited the RouteConfig.cs file to ignore "aspx" page requests, like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes) {
routes.Ignore("*.aspx");
routes.Add("Default", new DefaultRoute());
}
}
The default route handler that is defined, looks like this:
public class DefaultRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
// Use cases:
// ~/ -> ~/views/index.cshtml
// ~/about -> ~/views/about.cshtml or ~/views/about/index.cshtml
// ~/views/about -> ~/views/about.cshtml
// ~/xxx -> ~/views/404.cshtml
var filePath = requestContext.HttpContext.Request.AppRelativeCurrentExecutionFilePath;
if (filePath == "~/") {
filePath = "~/views/index.cshtml";
}
else {
if (!filePath.StartsWith("~/views/", StringComparison.OrdinalIgnoreCase)) {
filePath = filePath.Insert(2, "views/");
}
if (!filePath.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)) {
filePath = filePath += ".cshtml";
}
}
var handler = WebPageHttpHandler.CreateFromVirtualPath(filePath); // returns NULL if .cshtml file wasn't found
if (handler == null) {
requestContext.RouteData.DataTokens.Add("templateUrl", "/views/404");
handler = WebPageHttpHandler.CreateFromVirtualPath("~/views/404.cshtml");
}
else {
requestContext.RouteData.DataTokens.Add("templateUrl", filePath.Substring(1, filePath.Length - 8));
}
return handler;
}
}
The directory structure is like this:
MySPA
SPA <- contains the SPA application (.net 4.5)
Legacy <- contains the legacy applications (.net 3.0)
In IIS, I have the legacy folder set as a virtual directory (subdirectory) within the SPA application.
How do I set up routing so that, when a menu item is clicked, and the request is sent (containing a url that has query string information) for an .aspx page, the request can be routed to the legacy application?
I have solved this issue. The issue was caused by several problems. But, the problem that most pertains to my information above was this...I needed to find the correct way to ignore the requests for the legacy aspx pages from within the routing code of the new site. So, in the RouteConfig.cs file, I placed this ignore statement as the first line of the RegisterRoute function:
routes.Ignore("{*allaspx}", new { allaspx = #".*\.aspx(/.*)?"});
That corrected the main issue, there were other minor issues that confused the situation but, I have identified what they are and I am working on those. So, ignoring the legacy urls in the routing functionality was the correct solution.

Asp.net MVC VirtualPathProvider views parse error

I am working on a plugin system for Asp.net MVC 2. I have a dll containing controllers and views as embedded resources.
I scan the plugin dlls for controller using StructureMap and I then can pull them out and instantiate them when requested. This works fine. I then have a VirtualPathProvider which I adapted from this post
public class AssemblyResourceProvider : VirtualPathProvider
{
protected virtual string WidgetDirectory
{
get
{
return "~/bin";
}
}
private bool IsAppResourcePath(string virtualPath)
{
var checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
return checkPath.StartsWith(WidgetDirectory, StringComparison.InvariantCultureIgnoreCase);
}
public override bool FileExists(string virtualPath)
{
return (IsAppResourcePath(virtualPath) || base.FileExists(virtualPath));
}
public override VirtualFile GetFile(string virtualPath)
{
return IsAppResourcePath(virtualPath) ? new AssemblyResourceVirtualFile(virtualPath) : base.GetFile(virtualPath);
}
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies,
DateTime utcStart)
{
return IsAppResourcePath(virtualPath) ? null : base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
}
internal class AssemblyResourceVirtualFile : VirtualFile
{
private readonly string path;
public AssemblyResourceVirtualFile(string virtualPath)
: base(virtualPath)
{
path = VirtualPathUtility.ToAppRelative(virtualPath);
}
public override Stream Open()
{
var parts = path.Split('/');
var resourceName = Path.GetFileName(path);
var apath = HttpContext.Current.Server.MapPath(Path.GetDirectoryName(path));
var assembly = Assembly.LoadFile(apath);
return assembly != null ? assembly.GetManifestResourceStream(assembly.GetManifestResourceNames().SingleOrDefault(s => string.Compare(s, resourceName, true) == 0)) : null;
}
}
The VPP seems to be working fine also. The view is found and is pulled out into a stream. I then receive a parse error Could not load type 'System.Web.Mvc.ViewUserControl<dynamic>'. which I can't find mentioned in any previous example of pluggable views. Why would my view not compile at this stage?
Thanks for any help,
Ian
EDIT:
Getting closer to an answer but not quite clear why things aren't compiling. Based on the comments I checked the versions and everything is in V2, I believe dynamic was brought in at V2 so this is fine. I don't even have V3 installed so it can't be that. I have however got the view to render, if I remove the <dynamic> altogether.
So a VPP works but only if the view is not strongly typed or dynamic
This makes sense for the strongly typed scenario as the type is in the dynamically loaded dll so the viewengine will not be aware of it, even though the dll is in the bin. Is there a way to load types at app start? Considering having a go with MEF instead of my bespoke Structuremap solution. What do you think?
The settings that allow parsing of strongly typed views are in ~/Views/Web.Config. When the view engine is using a virtual path provider, it is not in the views folder so doesn't load those settings.
If you copy everything in the pages node of Views/Web.Config to the root Web.Config, strongly typed views will be parsed correctly.
Try to add content of Views/Web.config directly to your main web.config under specific location, e.g. for handling virtual paths like ~/page/xxx. See more details here: http://blog.sergkazakov.com/2011/01/aspnet-strongly-typed-view-and-virtual.html
Is there a way to load types at app start?
Yes, you may use BuildManager.AddReferencedAssembly(assembly), where assembly is the one, containing the requested type. So, once you use MEF, the last should be easy, but beware, everything should be done before Application_Start, so you may wish to use PreApplicationStartMethodAttribute.

ASP.Net MVC - Generate an ActionLink from code behind?

I have a number of permissions, and based on a set of conditions these permission determine if a user can see certain features. I have written a helper function for this as the logic in the view became quite extensive.
Essentially I'm looking for a function the same as Html.ActionLink that I can access from a class file (Ideally if I can access the Helper that would be great) So I can do somthing like so,
public static string GetAdminLinks()
{
if(PermCheck)
{
return(Html.ActionLink(...));
}
}
Any sugestions?
in controller:
Url.Action("Index", "Home", null, Request.Url.Scheme);
It largely depends on how your permission check is implemented (and of which information it needs to determine the user's permissions). Anyhow, I'd implement it as an extension to the HtmlHelper class.
Somewhere in your App_Code:
using System.Web.Mvc.Html;
public static class HtmlHelperExtensions {
public static string SecureActionLink(this HtmlHelper htmlHelper, string action, string controller){
if(PermCheck)
return htmlHelper.ActionLink(action, controller);
else
return string.Empty;
}
//add other ActionLink overrides if you like...
}
Then you'll be able to call the extension method from anywhere in your ViewPages without any code behind.

Can I use HttpHandler to fake the existence of aspx pages?

I am building a web site with ASP.NET 3.5, and most of the site structure is static enough to create a folder structure and aspx pages. However, the site administrators want the ability to add new pages to different sections of the site through a web interface and using a WYSIWYG editor. I am using nested master pages to give the different sections of the site their own menus. What I would like to do is have a generic page under each section of the site that uses the appropriate master page and has a place holder for content that could be loaded from a database. I would also like these "fake" pages to have a url like any other aspx page, as if they had corresponding files on the server. So rather than have my url be:
http://mysite.com/subsection/gerenicconent.aspx?contentid=1234
it would be something like:
http://mysite.com/subsection/somethingmeaningful.aspx
The problem is that somethingmeaningful.aspx does not exist, because the administrator created it through the web UI, and the content is stored in the database. What I'm thinking is that I'll implement an HTTP handler that handles requests for aspx files. In that handler, I'll check to see if the URL that was requested is an actual file or one of my "fake pages". If it is a request for a fake page, I'll re-route the request to the generic content page for the appropriate section, change the query string to request the appropriate data from the database, and rewrite the URL so that it looks to the user as if the fake page really exists. The problem I'm having right now is that I can't figure out how to route the request to the default handler for aspx pages. I tried to instantiate a PageHandlerFactory, but the constuctor is protected internal. Is there any way for me to tell my HttpHandler to call the HttpHandler that would normal be used to process a request? My handler code currently looks like this:
using System.Web;
using System.Web.UI;
namespace HandlerTest
{
public class FakePageHandler : IHttpHandler
{
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
if(RequestIsForFakedPage(context))
{
// reroute the request to the generic page and rewrite the URL
PageHandlerFactory factory = new PageHandlerFactory(); // this won't compile because the constructor is protected internal
factory.GetHandler(context, context.Request.RequestType, GetGenericContentPath(context), GetPhysicalApplicationPath(context)).ProcessRequest(context);
}
else
{
// route the request to the default handler for aspx pages
PageHandlerFactory factory = new PageHandlerFactory();
factory.GetHandler(context, context.Request.RequestType, context.Request.Path, context.Request.PhysicalPath).ProcessRequest(context);
}
}
public string RequestForPageIsFaked(HttpContext context)
{
// TODO
}
public string GetGenericContentPath(HttpContext context)
{
// TODO
}
public string GetPhysicalApplicationPath(HttpContext context)
{
// TODO
}
}
}
I still have some work to do to determine if the request is for a real page, and I haven't rewritten any URLs yet, but is something like this possible? Is there another way to create a PageHandlerFactory other than calling its constructor? Is there any way I can route the request up to the "normal" HttpHandler for an aspx page? I'd basically be saying "process this ASPX request as you normally would."
If you are using 3.5, look into using asp.net routing.
http://msdn.microsoft.com/en-us/library/cc668201.aspx
You would be better off using an http module for this, as in this case you can use the RewritePath method to route the request for fake pages, and do nothing for actual pages which will allow them to be processed as normal.
There is a good explanation of this here which also covers the benefits of using IIS 7.0 if that is an option for you.
I've just pulled this off a similar system we've just written.
This method takes care of physical pages and "fake" pages. You'll be able to ascertan how this fits with your fake page schema, I'm sure.
public class AspxHttpHandler : IHttpHandlerFactory
{
#region ~ from IHttpHandlerFactory ~
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
string url=context.Request.Url.AbsolutePath;
string[] portions = url.Split(new char[] { '/', '\\' });
// gives you the path, i presume this will help you identify the section and page
string serverSidePage=Path.Combine(context.Server.MapPath("~"),url);
if (File.Exists(serverSidePage))
{
// page is real
string virtualPath = context.Request.Url.AbsolutePath;
string inputFile = context.Server.MapPath(virtualPath);
try
{
// if it's real, send in the details to the ASPX compiler
return PageParser.GetCompiledPageInstance(virtualPath, inputFile, context);
}
catch (Exception ex)
{
throw new ApplicationException("Failed to render physical page", ex);
}
}
else
{
// page is fake
// need to identify a page that exists which you can use to compile against
// here, it is CMSTaregtPage - it can use a Master
string inputFile = context.Server.MapPath("~/CMSTargetPage.aspx");
string virtualPath = "~/CMSTargetPage.aspx";
// you can also add things that the page can access vai the Context.Items collection
context.Items.Add("DataItem","123");
return PageParser.GetCompiledPageInstance(virtualPath, inputFile, context);
}
public void ReleaseHandler(IHttpHandler handler)
{
}

Resources