c# instantiate mapping class by name - c#

I have standard XML data coming in that represents a purchase order from a customer. Each customer will populate the XML data differently so I need a separate method to process the order based on their specifications. My goal is to make this scalable so I used an interface because I would like to be able to create additional classes as new customers are added.
How do I select a different Map class based on the customer?
public class XmlPurchaseOrder
{
public DateTime Created { get; set; }
public string CustomerId { get; set; }
public string PurchaseOrderId { get; set; }
public string MapName { get; set; }
//...
}
public interface IXmlMapper
{
CustomerOrder MapToCustomerOrder(XmlPurchaseOrder po);
}
public class CustomerOrder
{
public int Id { get; set; }
public string CustomerId { get; set; }
public string CustomerPoId { get; set; }
public DateTime OrderDate { get; set; }
}
//Maps by customer
public class McClownMap : IXmlMapper
{
public CustomerOrder MapToCustomerOrder(XmlPurchaseOrder po)
{
return new CustomerOrder()
{
CustomerId = "McD123",
CustomerPoId = po.PurchaseOrderId,
OrderDate = DateTime.Today
};
}
}
public class BkMap : IXmlMapper
{
public CustomerOrder MapToCustomerOrder(XmlPurchaseOrder po)
{
return new CustomerOrder()
{
CustomerId = "BxK331",
CustomerPoId = string.Format("BxK{0}", po.PurchaseOrderId),
OrderDate = DateTime.Today.AddDays(-1)
};
}
}
public class TacoWorldMap : IXmlMapper
{
public CustomerOrder MapToCustomerOrder(XmlPurchaseOrder po)
{
return new CustomerOrder()
{
CustomerId = "TW-33",
CustomerPoId = string.Format("{0}-{1}",po.PurchaseOrderId, DateTime.Now.Ticks),
OrderDate = po.Created
};
}
}
class Program
{
private static void Main(string[] args)
{
const string xmlFile = "CustomerPo.xml";
var objStreamReader = new StreamReader(xmlFile);
var xmlData = new XmlSerializer(new XmlPurchaseOrder().GetType());
var po = (XmlPurchaseOrder)xmlData.Deserialize(objStreamReader);
objStreamReader.Close();
//How do I create the associated class by the MapName specified.
IXmlMapper t = Activator.CreateInstance(Type.GetType(po.MapName));
var customerOrder = t.MapToCustomerOrder(po);
//...
}
}
Thanks

Perhaps you could split the workload, so that your Deserializer decorates the XmlPurchaseOrder with a PurchaseOrderType (enum) based on the characteristics that determines the purchase order type. If this is determined by the XML structure itself, like via a tag or an attribute, this is a simple task - otherwise subclass the XmlPurchaseOrder and introduce a virtual method that "calculates" the type.
The other part of the job is to instantiate the concrete PurchaseOrder - this can be simplified using a Factory with one Create method for each kind of purchase order, or more brute force with a big switch on the PurchaseOrderType enum.

A very simple way would be to add a config setting for each customer that maps to the type used to process their order.
<appSettings>
<add key="Customer1" value="MyApp.Logic.Customer1Processor" />
<add key="Customer2" value="MyApp.Logic.Customer2Processor" />
//etc...
</appSettings>
then use Activator.CreateInstance like you have currently.

