2017/06/27

WPF 에서 WebApi 인증

WPF 에서 WebApi 인증

RoleProvider 생성


public class TestRoleProvider : RoleProvider
{
    //생략...
 
    public override string[] GetRolesForUser(string username)
    {
        string[] roles = { "Member" };//get roles by username
 
        return roles;
    }
}

RoleProvider 및 인증 설정


<system.web>
  <authentication mode="Forms">
    <forms loginUrl="~/member/login" timeout="20" slidingExpiration="true" />
  </authentication>
  <roleManager enabled="true" defaultProvider="TestRoleProvider">
    <providers>
      <clear />
      <add name="TestRoleProvider" type="WebApiTest.TestRoleProvider" />
    </providers>
  </roleManager>
</system.web>

권한 필터 설정


public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        filters.Add(new AuthorizeAttribute());
    }
}

Action에 권한 설정


public class ItemController : ApiController
{
    Item[] items = new Item[]
    {
    new Item { Id = "google", Name = "Google" },
    new Item { Id = "naver", Name = "Naver" },
    new Item { Id = "daum", Name = "Daum" }
    };
 
    [System.Web.Http.Authorize(Roles = "Member")]
    public IEnumerable<Item> GetAllItems()
    {
        return items;
    }
}

public class MemberController : Controller
{
    [HttpPost]
    [System.Web.Mvc.AllowAnonymous]
    public ActionResult Login(string userId, string password)
    {
        //사용자 유효성 검사
        if (userId == "test" && password == "1234")
        {
            //인증쿠키 설정
            System.Web.Security.FormsAuthentication.SetAuthCookie(userId, false);
            return new HttpStatusCodeResult(System.Net.HttpStatusCode.OK);
        }
 
        return new HttpStatusCodeResult(System.Net.HttpStatusCode.Unauthorized);
    }
}

HttpClient 로그인


private Uri uri = new Uri("http://localhost:59791");
private CookieContainer cookies = new CookieContainer();
private HttpClientHandler handler = new HttpClientHandler();
private HttpClient client;
 
private ItemCollection items = new ItemCollection();
 
public MainWindow()
{
    InitializeComponent();
 
    this.handler.CookieContainer = this.cookies;
    this.client = new HttpClient(this.handler);
 
    this.client.BaseAddress = uri;
 
    this.Login();
 
    this.client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));
 
 
    this.ItemsList.ItemsSource = this.items;
}
 
public HttpStatusCode Login()
{
    var result = this.client.PostAsync("member/login",
        new FormUrlEncodedContent(
        new Dictionary<string, string>
        {
            {"userId", "test"},
            {"password", "1234"}
        }
        )
    ).Result;
 
    return result.StatusCode;
}

WebApi 사용


private async void GetItems(object sender, RoutedEventArgs e)
{
    var response = await client.GetAsync("api/item/getallitems");
    response.EnsureSuccessStatusCode();
 
    var items = await response.Content.ReadAsAsync<IEnumerable<Item>>();
    this.items.CopyFrom(items);
}

WPF 에서 WebApi 사용하기

WPF 에서 WebApi 사용하기

Model

public class Item
{
    public string Id { get; set; }
    public string Name { get; set; }
}

WebAPI

public class ItemController : ApiController
{
    Item[] items = new Item[]
    {
        new Item { Id = "google", Name = "Google" },
        new Item { Id = "naver", Name = "Naver" },
        new Item { Id = "daum", Name = "Daum" }
    };
 
    public IEnumerable<Item> GetAllItems()
    {
        return items;
    }
}

WPF ObservableCollection

