ET Core 간단하게 알아보기

데이터 모델링은 3가지 방법으로 가능하다.

-Convention(관례)

-Data Annotation(데이터 주석)

-Fluent API(직접 정의)

1. 관례의 경우에는 Id나 [클래스]Id가 붙을 경우 자동적으로 priamry key로 인식이 된다.

2. Attribute 즉, 속성을 활용하여 가능하다.

ex)

public int Id{ {get; set;}

[MaxLength(32)]

public string UserName {get; set;}

3. API를 사용하여 직접정의한다.

protected overrid void OnModelCreationg(ModelBuilder builder)

{

builder.Entity<GameResult>().Properity(x => x.UserName).ISUnicode(false);

}

지금까지는 1번 즉 관례의 방식으로 데이터 모델링을 사용했으나

2번과 3번도 유용하게 사용되는 경우가 있기에 모든 경우에 대하여 간단하게 알아보고자 한다.

[1]Entity Class에 대한 관례

정의) EF가 참조하는 클래스를 Entity Class라고 한다.

관례)

-public

-static이면 안됨

-생성자가 없거나, 인자 없는 생성자가 있어야 함

-id혹은 [클래스]id 형태의 property를 테이블의 Primary Key로 간주한다.

Column에 대한 관례

관례)

-property의 이름이 테이블 컴럼 이릅으로 사용

-propery의 타입을 해당하는 SQL타입으로 변환

-C# 형식의 기본 nullable이 테이블 컬럼 nullable에 영향

[2]Data Annotation

Blazor의 Form 검증 때 사용한 모든 Annotation이 모두 해당한다.

ex) Required, MaxLength...

[3] FLUENT API

Convention이나 Data Annotation으로 할 수 없는 모든 경우

Fluent Api로 처리가 가능하다.

ex)

protected override void OnModelCreating(ModelBuilder builder)

{

builder.Entity<GameResult>().Property(x => x.UserName).IsUnicode(false);

//UserId를 Index로 추가하는 코드 Has라 헷갈릴수 있지만 추가하는 코드이다.

builder.Entity<GameResult>().HasIndex(x => x.UserId);

//Score가 1000을 넘는 경우에만 필터링을 처리해서 추출하는 코드

builder.Entity<GameResult>().HasQueryFilter(p => p.Score > 1000);

}

이번엔 DB에서 특정 부분만을 제거해도록 하겠다.

Data Annotation에서는 [NotMapped] 속성을 사용하면 된다.

이를 만약에 class에다가 사용할 경우 DB생성에서 제외가 된다.

그리고 Fluent Api에서 사용할 경우

builder.Entity<GameResult>().Ignore(g => g.Excluded);

를 사용하면 된다.

다음으로 DB의 칼럼에서 TYPE과 SIZE 그리고 NULL여부를 제어하고 싶은 경우를 알아보겠다.

[Data Annotation의 경우]

Not null => [Required]

Set String Size => [MaxLength(123)]

Set String VarChar => X

[Fluent Api의 경우]

Not null => .IsRequired();

Set String Size => .HasMaxLength(123)

Set String Varchar => .IsUnicode(false)

테이블의 Primary Key를 설정하는 방식을 알아보겠다.

이 경우에는 위의 3가지 방식이 모두 가능하다.

[관례의 경우]

property의 이름에 Id가 들어가 있으면 기본 키로 인식해준다.

[속성의 경우]

[Key] 속성 값을 프로퍼티위에 붙여준다.

만약 [Key]의 값이 2개 이상의 프로퍼티에 지정되어 있는 복합 키라면

복합 키로 사용되므로 [Column(Order =0)]의 속성을 붙여서

순서를 지정해준다.

[Api의 경우]

.HasKey()로 지정해준다.

다음으로 테이블의 Index를 설정하는 경우는

Fluent Api만 가능하다.

builder.Entity<GameResult>().HasIndex(x => x.UserId);

그리고 테이블의 이름을 설정하는 것은 관례,속성,Api 모든 경우가 가능하다.

[관례]

public DbSet<GameResult> GameResults { get; set: }

[속성]

[Table("GameResultTable")] 을 class위에 붙여준다.

[Api]

builder.Entity<GameResult>().ToTable("GameResultTable");

마지막으로 결론을 정리해보자면

1)기본 상태로 충분하다면 관례방식으로

2)속성으로 가능하다면, 속성방식을 무조건 사용하자!

속성은 낮우에 Blazor Form Validation등 다른 곳에서도 재사용이 가능하기 때문이다.

3)위의 2가지 경우가 안되는 정말 절망적인 상황이라면 Fluent Api를 사용하자!

ex) Index관련...

 

반응형

우선 테이블 명을 GameResultList 로 변경하였다

 

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using RankingApp.Data.Models;
using System;
using System.Collections.Generic;
using System.Text;

namespace RankingApp.Data
{
    public class ApplicationDbContext : IdentityDbContext
    {
        //DbSet 으로 추가해줘야지 GameReesult.cs 에서 GameResultList 내용을 DB에 저장할 수 있다. ORM
        //즉 서버를띄울때 자동으로 DB를 갱신하여 띄워주게 된다
        //PM> add-migration RankingService  명령어르 NeGet 콘솔에서 실행하여 DB를 갱신해준다
        //PM> update-database 명령어르 NeGet 콘솔에서 실행하여 DB를 갱신해준다
        //주의!! GameResultList 이 테이블 명과 동일해야 한다 dbo.GameResultList 에서 dbo 빼고   GameResultList 만
        public DbSet<GameResult> GameResultList { get; set; }

        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
}

 

그런다음 패키지 관리자 콘솔에서 다음 처럼 입력한다음 다음 처럼 적용한다

PM> Add-Migration GameResultList

PM> Update-Database

이 처럼 마이그레이션이 변경된다

테이블명도 자동으로 바뀌게 된다

 

서비스 쪽에 갱신과 삭제에 대한 로직을 추가 하고 테이블에도 값이 삭제/갱신이 적용 될수 있게 처리 한다

       public Task<bool> UpdateGameResult(GameResult gameResult)
        {
            var result = _context.GameResultList.Where(x => x.Id == gameResult.Id).FirstOrDefault();
            if(result==null)
            {
                return Task.FromResult(false);
            }
            result.UserName = gameResult.UserName;
            result.Score = gameResult.Score;
            _context.SaveChanges();

            return  Task.FromResult(true);
        }