This makes me think of the Provider Model available through .Net. I am currently using it to instantiate different API Providers based on their Provider Type.
You can set up a near infinite number of different classes that inherit from ProviderBase and add whatever methods you will need to this class. Then, you create each .dll to perform whatever functionality you need and since they have all inherited from some similar base class, you can put the primary method to begin processing the functionality in there.
Base class:
namespace ProviderManager
{
abstract public class SendProviderBase : ProviderBase
{
abstract public void Process(whatever args you need);
}
}
Helper class used to instantiate different Providers
namespace ProviderManger
{
public class ProviderManger
{
private ConfigHandler sendConfig;
public ProviderManger()
{
sendConfig = ConfigurationManger.GetSection("sendProvider") as ConfigHandler;
}
public SendProviderBase GetSendProviderBase(string MapName)
{
try
{
ProviderSettings settings = sendConfig.Providers[MapName];
return (SendProviderBase)ProvidersHelper.InstantiateProvider(settings, typeof(SendProviderBase));
}
//appropriate catch block and whatever else
}}
ConfigHandler code
namespace ProviderManger
{
class ConfigHandler : ConfigurationSection
{
[ConfigurationProperty("providers")}
public ProviderSettingsCollection Providers
{
get
{ return base["providers"] as ProviderSettingsCollection; }
}}}
Usage in Main for you
providerManager = new ProviderManager();
SendProviderManger provider = providerManager.GetSendProviderBase(MapName);
provider.Process(whatever args...);
Obviously you could rename SendProviderBase to something more related to what you're doing but I kept that name since it was consistent through my code here. The only other thing you'll need is a declaration of the .config section used to store MapNames that map to the .dll that is related to it. Since my application is a web service we have a web.config with the following sections:
Custom Section declaration:
<configSections>
<section name="sendProvider" type="KC.ProviderManager.ConfigHandler, ProviderManager"/>
</configSections>
And the Send Provider section:
<sendProviders>
<providers>
<add name="MapNameX" type="namespace.classname, assemblyname">
So basically what this does is you feed providerManger.GetSendProviderBase(MapNameX) the name in the web.config and it returns to you (assuming everything else is built correctly) the class found in that assembly. Then you can call the method found on the base class to begin processing (provider.Process()).
The other necessary References are as follows
System.Reflection;
System.Configuration;
System.Configuration.Provider;
System.Web.Configuration;
This is highly scalable as you can add as many providers as you want as long as they inherit correctly
Or, for a more simplified but still quite scalable solution similar to this check out this link

I did some further research and what I needed was a Factory. This is my interpretation of a demo in a Pluralsight.com video called Design Patterns Library that was presented by David Starr
public class CustomerMapFactory
{
private Type[] _mapTypes;
public CustomerMapFactory()
{
LoadAvailableMaps();
}
//Return a newly created Type
public IXmlMapper CreateInstance(string customerId)
{
var t = GetTypeToCreate(customerId);
if (t == null) throw new Exception("Customer map not found");
return Activator.CreateInstance(t) as IXmlMapper;
}
//Find the map to instantiate
Type GetTypeToCreate(string customerId)
{
return _mapTypes.FirstOrDefault(tpMap => tpMap.Name.Contains(customerId));
}
//Identify all Types that use the IXmlMapper
private void LoadAvailableMaps()
{
_mapTypes = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => t.GetInterface(typeof(IXmlMapper).ToString()) != null)
.ToArray();
}
}
}
Here is the program that utilizes the factory
class Program
{
private static void Main(string[] args)
{
//Same as above
const string xmlFile = "CustomerPo.xml";
var objStreamReader = new StreamReader(xmlFile);
var xmlData = new XmlSerializer(new XmlPurchaseOrder().GetType());
var po = (XmlPurchaseOrder)xmlData.Deserialize(objStreamReader);
objStreamReader.Close();
//Now utilizing the factory.
var mf = new CustomerMapFactory();
var poMap = mf.CreateInstance("BkMap");
var customerOrder = poMap.MapToCustomerOrder(po);
}

Related

Custom IOptions map path

I currently have a Json config file that looks something like this :
{
"MySettings" " {
"SomeSetting" : "SomeValue"
}
}
In the perfect world, I would have a class that matches that same structure. But, I need to map it to a class that would look something like this :
public class MySettingsUpdated
{
public string MyRenamedSetting {get;set;}
}
I am already using a custom ConfigurationProvider to get data from a configuration file (for various reasons), and I -could- create the data in the expected path in there, but it would make my life much easier if I could decorate the new class with some type of attribute in order to specify where the data needs to come from.
Any way to do this?
static string data = #"
{
""class"": {
""property"" : ""some string!""
}
}";
class DTO
{
[JsonProperty("class")]
public Data Property { get; set; }
}
class Data
{
[JsonProperty("property")]
public string Value { get; set; }
}
static void Main(string[] args)
{
var result = JsonConvert.DeserializeObject<DTO>(data);
}
You can use the .Bind() method exposed on the IConfigurationSection interface.
In a class it could look like
public class SomeClassDoingWork
{
private MyConfigClass MyConf = new MyConfigClass();
public SomeClassDoingWork(IConfiguration config)
{
config.GetSection("MySettings").Bind(MyConf);
}
}

Using Unity to get objects based on name alone

