How to use a dynamically generated object as the d

2019-05-07 11:08发布

问题:

We are using this component www.codeeffects.com which allows us to create business rules based on the object properties.

The html of the view is like this:

@{
    ViewBag.Title = "Post Example";

    Html.CodeEffects().Styles()
        .SetTheme(ThemeType.Gray)
        .Render();
}
@using (Html.BeginForm("Evaluate", "Post", FormMethod.Post))
{
    <div class="area">
        <h1 class="title">Post Example</h1>
        <div style="margin-top:10px;">
            <span>Info:</span>
            <span style="color:Red;">@ViewBag.Message</span>
        </div>
        <div style="margin-top:10px;">
            @{
    Html.CodeEffects().RuleEditor()
        .Id("ruleEditor")
        .SaveAction("Save", "Post")
        .DeleteAction("Delete", "Post")
        .LoadAction("Load", "Post")
        .Mode(RuleType.Execution)
        .ContextMenuRules(ViewBag.ContextMenuRules)
        .ToolBarRules(ViewBag.ToolBarRules)
        .Rule(ViewBag.Rule)
        .Render();
            }
        </div>
    </div>
    <div class="area">
        <h1 class="title" style="margin-top:20px;">Rule Test Form</h1>
        @{
    Html.RenderPartial("_PatientForm");
        }
    </div>
}
@{
    Html.CodeEffects().Scripts().Render();
}

the index action in the controller is like this:

 [HttpGet]
        public ActionResult Index()
        {
            ViewBag.Rule = RuleModel.Create(typeof(Patient));
            return View();
        }

The class Patient is like this:

// External methods and actions
    [ExternalMethod(typeof(PatientService), "IsToday")]
    [ExternalAction(typeof(PatientService), "RequestInfo")]

    // Dynamic Menu Data Sources; details can be found at
    // http://codeeffects.com/Doc/Business-Rules-Dynamic-Menu-Data-Sources

    // The getEducationTypes() client-side function declared in /Views/Shared/_Layout.cshtml
    [Data("Education", "getEducationTypes")]
    // The List() method declared by the Physician class
    [Data("Physicians", typeof(Physician), "List")]
    public class Patient
    {
        // C-tor
        public Patient()
        {
            this.ID = Guid.Empty;
            this.Gender = Gender.Unknown;
        }

        // This property will not appear in the Rule Editor - Code Effects component ignores Guids.
        // Details at http://codeeffects.com/Doc/Business-Rules-Data-Types
        public Guid ID { get; set; }

        [Field(DisplayName = "First Name", Description = "Patient's first name", Max = 30)]
        public string FirstName { get; set; }

        [Field(DisplayName = "Last Name", Max = 30, Description = "Patient's last name")]
        public string LastName { get; set; }

        [Field(DisplayName = "Email Address", ValueInputType = ValueInputType.User, Max = 150, Description = "Email address of the patient")]
        public string Email { get; set; }

        [Field(DisplayName = "Date of Birth", DateTimeFormat = "MMM dd, yyyy")]
        public DateTime? DOB { get; set; }

        [Field(ValueInputType = ValueInputType.User, Description = "Patient's gender")]
        public Gender Gender { get; set; }

        // This field uses the "Physicians" dynamic menu source (declared at class level)
        [Field(DisplayName = "Physician", DataSourceName = "Physicians", Description = "Patient's primary physician")]
        public int PhysicianID { get; set; }

        // This field uses the "Education" client-side dynamic menu source (declared at class level)
        [Field(DisplayName = "Education", DataSourceName = "Education", Description = "Patient's education level")]
        public int EducationTypeID { get; set; }

        [Field(Min = 0, Max = 200, Description = "Current pulse")]
        public int? Pulse { get; set; }

        [Field(Min = 0, Max = 200, DisplayName = "Systolic Pressure", Description = "Current systolic pressure")]
        public int? SystolicPressure { get; set; }

        [Field(Min = 0, Max = 200, DisplayName = "Diastolic Pressure", Description = "Current Diastolic pressure")]
        public int? DiastolicPressure { get; set; }

        [Field(Min = 0, Max = 110, Description = "Current temperature")]
        public decimal? Temperature { get; set; }

        [Field(DisplayName = "Headaches Box", Description = "Does the patient have frequent headaches?")]
        public bool Headaches { get; set; }

        [Field(DisplayName = "Allergies Box", Description = "Any allergies?")]
        public bool Allergies { get; set; }

        [Field(DisplayName = "Tobacco Box", Description = "Does the patient smoke?")]
        public bool Tobacco { get; set; }

        [Field(DisplayName = "Alcohol Box", Description = "Alcohol use?")]
        public bool Alcohol { get; set; }

        public Address Home { get; set; }
        public Address Work { get; set; }

        // This property is used to display outputs of rule actions
        [ExcludeFromEvaluation]
        public string Output { get; set; }

        [Method("Full Name", "Joins together patient's first and last names")]
        public string FullName()
        {
            return string.Format("{0} {1}", this.FirstName, this.LastName);
        }

        // Empty overload of the Register method.
        // No Method attribute is needed here because its
        // display name is the same as its declared name.
        [Action(Description = "Registers new patient")]
        public void Register()
        {
            this.Output = "The patient has been registered";
        }

        // Overload of the Register method that takes one param.
        // Both overloads can be used in Code Effects as two different actions
        // as long as their display names are different.
        [Action("Register with a Message", "Registers new patient with additional info")]
        public void Register([Parameter(ValueInputType.User, Description = "Output message")] string message)
        {
            this.Output = message;
        }
    }

