PowerShell script to upload a file to a remote FTP server0 Reacties

I needed a simple script to upload a file remotely to an FTP server, and found a script on StackOverflow.

However, the script could use some improvements such as SSL. Here it is. Good luck with it

$ftp = [System.Net.WebRequest]::Create("ftp://ftp.yourserverblahblah.com/public/" + $args[0])
$ftp = [System.Net.FtpWebRequest]$ftp #cast
$ftp.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile #"STOR"
$ftp.Credentials = new-object System.Net.NetworkCredential("someuser","incrediblepassword****")
$ftp.UseBinary = $true
$ftp.UsePassive = $true
$ftp.EnableSsl = $true

$fullPathIncFileName = $MyInvocation.MyCommand.Definition

$currentExecutingPath = [System.IO.Path]::GetDirectoryName($fullPathIncFileName)
$file = [System.IO.Path]::Combine($currentExecutingPath, $args[0])
Write-Host "Going To Copy " + $file
# read in the file as a stream.
$content = [System.IO.File]::OpenRead($file)

# get the request stream, and copy the bytes into it
$rs = $ftp.GetRequestStream()
$content.CopyTo($rs)
$rs.Close()
$content.Close()

An easy Taghelper to make an element ‘active’ when clicked using Razor.0 Reacties

Still using backend HTML logic.

ASP.NET Core makes things a lot more easier for us, backend developers. Though, accent today, is more on frontend development, still, for reasons of security for instance, backend state management is still used.

Now take a look at this tiny gem, a TagHelper.

A ‘which menu has been clicked-set-active-helper’