I have an app with a handler method. The handler method gets a json string which includes the name of the object that needs to handle the request and the parameters for the request. Basically, something (I'll keep it simple) like this:
public interface IJob
{
bool Execute();
bool Hydrate(string source);
}
public class JobBase
{
public int Id { get; set; }
public JobType JobType { get; set; }
public CronExpression CronExpression { get; set; }
}
public class JobSubmitClone : JobBase, IJob
{
public string[] Tokens { get; set; }
public bool Hydrate(string source)
{
// code omitted...
return true;
}
public bool Execute()
{
// code omitted...
return true;
}
}
IJob and JobBase are both kept in a Common class project. All apps reference this DLL.
In my main app I have Unity installed and one of the steps in loading the container is accomplished like:
// Scan assemblies for Job definitions...
_container.RegisterTypes(AllClasses.FromAssembliesInBasePath().
Where(type => typeof(IJob).IsAssignableFrom(type)),
WithMappings.FromAllInterfaces,
WithName.TypeName,
WithLifetime.Transient);
Each "Job" is defined in its own class project and is NOT referenced by the main app. Each "Job" must inherit from JobBase and IJob.
The main app has a simple REST service exposed. You can post something like:
{ jobName : JobSubmitClone, Id : 1, JobType : 2, CronExpression : '' }
In the main app I am trying to pull the object from the container based on JobName. I've tried this (yes, I know it violates the IoC pattern):
var container = UnityHelpers.GetConfiguredContainer();
var job = container.Resolve<IJob>(myParams.jobName); // "JobSubmitClone" //
var hydrated = job.Hydrate(myParams);
if(hydrated)
var result = job.Execute();
I am getting the following error:
Exception is: InvalidOperationException - The current type, IJob, is
an interface and cannot be constructed. Are you missing a type
mapping?
What am I missing?
Each "Job" is defined in its own class project and is NOT referenced
by the main app. Each "Job" must inherit from JobBase and IJob.
Have you looked into MEF? It has the ability to query and load a class by its metadata. I tend to use Unity for known, compile-time dependencies and MEF for dynamic assemblies loaded at runtime. (There's no reason you can't use both in the same project.)
We do something similar to what you're looking for, I think. We load workflows based on their class name.
Just decorate the Job with a System.ComponentModel.Composition.MetadataAttribute....
[MetadataAttribute]
public class WorkflowMetadataAttribute : Attribute, IWorkflowMetadata
{
public WorkflowMetadataAttribute(string typeName) {
TypeName = typename;
}
public string TypeName { get; private set; }
}
Which you put on the thing you want to export....
public interface IWorkflow // aka IJob
{
void Execute();
}
[Export(typeof(IWorkflow))]
[WorkflowMetadata("WhateverWorkflow")]
public class WhateverWorkflow : IWorkflow
{
public void Execute() { }
}
The exported class can be built separately of the project that runs it. If you build it as a library into a separate assembly, you can load the assembly (or directory of assemblies) in an importer class.
public class WorkflowCatalog : IPartImportsSatisfiedNotification
{
[ImportMany]
public IEnumerable<Lazy<IWorkflow, IWorkflowMetadata>> Workflows { get; private set; }
public void Compose() {
var path = Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location );
var catalog = new DirectoryCatalog( path );
var compositionContainer = new CompositionContainer( catalog );
compositionContainer.ComposeParts(this);
}
public void OnImportsSatisfied() {
var workflow = Workflows.Single(w => w.Metadata.TypeName == "WhateverWorkflow").Value;
workflow.Execute();
}
}
IJob, IJobMetadata, and JobBase live in the core. Job classes live in their own libraries (or they can live in the main program too I suppose).
Turns out that there are a lot of ways to manipulate Unity. This is what ended up working so far:
container.RegisterTypes(
AllClasses.FromLoadedAssemblies().Where(type => typeof(IJob).IsAssignableFrom(type) && type.IsClass),
WithMappings.FromAllInterfaces,
t => t.IsNested ? t.DeclaringType.Name + "." + t.Name : t.Name,
WithLifetime.Transient);
I also built an extension method:
public static IJob Job(this string src)
{
var container = UnityConfig.GetConfiguredContainer();
var job = container.Resolve<IJob>(src);
return job;
}
I created a small model for the Minimum Payload:
public class MinimumCommandModel : IRequest<MinimumResultModel>
{
public MinimumCommandModel(string json)
{
FullPayloadString = json;
MinimumPayload = JsonConvert.DeserializeObject<MinimumPayload>(json);
}
public string MinimumPayloadString => JsonConvert.SerializeObject(MinimumPayload);
public string FullPayloadString { get; set; }
public MinimumPayload MinimumPayload { get; set; }
}
I can then directly get a job from a (JSON) sting payload:
var command = new MinimumCommandModel(Request.Content.ReadAsStringAsync().Result);
var job = command.MinimumPayload.JobName.Job();

How to ignore a property based on a runtime condition?

