ASP.Net Core Docker容器

在Window10執行ASP.Net Core容器

Morris.C 2020/08/02 23:09:21
2809

前言

目前Docker運行以Linux/Mac系統居多,在微軟加入後,也可以在Windows OS上實作,而在Docker Registry上也有許多相關的image檔。這次要用Windows10+Docker+ASP.NET Core(MVC+WebAPI)來練習,建立Web MVC Container與WebAPI Container,並讓它們可以互相連接。

本文重點在於練習跟減少踩雷,相關內容或詳細程式由於內容繁多,建議至官方網站學習較為紮實。

 

安裝Docker

首先,到https://hub.docker.com/下載docker-desktop安裝程式,並且在Windows功能中,啟用ContainersHyper-V功能

 

安裝後即可看到Docker小鯨魚圖案及功能列

 

在命令提示字元輸入docker version即可看到相關訊息

 

ASP.NET Core API

接著開發WebAPI,以微軟的教學課程範例

建立新專案,選擇ASP.NET Core Web

設定專案名稱

選擇API

接下來在以下框框部份做處理,紅框為增加檔案,黃框為修改檔案

 Startup.cs(修改)

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<TodoContext>(opt =>
                opt.UseInMemoryDatabase("TodoList"));
            services.AddControllers();
        }

TodoItem.cs(新增)

public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
        public string Secret { get; set; }
    }

TodoItemDTO.cs(新增)

public class TodoItemDTO
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
    }

TodoContext.cs(新增)

public class TodoContext: DbContext
    {
        public TodoContext(DbContextOptions<TodoContext> options)
            : base(options)
        {
        }

        public DbSet<TodoItem> TodoItems { get; set; }
    }

TodoItemsController.cs(新增),在Controller僅先做取得全部資料與新增資料功能,其餘請參考微軟教學範例

public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;

        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }

        // GET: api/TodoItems
        [HttpGet]
        public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
        {
            return await _context.TodoItems.Select(x => ItemToDTO(x)).ToListAsync();
            //return await _context.TodoItems.ToListAsync();
        }

        [HttpPost]
        public async Task<ActionResult<TodoItemDTO>> CreateTodoItem(TodoItemDTO todoItemDTO)
        {
            var todoItem = new TodoItem
            {
                IsComplete = todoItemDTO.IsComplete,
                Name = todoItemDTO.Name
            };

            _context.TodoItems.Add(todoItem);
            await _context.SaveChangesAsync();

            return CreatedAtAction(
                nameof(GetTodoItem),
                new { id = todoItem.Id },
                ItemToDTO(todoItem));
        }

        private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
            new TodoItemDTO
            {
                Id = todoItem.Id,
                Name = todoItem.Name,
                IsComplete = todoItem.IsComplete
            };

    }

 

好的,這樣就完成初步的WebAPI,用PostMan測試一下,新增資料

取得全部資料

 

 

 

ASP.NET Core MVC

接下來要建立一個MVC網站,並將連接連接剛才的WebAPI來取得資料、新增資料

若是新建專案,建立流程與WebAPI相同,在應用程式的部份,請選擇Web應用程式

 

筆者用既有的網站加上連接WebAPI的部份,請注意紅框部份是要新增的檔案(若沒有該目錄,請自行建立)

TodoItemController.cs(僅先建立取得與新增的程式),webapiUrl請記得修改為運行時的port

    public class TodoItemController : Controller
    {
        public static string webapiUrl { get; set; }
        
        public IActionResult Index()
        {
            webapiUrl = "https://localhost:44377/";

            try
            {
                TodoItemsViewModel data = new TodoItemsViewModel();
                data.todoItems = new List<TodoItem>();
                using (HttpClient client = new HttpClient())
                {
                    client.BaseAddress = new Uri(webapiUrl);
                    HttpResponseMessage response = client.GetAsync("/api/TodoItems").Result;
                    string result = response.Content.ReadAsStringAsync().Result;
                    data.todoItems = JsonConvert.DeserializeObject<List<TodoItem>>(result);
                }

                return View(data);
            }
            catch(Exception ex)
            {
                return Content(ex.Message.ToString());
            }
        }

        [HttpPost]
        public IActionResult Create([FromForm] string addname)
        {
            if (!ModelState.IsValid)
            {
                return RedirectToAction("index");
            }

            TodoItem todoitem = new TodoItem();
            todoitem.Name = addname;
            todoitem.IsComplete = true;

            using HttpClient client = new HttpClient();
            client.BaseAddress = new Uri(webapiUrl);
            string stringData = JsonConvert.SerializeObject(todoitem);
            var data = new StringContent(stringData, System.Text.Encoding.UTF8, "application/json");
            HttpResponseMessage response = client.PostAsync("/api/TodoItems", data).Result;
            //var r = response.Content.ReadAsStringAsync( ).Result;
            return RedirectToAction("Index");
        }

    }

 TodoItem.cs

    public class TodoItem
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsComplete { get; set; }
    }

