Как мне реализовать профиль компании в 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;
}
}
}
}
и как выглядит обработчик контента для класса моей компании.
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 в таком порядке, профиль компании теперь будет содержать ссылку на созданный объект компании. Пожалуйста, скажите мне, если есть какой-то другой способ, которым я могу реструктурировать это.
1 ответ
Я думаю, что вам нужно изменить направление ссылки. Например, пользователь работает в компании, компания может иметь профиль. Таким образом, пользователь может использовать одно поле ссылки для ориентации на компанию или профиль компании. Этот механизм обеспечивает легкий доступ в обоих направлениях: если у вас есть пользовательский экземпляр user.Company
или же user.CompanyProfile
возвращает целевой объект. Обратное направление можно получить с помощью простого запроса, например, такого: Content.All.OfType<User>().Where(c => c.Company == company).FirstOrDefault()
,