ASP.NET MVC 4 Dev Preview Razor in Sections Error - asp.net-mvc

Well, I think the ASP.NET MVC team released a pretty significant bug in the developer preview for asp.net mvc 4, or I'm doing something stupid... Here's the issue and steps to reproduce.
Create a new MVC 4 mobile application
create a new section in the layout (ex. #RenderSection("head",false))
in the controller action just throw a Message into the ViewBag
then in a view that uses the main layout, add the following code below.
#section head {
$(function() {
var newVariableName = "#(ViewBag.Message)";
});
}
You'll notice that the razor parser actually thinks that the section has been completed for the jquery on dom loaded ending brace instead of the section's ending brace. I tried the exact same code in an asp.net MVC 3 application and it worked with no issues.
Has anyone else come across this bug in the ASP.NET MVC 4 Developer Preview?

A quick hack to solving this issue is to use < text > blocks < /text > around the java script. Here's what it might look like until the ASP.NET MVC team resolves this bug.
#{
<text>
$(function()
{
var newVariableName = "#(ViewBag.Message)";
});
</text>
}

As mentioned above, try this in your cshtml file...
#section head {
#{
function JSMeth1()
{
// doing your stuff, razor parser wont suck
}
}}

Related

Blazor Server OnAfterRenderAsync not firing for components

I am trying to integrate blazor server into an existing MVC project, and am a little confused as to the requirements for getting my components to call OnAfterRenderAsync().
I have the following simple Index.razor page for testing made up of the following:
#page "/Index"
<h1>Counter</h1>
<p>Current count: #currentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
#code {
private int currentCount = 0;
protected override Task OnAfterRenderAsync(bool firstRender)
{
return base.OnAfterRenderAsync(firstRender);
}
private void IncrementCount()
{
currentCount++;
}
}
If I navigate directly to /Index the OnAfterRenderAsync method is hit just fine. But if I render Index.razor as a component in my cshtml like this:
#(await Html.RenderComponentAsync<Index>(RenderMode.ServerPrerendered ))
Then the OnAfterRenderAsync() is not hit.
I have MapBlazorHub() in my Startup.cs and <script src="~/_framework/blazor.server.js"></script> in my _Host.cshtml.
Have I missed a step somewhere or is this behavior by design in Blazor Server, and if so, is there some workaround?
I need some reusable blazor components to be able to work with JS libraries that were implemented in the MVC project, so I need those components to be able to call OnAfterRenderAsync() without going directly to the page URL
After inspecting the sources in developer tools I saw that the folder for _framework was not present in my component.
Even though I could navigate to https://myurl/_framework/blazor.server.js and saw the file was present in my project, my component didn't have access to it. adding the script tag <script src="~/_framework/blazor.server.js"></script> directly to my cshtml file that was rendering my Blazor component fixed my issue.

Render view from DB in MVC 6

We are working on ASP.NET MVC 6 project and it's necessary to render Razor views from other than file system source (Azure Blob storage in particular but it's not important).
Earlier (in MVC 5) it was possible to create and register custom VirtualPathProvider which can take view content from DB or resource DLLs (for example).
It seems that approach has been changed in MVC 6. Does anybody know where to look for?
UPD:
Here is an example of code I'm looking for:
public IActionResult Index()
{
ViewBag.Test = "Hello world!!!";
string htmlContent = "<html><head><title>Test page</title><body>#ViewBag.Test</body></html>";
return GetViewFromString(htmlContent);
}
The question is: how to implement that GetViewFromString function?
You need to configure a ViewLocationExpander:
services.SetupOptions<RazorViewEngineOptions>(options =>
{
var expander = new LanguageViewLocationExpander(
context => context.HttpContext.Request.Query["language"]);
options.ViewLocationExpanders.Insert(0, expander);
});
and here is the implementation for the LanguageViewLocationExpander :
https://github.com/aspnet/Mvc/blob/ad8ab4b8fdb27494f5dece6f1186acea03f9dd52/test/WebSites/RazorWebSite/Services/LanguageViewLocationExpander.cs
Basing your AzureBlobLocationExpander on that one should put you in the right track.
Just posted a sample of store .cshtml in Azure Blob Storage to GitHub.
See also my answer to another question on this
Basically you need to create an implementation of IFileProvider. This can then be registered in Startup.cs by configuring RazorViewEngineOptions
services.Configure<RazorViewEngineOptions>(options =>
{
options.FileProvider = new AzureFileProvider(Configuration);
});

