一问三不知 https://www.sapze.com Wed, 15 Feb 2023 03:14:34 +0000 zh-CN hourly 1 https://wordpress.org/?v=4.9.22 一问三不知 https://www.sapze.com/net-core-new-repo.html Wed, 15 Feb 2023 03:14:34 +0000 https://www.sapze.com/?p=214 阅读全文 ]]> 在.net framework迁移到.net core的过程中,由于大量代码都是类似于 xxxRepo = new xxxRepo(),且在多线程中会造成连接丢失,所以暂定的修改方法为每次都直接UseSqlServer

public class BaseXXXContext: XXXContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                var envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
                IConfigurationRoot configuration = new ConfigurationBuilder()
                     .AddJsonFile("appsettings.json", optional: false)
                     .AddJsonFile($"appsettings.{envName}.json", optional: false)
                     .Build();

                optionsBuilder.UseSqlServer(configuration.GetConnectionString("XXXContext"));
            }
        }
    }
]]>
一问三不知 https://www.sapze.com/wcf-net-core-trace-log-and-credential.html Wed, 28 Dec 2022 15:37:46 +0000 https://www.sapze.com/?p=210 阅读全文 ]]> 日志记录

旧代码:

    public class TraceExtension : SoapExtension
    {
        Stream _oldStream;
        Stream _newStream;

        public override Stream ChainStream(Stream stream)
        {
            _oldStream = stream;
            _newStream = new MemoryStream();
            return _newStream;
        }

        public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return null;
        }

        public override object GetInitializer(Type WebServiceType)
        {
            return null;
        }

        public override void Initialize(object initializer)
        {

        }

        public override void ProcessMessage(SoapMessage message)
        {
            switch (message.Stage)
            {
                case SoapMessageStage.BeforeSerialize:
                    //TestRequest(message);
                    break;
                case SoapMessageStage.AfterSerialize:
                    WriteRequest(message);
                    break;
                case SoapMessageStage.BeforeDeserialize:
                    WriteResponse(message);
                    break;
                case SoapMessageStage.AfterDeserialize:
                    break;
                default:
                    throw new Exception("invalidstage");
            }
        }

        public void WriteRequest(SoapMessage message)
        {
            try
            {
                _newStream.Position = 0;

                TextReader reader = new StreamReader(_newStream);
                string methodName = message.MethodInfo.ToString();
                string reqLog = reader.ReadToEnd();

                //@@ Async log
                Task t = Task.Factory.StartNew(() =>
                {
                    try
                    {

                        WebSvcLogger.Logger.Information(string.Format("API - \"{0}\" Request:\r\n{1}", methodName, reqLog));
                    }
                    catch
                    {
                        //Log Failed
                    }
                });
            }
            catch
            {
                //Log Failed
            }
            finally
            {
                _newStream.Position = 0;
                Copy(_newStream, _oldStream);
            }
        }

        public void WriteResponse(SoapMessage message)
        {
            try
            {
                Copy(_oldStream, _newStream);
                _newStream.Position = 0;

                TextReader reader = new StreamReader(_newStream);
                string methodName = message.MethodInfo.ReturnType.FullName;                
                string respLog = reader.ReadToEnd();

                //Async run
                Task t = Task.Factory.StartNew(() =>
                {                    
                    WebSvcLogger.Logger.Information(string.Format("API - \"{0}\" Response:\r\n{1}", methodName, respLog));
                });
            }
            catch
            {
                //Log Failed
            }
            finally
            {
                _newStream.Position = 0;
            }
        }

        void Copy(Stream from, Stream to)
        {
            TextReader reader = new StreamReader(from);
            TextWriter writer = new StreamWriter(to);
            writer.WriteLine(reader.ReadToEnd());
            writer.Flush();
        }
    }

    [AttributeUsage(AttributeTargets.Method)]
    public class TraceExtensionAttribute : SoapExtensionAttribute
    {
        private int priority;

        public override Type ExtensionType
        {
            get { return typeof(TraceExtension); }
        }

        public override int Priority
        {
            get { return priority; }
            set { priority = value; }
        }
    }

