Why is this Roslyn analyzer test failing? - c#

I'm trying to understand why this test fails with:
Maak.Test.InitializeNewUnitTest.NoPropertiesInitialized_Diagnostic
Assert.AreEqual failed. Expected:<37>. Actual:<38>. Context:
Diagnostics of test state Expected diagnostic to end at column "38"
was actually at column "39"
Expected diagnostic:
// /0/Test0.cs(13,9,13,38): warning MaakInitializeNew VerifyCS.Diagnostic().WithSpan(13, 9, 13, 38),
Actual diagnostic:
// /0/Test0.cs(13,9): warning MaakInitializeNew: can be fully initialized VerifyCS.Diagnostic().WithSpan(13, 9, 13, 39),
The test:
[TestMethod]
public async Task NoPropertiesInitialized_Diagnostic()
{
await VerifyCS.VerifyCodeFixAsync(#"
using System;
public class MyClass
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
[|var myClass = new MyClass { }|];
}
}
", #"
using System;
public class MyClass
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
var myClass = new MyClass { Name = ""Stan"" };
}
}
");
}
The analyzer code:
public override void Initialize(AnalysisContext context)
{
// See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);
}
private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;
var typeDeclaration = localDeclaration.Declaration.Type;
var symbolInfo = context.SemanticModel.GetSymbolInfo(typeDeclaration);
var typeSymbol = symbolInfo.Symbol;
if(typeSymbol == null)
return;
// Special case: Ensure that 'var' isn't actually an alias to another type. (e.g. using var = System.String).
var aliasInfo = context.SemanticModel.GetAliasInfo(typeDeclaration);
if (aliasInfo != null)
return;
var namedSymbol = context.Compilation.GetTypeByMetadataName(typeSymbol.MetadataName);
if (namedSymbol?.TypeKind != TypeKind.Class)
return;
var hasDefaultConstructor = (namedSymbol?.Constructors)?.SingleOrDefault(c => !c.Parameters.Any()) != null;
var properties = namedSymbol?.GetMembers()
.Where(m => m.Kind == SymbolKind.Property
&& m.DeclaredAccessibility == Accessibility.Public
&& !((IPropertySymbol)m).IsReadOnly
&& !((IPropertySymbol)m).IsStatic)
.Select(m => new
{
Name = m.Name,
Type = ((IPropertySymbol)m).Type
})
.ToList();
var hasValidProperties = properties?.Any() != false;
if (!hasValidProperties)
return;
var initializerExpressions = (localDeclaration.Declaration.Variables.FirstOrDefault().Initializer.Value
as ObjectCreationExpressionSyntax)?.Initializer.Expressions.ToList();
var except = properties.Select(p => p.Name)
.Except(initializerExpressions.Select(e => (e as AssignmentExpressionSyntax)?.Left.ToString()))
.ToList();
if(except.Count == 0)
return;
context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation()));
}

Reporting the correct location fixed it:
context.ReportDiagnostic(Diagnostic.Create(Rule, localDeclaration.Declaration.GetLocation()));

Related

Moq keeps throwing null reference exception

I am new to Moq having used Rhino mocks for a while. I am trying to stub a method so that is returns the information I expect but the actual line of code when running the test returns a null reference exception. Am I missing something obvious here? Below is my code:
public void GetResSystemClients(ResSystem resSystem)
{
ResSystemDetail = new ResSystemDetails() {ResSys = resSystem};
//RETURNS NULL REFERENCE EXCEPTION
var resClients = FileReader.ReadFile((c) => c.Where(x =>
x.ReservationSystem.Replace(" ", "").ToLowerInvariant().Contains(resSystem.ToString().ToLowerInvariant())));
ResSystemDetail.ResSystemClients = resClients
.Select(y => new ResSystemClient() {ClientCode = y.ClientCode, ClientName = y.ClientName})
.OrderBy(z => z.ClientCode).ToList();
}
Test Code
[SetUp]
public void SetUp()
{
mockFileReader = new Mock<IClientLookUpFileReader>(MockBehavior.Default);
sut = new ClientLookupModel();
}
[TestCase(ResSystem.Test)]
public void When_GetResSystemCalled_With_ResSystem_Return_CorrectClients(ResSystem system)
{
//Arrange
mockFileReader.Setup(x =>
x.ReadFile(It.IsAny<Func<List<ExtraClientInfo>, IEnumerable<ExtraClientInfo>>>()))
.Returns(new List<ExtraClientInfo>
{
new ExtraClientInfo
{
ClientName = "Test",
ClientCode = "XX"
},
new ExtraClientInfo
{
ClientName = "Test1",
ClientCode = "X1"
},
new ExtraClientInfo
{
ClientName = "Test2",
ClientCode = "X2"
},
});
//Act
sut.GetResSystemClients(system);
}
Read file code
public interface IClientLookUpFileReader
{
T ReadFile<T>(Func<List<ExtraClientInfo>, T> predicateFunc);
}
public class ClientLookUpFileReader : IClientLookUpFileReader
{
private const string filePath = "D:/PersistedModels/ClientInfo.json";
T IClientLookUpFileReader.ReadFile<T>(Func<List<ExtraClientInfo>, T> predicateFunc)
{
if (File.Exists(filePath))
{
var clientInfoList = new JavaScriptSerializer().Deserialize<List<ExtraClientInfo>>(System.IO.File.ReadAllText(filePath));
return predicateFunc(clientInfoList);
}
throw new Exception($"File not found at path {filePath}");
}
}