Umbraco PartialView Macros and #helper

This has been driving me crazy for about 3 hours now. We upgraded from Umbraco 4 to Umbraco 7 and now our site menus are broken. We used to use a script that traversed the nodes and created a CSS menu system.
So I have started reading up on the new stuff and I just can't even get a #helper or #functions block to work. The script is now using a PartialView Macro that uses a parameter called MenuNode that is the Node I want to traverse down.
Here's the code that works:
#inherits Umbraco.Web.Macros.PartialViewMacroPage
#{
var menuNode1 = string.IsNullOrEmpty((string)Model.MacroParameters["MenuNode"])? 0 : Convert.ToInt32(Model.MacroParameters["MenuNode"]);
}
<h1>#menuNode1</h1>
As soon as I try to add a #helper or #functions like in the Navigation PartialView example provided in Umbraco it stops working. Here is what breaks it.
#inherits Umbraco.Web.Macros.PartialViewMacroPage
#{
var menuNode1 = string.IsNullOrEmpty((string)Model.MacroParameters["MenuNode"])? 0 : Convert.ToInt32(Model.MacroParameters["MenuNode"]);
}
#TestHelper(menuNode1)
#helper TestHelper(var testvalue)
{
<h1>#testvalue</h1>
}
Can anyone point me at what I'm doing wrong?
Problem solved. It was because I was using var in the #helper parameter list, changed to dynamic and it worked fine.

MVC 5 HTML Code Snippets not working after upgrade of MVC

