リード開発メモ

大阪のソフトウェア会社です。 技術的な事柄についてのメモとしてブログを始めます。

2016年03月

C# ネットワーク上のコンピュータの共有フォルダの一覧

.Net Framework には、ネットワーク上のコンピュータの共有フォルダの一覧を作成するための機能がないため、Windows API を使う必要があった。

[DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]
private static extern int NetShareEnum(
	String ServerName,
	int level, 
	ref IntPtr bufPtr,
	uint prefmaxlen,
	ref int entriesread,
	ref int totalentries,
	ref int resume_handle
);

[DllImport("Netapi32.dll", SetLastError = true)]
static extern int NetApiBufferFree(IntPtr Buffer);

public enum SHARE_TYPE : uint
{
	STYPE_DISKTREE = 0,	
	STYPE_PRINTQ = 1,
	STYPE_DEVICE = 2,
	STYPE_IPC = 3,
	STYPE_CLUSTER_FS = 0x02000000,
	STYPE_CLUSTER_SOFS = 0x04000000,
	STYPE_CLUSTER_DFS = 0x08000000,
	STYPE_TEMPORARY = 0x40000000,
	STYPE_SPECIAL = 0x80000000,
}

public struct SHARE_INFO_2
{
	[MarshalAs(UnmanagedType.LPWStr)]
	public string shi2_netname;
	public SHARE_TYPE shi2_type;
	[MarshalAs(UnmanagedType.LPWStr)]
	public string shi2_remark;
	public Int32 shi2_permissions;
	public Int32 shi2_max_uses;
	public Int32 shi2_current_uses;
	[MarshalAs(UnmanagedType.LPWStr)]
	public string shi2_path;
	[MarshalAs(UnmanagedType.LPWStr)]
	public string shi2_passwd;
}