        public Task<bool> DeleteGameResult(GameResult gameResult)
        {
            var result = _context.GameResultList.Where(x => x.Id == gameResult.Id).FirstOrDefault();
            if (result == null)
            {
                return Task.FromResult(false);
            }
            _context.GameResultList.Remove(gameResult);
            _context.SaveChanges();
            return Task.FromResult(true);
        }

 

 

Ranking.razor 

갱신과 삭제 버튼이 보여 내용을 수정 할수 있도록 하고

추가팝업과 갱신 팝업은 동일한 형태로 사용한다

그리고 로그인이 되어 있지 않다면 유저 정보는 보이지 않도록 Autorized, NotAutorized 처리를 해 놓았다

@page "/ranking"
@using RankingApp.Data.Models;
@using RankingApp.Data.Services;

@inject RankingService RankingService

<h3>Ranking</h3>

<AuthorizeView>
    <Authorized>
        
        @if (_gameResultList == null)
        {
            <p>Loading...</p>
        }else{
            <table class="table">
                <thead>
                    <tr>
                        <th>UserName</th>
                        <th>Score</th>
                        <th>Date</th>
                        <th></th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (var gameResult in _gameResultList)
                    {
                        <tr>
                            <td>@gameResult.UserName</td>
                            <td>@gameResult.Score</td>
                            <td>@gameResult.Date</td>
                            <td>
                                <button class="btn btn-primary" @onclick="() => EditGameResult(gameResult)">
                                    Edit
                                </button>
                            </td>
                            <td>
                                <button class="btn btn-primary" @onclick="() => DeleteGameResult(gameResult)">
                                    Delete
                                </button>
                            </td>
                        </tr>
                    }
                </tbody>
            </table>
            <p>
                <!-- make button to add gameresult -->
                <button class="btn btn-primary" @onclick="AddGameResult">
                    Add
                    </button>
            </p>


            @if(_showPopup)
            {
                //dialog to show gameresult
                <div class="modal" style="display:block" role="dialog">
                    <div class="modal-dialog">
                        <div class="modal-content">
                            <div class="modal-header"></div>
                            <h3 class="modal-title">Add/Update Game Result</h3>

                            <button type="button" class="close" @onclick="ClosePopup">
                                <span aria-hidden="true">X</span>
                            </button>
                            <div class="modal-body">
                                <label for="UserName"> UserName</label>
                                <input class="form-control" type="text" placeholder="UserName" @bind-value="_gameResult.UserName" />
                                <label for="Score"> Score</label>
                                <input class="form-control" type="text" placeholder="Score" @bind-value="_gameResult.Score" />

                                <button class="btn-primary" @onclick="SaveGameResult">Save</button>
                            </div>
                        </div>
                    </div>
                </div>
            
            }
        }

    </Authorized>
    <NotAuthorized>
        인증 안됨(로그인 안됨)
    </NotAuthorized>

</AuthorizeView>

@code {
    List<GameResult> _gameResultList;

    GameResult _gameResult;

    bool _showPopup = false;

    protected override async Task OnInitializedAsync()
    {
        //db 에서 데이터 긁어와서 _gameResults 에 넣어줌
        await readGameResultList();
    }

    public async Task<List<GameResult>> readGameResultList()
    {
        //db 에서 데이터 긁어와서 _gameResultList 에 넣어줌
        _gameResultList = await RankingService.GetGameResultAsync();
        return _gameResultList;
    }


    public void AddGameResult()
    {
        _showPopup = true;
        //Add new gameresult to
        _gameResult = new GameResult()
        {
            Id = 0
        };
    }

    //ClosePopup
    void ClosePopup()
    {
        _showPopup = false;
    }


    void EditGameResult(GameResult gameResult)
    {
        _showPopup = true;
        _gameResult = gameResult;
    }

    async Task DeleteGameResult(GameResult gameResult)
    {
        var result = RankingService.DeleteGameResult(gameResult);
        await readGameResultList();
    }


    async Task SaveGameResult()
    {
        //새로 만드는 상태
        if(_gameResult.Id==0)
        {
            //save to db
            _gameResult.Date = DateTime.Now;
            var result = RankingService.AddGameResult(_gameResult);
            //close popup
            ClosePopup();
        }
        else
        {
            //수정하고 있는 상태
            var result = RankingService.UpdateGameResult(_gameResult);
        }

        await readGameResultList();
        _showPopup = false;
    }


}

 

결과는 다음과 같고

 

Edit 버튼을 눌러 값 또한 수정 반영 할 수 있다

 

 

로그인 안했을대 로그인 안된다는 문자표시

반응형

 

랭킹 데이터를 추가 하고 다시 가져오는 코드이다

 

Ranking.razor

@page "/ranking"
@using RankingApp.Data.Models;
@using RankingApp.Data.Services;

@inject RankingService RankingService

<h3>Ranking</h3>

<AuthorizeView>
    <Authorized>
        
        @if (_gameResultLIst == null)
        {
            <p>Loading...</p>
        }else{
            <table class="table">
                <thead>
                    <tr>
                        <th>UserName</th>
                        <th>Score</th>
                        <th>Date</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (var gameResult in _gameResultLIst)
                    {
                        <tr>
                            <td>@gameResult.UserName</td>
                            <td>@gameResult.Score</td>
                            <td>@gameResult.Date</td>
                        </tr>
                    }
                </tbody>
            </table>
            <p>
                <!-- make button to add gameresult -->
                <button class="btn btn-primary" @onclick="AddGameResult">
                    Add
                    </button>
            </p>


            @if(_showPopup)
            {
                //dialog to show gameresult
                <div class="modal" style="display:block" role="dialog">
                    <div class="modal-dialog">
                        <div class="modal-content">
                            <div class="modal-header"></div>
                            <h3 class="modal-title">Add/Update Game Result</h3>

                            <button type="button" class="close" @onclick="ClosePopup">
                                <span aria-hidden="true">X</span>
                            </button>
                            <div class="modal-body">
                                <label for="UserName"> UserName</label>
                                <input class="form-control" type="text" placeholder="UserName" @bind-value="_gameResult.UserName" />
                                <label for="Score"> Score</label>
                                <input class="form-control" type="text" placeholder="Score" @bind-value="_gameResult.Score" />

                                <button class="btn-primary" @onclick="SaveGameResult">Save</button>
                            </div>
                        </div>
                    </div>
                </div>
            
            }
        }