public class ItemCollection : ObservableCollection<Item>
{
    public void CopyFrom(IEnumerable<Item> items)
    {
        this.Items.Clear();
        foreach (var p in items)
        {
            this.Items.Add(p);
        }
 
        this.OnCollectionChanged(
            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}

WPF View

<StackPanel Width="250" >
    <Button Name="getAllItemsButton" Click="GetItems">Get Items</Button>
    <ListBox Name="ItemsList">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Margin="2">
                    <TextBlock >Id: <Run Text="{Binding Path=Id}" /></TextBlock>
                    <TextBlock >Name: <Run Text="{Binding Path=Name}" /></TextBlock>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</StackPanel>

WPF Code

비동기 호출을 사용하여 사용자 응답성을 향상 시킨다.

public partial class MainWindow : Window
{
    HttpClient client = new HttpClient();
    ItemCollection items = new ItemCollection();
 
    public MainWindow()
    {
        InitializeComponent();
 
        client.BaseAddress = new Uri("http://localhost:59791");
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
 
        this.ItemsList.ItemsSource = this.items;
    }
 
    private async void GetItems(object sender, RoutedEventArgs e)
    {
        try
        {
            this.getAllItemsButton.IsEnabled = false;
 
            var response = await client.GetAsync("api/item/getallitems");
            response.EnsureSuccessStatusCode(); 
 
            var items = await response.Content.ReadAsAsync<IEnumerable<Item>>();
            this.items.CopyFrom(items);
        }
        catch (Newtonsoft.Json.JsonException jEx)
        {
            MessageBox.Show(jEx.Message);
        }
        catch (HttpRequestException ex)
        {
            MessageBox.Show(ex.Message);
        }
        finally
        {
            this.getAllItemsButton.IsEnabled = true;
        }
    }
}

2017/06/26

Asp.Net MVC 다국어

Asp.Net MVC 에서 다국어 설정

사용자 친화적인 라우팅

www.site.com/ko-kr/foo 와 같은 호스트네임 바로 뒤에 언어를 설정 한다.

   1:  public static void RegisterRoutes(RouteCollection routes)
   2:  {
   3:      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
   4:   
   5:      routes.LowercaseUrls = true;
   6:   
   7:      routes.MapRoute(
   8:          name: "DefaultLocale",
   9:          url: "{locale}/{controller}/{action}/{id}",
  10:          defaults: new
  11:          {
  12:              locale = "ko-kr",
  13:              controller = "Home",
  14:              action = "Index",
  15:              id = UrlParameter.Optional
  16:          });            
  17:  }

필터 설정

필터에서 사용자 응답 쓰레드에 Culture를 설정 하고 locale 입력값이 지원하지 않는 값이면 리디렉션 한다.

   1:  public class FilterConfig
   2:  {
   3:      public static void RegisterGlobalFilters(GlobalFilterCollection filters)
   4:      {
   5:          filters.Add(new HandleErrorAttribute());
   6:   
   7:          filters.Add(new LocalizationAttribute());
   8:      }
   9:   
  10:      public class LocalizationAttribute : ActionFilterAttribute
  11:      {
  12:          public static readonly List<string> Locales = new List<string> { "ko-kr", "en-us" };
  13:          public static readonly string DefaultLocale = "ko-kr";
  14:   
  15:   
  16:          private void SetLocale(string locale)
  17:          {
  18:              Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(locale);
  19:              Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(locale);
  20:          }
  21:   
  22:          public override void OnActionExecuting(ActionExecutingContext filterContext)
  23:          {
  24:              var locale = filterContext.RouteData.Values["locale"] as string;
  25:   
  26:              locale = locale.ToLower();
  27:   
  28:              if (!Locales.Contains(locale))
  29:              {
  30:                  filterContext.RouteData.Values["locale"] = null;
  31:                  filterContext.Result = new RedirectToRouteResult(filterContext.RouteData.Values);
  32:                  return;
  33:              }
  34:   
  35:              this.SetLocale(locale);
  36:          }
  37:      }
  38:  }

리소스 만들기


기본 리소스는 Public 으로 설정해서 View에서 접근 가능하도록 하고 추가 리소스는 '코드 생성 안함' 으로 설정 한다.

모델에 적용

Display Attribute 에 리소스 명과 ResourceType을 설정 한다.
   1:  public class TestModel
   2:  {
   3:      [Display(Name = "TestString", ResourceType = typeof(Messages))]
   4:      public string Name { get; set; }
   5:  }

언어 선택

Dropdown 목록으로 지원되는 언어를 표시 한다.
_SelectLanguage Partial View 작성.
   1:  @{ 
   2:      string currentLocale = TestWebApplication.FilterConfig.LocalizationAttribute.DefaultLocale;
   3:      if (this.ViewContext.RouteData.Values["locale"] != null)
   4:      {
   5:          currentLocale = this.ViewContext.RouteData.Values["locale"].ToString();
   6:      }
   7:   
   8:      var targetPath = string.Format("{0}/{1}{2}",
   9:          ViewContext.RouteData.Values["controller"].ToString(),
  10:          ViewContext.RouteData.Values["action"].ToString(),
  11:          Request.Url.Query);
  12:   
  13:      var targetUrlMask = string.Format("/{{0}}/{0}", targetPath);
  14:  }
  15:   
  16:  <li class="dropdown">
  17:      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">@currentLocale <span class="caret"></span></a>    
  18:      
  19:      <ul class="dropdown-menu">
  20:          @foreach (var locale in TestWebApplication.FilterConfig.LocalizationAttribute.Locales)
  21:          {
  22:              <li><a href="@string.Format(targetUrlMask, locale)">@locale</a></li>
  23:          }
  24:          
  25:      </ul>
  26:  </li>

레이아웃에 적용
   1:  <div class="navbar navbar-inverse navbar-fixed-top">
   2:      <div class="container">
   3:          <div class="navbar-header">
   4:              <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
   5:                  <span class="icon-bar"></span>
   6:                  <span class="icon-bar"></span>
   7:                  <span class="icon-bar"></span>
   8:              </button>
   9:              @Html.ActionLink("응용 프로그램 이름", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
  10:          </div>
  11:          <div class="navbar-collapse collapse">
  12:              <ul class="nav navbar-nav">
  13:                  <li>@Html.ActionLink("홈", "Index", "Home")</li>
  14:                  <li>@Html.ActionLink("정보", "About", "Home")</li>
  15:                  <li>@Html.ActionLink("연락처", "Contact", "Home")</li>
  16:              </ul>
  17:              <ul class="nav navbar-nav navbar-right">
  18:                  @Html.Partial("_SelectLanguage")
  19:              </ul>
  20:          </div>
  21:      </div>
  22:  </div>

View 테스트

리소스의 문자 출력, 모델에 적용된 리소스 문자 출력 및 현재 시간을 출력.
   1:  @model TestWebApplication.Models.TestModel
   2:   
   3:  <h1>
   4:      @TestWebApplication.Messages.TestString
   5:  </h1>
   6:  <h1>
   7:      @Html.LabelFor(i => i.Name)
   8:  </h1>
   9:  <h1>
  10:      Now: @DateTime.Now
  11:  </h1>

결과 화면


Action 링크에 라우팅이 적용 되었음을 확인 할 수 있다.

C# 문자열 포함 여부 확인하기.

ToUpper() 를 사용하면 불필요한 문자열을 생성하므로 좋은 방법은 아니다. string text = "This is an apple." ; string apple = "Apple." ; bool ...