Accessing shared folders on other servers from a ASP.NET Core application (Network Connected Program version)

Page update date :
Page creation date :

Operation verification environment

Visual Studio
  • Visual Studio 2022
ASP.NET Core
  • 8 (Razor Pages, MVC)
Windows Server
  • 2022 (ASP.NET Core System Requirements)
  • 2019 (Shared Folder Deployment Server)
IIS
  • 10.0

Operating environment

We haven't tested it in all cases, but it should work generally under the following conditions:

Visual Studio
  • Anything that can develop a ASP.NET Core project
ASP.NET Core
  • Any version (MVC, Razor Pages, API)
Windows Server
  • Windows Server 2008 or later
IIS
  • 7.0 or later

precondition

  • ASP.NET Core applications are intended to run on IIS.
  • Since it uses Windows APIs for authentication, it does not work on non-Windows.

environment

It is verified in the following environment.

Purpose of use of PCs and servers
Windows 11 (Local) An environment for developing programs.
SV2022Test An environment that runs IIS and ASP.NET Core. Access the SV2019Test shared folder from here
SV2019Test Servers with shared folders

In addition, the various settings are as follows.

Parameter Name Value
Access Username SharedUser
Shared Folder Name SharedFolder

Building a Shared Folder Server

Create a user

Create a user to access the common folder. In this case, we will create a local account, but if you are dealing with servers and accounts in a domain such as Active Directory, you can use that as well.

The process of creating a user is beyond the scope of these tips, so we won't go into too much detail.

SharedUser In this case, we'll create it with the name . Since this user does not operate the screen or change the settings, the password cannot be changed.

If you leave the default, you can log in with this user with Remote Desktop, etc., so please remove from the group Users .

Creating a Shared Folder

It doesn't matter where you create it. This is because other servers do not care about the location of the physical folder. In this case, we will create a folder named directly under SharedFolder the C drive and share it.

Open the properties and configure the sharing settings.

The name of the shared folder should SharedFolder be . This name will be visible to other servers. Add SharedUser in the permissions.

Everyone Delete the existing .

Confirm with the "Change" permission.

Since we have only added permissions that can be accessed from the outside, we will set it internally SharedUser so that can operate in this folder.

Confirm with the "Change" permission.

Create a file to check the operation. In this program, the encoding is processed in UFT-8, so please save it in UTF-8.

It is OK if you can access in Explorer from \\<サーバー名>\ another PC, logSharedUser in with , and view the file.

Create a program to read and write files from a shared folder from a ASP.NET Core application

The Mr./Ms. operation is to click the button.

  • Load files in a shared folder and display them on the screen
  • Write a new file to a shared folder

process.

Razor Pages and MVC code are examples of this, but the program that accesses the shared folder is the same for both. The same is true for Web APIs. If anything, it should work in the client program as well.

A process that connects to a network for the duration of a specific process

Create the following code anywhere in your project. The class name is SharedFolderAccessor , but the name is arbitrary. SharedFolderAccessor You can access the shared folder until you create an Dispose instance of . using allows you to specify the period of time during which access can be accessed in an explicit scope.

using System.ComponentModel;
using System.Net;
using System.Runtime.InteropServices;

/// <summary>
/// 共有フォルダにユーザー名とパスワードでアクセスするためのクラスです。
/// using を使用すればそのスコープの間、共有フォルダにアクセスできます。
/// </summary>
public class SharedFolderAccessor : IDisposable
{
  private readonly string _networkName;

  /// <summary>
  /// コンストラクタです。
  /// </summary>
  /// <param name="networkName">共有フォルダのあるサーバーを「\\&lt;サーバー名&gt;」形式で指定します。</param>
  /// <param name="credentials">共有フォルダにアクセスするための資格情報です。</param>
  /// <exception cref="Win32Exception"></exception>
  public SharedFolderAccessor(string networkName, NetworkCredential credentials)
  {
    _networkName = networkName;

    // 接続するネットワークの情報を設定
    var netResource = new NetResource
    {
      Scope = ResourceScope.GlobalNetwork,
      ResourceType = ResourceType.Disk,
      DisplayType = ResourceDisplaytype.Share,
      RemoteName = networkName,
    };

    // ドメインがある場合はドメイン名も指定、ない場合はユーザー名のみ
    var userName = string.IsNullOrEmpty(credentials.Domain)
        ? credentials.UserName
        : $@"{credentials.Domain}\{credentials.UserName}";

    // 共有フォルダにユーザー名とパスワードで接続
    var result = WNetAddConnection2(netResource, credentials.Password, userName, 0);

    if (result != 0)
    {
      throw new Win32Exception(result, $"共有フォルダに接続できませんでした。(エラーコード:{result})");
    }

    // 正常に接続できれば WNetCancelConnection2 を呼び出すまではプログラムで共有フォルダにアクセス可能
  }