    </Authorized>
    <NotAuthorized>
        인증 안됨(로그인 안됨)
    </NotAuthorized>

</AuthorizeView>

@code {
    List<GameResult> _gameResultLIst;

    GameResult _gameResult;

    bool _showPopup = false;

    protected override async Task OnInitializedAsync()
    {
        //db 에서 데이터 긁어와서 _gameResults 에 넣어줌
        await readGameResults();
    }

    public async Task<List<GameResult>> readGameResults()
    {
        //db 에서 데이터 긁어와서 _gameResults 에 넣어줌
        _gameResultLIst = await RankingService.GetGameResultAsync();
        return _gameResultLIst;
    }

    
    public void AddGameResult()
    {
        _showPopup = true;
        //Add new gameresult to
        _gameResult = new GameResult()
        {
            Id = 0
        };
    }

    //ClosePopup
    void ClosePopup()
    {
        _showPopup = false;
    }

    async Task SaveGameResult()
    {
        //새로 만드는 상태
        if(_gameResult.Id==0)
        {
            //save to db
            _gameResult.Date = DateTime.Now;
            var result = RankingService.AddGameResult(_gameResult);
            //close popup
            ClosePopup();
        }
        else
        {
            //수정하고 있는 상태
            
        }
        
        _gameResultLIst = await readGameResults();
    }
}

 

 

RankingService.cs

using RankingApp.Data.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace RankingApp.Data.Services
{
    public class RankingService
    {
        ApplicationDbContext _context;

        public RankingService(ApplicationDbContext context)
        {
            _context = context;
        }

        public Task<List<GameResult>> GetGameResultAsync()
        {
            //DB 에서 GameResult 테이블의 모든 데이터를 가져온다
            List<GameResult> results = _context.GameResult.ToList();
            return Task.FromResult(results);
        }

        public Task<GameResult> AddGameResult(GameResult gameResult)
        {
            _context.GameResult.Add(gameResult);
            _context.SaveChanges(); //db 에 실제 저장이 됨
            return Task.FromResult(gameResult);
        }

    }
}

AddGameResult() 함수가 추가 되었고  

_context.GameResult.Add(gameResult);
_context.SaveChanges(); //db 에 실제 저장이 됨

이 코드가 실제 DB 에 저장하는 부분이 된다

이후 결과는 Task 로 리턴해주고 결과를 담을때는 FromResult 로 결과를 담아줘서 리턴하면 await 으로 결과를 기다 릴 수 있다

    public async Task<List<GameResult>> readGameResults()
    {
        //db 에서 데이터 긁어와서 _gameResults 에 넣어줌
        _gameResultLIst = await RankingService.GetGameResultAsync();
        return _gameResultLIst;
    }

 

 

 

 

유저를 하나 추가하면 다음처럼 보이게 된다 (test1 유저 추가)

DB 데이터에 추가된 내용

반응형

Blazor Server 생성 단계에서 인증 유형을 개별 계정으로 선택한다

이러면 가입, 로그인 등을 할 수 있게 된다

 

RankingDB 이름으로 DB를 하나 추가 한다

그리고 appsettings.json 부분에서 다음 처럼 변경한다

{
  "ConnectionStrings": {
    //"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-RankingApp-b997cf13-bc70-49dd-9313-05e01b61b6ab;Trusted_Connection=True;MultipleActiveResultSets=true"
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=RankingDB;Trusted_Connection=True;MultipleActiveResultSets=true"

  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

DefaultConnectino 부분에서 Database=RankingDB 로 변경해준다

PM> add-migration anyName 으로 DB를 마이그레이션 할때 DefaultConnection 에 잇는 Database=RankingDB 

RankingDB 이 이름으로 DB 가 생성되게 된다

 

 

 

먼저 최종 작업한 화면은 다음과 같다

여기서 Entity = EntityFramework 는 ORM 같은 것이다

 

기본 Blazor Server 에서 Ranking 부분을 추가 한것이도 Fetch data 와는 다르게 데이터는 db 에서 읽어온다 그래서 미리 DB 에 데이터를 넣어 놓어 놓는 것으로 시작한다

 

오른쪽 상단 Register 를 눌러 가입을 하나 하면 dbo.AspNetUsers 에 추저가 하나 추가 된다

그런 다음 EmailConfirmed 를 수동으로 true 로 변경한다 (이 부분은 이메일 확인 부분이다)

유저는 기본으로 제공된느 db 를 사용한다

 

 

GameResult.cs 를 Modes 폴더를 하나 만들어추가한다(Services 로 마찬가지로 추가한다)

using System;

namespace RankingApp.Data.Models
{
    public class GameResult
    {
        public int Id { get; set; }
        public int UserID { get; set; }
        public string UserName { get; set; }
        public int Score { get; set; }
        public DateTime Date { get; set; }
    }
}

 

 

원래는 GameResult 테이블을 하나 추가해 다음 처럼 데이터를 추가해야 하지만 

이렇게 하지 않고 Entity 를 활용해 자동으로 테이블을 등록하게한다 => db에 대한 버전 관리도 되고 .cs 와 코드적으로 자동 연동 됨으로 이것이 편하다

 

ApplicationDbContext.cs 가 db 와 .cs 같에 연결해주는 핵심적인 부분이다

 

먼저 IdentityDbContext 를 상속받는다 

DbSet<GameResult> 이 부분이 DB와 코드의 연결 부분이다

IdentityDbContext : Base class for the Entity Framework database context used for identity.

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using RankingApp.Data.Models;
using System;
using System.Collections.Generic;
using System.Text;

namespace RankingApp.Data
{
    public class ApplicationDbContext : IdentityDbContext
    {
        //DbSet 으로 추가해줘야지 GameReesult.cs 에서 GameResult 내용을 DB에 저장할 수 있다. ORM
        //즉 서버를띄울때 자동으로 DB를 갱신하여 띄워주게 된다
        //PM> add-migration RankingService  명령어르 NeGet 콘솔에서 실행하여 DB를 갱신해준다
        //PM> update-database 명령어르 NeGet 콘솔에서 실행하여 DB를 갱신해준다
        public DbSet<GameResult> GameResult { get; set; }

        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
}

이렇게 추가해주고 

 

Startup.cs 에서 

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));

이 부분을 통해 DB 로 연결하게 된다 (Identity 를 사용하기 때문에 이 부분이 자동으로 등록 되어 있긴 하지만 안되어 있으면 추가해준다)

 

 

 

Nuget 패키지 관리자 > 패키지 관리자 콘솔 에서 다음 명령어들을 실행한다

 

PM> add-migration RankingService  명령어로 NeGet 콘솔에서 실행하여 DB를 마이그레이션 해준다
PM> update-database 명령어로 NeGet 콘솔에서 실행하여 DB를 업데이트해준다

 

이렇게 까지 하면 GameResult DB 가 자동으로 만들어진다

그럼 다음과 같이 Migratoinos 폴더에 코드로 옮겨진 것을 볼 수 있다

20230408233459_RankingService.cs (자동 생성된 파일)

using System;
using Microsoft.EntityFrameworkCore.Migrations;

namespace RankingApp.Data.Migrations
{
    public partial class RankingService : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "GameResult",
                columns: table => new
                {
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    UserID = table.Column<int>(nullable: false),
                    UserName = table.Column<string>(nullable: true),
                    Score = table.Column<int>(nullable: false),
                    Date = table.Column<DateTime>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_GameResult", x => x.Id);
                });
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "GameResult");
        }
    }
}

