ニッチな日々のブログ

ゲームとかプログラミング(Nodejs(React Express) python C#)とか自作PC作ったりいろいろ好きなことやって生きてます。詰まったプログラムとかあったら参考までに上げたりしますのでよかったら(*'▽')

WCF使用+CORSの設定をしてクローム等の新し目なブラウザからローカルのファイルを開く方法(Windowsユーザー向け)

 

どうもゆたです(^^♪

 

買ったものばかりじゃつまらないと思うので、

プログラミングで最近2日くらい頭を悩ませた事柄を

ちょっと書いていこうと思います。(/ω\)

 

きっと同じように困ってる人がいると思うので良ければ参考に~

(経緯書きまくってるのでコードだけ見たい人は最後のほうに書いてますので~)

※Reactを例にしていますけど自分が使っているのがreactなだけであって

phpとかでも同じなのでさらっと見てってください

 

本題ですが

僕は仕事にてオンプレミス(クラウドとか使わない内内のやつです)での

webサービスを作成保守管理したりしていて(最近はやりの脱Excelってやつですね~)

すごく困ったことに出くわしました。。。。

 

最近のブラウザ進化しててフォルダにブラウザから直接飛べない問題です。

IEの時代には、fileみたいなタグをつけると飛べたんですが

その辺が一切できなくなっていてwebでデータ管理していても

ローカルのおよびサーバーのフォルダデータを直接参照したい編集したいという

同僚のニーズによって何か突破方法はないかと思って探していました( ;∀;)

 

最初はレジストリを編集してbatファイル経由でエクスプローラを開いてと

やっていましたがいざやってみると遅い。。。

しかもユーザータイミングの操作なのでReactでボタン押したら何か制御するとか

一切できない・・・・

 

ほかの方法を調べるとNodejsをみんなにインストールしてもらったらできるとかいろいろあったんですが

Nodejsを開発もしてない人に入れてもらうのハードル高くね。。。。

と思ったりなんかしたりして

 

exeにしてインストール式にして配布できんかなといろいろ模索していたら

C#WCFとかいうローカルでrestできるめちゃ便利なサービスあるやん!?!?!?

ということで試してみました(ただこれが思ったより沼だった・・・)

 

やりたいこととメカニズム

 ReactにはAxiosというxhr通信用モジュールがあってRestAPIの

エンドポイントにアクセスすることができるのですが

ローカルでエンドポイントがあればアクセスできるじゃんと

つまりは下の感じ

f:id:niche_na_hibi:20210102170823p:plain

 

これができると何がいいか

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)