新代码:

    public class HttpProtocol : IClientMessageInspector
    {

        private readonly string _serviceName;
        public HttpProtocol(string serviceName)
        {
            _serviceName = serviceName;
        }

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            var buffer = reply.CreateBufferedCopy(Int32.MaxValue);
            reply = buffer.CreateMessage();

            WriteResponse(buffer.CreateMessage().CreateBufferedCopy(Int32.MaxValue));
        }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            var buffer = request.CreateBufferedCopy(Int32.MaxValue);
            request = buffer.CreateMessage();

            WriteRequest(buffer.CreateMessage().CreateBufferedCopy(Int32.MaxValue));
            return null;
        }
        public Message WriteRequest(MessageBuffer buffer)
        {
            Message msg = buffer.CreateMessage();
            StringBuilder sb = new StringBuilder();
            sb.Append(msg.ToString());

            string reqLog = sb.ToString();

            //@@ Async log
            Task t = Task.Factory.StartNew(() =>
            {
                try
                {

                    WebSvcLogger.Logger.Information(string.Format("API - \"{0}\" Request:\r\n{1}", _serviceName, reqLog));
                }
                catch
                {
                    //Log Failed
                }
            });
            return buffer.CreateMessage();
        }

        public Message WriteResponse(MessageBuffer buffer)
        {
            Message msg = buffer.CreateMessage();
            StringBuilder sb = new StringBuilder();
            sb.Append(msg.ToString());

            string respLog = sb.ToString();

            //Async run
            Task t = Task.Factory.StartNew(() =>
            {
                WebSvcLogger.Logger.Information(string.Format("API - \"{0}\" Response:\r\n{1}", _serviceName, respLog));
            });
            return buffer.CreateMessage();
        }
        private static Message Clone(Message message)
        {
            return message.CreateBufferedCopy(int.MaxValue).CreateMessage();
        }
    }

    public class TraceMessageBehavior : IEndpointBehavior
    {
        private readonly string _serviceName;
        public TraceMessageBehavior(string serviceName)
        {
            _serviceName = serviceName;
        }

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        { }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.ClientMessageInspectors.Add(new HttpProtocol(_serviceName));
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {

        }

        public void Validate(ServiceEndpoint endpoint)
        { }
    }

证书问题

旧方法:

    SearchServiceBinding client = new SearchServiceBinding();
    client.Url = "http://localhost/";

    //Set Basic Auth Authorization
    client.Credentials = new NetworkCredential(config.Username, config.Password);

    // Send the request
    var resp = client.service(req);

    return resp;

新方法:

    var binding = new BasicHttpBinding();
    binding.Security.Mode = BasicHttpSecurityMode.Transport;
    binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

    var address = "http://localhost/";

    ServicePortTypeClient client = new ServicePortTypeClient(binding, address);
    client.Endpoint.EndpointBehaviors.Add(new TraceMessageBehavior("Search"));

    //Set Basic Auth Authorization
    client.ClientCredentials.UserName.UserName = config.Username;
    client.ClientCredentials.UserName.Password = config.Password;

    // Send the request
    var resp = await client.serviceAsync(req);

    return resp;
]]>
一问三不知 https://www.sapze.com/axios-get-cross-origin.html Tue, 08 Feb 2022 07:25:53 +0000 https://www.sapze.com/?p=204 阅读全文 ]]> axios发送get请求报跨域的错误,用ajax的get方法不报错

axios的get请求报错是因为底层的实现方式跟jquery的ajax有所区别,jquery ajax的headers默认设置的content-type为application/x-www-form-urlencoded,axios默认为 application/json,而且axios会发送一个preflight来判断api是否可用,解决方式是修改axios的get请求的content-type,修改代码如下:

