Episerver Output Cache - implementing - asp.net-mvc

I am trying to follow some instructions I found online in order to implement output caching for episerver.
In my web.config I have set up the following:
<caching>
<outputCache enableOutputCache="true">
</outputCache>
<outputCacheSettings>
<outputCacheProfiles>
<add name="ClientResourceCache" enabled="true" duration="3600" varyByParam="*" varyByContentEncoding="gzip;deflate" />
</outputCacheProfiles>
</outputCacheSettings>
</caching>
As a test, I selected StartPageController.cs and added the [ContentOutputCache] tag like so:
[ContentOutputCache]
public class StartPageController : PageControllerBase<StartPage>
{
public ActionResult Index(StartPage currentPage)
{
var model = PageViewModel.Create(currentPage);
if (SiteDefinition.Current.StartPage.CompareToIgnoreWorkID(currentPage.ContentLink))
{
var editHints = ViewData.GetEditHints<PageViewModel<StartPage>, StartPage>();
editHints.AddConnection(m => m.Layout.Logotype, p => p.SiteLogotype);
editHints.AddConnection(m => m.Layout.Footer, p => p.FooterBlock);
}
return View(model);
}
}
}
Then the instructions say:
At some point on a page load you need to add the following code:
public void SetResposneHeaders()
{
HttpContext.Current.Response.Cache.SetExpires(DateTime.Now.AddMinutes(2.0));
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.Public);
HttpContext.Current.Response.Cache.SetValidUntilExpires(true);
}
Due to my limited knowledge of .NET, MVC etc.. this part confuses me as I am not sure in which file to place it and where? Does this go in StartPageController.cs? Or somewhere else?
Any pointers would be appreciated.
The instructions I am trying to follow are here.

Related

How to redirect all url which not related to controllers (Asp.Net MVC)

