リード開発メモ

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

ASP.NET

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();
		}
	}
}

以上です。

独自 MediaTypeFormatter を追加する

ASP.NET Web API のテスト用HTML において、enctype="multipart/form-data" なフォームからpostしたところ、「メディアの型 'multipart/form-data' のコンテンツから型 'RequestData' のオブジェクトを読み取るために使用可能な MediaTypeFormatter がありません。」というエラーが出た。

RequestDataは Controller の API の引数として定義しているクラス。

調べてみると、enctype="multipart/form-data" のときは、自分で MediaTypeFormatter を定義し、バインドできるようにする必要があった。

まず、RequestData の定義。
public class RequestDataFile
{
	public byte[] Buffer { get; set; }
	public string FileName { get; set; }
	public string MediaType { get; set; }
}

public class RequestData
{
	public string textdata { get; set; }
	public RequestDataFile filedata { get; set; }
}

そして、以下のような MediaTypeFormatter を継承したクラスを作る。
public class CustomMediaFormatter : MediaTypeFormatter
{
	public CustomMediaFormatter()
	{
		SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/octet-stream"));
		SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
	}

	public override bool CanReadType(Type type)
	{
		return type == typeof(RequestData);
	}

	public override bool CanWriteType(Type type)
	{
		return false;
	}

	public async override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
	{
		if (!content.IsMimeMultipartContent())
		{
			throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
		}

		var ret = new RequestData();

		var Parts = await content.ReadAsMultipartAsync();

		// textdata という名前のパートを探す
		foreach (var Part in Parts.Contents.Where(x => x.Headers.ContentDisposition.DispositionType == "form-data"
			&& x.Headers.ContentDisposition.Name.ToLower() == "\"textdata\""))
		{
			var Data = await Part.ReadAsStringAsync();
			ret.textdata = Data;
			break;
		}

		// filedataという名前のパートを探す
		foreach (var Part in Parts.Contents.Where(x => x.Headers.ContentDisposition.DispositionType == "form-data"
			&& x.Headers.ContentDisposition.Name.ToLower() == "\"filedata\""))
		{
			HttpContent FileContent = Part;
			if (FileContent.Headers.ContentLength > 0)
			{
				var fileInfo = new RequestDataFile();
				fileInfo.FileName = FileContent.Headers.ContentDisposition.FileName;
				fileInfo.MediaType = FileContent.Headers.ContentType.MediaType;
				using (var Imgstream = await FileContent.ReadAsStreamAsync())
				{
					fileInfo.Buffer = new byte[16 * 1024];
					using (var Ms = new MemoryStream())
					{
						int Read;
						while ((Read = Ms.Read(fileInfo.Buffer, 0, fileInfo.Buffer.Length)) > 0)
						{
							Ms.Write(fileInfo.Buffer, 0, Read);
						}
					}
				}
				ret.filedata= fileInfo;
			}
			break;
		}
		
		return ret;
	}
}

最後に App_Start の下にある WebApiConfig クラスの Register メソッドで、上のクラスを登録する。
public static class WebApiConfig
{
	public static void Register(HttpConfiguration config)
	{
		// multipart/form-data のリクエストをバインディングする
		config.Formatters.Add(new CustomMediaFormatter());

		config.Routes.MapHttpRoute(
			name: "DefaultApi",
			routeTemplate: "api/{controller}/{id}",
			defaults: new { id = RouteParameter.Optional }
		);
	}
}

以上です。

シンプルな ASP.NET Web API のプロジェクトを作成する。

ASP.NET Web API プロジェクトを作成すると、多くの不要なコンポーネントを参照することになるようだ。 packages フォルダを見てみると、Entity Framework や jQuery などがあり、単に Web API だけのプロジェクトには不要なものがたくさんある。

そこでシンプルな ASP.NET Web API プロジェクトを作成する。Visual Studio Professional 2013 を使う。

まず、
  ファイル - 新規作成 - プロジェクト
より、ダイアログを起動し、
  Visual C# - Web - Visual Studio 2012 - ASP.NET MVC 4 Web アプリケーション
を選択する。
aspnet_api1

次に、「Web API」ではなく、「空」を選ぶだけ。
aspnet_api2

できたプロジェクトはこんな感じ。
サンプルとして入っているコントローラーがないが、どうせ削除するので要らない。
aspnet_api3

ちなみに「Web API」を選んだ場合はこんな感じ。
aspnet_api4

以上です。

ASP.NET で Trace の内容をファイルに出力する。

ASP.NET で Trace の内容をファイルに出力するには、web.config に以下の設定を追加する。

<configuration>
  <system.codedom>
    <compilers>
      <compiler language="c#;cs;csharp"
                extension=".cs"
                compilerOptions="/d:TRACE"
                type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" warningLevel="1" />
      <compiler language="VB"
                extension=".vb"
                compilerOptions="/d:Trace=true"
                type="Microsoft.VisualBasic.VBCodeProvider, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    </compilers>
  </system.codedom>
  <system.diagnostics>
    <trace autoflush="true" indentsize="4">
      <listeners>
        <add name="myListener"
                  type="System.Diagnostics.TextWriterTraceListener"
                  initializeData="C:\path\trace.log" />
        <remove name="Default" />
      </listeners>
    </trace>
  </system.diagnostics>
</configuration>


以上です。

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

livedoor 天気