private List<string> getSharedDirectories(string servername)
{
	var sharedDirectories = new List<string>();

	int level = 2;
	uint prefmaxlen = 368880;
	int entriesread = 0;
	int totalentries = 0;
	int resume_handle = 0;
	IntPtr bufPtr = IntPtr.Zero;

	int ret = NetShareEnum(servername, level, ref bufPtr, prefmaxlen, ref entriesread, ref totalentries, ref resume_handle);
	if (ret != 0)
	{
		return sharedDirectories;
	}

	IntPtr currentPtr = bufPtr;
	int nStructSize = Marshal.SizeOf(typeof(SHARE_INFO_2));

	for (int i = 0; i < entriesread; i++)
	{
		SHARE_INFO_2 shio = (SHARE_INFO_2)Marshal.PtrToStructure(currentPtr, typeof(SHARE_INFO_2));
		if (shio.shi2_type == SHARE_TYPE.STYPE_DISKTREE)
		{
			// Disk driveのみ追加
			sharedDirectories.Add(@"\\" + path + @"\" + shio.shi2_netname + @"\");
		}
		currentPtr = new IntPtr(currentPtr.ToInt32() + nStructSize);
	}

	NetApiBufferFree(bufPtr);

	return sharedDirectories;
}

以上です。

ASP.NET Web API で MemoryCache を使う

ASP.NET Web API で簡単なキャッシュの仕組みが必要になったので、MemoryCache を使ってみた。

参照設定より System.Runtime.Caching を追加しておく。

コントローラーでの使い方は以下のようになる。
ここではある値を3秒間キャッシュし、キャッシュに存在する間はその値を返し続ける。
キャッシュが破棄されるときは、UpdateCallback で設定したデレゲートが呼び出されるので、いつ破棄されたかが分かる。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.Caching;
using System.Web.Http;

namespace MvcApplication1.Controllers
{
	public class ValuesController : ApiController
	{
		public IEnumerable Get()
		{
			var cache = MemoryCache.Default;
			if (cache["key1"] != null)
			{
				return new string[] { (string)cache["key1"] };
			}

			var now = DateTime.Now;
			var policy = new CacheItemPolicy();
			policy.Priority = CacheItemPriority.Default;
			policy.AbsoluteExpiration = now.AddSeconds(3);
			policy.UpdateCallback = new CacheEntryUpdateCallback(MyCachedItemUpdateCallback);

			var key = "key1";
			var value = "value1(" + now.ToString() + ")";
			System.Diagnostics.Trace.WriteLine(string.Format("{0} add. {1}:{2}", now, key, value));
			cache.Set(key, value, policy);

			return new string[] { (string)cache["key1"] };
		}

		private static void MyCachedItemUpdateCallback(CacheEntryUpdateArguments arguments)
		{
			var now = DateTime.Now;
			var reason = arguments.RemovedReason.ToString();
			var key = arguments.Key;
			var value = arguments.Source[arguments.Key].ToString();
			System.Diagnostics.Trace.WriteLine(string.Format("{0} remove({1}). {2}:{3}", now, reason, key, value));
		}
	}
}

実行してみると、以下のようになった。
どうも破棄されるタイミングは毎分00秒、20秒、40秒のいずれかとなっているようだ。そのため、キャッシュ時間を3秒と設定しても、最大で20秒キャッシュされてしまう。
2016/03/01 15:11:15 add. key1:value1(2016/03/01 15:11:15)
2016/03/01 15:11:20 remove(Expired). key1:value1(2016/03/01 15:11:15)

2016/03/01 15:11:36 add. key1:value1(2016/03/01 15:11:36)
2016/03/01 15:11:40 remove(Expired). key1:value1(2016/03/01 15:11:36)

2016/03/01 15:11:50 add. key1:value1(2016/03/01 15:11:50)
2016/03/01 15:12:00 remove(Expired). key1:value1(2016/03/01 15:11:50)

2016/03/01 15:13:20 add. key1:value1(2016/03/01 15:13:20)
2016/03/01 15:13:40 remove(Expired). key1:value1(2016/03/01 15:13:20)

以上です。

ASP.NET で web.config にカスタム設定を追加する

ASP.NET で web.config に独自の設定項目をした場合、ConfigurationSection を継承したクラスを作って読み込む。

例えば以下のようなカスタム設定を追加したとする。
<configuration>
  :
  <customSettings>
    <activeDirectory path="LDAP://192.168.1.201" />
    <servers>
      <add name="server1" ipAddress="192.168.1.202" />
      <add name="server2" ipAddress="192.168.1.203" />
      <add name="server3" ipAddress="192.168.1.204" />
    </servers>
  </customSettings>
</configuration>

クラスは ConfigurationSection を継承して以下のように定義する。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Web;

namespace MvcApplication1.Controllers
{
	public class CustomConfigurationSection : ConfigurationSection
	{
		[ConfigurationProperty("activeDirectory")]
		public ActiveDirectoryConfigElement ActiveDirectory
		{
			get { return (ActiveDirectoryConfigElement)base["activeDirectory"]; }
			set { base["activeDirectory"] = value; }
		}

		[ConfigurationProperty("servers")]
		[ConfigurationCollection(typeof(ServerConfigElement),
								AddItemName = "add",
								ClearItemsName = "clear",
								RemoveItemName = "remove")]
		public ServerConfigCollection Servers
		{
			get { return (ServerConfigCollection)base["servers"]; }
			set { base["servers"] = value; }
		}
	}

	public class ActiveDirectoryConfigElement : ConfigurationElement
	{
		[ConfigurationProperty("path")]
		public string Path
		{
			get { return (string)base["path"]; }
			set { base["path"] = value; }
		}
	}

	public class ServerConfigElement : ConfigurationElement
	{
		[ConfigurationProperty("name", IsKey = true, IsRequired = true)]
		public string Name
		{
			get { return (string)base["name"]; }
			set { base["name"] = value; }
		}

		[ConfigurationProperty("ipAddress")]
		public string IpAddress
		{
			get { return (string)base["ipAddress"]; }
			set { base["ipAddress"] = value; }
		}
	}

	public class ServerConfigCollection : ConfigurationElementCollection
	{
		public ServerConfigElement this[int index]
		{
			get { return (ServerConfigElement)this.BaseGet(index); }
			set
			{
				if (this.BaseGet(index) != null)
				{
					this.BaseRemoveAt(index);
				}
				this.BaseAdd(index, value);
			}
		}

		protected override ConfigurationElement CreateNewElement()
		{
			return new ServerConfigElement();
		}

		protected override object GetElementKey(ConfigurationElement element)
		{
			return ((ServerConfigElement)element).Name;
		}
	}
}

その上で web.config の configSections に、タグ名とクラス定義を結びつける設定を追加しておく。
<configuration>
  <configSections>
    <section name="customSettings" type="MvcApplication1.Controllers.CustomConfigurationSection"/>
  </configSections>
  :
  <customSettings>
    <activeDirectory path="LDAP://192.168.1.201" />
    <servers>
      <add name="server1" ipaddress="192.168.1.202" />
      <add name="server2" ipaddress="192.168.1.203" />
      <add name="server3" ipaddress="192.168.1.204" />
    </servers>
  </customSettings>
</configuration>

コントローラーなどから利用する場合は以下のようにする。
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
	public class HomeController : Controller
	{
		public ActionResult Index()
		{
			var config = ConfigurationManager.GetSection("customSettings") as CustomConfigurationSection;
			System.Diagnostics.Trace.WriteLine(config.ActiveDirectory.Path);
			foreach (ServerConfigElement sce in config.Servers)
			{
				System.Diagnostics.Trace.WriteLine(sce.Name + ":" + sce.IpAddress);
			}

			return View();
		}
	}
}

以上です。

アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

livedoor 天気