我将如何在 sensenet 中实施公司简介?

How would I implement a company profile in sensenet?

当用户在我的网络应用程序中注册时,他们必须输入三项内容:他们的用户名、公司名称和密码。现在,我接下来要如何处理这些信息完全取决于我如何实施它。理想情况下,我希望用户有两个配置文件:用户和公司配置文件。 Sensenet 已经带有内置的用户配置文件并查看内容类型定义,这就是它的定义方式。

<?xml version="1.0" encoding="utf-8"?>
<ContentType name="UserProfile" parentType="Workspace" handler="SenseNet.ContentRepository.UserProfile" xmlns="http://schemas.sensenet.com/SenseNet/ContentRepository/ContentTypeDefinition">
  <DisplayName>$Ctd-UserProfile,DisplayName</DisplayName>
  <Description>$Ctd-UserProfile,Description</Description>
  <Icon>UserProfile</Icon>
  <AllowedChildTypes>
    Blog,DocumentLibrary,EventList,MemoList,LinkList,TaskList,ImageLibrary,Posts,CustomList
  </AllowedChildTypes>
  <Fields>
    <Field name="IsWallContainer" type="Boolean">
      <Configuration>
        <VisibleBrowse>Advanced</VisibleBrowse>
        <VisibleEdit>Advanced</VisibleEdit>
        <VisibleNew>Show</VisibleNew>
        <DefaultValue>true</DefaultValue>
      </Configuration>
    </Field>
    <Field name="IsCritical" type="Boolean">
      <Configuration>
        <VisibleBrowse>Hide</VisibleBrowse>
        <VisibleEdit>Hide</VisibleEdit>
        <VisibleNew>Hide</VisibleNew>
      </Configuration>
    </Field>
    <Field name="Manager" type="Reference">
      <Configuration>
        <VisibleBrowse>Hide</VisibleBrowse>
        <VisibleEdit>Hide</VisibleEdit>
        <VisibleNew>Hide</VisibleNew>
      </Configuration>
    </Field>
    <Field name="Deadline" type="DateTime">
      <Configuration>
        <VisibleBrowse>Hide</VisibleBrowse>
        <VisibleEdit>Hide</VisibleEdit>
        <VisibleNew>Hide</VisibleNew>
      </Configuration>
    </Field>
    <Field name="IsActive" type="Boolean">
      <Configuration>
        <VisibleBrowse>Hide</VisibleBrowse>
        <VisibleEdit>Hide</VisibleEdit>
        <VisibleNew>Hide</VisibleNew>
      </Configuration>
    </Field>
    <Field name="User" type="Reference">
      <DisplayName>$Ctd-UserProfile,User-DisplayName</DisplayName>
      <Configuration>
        <AllowMultiple>false</AllowMultiple>
        <AllowedTypes>
          <Type>User</Type>
        </AllowedTypes>
        <SelectionRoot>
          <Path>/Root/IMS</Path>
        </SelectionRoot>
      </Configuration>
    </Field>
  </Fields>
</ContentType>

引起我注意的是引用附加到用户配置文件的用户的引用字段。因此,有了这些知识,也许最好创建一个单独的内容类型定义,其中包含与此相同的引用字段,以便它与同一用户相关联。我可以走的另一个方向是扩展用户配置文件内容类型定义 xml 文件,将公司的信息嵌入其中,而不是为其单独定义文件,但问题是现在我要改变关于什么是用户个人资料,可能包含太多信息。

最重要的是,任何其他用户都不能绑定到同一家公司,因为它必须是唯一的。考虑到所有这些,您认为以下哪种方法是执行此操作的最佳方法?

编辑 1

所以我在为用户制作公司简介方面取得了一些进展。我为公司简介创建了这个内容处理程序。

using SenseNet.ContentRepository.Schema;
using SenseNet.ContentRepository.Storage;
using SenseNet.ContentRepository.Workspaces;

namespace DerAssistantService.ContentHandlers
{
    [ContentHandler]
    public class CompanyProfile : Workspace
    {
        public CompanyProfile(Node parent) : this(parent, null) { }

        public CompanyProfile(Node parent, string nodeTypeName) : base(parent, nodeTypeName) { }

        protected CompanyProfile(NodeToken nt) : base(nt) { }

        public override string Name
        {
            get { return base.Name;}
            set { base.Name = value;}
        }

        [RepositoryProperty("Address")]
        public string Address
        {
            get { return GetProperty<string>("Address"); }
            set { this["Address"] = value; }
        }

        [RepositoryProperty("City")]
        public string City
        {
            get { return GetProperty<string>("City"); }
            set { this["City"] = value; }
        }