I want to always redirect to my home page if someone puts something like www.example/bla/qwerty.hm (it doesnt have controller and it doesnt exist) to www.example.com.
I want to redirect all urls, which have not controller, to my home page and not show error. For example: www.example.com/auto (auto is not controller) and it will be redirect to my home page. How do I do that?
I tried Route configuration
routes.MapRoute(
name: "MyRedirect"
, url: "{contextRedirect}/{*tasks}"
, defaults: new { controller = "Redirect", action = "TotalRedirect", contextRedirect = "" }
);
...
public ActionResult TotalRedirect(string contextRedirect)
{
return RedirectToAction("Index", "Home");
}
but this is called every time and it makes an infinity loop (it is called every time when redirectToAction is called)
The problem disappears if I write all RouteMaps for all existing controllers before this MapRoute, but I have lot of controllers, and I want avoid writing RouteMaps for all controllers.
Instead of MapRoute I tried Web.config and change errors
<customErrors mode="On">
<error redirect="/Error" statusCode="404" />
<error redirect="/Error" statusCode="500" />
</customErrors>
Error is controller which return RedirectToAction and I got same result as point 1 (infinity loop). But when I change /Error to /Home it is working (because Home return View), but on Url path is saved error text Home?aspxerrorpath=/auto. After redirect I do not want show text, that means if page will be redirected in to www.example.com, it not show www.example/Home?aspxerrorpath=/auto.
I am new to Asp.net MVC, and I don't know proper way how to do it.
Update
After some research I think there are two ways how to do it.
(Thanks to KevinLamb) Redirect from error with Application_Error and Web.Confing httpError. This is working for me:
This settings is put in Web.Confing on Project level, that means first Web.Config you see in Project Explorer (there is second Web.Config on View folder). Then you must create Controller named Error with ActionResult ErrorHandle
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404"/>
<error statusCode="404" responseMode="ExecuteURL" path="/Error/ErrorHandle"/>
<remove statusCode="400"/>
<error statusCode="400" responseMode="ExecuteURL" path="/Error/ErrorHandle"/>
<remove statusCode="500"/>
<error statusCode="500" responseMode="ExecuteURL" path="/Error/ErrorHandle"/>
</httpErrors>
...
// Error Controller .cs
namespace MyWebApp.Controllers
{
public class ErrorController : Controller
{
// GET: Error
public ActionResult Index()
{
return RedirectToAction("Home", "Index");
}
public ActionResult ErrorHandle()
{
return RedirectToAction("Index", "Home");
}
}
}
...
// code inside Global.asax.cs MvcApplication class
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
//Add some logging here
if(ex.GetType().IsAssignableFrom(typeof(HttpException)))
{
//Possibly log that you're redirecting the user
Response.Clear();
Response.Redirect("~/");
}
}
This is easy part.
Another way I discover is create HttpHandler or HttpModule. I am new in MVC and Asp.Net world and HttpHandler is not working allways for me, because it works only once, then only when application change page, but it not detect Url created by user (only first time). HttpModule work allways for me, but I don't know if it is good or bad. It is little harder then 1. point but you don't need Application_Error and httpErrors in Web.Config.
If you have httpErrors and Application_Error, delete it and create Module (Right click on Project > Add new Item > In search put "module" > and select Asp.Net Module. It create module class with Init and Dispose methods. Then create your own method and register it to BeginRequest.
And here is code for my HttpModule
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Reflection;
using System.Linq;
namespace MyWebApp
{
public class ErrorHttpModule : IHttpModule
{
#region IHttpModule Members
public void Dispose()
{
//clean-up code here.
}
public void Init(HttpApplication context)
{
// Below is an example of how you can handle LogRequest event and provide
// custom logging implementation for it
// context.LogRequest += new EventHandler(OnLogRequest);
context.BeginRequest += new EventHandler(BR); // register your own method in to Event where you check Url
}
#endregion
private HttpContext context = null;
public void BR(Object source, EventArgs e)
{
context = System.Web.HttpContext.Current;
// collect all controllers in web application
Assembly asm = Assembly.GetAssembly(typeof(MyWebApp.MvcApplication)); // need using System.Reflection;
var controlleractionlist = asm.GetTypes()
.Where(type => typeof(System.Web.Mvc.Controller).IsAssignableFrom(type)) // need using System.Linq;
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public))
.Where(m => !m.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any())
.Select(x => new { Controller = x.DeclaringType.Name, Action = x.Name, ReturnType = x.ReturnType.Name, Attributes = String.Join(",", x.GetCustomAttributes().Select(a => a.GetType().Name.Replace("Attribute", ""))) })
.OrderBy(x => x.Controller).ThenBy(x => x.Action).ToList();
// Get Url
string page = "";
if (context != null)
{
page = context.Request.Url.PathAndQuery;
}
string newUrl;
if (!String.IsNullOrEmpty(page))
{
bool continute = true;
// does url contain controller or action?
foreach (var controller in controlleractionlist)
{
string cpath = "/" + controller.Controller.Replace("Controller", "") + (controller.Action == "Index" ? "" : "/" + controller.Action);
if (cpath == page)
{
// Yes, don't continue to redirect
continute = false;
break;
}
else if (page == ("/" + controller.Action))
{
// Yes, don't continue to redirect
continute = false;
break;
}
}
// does page load your content, script etc.. ?
if (page.Contains("Content/") == true
|| page.Contains("Scripts/") == true
|| page.Contains(".ico") == true
|| page == "/"
)
{
// Yes, don't redirect.
continute = false;
}
if (continute)
{
// anything else will redirect to Home page
var urlHelper = new UrlHelper(context.Request.RequestContext); // nned using System.Web.Mvc;
newUrl = urlHelper.Action("About", "Home");
context.Response.Status = "301 Moved Permanently";
context.Response.AddHeader("Location", newUrl);
context.Response.End();
}
}
}
public void OnLogRequest(Object source, EventArgs e)
{
//custom logging logic can go here
}
}
}
And finnaly add module in to Web.Config (on Project level (ProjectName > Web.Config), not inside folder under Project (ProjectName > View > Web.Config))
<system.webServer>
<modules>
<add name="MyHttpErrorModule" type="MyWebApp.ErrorHttpModule, MyWebApp"/>
</modules>
</system.webServer>
Extended question
Either first or second point, I have problem with both, when in url is put characters like that /abc=45%$#r. It leads to Bad Request - Invalid URL HTTP Error 400. The request URL is invalid. and this don't detect Application_Error, httpErrors in Web.Config or my HttpModule with BeginRequest. Therefore I think this is on IIS settings but what I need to set up? It looks like those characters make IIS mess %$.
First you'll need to set errors to pass through in IIS so that your application can handle the file/parameter links instead of IIS. Add the below to your Web.config:
<system.webServer>
<httpErrors existingResponse="PassThrough" />
</system.webServer>
If you want to redirect to your home page (without any extra parameters in the URL) when you get an issue with a user going to the wrong controller/action, add the following code to your Global.asax.cs.
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
//Add some logging here
if(ex.GetType().IsAssignableFrom(typeof(HttpException)))
{
//Possibly log that you're redirecting the user
Response.Clear();
Response.Redirect("~/");
}
}
Basically, what this is is doing is making all errors in your ASP.NET MVC website go through this method. Then it checks to see if the exception is a HttpException. If it is, it will redirect to your home controller and action. If it is not a HttpException it will continue doing the error handling process. This way you can keep handling the important errors properly.