axios.interceptors.request.use(
    config => {
      if (config.method === 'get') {
        config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
        return config
      }})
]]>
一问三不知 https://www.sapze.com/entity-framework-dbconfiguration.html Mon, 12 Jul 2021 16:42:37 +0000 https://www.sapze.com/?p=201 阅读全文 ]]> using System.Data.Entity.ModelConfiguration.Configuration; using System.Linq; using System.Collections.Generic; namespace TestCase.EntityFramework { public class TestCaseDBConfig : DbConfiguration { private static string EFFoldPath = ""; public TestCaseDBConfig() : base() { SetProviderServices("System.Data.SqlClient", System.Data.Entity.SqlServer.SqlProviderServices.Instance); EFFoldPath = AppDomain.CurrentDomain.BaseDirectory + "App_Data\\EFCache"; if (!Directory.Exists(EFFoldPath)) Directory.CreateDirectory(EFFoldPath); TestCaseDbModelStore cachedDbModelStore = new TestCaseDbModelStore(EFFoldPath); IDbDependencyResolver dependencyResolver = new SingletonDependencyResolver<DbModelStore>(cachedDbModelStore); AddDependencyResolver(dependencyResolver); } private class TestCaseDbModelStore : DefaultDbModelStore { public TestCaseDbModelStore(string location) : base(location) { } public override XDocument TryGetEdmx(Type contextType) { return LoadXml2(contextType, XDocument.Load); } private void GetConceptualAssociationNames(List<System.Data.Entity.Core.Metadata.Edm.AssociationType> lstAssociationTypes, List<string> lstNames) { int oldCount = lstNames.Distinct().ToList().Count; foreach (var association in lstAssociationTypes) { if (association.AssociationEndMembers != null && association.AssociationEndMembers.Count > 0) { var isAssociaEsixt = association.AssociationEndMembers.Where(a => lstNames.Contains(a.GetEntityType().Name)).FirstOrDefault() != null; if (isAssociaEsixt) { if (!lstNames.Contains(association.Name)) lstNames.Add(association.Name); foreach (var endMember in association.AssociationEndMembers) { if (!lstNames.Contains(endMember.GetEntityType().Name)) lstNames.Add(endMember.GetEntityType().Name); } } } } if (oldCount != lstNames.Distinct().Count()) { GetConceptualAssociationNames(lstAssociationTypes, lstNames); } } public override void Save(Type contextType, DbModel model) { if (contextType.GenericTypeArguments != null && contextType.GenericTypeArguments.Length > 0) { string name = contextType.GenericTypeArguments[0].Name; List<string> storeModelNames = new List<string>(); storeModelNames.Add(name); storeModelNames.Add("Entities"); #region StoreModel List<string> storeModelAssociates = new List<string>(); if (model.StoreModel.AssociationTypes != null && model.StoreModel.AssociationTypes.Count() > 0) { GetConceptualAssociationNames(model.StoreModel.AssociationTypes.ToList(), storeModelNames); var removedStoreAssociates = model.StoreModel.AssociationTypes.Where(a => !storeModelNames.Contains(a.Name)).ToList(); foreach (var removedAssociate in removedStoreAssociates) { var tmp = model.StoreModel.AssociationTypes.Where(a => a.Name == removedAssociate.Name).FirstOrDefault(); if (tmp != null) { model.StoreModel.RemoveItem(tmp); } } } storeModelNames = storeModelNames.Distinct().ToList(); var removedStoreModels = model.StoreModel.EntityTypes.Where(a => !storeModelNames.Contains(a.Name)).ToList(); foreach (var removedStoreModel in removedStoreModels) { var tmp = model.StoreModel.EntityTypes.Where(a => a.Name == removedStoreModel.Name).FirstOrDefault(); if (tmp != null) { model.StoreModel.RemoveItem(tmp); } } var removeEntitySetBase = model.StoreModel.Container.BaseEntitySets.Where(a => !storeModelNames.Contains(a.ElementType.Name)).ToList(); foreach (var removedStoreModel in removeEntitySetBase) { var tmp = model.StoreModel.Container.BaseEntitySets.Where(a => a.Name == removedStoreModel.Name).FirstOrDefault(); if (tmp != null) { model.StoreModel.Container.RemoveEntitySetBase(tmp); } } #endregion #region ConceptualModel List<string> conceptualAssociates = new List<string>(); if (model.ConceptualModel.AssociationTypes != null && model.ConceptualModel.AssociationTypes.Count() > 0) { GetConceptualAssociationNames(model.ConceptualModel.AssociationTypes.ToList(), storeModelNames); var removedStoreAssociates = model.ConceptualModel.AssociationTypes.Where(a => !storeModelNames.Contains(a.Name)).ToList(); foreach (var removedAssociate in removedStoreAssociates) { var tmp = model.ConceptualModel.AssociationTypes.Where(a => a.Name == removedAssociate.Name).FirstOrDefault(); if (tmp != null) { model.ConceptualModel.RemoveItem(tmp); } } } storeModelNames = storeModelNames.Distinct().ToList(); var conceptualStoreModels = model.ConceptualModel.EntityTypes.Where(a => !storeModelNames.Contains(a.Name)).ToList(); foreach (var removedStoreModel in conceptualStoreModels) { var tmp = model.ConceptualModel.EntityTypes.Where(a => a.Name == removedStoreModel.Name).FirstOrDefault(); if (tmp != null) { model.ConceptualModel.RemoveItem(tmp); } } var removeConcepEntitySetBase = model.ConceptualModel.Container.BaseEntitySets.Where(a => !storeModelNames.Contains(a.ElementType.Name)).ToList(); foreach (var removedStoreModel in removeConcepEntitySetBase) { var tmp = model.ConceptualModel.Container.BaseEntitySets.Where(a => a.Name == removedStoreModel.Name).FirstOrDefault(); if (tmp != null) { model.ConceptualModel.Container.RemoveEntitySetBase(tmp); } } #endregion #region Mapping if (model.ConceptualToStoreMapping != null && model.ConceptualToStoreMapping.EntitySetMappings != null && model.ConceptualToStoreMapping.EntitySetMappings.Count() > 0) { List<System.Data.Entity.Core.Mapping.EntitySetMapping> lstEntitySetMappings = new List<System.Data.Entity.Core.Mapping.EntitySetMapping>(); bool isMappingExists = false; foreach (var mapping in model.ConceptualToStoreMapping.EntitySetMappings) { isMappingExists = false; if (mapping.EntityTypeMappings != null && mapping.EntityTypeMappings.Count > 0) { foreach (var subMapping in mapping.EntityTypeMappings) { if (subMapping.EntityTypes != null && subMapping.EntityTypes.Count > 0) { foreach (var ef in subMapping.EntityTypes) { if (!storeModelNames.Contains(ef.Name)) { lstEntitySetMappings.Add(mapping); isMappingExists = true; break; } } } if (isMappingExists) break; } } } lstEntitySetMappings = lstEntitySetMappings.Distinct().ToList(); if (lstEntitySetMappings.Count > 0) { foreach (var tmpMapping in lstEntitySetMappings) { var tmp = model.ConceptualToStoreMapping.EntitySetMappings.Where(a => a == tmpMapping).FirstOrDefault(); if (tmp != null) { model.ConceptualToStoreMapping.RemoveSetMapping(tmp); } } } } #endregion } using (var writer = XmlWriter.Create(GetFilePath2(contextType), new System.Xml.XmlWriterSettings { Indent = true })) { EdmxWriter.WriteEdmx(model, writer); } } public string GetFilePath2(Type contextType) { var filename = (contextType.GenericTypeArguments.Length > 0 ? contextType.GenericTypeArguments[0].Name : contextType.FullName) + ".edmx"; string path = Path.Combine(EFFoldPath, filename); return path; } public override DbCompiledModel TryLoad(Type contextType) { string path = GetFilePath2(contextType); if (File.Exists(path)) { DateTime lastWriteTime = File.GetLastWriteTimeUtc(path); DateTime lastWriteTimeDomainAssembly = File.GetLastWriteTimeUtc(contextType.Assembly.Location); if (lastWriteTimeDomainAssembly > lastWriteTime) { File.Delete(path); } } return LoadXml2(contextType, reader => { var defaultSchema = GetDefaultSchema(contextType); return EdmxReader.Read(reader, defaultSchema); }); } internal T LoadXml2<T>(Type contextType, Func<XmlReader, T> xmlReaderDelegate) { var filePath = GetFilePath2(contextType); if (!File.Exists(filePath)) { return default(T); } if (!FileIsValid2(contextType, filePath)) { File.Delete(filePath); return default(T); } using (var reader = XmlReader.Create(filePath)) { return xmlReaderDelegate(reader); } } public bool FileIsValid2(Type contextType, string filePath) { var contextCreated = File.GetLastWriteTimeUtc(contextType.Assembly.Location); var storeCreated = File.GetLastWriteTimeUtc(filePath); return storeCreated >= contextCreated; } } } } ]]> 一问三不知 https://www.sapze.com/chrome-content-download.html Sat, 09 May 2020 09:57:02 +0000 https://www.sapze.com/?p=196 阅读全文 ]]> 项目中使用uedbet下载功能时,要求api返回关键字uedbet下载的所有数据,最多可能有一万条左右,导致接口返回的数据渲染到页面需要很长的时间,使用chrome调试可以看到具体的加载时间。