        [RepositoryProperty("State")]
        public string State
        {
            get { return GetProperty<string>("State"); }
            set { this["State"] = value; }
        }

        public override object GetProperty(string name)
        {
            switch (name)
            {
                case "Address":
                    return Address;
                case "City":
                    return City;
                case "State":
                    return State;
                default:
                    return base.GetProperty(name);
            }
        }

        public override void SetProperty(string name, object value)
        {
            switch (name)
            {
                case "Address":
                    Address = (string)value;
                    break;
                case "City":
                    City = (string)value;
                    break;
                case "State":
                    State = (string)value;
                    break;
                default:
                    base.SetProperty(name, value);
                    break;
            }
        }
    }
}

他们注册时,会在公司域下创建公司资料。

using System;
using System.Linq;
using SenseNet.ApplicationModel;
using SenseNet.ContentRepository;
using SenseNet.ContentRepository.Storage;
using SenseNet.ContentRepository.Storage.Data;
using SenseNet.ContentRepository.Storage.Security;

namespace DerAssistantService.Actions
{
    public static class UserActions
    {
        [ODataAction]
        public static Content RegisterUser(Content content, string email, string companyname, string password)
        {
            if (string.IsNullOrEmpty(email))
                throw new ArgumentNullException(nameof(email));
            if (string.IsNullOrEmpty(companyname))
                throw new ArgumentNullException(nameof(companyname));
            if (string.IsNullOrEmpty(password))
                throw new ArgumentNullException(nameof(password));

            var username = email.Split('@').First();

            var isUserCreated = Node.LoadNode("Root/IMS/Public/" + username);
            var isCompanyProfileCreated = Node.LoadNode("Root/Profiles/Company" + companyname);

            if (isUserCreated != null)
            {
                throw new NodeAlreadyExistsException("There already exists a user with this name.");
            }

            if (isCompanyProfileCreated != null)
            {
                throw new NodeAlreadyExistsException("There already exists a company with this name.");
            }

            using (new SystemAccount())
            {            
                var user = Content.CreateNew("User", content.ContentHandler, username);

                user["FullName"] = username;
                user["Email"] = email;
                user["LoginName"] = email;
                user["Enabled"] = true;
                user["Password"] = password;
                user.Save();

                var parent = Node.LoadNode("Root/Profiles/Company");
                var companyProfile = Content.CreateNew("CompanyProfile", parent, companyname);

                companyProfile["Name"] = companyname;
                companyProfile.Save();

                var identifiedUsers = Node.Load<Group>("/Root/IMS/BuiltIn/Portal/IdentifiedUsers");
                identifiedUsers.AddMember(user.ContentHandler as IUser);
                identifiedUsers.Save();

                return user;
            }
        }
    }
}

我为它创建了这个内容类型定义。

<ContentType name="CompanyProfile" parentType="Workspace" handler="DerAssistantService.ContentHandlers.CompanyProfile" xmlns="http://schemas.sensenet.com/SenseNet/ContentRepository/ContentTypeDefinition">
  <DisplayName>CompanyProfile</DisplayName>
  <Description>This content contains basic information on a company</Description>
  <Icon>Company</Icon>
  <Fields>
    <Field name="Name" type="ShortText">
      <DisplayName>Company</DisplayName>
      <Description>The name of the company</Description>
      <Configuration>
        <VisibleBrowse>Show</VisibleBrowse>
        <VisibleEdit>Show</VisibleEdit>
        <VisibleNew>Show</VisibleNew>
        <Compulsory>true</Compulsory>
      </Configuration>
    </Field>
    <Field name="Address" type="ShortText">
      <DisplayName>Address</DisplayName>
      <Description>The location of the company</Description>
      <Configuration>
        <VisibleBrowse>Show</VisibleBrowse>
        <VisibleEdit>Show</VisibleEdit>
        <VisibleNew>Show</VisibleNew>
        <Compulsory>true</Compulsory>
      </Configuration>
    </Field>
    <Field name="City" type="ShortText">
      <DisplayName>City</DisplayName>
      <Description>The city where the company is located at</Description>
      <Configuration>
        <VisibleBrowse>Show</VisibleBrowse>
        <VisibleEdit>Show</VisibleEdit>
        <VisibleNew>Show</VisibleNew>
        <Compulsory>true</Compulsory>
      </Configuration>
    </Field>
    <Field name="State" type="ShortText">
      <DisplayName>State</DisplayName>
      <Description>The state the company resides in</Description>
      <Configuration>
        <VisibleBrowse>Show</VisibleBrowse>
        <VisibleEdit>Show</VisibleEdit>
        <VisibleNew>Show</VisibleNew>
        <Compulsory>true</Compulsory>
      </Configuration>
    </Field>
  </Fields>