I have a simple pair of classes which for I've set up a mapping at initialization time.
public class Order {
public int ID { get; set; }
public string Foo { get; set; }
}
public class OrderDTO {
public int ID { get; set; }
public string Foo { get; set; }
}
...
Mapper.CreateMap<Order, OrderDTO>();
Now at a certain point I need to map an Order to an OrderDTO. BUT depending on some circumstances, I might need to ignore Foo during mapping. Let's also assume that I cannot "store" the condition in the source or destination object.
I know how I can configure the ignored properties at initialization time, but I have no idea how I could achieve such a dynamic runtime behavior.
Any help would be appreciated.
UPDATE
My use case for this behaviour is something like this. I have an ASP.NET MVC web grid view which displays a list of OrderDTOs. The users can edit the cell values individually. The grid view sends the edited data back to the server like a collection of OrderDTOs, BUT only the edited field values are set, the others are left as default. It also sends data about which fields are edited for each primary key. Now from this special scenario I need to map these "half-empty" objects to Orders, but of course, skip those properties which were not edited for each object.
The other way would be to do the manual mapping, or use Reflection somehow, but I was just thinking about if I could use AutoMapper in some way.
I've digged into the AutoMapper source code and samples, and found that there is a way to pass runtime parameters at mapping time.
A quick example setup and usage looks like this.
public class Order {
public int ID { get; set; }
public string Foo { get; set; }
}
public class OrderDTO {
public int ID { get; set; }
public string Foo { get; set; }
}
...
Mapper.CreateMap<Order, OrderDTO>()
.ForMember(e => e.Foo, o => o.Condition((ResolutionContext c) => !c.Options.Items.ContainsKey("IWantToSkipFoo")));
...
var target = new Order();
target.ID = 2;
target.Foo = "This should not change";
var source = new OrderDTO();
source.ID = 10;
source.Foo = "This won't be mapped";
Mapper.Map(source, target, opts => { opts.Items["IWantToSkipFoo"] = true; });
Assert.AreEqual(target.ID, 10);
Assert.AreEqual(target.Foo, "This should not change");
In fact this looks quite "technical", but I still think there are quite many use cases when this is really helpful. If this logic is generalized according to application needs, and wrapped into some extension methods for example, then it could be much cleaner.
Expanding on BlackjacketMack's comment for others:
In your MappingProfile, add a ForAllMaps(...) call to your constructor.
using AutoMapper;
using System.Collections.Generic;
using System.Linq;
public class MappingProfile : Profile
{
public MappingProfile()
{
ForAllMaps((typeMap, mappingExpression) =>
{
mappingExpression.ForAllMembers(memberOptions =>
{
memberOptions.Condition((o1, o2, o3, o4, resolutionContext) =>
{
var name = memberOptions.DestinationMember.Name;
if (resolutionContext.Items.TryGetValue(MemberExclusionKey, out object exclusions))
{
if (((IEnumerable<string>)exclusions).Contains(name))
{
return false;
}
}
return true;
});
});
});
}
public static string MemberExclusionKey { get; } = "exclude";
}
Then, for ease of use, add the following class to create an extension method for yourself.
public static class IMappingOperationOptionsExtensions
{
public static void ExcludeMembers(this AutoMapper.IMappingOperationOptions options, params string[] members)
{
options.Items[MappingProfile.MemberExclusionKey] = members;
}
}
Finally, tie it all together: var target = mapper.Map<Order>(source, opts => opts.ExcludeMembers("Foo"));

Accessing custom objects in DomainService from client