前端使用vuejs加载数据,解决办法就是不用等到所有数据加载完成,类似下载功能一样,下载一部分就加载一部分数据,我们在onDownloadProgress里面返回已经下载的数据就行了,如下:
调用接口:

search (keywords, doAction) {
    cancelRequest()
    const data = {
      keywords: keywords
    }
    return new Promise((resolve, reject) => {
      axios({
        method: 'POST',
        url: 'search',
        data: data,
        dataType: 'json',
        cancelToken: new CancelToken(function executor (c) {
          cancel = c
        }),
        onDownloadProgress (progress) {
          doAction(progress.currentTarget.response)
        }
      })
        .then(response => {
          // resolve(response)
        })
        .catch(() => {})
    })
  }

处理数据:

hotel.search(key, function (json) {
    // 返回的数据可能不完整,所以需要替换一些特殊字符,让整个字符串可以转成json
            var willLoadJsonStr = json
              .replace('{"status":"Success","message":"","data":{"hotels":[', '')
              .replace(']}}', '')
            // 分割字符串,让每一段字符串都是一个对象
            var destArr = willLoadJsonStr.split('},{')
            for (var di = 0; di < destArr.length; di++) {
              var destStr = (di == 0 ? '' : '{') + destArr[di] + '}'
              if (root.isJSON(destStr)) {
                let destJson = JSON.parse(destStr)
                // 跳过已经添加的数据
                if (root.lstProperties.filter(a => a.hotelId == destJson.hotelId).length > 0) {
                  continue
                }

                root.lstProperties.push(destJson)
                root.setDestSectionElement(key, destJson)
              }
            }
          })
          .then(function (json) {
            // console.log(json)
          })
          .catch(error => {
          });