</ContentType>

问题,我无法轻松加载公司资料。两者之间没有联系,因为两者都在不同的域中,彼此之间没有任何信息。我该如何协调?

编辑 2

经过一些实验,我终于有了一个可行的解决方案。

这是我的公司资料现在的样子。

 <?xml version="1.0" encoding="utf-8"?>
 <ContentType name="CompanyProfile" parentType="UserProfile" handler="DerAssistantService.ContentHandlers.CompanyProfile" xmlns="http://schemas.sensenet.com/SenseNet/ContentRepository/ContentTypeDefinition">
      <DisplayName>CompanyProfile</DisplayName>
      <Description>This profile contains contains a single reference to a company that has registered itself in the web app.</Description>
      <Icon>UserProfile</Icon>
      <AllowedChildTypes>DocumentLibrary,EventList,MemoList,LinkList,TaskList,ImageLibrary,CustomList</AllowedChildTypes>
      <Fields>
        <Field name="Company" type="Reference">
          <DisplayName>Company</DisplayName>
          <Configuration>
            <AllowMultiple>false</AllowMultiple>
            <AllowedTypes>
              <Type>Company</Type>
            </AllowedTypes>
            <SelectionRoot>
              <Path>/Root/IMS</Path>
            </SelectionRoot>
          </Configuration>
        </Field>
      </Fields>
 </ContentType>

现在公司资料扩展了用户资料,这样我仍然有一个对用户对象的引用,并且有一个新的公司对象引用 属性。

现在我有一个公司的单独内容类型定义文件。

<?xml version="1.0" encoding="utf-8"?>
<ContentType name="Company" parentType="Workspace" handler="DerAssistantService.ContentHandlers.Company" xmlns="http://schemas.sensenet.com/SenseNet/ContentRepository/ContentTypeDefinition">
  <DisplayName>Company</DisplayName>
  <Description>This content contains basic information on a particular company</Description>
  <Icon>Company</Icon>
  <AllowedChildTypes>Image</AllowedChildTypes>
  <Fields>
    <Field name="Address" type="ShortText">
      <DisplayName>Address</DisplayName>
      <Description>The location of the company</Description>
      <Configuration>
        <VisibleBrowse>Show</VisibleBrowse>
        <VisibleEdit>Show</VisibleEdit>
        <VisibleNew>Show</VisibleNew>
        <Compulsory>true</Compulsory>
      </Configuration>
    </Field>
    <Field name="City" type="ShortText">
      <DisplayName>City</DisplayName>
      <Description>The city where the company resides in</Description>
      <Configuration>
        <VisibleBrowse>Show</VisibleBrowse>
        <VisibleEdit>Show</VisibleEdit>
        <VisibleNew>Show</VisibleNew>
        <Compulsory>true</Compulsory>
      </Configuration>
    </Field>
    <Field name="State" type="ShortText">
      <DisplayName>State</DisplayName>
      <Description>The state the company resides in</Description>
      <Configuration>
        <VisibleBrowse>Show</VisibleBrowse>
        <VisibleEdit>Show</VisibleEdit>
        <VisibleNew>Show</VisibleNew>
        <Compulsory>true</Compulsory>
      </Configuration>
    </Field>
  </Fields>
</ContentType>

这是我的 CompanyProfile 内容处理程序的样子

using SenseNet.ContentRepository;
using SenseNet.ContentRepository.Schema;
using SenseNet.ContentRepository.Storage;

namespace DerAssistantService.ContentHandlers
{
    [ContentHandler]
    public class CompanyProfile : UserProfile
    {
        public CompanyProfile(Node parent) : this(parent, null) { }

        public CompanyProfile(Node parent, string nodeTypeName) : base(parent, nodeTypeName) { }

        protected CompanyProfile(NodeToken token) : base(token) { }

        [RepositoryProperty("Company", RepositoryDataType.Reference)]
        public Company Company
        {
            get { return GetReference<Company>("Company"); }
            set { SetReference("Company", value); }
        }

        public override object GetProperty(string name)
        {
            switch (name)
            {
                case "Company":
                    return Company;
                default:
                    return base.GetProperty(name);
            }
        }
        public override void SetProperty(string name, object value)
        {
            switch (name)
            {
                case "Company":
                    Company = (Company)value;
                    break;
                default:
                    base.SetProperty(name, value);
                    break;
            }
        }
    }
}

以及我公司 class 的内容处理程序是怎样的。

