c# - 将 HTML 抓取到对象,然后使用 AutoMapper 进行建模

标签 c#

我很确定我所讨论的这个过程有很多缺陷,所以请随意指出您在此过程中看到的任何内容。

我收到一封传入的 HTML 电子邮件,需要从中抓取大量信息。我将这些信息放入一个非常简单的列表中,但随后我想将其映射到一个特定的模型中,然后我可以使用该模型。

HTML 电子邮件如下所示:

<tr>
    <td>Name: </td>
    <td>Larry Smith</td>
</tr>
<tr>
    <td>Email: </td>
    <td><a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="87ebe6f5f5fec7e4f2e4f2eae5e2f5a9e4e8ea" rel="noreferrer noopener nofollow">[email protected]</a></td>
</tr>
<tr>
    <td>Phone: </td>
    <td>(101)123-4567 (Mobile)</td>
</tr>

我使用 HTMLAgilityPack 将其解析为这个对象:

public class RawData
{
    public string Name { get; set; }
    public string Value { get; set; }
}

因此解析后的对象如下所示

我的模型非常相似,除了属性实际上命名为非通用:

public class FullModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
     ...etc....
 }

现在我们到达了问题的症结所在...尝试使用 AutoMapper 构建 map 以将所有内容对齐。

Mapper.CreateMap<RawData, FullData>()
  .ForMember(dest => dest.FullName,
  opts => opts.MapFrom(src => src.Name));

但是当然......RawData 对象中有大约 80 个属性是字段......并且 RawData 是一个要启动的列表。

我是否应该放弃 RawData 对象和映射,而是在电子邮件抓取解析方法中找到它所属的 FullData 属性并填充它?

欢迎其他想法和意见

谢谢

最佳答案

认为我理解这一点,并且希望您拥有的代码只是您尝试过的一种方法(但显然没有按预期工作)。话虽如此:

从我的角度来看,这看起来像是您想要 create a custom ValueResolver 来为该属性找到适当的“ key ”。像这样的东西:

class RawDataNameResolver : ValueResolver<IEnumerable<RawData>, String>
{
    private String _name;

    public RawDataNameResolver(String name)
    {
        _name = name;
    }

    protected override String ResolveCore(IEnumerable<RawData> source)
    {
        if (source != null)
        {
            var match = source.SingleOrDefault(x => x.Name == _name);
            if (match != null)
            {
                return match.Value;
            }
        }
        return null;
    }
}

这使其非常通用,因此您可以在映射中重复使用它:

Mapper.CreateMap<IEnumerable<RawData>, FullModel>()
    .ForMember(d => d.Email, m => m.ResolveUsing(new RawDataNameResolver("Email: ")))
    .ForMember(d => d.Name, m => m.ResolveUsing(new RawDataNameResolver("Name: ")))
    .ForMember(d => d.PhoneNumber, m => m.ResolveUsing(new RawDataNameResolver("Phone: ")));

(请注意我如何传递“名称”值,以便 e 可以执行比较并接收回“值”)。

这将提供:

var rawData = new[]{ // pretend this just read an email ;-)
  new RawData { Name = "Name: ", Value = "Larry Smith" },
  new RawData { Name = "Email: ", Value = "<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="afc3ceddddd6efccdaccdac2cdcadd81ccc0c2" rel="noreferrer noopener nofollow">[email protected]</a>" },
  new RawData { Name = "Phone: ", Value = "(101)123-4567 (Mobile)" }
}
var fullModel = Mapper.Map<FullModel>(rawData);

结果是:

new FullModel {
  Email = "<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="8ce0edfefef5cceff9eff9e1eee9fea2efe3e1" rel="noreferrer noopener nofollow">[email protected]</a>",
  Name = "Larry Smith",           // Yes, I've combined them!
  PhoneNumber = "(101)123-4567 (Mobile)
};

现在,如果您想要名字、姓氏,您可以进一步修改 ValueResolver ,以便执行一些后处理。也许是这样的:

class RawDataNameResolver : ValueResolver<IEnumerable<RawData>, String>
{
    private String _name;
    private Func<String, String> _postProcessor;

    public RawDataNameResolver(String name, Func<String, String> postProcessor = null)
    {
        _name = name;
        _postProcessor = postProcessor;
    }

    protected override String ResolveCore(IEnumerable<RawData> source)
    {
        if (source != null)
        {
            var match = source.SingleOrDefault(x => x.Name == _name);
            if (match != null)
            {
                return _postProcessor != null ? _postProcessor(match.Value) : match.Value;
            }
        }
        return null;
    }
}

(注意我添加了一个后处理器,这样我们就可以在获得值后对其进行操作)

这改变了我们的映射过程(但只是轻微改变):

Mapper.CreateMap<IEnumerable<RawData>, FullModel>()
    .ForMember(d => d.Email, m => m.ResolveUsing(new RawDataNameResolver("Email: ")))
    .ForMember(d => d.FirstName, m => m.ResolveUsing(new RawDataNameResolver("Name: ", x => x.Split()[0])))
    .ForMember(d => d.LastName, m => m.ResolveUsing(new RawDataNameResolver("Name: ", x => x.Split()[1])))
    .ForMember(d => d.PhoneNumber, m => m.ResolveUsing(new RawDataNameResolver("Phone: ")));

现在的结果是:

new FullModel {
  Email = "<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="bbd7dac9c9c2fbd8ced8ced6d9dec995d8d4d6" rel="noreferrer noopener nofollow">[email protected]</a>",
  FirstName = "Larry",
  LastName = "Smith",
  PhoneNumber = "(101)123-4567 (Mobile)
};

同样的方法也适用于解析 PhoneNumber(删除 (mobile) 或只是对其进行标准化)。另外,请记住这还不是“生产就绪”。我强烈建议您不要只使用 .Split()[0] 并将其分解为更安全的东西(就像我没有执行检查的其他地方一样)。

关于c# - 将 HTML 抓取到对象,然后使用 AutoMapper 进行建模,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29956538/

相关文章:

c# - 从时区日期时间转换为短日期 C#

c# - Winforms TabControl 对齐问题

c# - 比较两个不同数据库的所有字段: Whats the way to go?

c# - 从 C# 中的对象类型获取数据

c# - 在 WinPhone 中使用 PCL 中的 System.ComponentModel.DataAnnotations 就像 "microsoft bcl portability pack"一样?

c# - 如何将 processid 添加到 log4net 布局?

c# - 通过提供代理 token 处理来自 webapi 的长承载 token

c# - 将 Expression<Func> 选择器绑定(bind)到 linq 查询

c# - 带有 NAudio 和 WPFSoundVisualizationLib 的频谱分析仪

c# - Varbinary 最大图像