What it does, basically, is checking if a

  • element has an attribute activecheck. If so, it detects which ‘action-route’ was executed/is executing and if the name matches, it adds class=”active”. So the output will be  e.g.
  • active”>Home
  • e.g. in the Razor View I have:


                                           <li activecheck="true" data-menu="Index" >
                                                <a asp-controller="Home" asp-action="Index">Homea>
                                           

      I had to add a ‘data-menu’ attribute, because Taghelpers, can not, as far as I found, parse the raw Razor Markup, when using ‘GetChildContentAsync()’  for instance, it only gets the HTML output that’s already there, without Razor markup. So, I cannot parse the child anchor and retrieve asp-acction=”Index”.
      Therefore, I specify it again using data-menu=”Index”. Note, this is removed later by the taghelper.

      using Microsoft.AspNetCore.Html;
      using Microsoft.AspNetCore.Mvc.Controllers;
      using Microsoft.AspNetCore.Mvc.Infrastructure;
      using Microsoft.AspNetCore.Razor.TagHelpers;

      namespace ladidah.TagHelpers
      {
          [HtmlTargetElement("li", Attributes = "activecheck")]
          public class LiActiveHelper: TagHelper
          {
              public bool activecheck { get; set; }
              private readonly string menu;
              public LiActiveHelper(IActionContextAccessor httpContext)
              {
                  var currentRoute = (ControllerActionDescriptor)httpContext.ActionContext.ActionDescriptor;
                  menu = currentRoute.ActionName;
              }
              public override void Process(TagHelperContext context, TagHelperOutput output)
              {
                  if (activecheck)
                  {
                      var dataMenu = output.Attributes["data-menu"];
                   
                       if (dataMenu != null && dataMenu.Value is HtmlString str && str.Value == menu)
                      {
                          output.Attributes.RemoveAll("activecheck");
                          output.Attributes.RemoveAll("data-menu");
                          if (!output.Attributes.ContainsName("class"))
                          {
                              output.Attributes.SetAttribute("class", "active");
                          }
                      }               
                  }          
              }
          }
      }

      To activate this taghelper, I had to add the following line in my _ViewImports.cshtml file

      @addTagHelper ladidah.TagHelpers.LiActiveHelper, ladidah

      Benefit of IoC in ASP.NET Core.

      In my ConfigureServices method, in startup.cs I had to add the following line, in order to be able to get the action context of the current MVC Controller.

      public void ConfigureServices(IServiceCollection services)
             {

      // code removed for readability

        services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

      }

      I hope you find this snippet useful.

    Finally OData 7.x supports and ASP.NET Core 2.x0 Reacties

    Finally we can create OData (v4) services using ASP.NET Core.

    My first impression, it’s ok, plus it has extra features.

    Use my startup project, on Github as a sample. (It’s tricky to forget to enable features like Filter, otherwise you’ll and up with :

    value":"The query specified in the URI is not valid. The property 'Id' cannot be used in the $filter query option."}

    So, this is the Configure section

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
           {
               if (env.IsDevelopment())
               {
                   app.UseDeveloperExceptionPage();
               }
               var builder = new ODataConventionModelBuilder(app.ApplicationServices);
               builder.EntitySet("Cars");
          
               app.UseMvc(routeBuilder =>
               {
                   routeBuilder.Count().Filter().OrderBy().Select().MaxTop(null).Expand();
                   routeBuilder.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());
                   // Work-around for #1175

                   routeBuilder.EnableDependencyInjection();
               } ) ;
           }

    To get PATCH and POST working, we need the [FromBody] Attribute like here

    public async Task Post([FromBody] Car newCar)

     

    The sample, is downloadable here (ASP.NET-Core-With-Odata-7)

    Howto open ("/dev/urandom") on Windows0 Reacties

    Just copy-paste2 might save you some time. rand_s is pretty safe on Windows since it uses RtlGenRandom(), but you’ll have to define _CRT_RAND_S first before

    Assuming that the cpp somewhere in the class has: m_fd = open("/dev/urandom", O_RDONLY);

    Original code: (thanks jacketzhong)

    sds CRedLock::GetUniqueLockId()

    {

    unsigned char buffer[20];

    if (_read(m_fd, buffer, sizeof(buffer)) == sizeof(buffer)) {

    //获取20byte的随机数据

    sds s; s = sdsempty();

    for (int i = 0; i < 20; i++)

    {

    s = sdscatprintf(s, "%02X", bufferIdea); } return s;

    }

    else

    {

    //读取失败 printf("Error: GetUniqueLockId %d\n", __LINE__); }

    return NULL;

    }

    Windows CPP compatible:

    note: rand_s is not an ANSI standard.

    sds CRedLock::GetUniqueLockId()
    {
        //获取20byte的随机数据
        auto retVal = sdsempty();
        auto max = 20 / sizeof(unsigned int);
        for (int i = 0; i < max; i++)
        {
            unsigned int number;
            if (rand_s(&number) == 0)
            {           
                retVal = sdscatprintf(retVal, "%08X", number);           
            }
        }
        return retVal;
    }

    Howto get the ASP.NET Core ContentRoot path?0 Reacties

     

    There are 3 ways that I have found to be working within Startup.cs

    1. use Environment variables.  This however, only works if you also have added the environmentvariables. (builder.AddEnvironmentVariables(); )
    2. var env = Configuration.GetSection("ASPNETCORE_CONTENTROOT");
      var path =     env.Value

    3. Resolve IHostingEnvironment from Services.
        var sv = (IHostingEnvironment)services.FirstOrDefault(f => f.ServiceType == typeof(IHostingEnvironment)).ImplementationInstance;
      var path = sv.ContentRootPath;
      Though, I can imagine you would rather resolve HostingEnvironment but this does not work during ConfigureServices scope..
    4. Use Directory
      eg:  var path = Directory.GetCurrentDirectory();

    ASP.NET Core MVC. Howto set Casing strategy for JSON0 Reacties

    While converting a previous piece of code, written using Mvc 4.x I found that the output of the Json previously, when set no option, was unchanged, so a class with a property say, Name would output in JSON as ‘Name’.

    However, ASP.NET Core Mvc
    when returning JSon results, it now defaults to camelCasing.

     

    My output had to be:

    {
        "page": 1,
        "records": 2,
        "rows": [
            {
                "Id": 2,
                "Name": "asdfasdf",
                "UrlSlug": "asdfasdf",
                "Description": "asdf"
            },
            {
                "Id": 1,
                "Name": "Bkaat",
                "UrlSlug": "Blaat",
                "Description": "zdf
    asdfasdf asdf a"
            }
        ],
        "total": 1
    }

    but it was camelCased.

     

    So, HOW to set this to the previous behavior (its old software, so I won’t change everything)

    Simple like this:

    public void ConfigureServices(IServiceCollection services)

    {

        services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver() { NamingStrategy = new DefaultNamingStrategy()});

     

    And finding further options, you also can set it to SnakeCaseNamingStrategy (this is like ‘url_slug’ etc)

    And of course, CamelCaseNamingStrategy, which is the default.

    I hope this saves you some time, and you don’t need to implement a custom contract resolver.

    Give your C# class Initializer support0 Reacties

    Would you like a to boost your class which hosts a collection, and utilize a cool C# language feature, which is collection initializer support?

    For instance var myList = new List() {1,3,4} where {1,3,4} is the initializer.

    The basic idea is to implement IEnumerable, and IEnumerator and the class must have an ‘Add’ method with the correct signature.

    I made the class generic so you can initialize any array (ok, not for structs and simple types, but you modify a few things and that works too)

    // our sample class for iteration feeding

    public class Address
       {
           public string Street { get; set; }
           public string StreetNumber { get; set; }
           public string ZipCode { get; set; }
           public string City { get; set; }
       }
       public static class Program
       {
           static void Main()
           {
               var t = new ClassWithInitializerSupport

    {
                   new Address()
                   {
                       Street = "Mainstr.",
                       StreetNumber = "1",
                       City ="London"
                   },
                  new Address()
                   {
                       Street = "De Dam",
                       StreetNumber = "1",
                       City ="Amsterdam"
                   },
                   new Address()
                   {
                       Street = "Mangostreet",
                       StreetNumber = "123",
                       City ="New York"
                   }
               };
               foreach(var a in t)
               {
                   Console.WriteLine("Street {0}, City {1}", a.Street, a.City);
               }

    }

    public class ClassWithInitializerSupport : IEnumerable, IEnumerator where T : class
       {
           public ClassWithInitializerSupport()
           {
               arr = (T[])Array.CreateInstance(typeof(T), 0);
               pos = -1;
           }
           private T[] arr;
           private int pos;
           public T Current => arr[pos];

           object IEnumerator.Current => arr[pos];

           public void Dispose()
           {

           }

           public IEnumerator GetEnumerator()
           {
               return this;
           }
           public void Add(T v)
           {
               var p = arr.Length;
               Array.Resize(ref arr, p + 1);
               arr[p] = v;
           }
           public bool MoveNext()
           {
               if (pos + 1 < arr.Length)
               {
                   pos++;
                   return true;
               }
               return false;
           }

           public void Reset()
           {
               pos = -1;
           }

           IEnumerator IEnumerable.GetEnumerator()
           {
               return this;
           }
       }

    OData 6.0.0 howto fix Cannot apply PATCH to navigation property 'x' on entity type?0 Reacties

    The Microsoft OData library Roadmap? Please some insights?

    WP_20140923_005Microsoft has taken the road of open source and the community. Right, but I guess that this is the reason, that some libraries, in the past maintained by strictly managed teams, now are more or less ‘loosely’ managed.

    About OData and bugs and questions, I have seen comments in the community, that suggest that Microsoft abandons maintenance on oData, however, I doubt this is the case, since Azure heavily depends on Odata.

    For the reason of better versioning and no dependencies on Windows Updates for .NET components, .NET library has been split up, into smaller parts, the future part is ‘.NET Core’ and the current path, but popular as well, is .NET for which we see a mysterious overlap in versions, so say, if you target your assembly, you could target it using Visual Studio 2015 and C# to both as well .NET 462 and .Net Core 1.6 (standard core library). My focus on this posting, is not the .NET versions but the OData versions.

    It looks as if OData 5.8.x is still the most popular used OData Service library, while Microsoft guys, also created a System.WEb.Odata 6.0.0 version, which is compatible with the ‘Next’ platform of .NET (say, .NET Core, I am not quite sure what exactly is going on here).

    To make it worse, if you develop on Azure services, some clients, such as Azure Search Index Client, use OData 5.8 while DocumentDB already likes 6.0.0, these two odata branches, bite each other.

    Anyway, OData 6.0.0 has improved functionality when we talk about modelling, but it has a lot of BUGS in the Controller handling section, that still, since the release of it have not been solved.

    So, If you have these libraries, which are from the nuget packages.config file, read on!


    For complete details about the bug, I have created an Issue on Github, where the OData WebApi Git Repository resides.

    Now the question, how to workaround the BUG?

    Good news, you can workaround it, but the workaround requires you to break some consistancy with your OData API behavior. I tried to minimize the damage as much as possible AND if Microsoft fixes these, you just could rename a few things and your OData API Controller, should work on!

    Second, I just could post the binaries here, and say: “Good luck with it!” But it also explains a few other things

    1. How to intercept an OData server method by getting binary data and do it yourselves?
    2. How to deal with ‘custom’ serialisation on Odata, see, using Newtonsoft.JSON. (Which is possible, but I do not recommend it for 6.0.0)
    3. How to impress your team members with this neat OData PATCH-patch? Glimlach

     

    My Odata Controller layout

    Say, this is my Controller. You will recognize it, it’s not very different probably.

    [EnableQuery]
    [Authorize]
    [ODataRoutePrefix("companies")]
    public class CompaniesController : BaseODataController

    private static readonly ODataValidationSettings settings = new ODataValidationSettings()
           {
               // Initialize settings as needed.
               AllowedFunctions = AllowedFunctions.IndexOf | AllowedFunctions.ToLower | AllowedFunctions.All | AllowedFunctions.Any | AllowedFunctions.AllStringFunctions //includes contains
           };

    public CompaniesController(IManager companyManager, // DEAL with IoC, just a sample
           )
        {
            _companyManager = companyManager;
            }

    try
               {
                   options.Validate(settings);
               }
               catch (Exception ex)
               {
                   return BadRequest(ex.Message);
               }
               try
               {
                   var data = (await _companyManager.SearchAsync(options));

                   return Ok(data);
               }
               catch (Exception ex)
               {
                   return BadRequest(ex.Message);
               }

     


           [EnableQuery]
         public async Task Get(ODataQueryOptions options)
         {

    try
               {
                   options.Validate(settings);
               }
               catch (Exception ex)
               {
                   return BadRequest(ex.Message);
               }
               try
               {
                   var data = (await _companyManager.SearchAsync(options));

                   return Ok(data);
               }
               catch (Exception ex)
               {
                   return BadRequest(ex.Message);
               }

    }

     

    public async Task Delete(Guid key)
         {
             if (!ModelState.IsValid)
             {
                 return BadRequest(ModelState);
             }
             var result = await _companyManager.DeleteAsync(key);
             if (!result)
             {
                 return BadRequest(_companyManager.GetValidator());
             }
             return this.StatusCode(HttpStatusCode.NoContent);
         }

    public async Task Post(company c)
         {
             if (!ModelState.IsValid)
             {
                 return BadRequest(ModelState);
             }
             c.id = Guid.NewGuid();

       var success = await _companyManager.InsertAsync(c);

    if (!success)
                  {
                      return BadRequest(_companyManager.GetValidator());
                  }
        return Created(c);

    }


    // NOW we come to the BUG on Odata 6.0.0. The OData PATCH method would be USEless if you consider the fact, that complex entitytypes canNOT be patched as of the moment of writing.

    public async Task Patch(Guid key, [DeltaBody] Data.OData.Delta delta)
         {

             if (delta == null)
             {
                 return BadRequest("delta cannot be null");
             }
             var instance = delta.GetEntity();

             Validate(instance);

             if (!ModelState.IsValid)
             {
                 return BadRequest(ModelState);
             }
            
             var curCompany = await _companyManager.GetById(key);
             if (curCompany == null)
             {
                 return NotFound();
             }
                 delta.Patch(curCompany);

               try
             {

                 var result = await _companyManager.UpdateAsync(curCompany);
                 if (!result)
                 {
                     return BadRequest(_companyManager.GetValidator());
                 }
                 if (WantContent)
                 {
                     return Content(HttpStatusCode.Created, curCompany);
                 }
                 return Updated(curCompany);
             }
             catch (Exception ex)
             {

    //yadda
                     }
         }

    DeltaBodyAttribute my little Gem!

    Rick Strahl on Rick Strahls custom Body filter helped me to customize my PATCH method. Normally, the Delta<> would be like System.Web.OData.Delta. But now we have to tweak this code into some Delta override which is a duplicate code of OData 5.8 where the Delta Patch works.

    AS you can see, your PATCH method, only needs this adaption, and later on, you could change it back to the intended OData behavior if Microsoft fixes the library version 6.0.0 or higher.

    Keep in mind!

    1. Since we use NewtonSoft serialisation, ODataConventionModelBuilder, is ignored. All configuration, such as how to deal with nullables, capitalisation, complex property serialisation, must be emulated by Newtonsoft. OData has it’s own serialisation fabric, and especially for 6.0.0 the samples simply don’t work, and documentation lacks. (Enough with the rants, for now)
    2. Your model, that normally applies to Odata, must be copied so Newtonsoft works.
    3. I don’t use camelcase/ruby on rails naming conventions and JSonProperty tricks for our frontend HTML consumers, to get my C# naming conventions ‘right’. So my classes (POC) are really like this:  public string first_name {get;set;}
      This is to avoid further complex compatibility code, between OData entitytypes and our ‘patch’ that uses Newtonsoft. 
    4. if you use dynamic_properties, for your entitymodel, which OData supports, they are called Open Type definitions, this might bite
    5. Exceptions, during serialisation, will be Newtonsoft exceptions, not OData exceptions.

    If you keep these changes in mind, maybe, you could live with this workaround? Because after all,

    DeltaBodyAttribute

    using System;
    using System.Web.Http;
    using System.Web.Http.Controllers;


    // see https://github.com/RickStrahl/AspNetWebApiArticle/tree/master/AspNetWebApi

    namespace MyMuke.Data.OData
    {
        ///


        /// An attribute that captures the entire content body and stores it
        /// into the parameter of type
        ///

        ///
        /// The parameter marked up with this attribute should be the only parameter as it reads the
        /// entire request body and assigns it to that parameter.   
        ///

        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
        public sealed class DeltaBodyAttribute : ParameterBindingAttribute
        {
            ///
            ///
            ///

            ///
            ///
            public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
            {
                if (parameter == null)
                    throw new ArgumentNullException(nameof(parameter));

                return new DeltaBodyParameterBinding(parameter);
            }
        }
    }

    DeltaBodyParameterBinding


    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    using System;
    using System.IO;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web.Http.Controllers;
    using System.Web.Http.Metadata;

     

    namespace MyMuke.Data.OData
    {
        ///


        /// Reads the Request body into a Delta<>
        /// assigns it to the parameter bound.
        ///
        /// Should only be used with a single parameter on
        /// a Web API method using the [RawBody] attribute
        ///

        public class DeltaBodyParameterBinding : HttpParameterBinding
        {
            ///
            /// ctor
            ///

            ///
            public DeltaBodyParameterBinding(HttpParameterDescriptor descriptor)
                : base(descriptor)
            {

            }

            ///


            /// Check for simple
            ///

            ///
            ///
            ///
            ///
            public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
                                                        HttpActionContext actionContext,
                                                        CancellationToken cancellationToken)
            {
     
                if (!actionContext.Request.Method.Method.Equals("PATCH", StringComparison.InvariantCultureIgnoreCase))// PUT ~Patch is considered
                    return Task.FromResult(0);

               var binding = actionContext
                    .ActionDescriptor
                    .ActionBinding;
                var type = binding
                            .ParameterBindings.FirstOrDefault(f => f is DeltaBodyParameterBinding)
                            .Descriptor.ParameterType;

                if (type.IsGenericType && type.GetGenericTypeDefinition().IsAssignableFrom(typeof(Data.OData.Delta<>)))
                {
                    return actionContext.Request.Content
                            .ReadAsStreamAsync()
                            .ContinueWith((task) =>
                            {
                                // create instance of e.g. Delta()
                                var delta = (Delta)Activator.CreateInstance(typeof(Data.OData.Delta<>).MakeGenericType(type.GetGenericArguments()));

                                var jOb = default(JToken);

                                using (var mem = new MemoryStream((int)task.Result.Length))
                                {
                                    task.Result.CopyTo(mem);
                                    mem.Position = 0;
                                    var sr = new StreamReader(mem);

                                    // deserialize twice
                                    // one time as JTOken, the other time to get TEntityType
                                    try
                                    {
                                        var ser = new JsonSerializer()
                                        {
                                            PreserveReferencesHandling = PreserveReferencesHandling.All,
                                            MissingMemberHandling = MissingMemberHandling.Ignore//you dont want JsonRequired to cause problems here
                                        };
                                       // ser.Converters.Add(new ArrayReferencePreservngConverter()); HERE YOUR OWN solution for self referencing types

                                        jOb = JToken.ReadFrom(new JsonTextReader(sr));
                                    }
                                    catch (Exception ex)
                                    {
                                        var msg = ex.Message;
                                        throw ex;
                                    }
                                }
                                JToken token;
                                if ((token = jOb["id"]) != null)//not the primary key can be patched todo check on KeyAttribute
                                {
                                    token.Parent.Remove();
                                }
                                DeltaCopyUtil.CopyEntityToDelta(delta, jOb);

                                SetValue(actionContext, delta);
                            });
                }
            

                throw new InvalidOperationException("Only Delta parameters");
            }
            ///


            /// returns blah
            ///

            public override bool WillReadBody
            {
                get
                {
                    return true;
                } <

    Howto: Azure DocumentDB update/delete objects, without SelfReference0 Reacties

    Inherit Microsoft.Azure.Documents.Resource? No

    We can be short, just don’t. If you do, your library gets bloated and you need to add references to documentdb to ‘cross-reffed’ libraries as well, because of the ‘Resource’ dependency.

    Use a POC based ‘base object’? Yes

    If you implement DocumentDB for the first time, you’ll quickly find that documentation and old samples, suggest you, to use the Build in Microsoft.Azure.Documents.Resource base object properties, that DocumentDB supports.

    It’s quite simple, don’t use them. There is an improved syntax, like using mongodb, which enables you to define your own id and such.

    say, this one

    public class BaseObject
        {
            public BaseObject()
            {
                UpdateTimeStamp();
            }
                 public void UpdateTimeStamp()
            {
                this.timestamp = DateTime.UtcNow;

            }
     
            [ModelIgnore]
            public virtual int? document_type { get; set; }

            [JsonProperty("id", Required = Required.DisallowNull)]
               public Guid id { get; set; }


            [JsonProperty("timestamp", Required = Required.DisallowNull)]
            public DateTime timestamp { get;  set; }
            ///


            /// warning, do not SET this is a calculated field
            ///

            [JsonProperty("name", Required = Required.DisallowNull)]
            public string name { get; set; }

    }

     

    Now, your DocumentDB Context class (or whatever you named it) could have a method like this

     

    public async Task DeleteItemsAsync(BaseObject item)
            {
                          var collection = MetaUtil.CollectionId(item.GetType());
                
                //calculate the URL ourselves
                // this differs from SelfLink but seems to work!
              
    var docuId = UriFactory.CreateDocumentUri(DatabaseId, collection, item.id.ToString());
                try
                {
         
                    var response = await _client.DeleteDocumentAsync(docuId);

                       return response.StatusCode == HttpStatusCode.NoContent;
                }
                catch (Exception ex)
                {
                    Trace.TraceError("DeleteItem failed {0}", ex);
                    return false;
                }
            }

     

     

    As you can use, there is a UriFactory class, that contains a lot of static uri creators, for any object type, that DocumentDB supports.

    B.t.w. I like DocumentDB. After finding out about https://azure.microsoft.com/en-us/blog/azure-documentdb-bids-fond-farewell-to-self-links/, I quickly could ‘unbloat’ the library :)

    How to parse and read web.Config or app.Config in C++/on Windows0 Reacties

    Just a tiny gem, which not often would be required but it can save you some time. It also demonstrates the power of the IXmlReader in unmanaged code. Because, as far I am aware of, the processing time a .config file measuring it with TickCount always is 0 ms (too small to measure). Microsoft has optimized the XML Reader implementation for fast forward reading, and it also does not allocate strings in memory, it just passes the pointer to the unicode strings (either key or value). In line with that, you might appreciate :) why I attach to the BSTR key to find as well.

    What this class does, it reads the   section and puts the key value pairs in a ‘named value collection’ item.

    Note 1: I am a big fan of CComBSTR when the final client still understands COM/automation. That is the reason I did not use CString in this class. In addition, the CComBSTR class has been boosted by me, to optimize reallocation of existing memory. But you can use the default MS implementation as well. So, you can change CSimpleMap to CSimpleMap if you wish.

    Note 2: The .config file is cached but it is parsed again if the filewritetime of the .config file was changed.

    config.h header.

    #include
    #include
    #pragma once


    using namespace ATL;

    class ConfigurationManager
    {
    private:
        static const int DELAYTICKS = 1000;
        std::map _map;
        time_t _ftLastCheck;
        CComPtr _xmlReader;
        CComPtr _malloc;
        HRESULT CheckTimeOut();
        //ansi version!
        CComBSTR _szFilePath;
       
        void Init();

    public:
        ConfigurationManager();
        ConfigurationManager(const BSTR configFile);
        std::wstring& AppSettings(const std::wstring key, PCWSTR defaultValue = NULL);
        BSTR AppSettings(const BSTR key, PCWSTR defaultValue = NULL);
        time_t GetFileTime();
        ~ConfigurationManager();
    };

     

    Implementation:


    #include
    #include
    #include "config.h"
    #pragma comment(lib, "xmllite.lib")


    ConfigurationManager::ConfigurationManager(const BSTR configFile) throw()
    {
        _szFilePath = configFile;
        time(&_ftLastCheck);
        Init();
    }
    ConfigurationManager::ConfigurationManager() throw()
    {   
        time(&_ftLastCheck);
        _szFilePath.Attach(GetModulePath());   
        if (!_szFilePath.IsEmpty())
        {       
            _szFilePath.Append(L".config");
           
            Init();
        }   
    }
    void ConfigurationManager::Init() throw()
    {
        if (!_szFilePath.IsEmpty())
        {
            HRESULT hr = CoGetMalloc(1, &_malloc);
            hr = CreateXmlReader(IID_IXmlReader, (void**)&_xmlReader, _malloc);
            if (FAILED(hr))
            {
       
            }
        }
    }
    time_t ConfigurationManager::GetFileTime() throw()
    {   
        struct stat stResult;
        CComBSTR ansi(_szFilePath);
        ansi.Attach(ansi.ToByteString());
        ::stat((char*)ansi.m_str, &stResult);        // get the attributes of afile.txt
       
        return stResult.st_mtime;
    }
    BSTR ConfigurationManager::AppSettings(const BSTR key, PCWSTR defaultValue) throw()
    {
       
        HRESULT hr = CheckTimeOut();
        if (FAILED(hr))
        {
          
            return NULL;
        }
        CComBSTR find;
        find.Attach(key);
       
        auto found = _map.find(find);
        find.Detach();
           if (found != _map.end())
        {
            return found->second.Copy();
        }
        else if (defaultValue != NULL)
        {
            return ::SysAllocString(defaultValue);
        }
        return NULL;
       
    }
    ConfigurationManager::~ConfigurationManager() throw()
    {
        _map.clear();
        _xmlReader.Release();
        _malloc.Release();
        _szFilePath.Empty();
    }
    HRESULT ConfigurationManager::CheckTimeOut() throw()
    {
       
        auto curT = GetFileTime();
       
        PCWSTR pwzValue;
        auto memResult = ::difftime(curT, _ftLastCheck);
        if (memResult != 0.0F)
        {
            DWORD start = ::GetTickCount();
       
            HRESULT hr = S_OK;
           
            CComPtr pStream;
            CComPtr _readerInput;
           
            hr = ::SHCreateStreamOnFileEx(_szFilePath, STGM_READ | STGM_SHARE_DENY_NONE, FILE_ATTRIBUTE_NORMAL, FALSE,NULL, &pStream);

            if (SUCCEEDED(hr))
            {
                hr = ::CreateXmlReaderInputWithEncodingCodePage(pStream, _malloc, CP_UTF8, TRUE, NULL, &_readerInput);           
                hr = _xmlReader->SetProperty(XmlReaderProperty_DtdProcessing, DtdProcessing_Prohibit);   
                hr = _xmlReader->SetInput(_readerInput);
            }   
            else
            {
                return hr;
            }
           
            XmlNodeType nodeType = XmlNodeType::XmlNodeType_None;
            UINT lenValue;
            PCWSTR key;
            bool startCollecting  = false;
            while (S_OK == _xmlReader->Read(&nodeType) && hr == S_OK)
            {
                switch(nodeType) {
                case XmlNodeType::XmlNodeType_EndElement:
                   
                    //hr = pReader->GetDepth(&dept);
                    hr = _xmlReader->GetLocalName(&pwzValue, NULL);
                    if (startCollecting && lstrcmpW(pwzValue, L"appSettings") == 0)
                    {
                        //break loop
                        hr = S_FALSE;
                    }
                    break;
                case XmlNodeType::XmlNodeType_Element:
                    {
                        // get element name such as option in

                            hr = _xmlReader->MoveToAttributeByName(L"configSource", NULL);
                            if (hr == S_OK)
                            {
                                hr = _xmlReader->GetValue(&pwzValue, NULL);
                                
                                                  
                                if (::PathIsRelativeW(pwzValue) == TRUE)
                                {
                                    //TODO: call back to do a Server.MapPath
                                    _szFilePath.Attach(FileStripFile(_szFilePath));                               
                                    _szFilePath.Append(L'\\');
                                    _szFilePath.Append(pwzValue);
                                }
                                else
                                {
                                    _szFilePath = pwzValue;
                                }
                                _readerInput.Release();
                                pStream.Release();
                                return CheckTimeOut(); //recursion                           
                            }
                            hr = S_OK;//reset otherwise loop stops
                        }                   
                        else if (startCollecting && lstrcmpW(pwzValue, L"add") == 0)
                        {
                           
                            hr = _xmlReader->MoveToAttributeByName(L"key", NULL);
                            if (hr == S_OK)
                            {
                                hr = _xmlReader->GetValue(&pwzValue, &lenValue);
                                //key.Append( pwzValue, lenValue);
                                key = pwzValue;

                                //ATLTRACE(L"found key %s %d\r\n", pwzValue, lenValue);
                                hr = _xmlReader->MoveToAttributeByName(L"value", NULL);
                                if (hr == S_OK)
                                {
                                    _xmlReader->GetValue(&pwzValue, NULL);
                                    _map.insert(std::pair(key, pwzValue));
                                }
                            }
                        }                   
                    }
                    break;
                }
            }
            if (SUCCEEDED(hr)) _ftLastCheck = curT;
            if (_xmlReader != NULL)
            {
                _xmlReader->SetInput(NULL);
            }
        
            return S_FALSE;
           
        }

        return S_OK;
       
    };