using SenseNet.ContentRepository.Schema;
using SenseNet.ContentRepository.Storage;
using SenseNet.ContentRepository.Workspaces;

namespace DerAssistantService.ContentHandlers
{
    [ContentHandler]
    public class Company : Workspace
    {
        public Company(Node parent) : this(parent, null) { }

        public Company(Node parent, string nodeTypeName) : base(parent, nodeTypeName) { }

        protected Company(NodeToken token) : base(token) { }

        [RepositoryProperty("Address", RepositoryDataType.String)]
        public string Address
        {
            get { return GetProperty<string>("Address"); }
            set { this["Address"] = value; }
        }

        [RepositoryProperty("City", RepositoryDataType.String)]
        public string City
        {
            get { return GetProperty<string>("City"); }
            set { this["City"] = value; }
        }

        [RepositoryProperty("State", RepositoryDataType.String)]
        public string State
        {
            get { return GetProperty<string>("State"); }
            set { this["State"] = value; }
        }

        public override object GetProperty(string name)
        {
            switch (name)
            {
                case "Address":
                    return Address;
                case "City":
                    return City;
                case "State":
                    return State;
                default:
                    return base.GetProperty(name);
            }
        }

        public override void SetProperty(string name, object value)
        {
            switch (name)
            {
                case "Address":
                    Address = (string)value;
                    break;
                case "City":
                    City = (string)value;
                    break;
                case "State":
                    State = (string)value;
                    break;
                default:
                    base.SetProperty(name, value);
                    break;
            }
        }
    }
}

我在我的 RegisterUser 方法中分离了逻辑,因为它做的太多了。

我的 RegisterUser 函数现在看起来像这样。

        [ODataAction]
        public static Content RegisterUser(Content content, string email, string password)
        {
            using (new SystemAccount())
            {
                var username = email.Split('@').First();
                var user = CreateUser("Public", email, password, username, true);

                var identifiedUsers = Node.Load<Group>("/Root/IMS/BuiltIn/Portal/IdentifiedUsers");
                identifiedUsers.AddMember(user.ContentHandler as IUser);
                identifiedUsers.Save();

                return user;
            }
        }

        private static Content CreateUser(string domainName, string username, string password, string fullname, bool enabled, Dictionary<string, object> properties = null)
        {
            var domainPath = RepositoryPath.Combine(RepositoryStructure.ImsFolderPath, domainName);
            var domain = Node.LoadNode(domainPath);
            var user = Content.CreateNew("User", domain, username);
            user["Name"] = username;
            user["Password"] = password;
            user["FullName"] = fullname;
            user["Enabled"] = enabled;

            if (properties != null)
            {
                foreach (var key in properties.Keys)
                {
                    user[key] = properties[key];
                }
            }

            user.Save();
            return user;
        }
    }

我创建了一个单独的函数,负责在创建用户后注册公司,因为我需要创建用户对象,以便它可以创建公司资料。

        [ODataAction]
        public static Content RegisterCompany(Content content, string companyName, string userEmail)
        {
            using (new SystemAccount())
            {
                CompanyProfile companyProfile = Node.LoadNode("/Root/Profiles/Public/" + userEmail) as CompanyProfile;

                Company company = CreateCompany(companyName, companyProfile);

                var companyContent = Content.Create(company);
                companyContent.Save();

                companyProfile.Company = company;
                companyProfile.Save();

                return companyContent;
            }
        }

        private static Company CreateCompany(string companyName, CompanyProfile companyProfile)
        {
            var parent = Node.LoadNode("/Root/IMS/Company");

            Company company = new Company(parent);

            company.Name = companyName;
            company.Address = "N/A";
            company.City = "N/A";
            company.State = "N/A";
            company.VersionCreatedBy = companyProfile.User;
            company.VersionModifiedBy = companyProfile.User;
            company.CreatedBy = companyProfile.User;
            company.ModifiedBy = companyProfile.User;
            company.Owner = companyProfile.User;

            return company;
        }
    }

有了这个,如果我按顺序向 RegisterUser 和 RegisterCompany 发出请求,公司资料现在将具有对创建的公司对象的引用。请告诉我是否有任何其他方法可以重组它。

我认为你需要反转参考方向。例如,用户在一家公司工作,该公司可以有个人资料。因此,用户可以使用单个参考字段来定位公司或公司的资料。这种机制提供了双向的轻松访问:如果您有用户实例 user.Companyuser.CompanyProfile returns 目标对象。可以通过类似这样的简单查询访问向后方向:Content.All.OfType<User>().Where(c => c.Company == company).FirstOrDefault().