WCF使用+CORSの設定をしてクローム等の新し目なブラウザからローカルのファイルを開く方法(Windowsユーザー向け)
どうもゆたです(^^♪
買ったものばかりじゃつまらないと思うので、
プログラミングで最近2日くらい頭を悩ませた事柄を
ちょっと書いていこうと思います。(/ω\)
きっと同じように困ってる人がいると思うので良ければ参考に~
(経緯書きまくってるのでコードだけ見たい人は最後のほうに書いてますので~)
※Reactを例にしていますけど自分が使っているのがreactなだけであって
phpとかでも同じなのでさらっと見てってください
本題ですが
僕は仕事にてオンプレミス(クラウドとか使わない内内のやつです)での
webサービスを作成保守管理したりしていて(最近はやりの脱Excelってやつですね~)
すごく困ったことに出くわしました。。。。
最近のブラウザ進化しててフォルダにブラウザから直接飛べない問題です。
IEの時代には、fileみたいなタグをつけると飛べたんですが
その辺が一切できなくなっていてwebでデータ管理していても
ローカルのおよびサーバーのフォルダデータを直接参照したい編集したいという
同僚のニーズによって何か突破方法はないかと思って探していました( ;∀;)
最初はレジストリを編集してbatファイル経由でエクスプローラを開いてと
やっていましたがいざやってみると遅い。。。
しかもユーザータイミングの操作なのでReactでボタン押したら何か制御するとか
一切できない・・・・
ほかの方法を調べるとNodejsをみんなにインストールしてもらったらできるとかいろいろあったんですが
Nodejsを開発もしてない人に入れてもらうのハードル高くね。。。。
と思ったりなんかしたりして
exeにしてインストール式にして配布できんかなといろいろ模索していたら
C#にWCFとかいうローカルでrestできるめちゃ便利なサービスあるやん!?!?!?
ということで試してみました(ただこれが思ったより沼だった・・・)
やりたいこととメカニズム
ReactにはAxiosというxhr通信用モジュールがあってRestAPIの
エンドポイントにアクセスすることができるのですが
ローカルでエンドポイントがあればアクセスできるじゃんと
つまりは下の感じ
これができると何がいいか
webのフロントサイドからローカルに向けて操作ができる
フロントエンドからrestを使っての処理なので命令後にどういう画面遷移にするとかいった細かい設定ができる
沼にはまったところ
まず初めにCorsというものに阻まれました・・・・
wcfでcorsの設定の仕方わからな過ぎて検索しても答えに近いようで遠いようなものとか
英語のものばかりでよくわからんかったりとか・・・・
次にxhr通信の本質を全く今まで理解せずにフロントエンドバックエンド言語
で物を作ってきた(ただのまぬけ・・・)
私の未熟さにありましたそれが今に来たかという
Get以外にはpreflight requestという謎なものがあって
postなどが来た時にOptionsというメソッドが走って
調子はどう?やってる?みたいなことをしていてそいつをやってるよって返してあげるのに気が付くまでに半日かかりました・・・・( ;∀;)
制約
インストールする際にユーザー様のportを開放しないといけないので
netsh http add urlacl url=http://+:80/ServiceURL user=Everyone
(URLあってもなくてもよかったかも。。。)
こんな感じのを管理者でユーザーにたたいてもらう
or
batを作って配布してあげないといけないです。
(セキュリティの観点からみても制約込みの機能ということですね( ..)φ)
実際のWCFプログラム
まずVisualstudioを立ち上げてもらってC#のコンソールアプリケーションを
立ち上げましょう
そしていったん仮にProgram_1という名前で作りましょう
(常駐させるのであればコンパイル時に表示オプションを切ることをお勧めします)
注意点としては.Net coreを選ぶと参照が見つからなくて何だこいつ嘘かよって
なるかもしれないのでそこだけは先に注意です!( ;∀;)
あとpost,put,delete(厳密にいえばGet以外のメソッド)には
preflight requestがあるのでoptionsのエンドポイントを追加を忘れずに!
Program_1.cs
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Web;
namespace Program_1
{
class Program_1
{
static void Main(string[] args)
{
using (ServiceHost sh = new ServiceHost(typeof(MyService)))
{
sh.Open();
Console.WriteLine("service...");
Console.ReadLine();
sh.Close();
}
}
}
[ServiceContract(Namespace = "Program_1")]
public interface IService
{
//個々の部分がexpressとかでいうエンドポイント
//postに変えてjsonで処理というのでもいいと思います
//postにはpreflight requestがあるのでoptionsを追加を忘れずに!
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json)]
string Helloworld();
}
public class MyService : IService
{
//処理層
public string Helloworld()
{
//この辺にフォルダ展開なりを入れればいい
//やり方はいろいろあると思います
return $"HelloWorld,{DateTime.Now.ToShortTimeString()}";
}
}
public class CustomHeaderMessageInspector : IDispatchMessageInspector
{
Dictionary<string, string> requiredHeaders;
public CustomHeaderMessageInspector(Dictionary<string, string> headers)
{
requiredHeaders = headers ?? new Dictionary<string, string>();
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
string displayText = $"Server has received the following message:{ request}";
Console.WriteLine(displayText);
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
var httpHeader = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
foreach (var item in requiredHeaders)
{
httpHeader.Headers.Add(item.Key, item.Value);
}
string displayText = $"Server has replied the following message:{ reply}";
Console.WriteLine(displayText);
}
}
public class CustomContractBehaviorAttribute : BehaviorExtensionElement, IEndpointBehavior
{
public override Type BehaviorType => typeof(CustomContractBehaviorAttribute);
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
var requiredHeaders = new Dictionary<string, string>();
//originヘッダーを上書き
requiredHeaders.Add("Access-Control-Allow-Origin", "*");
requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS");
requiredHeaders.Add("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new CustomHeaderMessageInspector(requiredHeaders));
}
public void Validate(ServiceEndpoint endpoint)
{
}
protected override object CreateBehavior()
{
return new CustomContractBehaviorAttribute();
}
}
}
App.config
ぐちゃっとしてますが以下を追記でOKお願いします・・・
<?xml version="1.0" encoding="utf-8" ?><?xml version="1.0" encoding="utf-8" ?><configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> </startup>
<system.serviceModel> <services> <service name="Program_1.MyService" behaviorConfiguration="mybahavior"> <endpoint address="" binding="webHttpBinding" contract="Program_1.IService" behaviorConfiguration="rest"></endpoint> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"></endpoint> <host> <baseAddresses> <add baseAddress="http://localhost:5638"/> </baseAddresses> </host> </service> </services> <behaviors> <serviceBehaviors> <behavior name="mybahavior"> <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> <endpointBehaviors> <behavior name="rest"> <webHttp /> <CorsBehavior /> </behavior> </endpointBehaviors> </behaviors> <extensions> <behaviorExtensions> <add name="CorsBehavior" type="Program_1.CustomContractBehaviorAttribute, Program_1" /> </behaviorExtensions> </extensions> </system.serviceModel></configuration></configuration>
最後に
あとはお好みに合わせてメソッド足したりやりたいことを足していただければ
webからローカルへの命令がいろいろできます~
だいぶわかりにくいと思いますがいじょうです~
困ったことがこれですっきりしたので同じ境遇の人が見つけてくれるといいな(/ω\)
以上~~(#^.^#)
参考:自己ホステッドWCFサービス上のCORS (366service.com)