However we want to a dinamyc object built using reflection, the method I created is this:

 private static object CreateOurNewObject()
        {
            string _xml = "<root>" +
                "<column name=\"Name\">Miron</column>" +
                "<column name=\"LastName\">Abramson</column>" +
                "<column name=\"Blog\">www.blog.mironabramson.com</column>" +
                "</root>";

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(_xml);

            // create a dynamic assembly and module 
            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = "tmpAssembly";
            System.Reflection.Emit.AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            ModuleBuilder module = assemblyBuilder.DefineDynamicModule("tmpModule");

            // create a new type builder
            TypeBuilder typeBuilder = module.DefineType("BindableRowCellCollection", TypeAttributes.Public | TypeAttributes.Class);

            // Loop over the attributes that will be used as the properties names in out new type
            foreach (XmlNode node in xmlDoc.SelectSingleNode("root").ChildNodes)
            {
                string propertyName = node.Attributes["name"].Value;

                // Generate a private field
                FieldBuilder field = typeBuilder.DefineField("_" + propertyName, typeof(string), FieldAttributes.Private);
                // Generate a public property
                PropertyBuilder property =
                    typeBuilder.DefineProperty(propertyName,
                                     PropertyAttributes.None,
                                     typeof(string),
                                     new Type[] { typeof(string) });

                // The property set and property get methods require a special set of attributes:

                MethodAttributes GetSetAttr =
                    MethodAttributes.Public |
                    MethodAttributes.HideBySig;

                // Define the "get" accessor method for current private field.
                MethodBuilder currGetPropMthdBldr =
                    typeBuilder.DefineMethod("get_value",
                                               GetSetAttr,
                                               typeof(string),
                                               Type.EmptyTypes);

                // Intermediate Language stuff...
                ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
                currGetIL.Emit(OpCodes.Ldarg_0);
                currGetIL.Emit(OpCodes.Ldfld, field);
                currGetIL.Emit(OpCodes.Ret);

                // Define the "set" accessor method for current private field.
                MethodBuilder currSetPropMthdBldr =
                    typeBuilder.DefineMethod("set_value",
                                               GetSetAttr,
                                               null,
                                               new Type[] { typeof(string) });

                // Again some Intermediate Language stuff...
                ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
                currSetIL.Emit(OpCodes.Ldarg_0);
                currSetIL.Emit(OpCodes.Ldarg_1);
                currSetIL.Emit(OpCodes.Stfld, field);
                currSetIL.Emit(OpCodes.Ret);

                // Last, we must map the two methods created above to our PropertyBuilder to 
                // their corresponding behaviors, "get" and "set" respectively. 
                property.SetGetMethod(currGetPropMthdBldr);
                property.SetSetMethod(currSetPropMthdBldr);
            }

            // Generate our type
            Type generetedType = typeBuilder.CreateType();

            // Now we have our type. Let's create an instance from it:
            object generetedObject = Activator.CreateInstance(generetedType);

            // Loop over all the generated properties, and assign the values from our XML:
            PropertyInfo[] properties = generetedType.GetProperties();

            int propertiesCounter = 0;

            // Loop over the values that we will assign to the properties
            foreach (XmlNode node in xmlDoc.SelectSingleNode("root").ChildNodes)
            {
                string value = node.InnerText;
                properties[propertiesCounter].SetValue(generetedObject, value, null);
                propertiesCounter++;
            }

            //Yoopy ! Return our new genereted object.
            return generetedObject;
        }

How do we replace the line in the index action to use that object instead of Patient? typeof(object), wouldn't work.

回答1:

Extract a portion from the CreateOurNewObject() method that deals with a type creation. Call it CreateType(string xml).

Change the AssemlbyBuilderAccess.Run to AssemblyBuilderAccess.RunAndSave. Then, once the type has been created, call the assemblyBuilder.Save() method. Save it in the location that Assembly.Load will find (e.g. bin or one of .net temp folders), or any other place so long it in the search path.

Use it to create the type and to instantiate objects.

Then, in the index, call

Type myType = CreateType(xml);
RuleModel.Create(myType);

If you are doing evaluation outside make sure to use the same type (don't regenerate it every time). Which means you will need to load it first.

Type myType = Assembly.Load(assemblyName);
object myObject = Activator.CreateInstance(myType);
//...populate myObject with necessary values based on your xml
Evaluator ev = new Evaluator(myType, rule);
bool result = ev.Evaluate(myObject);

or you could use DynamicEvaluator, which simply calls myObject.GetType()

DynamicEvaluator ev = new DynamicEvaluator(rule);
bool result = ev.Evaluate(myObject);

This should work. The important piece here is that you save your assembly first (it cannot be read from the memory at the moment), and that it is in a folder that is part of a search path so that Assembly.Load(name) can find it.



回答2:

Using your TypeBuilder, you might try tacking on an existing or custom interface to your generated type and then using that as your type.

typeBuilder.AddInterfaceImplementation(typeof(IMyInterface));

Now the object you've created (in the above example) has type IMyInterface, which you could then use in your Index action:

ViewBag.Rule = RuleModel.Create(typeof(IMyInterface));