즉 GameResult 를 알아서 해석하여 테이블을 만드는 코드가 된다

Up 일때는 들어가고 Down 일때는 Drop 하게된다 (버전 관리 개념)

 

이렇게 하지 않으면 DB 에서 Field 를 변경하거나 코드에서 Field 명등 수정 사항이 안맞는 상황들이 발생 할수 있기 때문에 번거로운 작업이 계속 될 수 있는데 이것을 코드수준에서 DB로 맞춰만들어주기 때문에 작업의 효율을 높여준다

이에 더해 버전 관리 또한 가능해지게 된다

 

 

 

데이터는 우선 수동으로 다음처럼 추가한다

 

데이터를 읽어오는 코드를 먼저 작업하자

 

그러기 위해서  ApplicationDbContext.cs 에서 다음 처럼 

DbSet 을 통해서 DB 와 연결된 부분을 읽어와야 하는데 이미 위에서 다음과 같은 코드를 통해 연결을 해놨으니

public DbSet<GameResult> GameResult { get; set; }

 

RankingService.cs 서비스를 만들어 데이터를 가져올 수 있는 코드를 만들어 준면 된다

using RankingApp.Data.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace RankingApp.Data.Services
{
    public class RankingService
    {
        ApplicationDbContext _context;

        public RankingService(ApplicationDbContext context)
        {
            _context = context;
        }

        public Task<List<GameResult>> GetGameResultAsync()
        {
            //DB 에서 GameResult 테이블의 모든 데이터를 가져온다
            List<GameResult> results = _context.GameResult.ToList();
            return Task.FromResult(results);
        }

    }
}

이 코드를 통해 db 에 있는 GameResult 테이블 내용을 읽어올 수 있게 된다

 

그리고 RankingService 를 사용하기 위해서 Startup.cs 에 AddScoped 로 RankigService 를 추가해준다

  public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
            services.AddSingleton<WeatherForecastService>();
            services.AddScoped<RankingService>();
        }

 

