How to use UseExceptionHandler in the mvc startup without redirecting the user? - asp.net-core

I have a ASP.NET Core 2.1 MVC application, and I'm trying to return a separate html view when an exception occurs. The reason for this is that if there are errors, we don't want google to register the redirects to the error page for our SEO (I've omitted development settings to clear things up).
Our startup contained this:
app.UseExceptionHandler("/Error/500"); // this caused a redirect because some of our middleware.
app.UseStatusCodePagesWithReExecute("/error/{0}");
But we want to prevent a redirect, so we need to change the UseExceptionHandler.
I've tried to use the answer from this question like below:
app.UseExceptionHandler(
options =>
{
options.Run(
async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("sumtin wrong").ConfigureAwait(false);
});
});
But this gives a very ugly page without any styling. Another solution we've tried to use is creating an error handling middle ware, but there we run into the same problem there where we can't add a view.
How can I return a styled view in case of an exception, without redirecting the user?
EDIT: the UseExceptionHandler doesn't cause a redirect, it was caused by a bug in some of our middleware.

How can I return a styled view in case of an exception, without redirecting the user?
You're almost there. You could rewrite(instead of redirect) the path, and then serve a HTML according to current path.
Let's say you have a well-styled sth-wrong.html page in your wwwroot/ folder. Change the code as below:
app.UseExceptionHandler(appBuilder=>
{
// override the current Path
appBuilder.Use(async (ctx, next)=>{
ctx.Request.Path = "/sth-wrong.html";
await next();
});
// let the staticFiles middleware to serve the sth-wrong.html
appBuilder.UseStaticFiles();
});
[Edit] :
Is there anyway where I can make use of my main page layout?
Yes. But because a page layout is a View Feature that belongs to MVC, you can enable another MVC branch here
First create a Controllers/ErrorController.cs file :
public class ErrorController: Controller
{
public IActionResult Index() => View();
}
and a related Views/Error/Index.cshtml file:
Ouch....Something bad happens........
Add a MVC branch in middleware pipeline:
app.UseExceptionHandler(appBuilder=>
{
appBuilder.Use(async (ctx, next)=>{
ctx.Request.Path = "/Error/Index";
await next();
});
appBuilder.UseMvc(routes =>{
routes.MapRoute(
name: "sth-wrong",
template: "{controller=Error}/{action=Index}");
});
});
Demo:

Related

Catch url with no route

To catch an url which doesn't have a route one can do something like this
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2).AddRazorPagesOptions(options =>
{
options.Conventions.AddPageRoute("/Error/Nopage", "{*url}");
});
But then I noticed that the OnGet() method in Nopage.cshtml.cs gets called for all routes, even the one's that has a route.
Is this the standard behavior, and how one is suppose to catch non-routed url's? ...or is there some other way to catch url with no routes.
Also, from a workload/performance perspective, it feels kind of wrong to initiate and load a page model that will not be used.
As a note, prior to using AddPageRoute I did like this in Startup.cs, which worked just fine, though the above felt more as how one is suppose to do it.
app.UseMvc();
// Page missing in MVC...
app.Use(async (context, next) =>
{
//simplified code snippet
s = await File.ReadAllTextAsync(Path.Combine(env.WebRootPath, "pagemissing.html"));
await context.Response.WriteAsync(s);
});
It seems that you are trying to intercept 404s and return a custom error page. ASP.NET Core includes middeleware that does this: StatusCodePagesMiddleware. You put the following in your Configure method:
app.UseStatusCodePagesWithReExecute("/{0}");
where {0} is a placeholder for the status code. Create a page called 404.cshtml and it will be executed whenever someone browses to a non-existent URL. You can also create a page named 500.cshtml and it will be executed if there is a server error.
See more about this here: https://www.learnrazorpages.com/configuration/custom-errors

asp.net core 2.1 custom default route per user

