MVC, How to read rendered HTML in a controller? - html

Maybe it´s a strange question, but imagine this:
//We all know that View is a method...
public ActionResult Something()
{
return View("index");
}
But what if I step before this method to perform some stats
public ActionResult Something()
{
return PerformStats(View("index"));
}
I will have a private method like this:
private ActionResult PerformStats(ViewResult viewResult)
{
//THIS IS WHAT I WANT TO ACCHIEVE:
//*********************************
var contentSent = viewResult.InnerHtml.Lengh; <<-- I wish!
return viewResult;
}
And latter, what i want to do, is to save that ammount of content sent to the client.
It doesn´t matter if it is the exactly quantity of html, even if I get the .count() of a json it will do the trick.
Is any way to know the rendered content on the controller?
Thanks!

OnActionExecuting: Called before action method executes. You can put stats related logic in there.
http://msdn.microsoft.com/en-us/library/system.web.mvc.iactionfilter.onactionexecuting(v=vs.98).aspx
OnActionExecuted: Called after action method executed.
http://msdn.microsoft.com/en-us/library/system.web.mvc.iactionfilter.onactionexecuted(v=vs.98).aspx
Within these methods you can access ActionExecuting and ActionExecutedContext
If you want to get a size of rendered HTML (partial or complete view), then you probably need to:
Find the view that you want to render
Store it in the string builder
Get its length
There is a question that explains how to render view as a string within the action method: In MVC3 Razor, how do I get the html of a rendered view inside an action?

Related

How do I handle a .ashx Generic Handler page in Blazor?

