In my app, I want to use Redirect when any method throws a RedirectException.
For example...
Note: ActivePeriodException inherits from RedirectException
if (Period.ActivePeriod(Db))
throw new ActivePeriodException();
My ActivePeriodException class is
public class ActivePeriodException : RedirectException
{
public ActivePeriodException ()
{
PathRedirect = Constantes.PathMantenimiento;
}
protected ActivePeriodException (SerializationInfo serializationInfo, StreamingContext streamingContext) :
base(serializationInfo, streamingContext)
{
PathRedirect = Constantes.PathMantenimiento;
}
public override string Message =>
"Hello World :)";
}
And this example throws me:
Could not find an IRouter associated with the ActionContext.
If your application is using endpoint routing then you can get a IUrlHelperFactory with dependency injection
and use it to create a UrlHelper, or use Microsoft.AspNetCore.Routing.LinkGenerator.
public override void OnException(ExceptionContext context)
{
_logger = ApplicationLogging.LoggerFactory.CreateLogger(context.RouteData.Values["controller"].ToString());
HandleCustomException(context);
base.OnException(context);
}
private void HandleCustomException(ExceptionContext context)
{
if (context.Exception is RedirectException exception)
{
var urlHelper = new UrlHelper(context);
var path = exception.PathRedirect;
path = IsHTTP(path) ? path : urlHelper.RouteUrl(path); //HERE IS THE PROBLEM
_logger.LogInformation(ApplicationLogging.ExceptionMessage(context.Exception));
Session.MsjErrorView = exception.Message;
context.HttpContext.Response.Redirect(path);
FuncionesUtiles.ArmarRespuestaErrorRedirect(context, path);
if (exception.NotificarViaMail)
MailHelper.MandarMail(MailHelper.GetMensajeMailDeError(context), new List<string> { Configuracion.GetValueFromCache(Configuraciones.MailEnvioErrores) }, Constantes.MailSubject);
}
else if (context.Exception is CustomException)
{
_logger.LogInformation(ApplicationLogging.ExceptionMessage(context.Exception));
FuncionesUtiles.ArmarMsgRespuestaError(context, null);
}
}
How can I fix that problem?
Try building UrlHelper with IUrlHelperFactory resolved from the context services:
public void OnException(ExceptionContext context)
{
// ...
var urlHelperFactory = context.HttpContext.RequestServices.GetRequiredService<IUrlHelperFactory>();
var urlHelper = urlHelperFactory.GetUrlHelper(context);
// ...
}
Related
It was working earlier before adding actionedService which is similar to rejectionService, throws following error
An unhandled exception occurred while processing the request.
DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'Sx.Workflow.Server.Controllers.ReportController' can be invoked with the available services and parameters:
Cannot resolve parameter 'Sx.Workflow.Reporting.Services.IActionedService actionedService' of constructor 'Void .ctor(NLog.ILogger, AutoMapper.IMapper, Sx.Workflow.Reporting.Services.IRejectionService, Sx.Workflow.Reporting.Services.IActionedService)'.
Autofac.Core.Activators.Reflection.ReflectionActivator.GetValidConstructorBindings(ConstructorInfo[] availableConstructors, IComponentContext context, IEnumerable parameters) in ReflectionActivator.cs, line 160
Controller
namespace Sx.Workflow.Server.Controllers
{
[MenuItem("report")]
[ServiceFilter(typeof(SettingsFilter))]
[Authorize(Policy = Security.Constants.RolePolicy)]
public class ReportController : Controller
{
private readonly ILogger _logger;
private readonly IMapper _mapper;
private readonly IRejectionService _rejectionService;
private readonly IActionedService _actionedService;
public ReportController(ILogger logger, IMapper mapper, IRejectionService rejectionService, IActionedService actionedService)
{
_logger = logger;
_mapper = mapper;
_rejectionService = rejectionService;
_actionedService = actionedService;
}
[HttpGet]
public IActionResult Index()
{
_logger.Info("Report Controller");
return View();
}
[HttpPost]
[ApiExceptionFilter]
public async Task<IActionResult> Reject(RejectionReportRequestDto criteria)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_logger.Info("Generate Rejection Report");
var result = await _rejectionService.Generate(criteria.From, criteria.To);
var items = _mapper.Map<RejectionReportDto>(result);
return Ok(items);
}
[HttpPost]
[ApiExceptionFilter]
public async Task<IActionResult> Actioned(ActionedReportRequestDto criteria)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_logger.Info("Generate Actioned Report");
var result = await _actionedService.Generate(criteria.From, criteria.To);
var items = _mapper.Map<ActionedReportDto>(result);
return Ok(items);
}
}
}
Handler
namespace Sx.Workflow.Reporting.Handlers
{
public class ActionedReportHandler : IHandleEvent<ApplicationActionedEvent>
{
private readonly IActionedService _service;
public ActionedReportHandler(IActionedService service)
{
_service = service;
}
public Task Handle(ApplicationActionedEvent args)
{
var actioned = new Actioned
{
ApplicationNumber = args.ApplicationNumber,
AssigneeFrom = args.AssigneeFrom,
AssigneeTo = args.AssigneeTo,
DepartmentFrom = args.DepartmentFrom.Name,
DepartmentTo = args.DepartmentTo.Name,
Reason = args.RejectReasonName,
Comments = args.RejectReasonText,
RejectionDate = DateTime.Now
};
return _service.Save(actioned);
}
}
}
Service
namespace Sx.Workflow.Reporting.Services
{
public class ActionedService : IActionedService
{
private readonly ISaveActioned _saveActioned;
private readonly IGenerateActionedReport _actionedReport;
public ActionedService(ISaveActioned saveActioned, IGenerateActionedReport actionedReport)
{
_saveActioned = saveActioned;
_actionedReport = actionedReport;
}
public Task<ActionedReport> Generate(DateTime from, DateTime to)
{
return _actionedReport.Generate(from, to);
}
public Task Save(Actioned actioned)
{
return _saveActioned.Save(actioned);
}
}
}
Interface
namespace Sx.Workflow.Reporting.Services
{
public interface IActionedService
{
Task Save(Actioned actioned);
Task<ActionedReport> Generate(DateTime from, DateTime to);
}
}
Service Module
public class ServiceModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<RejectionService>()
.As<IRejectionService>()
.InstancePerLifetimeScope();
builder.RegisterType<ActionedService>()
.As<IActionedService>()
.InstancePerLifetimeScope();
}
}
Makes sense. While you are registering the type in DI, you have nothing for:
public ActionedService(ISaveActioned saveActioned, IGenerateActionedReport actionedReport)
So autofac assumes that there must be an empty constructor in ActionedService
So there are 2 solutions:
Remove the constructor parameters and create them without DI
Create the registrations for the two parameters of the constructor. Something like the following:
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<RejectionService>()
.As<IRejectionService>()
.InstancePerLifetimeScope();
builder.RegisterType<ActionedService>()
.As<IActionedService>()
.InstancePerLifetimeScope();
builder.RegisterType<SaveActioned>()
.As<ISaveActioned>()
.InstancePerLifetimeScope();
builder.RegisterType<GenerateActionedReport>()
.As<IGenerateActionedReport>()
.InstancePerLifetimeScope();
}
I have 2 domains (.com and .ru) and 2 URLs like site.com/about-us and site.ru/o-nas which should be redirected to the same page. The site uses Razor Pages.
Also, the particular URL should be available in the appropriate domain. For example:
site.COM/o-nas should not work and return Not Found (404)
site.RU/about-us should not work and return Not Found (404)
I found that filters work OK, but for both for site.com/about-us and site.ru/o-nas both filters are called.
How to call only 1 for particular URL, is it possible? Thank you, my current code is below.
public static class DomainFilters
{
public static IPageApplicationModelConvention DomainEng(
this PageConventionCollection con, string pageName, string route = "")
{
return con.AddPageApplicationModelConvention(pageName, model =>
{
model.Filters.Add(new EnglishActionFilter(route));
});
}
public static IPageApplicationModelConvention DomainRussian(
this PageConventionCollection con, string pageName, string route = "")
{
return con.AddPageApplicationModelConvention(pageName, model =>
{
model.Filters.Add(new RussianActionFilter(route));
});
}
}
public class EnglishActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.HttpContext.Request.Host.ToString().Contains(".ru"))
{
context.Result = new NotFoundResult();
}
}
public void OnResultExecuted(ResultExecutedContext context) { }
}
public class RussianActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.HttpContext.Request.Host.ToString().Contains(".com"))
{
context.Result = new NotFoundResult();
}
}
public void OnResultExecuted(ResultExecutedContext context) { }
}
And finally ConfigureServices method from Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.DomainEng("/AboutUs", "about-us");
options.Conventions.DomainRussian("/AboutUs", "o-nas");
})
}
Consider implementation of a custom FilterFactory:
public class LanguageFilterFactory : Attribute, IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var context = serviceProvider.GetService<IHttpContextAccessor>();
if (context.HttpContext.Request.Host.ToString().Contains(".com"))
{
return new EnglishActionFilter();
}
return new RussianActionFilter();
}
}
This factory will create either an English or Russian filter (depending on the domain). That's all about its responsibilities. The rest goes to Filters themselves (you'll need to change a code inside the filters to make them validate the page locator):
public class RussianActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
// you may want to play with RouteData in order to make this check more elegant
if (context.HttpContext.Request.Path.Value.Contains("About"))
{
context.Result = new NotFoundResult();
}
}
}
The filter factory is applied in the same way as other filters:
[LanguageFilterFactory]
public class IndexModel : PageModel
The Startup.cs file update:
.AddMvcOptions(options =>
{
options.Filters.Add<LanguageFilterFactory>();
});
I want to know if there is a better to way to handle this.
I've set up Unity for dependency injection for our project. The project itself is an ASP.NET application that uses Web API.
I have the following packages installed.
Unity
Unity.ASPNet.WebAPI
I see no option to close/dispose the DBContext right after fetching the data.
My controller
public class NinjasController : ApiController
{
public Ninja Get(int id)
{
INinjaRepository repository = UnityConfig.Container.Resolve(typeof(INinjaRepository), null) as INinjaRepository;
Ninja ninja = repository.GetNinjaById(id);
repository.CanBeDisposed = true;
repository = null;
UnityConfig.PerRequestLifetimeManager.Dispose();
return ninja;
}
}
UnityConfig
public static class UnityConfig
{
private static Lazy<IUnityContainer> container =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer Container => container.Value;
public static PerRequestLifetimeManager PerRequestLifetimeManager;
public static void RegisterTypes(IUnityContainer container)
{
PerRequestLifetimeManager = new PerRequestLifetimeManager();
container.RegisterType<INinjaRepository, NinjaRepository>(PerRequestLifetimeManager);
}
}
Lifetime Manager
public class PerRequestLifetimeManager : TransientLifetimeManager, IDisposable
{
private static List<IBaseRepository> list = new List<IBaseRepository>();
public override void SetValue(object newValue, ILifetimeContainer container = null)
{
base.SetValue(newValue, container);
IBaseRepository disposable = newValue as IBaseRepository;
if (disposable != null)
list.Add(disposable);
}
public void Dispose()
{
foreach (IBaseRepository item in list.FindAll(item => item.CanBeDisposed))
{
if (item != null)
{
try
{
item.Dispose();
}
catch (Exception)
{
// log exception and continue
}
}
}
list.RemoveAll(item => item.CanBeDisposed);
}
}
Repository
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
internal DbContext _context;
internal DbSet<TEntity> _dbSet;
public bool CanBeDisposed { get; set; }
public GenericRepository(DbContext context)
{
_context = context;
_dbSet = context.Set<TEntity>();
}
protected void Dispose(bool disposing)
{
if (disposing)
{
if (_context != null)
{
_context.Dispose();
_context = null;
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
First you might want to add one more Unity bootstrapper to your project Unity.AspNet.Mvc
https://msdn.microsoft.com/en-us/library/dn507440(v=pandp.30).aspx
To use the PerRequestLifetimeManager class in an ASP.NET Web API application, you must also add the the Unity bootstrapper for ASP.NET MVC NuGet package to your project.
Unity.Mvc and Unity.AspNet.WebApi will register your controllers for DI.
UnityConfig.cs
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<INinjaContext, NinjaContext>(new PerRequestLifetimeManager());
container.RegisterType<INinjaRepository, NinjaRepository>(new PerRequestLifetimeManager());
}
UnityWebApiActivator.cs Uncomment the line...
public static void Start()
{
// Use UnityHierarchicalDependencyResolver if you want to use
// a new child container for each IHttpController resolution.
var resolver = new UnityHierarchicalDependencyResolver(UnityConfig.Container);
...
}
UnityMvcActivator.cs Uncomment the line...
public static void Start()
{
...
// TODO: Uncomment if you want to use PerRequestLifetimeManager
Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
}
Your controller is simply
public class NinjasController : ApiController
{
private readonly INinjaRepository repository;
public NinjasController(INinjaRepository repository)
{
this.repository = repository;
}
public Ninja Get(int id)
{
var ninja = repository.GetNinjaById(id);
return ninja;
}
}
With PerRequestLifetimeManager Unity will take care of disposal after the request is complete.
I have an example here https://github.com/jasenhk/MovieStar
If you are using OWIN see Unity IoC does not inject dependency into Web API Controller
I have created an Asp.Net Core MVC application. I want to handle two types of errors.
I have create two exceptions: UserFriendlyException and UserFriendlyViewException.
I have tried to create the ExceptionFilter that I need handle these two exceptions according these rules:
If is exception UserFriendlyViewException called then I want to return ViewResult with original ViewName and AddModelError and return original Model.
If is exception UserFriendlyException called then I want to redirect to Error view.
This is my ExceptionFilterAttribute:
public class ControllerExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly ITempDataDictionaryFactory _tempDataDictionaryFactory;
private readonly IModelMetadataProvider _modelMetadataProvider;
public ControllerExceptionFilterAttribute(ITempDataDictionaryFactory tempDataDictionaryFactory,
IModelMetadataProvider modelMetadataProvider)
{
_tempDataDictionaryFactory = tempDataDictionaryFactory;
_modelMetadataProvider = modelMetadataProvider;
}
public override void OnException(ExceptionContext context)
{
if (!(context.Exception is UserFriendlyException) && !(context.Exception is UserFriendlyViewException)) return;
var tempData = _tempDataDictionaryFactory.GetTempData(context.HttpContext);
//CreateNotification(NotificationHelpers.AlertType.Error, tempData, context.Exception.Message);
if (!tempData.ContainsKey(NotificationHelpers.NotificationKey)) return;
if (context.Exception is UserFriendlyViewException userFriendlyViewException)
{
context.ModelState.AddModelError(userFriendlyViewException.ErrorKey, userFriendlyViewException.Message);
}
if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
//How pass here Model from context??
//If exists more views with same name but in another controller how pass correct ViewName?
var result = new ViewResult
{
ViewName = context.Exception is UserFriendlyViewException ?
controllerActionDescriptor.ActionName
: "Error",
TempData = tempData,
ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState)
{
{"Notifications", tempData[NotificationHelpers.NotificationKey] },
}
};
context.ExceptionHandled = true;
context.Result = result;
}
tempData.Remove(NotificationHelpers.NotificationKey);
}
}
I have two issues:
1.) How can I pass original Model from ExceptionContext to ViewResult?
2.) How return correct ViewName for UserFriendlyViewException if exists more Views with same name but in another Controller?
How can I pass original Model from ExceptionContext to ViewResult?
You may use context.ModelState collection.
foreach(var item in context.ModelState)
{
string parameter = item.Key;
object rawValue = item.Value.RawValue;
string attemptedValue = item.Value.AttemptedValue;
System.Console.WriteLine($"Parameter: {parameter}, value: {attemptedValue}");
}
Note, collection will contain only bound parameters.
How return correct ViewName for UserFriendlyViewException if exists more Views with same name but in another Controller?
The same View discovery process will be used by framework as in the controller's action, so instead of View name you can specify the path:
A view file path can be provided instead of a view name. If using an absolute path starting at the app root (optionally starting with "/" or "~/"), the .cshtml extension must be specified:
return View("Views/Home/About.cshtml");
You can also use a relative path to specify views in different directories without the .cshtml extension. Inside the HomeController, you can return the Index view of your Manage views with a relative path:
return View("../Manage/Index");
Similarly, you can indicate the current controller-specific directory with the "./" prefix:
return View("./About");
Pass stuff via ViewData
public class UIFriendlyExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (context.Exception is UIFriendlyException uex)
{
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), context.ModelState);
//here we go
viewData.Add("Message", MY_CUSTOM_MESSAGE);
var res = new ViewResult() {
ViewName = "Error",
ViewData = viewData
};
context.Result = res;
}
context.ExceptionHandled = true;
}
}
Then register in Startup
services.AddControllersWithViews(options =>
{
options.Filters.Add(typeof(UIFriendlyException.UIFriendlyExceptionFilter));
});
Prepare two Filters.
One caches the Binding Model and the other handles exceptions.
IActionFilter
public class BindModelCacheFilter : IActionFilter
{
public static readonly string Key = "BindModelCacheFilterKey. This string can be anything.";
public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Items[BindModelCacheFilter.Key] = context.ActionArguments;
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
IExceptionFilter
public class ExceptionHandlingFilter : IExceptionFilter
{
private readonly ILogger<ExceptionHandlingFilter> _logger;
public ExceptionHandlingFilter(ILogger<ExceptionHandlingFilter> logger)
{
_logger = logger;
}
public void OnException(ExceptionContext context)
{
// ...
var requestPayload = "";
try
{
var actionArguments = context.HttpContext.Items[BindModelCacheFilter.Key] as IDictionary<string, object?>;
if (0 < actionArguments?.Count)
{
object requestDto = actionArguments.First().Value!;
var options = new System.Text.Json.JsonSerializerOptions()
{
IncludeFields = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All)
};
requestPayload = System.Text.Json.JsonSerializer.Serialize(requestDto, options);
}
}
catch
{
}
// ...
}
}
Create an ExceptionFilter that derives both from IExceptionFilter, IActionFilter
This gives you access to the view and the controller
[TypeFilter(typeof(ExceptionFilter))]
public class MyController : ControllerBase
{
public IActionResult SomeAction([Bind("ID")] MyView vm) {}
}
public class ExceptionFilter : IExceptionFilter, IActionFilter
{
private MyController? MyController { get; set; }
private MyViewModel vm { get; set; }
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
MyController = (MyController)context.Controller;
//
if (context.ActionArguments.TryGetValue("vm", out object vmObj))
vm = vmObj as MyView;
else
vm = null;
}
public void OnException(ExceptionContext context)
{
// Do something using MyController and MyView
}
}
I am trying to use log4net.LogicalThreadContext to store some data in the OwinMiddleware, so i can log it later in the ApiController, but it doesn't seem to work. The data stored in log4net.LogicalThreadContext doesn't seems to make it available in the ApiController. Here is my code snippet:
Created the ApiMiddleWare in order to inject some log data to LogicalThreadContext.Properties["logdata"]:
public class ApiMiddleWare : OwinMiddleware
{
public ApiMiddleWare(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
var loggers = new ConcurrentDictionary<string, object>();
if (!loggers.ContainsKey("CorellationId"))
{
var correlationId = new[] {Guid.NewGuid().ToString().Replace("-", "")};
loggers.TryAdd("CorellationId", correlationId[0]);
}
if (context.Request.Path.HasValue)
{
loggers.TryAdd("Route", context.Request.Uri.AbsoluteUri);
}
LogicalThreadContext.Properties["logdata"] = loggers;
await Next.Invoke(context);
}
}
Then ApiMiddleWare will be used in Startup.cs in ServiceHost as below:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use<ApiMiddleWare>();
Log.Configure();
}
}
I created a custom RollingFileAppeanderEx to capture log data that was assigned in the middleware and log it:
public class RollingFileAppenderEx: RollingFileAppender
{
protected static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
DateFormatHandling = DateFormatHandling.IsoDateFormat
};
protected override void Append(LoggingEvent loggingEvent)
{
if (FilterEvent(loggingEvent))
{
var logdata = loggingEvent.GetLoggingEventData();
logdata.Message = GetLogData(loggingEvent);
loggingEvent = new LoggingEvent(loggingEvent.GetType(), loggingEvent.Repository, logdata, loggingEvent.Fix);
base.Append(loggingEvent);
}
}
protected string GetLogData(LoggingEvent logEvent)
{
IDictionary<string, object> logData = new Dictionary<string, object>();
var logD = logEvent.Properties["logdata"] as ConcurrentDictionary<string, object>;
if logD != null)
{
foreach (var log in logD)
{
logData.Add(log.Key, log.Value);
}
}
logData.Add("Message", logObject.Message);
var logString = JsonConvert.SerializeObject(logData, JsonSettings);
return logString;
}
}
From The ApiController, call Info function to log:
public class TestsController : ApiController
{
private static readonly ILogger Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public string Get(int id)
{
Log.Info("Something");
return id;
}
}
Here is my problem: Only "Something" was written to the log. However, CorellationId and Route were not. Debugging through the code, I found that "logEvent.Properties["logdata"] as ConcurrentDictionary" returned nullable value in the RollingFileAppenderEx. So i have a theory: it seems that TestsController class is not in the same thread or not a child thread from ApiMiddleWare. Therefore, data stored in LogicalThreadContext does not propagate all the way.
If anyone can help to see if there is a way to do this, or maybe there is a bug in my code. I would appreciate it. Thanks.
Maybe you have to call loggingEvent.GetLoggingEventData()?
I had:
public abstract class AwsVerboseLogsAppender : AppenderSkeleton
{
// ...
protected override void Append(LoggingEvent loggingEvent)
{
// ...
}
// ...
}
// ...
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log4net.config");
var fileinfo = new FileInfo(path);
XmlConfigurator.Configure(fileinfo);
var log = LogManager.GetLogger(GetType());
LogicalThreadContext.Properties["Test"] = "MyValue";
log.Debug("test");
And loggingEvent.Properties was empty inside Append.
However if I called loggingEvent.GetLoggingEventData(), then "Test" and MyValue showed up inside loggingEvent.Properties. So maybe you have to call that method.