I'vew got an company internal website containing different areas implemented with asp.net core's area-logic.
I'd like to redirect the users to a homepage of their choice.
For Example:
User A: contonso.com/ showing index of area invoices
User B: contonso.com/ showing index of area customers
Possible workaround: Use a generic home controller that redirects the user to the appropriate area but i would like to know if there is a more generic solution using the build-in routing capabilities.
At the moment, i would stores those information in a database but actually i don't care how to configure, as long as I'm able to do the routing dynamically.
The docs and google doesn't say anything about this case.
Is there any way to get arround a custom middleware? Some buildin support, or an existing nuget-package?
You could try Middleware to redirect the requests based on your logic.
Here is a demo code for MVC Controller:
app.Use(async (context, next) =>
{
if (context.Request.Path == "/" && context.User.Identity.Name == "test#outlook.com")
{
context.Response.Redirect("Home/About", true);
}
else if (context.Request.Path == "/" && context.User.Identity.Name == "test1#outlook.com")
{
context.Response.Redirect("Home/Contact", true);
}
await next();
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Note, change the redirect URL and MVC Route based on your Area
U can use action like this:
public IActionResult MyIndex()
{
string action = // get action for user
return RedirectToAction(action, "Home")
}
along with tag helper:
<a asp-controller="Home" asp-action="MyIndex">Go somewhere</a>

Recommend way to make a UI middleware in aspnet core 1

I am trying to make a UI middleware and wanted to know what's the recommended way to go about it.
Should I do a AddMVC again in my middleware and give it a custom route or go by embedding resources.
I tried to make a MVC inside my middleware and I am able to hit the controller with the custom route but not the views in my middleware project. The sample website seems to always only look inside the main MVC views folder.
Let me know if you need more information and I will update the question accordingly.
Try to use embed views. You need to add:
"buildOptions": { "embed": [ "Views/**" ] },
Then you should tell mvc to look inside embed files
services
.AddMvc()
.AddRazorOptions(
o =>
{
o.FileProviders.Add(new EmbeddedFileProvider(yourAssembly, yourAssembly.GetName().Name));
}
);
You alse could try application parts:
AssemblyPart part = new AssemblyPart(yourAssembly);
mvcBuilder.ConfigureApplicationPartManager(manager =>
{
manager.ApplicationParts.Add(part);
});
foreach (var applicationPart in mvcBuilder.PartManager.ApplicationParts)
{
var assemblyPart = applicationPart as AssemblyPart;
if (assemblyPart != null)
{
mvcBuilder.AddRazorOptions(options =>
{
options.FileProviders.Add(new EmbeddedFileProvider(assemblyPart.Assembly, applicationPart.Name));
});
}
}
Hope it helps

Laravel routes.php include file using Session

Not sure if this is possible, but here it goes.
What I am looking to do is include my "admin" routes as a separate file, only if the user is an admin (therefore a non admin will get a 404 error
routes.php
if( Session::get('user')->is_admin )
require_once('routes-admin.php');
if( Auth::check() )
require_once('routes-user.php');
Route::get('/', function() {
return view('home');
});
routes-admin.php
Route::get('admin', function() {
return view('admin-dashboard');
});
routes-user.php
Route::get('user', function() {
return view('user-dashboard');
});
What I am trying to do is avoid having the test repeated with every single Route
so if my user segment has 10 pages I currently need 30 lines of code dedicated to Auth::check() (the if, else and redirect if not), where I can instead have a single check on routes.php and the user will get a 404 if they don't belong
Is there a way to perform this check outside of the Route?
Perhaps you want to read documentation first?
Route::group(['middleware' => 'auth'], function()
{
Route::get('/', function()
{
// Uses Auth Middleware
});
Route::get('user/profile', function()
{
// Uses Auth Middleware
});
});
Above code does exactly what you need, is "person logged in?" let him go to page "whatever".
You can create middlewares (check if user is admin or basic user) yourself and apply on groups.
Example middleware
class BeforeMiddleware implements Middleware
{
public function handle($request, Closure $next)
{
// Perform action
return $next($request);
}
}
Do not get me wrong, just your approach is really not Laravel like. Try to see some open source projects done in L5 or even in L4. Try to use everything Taylor already done for you. Documentation is your firend here.
Following the response of #Kyslik for the middleware, you can "include" your own routes file in your RouteServiceProvider like the default routes file, the RouteServiceProvide is located in: app/Providers/RouteServiceProvider.php,
Find the section
require app_path('Http/routes.php');
and just replicate with the name of your routes file want to include

Refresh MVC view after logging in using Angular

Working with the Breeze Angular SPA template found here, http://www.breezejs.com/samples/breezeangular-template, I'm trying to update a menu that changes after user authenticates.
My example is slightly different from the default template in that I've moved the Login and Register views into modal windows. When the modal closes after a successful login, the menu, which is in the MVC View (and not the Angular View) does not update as a complete page refresh does not occur.
In the SPA template, authentication is required before entering the SPA, then a hard redirect/refresh occurs and the SPA is loaded. In my case, you could be browsing views/pages in the SPA before authenticating.
MVC View Code Snippet (Views/Home/Index.cshtml)
...
<li>
#if (#User.Identity.IsAuthenticated)
{
User Logged In: #User.Identity.Name
}
else
{
User Logged In: Annon
}
</li></ul>
<div ng-app="app">
<div ng-view></div>
</div>
....
I have working the root redirect, after login, the page hard refreshes if json.redirect is set to '/'. However, if its set to the current page, i.e. '#/about', Angular handles the routing and therefore no hard refresh occurs, thus the menu is not updated.
Ajax Login Code Snippet (App/ajaxlogin.js)
... part of login/register function
if (json.success) {
window.location = json.redirect || location.href;
} else if (json.errors) {
displayErrors($form, json.errors);
}
...
Is this possible to do using my current setup? Or do I need to move the menu somewhere inside the SPA and use Angular to determine what menu to show? If the latter, direction in how to best do this? I'm new to both Angular and Breeze.
The TempHire sample in Breeze has a really good way of handling authentication for a SPA (in my opinion at least!) Granted this is using Durandal so you will need to adapt it to Angular, but they are both frameworks doing the same basic principles so good luck! -
Basically, the Controller action has an annotation [Authorize] on the action that the prepare method is calling on the entitymanagerprovider. If a 401 is returned (not authorized) the SPA takes the bootPublic path and only exposes a login route to the user. When the login is successful, the login method tells the window to reload everything, at which time the authorization passes, and the bootPrivate method is called -
shell.js (Durandal, but should be adaptable)
//#region Internal Methods
function activate() {
return entitymanagerprovider
.prepare()
.then(bootPrivate)
.fail(function (e) {
if (e.status === 401) {
return bootPublic();
} else {
shell.handleError(e);
return false;
}
});
}
function bootPrivate() {
router.mapNav('home');
router.mapNav('resourcemgt', 'viewmodels/resourcemgt', 'Resource Management');
//router.mapRoute('resourcemgt/:id', 'viewmodels/resourcemgt', 'Resource Management', false);
log('TempHire Loaded!', null, true);
return router.activate('home');
}
function bootPublic() {
router.mapNav('login');
return router.activate('login');
}
login.js -
function loginUser() {
if (!self.isValid()) return Q.resolve(false);
return account.loginUser(self.username(), self.password())
.then(function() {
window.location = '/';
return true;
})
.fail(self.handleError);
}
The account.loginUser function is basically just an ajax call that passes credentials to the account controller and returns a success or failure. On success you can see the callback is fired for window.location = '/' which does a full reload. On failure simply show an alert or something.