How to configure HTML Minification in ASP.NET MVC

I would like to configure HTML minification to my ASP>NET MVC5 web application.
I installed Nuget
Install-Package WebMarkupMin.Mvc
Then I add Filter Attributte:
[MinifyHtmlAttribute]
public ActionResult Index()
{
return View();
}
But the HTML minification does not work.
Nuget Installation add few lines to the web.config:
<sectionGroup name="webMarkupMin">
<section name="core" type="WebMarkupMin.Core.Configuration.CoreConfiguration, WebMarkupMin.Core" />
<section name="webExtensions" type="WebMarkupMin.Web.Configuration.WebExtensionsConfiguration, WebMarkupMin.Web" />
</sectionGroup>
<webMarkupMin xmlns="http://tempuri.org/WebMarkupMin.Configuration.xsd">
<core>
<css>
<minifiers>
<add name="NullCssMinifier" displayName="Null CSS Minifier" type="WebMarkupMin.Core.Minifiers.NullCssMinifier, WebMarkupMin.Core" />
<add name="KristensenCssMinifier" displayName="Mads Kristensen's CSS minifier" type="WebMarkupMin.Core.Minifiers.KristensenCssMinifier, WebMarkupMin.Core" />
</minifiers>
</css>
<js>
<minifiers>
<add name="NullJsMinifier" displayName="Null JS Minifier" type="WebMarkupMin.Core.Minifiers.NullJsMinifier, WebMarkupMin.Core" />
<add name="CrockfordJsMinifier" displayName="Douglas Crockford's JS Minifier" type="WebMarkupMin.Core.Minifiers.CrockfordJsMinifier, WebMarkupMin.Core" />
</minifiers>
</js>
<html whitespaceMinificationMode="Medium" removeHtmlComments="true"
removeHtmlCommentsFromScriptsAndStyles="true"
removeCdataSectionsFromScriptsAndStyles="true"
useShortDoctype="true" useMetaCharsetTag="true"
emptyTagRenderMode="NoSlash" removeOptionalEndTags="true"
removeTagsWithoutContent="false" collapseBooleanAttributes="true"
removeEmptyAttributes="true" attributeQuotesRemovalMode="Html5"
removeRedundantAttributes="true"
removeJsTypeAttributes="true" removeCssTypeAttributes="true"
removeHttpProtocolFromAttributes="false"
removeHttpsProtocolFromAttributes="false"
removeJsProtocolFromAttributes="true"
minifyEmbeddedCssCode="true" minifyInlineCssCode="true"
minifyEmbeddedJsCode="true" minifyInlineJsCode="true"
processableScriptTypeList="" minifyKnockoutBindingExpressions="false"
minifyAngularBindingExpressions="false" customAngularDirectiveList="" />
<logging>
<loggers>
<add name="NullLogger" displayName="Null Logger" type="WebMarkupMin.Core.Loggers.NullLogger, WebMarkupMin.Core" />
<add name="ThrowExceptionLogger" displayName="Throw exception logger" type="WebMarkupMin.Core.Loggers.ThrowExceptionLogger, WebMarkupMin.Core" />
</loggers>
</logging>
</core>
</webMarkupMin>
The html element was added by me manually according to documentation.
Am I missing something?
Web application may be in debug mode. In order to switch it to release mode you need to edit the Web.config file:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
...
<system.web>
<compilation debug="false" ... />
...
</system.web>
...
</configuration>
In addition, you can disable dependence on web application mode. Using the following settings:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
...
<webMarkupMin xmlns="http://tempuri.org/WebMarkupMin.Configuration.xsd">
<webExtensions disableMinificationInDebugMode="false"
disableCompressionInDebugMode="false" />
...
</webMarkupMin>
...
</configuration>
So large library with so difficult usage and configuration... Are you sure need all this for just the HTML minification?
Create a new filter under the Filters subfolder of your project and call it CompactHtmlFilterAttribute Use the following code:
public class CompactHtmlFilterAttribute : ActionFilterAttribute
{
public class WhitespaceFilter : MemoryStream
{
private string Source = string.Empty;
private Stream Filter = null;
public WhitespaceFilter(HttpResponseBase HttpResponseBase)
{
Filter = HttpResponseBase.Filter;
}
public override void Write(byte[] buffer, int offset, int count)
{
Source = UTF8Encoding.UTF8.GetString(buffer).Replace("\r", "").Replace("\n", "").Replace("\t", "");
Filter.Write(UTF8Encoding.UTF8.GetBytes(Source), offset, UTF8Encoding.UTF8.GetByteCount(Source));
}
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
#if DEBUG
base.OnActionExecuting(filterContext);
#else
try
{
filterContext.HttpContext.Response.Filter = new WhitespaceFilter(filterContext.HttpContext.Response);
}
catch (Exception) { }
#endif
}
}
Pay atention on the #if DEBUG dirrective. HTML will be minified only in release configuration, while on debug the original code will be kept for the better readability.
Add this attribute to the controller methods
[CompactHtmlFilter]
public ActionResult Index()
{
return View();
}
and we're done.
You need to add the following to enable the webextensions (from the doc):
<webMarkupMin xmlns="http://tempuri.org/WebMarkupMin.Configuration.xsd">
…
<webExtensions enableMinification="true" disableMinificationInDebugMode="true"
enableCompression="true" disableCompressionInDebugMode="true"
maxResponseSize="100000" disableCopyrightHttpHeaders="false" />
…
</webMarkupMin>
Note that it's outside the <core> element.
also in your view markup you should have the attribute as:
[MinifyHtml]
Itshouldn't have the ..Attribute at the end of it.