I am replacing a Project built using ASP.NET WebForms and I need to replace the .ashx Generic Handlers - but I need to keep the page names so an app that has these URIs hardcoded does not require updating.
I know how to deal with the logic, that is not the problem. The problem is that these pages are referenced by an app that I do not want to update, so I need to be able to use URIs that point to pages ending in .ashx.
I have tried everything I can think of. I had hoped to just use the #page directive as shown below:
#page "/mygenerichandler.ashx"
Unfortunately, that does not work. If it did, I would be all set.
I have seen pages telling me to handle the .ashx as a sort of parameter:
#page "/mygenerichandler/{ashx}" (and variations of this, none work)
This does not work.
I have tried just naming the pages with the .ashx extension. This does not work.
I do not want to have to update the apps that have the URLs embedded in them, but it is looking more and more like that is my only option.
Is there any way to accept a page request in Blazor to a page that is named something like "mygenerichandler.ashx"?
I figured it out. I am using the Middleware Pattern, and it turns out that this will execute early in the pipeline and allow me to inspect the URL for the .ashx extension and then route accordingly. I should be able to return a response from this point - I still have to implement that code, but it is not directly germane to this question so I will not cover it here.
public class HandlerTrapper
{
private readonly RequestDelegate _next;
public string? AccountID { get; set; }
public HandlerTrapper(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext httpContext) //, [FromQuery(Name = "AccountID")] string accountId
{
string? page = httpContext.Request.Path.Value?.Replace(#"/", "");
Console.WriteLine("Page Name is {0}, AccountID = {1}", page, AccountID);
if(page==null || !page.Contains(".ashx"))
return _next(httpContext);
AccountID = httpContext.Request.Query["AccountID"];
switch (page)
{
case "GetAmzRefreshToken":
break;
}
return _next(httpContext);
}
private
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class HandlerTrapperExtensions
{
public static IApplicationBuilder UseHandlerTrapper(this IApplicationBuilder builder)
{
return builder.UseMiddleware<HandlerTrapper>();
}
}
This is called as shown here in Program.cs:
app.UseHandlerTrapper();
I am pretty sure I can just return a Response from here and after implementing the code that does the work based on the incoming legacy page name, I should have a replacement for my .ashx Generic Handlers.
There is an even better solution which I implemented in my code. The WebApplication class has a UrlReWriter method that solves this problem quite elegantly when used in conjunction with the Controller Routing.
I added these lines to my Program.cs file - I placed them before the UseHttpRedirection and the UseRouting calls.:
RewriteOptions urlOptions = new RewriteOptions().AddRewrite(#"^(.*).ashx$", "api/$1", false);
urlOptions.AddRewrite(#"^(.*).inf$", "api/ComputerInfo", false);
app.UseRewriter(urlOptions);
That resolved the issue for both of the file type patterns I needed to handle, and I can add more if need be.

How to call Html.Raw and Html.Partial outside a razor page?

I have this requirements. I need to be able to write this code in my razor views:
#Filters.Render(Filters.DateRangeFilter, new DateRangeFilterParameters { });
The alternative is:
#Html.Partial("/Views/Shared/DateRangeFilter.cshtml", new DateRangeFilterParameters { });
In other words, I want Filters class to wrap Html.Partial. For that reason, I thought of this code:
public class Filters {
public const string DateRangeFilter = "/Views/Shared/DateRangeFilter.cshtml";
public static HtmlString Render(string filterPath, object parameters)
{
// Here I need to call Html.Partail, how?
}
}
To use Html.Raw within the controller you can request the injected IHtmlHelper service. E.g.:
HttpContext.RequestServices.GetService(typeof(IHtmlHelper)) as IHtmlHelper;
Or you can do your own implementation for the helper. And in order to use Html.Partial you need to use IRazorViewEngine, ViewContext, and other stuff. So basically you need to implement a service for that, and here is a good example Render Partial View To String Outside Controller Context.
I don't know if there is an easier way to achieve those, but that is what on my mind.

Adding new .cshtml view to Visual Studio solution

I'm trying to add a new WebPage.cshtml view to my VS solution, but when I compile/run my solution (F5) on my localhost, the view is empty. It's just a simple HTML page. Why is it empty?
I'm new to MVC development and so I suspect that I am missing an important concept related to how Models, Controllers, and Views work together to produce the view, however, I've read about it enough that I understand the basic concept of each.
I've been playing around with the controller and am guessing that I need something like the following before the view will work:
public ActionResult WebPage()
{
return View();
}
Is that correct?
What other pieces to the puzzle am I missing?
Based on your comment #Keven your trying to evaluate to the file but with Mvc your getting extensionless urls. That isn't technically a simple html page. It's a razor view that when data is applied to generates your output which is html.
I'm guessing your route.config looks something like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
This is saying that your expecting your urls to look like http://localhost:[port]/[controller name]/[action name] or in the case if you have a querystring parameter named id http://localhost:[port]/[controller name]/[action name]/[id]. To bring this full circle your url for that page based on the route above would be http://localhost:[port]/home/webpage.
Here's the documentation on routing

Return Multiple Collections for use in an MVC4 asp.net View page

I understand how to return a single collection (List<>) from a controller by returning View({mylist}); then referencing that with razor syntax #Model etc. inside the view page. In my case, I have several different collections I want to use separately on the page. I understand that I can make "the mother of all collections collection" and simply include the collections inside another collection (and return that one), but I'm wondering if there is some way in MVC4 that allows me to handle this situation more gracefully.
Thanks and Happy New Year!
You should create a view model. That is a regular class that will hold all the collection or properties you need in your view.
Check this post: http://msdn.microsoft.com/en-us/vs2010trainingcourse_aspnetmvc3fundamentals_topic7.aspx
You can make use of ViewBag for every collection in controller and use those ViewBag variables View
In controller
public ActionResult HotelDetails(List<HotelDetailsModel> model)
{
ViewBag.HotelDetails = model;
return View();
}
and in View
#{
List<HotelDetailsModel> hotels = (List<HotelDetailsModel>)ViewBag.SearchResultsModel;
if (hotels.Count > 0)
{
foreach (HotelDetailsModel item in hotels)
{
}
}
}

How do I use content containing razor markup as a parameter for a method?

What I am trying to do is to be able to write javascript blended with razor styled markup and methods inside .cshtml file and send this to a separate method for usage later on.
My .cshtml looks something like this:
#{SomeClass.SaveForLater(#<script type="text/javascript">window.alert('#Model.SomeParamter')}</script>);
And inside the class SomeClass:
public static void SaveForLater(HtmlString str) {
// will be using str.ToString() here and save the string output for use later on.
}
But what I receive is this error message:
CS1660: Cannot convert lambda expression to type 'System.Web.HtmlString' because it is not a delegate type
Am I using the wrong type for the argument or do I need to rethink the whole concept?
Solution
Thanks to SLaks below I ended up doing this:
public static void SaveForLater<T>(Func<T, HelperResult> template, dynamic model)
{
// template(model).ToHtmlString()
}
Using it like this:
#{SomeClass.SaveForLater<SomeModel>(
#<script type="text/javascript">window.alert('#Model.SomeParamter')</script>,
Model
);
You're trying to take an inline helper.
You need to accept a Func<Something, HelperResult>.