Implementing ExceptionFilterAttribute OnException method and need to redirect
The old implementation (.Net 48) was
exceptionContext.Controller.TempData.Remove("");
exceptionContext.Controller.TempData.Add("");//exception message is added
exceptionContext.Controller.ControllerContext.HttpContext.Response.Redirect(url);
For the redirect I guess I can:
exceptionContext.HttpContext.Response.Redirect(url);//Is this correct?
Please suggest alternate for the remove and add?
In .net 6,there's no exceptionContext.Controller.TempData if you want to access TempData in your ExceptionFilter,you could try to inject the TempdataDictionaryFactory into the filter
For example, I tried as below:
public class MyExceptionFilter : ExceptionFilterAttribute
{
private readonly ITempDataDictionaryFactory _tempDataDictionaryFactory;
public MyExceptionFilter(ITempDataDictionaryFactory tempDataDictionaryFactory)
{
_tempDataDictionaryFactory = tempDataDictionaryFactory;
}
public override void OnException(ExceptionContext context)
{
var tempData = _tempDataDictionaryFactory.GetTempData(context.HttpContext);
tempData.Add("key", "value");
context.Result = new RedirectToActionResult("Error", "Home", null);
}
}
in Program.cs:
builder.Services.AddControllersWithViews(x=>x.Filters.Add(typeof(MyExceptionFilter)));
The Result:
Related
I've a route like Admin/Vendor in my MVC application . Without changing this route I need to point this same route to another method say CustomAdmin/CustomVendor.
I tried attribute routing but no luck . Is there any way to do this. My current code is given below
Original Method:
public class AdminController
{
public ActionResult Vendor()
{
return View();
}
}
Custom Method:
public class CustomAdminController
{
[Route("Admin/Vendor")]
public ActionResult CustomVendor()
{
return View();
}
}
As you're developing a plugin. You have to add your custom route to the RouteProvider.
In default nopCommerce AdminController and Vendor doesn't exists, so I assume that you're trying to override vendor list method of admin.
Which looks like:
public partial class RouteProvider : IRouteProvider
{
public void RegisterRoutes(RouteCollection routes)
{
var route = routes.MapRoute("Plugin.GroupName.PluginName.CustomVendor",
"Admin/Vendor/List",
new { controller = "CustomAdminController", action = "CustomVendor", orderIds = UrlParameter.Optional, area = "Admin" },
new[] { "Nop.Plugin.GroupName.PluginName.Controllers" });
route.DataTokens.Add("area", "admin");
routes.Remove(route);
routes.Insert(0, route);
}
public int Priority
{
get
{
return 100; // route priority
}
}
}
Side Note: GroupName and PluginName should be your plugin group name and plugin name.
Hope this helps !
On your plugin which class implements the interface IRouteProvider, you can easily override the route there.
Likewise I have a class named RouteProvider in my plugin, So I have Implemented the abstract function RegisterRoutes and simply it can be overrided by
routes.MapRoute("Plugin.Promotion.Combo.SaveGeneralSettings",
"Admin/Vendor",
new { controller = "CustomAdmin", action = "CustomVendor", },
new[] { "Nop.Plugin.Promotion.Combo.Controllers" }
);
Here Plugin.Promotion.Combo must be replaced by your plugin directory.And using SaveGeneralSettings or any things you want to use that will be your route url
I have a custom Nancy Bootstrapper which uses StructureMapNancyBootstrapper but the issue is the same regardless of container.
public class CustomNancyBootstrapper : StructureMapNancyBootstrapper
{
protected override void RequestStartup(IContainer container, IPipelines pipelines, NancyContext context)
{
var auth = container.GetInstance<ICustomAuth>();
auth.Authenticate(context);
}
}
I want to write a test to assert that Authenticate is called with the context... something like this...
[Test]
public void RequestStartup_Calls_CustomAuth_Authenticate_WithContext()
{
// set up
var mockAuthentication = new Mock<ICustomAuth>();
var mockContainer = new Mock<IContainer>();
var mockPipelines = new Mock<IPipelines>();
var context = new NancyContext();
mockContainer.Setup(x => x.GetInstance<ICustomAuth>()).Returns(mockAuthentication.Object);
// exercise
_bootstrapper.RequestStartup(_mockContainer.Object, _mockPipelines.Object, context);
// verify
mockAuthentication.Verify(x => x.Authenticate(context), Times.Once);
}
The problem is that I can't call RequestStartup because it's protected as defined in NancyBootstrapperBase.
protected virtual void RequestStartup(TContainer container, IPipelines pipelines, NancyContext context);
Is there a "proper"/"offical" Nancy way to do this without creating another derived class and exposing the methods as that just seems like a hack?
Thanks
I guess you can "fake" the request by using Browser from Nancy.Testing:
var browser = new Browser(new CustomNancyBootstrapper());
var response = browser.Get("/whatever");
There is a good set of articles about testing NancyFx application:
http://www.marcusoft.net/2013/01/NancyTesting1.html
Turns out Nancy offers a IRequetStartup interface so you can take the code out of the custom bootstrapper and do something like this...
public class MyRequestStart : IRequestStartup
{
private readonly ICustomAuth _customAuthentication;
public MyRequestStart(ICustomAuth customAuthentication)
{
if (customAuthentication == null)
{
throw new ArgumentNullException(nameof(customAuthentication));
}
_customAuthentication = customAuthentication;
}
public void Initialize(IPipelines pipelines, NancyContext context)
{
_customAuthentication.Authenticate(context);
}
}
and the test is easy and concise
[Test]
public void When_Initialize_Calls_CustomAuth_Authenticate_WithContext()
{
// set up
var mockAuth = new Mock<ICustomAuth>();
var requestStartup = new MyRequestStart(mockAuth.Object);
var mockPipeline = new Mock<IPipelines>();
var context = new NancyContext();
// exercise
requestStartup.Initialize(mockPipeline.Object, context);
// verify
mockAuth.Verify(x => x.Authenticate(context), Times.Once);
}
https://github.com/NancyFx/Nancy/wiki/The-Application-Before%2C-After-and-OnError-pipelines#implementing-interfaces
I have seen many examples using Entity Framework in MVC3 applications, they are very simple demos which only have one mvc3 web project with edmx inside it.
So, they can use the best practice for open and close connection by "using" statement:
using(var context = new SchoolEntities())
{
// do some query and return View with result.
}
And, It can use lazy load (navigation properties) inside the "using" statment correctly, because the context is not yet
disposed:
foreach(var item in student.Course)
{
// do something with the navigation property Course
}
All things seems to be perfect until it becomes an n-tier application.
I created DAL, BLL, and a MVC3 UI.
The DAL have edmx inside it, and operator classes like SchoolDA.cs:
public class StudentDA()
{
public Student FindStudent(int studentId)
{
using(var context = new SchoolContext())
{
// do query, return a student object.
}
}
}
Then, in the BLL, if I use:
var student = studentDa.FindStudent(103);
then invoke it's navigation property:
student.Course
I will get an error (of course):
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
So, I have to change StudentDA.cs like this:
public class StudentDA() : IDisposable
{
private SchoolEntites context;
public StudentDA()
{
context = new SchoolEntities();
}
public void Dispose()
{
context.Dispose();
}
public Student FindStudent(int studentId)
{
// do query, return a student object.
}
}
Then, the BLL will change like this:
public Student FindStudent(int id)
{
using(var studentDa = new StudentDA())
{
// this can access navigation properties without error, and close the connection correctly.
return studentDa.FindStudent(id);
}
}
All things seem to be perfect again until it meet Update() method.
Now, if I want to update a student object which is taken from BLL.FindStudent(), the context.SaveChanges() will return 0, because the context is already disposed in the BLL.FindStudent(), and nothing will be updated to database.
var optStudent = new StudentBO();
var student = optStudent.FindStudent(103);
student.Name = "NewValue";
optStudent.Update(student);
Does anyone have idea on how to use EntityFramework in 3 tire application? or how can I manage the context correctly. I will use navigation propertites very often in the web layer, but I can't always remain connection open to consume the server memory.
There are multiple ways to handle EF context's lifetime. In web apps, usually context is unique for an HttpRequest. For example, if you want to handle this manually in a web application and have a per Thread/HttpRequest EF context, you can do so with the following (Code copied from http://www.west-wind.com/weblog/posts/2008/Feb/05/Linq-to-SQL-DataContext-Lifetime-Management):
internal static class DbContextManager
{
public static DbContext Current
{
get
{
var key = "MyDb_" + HttpContext.Current.GetHashCode().ToString("x")
+ Thread.CurrentContext.ContextID.ToString();
var context = HttpContext.Current.Items[key] as MyDbContext;
if (context == null)
{
context = new MyDbContext();
HttpContext.Current.Items[key] = context;
}
return context;
}
}
}
And then you can easily use:
var ctx = DbContextManager.Current
But I suggest you leave the lifetime management to an IoC framework like Autofac, Castle Windsor, or Ninject which automatically handle the creation/disposal of your registered obejcts along with many other features.
Thanks for your answer Kamyar. I came across this whilst looking for a simple strategy to manage the ObjectContext lifetime without having to use an IoC framework, which seems a bit overkill for my needs.
I also came across your other post here, for disposing of the context at the end of the request.
Thought this might be useful for others coming across this, so just posting my implementation of your code here:
Context manager class -
internal static class MyDBContextManager
{
//Unique context key per request and thread
private static string Key
{
get
{
return string.Format("MyDb_{0}{1}", arg0: HttpContext.Current.GetHashCode().ToString("x"),
arg1: Thread.CurrentContext.ContextID);
}
}
//Get and set request context
private static MyDBContext Context
{
get { return HttpContext.Current.Items[Key] as MyDBContext; }
set { HttpContext.Current.Items[Key] = value; }
}
//Context per request
public static MyDBContext Current
{
get
{
//if null, create new context
if (Context == null)
{
Context = new MyDBContext();
HttpContext.Current.Items[Key] = Context;
}
return Context;
}
}
//Dispose any created context at the end of a request - called from Global.asax
public static void Dispose()
{
if (Context != null)
{
Context.Dispose();
}
}
}
Global.asax (MVC) -
public override void Init()
{
base.Init();
EndRequest +=MvcApplication_EndRequest;
}
private void MvcApplication_EndRequest(object sender, EventArgs e)
{
MyDBContextManager.Dispose();
}
This Action Filter doesn't seem to work consistently. Some times it turns SSL off and sometimes it doesn't. I have it applied to the entire controller at it's declaration.
public class SSLFilter:ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpRequestBase req = filterContext.HttpContext.Request;
HttpResponseBase res = filterContext.HttpContext.Response;
if (req.IsSecureConnection)
{
var builder = new UriBuilder(req.Url)
{
Scheme = Uri.UriSchemeHttp,
Port = 80
};
res.Redirect(builder.Uri.ToString());
}
base.OnActionExecuting(filterContext);
}
}
It's kind of odd...any ideas why it might be working sporadically?
Have you tried decorating your controllers/actions with the [RequireHttps] attribute?
Oops, haven't noticed you was asking about ASP.NET MVC 2. This attribute is available in ASP.NET MVC 3 only, so here's the source code for it (as implemented in ASP.NET MVC 3):
using System;
using System.Diagnostics.CodeAnalysis;
using System.Web.Mvc.Resources;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter {
public virtual void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
if (!filterContext.HttpContext.Request.IsSecureConnection) {
HandleNonHttpsRequest(filterContext);
}
}
protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext) {
// only redirect for GET requests, otherwise the browser might not propagate the verb and request
// body correctly.
if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) {
throw new InvalidOperationException(MvcResources.RequireHttpsAttribute_MustUseSsl);
}
// redirect to HTTPS version of page
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
Notice that how instead of doing any redirects it uses a RedirectResult which is the correct way of performing redirects in ASP.NET MVC => by returning action results:
filterContext.Result = new RedirectResult(url);
Not only that this will perform the correct redirect but that's how to short-circuit the execution of an action. Also semantically your filter should actually be an IAuthorizationFilter as you are blocking access to some resource here.
Is there a way maybe via an action filter to turn off ssl for specific methods?
Example...if someone somehow hits my Index method and has ssl on...can I redirect to the method again and turn it off?
Global.asax.cs
protected void Application_BeginRequest(){
if (Context.Request.IsSecureConnection)
Response.Redirect(Context.Request.Url.ToString().Replace("https:", "http:"));
}
You could add the same code to an action filter:
public class SSLFilter : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext){
if (filterContext.HttpContext.Request.IsSecureConnection){
var url = filterContext.HttpContext.Request.Url.ToString().Replace("https:", "http:");
filterContext.Result = new RedirectResult(url);
}
}
}