  ~SharedFolderAccessor()
  {
    // Dispose を呼び忘れたときの保険
    WNetCancelConnection2(_networkName, 0, true);
  }

  public void Dispose()
  {
    WNetCancelConnection2(_networkName, 0, true);
    GC.SuppressFinalize(this);  // Dispose を明示的に呼んだ場合はデストラクタの処理は不要
  }

  /// <summary>
  /// ネットワーク リソースへの接続を確立し、ローカル デバイスをネットワーク リソースにリダイレクトできます。
  /// </summary>
  /// <param name="netResource">ネットワーク リソース、ローカル デバイス、ネットワーク リソース プロバイダーに関する情報など。</param>
  /// <param name="password">ネットワーク接続の作成に使用するパスワード。</param>
  /// <param name="username">接続を確立するためのユーザー名。</param>
  /// <param name="flags">接続オプションのセット。</param>
  /// <returns></returns>
  [DllImport("mpr.dll")]
  private static extern int WNetAddConnection2(NetResource netResource, string password, string username, int flags);

  /// <summary>
  /// 既存のネットワーク接続を取り消します。
  /// </summary>
  /// <param name="name">リダイレクトされたローカル デバイスまたは切断するリモート ネットワーク リソースの名前。</param>
  /// <param name="flags">接続の種類。</param>
  /// <param name="force">接続に開いているファイルまたはジョブがある場合に切断を行う必要があるかどうか。</param>
  /// <returns></returns>
  [DllImport("mpr.dll")]
  private static extern int WNetCancelConnection2(string name, int flags, bool force);

  /// <summary>
  /// NETRESOURCE 構造体を定義しています。
  /// </summary>
  [StructLayout(LayoutKind.Sequential)]
  private class NetResource
  {
    public ResourceScope Scope;
    public ResourceType ResourceType;
    public ResourceDisplaytype DisplayType;
    public int Usage;
    public string LocalName = "";
    public string RemoteName = "";
    public string Comment = "";
    public string Provider = "";
  }

  /// <summary>
  /// ネットワークリソースのスコープ。
  /// </summary>
  private enum ResourceScope : int
  {
    /// <summary>ネットワークリソースへの現在の接続。</summary>
    Connected = 1,
    /// <summary>すべてのネットワークリソース。</summary>
    GlobalNetwork = 2,
    Remembered = 3,
    Recent = 4,
    /// <summary>ユーザーの現在および既定のネットワークコンテキストに関連付けられているネットワークリソース。</summary>
    Context = 5,
  };

  /// <summary>
  /// リソースの種類。
  /// </summary>
  private enum ResourceType : int
  {
    /// <summary>印刷リソースとディスクリソースの両方のコンテナー、または印刷またはディスク以外のリソースなど。</summary>
    Any = 0,
    /// <summary>共有ディスクボリューム。</summary>
    Disk = 1,
    /// <summary>共有プリンター。</summary>
    Print = 2,
    Reserved = 8,
  }

  /// <summary>
  /// ユーザーインターフェイスで使用する必要がある表示の種類。
  /// </summary>
  private enum ResourceDisplaytype : int
  {
    /// <summary>リソースの種類を指定しないネットワークプロバイダーによって使用されます。</summary>
    Generic = 0x0,
    /// <summary>サーバーのコレクション。</summary>
    Domain = 0x01,
    /// <summary>サーバー。</summary>
    Server = 0x02,
    /// <summary>共有ポイント。</summary>
    Share = 0x03,
    File = 0x04,
    Group = 0x05,
    /// <summary>ネットワークプロバイダー。</summary>
    Network = 0x06,
    Root = 0x07,
    Shareadmin = 0x08,
    /// <summary>ディレクトリ。</summary>
    Directory = 0x09,
    Tree = 0x0a,
    Ndscontainer = 0x0b,
  }
}

Since it uses the Win32 APIs and WNetAddConnection2 WNetCancelConnection2 , it works only in the Windows environment. I have a comment for the time being, but if you want to know more, please look it up on the Internet. In addition to shared folders, you can also perform operations to access network resources.

It is easy to use, and if you write the following, you using can access the shared folder during the scope.

using (new SharedFolderAccessor($@"\\{serverName}", credentials))
{
  // この間は共有フォルダにアクセスできる
}

However, in reality WNetCancelConnection2 , the connection is not immediately disconnected at the timing of calling , so using you can access the shared folder even after the scope.

Test code using SharedFolderAccessor

Since the process of accessing the shared folder does not depend on the framework, we create a common method for testing that reads and writes files. Pazor Pages and MVC should be called the same.