Getting logged out in short time in mvc application

I have an ASP.NET MVC application and I have received complaints from users that they are getting logged out after a relatively short time.
I have the following settings in web.config but apparently it does not work.
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
Any idea why this is not working?
I believe you have same issue as here
So you should place machine key in your Web.Config. Ex:
<configuration>
<system.web>
<machineKey decryptionKey="F6722806843145965513817CEBDECBB1F94808E4A6C0B2F2,IsolateApps" validationKey="C551753B0325187D1759B4FB055B44F7C5077B016C02AF674E8DE69351B69FEFD045A267308AA2DAB81B69919402D7886A6E986473EEEC9556A9003357F5ED45,IsolateApps" />
</system.web>
</configuration>
You can learn how to generate machine key here or here
If you want user stays login until his/her browser is open, I do a trick. I create an Action or any Handler like below, it just return some dummy text (ex: context.User.Identity.IsAuthenticated), and then use jQuery.get to request this. I keeps user login because browser keep sends requests:
Handler:
public class KeepAlive : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.Write(context.User.Identity.IsAuthenticated);
}
public bool IsReusable
{
get
{
return false;
}
}
}
MVC Action:
public bool KeppChecking()
{
return User.Identity.IsAuthenticated;
}
jQuery:
setInterval(function () {
$.get('/Ajax/KeepAlive.ashx');
}, 10000);

Catch request to a file in content folder to handle with controller action