I am using Domain Service to fetch data from database from Silverlight Client.
In DomainService1.cs, I have added the following:
[EnableClientAccess()]
public class Product
{
public int productID;
public string productName;
public List<Part> Parts = new List<Part>(); //Part is already present in Model designer
}
In DomainService1 class I added a new method to retrive a collection of the custom class object:
[EnableClientAccess()]
public class DomainService1 : LinqToEntitiesDomainService<HELPERDBNEWEntities1>
{
...
public List<Product> GetProductsList(...)
{
List<Product> resultProducts = new List<Product>();
...
return resultProducts;
}
}
From the silverlight client I am trying to access that method:
DomainService1 ds1 = new DomainService1();
var allproductList = ds1.GetProductsList(...);
ds1.Load<SLProduct>(allproductList).Completed += new EventHandler(Load_Completed); //Not correct usage
However it is not the correct way to call the new method. The reason I added a new class Product in DomainServices.cs is to have an efficient grouping. I cannot achieve the same using the model classes auto-generated by the entity framework.
How call I call the new method from the client?
I believe there is a similar question with an answer here:
Can a DomainService return a single custom type?
Also, here is some discussion about the overall problem of adding custom methods in a Domain Service:
http://forums.silverlight.net/t/159292.aspx/1
While I don't know what you mean by "it is not the correct way to call the new method", or if you're getting any errors, I thought maybe posting some working code might help.
My POCO
public class GraphPointWithMeta
{
[Key]
public Guid PK { get; set; }
public string SeriesName { get; set; }
public string EntityName { get; set; }
public double Amount { get; set; }
public GraphPointWithMeta(string seriesName, string entityName, double amount)
{
PK = Guid.NewGuid();
SeriesName = seriesName;
EntityName = entityName;
Amount = amount;
}
// Default ctor required.
public GraphPointWithMeta()
{
PK = Guid.NewGuid();
}
}
A method in the domain service (EnableClientAccess decorates the class)
public IEnumerable<GraphPointWithMeta> CallingActivityByCommercial()
{
List<GraphPointWithMeta> gps = new List<GraphPointWithMeta>();
// ...
return gps;
}
Called from the Silverlight client like
ctx1.Load(ctx1.CallingActivityByCommercialQuery(), CallingActivityCompleted, null);
client call back method
private void CallingActivityCompleted(LoadOperation<GraphPointWithMeta> lo)
{
// lo.Entities is an IEnumerable<GraphPointWithMeta>
}
I am not sure if your Product class is an actual entity or not. From the way it is defined, it does not appear to be an entity. My answer is assuming it is not an entity. You will need to apply the DataMemberAttribute for your Product properties, and you wouldn't load the product list - load is for Entity Queries (IQueryable on the service side). You would just invoke it like this (client side):
void GetProductList( Action<InvokeOperation<List<Product>>> callback)
{
DomainService ds1 = new DomainService();
ds1.GetProductsList(callback, null);//invoke operation call
}
And the domain service's (server side) method needs the InvokeAttribute and would look like this:
[EnableClientAccess]
public class MyDomainService
{
[Invoke]
public List<Product> GetProductList()
{
var list = new List<Product>();
...
return list;
}
}
And here is how your Product class might be defined (if it is not an entity):
public class Product
{
[DataMember]
public int productID;
[DataMember]
public string productName;
[DataMember]
public List<Part> Parts = new List<Part>(); // you might have some trouble here.
//not sure if any other attributes are needed for Parts,
//since you said this is an entity; also not sure if you
//can even have a list of entities or it needs to be an
//entity collection or what it needs to be. You might
//have to make two separate calls - one to get the products
//and then one to get the parts.
}
Like I said, i am not sure what Product inherits from... Hope this helps.

How to extend mef using custom attributes?

Is it possible to add some additional attributes to my components which are then set/hydrated using some custom logic/perhaps from a data store? Similar to adding some custom builder strategy in cab/unity ?
UPDATE
e.g.
assuming a class has these properties
[MyImport] string name1 { get; set }
[MyImport] MyType name2 { get; set }
[MyGuid] Guid { get; set; }
with custom attributes MyImport and MyGuid which are resolved by an "extension" to MEF ( which gets executed after the [imports] are resolved ) and has code along these lines
// property SET
var valu = myDBStore.GetValue( instanceGUID, propertyInfo.Name);
propertyInfo.SetValue( instance, TypeDescripter.GetConverter(valu).ConvertTo(propertyType), null);
// property GET - for example only, used during dehydration outside of MEF !
var valu = propertyInfo.GetValue( instance, null);
myDBStore.SetValue( instanceGUID, propertyInfo.Name, TypeDescripter.GetConverter(valu).ConvertTo(typeof(string));
// the above is pseudo code only, pls no comments on correct args/syntax :)
EDIT
components which are then set/hydrated using some custom logic/perhaps from a data store
One can do this via an "ExportFactory".
// "ExportFactory"
public sealed class DataStoreProvider
{
[Export(typeof(Model))]
public Model Item
{
get
{
return [custom logic];
}
}
}
public class NeedsModel
{
[Import(typeof(Model))]
public Model Item { get; set; }
}
Initial Answer
This is possible through MEF's Lazy<T, TMetadata>.
public interface ISomeMetadata
{
string UsefulInfo { get; }
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class ExportBaseAttribute : ExportAttribute, ISomeMetadata
{
public ExportBaseAttribute(string usefulInfo)
:base(typeof(BaseExport))
{
UsefulInfo = usefulInfo;
}
public string UsefulInfo { get; private set; }
}
// BaseExport class is not needed.. just showing advanced attribute usage.
public abstract class BaseExport { }
[ExportBase("Useful Filter Information")]
public class SomeExport : BaseExport
{
}
Then, in your host (composer), you can
[ImportMany(typeof(BaseExport))]
Lazy<BaseExport, ISomeMetadata>[] _baseExports
After you compose, you can run a LINQ filter using .Metadata
var goodExports = from export in _baseExports
where export.Metadata.UsefulInfo ...
select export;

Categories

Resources