Reputation: 26061
I'm trying to use the Ionad plugin for Fody. I've created my substitution but the dependent code doesn't call the substitute.
[StaticReplacement(typeof(AppUtils))]
public static class AppUtilsSubsitute
{
public static void LogException(string func, Exception ex) => Write("Exception", ex.ToJson());
public static void LogError(string err) => Write("Error", err);
public static void LogInfo(string info) => Write("Info", info);
public static void LogWarning(string info) => Write("Warning", info);
public static void LogCypherQuery(string func, ICypherFluentQuery query) => Write(func, query.ToJson());
static void Write(string logType, string message)
{
var dictionary = new Dictionary<string, string> { { logType, message } };
var assembly = Assembly.GetExecutingAssembly();
using (var writer = new StreamWriter(assembly.Location + "Machine.Specifications.log"))
{
writer.Write(dictionary.ToJson());
}
}
public static Dictionary<string, string> Log()
{
var assembly = Assembly.GetExecutingAssembly();
Dictionary<string, string> content;
using (var reader = new StreamReader(assembly.GetManifestResourceStream("Machine.Specifications.log")))
{
content = JsonConvert.DeserializeObject<Dictionary<string, string>>(reader.ReadToEnd());
}
return content;
}
}
[AttributeUsage(AttributeTargets.Method)]
public class EnsurePresencesOfAttribute : ActionFilterAttribute
{
internal virtual string Required { get; }
internal virtual string Param { get; }
public EnsurePresencesOfAttribute(string required = "", string param="request")
{
Required = required;
Param = param;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
Dictionary<string, object> model = actionContext.ActionArguments;
if (model == null || model.Count == 0 || !model.ContainsKey(Param))
{
ValueError(actionContext, $"{Param} parameter");
return;
}
foreach (var requirement in Required.Split(',').Select(e => e.Trim()))
{
if (model[requirement] == null)
{
ValueError(actionContext, requirement);
return;
}
}
base.OnActionExecuting(actionContext);
}
public override Task OnActionExecutingAsync(HttpActionContext context, CancellationToken token)
{
Dictionary<string, object> model = context.ActionArguments;
if(model == null || model.Count == 0 || !model.ContainsKey(Param))
{
ValueError(context, $"{Param} parameter");
return Task.FromResult(0);
}
foreach (var requirement in Required.Split(',').Select(e => e.Trim()))
{
if (model[requirement] == null)
{
ValueError(context, requirement);
Task.FromResult(0);
}
}
return base.OnActionExecutingAsync(context, token);
}
private static void ValueError(HttpActionContext context, string requirement)
{
var action = context.ActionDescriptor.ActionName;
AppUtils.LogError($"{action} Failed : Missing Required Attribute {requirement}. ");
using (var controller = new BaseApiController { Request = new HttpRequestMessage() })
{
controller.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
context.Response = controller.InvalidInputResponse();
}
}
}
Upvotes: 1
Views: 349
Reputation: 246998
Static dependencies tend to make it difficult to maintain and test code that is tightly coupled to them.
ASP.NET Web API provides extensibility points to be able to avoid having such dependencies. Mainly the IDependencyResolver
which is used for Dependency Injection in ASP.NET Web API 2.
With that I would suggest converting that static utility into an abstraction or at least abstracting it behind an interface that can be replaced easily.
public interface ILogger {
void LogException(string func, Exception ex);
void LogError(string err);
void LogInfo(string info);
void LogWarning(string info);
void LogCypherQuery(string func, ICypherFluentQuery query);
Dictionary<string, string> Log();
}
This will now allow for any number of implementations, especially stubs that can be used for unit testing if needed.
public class AppUtilsSubsitute : ILogger {
public void LogException(string func, Exception ex) => Write("Exception", ex.ToJson());
public void LogError(string err) => Write("Error", err);
public void LogInfo(string info) => Write("Info", info);
public void LogWarning(string info) => Write("Warning", info);
public void LogCypherQuery(string func, ICypherFluentQuery query) => Write(func, query.ToJson());
static void Write(string logType, string message) {
var dictionary = new Dictionary<string, string> { { logType, message } };
var assembly = Assembly.GetExecutingAssembly();
using (var writer = new StreamWriter(assembly.Location + "Machine.Specifications.log")) {
writer.Write(dictionary.ToJson());
}
}
public Dictionary<string, string> Log() {
var assembly = Assembly.GetExecutingAssembly();
Dictionary<string, string> content;
using (var reader = new StreamReader(assembly.GetManifestResourceStream("Machine.Specifications.log"))) {
content = JsonConvert.DeserializeObject<Dictionary<string, string>>(reader.ReadToEnd());
}
return content;
}
}
The dependent code would now be refactored to depend on the abstraction and not the implmentation which could be anything.
private static void ValueError(HttpActionContext context, string requirement) {
var httpConfiguration = context.RequestContext.Configuration;
var resolver = httpConfiguration.DependencyResolver;
var logger = resolver.GetService(typeof(ILogger));
var action = context.ActionDescriptor.ActionName;
logger.LogError($"{action} Failed : Missing Required Attribute {requirement}. ");
using (var controller = new BaseApiController { Request = new HttpRequestMessage() }) {
//USE THE GLOBAL HTTPCONFIGURATION ALREADY ASSOCIATED WITH THE
//REQUEST INSTEAD OF CREATING A NEW ONE THAT HAS NONE OF THE
//WEB API CONFIG THAT WOULD HAVE BEEN ADDED AT STARTUP
controller.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, httpConfiguration);
context.Response = controller.InvalidInputResponse();
}
}
Just make sure to configure the Dependency Resolver with your IoC container of choice at start up.
Set the dependency resolver on the
DependencyResolver
property of the globalHttpConfiguration
object.The following code registers the
ILogger
interface with Unity and then creates aUnityResolver
.public static void Register(HttpConfiguration config) { var container = new UnityContainer(); container.RegisterType<ILogger, AppUtilsSubsitute>(new HierarchicalLifetimeManager()); config.DependencyResolver = new UnityResolver(container); // Other Web API configuration not shown. }
Upvotes: 2