Swagger Upload FileFilter for 5.0

Has anyone been successful in building a file filter for swagger 5.0?
I currently have one for Swashbuckle.AspNetCore 4.0.1: https://www.nuget.org/packages/Swashbuckle.AspNetCore/4.0.1?_src=template
However since I migrated to net core 3.0, I needed to update my swagger to 5.0. My FileFilter needed to be ammended however I failed to successfully migrate the file filter. Can someone assist please?
Current working SwaggerUploadFileFilter for swagger 4.0.1:
public class SwaggerUploadFileFilter : IOperationFilter
{
private const string formDataMimeType = "multipart/form-data";
private static readonly string[] formFilePropertyNames =
typeof(IFormFile).GetTypeInfo().DeclaredProperties.Select(p => p.Name).ToArray();
public void Apply(Operation operation, OperationFilterContext context)
{
var parameters = operation.Parameters;
if (parameters == null || parameters.Count == 0) return;
var formFileParameterNames = new List<string>();
var formFileSubParameterNames = new List<string>();
foreach (var actionParameter in context.ApiDescription.ActionDescriptor.Parameters)
{
var properties =
actionParameter.ParameterType.GetProperties()
.Where(p => p.PropertyType == typeof(IFormFile))
.Select(p => p.Name)
.ToArray();
if (properties.Length != 0)
{
formFileParameterNames.AddRange(properties);
formFileSubParameterNames.AddRange(properties);
continue;
}
if (actionParameter.ParameterType != typeof(IFormFile)) continue;
formFileParameterNames.Add(actionParameter.Name);
}
if (!formFileParameterNames.Any()) return;
var consumes = operation.Consumes;
consumes.Clear();
consumes.Add(formDataMimeType);
foreach (var parameter in parameters.ToArray())
{
if (!(parameter is NonBodyParameter) || parameter.In != "formData") continue;
if (formFileSubParameterNames.Any(p => parameter.Name.StartsWith(p + "."))
|| formFilePropertyNames.Contains(parameter.Name))
parameters.Remove(parameter);
}
foreach (var formFileParameter in formFileParameterNames)
{
parameters.Add(new NonBodyParameter()
{
Name = formFileParameter,
Type = "file",
In = "formData"
});
}
}
}
Attempt using code located online for swagger 5.0:
public class SwaggerUploadFileFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (!(operation?.RequestBody?.Content?.Any(x => x.Key.ToLower() == "multipart/form-data") ?? false)) return;
var uploadFiles = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
.Union(context.MethodInfo.GetCustomAttributes(true))
.OfType<SwaggerUploadFile>();
if (uploadFiles.Count() == 0) return;
var uploadFile = uploadFiles.First();
operation.RequestBody.Content["multipart/form-data"].Schema.Properties =
new Dictionary<string, OpenApiSchema>
{
[uploadFile.Parameter] = new OpenApiSchema
{
Type = "string",
Format = "binary",
Description = uploadFile.Description
}
};
if (!string.IsNullOrEmpty(uploadFile.Example))
{
operation.RequestBody.Content["multipart/form-data"].Schema.Example = new OpenApiString(uploadFile.Example);
operation.RequestBody.Content["multipart/form-data"].Schema.Description = uploadFile.Example;
}
}
private class SwaggerUploadFile
{
public string Parameter { get; set; }
public string Description { get; set; }
public string Example { get; set; }
}
}
I am having trouble making the 5.0 version work with IFormFile.
Any assistance is appreciated.
5.0-rc4 should work with files without custom operation filter - you just need to remove [FromForm] parameter attribute if you have one.
Specified in GitHub issue discussion and tested myself.