AddScoped() 이 내용은 (https://3dmpengines.tistory.com/search/AddScoped) 이 글을 참고

 

 

 

이제 이 내용을 Blazor 를 통해서 보여주면 된다

 

Ranking.razor

@page "/ranking"
@using RankingApp.Data.Models;
@using RankingApp.Data.Services;

@inject RankingService RankingService

<h3>Ranking</h3>

@if (_gameResults == null)
{
    <p>Loading...</p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>UserName</th>
                <th>Score</th>
                <th>Date</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var gameResult in _gameResults)
            {
                <tr>
                    <td>@gameResult.UserName</td>
                    <td>@gameResult.Score</td>
                    <td>@gameResult.Date</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    List<GameResult> _gameResults;

    protected override async Task OnInitializedAsync()
    {
        //db 에서 데이터 긁어와서 _gameResults 에 넣어줌
        _gameResults = await RankingService.GetGameResultAsync();
    }

}

이 페이지를 보여주기 위해 탭을 하나 추가한다

 

이때 상단에

@inject RankingService RankingService

이 코드를 통해 RankingService 를 사용 할수 있도록 Dependency injection 해준다

 

 

NavMenu.razor

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">RankingApp</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="ranking">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Ranking data
            </NavLink>
        </li>
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

 

그럼 다음과 같은 결과를 볼 수 있다

 

반응형

Blazor 앱은 .NET 메서드에서 JavaScript함수를 호출하고, JavaScript함수에서 .NET메서드를 호출할 수 있다.

이것을 JavaScript interop이라고 한다. 

 

Blazor의 .NET 메서드에서 JavaScript 함수 호출

1. index.html body 태그안에 script를 추가한다. 

    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
    <script>
        window.showAlert = (comment) => {
            alert(comment);
        };
    </script>

2. index.razor에서 버튼을 클릭하면 showAlert 함수를 호출하도록 한다.

  • .NET에서 JS를 호출하려면 IJSRuntime 추상화를 삽입하고 InvokeVoidAsync 함수를 호출한다.
@page "/"
@inject IJSRuntime JS

<h1>Hello, world!</h1>

Welcome to your new app.


<p>
		<button @onclick="ShowAlert">알림창 띄우기</button>
</p>

<SurveyPrompt Title="How is Blazor working for you?" />

@code {
		private async Task ShowAlert()
		{
				await JS.InvokeVoidAsync("showAlert", "Hello, world!");
		}
}

3. 알림창 띄우기 버튼을 클릭하면 alert 을 확인할 수 있다. 

showAlert 자바스크립트 함수를 index.html에서 별도의 파일로 옮겨보자. => JS isolation

1. wwwroot/js/script.js 에 아래 함수 저장

export function showAlert(comment) {
    alert(comment);
}

2. index.razor 수정

IJSInProcessObjectReference는 함수를 동기적으로 호출할 수 있는 JS 개체에 대한 참조를 나타냅니다.

@page "/"
@inject IJSRuntime JS

<h1>Hello, world!</h1>

Welcome to your new app.


<p>
		<button @onclick="ShowAlert">알림창 띄우기</button>
</p>

<SurveyPrompt Title="How is Blazor working for you?" />

@code {

		private IJSObjectReference module;

		protected override async Task OnAfterRenderAsync(bool firstRender)
		{
				if (firstRender)
				{
						module = await JS.InvokeAsync<IJSObjectReference>("import",
								"./js/scripts.js");
				}
		}

		private async Task ShowAlert()
		{
				await module.InvokeVoidAsync("showAlert", "Hello, world!");
		}
}

3. 알림창 띄우기 버튼을 클릭하면 alert 을 확인할 수 있다. 

 

 

ref : https://bigexecution.tistory.com/49

반응형

Blazor 서버 기본 구조

Dependency Injection, Router, _imports, Layout 등을 알아본다

 

 

 

Dependency Injection

이를 간단하게 알아보기 위해서 이미 존재하고있던 Data폴더에 FoodService.cs를 생성한다.

이후 이 안에 Food class를 생성해주고 프로퍼티값으로 Name, Price를 선언해준다.

그리고 cs파일을 생성하면서 생성된 FoodService class안에

public List<Food> GetFoods()
{
List<Food> foods = new List<Food>()
{
new Food() {Name = "bap", Price = 7000},
new Food() {Name = "KimBap", Price = 3000},
new Food() {Name = "ChoBap", Price = 9000}
};
return foods;
}

위와 같이 List<Food>를 반환해주는 함수를 생성해준다.

이후에 Blazor App을 생성하면서 존재하는 Index.razor파일안에 아래와 같은 코드를 추가해주면

웹 페이지에 하드코딩해서 넣어준 서비스의 목록들이 나오긴 한다.

 

@using BlazorApp.Data;

<div>
@foreach(var food in _foodService.GetFood())
{
<div>@food.Name</div>
<div>@food.Price</div>
}

</div>
@code{
FoodSerive _foodService= new FoodService();
}

다만, 위와 같이 코드를 작성하게 된다면 코드간의 의존성이 너무 심해진다는 문제가 발생한다.

따라서 Interface을 활용하여 이를 해결해주자.

다시 FoodService.cs로 돌아와서 Interface를 선언해주자 이렇게 해주면 이 FoodService를 사용하고싶은

사람들은 이 interface를 구현해야한다는 규칙이 생기게 된다.

public interface IFoodService
{
IEnumeralbe<Food> GetFoods();
}

public class FoodService : IFoodService
{
	public IEnumeralbe<Food> GetFoods()
	{
		List<Food> foods = new List<Food>()
		{
		new Food() {Name = "bap", Price = 7000},
		new Food() {Name = "KimBap", Price = 3000},
		new Food() {Name = "ChoBap", Price = 9000}
		};
		return foods;
	}
}

하지만 아직까지 razor의 컴포넌트들마다 일일이 new FoodService()를 호출해야하는 문제가 존재한다.

이를 해결하기 위해서 Dependency Injection이다.

이는 Startup.cs안에 ConfigureServices 함수안에 service.AddSingleton<IFoodService, FoodService>();로 선언하게 되면

이제 new를 통해서 선언하는 것이아닌

@inject IFoodService foodSerivce를 통해서 FoodService를 new를 통해서 생성하지 않고 사용이 가능하다.

[예시]

@using BlazorApp.Data;

@inject IFoodService foodSerivce

<div>
@foreach(var food in foodSerivce.GetFood())
{
<div>@food.Name</div>
<div>@food.Price</div>
}

</div>

하지만 이러한 방식이 전역적으로 사용되는것은 아니다.

이를 살펴보자.

public class SingletonService : IDisposable
{
public Guid ID {get; set;} //간단하게 ID를 생성하는 인터페이스이다.

public SingletonService() //cotr + tab,tab으로 해당 class의 생성자를 간단하게 생성할 수 있다.
{
ID = Guid.NewGuid();
}

public Dispose()
{
Console.WriteLine("SingletonService Disposed!");
}
}

public class TransientService : IDisposable
{

public Guid ID {get; set;} //간단하게 ID를 생성하는 인터페이스이다.

public TransientService() //cotr + tab,tab으로 해당 class의 생성자를 간단하게 생성할 수 있다.
{
ID = Guid.NewGuid();
}

public Dispose()
{
Console.WriteLine("TransientService Disposed!");
}
}

public class ScopeService : IDisposable
{
public Guid ID {get; set;} //간단하게 ID를 생성하는 인터페이스이다.

public ScopeService() //cotr + tab,tab으로 해당 class의 생성자를 간단하게 생성할 수 있다.
{
ID = Guid.NewGuid();
}

public Dispose()
{
Console.WriteLine("ScopeService Disposed!");
}

}

[생명주기]

3가지 모드를 파악해보자.

 

다음처럼 생성

service.AddSingleton<SingletonService>();

service.AddTransient<TransientService>();

service.AddScoped<ScopedService>();

 

​다음 처럼 사용 (주로 상단에 선언)

@inject SingletonService singleton;

@inject TransientService transient;

@inject ScopedService scoped;

@inject 가 있는 동일페이지에서 다음 코드가 있을때 guid를 통해  id 가 페이지개 갱신 될때마다 재생성 되는 것을 보면 생명 주기를 파악 할 수 있다

<div>

<h1>Singleton</h1>

Guid: @singleton.ID

<h1>Singleton</h1>

Guid: @transient.ID

<h1>Singleton</h1>

Guid: @scoped.ID

<div>

이렇게 하고 생명주기를 살펴보면

singleton와scoped의 id는 변화하지 않고

transient의 id는 변화한다.

하지만 새로고침을 실행하면 singleton의 id는 바뀌지 않지만 나머지 두개는 변화한다.

(Balzor Server 프로젝트의 경우에는 변화가 없지만 클라이언트(Blazor WebAssembly) 사이드같은 경우에는 singleton 방식도 guid 도 변경 된다)

즉, 변동하지 않고 모두에게 똑같이 보여야 한다면 singleton을

유저마다 갱신되어 보여야 한다면 나머지 2개를 선택하여 사용해야 하는데

Transient는 말그래도 웹페이지의 요청이 일어날때마다 바뀌고

Scoped는 웹에 접속할때 마다 변경된다.

 

 

SPA(single Page Application)

SPA는 기존에 존재하던 정적 페이지가 아닌 동적 페이지라고 할 수 있다

기존에는 웹서버에서 전체를 실시간으로 만들어 보내주는 방식이였지만

일부 변경되는 데이터에 따라(Dom) 모습이 달라지는 것을 동적페이지 방식이라 한다=> SPA

 

 

레이아웃

다음 과 같은 레이웃의 범위를 알아보면 아래 코드로 글자가 어디에 들어 가있는지 보면 된다 a, d , c

 

MainLayout.razor

@inherits LayoutComponentBase

<div class="sidebar">
    <p style="color : #be0000;">
        a
    </p>
    
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        c
        <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
    </div>

    <div class="content px-4">
        d
        @Body
    </div>
</div>

_imports.razor 알아보기

이 컴포넌트 안에 사용되는 @키워드들은 같은 폴더안에 있는 모든 컴포넌트들에게 적용되게 된다.

imports.razor 의 내용을 ABC 라는 폴더에 넣어놓고
내용을 새로만든 레이아웃 @layout MainLayout2.razor

해놓으면 해당 폴더에 있는 razor 페이지들의 기본 레이아웃은 MainLayout 이 아닌  MainLayout2 으로 변경된다

 

 

기본 레이아웃은 자체는 다음 코드를 상속받아 레이아웃 자체를 구성한다

@inherits LayoutComponentBase

 반드시 하나는 이 것을 상속받는 레잉아웃이 있어야 한다 이것이 적용된 페이지가

MainLayout.razor 이다

 

@inherits를 사용하게되면 class를 상속해서 사용할 수 있게 된다.

@layout키워드를 사용하면 레이아웃을 바꿔서 적용할 수 있다.

키워드를 사용하지 않고 모든 레이아웃을 변경하기 위해서는 App.razor에 DefaultLayout을 변경해주면 된다.

 

 

 

Router : 주소/sports/55  과 같이 주소 뒤에 어떤 페이지를 보여줄지에 대한 것

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

 

<Router 는 다음 처럼 되어 있다 Found 가 RenderFragment 로 내용을 보여주기 위한 형태인것을 알 수있다(템플릿)

그리고 Router는 상대적 기준으로 생각하고 작성해야 한다 즉 현재 경로 위쪽이 무엇인지 기본적으로 절대경로로 생각하면 안된다는것

 

 

 

 

 아래코드에서

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorApp</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="user">
                <span class="oi oi-list-rich" aria-hidden="true"></span> User
            </NavLink>
        </li>
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

 

 

 이 부분은 

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorApp</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

이 처럼 화면이 작아지는 모바일 환경에서 메뉴표현등으로 나타내기 위한 버튼으로 화면이 큰 윈도우 환경에선 기본적으로는 화면을 줄이지 않는 이상 안보일 수 있다

 

 

NavLink : 이동 시키는 <a href > 와 유사한데 비주얼 적으로 차이가 있는데 버튼을 누를때 스타일 적인 것만 다르다

상단에 Home, FetchData 등의 버튼을 참고하면 된다

 NavLink 는

<a href  를 써서 동일하게 구현할 수도 있고

NavigationManager 를 인젝션 하여

 이렇게 구현하여 구현 할 수도 있다

 

counter 페이지가 뜰때 다음처럼 값을 받을 수도 있다 값은 CurrentCount 로 들어가게 된다

 

 

https://localhost:44337/counter/30  30을 입력하면 CurrentCount 값이 30 이 된다

 

 

 

 

ref : https://blog.naver.com/whro152/223041082396

반응형

[색상 변경하기 예제]

 

 

Cascading Parameter 는 변수를 전달하는데 다른 페이지에

<Cascading Parameter 로 동일하게 선언되어 있는 변수가 있다면 그곳 까지 값이 전달된다

 

TableTemplate 은 C# 의 tempalte 같은 것인데 당연히 동일하지 않고 엇비슷하다


Cascading Parameter알아보기

만약 우리가 동일한 Parameter변수들을 넘겨주는 경우가 발생한다면 한번에 흘러내리는 것처럼

변수를 넘겨주게 하는 문법이다.

사용 방법은 매우 간단한데

<CascadingValue Name ="ThemeColor" Value="_selectedColor">

<ShowUser ...>

<ShowUser2 ...>

</CascadingValue>

태그를 사용하여 사용할 범위를 지정해 준다.

Name은 구분하기 위한 Key이며 Value는 범위내에 뿌릴 값이다.

이렇게 사용하게 되면 범위내에 있는 ShowUser, ShowUser2의 컴포넌트에 자동적으로 Value = "_selectedColor"

Parameter가 흘러내리듯이 정해지게 된다.

이를 ShowUser,Showuser2의 컴포넌트에서 사용하기 위해서는

[CascadingParameter(Name = "ThemeColor")]

string _color {get; set;}

위와 같이 사용하여 _color의 값에 Value="_selectedColor"가 Binding된것처럼 사용할 수 있게 된다.

[추가]

컴포넌트의 깊이에 상관없이 Parameter가 전달된다는 특징이 있다.

[Templated Component]알아보기

C#의 제너릭형식과 유사한 문법이다.

RenderFragment로 이루어진 Razor Component이다.

[넘어가는 이야기]

[c#에는 c++에 없는 partial class가 존재한다.

이는 a라는 파일에서 partial class a를 작성하고

추후에 b라는 파일에서 aprtial class a를 이어서 작성하게 되면 두 class를 하나로 합친 것처럼

작성하게 해주는 키워드이다. 현재 Blazor와는 상관없는 c#문법이므로 넘어가도 상관없다.] 딱히 이 부분은 크게 상관없는 부분이다.

 

정리하자면 TableTemplate.razor를 생성한다.

1) 코드 부분에 [Parameter] public RenderFragment Header {get; set;}를 작성한다.

2) HTML 부분에 사용하기 위한 장소에 @Header를 선언해준다.