这样处理之后不管api返回多大的数据,页面都可以很快的响应。

]]>
一问三不知 https://www.sapze.com/20200216.html Sun, 16 Feb 2020 08:04:38 +0000 https://www.sapze.com/?p=193 gulp项目中偶尔不能自动生成js文件,需要使用tsc手动生成文件
tsc --build tsconfigs/Common/tsconfig.json

]]>
一问三不知 https://www.sapze.com/net-core-the-signature-key-was-not-found.html Tue, 24 Dec 2019 10:26:33 +0000 https://www.sapze.com/?p=188 阅读全文 ]]> .net core使用OAuth进行验证,本地调试的时候调用接口一切正常,但是发布到IIS测试的时候一直提示401 the signature key was not found。
经过检查发现是“tempkey.rsa”文件的问题, service.AddDeveloperSigningCredential()会自动生成一个私钥文件,但是发布到IIS的时候由于域名、端口等原因,调用接口会重新生成key,造成登录生成的access_token在其它api不可用,解决方法也很简单:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options => {
        options.Authority = "http://192.168.1.1"; // 这里要是具体访问的域名
        options.Audience = "api";
        options.RequireHttpsMetadata = false;
    });

重新发布后删除根目录的 tempkey.rsa 文件,重启IIS即可。

]]>
一问三不知 https://www.sapze.com/semantic-ui-dropdown.html Thu, 18 Jul 2019 03:31:36 +0000 https://www.sapze.com/?p=179 阅读全文 ]]> 由于项目需要对dropdownuedbet下载结果进行高亮处理,但是目前的semantic ui没有对应的接口可以调用,所以不得不对源码进行一些修改,这里只用作记录,避免将来代码被覆盖的时候找不回修改的部分。

