I am making a custom configuration in my winform application.
(It will represent a country-corrency list)
First the CountryList class
namespace UtilityMethods
{
public class CountryList : ConfigurationSection
{
public CountryList()
{
//
// TODO: Add constructor logic here
//
}
[ConfigurationProperty("CountryCurrency", IsRequired = true)]
public Hashtable CountryCurrencies
{
get
{
return CountryCurrency.GetCountryCurrency();
}
}
}
}
The GetCountryCurrency() method is defined in CountryCurrency class as under
namespace UtilityMethods
{
public static class CountryCurrency
{
public static Hashtable GetCountryCurrency()
{
Hashtable ht = new Hashtable();
ht.Add("India", "Rupees");
ht.Add("USA", "Dollar");
return ht;
}
}
}
The app.config file looks like
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name ="CountryList1" type ="UtilityMethods.CountryList,CountryList,Version=2.0.0.0,
Culture=neutral"/>
</configSections>
<appSettings />
</configuration>
And I am calling this from a button_click's event as
try
{
CountryList cList = ConfigurationManager.GetSection("CountryList") as CountryList;
Hashtable ht = cList.CountryCurrencies;
}
catch (Exception ex)
{
string h = ex.Message;
}
Upon running the application and clicking on the button I am getting this error
Could not load type 'UtilityMethods.CountryList' from assembly 'System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
Please help (dotnet framework : 3.5 Language: C#)
I figured out.Instead of using
<section name ="CountryList1" type ="UtilityMethods.CountryList,CountryList,Version=2.0.0.0, Culture=neutral"/>
It will be
<section name ="CountryList1" type ="UtilityMethods.CountryList,UtilityMethods"/>
i.e. namespace.classname,namespace
Related
When I try to the following LINQ Filter
var productsInfo = from product in productsElement.Descendants("product").Filter(rule)
from photo in product.Descendants("photo")
from parameter in product.Descendants("parameter")
let id = product.Attribute("id")
let addr = photo.Attribute("addr")
let name = parameter.Attribute("name")
select new { ID = id.Value, Addr = addr.Value, Name = name.Value };
I get the following error:
The given ruleset does not contain any rules with type
System.Xml.Linq.XElement, System.Xml.Linq, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089 (Error E106)
My rule:
<?xml version="1.0" encoding="utf-8"?><codeeffects xmlns="http://codeeffects.com/schemas/rule/41" xmlns:ui="http://codeeffects.com/schemas/ui/4"><rule id="09973a56-3d6a-4616-ae1c-40d0d17e95b9" webrule="4.3.6.7" utc="2017-07-24T10:07:08.6346" type="testSlimWebRule.products, testSlimWebRule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" eval="true"><definition><condition type="equal"><property name="AllProducts.product.id" /><value type="numeric">1</value></condition></definition><format><lines /></format></rule></codeeffects>
The XML:
XDocument productsElement = XDocument.Parse(#"<products>
<AllProducts>
<product id='1'>
<photo addr='1.jpg'/>
<parameter name='name'/>
</product>
<product id='2'>
<photo addr='2.jpg'/>
<parameter name='Kenneth'/>
</product>
</AllProducts>
</products> ");
The products class generated using Visual Studio "Insert as XML class":
namespace testSlimWebRule
{
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class products
{
private productsAllProducts allProductsField;
/// <remarks/>
public productsAllProducts AllProducts
{
get
{
return this.allProductsField;
}
set
{
this.allProductsField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsAllProducts
{
private productsAllProductsProduct productField;
/// <remarks/>
public productsAllProductsProduct product
{
get
{
return this.productField;
}
set
{
this.productField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsAllProductsProduct
{
private productsAllProductsProductPhoto photoField;
private productsAllProductsProductParameter parameterField;
private byte idField;
/// <remarks/>
public productsAllProductsProductPhoto photo
{
get
{
return this.photoField;
}
set
{
this.photoField = value;
}
}
/// <remarks/>
public productsAllProductsProductParameter parameter
{
get
{
return this.parameterField;
}
set
{
this.parameterField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public byte id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsAllProductsProductPhoto
{
private string addrField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string addr
{
get
{
return this.addrField;
}
set
{
this.addrField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsAllProductsProductParameter
{
private string nameField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string name
{
get
{
return this.nameField;
}
set
{
this.nameField = value;
}
}
}
}
The ASP.net part:
<rule:RuleEditor ID="ruleEditor" runat="server"
Mode="Filter"
ShowToolBar="false"
SourceAssembly="testSlimWebRule"
SourceType="testSlimWebRule.products" />
I have tried several combinations of setting the filter, but just can't find a solution.
What am I missing?
The Filter() extension takes a collection of objects and evaluates them against a given rule individually. The rule engine takes a type of an element of the collection and uses it to compile a lambda with a parameter of that type.
List<MyClass> list = new List<MyClass>();
list.Filter(rule);
In this example the rule is of type MyClass and it gets applied to each object in the list similar to:
Evaluator<MyClass> ev = new Evaluator<MyClass>(rule);
foreach (var item in list)
{
ev.Evaluate(item);
}
You may read more on CodeEffects documentation page: Rule-Based Data Filtering Using LINQ to Object Provider.
In your example you made few mistakes:
You generate classes for XML elements, but don't use them.
The auto-generated classes incorrectly map the AllProduct field. It is supposed to be an array of productsProduct.
You apply a filter to a collection of XElement objects but the rule has type testSlimWebRule.products. It should be XElement in this case.
The example below will demonstrate four possible options, with the first one being the best.
Main
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Serialization;
using CodeEffects.Rule.Core;
namespace testSlimWebRule
{
class Program
{
/* Output:
* Option A: 1, Bob, 1.jpg
* Option B: 1, Bob, 1.jpg
* Option C: 1, Bob, 1.jpg
* Option D: 2, Kenneth, 2.jpg
*/
static void Main(string[] args)
{
string xml =
#"<products>
<AllProducts>
<product id='1'>
<photo addr='1.jpg'/>
<parameter name='Bob'/>
</product>
<product id='2'>
<photo addr='2.jpg'/>
<parameter name='Kenneth'/>
</product>
</AllProducts>
</products>";
XDocument dom = XDocument.Parse(xml);
products products;
//You need to load auto-generated classes. I prefer serialization. You may parse XML elements individually.
using (var xmlReader = dom.CreateReader())
{
var serializer = new XmlSerializer(typeof(products));
products = (products)serializer.Deserialize(xmlReader);
}
string productsProductRule = File.ReadAllText("rule1.xml");
//A: Filter productsProduct[]; result is IEnumerable<productsProduct>.
//This rule evaluates objects of the productsProduct type.
var filteredProducts = products.AllProducts.Filter(productsProductRule);
foreach (var product in filteredProducts)
Console.WriteLine("Option A: {0}, {1}, {2}", product.id, product.parameter.name, product.photo.addr);
string xElementRule = File.ReadAllText("rule2.xml");
//B: Filter IEnumerable<XElement>; result is IEnumerable<XElement>.
//This rule evaluates objects of the XElement type.
var filteredElements = dom.Descendants("product").Filter(xElementRule);
foreach (var element in filteredElements)
Console.WriteLine("Option B: {0}, {1}, {2}", element.Attribute("id").Value, element.Element("parameter").Attribute("name").Value, element.Element("photo").Attribute("addr").Value);
//C: Filter IEnumerable<XElement>; result is IEnumerable<'a> (anonymous)
//This rule also evaluates objects of the XElement type.
var productsInfo = from product in dom.Descendants("product").Filter(xElementRule)
from photo in product.Descendants("photo")
from parameter in product.Descendants("parameter")
let id = product.Attribute("id")
let addr = photo.Attribute("addr")
let name = parameter.Attribute("name")
select new
{
ID = id.Value,
Addr = addr.Value,
Name = name.Value
};
foreach (var info in productsInfo)
Console.WriteLine("Option C: {0}, {1}, {2}", info.ID, info.Name, info.Addr);
string anonymousRule = File.ReadAllText("rule3.xml");
//D: Filter IEnumerable<'a>; result is IEnumerable<'a>
//This rule evaluates objects of the anonymous type 'a with properties ID, Addr, and Name.
var productsInfo2 = (from product in dom.Descendants("product")
from photo in product.Descendants("photo")
from parameter in product.Descendants("parameter")
let id = product.Attribute("id")
let addr = photo.Attribute("addr")
let name = parameter.Attribute("name")
select new
{
ID = id.Value,
Addr = addr.Value,
Name = name.Value
})
.Filter(anonymousRule);
foreach (var info in productsInfo2)
Console.WriteLine("Option D: {0}, {1}, {2}", info.ID, info.Name, info.Addr);
}
}
}
Auto-generated classes
You need re-paste your XML example to generate proper array fields. The one you have was generated with XML sample that only had one record. However to filter you need a collection.
using System;
namespace testSlimWebRule
{
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class products
{
private productsProduct[] allProductsField;
/// <remarks/>
[System.Xml.Serialization.XmlArrayItemAttribute("product", IsNullable = false)]
public productsProduct[] AllProducts
{
get
{
return this.allProductsField;
}
set
{
this.allProductsField = value;
}
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsProduct
{
private productsProductPhoto photoField;
private productsProductParameter parameterField;
private byte idField;
/// <remarks/>
public productsProductPhoto photo
{
get
{
return this.photoField;
}
set
{
this.photoField = value;
}
}
/// <remarks/>
public productsProductParameter parameter
{
get
{
return this.parameterField;
}
set
{
this.parameterField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public byte id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsProductPhoto
{
private string addrField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string addr
{
get
{
return this.addrField;
}
set
{
this.addrField = value;
}
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsProductParameter
{
private string nameField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string name
{
get
{
return this.nameField;
}
set
{
this.nameField = value;
}
}
}
}
rule1.xml
This rule has proper type testSlimWebRule.productsProduct. It gets evaluated against each element in the auto-generated array of testSlimWebRule.productsProduct.
<?xml version="1.0" encoding="utf-8"?>
<codeeffects xmlns="http://codeeffects.com/schemas/rule/41" xmlns:ui="http://codeeffects.com/schemas/ui/4">
<rule id="09973a56-3d6a-4616-ae1c-40d0d17e95b9" webrule="4.3.6.7" utc="2017-07-24T10:07:08.6346" type="testSlimWebRule.productsProduct, testSlimWebRule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" eval="true">
<definition>
<condition type="equal">
<property name="id" />
<value type="numeric">1</value>
</condition>
</definition>
</rule>
</codeeffects>
rule2.xml
This rule is the way you would have. It is applied to objects of type System.Xml.Linq.XElement. As such it can only operate on properties and methods of that type, i.e. you don't get your custom fields id, addr, name, etc.
<?xml version="1.0" encoding="utf-8"?>
<codeeffects xmlns="http://codeeffects.com/schemas/rule/41" xmlns:ui="http://codeeffects.com/schemas/ui/4">
<rule id="e38da351-1190-47fb-b99b-d06787c9a459" webrule="4.3.6.7" utc="2017-07-24T10:07:08.6346" type="System.Xml.Linq.XElement, System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" eval="true">
<definition>
<condition type="equal">
<property name="FirstAttribute.Value" />
<value>1</value>
</condition>
</definition>
</rule>
</codeeffects>
rule3.xml
This rule does not have any type. Instead it receives whatever type it is evaluated against. In the example it gets anonymous type 'a, so it can operate on properties ID, Name, and Addr.
<?xml version="1.0" encoding="utf-8"?>
<codeeffects xmlns="http://codeeffects.com/schemas/rule/41" xmlns:ui="http://codeeffects.com/schemas/ui/4">
<rule id="7d72463f-5ae2-4617-a2bf-fd605fcb4f54" webrule="4.3.6.7" utc="2017-07-24T10:07:08.6346" type="" eval="true">
<definition>
<condition type="startsWith">
<property name="Addr" />
<value>2</value>
</condition>
</definition>
</rule>
</codeeffects>
I'm using EF6 code-first and at the first, I put the connection string in a text file called 'Settings.txt'
The data in the 'Settings.txt' file is
DataProvider: sqlserver
DataConnectionString: Data Source=.\SQLEXPRESS;Initial Catalog=MyDb;Integrated Security=True;Persist Security Info=False;Enlist=False;
Here what I use for the dbContext class:
public class DbDataContext : BaseDbContext
{
static DbDataContext()
{
Database.SetInitializer(new ContextInitializer());
}
public DbDataContext():base() { }
public DbDataContext(string nameOrConnectionString)
: base(nameOrConnectionString) { }
...
}
[DbConfigurationType(typeof(MyDbConfiguration))]
public abstract partial class BaseDbContext : DbContext, IDbContext
{
public BaseDbContext() : this(GetConnectionString())
{ }
public BaseDbContext(string nameOrConnectionString) : base(nameOrConnectionString)
{ }
public static string GetConnectionString()
{
if (DataSettings.DataSettings.Current.IsValid())
{
return DataSettings.DataSettings.Current.DataConnectionString;
}
throw Error.Application("A connection string could not be resolved for the parameterless constructor of the derived DbContext. Either the database is not installed, or the file 'Settings.txt' does not exist or contains invalid content.");
}
}
public class MyDbConfiguration : DbConfiguration
{
public MyDbConfiguration()
{
IEfDataProvider provider = null;
try
{
provider = (new EfDataProviderFactory(DataSettings.DataSettings.Current).LoadDataProvider()) as IEfDataProvider;
}
catch {
}
if (provider != null)
{
base.SetDefaultConnectionFactory(provider.GetConnectionFactory());
}
}
}
public partial class EfDataProviderFactory : DataProviderFactory
{
public EfDataProviderFactory()
: this(DataSettings.DataSettings.Current){ }
public EfDataProviderFactory(DataSettings.DataSettings settings)
: base(settings) { }
public override IDataProvider LoadDataProvider()
{
var providerName = Settings.DataProvider;
if (providerName.IsEmpty())
{
throw new Exception("Data Settings doesn't contain a providerName");
}
switch (providerName.ToLowerInvariant())
{
case "sqlserver":
return new SqlServerDataProvider();
case "sqlserverce":
return new SqlServerCeDataProvider();
default:
throw new Exception(string.Format("Unsupported dataprovider name: {0}", providerName));
}
}
}
public class SqlServerDataProvider : IEfDataProvider
{
public virtual IDbConnectionFactory GetConnectionFactory()
{
return new SqlConnectionFactory();
}
public bool StoredProceduresSupported
{
get { return false; }
}
public DbParameter GetParameter()
{
return new SqlParameter();
}
public string ProviderInvariantName
{
get { return "System.Data.SqlClient"; }
}
}
I use a static function in 'BaseDbContext' class called 'GetConnectionString()'
This function just for return the connection string from the text file. This behavior is working very well at runtime, but it not working when adding a migration.
This is the problem: how can I add a migration by this way, knowing that when I put the connection string directly in the function like this
public static string GetConnectionString()
{
return (#"Data Source=.\\SQLEXPRESS;Initial Catalog=MyDb;Integrated Security=True;Persist Security Info=False;Enlist=False;");
}
the Add-Migration command is working
How can I solve this problem without forcing the connection string in the code?
I solved this,
the problem occurs in getting the file path (text file) in design-mode or even
unit tests
string filePath = Path.Combine(MapPath("~/App_Data/"), "Settings.txt");
public static string MapPath(string path)
{
path = path.Replace("~/", "").TrimStart('/').Replace('/', '\\');
var testPath = Path.Combine(baseDirectory, path);
var dir = FindSolutionRoot(baseDirectory);
if (dir != null)
{
baseDirectory = Path.Combine(dir.FullName, "MyProjectName.WebAPI");
testPath = Path.Combine(baseDirectory, path);
return testPath;
}
}
private static DirectoryInfo FindSolutionRoot(string currentDir)
{
var dir = Directory.GetParent(currentDir);
while (true)
{
if (dir == null || IsSolutionRoot(dir))
break;
dir = dir.Parent;
}
return dir;
}
private static bool IsSolutionRoot(DirectoryInfo dir)
{
return File.Exists(Path.Combine(dir.FullName, "MySolutionName.sln"));
}
and by this, we can get the file path in runtime-mode
I guess you use the Add-Migration command in the Package Manager Console.
If you are running Add-Migration manually you can just add the connection string as the -ConnectionString parameter:
Add-Migration -ConnectionString "Data Source=.\\SQLEXPRESS;Initial Catalog=MyDb;Integrated Security=True;Persist Security Info=False;Enlist=False;"
You will probably have to add parameter -ConnectionProviderName as well, unless you have a provider defined in your app.config.
I would recommend you to stop using this Settings.txt file and move your connection string to your app.config file, in the section connectionStrings. This is the recommended way to deal with connection strings, much easier than using a custom file like your Settings.txt file.
<connectionStrings>
<add name="MyLocalDatabase" connectionString="Data Source=.\\SQLEXPRESS;Initial Catalog=MyDb;Integrated Security=True;Persist Security Info=False;Enlist=False;" />
</connectionStrings>
If you do that you can use the parameter -ConnectionStringName in the Package Manager Console, using the name you defined in the app.config:
Add-Migration -ConnectionStringName "MyLocalDatabase"
Also, with the connection string in your app.config file you can add a constructor to your context that receives the connection string name as a parameter and can be used when using the Package Manager console:
public DbDataContext():base("MyLocalDatabase") { }
This will allow you to run your commands in Package Manager Console without specifying any connection string parameters at all. Just make sure the right connection strings are included in the app.config file of the start project selected in the console.
And you can get rid of your GetConnectionString code. You are just re-implementing code that you have out-of-the-box when using app.settings connectionString section. That's how DbContext base constructors parameter NameOrConnectionString is meant to be used. You can provide either a full connection string or the name of a connection string defined in the app.settings file.
Is there any way to log method parameter name , its value and return type value using Enterprise library logging application block.
I have provided a code sample below. The requirement is to log it's methods input parameters value and its return type value
// Complex Types
public class UserDetails
{
public string UserName { get; set; }
public int UserAge { get; set; }
public string UserAddress { get; set; }
}
public class User
{
public string UserId { get; set; }
public string Pwd { get; set; }
}
//Interface
public interface IService
{
UserDetails GetUserDetails(User ReqUser);
}
//Imp
public class Service : IService
{
[LogCallHandler(Categories = new string[] { "General" }, LogBeforeCall = true, LogAfterCall = true ,
BeforeMessage = "This occurs before the call to the target object",AfterMessage="This occured after method call",IncludeParameters=true)]
public UserDetails GetUserDetails(User ReqUser)
{
UserDetails oUD = new UserDetails();
oUD.UserName = "hhh" + ReqUser.UserId;
oUD.UserAge = 100;
oUD.UserAddress = "HHHHHHHHHHHHHHHHHHHHHHH";
return oUD;
}
#endregion
}
//Usage
private void button2_Click(object sender, EventArgs e)
{
IUnityContainer container = new UnityContainer().LoadConfiguration();
container.AddNewExtension<EnterpriseLibraryCoreExtension>();
IService service = container.Resolve<IService>();
User nUser = new User();
nUser.UserId = "TTTTT";
nUser.Pwd = "XXXXX";
UserDetails mm = service.GetUserDetails(nUser);
}
Could anyone please explain how to implement this using Enterprise library logging application block?
You can write an OnMethodBoundaryAspect to intercept your method calls using PostSharp API.
OnMethodBoundaryAspect.OnEntry method includes MethodExecutionArgs parameter which provides all the information you need about the method and its arguments.
See this post for a sample logging aspect implementation very close to your requirements.
// This method is executed before the execution of target methods of this aspect.
public override void OnEntry( MethodExecutionArgs args )
{
// Build method information to log.
string methodInfo = BuildMethodInformation(args.Arguments);
// continue with your logging...
}
You can get method parameters via Arguments member of MethodExecutionArgs parameter like this:
private string BuildMethodInformation(Arguments arguments)
{
var sb = new StringBuilder();
sb.Append(_methodName);
foreach (var argument in arguments.ToArray())
{
sb.Append(arguments.GetArgument( i ) ?? "null");
}
return sb.ToString();
}
For method parameters, check this or this samples. They are built for caching but BuildCacheKey/GetCacheKey methods include all the information you need to get argument information of a method.
You can use EntLib LogCallHandler by code:
container.AddNewExtension<EnterpriseLibraryCoreExtension>();
container.RegisterType<IService, Service>(
new InterceptionBehavior<PolicyInjectionBehavior>(),
new Interceptor<TransparentProxyInterceptor>());
Or by config file:
<unity>
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration" />
<namespace name="LoggingCallHandler" />
<assembly name="LoggingCallHandler" />
<container>
<extension type="Interception" />
<extension type="Microsoft.Practices.EnterpriseLibrary.Common.Configuration.Unity.EnterpriseLibraryCoreExtension, Microsoft.Practices.EnterpriseLibrary.Common" />
<register type="IService" mapTo="Service">
<interceptor type="TransparentProxyInterceptor" />
<policyInjection />
</register>
</container>
</unity>
Here, LoggingCallHandler is namespace/assembly for your service class. Alternatively, you can define your type alias like this:
<alias alias="Service" type="LoggingCallHandler.Service, LoggingCallHandler"/>
<alias alias="IService" type="LoggingCallHandler.IService, LoggingCallHandler"/>
See this or this discussion for full configuration including logging block configuration.
So I understand IoC, especially when "injecting an unknown concrete" into a class.
The most generic example of injecting an "ILogger" into a class either in the constructor or by a property.
Now, I have a older Factory Pattern, that I'm trying to figure out how/if I can convert to IoC. (I am using Unity, fyi).
Below I have my older Factory pattern. It basically makes a factory decision based on the current price of gas (petro). If gas is really expensive, I'm riding my bicycle. If gas is medium priced, I'm driving the car. If gas goes cheap, then I'm driving my truck to work!
(Its a dumb example, just roll with it, please).
What I don't understand is how I "translate" this into IoC......when it comes to making a business-logic decision on which concrete class is returned.
What am I missing?
Maybe I still need a factory? Or am I missing some key concept?
Thanks in advance for any help...
namespace MyApp
{
public interface IVehicle
{
void MakeTrip();
}
public class Bicycle : IVehicle
{
public void MakeTrip() { Console.WriteLine("Bicycles are good when gas is expensive."); }
}
public class Car : IVehicle
{
public void MakeTrip() { Console.WriteLine("Cars are good when gas is medium priced"); }
}
public class PickupTruck : IVehicle
{
public void MakeTrip() { Console.WriteLine("Gas is back to 1980's prices. Drive the truck!"); }
}
public static class VehicleFactory
{
public static IVehicle GetAConcreteVehicle(decimal priceOfGasPerGallon)
{
if (priceOfGasPerGallon > 4.00M)
{
return new Bicycle();
}
if (priceOfGasPerGallon > 2.00M)
{
return new Car();
}
return new PickupTruck();
}
}
public class TripControllerOldFactoryVersion
{
public decimal PriceOfGasPerGallon { get; set; }
public TripControllerOldFactoryVersion(decimal priceOfGas)
{
this.PriceOfGasPerGallon = priceOfGas;
}
public void TakeATrip()
{
IVehicle v = VehicleFactory.GetAConcreteVehicle(this.PriceOfGasPerGallon);
v.MakeTrip();
}
}
}
class Program
{
static void Main(string[] args)
{
try
{
TripControllerOldFactoryVersion controller1 = new TripControllerOldFactoryVersion(5.00M);
controller1.TakeATrip();
TripControllerOldFactoryVersion controller2 = new TripControllerOldFactoryVersion(3.33M);
controller2.TakeATrip();
TripControllerOldFactoryVersion controller3 = new TripControllerOldFactoryVersion(0.99M);
controller3.TakeATrip();
}
catch (Exception ex)
{
Exception exc = ex;
while (null != exc)
{
Console.WriteLine(exc.Message);
exc = exc.InnerException;
}
}
finally
{
Console.WriteLine("Press ENTER to Exit");
Console.ReadLine();
}
}
}
So the above is the Factory version.
So I'm trying to figure out what the best way is to convert this to IoC, but still have some "based on the price of gas" logic for determining the IVehicle.
Starter code is below.
public class TripControllerIoCVersion
{
public IVehicle TheVehicle { get; set; }
public TripControllerIoCVersion(IVehicle v)
{
this.TheVehicle = v;
}
public void TakeATrip()
{
if (null != this.TheVehicle)
{
this.TheVehicle.MakeTrip();
}
}
}
Well. I figured out something. I still need to think about it.
I don't like the hardcoded "key" names. But it does work.
The code above is still needed, but I changed my "factory" to the below.
First, the unity configuration.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
</configSections>
<unity>
<container>
<register type="MyProjectName.IVehicle, MyProjectName" mapTo="MyProjectName.Bicycle, MyProjectName" name="myBicycleKey" />
<register type="MyProjectName.IVehicle, MyProjectName" mapTo="MyProjectName.Car, MyProjectName" name="myCarKey" />
<register type="MyProjectName.IVehicle, MyProjectName" mapTo="MyProjectName.PickupTruck, MyProjectName" name="myPickupTruckKey" />
</container>
</unity>
</configuration>
Now the cs code.
public static class VehicleFactory
{
public static IVehicle GetAConcreteVehicle(decimal priceOfGasPerGallon)
{
var container = new UnityContainer();
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Configure(container);
IVehicle v = null;
if (priceOfGasPerGallon > 4.00M)
{
v = container.Resolve<IVehicle>("myBicycleKey");
return v;
}
if (priceOfGasPerGallon > 2.00M)
{
v = container.Resolve<IVehicle>("myCarKey");
return v;
}
v = container.Resolve<IVehicle>("myPickupTruckKey");
return v;
}
}
I guess I got the idea from here:
http://www.sharpfellows.com/post/Unity-IoC-Container-.aspx
I am attempting to inject an object into my saga. Using the following endpoint, when the message arrives at the handle method of the saga the property is null.
The endpoint:
public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, IWantToRunAtStartup
{
public void Run()
{
IOrderRepository orderRepository = new OrderRepository();
Configure.Instance.Configurer.ConfigureProperty<CreateOrderSaga>(x => x.OrderRepository, orderRepository);
}
// stop method removed
}
The app.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="MsmqTransportConfig" type="NServiceBus.Config.MsmqTransportConfig, NServiceBus.Core" />
<section name="UnicastBusConfig" type="NServiceBus.Config.UnicastBusConfig, NServiceBus.Core" />
</configSections>
<MsmqTransportConfig
InputQueue="Fulfilment.CreateOrder.OrderRecievedMessage"
ErrorQueue="error"
NumberOfWorkerThreads="1"
MaxRetries="3"
/>
<UnicastBusConfig
DistributorControlAddress=""
DistributorDataAddress="">
<MessageEndpointMappings>
<add Messages="NServiceBus.Saga.TimeoutMessage, NServiceBus" Endpoint="timeoutmanager" />
</MessageEndpointMappings>
</UnicastBusConfig>
</configuration>
and my Saga accepting messages as follows
public class CreateOrderSaga : Saga<CreateOrderSagaData>,
IAmStartedByMessages<OrderRecievedMessage>,
IHandleMessages<OrderCompletedMessage>,
IHandleMessages<OrderCancelledMessage>
{
public IOrderRepository OrderRepository { get; set; }
public void Handle(OrderRecievedMessage message)
{
var order = new Order();
OrderRepository.SaveOrder(order);
}
a null reference expection will be thrown when attempting to call SaveOrder(). Have i configured the dependency injection correctly?
NServiceBus will automatically do property injection for you so you only need to register your repository with the container:
In your Init() method: (Implement IWantCustomInitialization on a separate class)
Configure.Instance.ConfigureComponent< OrderRepository >([The lifecycle you want]);
IWantToRunAtStartup is not meant for configuration tasks (use IWantCustomInitialization instead)