3) 다른 razor 컴포넌트에서 <TableTemplate></TableTemplate>를 선언해준다.

4) <TableTemplate></TableTemplate>의 범위안에 <Header></Header>를 서언하여

RenderFragment Header를 사용한다는 것을 알려준다.

5) <Hedaer></Header>의 범위안에 사용하고 싶은 HTML의 태그들을 지정해준다.

6) 이렇게 사용하게 되면 TableTemplate.razor의 @Header부분에 지정한 태그들이 들어가게 된다.

 

 

TableTemplate 예시

노란색 화살표가 해당 부분이 .razor 페이지로 들어가는 부분이 된다

이때 해당 부분을 대체하는데 쓰이는 역할로 RenderFragment 가 사용된다 : 태그를 그리는 느낌

TableTemplate 을 통해 보여지는 부분의 개수를 변경하거나 스타일을 다르게 하는 등이 가능해질 수 있다

 

 

ref : https://blog.naver.com/whro152/223039845752

반응형

User.razor 전체 코드

 

컴포넌트 분리

저번 포스팅에 @code부분인 AddUser()와 KickUser()를 컴포넌트화 시키기 위해 새로운 Blazor파일을 생성해 분리하도록 해보자

ShowUser 블레이저 파일 생성

ShowUser.razor

ShowUser.razor파일을 생성해 User.razor에있던 리스트 생성과 유저 추가, 삭제하는 함수들을 옮겼다.

 

설명

User.razor

-<ShowUser></ShowUser> : 새로 생성한 ShowUser.razor를 사용하기 위한 처리

-<ShowUser User ="_users"> : ShowUser.razor의 User변수와 User.razor의 _user의 변수를 공유한다

-@ref ="_showUser" : 부모쪽에서 자식에 접근하는 방식(이 처리가 없으면 @code에 ShowUser는 null임)

CallbackTest="CallbackTestFunc" : 콜백을 사용할 때

 

ShowUser.razor

[Parameter] : 를 통해  다른 블레이저 파일에서 접근 가능하도록 명시한다.

(블레이저에서는 Action보다는 EventCallback을 추천한다. StateHasChanged()가 없어도 리프래시 처리 )

 

유저리스트에 요소 추가

유저리스트에 요소 삭제

"CallbackTest"문자가 보임 : 콜백 호출이 잘되고 있다

 

 

ref : https://funfunhanblog.tistory.com/419

반응형
@page "/bind"

<p>
    <input @bind="inputValue" />
</p>

<p>
    <input @bind="InputValue" />
</p>

<ul>
    <li><code>inputValue</code>: @inputValue</li>
    <li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
    private string? inputValue;

    private string? InputValue { get; set; }
}