需求如下:

semantic-ui的dropdown源码在/semantic/src/definitions/modules/dowpdown.js.

我们在第805行添加处理字符串的代码:

// 上一个uedbet下载条件的html标签对当前字符串有影响,需要清理上一个的结果
var originalHtml = $(this).html().toString()
                                  .replace(/<strong>/g, '')
                                  .replace(/<\/strong>/g, '');
// 忽略大小和html标签,替换uedbet下载的字符串,并加上特殊的html,我这里用的是strong
var newHtml = originalHtml.replace(new RegExp(searchTerm + "(?![^<>]*>)", "ig"), function(e){
  return '<strong>' + e + '</strong>';
});
// 替换当前的html,由于添加了strong标签,所以下一次uedbet下载需要清理掉对应的标签
$(this).html(newHtml);

注意第794行的条件,if( module.has.query() ),也就是清空uedbet下载内容后,这部分为空,之前的加粗标签在显示所有下拉选项的时候并没有被清理,所以需要在之后添加else处理没有uedbet下载内容的情况。

// 方法很简单,遍历所有item并删掉strong标签即可
$item
  .each(function(){
    var defaultText = $(this).html().toString()
                      .replace(/<strong>/g, '')
                      .replace(/<\/strong>/g, '');
    $(this).html(defaultText)

    return true;
  })
;

另外,由于项目中dropdown进行了分组显示,uedbet下载结果中如果itemgroup没有对应的内容,需要对itemgroup进行隐藏处理。

$item
  .not(results)
  .length === $item.length
  ? $item.parent().addClass(className.filtered)  // 如果uedbet下载结果为空,itemgroup添加filtered的样式
  : $item.parent().removeClass(className.filtered)  // 如果uedbet下载结果不为空,itemgroup删除filtered样式
;

项目中的html格式如下,具体的显示样式需要单独处理,比如strong标签取消加粗、修改颜色等:

<div class="ui selection dropdown search">
    <div class="text">Click to search</div>
    <div class="menu">
        <div class="itemgroup">
            <label>San Francisco</label>
            <div class="item">
                <span>San Francisco, International Airport</span>
                <span>SFO</span>
            </div>
            <div class="item">
                <span>Normal Y. Mineta San Jose International Airport</span>
                <span>SJC</span>
            </div>
        </div>
        <div class="itemgroup">
            <label>San Francisco</label>
            <div class="item">
                <span>San Francisco, International Airport</span>
                <span>SFO</span>
            </div>
            <div class="item">
                <span>Normal Y. Mineta San Jose International Airport</span>
                <span>SJC</span>
            </div>
        </div>
    </div>
</div>
]]>
一问三不知 https://www.sapze.com/net-core-vue-ssr.html Thu, 20 Jun 2019 08:44:36 +0000 https://www.sapze.com/?p=173 阅读全文 ]]> 这里不再重复vue的服务端渲染逻辑,只记录一点.net core的问题

HomeController.cs

public async Task<IActionResult> Index()
{
    string path = Request.Path.Value.Trim().ToString();
    var r = await _nodeServices.InvokeAsync<string>(@"server.js", path).ConfigureAwait(false);
    return Content(r, "text/html");
}