The content is a process that writes the text passed to the argument to the shared folder, reads the text file already in the shared folder, and returns the text.

using System.Net;

public static class Util
{
  public static string ReadAndWrite(string text)
  {
    var serverName = "ServerName";
    var folderName = "SharedFolder";
    var inputFileName = "Input.txt";
    var outputFileName = "Output.txt";
    var username = "SharedUser";
    var password = "password";

    var credentials = new NetworkCredential(username, password);
    using (new SharedFolderAccessor($@"\\{serverName}", credentials))
    {
      // ファイルの書き出し
      System.IO.File.WriteAllText(Path.Combine($@"\\{serverName}\{folderName}", outputFileName), text);

      // ファイルの読み込み
      return System.IO.File.ReadAllText(Path.Combine($@"\\{serverName}\{folderName}", inputFileName));
    }
  }
}

Razor Pages

In Razor Pages, we place a button in Index.cshtml, click it, run the test code, and display the results on the screen. In some cases, the shared folder may not be accessible, so it is enclosed in a try-catch.

Index.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace SharedFolderAccessRazorPages.Pages
{
  public class IndexModel : PageModel
  {
    private readonly ILogger<IndexModel> _logger;
    public string Message { get; set; } = "";

    public IndexModel(ILogger<IndexModel> logger)
    {
      _logger = logger;
    }

    public void OnPost()
    {
      try
      {
        Message = Util.ReadAndWrite($"プログラムからの書き込み ({DateTime.Now})");
      }
      catch (Exception ex)
      {
        Message = ex.ToString();
      }
    }
  }
}

Index.cshtml

@page
@model IndexModel
@{
  ViewData["Title"] = "Home page";
}

<div class="text-center">
  <h1 class="display-4">Welcome</h1>
  <p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

@* ここから追加 *@
<form method="post">
  <button type="submit">サブミット</button>
</form>

<div>
  @Model.Message
</div>
@* ここまで追加 *@

MVC

Similarly, in the case of MVC, Index.cshtml a button is placed in , and when clicked, it calls the test code of the shared folder.

Controllers/HomeController.cs

// 省略

namespace SharedFolderAccessMvc.Controllers
{
  public class HomeController : Controller
  {
    // 省略

    public IActionResult Index()
    {
      return View();
    }

    // ここから追加
    [HttpPost]
    public IActionResult Index(string dummy)
    {
      try
      {
        ViewData["Message"] = Util.ReadAndWrite($"プログラムからの書き込み ({DateTime.Now})");
      }
      catch (Exception ex)
      {
        ViewData["Message"] = ex.ToString();
      }
      return View();
    }
    // ここまで追加

    public IActionResult Privacy()
    {
      return View();
    }

    // 省略
  }
}

Index.cshtml

@{
  ViewData["Title"] = "Home Page";
}

<div class="text-center">
  <h1 class="display-4">Welcome</h1>
  <p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

@* ここから追加 *@
<form method="post">
  <button type="submit">サブミット</button>
</form>

<div>
  @ViewData["Message"]
</div>
@* ここまで追加 *@

Confirmation of operation

Debug and verify that you can successfully access the shared folder.

Building an Application Server

Since we have confirmed that the shared folder can be accessed by running the program, the next step is unnecessary. If you want to check the operation on the IIS server, you can do it by following the steps below.

From here, it is a supplement, so I will not explain it in too much detail, but will mainly explain the image.

Installing IIS

Install it by default from the Server Manager. I won't go into the details of the procedure.

No additional features are required.

No additional IIS services are required at this time.

ASP.NET Core Runtime Hosting Bundle Installation

Since we are using ASP.NET Core 8, we need to install the runtime accordingly. Download it from the following URL:

In order to run ASP.NET Core in IIS, you need something called "Hosting Bundle". Download the "Hosting Bundle" from the ASP.NET Core Runtime.

Once downloaded, run it on the server.

Follow the wizard to install it.

Publish your program

Output the program that you want to deploy to IIS as a file from Visual Studio.

Modify it for Windows.

Publish when you're done.

If you click on the target location, you can open the folder where the published file is located.

You don't have to bring them all, but if you're not sure, you can take them all for now. At this point, all you need to do is make sure that the file exists.

Creating and Deploying Web Applications

From Windows Administrative Tools, open Internet Information Services (IIS) Manager.

We will create a site, but this time we will use the "Default Web Site" that is there from the beginning.

With Default Web Site selected, click Explorer to open the folder. Copy the published program here. You can delete the original file.

Open the page from the IIS link and see if the screen appears. You can open a web browser first and enter the URL directly.

Confirmation of operation

Click the button to verify that it works without any issues. In the above, it is accessed from within the web server, but since it is a web server, it should be possible to operate it from other PCs.