위 처럼 string 변수에 있는 문자 내용을 input 쪽 변수에  바인딩 시킬수도 있고

 

<button class="@inputValue"   type="button"

처럼 버튼의 스타일 또한 변경이 가능하다

<button class="@inputValue"   type="button" disabled="@(_users.Count() >= 5)"

이렇게 유저의 카운트가 0 보다 크면 버튼이 안보이게 처리 또한 가능하다

_users 이 글을 참고해보면 된다

https://3dmpengines.tistory.com/2375

 

만약 데이터가 html 즉 UI 상에 변경된 것이 보이지 않는 상황이 다음 time 관련 코드에서 적용이 안될 수 있는데

 

 

이때는 StateHasChanged(); 함수를 호출해주면 변경된 값이 HTML/UI 상에 보이게 된다

 

 

반응형

유저의 목록과 추가및 제거에 대한 변수와 함수를 바인딩하는 것을 알아본다

 

기본적으로 html 부분에 code 쪽의 변수가 바인딩 되어 있는 경우 변수가 바뀔때 자동으로 데이터가 html 상에서 갱신된다

 

[유저 목록과 추가하기]

유저의 목록 기본적으로 3개를 만들어 for 문으로 3개가 보여지도록 처리해놓는다

@page "/user"
@using BlazorApp.Data;

<h3>Online Users</h3>

<p>
    Users : <b>@_users.Count</b>
</p>
<br/>

<ul class="list-group">
    @foreach(var user in _users)
    {
        <li @key="user" class="list-group-item">
            <label>@user.Name</label>
        </li>
    }
</ul>


<br/>

<div class="contrainer">
    <div class="row">
        <div class="col-md-6">
            <input type="text" class="form-control" placeholder="Add User" @bind-value="_inputName"/>
        </div>
        <div class="col-md-6">
            <button class="btn btn-primary" type="button" @onclick="AddUser" > Add a user </button>
        </div>
    </div>

</div>


@code {
    List<UserData> _users = new List<UserData>();

    string _inputName;

    protected override void OnInitialized()
    {
        _users.Add(new UserData { Name = "John" });
        _users.Add(new UserData { Name = "Jane" });
        _users.Add(new UserData { Name = "Jack" });
    }

    void AddUser()
    {
        _users.Add(new UserData { Name = _inputName });
        _inputName = string.Empty;
    }

}

 

user 명을 추가 하면 li 목록에 추가 되는 코드이다

 

함수 AddUser 또한 @onclick= 에 연결 할 수 있다

 

 

 

 

[제거]

@page "/user"
@using BlazorApp.Data;

<h3>Online Users</h3>

<p>
    Users : <b>@_users.Count</b>
</p>
<br/>

<ul class="list-group">
    @foreach(var user in _users)
    {
        <li @key="user" class="list-group-item">
            <button type="button" class=" btn btn-link" @onclick="(()=>KickUser(user))" > X </button>
            <label>@user.Name</label>
        </li>
    }
</ul>


<br/>

<div class="contrainer">
    <div class="row">
        <div class="col-md-6">
            <input type="text" class="form-control" placeholder="Add User" @bind-value="_inputName"/>
        </div>
        <div class="col-md-6">
            <button class="btn btn-primary" type="button" @onclick="AddUser" > Add a user </button>
        </div>
    </div>

</div>


@code {
    List<UserData> _users = new List<UserData>();

    string _inputName;

    protected override void OnInitialized()
    {
        _users.Add(new UserData { Name = "John" });
        _users.Add(new UserData { Name = "Jane" });
        _users.Add(new UserData { Name = "Jack" });
    }

    void AddUser()
    {
        _users.Add(new UserData { Name = _inputName });
        _inputName = string.Empty;
    }

    void KickUser(UserData user)
    {
        _users.Remove(user);
    }

}

 

 

이름 앞에 X 표시 버튼을 추가하였고 x 를 누르면 유저목록에서 유저가 제거된다

기본 3개에서 하나를 제거한 상태이다

 

@onclick="(()=>KickUser(user))"  이 코드는 람다형태로 onclick="KickUser(user)" 의 형태로는 지원이 되지 않기 때문에 람다로 연결해주면 가능하다

 

반응형

 

 

Binding.razor 페이지를 만들고 다음 처럼 작성한다

@code 부분의 변수가 html 코드 중 @_value 로 연결 되게 처리 할 수 있고

html 에서는 @bind-value=  들로 @code 쪽 갑과 연결 되어 양방향 연결이 되게 할 수 있다

@page "/binding"

<h3>Binding</h3>

<p>Value : @_value</p>

<input type="range" step="1" @bind-value="_value" @bind-value:event="oninput" />

@code {
    int _value = 10;

}

 

 

 

메뉴 레이아웃중 하단에 binding 텝을 만들어주면

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorApp</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="binding">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Binding
            </NavLink>
        </li>
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

 

 

환경은 Blazor Server 이다

반응형

ValuesController

using HelloEmpty.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace HelloEmpty.controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet]
        public List<HelloMessage> Get()
        {
            List<HelloMessage> message = new List<HelloMessage>();
            message.Add(new HelloMessage() { Message = "Hello Web API 1!" });
            message.Add(new HelloMessage() { Message = "Hello Web API 2!" });
            message.Add(new HelloMessage() { Message = "Hello Web API 3!" });
            return message;
        }
    }
}

get() 이 클라에서 서버로 api/Values 경로로 api 를 쏘면 

서버에서 보낼 내용을 message 에 담아 브라우저에 보낸다

 

 

HelloMessage

namespace HelloEmpty.Models
{
    public class HelloMessage
    {
        public string Message { get; set; }
    }
}

실제 데이터는 Models 에 들어있게 된다

 

 

program 부분인데 이 부분은 디폴트이다

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace HelloEmpty
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

 

 

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace HelloEmpty
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            //mvc 를 사용하겠다는 것
            //services.AddControllersWithViews();
            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();


            //홈페이지 실행할때 어떤 주소로 어떤 부분을 연동하여 실행할지에 대한 부분
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                //endpoints.MapRazorPages();
                //endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
                /*
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
                */
            });
        }
    }
}

 

DotNet Core 3.1 버전으로 하면 StartUp.cs 가 생성된다

 

 

실행결과

 

실제 데이터가 JSON 형태로 온것을 알 수 있다

 

반응형

 

 

우리는 웹 어플리케이션을 개발할 때, 보통 java, .NET, Node.js 등의 framework 또는 language를 사용하여 server-side project를 만든다. 그리고 Angular, React, Vue.js와 같은 javascript framework를 사용하여 브라우저에 데이터를 보여준다.

 