Server.js

const fs = require('fs');
const { createBundleRenderer } = require('vue-server-renderer')

const renderer = createBundleRenderer(require('./vue-ssr-server-bundle.json'), {
    runInNewContext: false,
    template: fs.readFileSync('./index.html', 'utf-8'),
    clientManifest: require('./vue-ssr-client-manifest.json')
})

function renderToString(context) {
    //var c = JSON.parse(context.replace(/\\"/g, ""));
    //return JSON.stringify(context);
    return new Promise((resolve, reject) => {
        renderer.renderToString(context, (err, html) => {
            resolve(html);
        })
    })
}

module.exports = async (callback, data) => {
    const url = data.replace(/\\"/g, "");
    var title = 'Home';
    var d = {
        title: title,
        url: url,
        meta: '<meta name="description" content="desc" />'
    }
    var k = await renderToString(d);
    callback(null, k.toString());
}

代码其实很简单,但部署网站的时候还是遇到了一些问题,首先服务器上的.net core版本必须与vs发布所选的版本一致,否则会提示服务器500错误。
我在代码中使用了await,导致页面出现错误的时候不能及时返回,页面会显示超时,callback里面promise返回的结果添加了toString()的方法,是为了避免返回结果可能为 object object 不能由c#解析为string:“_nodeServices.InvokeAsync”。

]]>
一问三不知 https://www.sapze.com/yzjxhdfsgys.html Wed, 06 Mar 2019 15:47:58 +0000 https://www.sapze.com/?p=161 阅读全文 ]]> 《以自己喜欢的方式过一生》书评

-- 追风少女

有人说这本书:

林特特说:“这世界上只有一种成功,就是以自己喜欢的方式过一生。”

王小波说:“我对自己的要求很低,我活在世上,无非想要明白些道理,遇见些有趣的事。倘能如我所愿,我的一生就算成功。”

杨绛说:“我们曾如此渴望命运的波澜,到最后才发现:人生最曼妙的风景,竟是内心的淡定与从容……我们曾如此期盼外界的认可,到最后才知道:世界是自己的,与他人毫无关系。”

关于本书:
  没有写远大的理想,锦绣的前程。
  作者把生活里的拧巴、纠结一一融化在淡淡的笔尖。写那些最朴素的人、最朴素的生活。偏偏让你嗅到理想应该有的味道。
  合上书,你也许会忽然发现,一直误解了理想的意义,生活不应只是为了周遭的人对自己满意而已。

一点想说的:
  这本书是学生时代收到的一份生日礼物。
  依稀记得那是一个很忙的阴天,学生会的工作忙的焦头烂额,迎新晚会的贺卡还没做出来,抱着一堆颜料陪学弟学妹排练了一下午,终于把贺卡赶出来了个七七八八。
  回宿舍已经快九点了,买了一碗加荷包蛋的清汤面,正在愉快的吸溜,腿腿儿提着个纸袋子进来对我晃了晃说生日快乐宝宝。
  于是就有了这本书。
  多数时候它用来垫枕头,但是也有偶尔,在繁事绕心头或者在某个坏情绪爆炸的时刻,会在遮光帘里打开小夜灯读一读。

  封面提出的核心思想“要有最朴素的生活和最遥远的梦想”,在这个快节奏的浮躁时代应该已经不适用了吧。
  只是反复咀嚼,字里行间,不经意会流露一种平淡和满足,仿佛我们在另一个平行世界,都是多金高颜值的超级英雄。
  如果说那个时候的我喜欢的是这本书的名字,那么现在的我是由衷喜欢书里面的故事了。
  54个人生最温暖的瞬间里,我们渐渐明白,平凡的一生不代表碌碌无为;变得成熟也不意味着要丢掉初心。就算怀揣世上最伟大的梦想,也不妨碍我们得到一个普通人的快乐。


文章来自:2019.02.26 风风(QQ音乐电台-追风少女)

]]>