在PCL中实现跨平台HTTP客户端以在Azure上使用您的ASP.NET Web API后端

使用移动应用程序时,您通常会使用后端工作,如果您正在使用C#,则可能是Web API后端。在此博客中,我们将看看我们如何消耗和写入我们可以在Azure上托管的Web API控制器。为什么天空?因为它是直接托管Web服务的最简单方法,如果您碰巧有一个MSDN订阅,您可以免费完成所有。对于客户,我们将使用Windows 10 Universal应用程序,该应用程序允许我们为桌面,平板电脑和手机编写客户端。

我们将建设的应用程序将添加能力从服务中读取人员列表并将一个人添加到现有。所以我们会看到我们如何阅读和写入HTTP服务。

客户端

作为客户,我们将定位Windows Phone(即将再次成为Windows Mobile),Android和iOS。我们实现与后端的整合我们将在便携式类库(PCL)中执行,该库为我们提供独特的优势,只有必须编写我们的后端服务集成一次。

PCL集成

在PCL中,我们将需要以下Nuget包:

武装能力现在能够与HTTP后端和序列化I.E.将对象序列化到JSON中,我们可以通过在构造函数中创建HTTP客户端来开始实现我们的通信层。我们通常只需要HTTP客户端的一个实例,因为它将处理多个请求,只有在HTTP上合理地生成多个活动连接,

public class PersonService : IPersonService
{
    private HttpClient _httpClient;

    public PersonService()
    {
        _httpClient = new HttpClient();
    }

    // ... Person Service implementation
}

为了阅读HTTP,我们应该使用HTTP客户端提供的GET关键字,并实现如下:

public async Task<IEnumerable<Person>> GetPeople()
{
    var result = await _httpClient.GetAsync(BASE_URI);
    var peopleJson = await result.Content.ReadAsStringAsync();

    return JsonConvert.DeserializeObject<IEnumerable<Person>>(peopleJson);
}

请注意,如果您在Azure上使用免费网站,您可以选择通过SSL运行通信,因此您必须更改此内容正在添加 s 在http i.e之后的URI中。导致一个 https. URI.

写入HTTP服务取决于我们要执行的操作。创造新的东西应在HTTP帖子中向现有人员列表添加新人。实施帖子可以如下所示:

public async Task<bool> CreatePerson(Person person)
{
    var personJson = JsonConvert.SerializeObject(person);
    var content = new StringContent(personJson, Encoding.UTF8, "application/json");
    var result = await _httpClient.PostAsync(BASE_URI, content);

    return result.IsSuccessStatusCode;
}

stringContent. 方法具有附加参数,以向服务器指示如何解释内容。这是通知后端服务所需的是我们发送的数据被序列化为JSON。不这样做将以标题中的标题中的虚假信息结束,这将阻止Web API自动反序列化身体。

更新现有人通常通过放置完成,类似于 Createperson. 方法,但包含URI中的ID,其通常与在后端设置的人的ID对应。

public async Task<bool> UpdatePerson(int id, Person person)
{
    var uri = Path.Combine(BASE_URI, id.ToString());

    var personJson = JsonConvert.SerializeObject(person);
    var content = new StringContent(personJson, Encoding.UTF8, "application/json");

    var result = await _httpClient.PutAsync(uri, content);

    return result.IsSuccessStatusCode;
}

正如您可以看到的,使用HTTP客户端实现Web服务非常简单,并且允许轻松为可以在平台共享的本机客户端创建后端集成。

UI.

在客户端,我们现在可以调用我们的便携式服务并显示一个人列表。

<Page
    x:Class="HttpClientSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HttpClientSample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    DataContext="{Binding Main, Source={StaticResource Locator}}"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <GridView ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson, Mode=TwoWay}"></GridView>

        <AppBar VerticalAlignment="Bottom">
            <AppBarButton Icon="Add" Label="Add" Command="{Binding AddNewPerson}" />
        </AppBar>
    </Grid>
</Page>

在ViewModel中,我们可以调用后端服务以提供数据。

public class MainViewModel:ViewModelBase
{
    private readonly IPersonService _personService;
    private ObservableCollection<Person> _people;
    private Person _selectedPerson;

    public MainViewModel(IPersonService personService)
    {
        if (personService == null) throw new ArgumentNullException(nameof(personService));
        _personService = personService;
        _people = new ObservableCollection<Person>();
        ShowPerson = person => { };
        AddNewPerson = new RelayCommand(() => ShowPerson(0));
    }

    public ObservableCollection<Person> People => _people;
    public Action<int> ShowPerson { get; set; }

    public Person SelectedPerson
    {
        get { return _selectedPerson; }
        set
        {
            if (_selectedPerson == value) return;
            _selectedPerson = value;
            RaisePropertyChanged(nameof(SelectedPerson));
            if (_selectedPerson != null) ShowPerson(_people.IndexOf(_selectedPerson));
        }
    }

    public ICommand AddNewPerson { get; private set; }

    public async Task Init()
    {
        var people = await _personService.GetPeople();

        _people.Clear();

        foreach (var person in people)
        {
            _people.Add(person);
        }
    }
}

添加一个人意味着我们必须将另一页添加到我们的客户端应用程序,为我们提供一种表单来输入一个人的名称和名字。为了保持简单的事情,如果我们必须在后端创建或更新该人,我们将使用相同的UI并通过导航参数识别。

