Avatar

Blog (pg. 11)

  • Published on
    Shared projects are a relatively new thing in Visual Studio, they allow you to share code across multiple assemblies without compiling it in its own dll (the code is compiled into the referencing projects dll). Today I came across a bit of a gotcha - If you reference a shared project in an assembly A and in assembly B, now both of those projects define and export all of the public code from the shared project. Now you have a 3rd project C that also has a reference to the shared project, but also references A and B. You will get a compiler error "call is ambiguous" since the namespace/class/method you are trying to use is now defined 3 times. The simple answer, which is counter intuitive to "normal" library development is to make all of your "shared project" classes "internal" instead of "public". That way each assembly that references the shared project can use the code internally but will not export it to other assemblies.
  • Published on
    A common design pattern is to use a dependency injection container in the composition root to switch between various concrete implementations of an interface abstraction (such as for services, repositories etc.). The configuration for these bindings are often dependant on the build configuration, or an application configuration settings and are singular, as such that only one "concrete implementation" is bound at run time. While recently working on a project, we needed to swap out the implementation of each IRepository based on the user's selection. To put this into context, the application is an ASP.NET MVC 5 website that has some common UI functionality, which we can then plug into multiple different data sources, as per the user's choice of repository. Since we are using Ninject, we can rely on it's ability to bind multiple implementations of an abstraction, by name. We can then use that name (as selected by the user) in a factory method to create instances of each type. Example Ninject module:
    
    using MyProject.ApplicationServices.ExampleData;
    using MyProject.Domain.RepositoryDefinitions.ExampleDataRepository;
    using Ninject.Modules;
    
    namespace MyProject.UI.MVC.DependencyInjection
    {
        public class ExampleDataRepositoryModule : NinjectModule
        {
            public override void Load()
            {
                // define the repo names
                var exampleRepo1Name = "Example Repo 1";
                var exampleRepo2Name = "Example Repo 2";
                var exampleRepo3Name = "Example Repo 3";
                
                // setup all the named versions of the service layers for each of the repository names
                this.Bind<ExampleDataService>().ToSelf().Named(exampleRepo1Name);
                this.Bind<ExampleDataService>().ToSelf().Named(exampleRepo2Name);
                this.Bind<ExampleDataService>().ToSelf().Named(exampleRepo3Name);
                
                // set up the repositories that are bound in each of the above cases
                this.Bind<IExampleDataRepository>().To<Repositories.ExampleRepo1.ExampleDataRepository>().WhenAnyAncestorNamed(exampleRepo1Name);
                this.Bind<IExampleDataRepository>().To<Repositories.ExampleRepo2.ExampleDataRepository>().WhenAnyAncestorNamed(exampleRepo2Name);
                this.Bind<IExampleDataRepository>().To<Repositories.ExampleRepo3.ExampleDataRepository>().WhenAnyAncestorNamed(exampleRepo3Name);
            }
        }
    }
    
    We can store the user's selection in some kind of state (in ASP.NET this can be backed by the web session), so we can now use this selection along with the above bindings in a service factory, to create instances of the service with the specified repository.
    
    using MyProject.ApplicationServices.ExampleData;
    using MyProject.UI.MVC.Helpers;
    using Ninject;
    
    namespace MyProject.UI.MVC.ServiceFactories
    {
        public class ExampleDataServiceFactory
        {
            private readonly IKernel ninjectKernel;
            private readonly ISessionHelper sessionHelper;
    
            public ExampleDataServiceFactory(IKernel ninjectKernel, ISessionHelper sessionHelper)
            {
                this.ninjectKernel = ninjectKernel;
                this.sessionHelper = sessionHelper;
            }
    
            public ExampleDataService CreateInstance()
            {
                return this.ninjectKernel.Get<ExampleDataService>(this.sessionHelper.CurrentExampleRepoName);
            }
        }
    }
    
    Now in the controller, we can take a dependency on the factory and have it create us an instance:
    
    public class ExampleDataController : Controller
        {
            private ExampleDataService exampleDataService;
            private ISessionHelper sessionHelper;
    
            public ExampleDataController(ExampleDataServiceFactory exampleDataServiceFactory, ISessionHelper sessionHelper)
            {
                this.exampleDataService= exampleDataServiceFactory.CreateInstance();
                this.sessionHelper = sessionHelper;
            }
    }
    
  • Published on
    In previous versions of Visual Studio, StyleCop was an msi installation of a VS extension and a global settings file was used to configure the application (StyleCop.settings). In VS2015, with Roslyn and Code Analysis, StyleCop comes as a NuGet package "StyleCop.Analyzers" and it requires two files to configure it (I recommend saving these two files to a common folder and adding the linked files from there in each project). Therefore, in each project:
    • The first step is to install the StyleCop.Analyzers package into the desired projects (or entire solution) using NuGet.
    • File 1: MyRuleset.ruleset - Contains your specific rule customizations
    • File 2: stylecop.json - Configures the StyleCop analyzers process
    • Goto Project > Properties > Code Analysis.. Change the "Rule Set" to "My Ruleset" (for all build configs)
    • Edit the .csproj file, change the item entry from "None/Content" to "AdditionalFiles" e.g.
      
      <AdditionalFiles Include="..\SharedFolder\SyleCop.Analyzers-VS2015\stylecop.json">
           <Link>stylecop.json</Link>
      </AdditionalFiles>
      
    Example contents of the files: MyRuleset.ruleset
    
    <?xml version="1.0" encoding="utf-8"?>
    <RuleSet Name="My Ruleset" Description="My code analysis rules for StyleCop.Analyzer" ToolsVersion="14.0">
      <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
        <Rule Id="SA1600" Action="None" />
        <Rule Id="SA1601" Action="None" />
        <Rule Id="SA1602" Action="None" />
        <Rule Id="SA1633" Action="None" />
        <Rule Id="SA1634" Action="None" />
        <Rule Id="SA1635" Action="None" />
        <Rule Id="SA1636" Action="None" />
        <Rule Id="SA1637" Action="None" />
        <Rule Id="SA1638" Action="None" />
        <Rule Id="SA1640" Action="None" />
        <Rule Id="SA1641" Action="None" />
        <Rule Id="SA1652" Action="None" />
        <Rule Id="SA1118" Action="None" />
        <Rule Id="SA1516" Action="None" />
      </Rules>
    </RuleSet>
    
    stylecop.json
    
    {
      "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
      "settings": {
        "documentationRules": {
            "companyName": "PlaceholderCompany"
        },
        "orderingRules": {
            "usingDirectivesPlacement": "outsideNamespace"
        }
      }
    }
    
  • Published on
    The CDSA architecture templates generate managers and a SQLDAL that is capable of performing any combination of the "normal" queries that you might perform in SQL (such as Equals, Like, In, Less, Greater etc..) However, the default template output does not include any code for performing spatial queries. This is where the extensible nature of CDSA helps us, as we can simply define a new abstraction in the DML for performing a spatial query against the entity that supports it, such as:
    
    public abstract class ExampleGeographyManagerExtns
    {
        public abstract Dictionary<Guid, double> GetDataIDsInProximityTo(Guid targetExampleId, double distance);
    
        public abstract Dictionary<Guid, double> GetDataIDsInProximity(double latitude, double longitude, double distance);
    
        public abstract List<Guid> GetDataIDsInWKT(string wkt);
    }
    
    You can then implement that extension in the SQLDAL as a set of SQL queries:
    
    public class ExampleGeographyManagerExtns : DML.ManagementExtns.ExampleGeographyManagerExtns
    {
        private Provider.SqlDataProvider provider;
        private Schemas.SqlExampleGeographySchema schema = Schemas.SqlExampleGeographySchema.GetInstance();
    
        internal ExampleGeographyManagerExtns(Provider.SqlDataProvider providerInstance)
        {
            this.provider = providerInstance;
        }
    
        public override Dictionary<Guid, double> GetDataIDsInProximityTo(Guid targetExampleId, double distance)
        {
            // use a sql query to get the averaged data
            string sqlQryText = string.Format(
                @"
    SELECT ExampleGeography.ExampleId, ExampleGeography.PointGeography.STDistance(b.PointGeography) Distance
    from ExampleGeography
    inner join ExampleGeography b
    on b.ExampleId=@ExampleId
    and b.PointGeography.STBuffer({0}).STIntersects(ExampleGeography.PointGeography)=1
    where ExampleGeography.ExampleId<>b.ExampleId
    order by Distance asc", distance);
    
            // create the parameters to the query
            Dictionary<string, object> sqlQryParams = new Dictionary<string, object>();
            sqlQryParams.Add("ExampleId", targetExampleId);
    
            DataSet rawData = this.provider.SqlHelper.Execute(sqlQryText, CommandType.Text, sqlQryParams);
    
            Dictionary<Guid, double> results = new Dictionary<Guid, double>();
    
            if (rawData != null && rawData.Tables.Count > 0)
            {
                foreach (DataRow dr in rawData.Tables[0].Rows)
                {
                    results.Add((Guid)dr[this.schema.UniqueId], (double)dr["Distance"]);
                }
            }
    
            return results;
        }
    
        public override Dictionary<Guid, double> GetDataIDsInProximity(double latitude, double Longitude, double distance)
        {
            // use a sql query to get the averaged data
            string sqlQryText = string.Format(
                @"
    Declare @GeogPoint as geography
    Declare @GeogShape as geography
    
    SET @GeogPoint = geography::STGeomFromText('POINT (' + Cast(@longitude as varchar) + ' ' + Cast(@latitude as varchar) + ')', 4326)
    SET @GeogShape = @GeogPoint.STBuffer({0})
    
    SELECT ExampleGeography.ExampleId, ExampleGeography.PointGeography.STDistance(@GeogPoint) Distance
    from ExampleGeography
    where @GeogShape.STIntersects(ExampleGeography.PointGeography)=1
    order by Distance asc", distance);
    
            // create the parameters to the query
            Dictionary<string, object> sqlQryParams = new Dictionary<string, object>();
            sqlQryParams.Add("Latitude", latitude);
            sqlQryParams.Add("Longitude", longitude);
    
            DataSet rawData = this.provider.SqlHelper.Execute(sqlQryText, CommandType.Text, sqlQryParams);
    
            Dictionary<Guid, double> results = new Dictionary<Guid, double>();
    
            if (rawData != null && rawData.Tables.Count > 0)
            {
                foreach (DataRow dr in rawData.Tables[0].Rows)
                {
                    results.Add((Guid)dr[this.schema.UniqueId], (double)dr["Distance"]);
                }
            }
    
            return results;
        }
    
        public override List<Guid> GetDataIDsInWKT(string wkt)
        {
            string sqlQryText = @"
    Declare @GeogPolygon as geography
    Declare @GeomPolygon as geometry
    
    Set @GeomPolygon = Geometry::STGeomFromText(@WKT, 4326)
    Set @GeogPolygon = Geography::STGeomFromWKB(@GeomPolygon.MakeValid().STUnion(@GeomPolygon.STStartPoint()).STAsBinary(), 4326)
    
    SELECT ExampleGeography.ExampleId
    from ExampleGeography
    WHERE PointGeography.STIntersects(@GeogPolygon)=1";
    
            // create the parameters to the query
            Dictionary<string, object> sqlQryParams = new Dictionary<string, object>();
            sqlQryParams.Add("WKT", wkt);
    
            DataSet rawData = this.provider.SqlHelper.Execute(sqlQryText, CommandType.Text, sqlQryParams);
    
            List<Guid> results = new List<Guid>();
    
            if (rawData != null && rawData.Tables.Count > 0)
            {
                foreach (DataRow dr in rawData.Tables[0].Rows)
                {
                    results.Add((Guid)dr[this.schema.UniqueId]);
                }
            }
    
            return results;
        }
    }
    
    We can then expose the extension in the IDataProvider interface and SQL implemention:
    
    public partial interface IDataProvider : ClauseWrappers.WhereClauseWrapper.IWhereClauseHandler, ClauseWrappers.OrderByClauseWrapper.IOrderByClauseHandler
    {
        // expose specialised management functions/classes
        ManagementExtns.ExampleGeographyManagerExtns ExampleGeographyManagerExtns { get; }
    }
    
    public partial class SqlDataProvider : DML.Provider.IDataProvider
    {
        private ManagementExtns.ExampleGeographyManagerExtns exampleGeographyManagerExtns;
    
        public DML.ManagementExtns.ExampleGeographyManagerExtns ExampleGeographyManagerExtns
        {
            get
            {
                if (this.exampleGeographyManagerExtns == null)
                {
                    this.exampleGeographyManagerExtns = new ManagementExtns.ExampleGeographyManagerExtns(this);
                }
    
                return this.exampleGeographyManagerExtns;
            }
        }
    }
    
    Finally, we can now expose this functionality through our extended business layer manager class:
    
    public partial class ExampleGeographyManager : BaseClasses.ExampleGeographyManagerBase
    {
        public Dictionary<long, double> GetRecordIDsInProximityTo(long targetExampleId, double distance)
        {
            return DataProviders.Current.ExampleGeographyManagerExtns.GetDataIDsInProximityTo(targetExampleId, distance);
        }
    
        public List<long> GetRecordIDsInWKT(string wkt)
        {
            return DataProviders.Current.ExampleGeographyManagerExtns.GetDataIDsInWKT(wkt);
        }
    
        public Dictionary<long, double> GetRecordIDsInProximity(double latitude, double longitude, double radiusInMeters)
        {
            return DataProviders.Current.ExampleGeographyManagerExtns.GetDataIDsInProximity(latitude, longitude, radiusInMeters);
        }
    }
    
  • Published on
    If you want to share data amongst all instances of a class you will naturally create some "static" variables. This is fine, except when using the binding engine within Silverlight, as it relies on "PropertyChanged" events to notify the UI of a change to a property, but you can't raise an instance event from within a static property. The way I solved this was to create a static event called "StaticPropertyChanged" which the static properties raise whenever they are changed. Within the constructor of the class I attach to this event (i.e. each instance attaches to the static event) and re-raise the event through the PropertyChanged event for the current instance. In effect, every instance of the class will raise a PropertyChanged event whenever any of the static data is changed. The code is as follows:
    
    public abstract class ViewModelBase : NotificationObject
        {
    	//use a shared event so that any instance can propogate the event to all instances
    	private static event Action<string> _staticPropertyChanged;
    
    	protected static void OnStaticPropertyChanged(string propertyName)
    	{
    		if (_staticPropertyChanged != null)
    		_staticPropertyChanged(propertyName);
    	}         
    
    	public ViewModelBase()
    	{
    		//when a new instance is created, we subscribe it to shared Busy Property Changed event
    		_staticPropertyChanged += ViewModelBase__staticPropertyChanged;
    	}
    
    	/// <summary>
    	/// Within each instance, handle the shared staticPropertyChanged event by raising the instance version of RaisePropertyChanged event
    	/// </summary>
    	void ViewModelBase__staticPropertyChanged(string propertyName)
    	{
    		RaisePropertyChanged(propertyName);
    	}
    
    
    	//example
    	private static Dictionary<long, string> _SomeSharedLookup;
    	public static Dictionary<long, string> SomeSharedLookup
    	{
    		get { return ViewModelBase._SomeSharedLookup; }
    		set { ViewModelBase._SomeSharedLookup = value; OnStaticPropertyChanged("SomeSharedLookup"); }
    	}
    }