How to invoke Method immediately after instantiating

I have a controller that calls an api to get a list of Positions and Employees . First it puts the api results into a model class - IDMSElements (1). Then the controller takes the IDMSElements object and converts it to a PositionSlots object (2). Then the PositionSlots object needs to be updated with additional data from a database (3). So in simplified version of my controller I have:
(1) IDMSElements elements = getslots.CopyDocToElements(doc);
(2) PositionSlots myslots = (PositionSlots)elements;
(3) myslots.UpdateDetails(db);
I am concerned about the myslots.UpdateDetails(db) because additional code in the controller depends on UpdateDetails having been run. I would like the UpdateDetails to be run by default when creating the PositionSlots object. But I think multiple database calls in a constructor probably should not be done. How can I make it so the UpdateDetails is automatically invoked after the PositionSlots object is instantiated?
Thank you very much!
Controller:
[Authorize]
public class PSListController : Controller
{
private static readonly log4net.ILog _logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private PositionControlContext db = new PositionControlContext();
private GetPositionSlots getslots = new GetPositionSlots();
...
public async Task<ActionResult> ByCostCenter(int costCenter)
{
string ssapiuri = getslots.BuildAPIuri($"/current/?UDAKunit={costCenter.ToString()}");
_logger.Debug($"{ssapiuri.ToString()}");
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
HttpResponseMessage result = await getslots.GetDataFromIDMSapi(ssapiuri);
stopWatch.Stop();
_logger.Debug($"Response received. Milliseconds elapsed: {stopWatch.Elapsed.TotalMilliseconds.ToString()}");
if (result.StatusCode != HttpStatusCode.OK)
{
_logger.Debug("Error retrieving data from API. Milliseconds elapsed: " + stopWatch.Elapsed.TotalMilliseconds.ToString());
throw new HttpException(404, "NotFound");
}
stopWatch.Restart();
XDocument doc = XDocument.Load(result.Content.ReadAsStreamAsync().Result);
stopWatch.Stop();
_logger.Debug($"API result loaded into XDocument. Milliseconds elapsed: {stopWatch.Elapsed.TotalMilliseconds.ToString()}\n");
_logger.Debug(doc.ToString());
IDMSElements elements = getslots.CopyDocToElements(doc);
XMLDocStats docstats = new XMLDocStats(elements);
_logger.Debug(docstats.ToString());
PositionSlots myslots = (PositionSlots)elements;
myslots.UpdateDetails(db);
//because this is dependent upon UpdatePositionSlotId having been run
//need to find a way to force UpdateDetails to run other than calling from Controller??
var mainPositionSlots = myslots.PositionsCurrentAndActive;
var budget = db.Database.SqlQuery<Budget>("GetBudgetForCostCenter #costCenter = {0}", costCenter);
List<Budget> budgetRows = budget.ToList();
JobClassGroups jobClassGroups = new JobClassGroups(mainPositionSlots, budgetRows);
Department dept = db.Departments.AsNoTracking().Where(d => d.CostCenter == costCenter).SingleOrDefault();
var model = new ByCostCenter_vm(dept, myslots, jobClassGroups);
ViewBag.stats = docstats.ToString();
return View("ByCostCenter", model);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
IDMSElements Class:
public class IDMSElements
{
//properties
public ICollection<IDMSElementData> Elements { get; set; }
//constructors
public IDMSElements() { }
public IDMSElements(ICollection<IDMSElementData> elList)
{
Elements = elList;
}
//methods
public static explicit operator PositionSlots(IDMSElements obj)
{
//this is assuming we are looking at a list of elements
//that contains the "current" positions
Dictionary<string, PositionSlotDetail> positionSlots = new Dictionary<string, PositionSlotDetail>();
var sorted = from element in obj.Elements
orderby element.positionNbr
select element;
foreach (IDMSElementData el in sorted)
{
if (!positionSlots.ContainsKey(el.positionNbr))
{
PositionSlotDetail psd = new PositionSlotDetail
{
CostCenter = Int32.Parse(el.UDAKunit),
CostCenter_7Char = el.UDAKunit,
PositionNumber = el.positionNbr,
PositionSlotId = 0,
JobClassId = el.jobClassCode.Replace("-", ""),
JobClassFullDisplayCd = string.Empty,
JobTitle = string.Empty,
SalaryGradeCd = string.Empty,
FTE_percent = el.FTEpercent / 100,
EmployeeId = el.employeeID,
EmployeeName = String.Empty,
PositionEffDate = el.positionEffDate,
PositionEffDateNext = el.positionEffDateNext,
PositionBeginDate = el.positionBeginDate,
PositionEndDate = el.positionEndDate,
DirectLeaderID = string.Empty,
DirectLeaderName = string.Empty,
DirectLeaderNetID = string.Empty,
FLSAstatusCode = el.FLSAstatusCode,
FLSAstatusDesc = el.FLSAstatusDesc,
EmploymentTypeCode = el.employmentTypeCode,
EmploymentTypeDesc = el.employmentTypeDesc,
IsOverloaded = false,
};
positionSlots[el.positionNbr] = psd;
}
Assignment newAssignment = new Assignment
{
PvID = el.employeeID,
AssignBeginDate = el.assignBeginDate,
AssignEndDate = el.assignEndDate,
AssignEffDate = el.assignEffDate,
AssignEffDateNext = el.assignEffDateNext,
};
PositionSlotDetail thePSD = positionSlots[el.positionNbr];
thePSD.Assignments.Add(newAssignment);
if (thePSD.Assignments.Any(assignment => Regex.IsMatch(assignment.PvID, #"^\d+$")))
{
thePSD.Status = "FILLED";
if (thePSD.Assignments.Where(assignment => Regex.IsMatch(assignment.PvID, #"^\d+$")).Count() > 1)
{
thePSD.IsOverloaded = true;
}
}
else
{
thePSD.Status = "VACANT";
}
}
var output = new PositionSlots(positionSlots.Values.ToList());
return output;
}
...
}
PositionSlots class:
public class PositionSlots
{
//Constructor
public PositionSlots(ICollection<PositionSlotDetail> pslist)
{
Positions = pslist;
}
//properites
public ICollection<PositionSlotDetail> Positions { get; }
private bool DetailsUpdated { get; set; } = false;
public IEnumerable<PositionSlotDetail> PositionsCurrentAndActive
{
get
{
return from position in Positions
where position.PositionSlotId > 0 && position.PositionEndDate >= DateTime.Today
select position;
}
}
public IEnumerable<PositionSlotDetail> PositionsNotFoundInPositionControl
{
get
{
return from position in Positions
where position.PositionSlotId == 0
select position;
}
}
public IEnumerable<PositionSlotDetail> PositionsClosed
{
get
{
return from psd in Positions
where psd.PositionEndDate < DateTime.Today
&& psd.PositionSlotId > 0
select psd;
}
}
public decimal ActualFTETotal
{
get
{
return (from position in PositionsCurrentAndActive
from assignment in position.Assignments
where position.Assignments.Count() >= 1 && (!assignment.PvID.Equals("VACANT"))
select position.FTE_percent).Sum();
}
}
public int FilledTotal
{
get
{
return PositionsCurrentAndActive.Where(x => x.Status == "FILLED").Count();
}
}
public int VacantTotal
{
get
{
return PositionsCurrentAndActive.Where(x => x.Status == "VACANT").Count();
}
}
public int OverloadedTotal
{
get
{
return PositionsCurrentAndActive.Where(x => x.IsOverloaded).Count();
}
}
//methods
public void UpdateDetails(PositionControlContext db)
{
if (!DetailsUpdated)
{
UpdateJobClassificationInfo(db);
UpdateEmployeeName(db);
UpdatePositionSlotId(db); //if not found, PositionSlotId = 0
//UpdateIsBudgeted(db);
UpdateDirectLeader(db);
DetailsUpdated = true;
}
else
{
return;
}
}
private void UpdateJobClassificationInfo(PositionControlContext db)
{
string[] jobClassIds = (from x in Positions select x.JobClassId).Distinct().ToArray();
var JobClassList = (from jc in db.JobClassifications where jobClassIds.Contains(jc.JobClassId) select jc).ToDictionary(jc => jc.JobClassId, jc => jc, StringComparer.OrdinalIgnoreCase);
foreach (PositionSlotDetail psd in Positions)
{
if (!string.IsNullOrWhiteSpace(psd.JobClassId) && !psd.JobClassId.Equals("MISSING"))
{
JobClassification jobClassification;
if (JobClassList.TryGetValue(psd.JobClassId, out jobClassification))
{
psd.JobClassFullDisplayCd = jobClassification.JobClassFullDisplayCd;
psd.JobTitle = jobClassification.JobTitle;
psd.SalaryGradeCd = jobClassification.SalaryGradeCd;
}
else
{
psd.JobClassFullDisplayCd = ($"{psd.JobClassId} not found in view V_JobClassifications.");
psd.JobTitle = ($"{psd.JobClassId} not found in view V_JobClassifications.");
psd.SalaryGradeCd = "--";
}
}
else
{
psd.JobClassFullDisplayCd = "MISSING";
psd.JobTitle = "MISSING";
}
}
return;
}
private void UpdateEmployeeName(PositionControlContext db)
{
string[] empIdsStr = (from position in Positions
from assignment in position.Assignments
where (!assignment.PvID.Equals("VACANT"))
select assignment.PvID).Distinct().ToArray();
// Positions.SelectMany(x => x.Assignments).SelectMany(x => x.PvID).ToArray();
//string[] empIdsStr = (from x in Positions where (!x.EmployeeId.Contains("VACANT")) select x.EmployeeId).Distinct().ToArray();
//int[] empIdsInt = Array.ConvertAll(empIdsStr, int.Parse);
var EmployeeList = (from emp in db.IdAM_personLU where empIdsStr.Contains(emp.pvID) select emp).ToDictionary(emp => emp.pvID,
emp => emp.EmployeeFullName, StringComparer.OrdinalIgnoreCase);
EmployeeList["VACANT"] = "VACANT";
foreach (PositionSlotDetail psd in Positions)
{
string empName;
if (EmployeeList.TryGetValue(psd.EmployeeId, out empName))
{
psd.EmployeeName = empName;
}
else
{
psd.EmployeeName = ($"{psd.EmployeeId} not found in Employee table.");
}
foreach (Assignment emp in psd.Assignments)
{
string empName2;
if (EmployeeList.TryGetValue(emp.PvID, out empName2))
{
emp.EmpDisplayName = empName2;
}
else
{
emp.EmpDisplayName = ($"{psd.EmployeeId} not found in Employee table.");
}
}
}
return;
}
private void UpdateDirectLeader(PositionControlContext db)
{
string[] empIdsStr = (from x in Positions where (!x.EmployeeId.Contains("VACANT")) select x.EmployeeId).Distinct().ToArray();
//int[] empIdsInt = Array.ConvertAll(empIdsStr, int.Parse);
Dictionary<string, IdAM_arborLU> DirectLeader = new Dictionary<string, IdAM_arborLU>();
var EmployeeDirectLeaderList = (from emp in db.IdAM_arborLU where empIdsStr.Contains(emp.emp_pvID) select emp).ToDictionary(emp => emp.emp_pvID,
emp => emp, StringComparer.OrdinalIgnoreCase);
foreach (PositionSlotDetail psd in Positions)
{
if (psd.EmployeeId != "VACANT") //will just leave DirectLeaderId and DirectLeaderName as string.Empty
{
IdAM_arborLU supervisor;
if (EmployeeDirectLeaderList.TryGetValue(psd.EmployeeId, out supervisor))
{
psd.DirectLeaderName = supervisor.sup_name;
psd.DirectLeaderID = supervisor.sup_pvID;
psd.DirectLeaderNetID = supervisor.sup_netID;
}
else
{
psd.DirectLeaderName = ($"{psd.EmployeeId} not found in Arbor table.");
}
}
foreach (Assignment emp in psd.Assignments)
{
if (psd.EmployeeId != "VACANT")
{
IdAM_arborLU supervisor2;
if (EmployeeDirectLeaderList.TryGetValue(psd.EmployeeId, out supervisor2))
{
emp.DirectLeaderName = supervisor2.sup_name;
emp.DirectLeaderID = supervisor2.sup_pvID;
emp.DirectLeaderNetID = supervisor2.sup_netID;
}
else
{
emp.DirectLeaderName = ($"{psd.EmployeeId} not found in Arbor table.");
emp.DirectLeaderID = "--";
emp.DirectLeaderNetID = "--";
}
}
}
}
return;
}
private void UpdatePositionSlotId(PositionControlContext db)
{
string[] posnumber = (from x in Positions
select x.PositionNumber).ToArray();
var slots = (from ps1 in db.PositionSlots
where posnumber.Contains(ps1.PositionNumber)
select ps1).ToDictionary(ps => ps.PositionNumber.Trim(), ps => ps.PositionSlotId, StringComparer.OrdinalIgnoreCase);
foreach (PositionSlotDetail psd in Positions)
{
int posSlotId = 0;
if (slots.TryGetValue(psd.PositionNumber, out posSlotId))
{
psd.PositionSlotId = posSlotId;
}
}
return;
}
private void UpdateIsBudgeted(PositionControlContext db)
{
string[] posnumber = (from x in Positions
select x.PositionNumber).ToArray();
var slots = (from ps1 in db.PositionSlots
where posnumber.Contains(ps1.PositionNumber)
select ps1).ToDictionary(ps => ps.PositionNumber.Trim(), ps => ps.IsBudgeted, StringComparer.OrdinalIgnoreCase);
foreach (PositionSlotDetail psd in Positions)
{
bool isbudgeted = false;
if (slots.TryGetValue(psd.PositionNumber, out isbudgeted))
{
psd.IsBudgeted = isbudgeted;
}
}
return;
}
}
you can achieve this by writing a method:
IDMSElement.ToPositionSlot(db)
and then use it as follow:
PositionSlots myslots = elements.Select(x => x.ToPositionSlot(db))

Use workflow to evaluate dynamic expression

I would like to pass an object and expression into a dynamically created workflow to mimic the Eval function found in many languages. Can anyone help me out with what I am doing wrong? The code below is a very simple example if taking in a Policy object, multiple its premium by 1.05, then return the result. It throws the exception:
Additional information: The following errors were encountered while processing the workflow tree:
'DynamicActivity': The private implementation of activity '1: DynamicActivity' has the following validation error: Value for a required activity argument 'To' was not supplied.
And the code:
using System.Activities;
using System.Activities.Statements;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Policy p = new Policy() { Premium = 100, Year = 2016 };
var inputPolicy = new InArgument<Policy>();
var theOutput = new OutArgument<object>();
Activity dynamicWorkflow = new DynamicActivity()
{
Properties =
{
new DynamicActivityProperty
{
Name="Policy",
Type=typeof(InArgument<Policy>),
Value=inputPolicy
}
},
Implementation = () => new Sequence()
{
Activities =
{
new Assign()
{
To = theOutput,
Value=new InArgument<string>() { Expression = "Policy.Premium * 1.05" }
}
}
}
};
WorkflowInvoker.Invoke(dynamicWorkflow);
}
}
public class Policy
{
public int Premium { get; set; }
public int Year { get; set; }
}
}
You can use Workflow Foundation to evaluate expressions, but it is far easier to use almost any other option.
The key issue at play with your code was that you were not trying to evaluate the expression (with either VisualBasicValue or CSharpValue). Assigning InArgument`1.Expression is an attempt to set the value - not to set the value to the result of an expression.
Keep in mind that compiling expressions is fairly slow (>10ms), but the resultant compiled expression can be cached for quick executions.
Using Workflow:
class Program
{
static void Main(string[] args)
{
// this is slow, only do this once per expression
var evaluator = new PolicyExpressionEvaluator("Policy.Premium * 1.05");
// this is fairly fast
var policy1 = new Policy() { Premium = 100, Year = 2016 };
var result1 = evaluator.Evaluate(policy1);
var policy2 = new Policy() { Premium = 150, Year = 2016 };
var result2 = evaluator.Evaluate(policy2);
Console.WriteLine($"Policy 1: {result1}, Policy 2: {result2}");
}
}
public class Policy
{
public double Premium, Year;
}
class PolicyExpressionEvaluator
{
const string
ParamName = "Policy",
ResultName = "result";
public PolicyExpressionEvaluator(string expression)
{
var paramVariable = new Variable<Policy>(ParamName);
var resultVariable = new Variable<double>(ResultName);
var daRoot = new DynamicActivity()
{
Name = "DemoExpressionActivity",
Properties =
{
new DynamicActivityProperty() { Name = ParamName, Type = typeof(InArgument<Policy>) },
new DynamicActivityProperty() { Name = ResultName, Type = typeof(OutArgument<double>) }
},
Implementation = () => new Assign<double>()
{
To = new ArgumentReference<double>() { ArgumentName = ResultName },
Value = new InArgument<double>(new CSharpValue<double>(expression))
}
};
CSharpExpressionTools.CompileExpressions(daRoot, typeof(Policy).Assembly);
this.Activity = daRoot;
}
public DynamicActivity Activity { get; }
public double Evaluate(Policy p)
{
var results = WorkflowInvoker.Invoke(this.Activity,
new Dictionary<string, object>() { { ParamName, p } });
return (double)results[ResultName];
}
}
internal static class CSharpExpressionTools
{
public static void CompileExpressions(DynamicActivity dynamicActivity, params Assembly[] references)
{
// See https://learn.microsoft.com/en-us/dotnet/framework/windows-workflow-foundation/csharp-expressions
string activityName = dynamicActivity.Name;
string activityType = activityName.Split('.').Last() + "_CompiledExpressionRoot";
string activityNamespace = string.Join(".", activityName.Split('.').Reverse().Skip(1).Reverse());
TextExpressionCompilerSettings settings = new TextExpressionCompilerSettings
{
Activity = dynamicActivity,
Language = "C#",
ActivityName = activityType,
ActivityNamespace = activityNamespace,
RootNamespace = null,
GenerateAsPartialClass = false,
AlwaysGenerateSource = true,
ForImplementation = true
};
// add assembly references
TextExpression.SetReferencesForImplementation(dynamicActivity, references.Select(a => (AssemblyReference)a).ToList());
// Compile the C# expression.
var results = new TextExpressionCompiler(settings).Compile();
if (results.HasErrors)
{
throw new Exception("Compilation failed.");
}
// attach compilation result to live activity
var compiledExpression = (ICompiledExpressionRoot)Activator.CreateInstance(results.ResultType, new object[] { dynamicActivity });
CompiledExpressionInvoker.SetCompiledExpressionRootForImplementation(dynamicActivity, compiledExpression);
}
}
Compare to the equivalent Roslyn code - most of which is fluff that is not really needed:
public class PolicyEvaluatorGlobals
{
public Policy Policy { get; }
public PolicyEvaluatorGlobals(Policy p)
{
this.Policy = p;
}
}
internal class PolicyExpressionEvaluator
{
private readonly ScriptRunner<double> EvaluateInternal;
public PolicyExpressionEvaluator(string expression)
{
var usings = new[]
{
"System",
"System.Collections.Generic",
"System.Linq",
"System.Threading.Tasks"
};
var references = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
.ToArray();
var options = ScriptOptions.Default
.AddImports(usings)
.AddReferences(references);
this.EvaluateInternal = CSharpScript.Create<double>(expression, options, globalsType: typeof(PolicyEvaluatorGlobals))
.CreateDelegate();
}
internal double Evaluate(Policy policy)
{
return EvaluateInternal(new PolicyEvaluatorGlobals(policy)).Result;
}
}
Roslyn is fully documented, and has the helpful Scripting API Samples page with examples.

Replace object on heap?

Maybe this is real simple or breaking all the rules or maybe I just dont know what its called so I cant find it.
Anyway, I want to be able to replace an entire object on the heap. I've added a small code sample to show what I want to do, and a way of doing it, but I just want to know if there is a more elegant way?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BasicObjectTest
{
class Program
{
static void Main(string[] args)
{
List<Test> testList = new List<Test>
{
new Test {Value=1,NiceString="First" },
new Test {Value=2,NiceString="Second" },
new Test {Value=3,NiceString="Third" }
};
var replacementTestClass = new Test { Value = 2, NiceString = "NEW" };
EasyWay(testList, replacementTestClass);
var correctTestClass = testList.FirstOrDefault(x => x.Value == 2);
Console.WriteLine(correctTestClass.NiceString); //Expecting "Forth"
Console.ReadLine();
HardWay(testList, replacementTestClass);
correctTestClass = testList.FirstOrDefault(x => x.Value == 2);
Console.WriteLine(correctTestClass.NiceString);
Console.ReadLine();
}
private static void HardWay(List<Test> testList, Test replacementTestClass)
{
//This will work!
var secondTestClass = testList.FirstOrDefault(x => x.Value == 2);
CopyPropertiesUsingPropertyInfo(secondTestClass, replacementTestClass);
}
private static void CopyPropertiesUsingPropertyInfo(Test secondTestClass, Test replacementTestClass)
{
foreach(var pi in secondTestClass.GetType().GetProperties())
{
pi.SetValue(secondTestClass, pi.GetValue(replacementTestClass, null));
}
}
private static void EasyWay(List<Test> testList, Test replacementTestClass)
{
//This wont work, but I want it to!
var secondTestClass = testList.FirstOrDefault(x => x.Value == 2);
secondTestClass = replacementTestClass;
}
}
}
and my Test object
class Test
{
public int Value { get; set; }
public string NiceString { get; set; }
}
There must be a more elegant way of doing this?
I know why the first alternative does not work: I just change the object reference for that variable.
Update:
Using this thinking I understood it for a long time I tested this now thinking it would work, but the test fails. Why? Didnt I replace the object so that every object using it should use the new object? See complete code below
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var main = new Main { Property = 1 };
var dependent = new Dependent(main);
void ChangeRef(ref Main Oldmain, Main newMain)
{
Oldmain = newMain;
}
ChangeRef(ref main, new Main { Property = 5 });
Assert.AreEqual(5,dependent.Main.Property);
}
}
public class Main
{
public int Property { get; set; }
}
public class Dependent
{
public Dependent(Main main)
{
Main = main;
}
public Main Main { get; set; }
}
There must be a more elegant way of doing this?
There is one basic thing you're missing. When you search for the object in the list, and one is found, you get back a copy of the reference pointing to that object. This means that when you alter it, you're only altering the copy. The original reference in the list is still pointing to that same old object instance.
but what if I didnt have a list. I just had the object reference in a
variable?
Then you could use the ref keyword to pass the reference type by reference:
public static void Main(string[] args)
{
var test = new Test { Value = 1, NiceString = "First" };
var newTest = new Test { Value = 2, NiceString = "AlteredTest!" };
UpdateTest(ref test, newTest);
Console.WriteLine(test.NiceString); // "AlteredTest!"
}
public static void UpdateTest(ref Test originalTest, Test other)
{
originalTest = other;
}
An alternative way to approach this is with the proverbial "extra level of indirection".
Instead of storing the objects in the list, you store wrapper objects instead. The wrapper object provides an "Item" field which points to the actual object. Then you can update the "Item" field to point it at the new object.
A simple generic wrapper class could look like this:
class Wrapper<T>
{
public T Item;
public Wrapper(T item)
{
Item = item;
}
public static implicit operator Wrapper<T>(T item)
{
return new Wrapper<T>(item);
}
}
Then you could use it like so:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication2
{
class Test
{
public int Value { get; set; }
public string NiceString { get; set; }
}
class Wrapper<T>
{
public T Item;
public Wrapper(T item)
{
Item = item;
}
public static implicit operator Wrapper<T>(T item)
{
return new Wrapper<T>(item);
}
}
class Program
{
static void Main(string[] args)
{
var testList = new List<Wrapper<Test>>
{
new Test {Value = 1, NiceString = "First"},
new Test {Value = 2, NiceString = "Second"},
new Test {Value = 3, NiceString = "Third"}
};
var replacementTestClass = new Test { Value = 2, NiceString = "NEW" };
EasyWay(testList, replacementTestClass);
var correctTestClass = testList.FirstOrDefault(x => x.Item.Value == 2);
Console.WriteLine(correctTestClass.Item.NiceString); //Expecting "New"
Console.ReadLine();
}
private static void EasyWay(List<Wrapper<Test>> testList, Test replacementTestClass)
{
var secondTestClass = testList.FirstOrDefault(x => x.Item.Value == 2);
secondTestClass.Item = replacementTestClass;
}
}
}

Categories

Resources