在Window10執行ASP.Net Core容器
前言
目前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功能中,啟用Containers跟Hyper-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的原理與實例好好學習,內容頗為豐富哦
請問有試過將此container運行在Linux機器上嗎?
好奇這種linux base的.NET container,能否正常運行於Linux OS的環境上。