TodoItemsViewModel.cs

    public class TodoItemsViewModel
    {
        public List<TodoItem> todoItems { get; set; }
    }

Views→TodoItem→index.cshtml,當運行時它會先去取得資料,然後可以在Add的部份新增資料並且重新載入資料

@model CoreWeb.ViewModel.TodoItemsViewModel
@{
    ViewData["Title"] = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
    <div>
        <h3>Add</h3>
        <form asp-action="Create">
            <input type="text" id="addname" name="addname" placeholder="New to-do" />
            <input type="submit" value="add" />
        </form>
    </div>

<hr />

<table class="table">
    <tr>
        <th>Is Complete?</th>
        <th>Name</th>
        <th></th>
        <th></th>
    </tr>
    <tbody id="todos">
        @foreach (var item in Model.todoItems)
        {
        <tr>
            <td>@Html.DisplayFor(modelItem => item.IsComplete)</td>
            <td>@Html.DisplayFor(modelItem => @item.Name)</td>
            <td><a asp-action="Edit" asp-route-id="@item.Id">Edit</a></td>
            <td><a asp-action="Delete" asp-route-id="@item.Id">Delete</a></td>
        </tr>
        }
        </tbody>

</table>

接下來實際運行看看,看能否正常取得資料與新增資料

 

 

發布到linux container

筆者習慣先將程式發佈後再打包成docker image,docker也可以直接將程式進行編譯再打包成image,但dockerfile語法跟base image要另外寫。

 

先到Docker Hub找到對應的base image,再來跟已發佈的程式打包成自己的image檔。

到docker hub尋找asp.net core就可以找到,它也有說明這個image是支援什麼以及已經有多少下載量

底下也有說明在各種版本,上述有提到,若希望docker可以一併編譯,其base image要選.Net Core SDK,但現在是已發佈程式,所以選擇ASP.NET Core Runtime版本

點進行去,它有如何下載的指令說明,現在先記下它的路徑,後續要用Dockerfile來執行

 

這邊先提一下,要用Linux containers,所以在小鯨魚按右鍵顯示項目列,紅框部份要像這樣,至於為什麼用Linux Containers,後面再說明

 

 

我們先停一下,接下來會先說明docker執行WebAPI/Web MVC的流程與指令,但在真正執行時,請先執行WebAPI的部份(建立image,run container),然後取得它的ip address,再回來更改Web MVC的webapiUrl,然後繼續一樣的流程,或是各位可以利用設定檔等其它方式,那image檔可以先打包,但API跟網站的順序,跟run container指令有關,這部份要注意一下。

制作WebAPI image→run Container→取得IP→修改Web MVC連接api部份→制作Web MVC image→run container

另外也可以利用docker net的方式,來達到一樣的目的,請再自行查閱相關語法囉。

 

接下來是利用Dockerfile建立我們的image檔,WebAPI跟Web MVC都用同一個base image,基本上改Copy的路徑即可,簡要說明一下各行

第1行,base image的路徑,也就是上面在Docker hub提供的

第2行,運行的工作目錄

第3行,將發佈的程式Copy進去

第4行,傾聽的port,WebAPI跟Web MVC有各自的port

第5行,類似於 CMD,在 container 啟動時會自動執行的指令

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
WORKDIR /app
COPY WebApi_Practice/ .
EXPOSE 8088
ENTRYPOINT ["dotnet", "CoreWebapi.dll"]

 

接下來執行Dockerfile,給它一個別名並指定執行Dockerfile的名稱,若後續對docker更熟悉時,可以考慮利用YML來執行

docker build -t corewebapi:v0.2 -f Dockerfile-netcorewebapi .

docker build -t coreweb3:v0.2 -f Dockerfile-netcoreweb_mvc .

執行用,用docker images看一下結果

 

再來就要將image運行成container了,使用docker run指令,建議給予別名以方便後續流程

docker run -d -p 8088:80 --name corewebapi corewebapi:v0.2

用docker ps來看是container是否有正常運行

因為要取得WebAPI的ip address,接下來用docker exec指令進入container,因為base image沒有安裝一堆指令,所以要自行安裝指令才能查詢ip,請參考紅框部份

接下來請將這個ip address要記起來,到Web MVC的controller,webapiUrl要改成這個ip address,然後依WebAPI的流程再跑一次,發佈、打包成image

而在run container的指令略有不同,因為我們要讓2個container互連,要特別指定,否則container會當成是不同的網域而無法互連,請參考以下指令。

--link corewebapi(container名稱),這個指令會將container串成同一個網域才能互連,而它必須是這個container已經在運行才能下此指令,因此要注意執行順序,要被連接的container要先run起來,不然在做--link時會失敗

docker run -d -p 8089:80 -v d:\webapps\logs:/app/logs --link corewebapi --name coreweb3 coreweb3:v0.2

執行完成,再用docker ps檢查是否有正常運作

看來都ok,接下來進行下一步

 

網頁測試

上面我們有指定各自的port,所以記得網址要加上port

新增資料

取得資料

 

OK,看起來連接沒問題,新增與取得全部資料也都正常,這樣就完成整個流程了。後續可自行加上修改或刪除功能。

至此,一個簡單的docker+ASP.NET Core MVC&WebAPI就完成了,全部都在windows10,接下來有些部份再與大家說明一下

 

小提醒

有些踩到的坑跟大家分享一下

Q:為什麼用linux container,而不用windows container?

依這個範例,是可以用windows container,因為WebAPI的資料庫是用MemoryDatabase,但如果是要接到實際的資料庫時,用windows container可能有狀況。

筆者當時是連接MS SQL Server,相同的發行檔案,在windows container一直連接失敗,但在linux container卻成功了,有試著去設定windows container的網路部份,也有設定本機相關設定(如防火牆等),最後是可以ping到sql server ip卻連不到主機,這部份一直不知怎麼解決,後來找到在2018年底,有人在docker討論區反應相同的狀況,但沒看到解決方式或是有人說他可以連,在嘗試好久仍是無法連接,所以此部份是要注意的,筆者現在還在問看看。如果有人知道的話,也觀迎分享做法,謝謝

Q:如果想連到實體的SQL Server,有什麼要注意的?

網路上有相關文章可以找一下,

sql server configuration manager打開Listen All,

有些會建議打開防火牆,

在ASP.NET Core的appsetting中設定連線字串,可以設定sql server ip address與port,如果設定名稱的話要另外設定DNS,另外也建議用帳號密碼連線,別直接複制教學範例上的連線字串,有權限問題

大致上,當時踩的坑有這些@@

Q:如果想加上Nlog之類的,可以嗎?有沒有要注意的?

這部份就照一般NLog的設定即可,惟一要注意是log的存放位置,還記得工作目錄是設/APP,所以路徑指定要注意,如果是直接設在網站目錄底下,那倒還好。另外就是如果設在container裡面,一旦這個container stop了,那log會不見,所以筆者是設在本機,再用-v指令串起來

Q:還有其它要注意的嗎?

做登入驗証與使用jquery等功能,都沒遇到狀況,這部份應該是ok的。其它就注意執行順序與port,應該可以順利完成這個練習

 

最後,謝謝各位耐心的看完本篇,希望有幫助到各位。仍建議各位至官網把Docker與ASP.NET Core的原理與實例好好學習,內容頗為豐富哦

Morris.C
Weber
2020/08/03 15:52:00

請問有試過將此container運行在Linux機器上嗎?

好奇這種linux base的.NET container,能否正常運行於Linux OS的環境上。

Morris.C
2020/08/03 17:26:50

Hi 曹Sir

依微軟官方文件說明,請參考下列網址

https://docs.microsoft.com/zh-tw/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-3.1

另外我是使用linux container,而linux container使用hyper-V建立的Linux虛擬機器。所以綜合以上資料,認為應該是可以的。

有機會再來灌VM實測看看。謝謝你