<Page
    x:Class="HttpClientSample.PersonDetail"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HttpClientSample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    DataContext="{Binding Person, Source={StaticResource Locator}}"
    Loaded="PersonDetail_OnLoaded"
    mc:Ignorable="d">

    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBox Header="Firstname" Text="{Binding Firstname, Mode=TwoWay}"/>
        <TextBox Header="Lastname" Text="{Binding Lastname, Mode=TwoWay}"/>
        <Button Content="Save" Command="{Binding StoreCommand, Mode=TwoWay}" IsEnabled="{Binding HasPendingChanges}" HorizontalAlignment="Center" Margin="0,10"/>
    </StackPanel>
</Page>

ViewModel正在处理与之集成 personservice. 来自PCL库:

public class PersonViewModel:ViewModelBase
{
    private readonly IPersonService _personService;
    private int _id;
    private string _firstname;
    private string _lastname;
    private bool _hasPendingChanges;

    public PersonViewModel(IPersonService personService)
    {
        if (personService == null) throw new ArgumentNullException(nameof(personService));
        _personService = personService;

        StoreCommand = new RelayCommand(StorePerson, () => true);
        HasPendingChanges = false;
    }

    public bool HasPendingChanges
    {
        get { return _hasPendingChanges; }
        set
        {
            if (value == _hasPendingChanges) return;
            _hasPendingChanges = value;
            RaisePropertyChanged(nameof(HasPendingChanges));
        }
    }

    public string Firstname
    {
        get { return _firstname; }
        set
        {
            if (value == _firstname) return;
            _firstname = value;
            HasPendingChanges = true;
            RaisePropertyChanged(nameof(Firstname));
        }
    }

    public string Lastname
    {
        get { return _lastname; }
        set
        {
            if (_lastname == value) return;
            _lastname = value;
            HasPendingChanges = true;
            RaisePropertyChanged(nameof(Lastname));
        }
    }

    public ICommand StoreCommand { get; set; }

    public async Task Init(int id)
    {
        _id = id;
        var person = id > 0 ? (await _personService.GetPeople()).ToList()[id] : new Person("", "");
        Firstname = person.FirstName;
        Lastname = person.LastName;
        HasPendingChanges = false;
    }

    private async void StorePerson()
    {
        HasPendingChanges = false;

        var person = new Person(Firstname, Lastname);

        if (_id > 0)
        {
            await _personService.UpdatePerson(_id, person);
        }
        else
        {
            await _personService.CreatePerson(person);
        }

        HasPendingChanges = false;
    }
}

再次集成PCL库并不困难,不需要额外的箍跳过。我们通过将我们的逻辑放入PCL来获益的是,我们可以在多个平台中重复使用代码。

控制器

我们将重点关注在此帖子中的客户端,但为了了解这是我们将与之通信的控制器。

public class PersonController : ApiController
{
    private readonly static Lazy<IList<Person>> _people = new Lazy<IList<Person>>(() => new PersonService().GeneratePeople(1000), LazyThreadSafetyMode.PublicationOnly);
    // GET: api/Person
    public IEnumerable<Person> Get()
    {
        return _people.Value;
    }

    // GET: api/Person/5
    public Person Get(int id)
    {
        if (id < 0 || _people.Value.Count < id) return null;
        return _people.Value[id];
    }

    // POST: api/Person
    [HttpPost]
    public IHttpActionResult Post(Person value)
    {
        if (value == null) return BadRequest();

        _people.Value.Add(value);

        return CreatedAtRoute("DefaultApi", new { id = _people.Value.IndexOf(value) }, _people.Value.Last());
    }

    // PUT: api/Person/5
    [HttpPut]
    public IHttpActionResult Put(int id, [FromBody]Person value)
    {
        if (id < 0 || id >= _people.Value.Count()) return BadRequest();

        _people.Value[id] = value;

        return Ok(value);
    }

    // DELETE: api/Person/5
    public IHttpActionResult Delete(int id)
    {
        if (id < 0 || id >= _people.Value.Count()) return BadRequest();

        _people.Value.RemoveAt(id);

        return StatusCode(System.Net.HttpStatusCode.NoContent);
    }
}

internal class PersonService
{
    public IList<Person> GeneratePeople(int count)
    {
        var firstNames = new List<string>(count);
        var lastNames = new List<string>(count);

        for (int i = 0; i < count; ++i)
        {
            firstNames.Add(NameGenerator.GenRandomFirstName());
            lastNames.Add(NameGenerator.GenRandomLastName());
        }

        return firstNames.Zip(lastNames, (firstName, lastName) => new Person(firstName, lastName)).ToList();
    }
}

正如您所看到的,它是相当基本的,通过静态变量,我们有一些穷人数据库。不要在生产环境中使用这种存储数据的方式!

结论

在此帖子中,我们显示了如何在PCL中集成ASP.NET Web API Server。从而了解如何读取和写入Web服务的对象。此外,我们了解我们如何在Windows 10客户端上显示和输入数据。整个样本都可以写成Xamarin.ios,Xamarin.android或WPF,这正是在PCL中写出您的业务逻辑的甜蜜点,这无关您打算在现在或将来写的客户无关紧要PCL允许您集成多个客户端平台。

您可以找到整个示例项目 尼古特.

Updated: