如果涉及异步函数需要,仅在需要时初始化某些数据

时间:2016-11-09 15:50:01

标签: c# async-await singleton lazy-initialization

我的网站只有在应用需要时才需要初始化一些数据,而且必须只进行一次。初始化必须调用API来获取数据。

所以基本上,我认为我需要制作一个 lazy singleton ,它将在第一次访问时调用api。但是当我使用来自HttpClient类的异步方法GetAsync时,我遇到了问题。调用API时有些东西崩溃但我的TryCatch没有抓住异常。

我尝试将Piv GetPiv()转换为Task<Piv> GetPiv(),但我不知道如何将它与Lazy对象一起使用。

public class PivHelper
{
    private static readonly Lazy<Piv> _lazyPiv = new Lazy<Piv>(GetPiv);

    public string Header()
    {
        return _lazyPiv.Value.Header;
    }

    public string Footer()
    {
        return _lazyPiv.Value.Footer;
    }

    public string Scripts()
    {
        return _lazyPiv.Value.Scripts;
    }

    public string Styles()
    {
        return _lazyPiv.Value.Styles;
    }

    private static Piv GetPiv()
    {
        try
        {
            var client = new HttpClient();

            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            HttpResponseMessage response = client.GetAsync("http://some-web-site.com").Result;

            if (response.IsSuccessStatusCode)
            {
                return response.Content.ReadAsAsync<Piv>().Result;
            }
            else
            {
                return new Piv();
            }
        }
        catch (Exception ex)
        {
            return new Piv();
        }
    }
}

public class Piv
{
    public string Header { get; set; }
    public string Footer { get; set; }
    public string Styles { get; set; }
    public string Scripts { get; set; }
}

public static class MtoHelper
{
    private static readonly Lazy<PivHelper> lazyPivHelper = new Lazy<PivHelper>(() => new PivHelper());

    public static PivHelper Piv
    {
        get
        {
            return lazyPivHelper.Value;
        }
    }
}

我的_Layout.cshtml文件

<!DOCTYPE html>
<html>
<head>
    <!-- Instead of using our own copy of the piv.css, we use the one
         coming from the webservices -->
    <!--<link href="~/content/piv.css" rel="stylesheet" type="text/css">-->
    @MtoHelper.Piv.Styles()

    <link rel="shortcut icon" href="/favicon.ico" />
</head>

<body>
    <header id="pageHeaderContainer" class="container">
        @MtoHelper.Piv.Header()
    </header>

    <section id="main" class="container">
        @RenderBody()
    </section>

    <footer id="pageFooterContainer" class="container">
        @MtoHelper.Piv.Footer()
    </footer>

    @MtoHelper.Piv.Scripts()
</body>
</html>

1 个答案:

答案 0 :(得分:0)

您需要使用asyncawait关键字。使用.Result会导致问题。您可以实现如下所示的AsyncLazy类,这样可以确保GetPiv上的调用仅发生一次。

public class AsyncLazy<T> : Lazy<Task<T>>
{
    public AsyncLazy(Func<T> valueFactory) : base(() => Task.Run(valueFactory))
    {
    }

    public AsyncLazy(Func<Task<T>> taskFactory) : base(() => Task.Run(taskFactory))
    {
    }
}

然后,您必须更改代码才能使用上述关键字:

public class PivHelper
{
    static readonly AsyncLazy<Piv> _lazyPiv = new AsyncLazy<Piv>(() => GetPivAsync());

    public Task<Piv> PivTask => _lazyPiv.Value;

    static Task<Piv> GetPivAsync()
    {
        try
        {
            using (var client = new HttpClient())
            {
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                var response = await client.GetAsync("http://some-web-site.com");

                if (response.IsSuccessStatusCode)
                {
                    return await response.Content.ReadAsAsync<Piv>();
                }
                else
                {
                    return new Piv();
                }
            }
        }
        catch (Exception ex)
        {
            return new Piv();
        }
    }
}

public class Piv
{
    public string Header { get; set; }
    public string Footer { get; set; }
    public string Styles { get; set; }
    public string Scripts { get; set; }
}

然后在你的API代码中你可以使用帮助器(我做了一些关于将ASP.NET Core用于其Web API的假设):

public class PivController : Controller
{
    private readonly PivHelper _helper;

    public PivController (PivHelper pivHelper)
    {
        _helper = pivHelper;
    }

    public async Task<IActionResult> GetPiv()
    {
        var piv = await _helper.PivTask;
        // Act on the piv, or simply return it...
    }
}

<强>更新

根据OP编辑,我需要更新我的答案。

你错误地做了MVC。整个想法是根据SOLID等以下原则分离关注点并推广最佳实践。您的观点不应该了解PivHelper而是Piv模型,而您的控制器应该了解PivHelper as它是Piv

的提供者

至于要求将模型作为布局的一部分,这是一场不同的战斗。我建议不要这样做,并建议您使用最少量的元素将您的布局保留为.css的链接和.js的脚本标记。