I recently upgraded from MVC 4 to MVC 5. Since that upgrade, my Code Snippets have not been working that I previously created. There are almost no resources online for this (for MVC 5) and any previously tutorial that shows how to create a snippet does not work. I'm using Visual Studio 2013 (update 1, but update 2 also does not fix it).
Here's my snippet:
<CodeSnippet Format="1.1.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<Header>
<Title>JSLessBehavior</Title>
<Author>OptixConnect LLC</Author>
<Shortcut>jslessBehavior</Shortcut>
<Description>Markup snippet for a JSLess behavior helper</Description>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>name</ID>
<ToolTip>name</ToolTip>
<Default>execute</Default>
</Literal>
<Literal>
<ID>toggle</ID>
<ToolTip>toggle</ToolTip>
<Default>toggle</Default>
</Literal>
<Literal>
<ID>target</ID>
<ToolTip>target</ToolTip>
<Default>self</Default>
</Literal>
<Literal>
<ID>params</ID>
<ToolTip>params</ToolTip>
<Default>params</Default>
</Literal>
</Declarations>
<Code Language="html">
<![CDATA[data-jsless="#(new object[] { new { name = "$name$", method = "$toggle$", target = "$target$", #params = new object[] { "$params$" } } }.ToJsonObject())"]]>
</Code>
</Snippet>
The expected outcome would be if I start typing <div jslessBehavior and then press tab + tab it should auto generate that snippet. Has something changed when going from MVC 4 to 5?
The default code snippets that Visual Studio provides work. And I tried copying one of their HTML snippets and just refactoring it to use my code with no luck :(

ASP.NET MVC 4 Mobile Display Modes Stop Working

Mobile display modes in ASP.NET MVC 4 stop serving the correct views after about an hour of uptime, despite browser overrides correctly detecting an overridden mobile device.
Recycling the application pool temporarily solves the problem.
The new browser override feature correctly allows mobile devices to view the desktop version of a site, and vice-versa. But after about an hour of uptime, the mobile views are no longer rendered for a mobile device; only the default desktop Razor templates are rendered. The only fix is to recycle the application pool.
Strangely, the browser override cookie continues to function. A master _Layout.cshtml template correctly shows "mobile" or "desktop" text depending on the value of ViewContext.HttpContext.GetOverriddenBrowser().IsMobileDevice, but the wrong views are still being rendered. This leads me to believe the problem lies with the DisplayModes.
The action in question is not being cached:
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
I am using 51Degrees for mobile detection, but I don't think this should affect the overridden mobile detection. Is this a bug in DisplayModes feature for ASP.NET MVC 4 Beta & Developer Preview, or am I doing something else wrong?
Here is my DisplayModes setup in Application_Start:
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("iPhone")
{
ContextCondition = context =>
context.GetOverriddenBrowser().IsMobileDevice
&& (context.Request.UserAgent.IndexOf("iPhone", StringComparison.OrdinalIgnoreCase) >= 0
|| context.Request.UserAgent.IndexOf("Android", StringComparison.OrdinalIgnoreCase) >= 0
|| !context.Request.Browser.IsMobileDevice)
});
/* Looks complicated, but renders Home.iPhone.cshtml if the overriding browser is
mobile or if the "real" browser is on an iPhone or Android. This falls through
to the next instance Home.Mobile.cshtml for more basic phones like BlackBerry.
*/
DisplayModeProvider.Instance.Modes.Insert(1, new DefaultDisplayMode("Mobile")
{
ContextCondition = context =>
context.GetOverriddenBrowser().IsMobileDevice
});
This is a known issue in MVC 4 (Codeplex: #280: Multiple DisplayModes - Caching error, will show wrong View). This will be fixed in the next version of MVC.
In the meantime you can install a workaround package available here: http://nuget.org/packages/Microsoft.AspNet.Mvc.FixedDisplayModes.
For most applications simply installing this package should resolve the issue.
For some applications that customize the collection of registered view engines, you should make sure that you reference Microsoft.Web.Mvc.FixedRazorViewEngine or Microsoft.Web.Mvc.FixedWebFormViewEngine, instead of the default view engine implementations.
I had a similar issue and it turned out to be a bug when mixing webforms based desktop views with razor based mobile views.
See http://aspnetwebstack.codeplex.com/workitem/276 for more info
Possibly a bug in ASP.NET MVC 4 related to caching of views, see:
http://forums.asp.net/p/1824033/5066368.aspx/1?Re+MVC+4+RC+Mobile+View+Cache+bug+
I can't speak for this particular stack (I'm still in MVC2) but check your output caching setup (either in your controllers or views - and in your web.config in your app and at the machine level). I've seen it work initially for the first few users and then a desktop browser comes in right around the time ASP decides to cache, then everyone gets the same view. We've avoided output caching as a result, hoping this would get addressed later.
If you want all mobile devices to use the same mobile layout you can use
DisplayModeProvider.Instance.Modes.Insert(1, new DefaultDisplayMode("Mobile")
{
ContextCondition = context =>
context.GetOverriddenBrowser().IsMobileDevice
});
And of course you need to make a view in the shared layout folder named _Layout.Mobile.cshtml
If you want to have a separate layout for each type of device or browser you need to do this;
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("Android")
{
ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf
("Android", StringComparison.OrdinalIgnoreCase) >= 0)
});
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("iPhone")
{
ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf
("iPhone", StringComparison.OrdinalIgnoreCase) >= 0)
});
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("Mobile")
{
ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf
("IEMobile", StringComparison.OrdinalIgnoreCase) >= 0)
});
And of course you need to make a view in the shared layout folder for each named
_Layout.Android.cshtml
_Layout.iPhone.cshtml
_Layout.Mobile.cshtml
Can you not just do this?
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
// Code removed for clarity.
// Cache never expires. You must restart application pool
// when you add/delete a view. A non-expiring cache can lead to
// heavy server memory load.
ViewEngines.Engines.OfType<RazorViewEngine>().First().ViewLocationCache =
new DefaultViewLocationCache(Cache.NoSlidingExpiration);
// Add or Replace RazorViewEngine with WebFormViewEngine
// if you are using the Web Forms View Engine.
}
So guys here is the answer to all of your worries..... :)
To avoid the problem, you can instruct ASP.NET to vary the cache entry according to whether the visitor is using a mobile device. Add a VaryByCustom parameter to your page’s OutputCache declaration as follows:
<%# OutputCache VaryByParam="*" Duration="60" VaryByCustom="isMobileDevice" %>
Next, define isMobileDevice as a custom cache parameter by adding the following method override to your Global.asax.cs file:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (string.Equals(custom, "isMobileDevice", StringComparison.OrdinalIgnoreCase))
return context.Request.Browser.IsMobileDevice.ToString();
return base.GetVaryByCustomString(context, custom);
}
This will ensure that mobile visitors to the page don’t receive output previously put into the cache by a desktop visitor.
please see this white paper published by microsoft. :)
http://www.asp.net/whitepapers/add-mobile-pages-to-your-aspnet-web-forms-mvc-application
Thanks and Keep coding.....

Resources