하지만, 우리는 server-side application에서 사용한 언어를 client-side에는 사용할 수가 없다. server-side와 client-side는 기본적으로 단절되어있다. 

 

우리가 만약, server language를 client에도 재사용한다면 좋지 않겠는가? 우리는 WebAssembly를 사용하여 그렇게 할 수 있다. 

 

Blazor는 무엇이고, 어떻게 사용해야 하는가?

Blazor는 C#을 사용하여 interactive web application을 만들 수 있는 오픈소스 framework이다. 우리는 Blazor에서 server, client code에 모두 C#을 사용할 수 있다. .NET runtime은 브라우저에서 우리가 생성한 .NET DLL을 실행할 수 있다. 

 

추가적인 plugin없이 주요 브라우저에서 실행이 가능하다. .NET standard와 호환하는 library를 사용할 수 있다. (NuGet package 포함) 

 

Blazor application은 WebAssembly에서 실행되고, WebAssembly는 JavaScript sandbox에서 실행된다. 

JavaScript와의 연결이 되어 있으므로, web sockets, file API, DOM의 브라우저 기능을 사용할 수 있다. 

 

Blazor application에서 JavaScript를 호출할 수 있고, 그 반대도 가능하다. 

 

Blazor Application의 Different Models

Blazor Server

Blazor Server는 ASP.NET Core application의 server측에서만 동작하고, SignalR을 사용하여 HTML과 통신한다. blazor.server.js라는 파일을 통해 가능하다. 

server-side application은 메모리에 남아있기 때문에, 사용자당 instance가 생성된다. 

장점은, Blazor WebAssembly에 비해 다운로드 사이즈가 작다. older browser에서도 디버깅이 가능하다. 

 

단점은, 서버와 연결되어 있어야 하기 때문에 offline support는 없다. 확장성도 리스크가 될 수 있지만, 동시접속 천단위까지 제공이 가능하다. 마지막으로, serverless deployment는 불가능하며, ASP.NET Core server가 반드시 필요하다. 

 

Blazor WebAssembly

WebAssembly의 도움으로 브라우저 client에서 실행된다. application이 실행되면 browser로 모든 파일을 다운받는다. (HTML, CSS, JavaScript files, .NET Standard DLL files)

 

offline mode에서 동작한다. server가 필요하지 않다. static files로 구성되어 있기 때문에, CDN, Server, Azure Storage에 배포가 가능하다. 

 

장점은, native browser application의 속도와 비슷한 정도로 빠르다. offline mode에서 동작이 가능하다. serverless deployment가 가능하다. browser로 file만 받으면, server는 필요하지 않다. 추가 plugin 없이, modern browser에서 동작이 가능하다. 배포가 쉽다. 

 

브라우저에서만 실행되기 때문에, 브라우저 capability에 의해 성능이 제한된다. 

 

단점은 최초 로딩시간이 길다. older browser에서는 동작하지 않는다. older browser에서 동작해야 한다면 Blazor server-side application이 더 나은 선택이 되겠다. 

 

Blazor Server와 Blazor WebAssembly Application의 차이점

두 application의 차이점을 살펴보자. 

 

1. index.html, _Host.cshtml

 wwwroot 폴더를 보면 Blazor WebAssembly App은 index.html 파일이 없고, Blazor 서버 앱은 없다. 

index.html파일은 blazor.webassembly.js 파일을 참조하고, application의 모든 파일을 다운로드 한다. 

이와 비슷한 일은 Blazor 서버 앱 Page/_Host.cshtml 파일에서 발생한다. 이 파일에서 참조하는 blazor.server.js 파일은 client WebSocket connection을 담당한다. 

 

그 외 Counter, Index, FetchData 파일이 동일하게 들어가 있으며, 이것은 component files를 재사용 할 수 있다는 뜻이다. 

 

2. setting

Blazor 서버 앱은 Startup.cs, appsettings.json 파일이 있고, Blazor WebAssembly App에는 없다. 

Blazor WebAssembly App에서 서비스를 등록하기 위해서는 Program.cs 파일을 사용해야 한다. 

 

 

ref : https://bigexecution.tistory.com/65

 

반응형

SPA 중심에는 대표적인 라이브러리/프론트프레임워크 React, vue/Angular, Blazor WebAssembly가 있다.

📖 SPA (Single Page Application)

기존의 웹서비스는 클라이언트(사용자)가 요청하면 서버가 해당 웹 페이지를 HTML 코드로 계산하고 반환하며, 많은 요청이 이루어지면 계산이 더욱 오래 걸립니다.

SPA는 모든 처리가 단일 페이지에서 수행되는 방식입니다. 첫 로드시에만 웹페이지를 계산하여 HTML로 반환합니다. 그 이후에는 필요한 데이터를 HTML로 전달받지 않고 (서버에서 렌더링 X) 필요한 데이터만 서버로부터 JSON으로 전달받아 동적으로 렌더링 합니다.

장점

1. 빠른 로딩 시간 (Quick Loading Time)

  • 단일 페이지에서 웹 앱에서 페이지가 로드되면 서버는 더 이상 HTML 또는 CSS를 보내지 않기 때문입니다.
    (Google Reasearch에 따르면 페이지를 로드하는 데 200 밀리 초 이상 걸리면 비즈니스와 판매에 큰 영향을 미침)

2. 좋은 캐싱 능력 (Good Caching Abilities)

  • 로컬 데이터를 효율적으로 캐시 할 수 있습니다. SPA는 하나의 요청 만 보내고 서버의 모든 데이터를 저장, 데이터에 대한 지속적인 액세스가 있기 때문에 사용자는 오프라인으로 작업할 수도 있습니다. (방문자에게 데이터비용을 요구하지 않음)

3. 신속한 프런트 엔드 개발 (Rapid Front-End Developement)

  • 단일 페이지 애플리케이션 아키텍쳐이기 때문

4. 향상된 사용자 경험 (Improved User Experience)

  • 굳이 새 페이지가 로드 될 때까지 기다릴 필요가 없음, 콘텐츠의 동적인 로드는 사용자에게 좋은 경험을 제공

주의

초기 구동속도가 느릴 수 있음.

검색엔진 최적화(SEO)

 

왜 SPA를 사용하나요?

단일 페이지 웹 앱 (SPA)는 웹 개발을 고려할 때 이상적인 선택입니다. Saas플랫폼, SEO가 중요하지 않은 커뮤니티에 쓰입니다.

프로그램과 사용자간의 훌륭하고 풍부한 통신을 원한다면 SPA 접근 방식이 필요합니다.

 

 

 

 

기존 MVC 등은 페이지 단위를 받는 형태였다고 함..  비효율적인 구조..

 

ref : https://dhddl.tistory.com/230

반응형

+ Recent posts