I got a third party widget library I have to use. This library has a hardcoded string to a file. Is it possible to intercept this request with routes? My try looked like this:
routes.MapRoute(name: "ribbonbar",
url: "Content/Ribbon/Scripts/Ribbon.Tabs.foo",
defaults: new { controller = "Ribbon", action = "Index" });
But I only got a 404. Is it impossible or do I have mixed something up?
Yes, this is possible. You will need to add the following handler to your web.config in order to ensure that this request goes through the managed pipeline and your routes:
<system.webServer>
<handlers>
...
<add
name="MyCustomhandler"
path="Content/Ribbon/Scripts/Ribbon.Tabs.foo"
verb="GET"
type="System.Web.Handlers.TransferRequestHandler"
preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
and then you could have the following controller action to serve this request:
public class RibbonController
{
// GET Content/Ribbon/Scripts/Ribbon.Tabs.foo
public ActionResult Index()
{
var file = Server.MapPath("~/App_Data/foo.bar");
return File(file, "application/foo-bar");
}
}
You could also serve all requests to Content/Ribbon/Scripts/* from the same controller action:
<system.webServer>
<handlers>
...
<add
name="MyCustomhandler"
path="Content/Ribbon/Scripts/*"
verb="GET"
type="System.Web.Handlers.TransferRequestHandler"
preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
and a route like that:
routes.MapRoute(
name: "ribbonbar",
url: "Content/Ribbon/Scripts/{name}",
defaults: new { controller = "Ribbon", action = "Index" }
);
with an action like that:
public class RibbonController
{
// GET Content/Ribbon/Scripts/*
public ActionResult Index(string name)
{
...
}
}
Alternatively to using a specific handler you could have enabled managed modules for all requests like that:
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
...
</system.webServer>
But I wouldn't recommend you enabling this option because now all requests will go through the managed pipeline, even those from for static resources which might have a negative impact on the performance of your application. It's much better to selectively enable this only for selected urls.

NHibernate.Search with S#arp Architecture

Has anyone managed to get nhibernate.search (Lucene) to work with S#arp Architecture? I think I have it all wired up correctly except Luke shows no records or indexes when I run my indexing method. The index files for the entity are created (segments.gen & segments_1) but both are 1kb in size which explains why Luke shows no data.
I execute no other code specific to getting search to work, am I missing some initialisation calls? I assume the listeners get picked up automatically by nhibernate.
In my Web project I have:
NHibernate.config
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.connection_string">Data Source=.\SQLEXPRESS;Database=MyDatabase;Integrated Security=True;</property>
<property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="show_sql">true</property>
<property name="generate_statistics">true</property>
<property name="connection.release_mode">auto</property>
<property name="adonet.batch_size">500</property>
<property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
<listener class='NHibernate.Search.Event.FullTextIndexEventListener, NHibernate.Search' type='post-insert'/>
<listener class='NHibernate.Search.Event.FullTextIndexEventListener, NHibernate.Search' type='post-update'/>
<listener class='NHibernate.Search.Event.FullTextIndexEventListener, NHibernate.Search' type='post-delete'/>
</session-factory>
</hibernate-configuration>
Web.Config
<configSections>
...
<section name="nhs-configuration" type="NHibernate.Search.Cfg.ConfigurationSectionHandler, NHibernate.Search" requirePermission="false" />
</configSections>
<nhs-configuration xmlns='urn:nhs-configuration-1.0'>
<search-factory>
<property name="hibernate.search.default.directory_provider">NHibernate.Search.Store.FSDirectoryProvider, NHibernate.Search</property>
<property name="hibernate.search.default.indexBase">~\Lucene</property>
</search-factory>
</nhs-configuration>
My entity is decorated as follows:
[Indexed(Index = "Posting")]
public class Posting : Entity
{
[DocumentId]
public new virtual int Id
{
get { return base.Id; }
protected set { base.Id = value; }
}
[Field(Index.Tokenized, Store = Store.Yes)]
[Analyzer(typeof(StandardAnalyzer))]
public virtual string Title { get; set; }
[Field(Index.Tokenized, Store = Store.Yes)]
[Analyzer(typeof(StandardAnalyzer))]
public virtual string Description { get; set; }
public virtual DateTime CreatedDate { get; set; }
...
}
And I run the following to create the index
public void BuildSearchIndex()
{
FSDirectory directory = null;
IndexWriter writer = null;
var type = typeof(Posting);
var info = new DirectoryInfo(GetIndexDirectory());
if (info.Exists)
{
info.Delete(true);
}
try
{
directory = FSDirectory.GetDirectory(Path.Combine(info.FullName, type.Name), true);
writer = new IndexWriter(directory, new StandardAnalyzer(), true);
}
finally
{
if (directory != null)
{
directory.Close();
}
if (writer != null)
{
writer.Close();
}
}
var fullTextSession = Search.CreateFullTextSession(this.Session);
// select all Posting objects from NHibernate and add them to the Lucene index
foreach (var instance in Session.CreateCriteria(typeof(Posting)).List<Posting>())
{
fullTextSession.Index(instance);
}
}
private static string GetIndexDirectory()
{
var nhsConfigCollection = CfgHelper.LoadConfiguration();
var property = nhsConfigCollection.DefaultConfiguration.Properties["hibernate.search.default.indexBase"];
var fi = new FileInfo(property);
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fi.Name);
}
Found an answer to my question so here it is in case anyone else comes by this problem.
NHS configuration in web.config contained such lines:
<property name="hibernate.search.default.directory_provider">NHibernate.Search.Store.FSDirectoryProvider, NHibernate.Search</property>
<property name="hibernate.search.default.indexBase">~\SearchIndex</property>
First line should be removed because in this case NHS consider it as though index shares. It is known NHibernateSearch issue.
If the site is run from IIS, Network Service should have all permissions on search index directory.
Jordan, are you using the latest bits from NHContrib for NHibernate.Search? I just recently updated my bits and I am running into the same situation you are. It works for me on older bits, from about July. But I can't get my indexes to create either. Your config looks right, same as mine. And your indexing method looks good too.
Jordan, there is now an alternative to attribute based Lucene.NET mapping called FluentNHibernate.Search, this project is hosted on codeplex.
http://fnhsearch.codeplex.com/
public class BookSearchMap : DocumentMap<Book>
{
public BookSearchMap()
{
Id(p => p.BookId).Field("BookId").Bridge().Guid();
Name("Book");
Boost(500);
Analyzer<StandardAnalyzer>();
Map(x => x.Title)
.Analyzer<StandardAnalyzer>()
.Boost(500);
Map(x => x.Description)
.Boost(500)
.Name("Description")
.Store().Yes()
.Index().Tokenized();
}
}

Resources