msgpack-render (wip)

This commit is contained in:
Daniele Teti 2024-03-28 19:00:37 +01:00
parent a5cb0074b3
commit ef474a4a85
7 changed files with 1750 additions and 0 deletions

View File

@ -0,0 +1,156 @@
unit MyControllerU;
interface
uses
MVCFramework, MVCFramework.Commons, MVCFramework.Serializer.Commons, System.Generics.Collections;
type
[MVCNameCase(ncCamelCase)]
TPerson = class
private
fFirstName: String;
fLastName: String;
fDOB: TDate;
public
property FirstName: String read fFirstName write fFirstName;
property LastName: String read fLastName write fLastName;
property DOB: TDate read fDOB write fDOB;
constructor Create(FirstName, LastName: String; DOB: TDate);
end;
[MVCPath('/api')]
TMyController = class(TMVCController)
public
[MVCPath]
[MVCHTTPMethod([httpGET])]
function Index: String;
[MVCPath('/reversedstrings/($Value)')]
[MVCHTTPMethod([httpGET])]
[MVCProduces(TMVCMediaType.TEXT_PLAIN)]
function GetReversedString(const Value: String): String;
protected
procedure OnBeforeAction(Context: TWebContext; const AActionName: string; var Handled: Boolean); override;
procedure OnAfterAction(Context: TWebContext; const AActionName: string); override;
public
//Sample CRUD Actions for a "People" entity
[MVCPath('/people')]
[MVCHTTPMethod([httpGET])]
[MVCProduces(TMVCMediaType.APPLICATION_MESSAGE_PACK)]
function GetPeople: TObjectList<TPerson>;
[MVCPath('/people/($ID)')]
[MVCHTTPMethod([httpGET])]
function GetPerson(ID: Integer): TPerson;
[MVCPath('/people')]
[MVCHTTPMethod([httpPOST])]
function CreatePerson([MVCFromBody] Person: TPerson): IMVCResponse;
[MVCPath('/people/($ID)')]
[MVCHTTPMethod([httpPUT])]
function UpdatePerson(ID: Integer; [MVCFromBody] Person: TPerson): IMVCResponse;
[MVCPath('/people/($ID)')]
[MVCHTTPMethod([httpDELETE])]
function DeletePerson(ID: Integer): IMVCResponse;
end;
implementation
uses
System.SysUtils, MVCFramework.Logger, System.StrUtils;
function TMyController.Index: String;
begin
//use Context property to access to the HTTP request and response
Result := 'Hello DelphiMVCFramework World';
end;
function TMyController.GetReversedString(const Value: String): String;
begin
Result := System.StrUtils.ReverseString(Value.Trim);
end;
procedure TMyController.OnAfterAction(Context: TWebContext; const AActionName: string);
begin
{ Executed after each action }
inherited;
end;
procedure TMyController.OnBeforeAction(Context: TWebContext; const AActionName: string; var Handled: Boolean);
begin
{ Executed before each action
if handled is true (or an exception is raised) the actual
action will not be called }
inherited;
end;
//Sample CRUD Actions for a "People" entity
function TMyController.GetPeople: TObjectList<TPerson>;
var
lPeople: TObjectList<TPerson>;
begin
lPeople := TObjectList<TPerson>.Create(True);
try
lPeople.Add(TPerson.Create('Peter','Parker', EncodeDate(1965, 10, 4)));
lPeople.Add(TPerson.Create('Bruce','Banner', EncodeDate(1945, 9, 6)));
lPeople.Add(TPerson.Create('Reed','Richards', EncodeDate(1955, 3, 7)));
Result := lPeople;
except
lPeople.Free;
raise;
end;
end;
function TMyController.GetPerson(ID: Integer): TPerson;
var
lPeople: TObjectList<TPerson>;
begin
lPeople := GetPeople;
try
Result := lPeople.ExtractAt(ID mod lPeople.Count);
finally
lPeople.Free;
end;
end;
function TMyController.CreatePerson([MVCFromBody] Person: TPerson): IMVCResponse;
begin
LogI('Created ' + Person.FirstName + ' ' + Person.LastName);
Result := MVCResponseBuilder
.StatusCode(HTTP_STATUS.Created)
.Body('Person created')
.Build;
end;
function TMyController.UpdatePerson(ID: Integer; [MVCFromBody] Person: TPerson): IMVCResponse;
begin
LogI('Updated ' + Person.FirstName + ' ' + Person.LastName);
Result := MVCResponseBuilder
.StatusCode(HTTP_STATUS.NoContent)
.Build;
end;
function TMyController.DeletePerson(ID: Integer): IMVCResponse;
begin
LogI('Deleted person with id ' + ID.ToString);
Result := MVCResponseBuilder
.StatusCode(HTTP_STATUS.NoContent)
.Build;
end;
constructor TPerson.Create(FirstName, LastName: String; DOB: TDate);
begin
inherited Create;
fFirstName := FirstName;
fLastName := LastName;
fDOB := DOB;
end;
end.

View File

@ -0,0 +1,7 @@
object MyWebModule: TMyWebModule
OnCreate = WebModuleCreate
OnDestroy = WebModuleDestroy
Actions = <>
Height = 230
Width = 415
end

View File

@ -0,0 +1,127 @@
unit WebModuleU;
interface
uses
System.SysUtils,
System.Classes,
Web.HTTPApp,
MVCFramework;
type
TMyWebModule = class(TWebModule)
procedure WebModuleCreate(Sender: TObject);
procedure WebModuleDestroy(Sender: TObject);
private
FMVC: TMVCEngine;
public
{ Public declarations }
end;
var
WebModuleClass: TComponentClass = TMyWebModule;
implementation
{$R *.dfm}
uses
System.IOUtils,
MVCFramework.Commons,
MVCFramework.Middleware.ActiveRecord,
MVCFramework.Middleware.StaticFiles,
MVCFramework.Middleware.Analytics,
MVCFramework.Middleware.Trace,
MVCFramework.Middleware.CORS,
MVCFramework.Middleware.ETag,
MVCFramework.Middleware.Compression,
MyControllerU, MVCFramework.Serializer.MessagePack;
procedure TMyWebModule.WebModuleCreate(Sender: TObject);
begin
FMVC := TMVCEngine.Create(Self,
procedure(Config: TMVCConfig)
begin
Config.dotEnv := dotEnv;
// session timeout (0 means session cookie)
Config[TMVCConfigKey.SessionTimeout] := dotEnv.Env('dmvc.session_timeout', '0');
//default content-type
Config[TMVCConfigKey.DefaultContentType] := dotEnv.Env('dmvc.default.content_type', TMVCConstants.DEFAULT_CONTENT_TYPE);
//default content charset
Config[TMVCConfigKey.DefaultContentCharset] := dotEnv.Env('dmvc.default.content_charset', TMVCConstants.DEFAULT_CONTENT_CHARSET);
//unhandled actions are permitted?
Config[TMVCConfigKey.AllowUnhandledAction] := dotEnv.Env('dmvc.allow_unhandled_actions', 'false');
//enables or not system controllers loading (available only from localhost requests)
Config[TMVCConfigKey.LoadSystemControllers] := dotEnv.Env('dmvc.load_system_controllers', 'true');
//default view file extension
Config[TMVCConfigKey.DefaultViewFileExtension] := dotEnv.Env('dmvc.default.view_file_extension', 'html');
//view path
Config[TMVCConfigKey.ViewPath] := dotEnv.Env('dmvc.view_path', 'templates');
//use cache for server side views (use "false" in debug and "true" in production for faster performances
Config[TMVCConfigKey.ViewCache] := dotEnv.Env('dmvc.view_cache', 'false');
//Max Record Count for automatic Entities CRUD
Config[TMVCConfigKey.MaxEntitiesRecordCount] := dotEnv.Env('dmvc.max_entities_record_count', IntToStr(TMVCConstants.MAX_RECORD_COUNT));
//Enable Server Signature in response
Config[TMVCConfigKey.ExposeServerSignature] := dotEnv.Env('dmvc.expose_server_signature', 'false');
//Enable X-Powered-By Header in response
Config[TMVCConfigKey.ExposeXPoweredBy] := dotEnv.Env('dmvc.expose_x_powered_by', 'true');
// Max request size in bytes
Config[TMVCConfigKey.MaxRequestSize] := dotEnv.Env('dmvc.max_request_size', IntToStr(TMVCConstants.DEFAULT_MAX_REQUEST_SIZE));
end);
FMVC
.AddController(TMyController);
FMVC
.AddSerializer(TMVCMediaType.APPLICATION_MESSAGE_PACK, TMVCMessagePackSerializer.Create());
// Analytics middleware generates a csv log, useful to do traffic analysis
//FMVC.AddMiddleware(TMVCAnalyticsMiddleware.Create(GetAnalyticsDefaultLogger));
// The folder mapped as documentroot for TMVCStaticFilesMiddleware must exists!
//FMVC.AddMiddleware(TMVCStaticFilesMiddleware.Create('/static', TPath.Combine(ExtractFilePath(GetModuleName(HInstance)), 'www')));
// Trace middlewares produces a much detailed log for debug purposes
//FMVC.AddMiddleware(TMVCTraceMiddleware.Create);
// CORS middleware handles... well, CORS
//FMVC.AddMiddleware(TMVCCORSMiddleware.Create);
// Simplifies TMVCActiveRecord connection definition
{
FMVC.AddMiddleware(TMVCActiveRecordMiddleware.Create(
dotEnv.Env('firedac.connection_definition_name', 'MyConnDef'),
dotEnv.Env('firedac.connection_definitions_filename', 'FDConnectionDefs.ini')
));
}
// Compression middleware must be the last in the chain, just before the ETag, if present.
//FMVC.AddMiddleware(TMVCCompressionMiddleware.Create);
// ETag middleware must be the latest in the chain
//FMVC.AddMiddleware(TMVCETagMiddleware.Create);
{
FMVC.OnWebContextCreate(
procedure(const Context: TWebContext)
begin
// Initialize services to make them accessibile from Context
// Context.CustomIntfObject := TMyService.Create;
end);
FMVC.OnWebContextDestroy(
procedure(const Context: TWebContext)
begin
//Cleanup services, if needed
end);
}
end;
procedure TMyWebModule.WebModuleDestroy(Sender: TObject);
begin
FMVC.Free;
end;
end.

View File

@ -0,0 +1,91 @@
program msgpack_renders;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
MVCFramework,
MVCFramework.Logger,
MVCFramework.DotEnv,
MVCFramework.Commons,
MVCFramework.Signal,
Web.ReqMulti,
Web.WebReq,
Web.WebBroker,
IdContext,
IdHTTPWebBrokerBridge,
MyControllerU in 'MyControllerU.pas',
WebModuleU in 'WebModuleU.pas' {MyWebModule: TWebModule},
MVCFramework.Serializer.MessagePack in '..\..\sources\MVCFramework.Serializer.MessagePack.pas';
{$R *.res}
procedure RunServer(APort: Integer);
var
LServer: TIdHTTPWebBrokerBridge;
begin
LServer := TIdHTTPWebBrokerBridge.Create(nil);
try
LServer.OnParseAuthentication := TMVCParseAuthentication.OnParseAuthentication;
LServer.DefaultPort := APort;
LServer.KeepAlive := True;
LServer.MaxConnections := dotEnv.Env('dmvc.webbroker.max_connections', 0);
LServer.ListenQueue := dotEnv.Env('dmvc.indy.listen_queue', 500);
LServer.Active := True;
LogI('Listening on port ' + APort.ToString);
LogI('Application started. Press Ctrl+C to shut down.');
WaitForTerminationSignal;
EnterInShutdownState;
LServer.Active := False;
finally
LServer.Free;
end;
end;
begin
{ Enable ReportMemoryLeaksOnShutdown during debug }
// ReportMemoryLeaksOnShutdown := True;
IsMultiThread := True;
// DMVCFramework Specific Configuration
// When MVCSerializeNulls = True empty nullables and nil are serialized as json null.
// When MVCSerializeNulls = False empty nullables and nil are not serialized at all.
MVCSerializeNulls := True;
UseConsoleLogger := True;
LogI('** DMVCFramework Server ** build ' + DMVCFRAMEWORK_VERSION);
try
if WebRequestHandler <> nil then
WebRequestHandler.WebModuleClass := WebModuleClass;
dotEnvConfigure(
function: IMVCDotEnv
begin
Result := NewDotEnv
.UseStrategy(TMVCDotEnvPriority.FileThenEnv)
//if available, by default, loads default environment (.env)
.UseProfile('test') //if available loads the test environment (.env.test)
.UseProfile('prod') //if available loads the prod environment (.env.prod)
.UseLogger(procedure(LogItem: String)
begin
LogD('dotEnv: ' + LogItem);
end)
.Build(); //uses the executable folder to look for .env* files
end);
WebRequestHandlerProc.MaxConnections := dotEnv.Env('dmvc.handler.max_connections', 1024);
if dotEnv.Env('dmvc.profiler.enabled', false) then
begin
Profiler.ProfileLogger := Log;
Profiler.WarningThreshold := dotEnv.Env('dmvc.profiler.warning_threshold', 2000);
end;
RunServer(dotEnv.Env('dmvc.server.port', 8080));
except
on E: Exception do
LogF(E.ClassName + ': ' + E.Message);
end;
end.

File diff suppressed because it is too large Load Diff

View File

@ -66,6 +66,7 @@ type
APPLICATION_XHTML_XML = 'application/xhtml+xml';
APPLICATION_XML = 'application/xml';
APPLICATION_OCTETSTREAM = 'application/octet-stream';
APPLICATION_MESSAGE_PACK = 'application/msgpack';
MEDIA_TYPE_WILDCARD = '*';
MULTIPART_FORM_DATA = 'multipart/form-data';
APPLICATION_FORM_URLENCODED = 'application/x-www-form-urlencoded';

View File

@ -0,0 +1,340 @@
// ***************************************************************************
//
// Delphi MVC Framework
//
// Copyright (c) 2010-2024 Daniele Teti and the DMVCFramework Team
//
// https://github.com/danieleteti/delphimvcframework
//
// ***************************************************************************
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ***************************************************************************
unit MVCFramework.Serializer.MessagePack;
{$I dmvcframework.inc}
{$WARN SYMBOL_DEPRECATED OFF}
interface
uses
System.SysUtils,
System.Classes,
System.Rtti,
System.TypInfo,
System.Variants,
System.Generics.Collections,
Data.SqlTimSt,
Data.FmtBcd,
Data.DB,
DMsgPackHelper,
SimpleMsgPack,
MVCFramework.Commons,
MVCFramework.Serializer.Intf,
MVCFramework.Serializer.Abstract,
MVCFramework.DuckTyping,
MVCFramework.Serializer.Commons;
type
TMVCMessagePackSerializer = class(TMVCAbstractSerializer, IMVCSerializer)
protected
procedure RaiseNotImplemented;
public
procedure AfterConstruction; override;
{ IMVCSerializer }
procedure RegisterTypeSerializer(const ATypeInfo: PTypeInfo; AInstance: IMVCTypeSerializer);
function SerializeObject(
const AObject: TObject;
const AType: TMVCSerializationType = stDefault;
const AIgnoredAttributes: TMVCIgnoredList = nil;
const ASerializationAction: TMVCSerializationAction = nil
): string; overload;
function SerializeObject(
const AObject: IInterface;
const AType: TMVCSerializationType = stDefault;
const AIgnoredAttributes: TMVCIgnoredList = nil;
const ASerializationAction: TMVCSerializationAction = nil
): string; overload;
function SerializeRecord(
const ARecord: Pointer;
const ARecordTypeInfo: PTypeInfo;
const AType: TMVCSerializationType = stDefault;
const AIgnoredAttributes: TMVCIgnoredList = nil;
const ASerializationAction: TMVCSerializationAction = nil
): string; overload;
function SerializeCollection(
const AList: TObject;
const AType: TMVCSerializationType = stDefault;
const AIgnoredAttributes: TMVCIgnoredList = nil;
const ASerializationAction: TMVCSerializationAction = nil
): string; overload;
function SerializeCollection(
const AList: IInterface;
const AType: TMVCSerializationType = stDefault;
const AIgnoredAttributes: TMVCIgnoredList = nil;
const ASerializationAction: TMVCSerializationAction = nil
): string; overload;
function SerializeDataSet(
const ADataSet: TDataSet;
const AIgnoredFields: TMVCIgnoredList = [];
const ANameCase: TMVCNameCase = ncAsIs;
const ASerializationAction: TMVCDatasetSerializationAction = nil
): string;
function SerializeDataSetRecord(
const ADataSet: TDataSet;
const AIgnoredFields: TMVCIgnoredList = [];
const ANameCase: TMVCNameCase = ncAsIs;
const ASerializationAction: TMVCDatasetSerializationAction = nil
): string;
procedure DeserializeObject(
const ASerializedObject: string;
const AObject: TObject;
const AType: TMVCSerializationType = stDefault;
const AIgnoredAttributes: TMVCIgnoredList = nil;
const ARootNode: string = ''
); overload;
procedure DeserializeObject(
const ASerializedObject: string;
const AObject: IInterface;
const AType: TMVCSerializationType = stDefault;
const AIgnoredAttributes: TMVCIgnoredList = nil
); overload;
procedure DeserializeCollection(
const ASerializedList: string;
const AList: TObject;
const AClazz: TClass;
const AType: TMVCSerializationType = stDefault;
const AIgnoredAttributes: TMVCIgnoredList = nil;
const ARootNode: string = ''
); overload;
procedure DeserializeCollection(
const ASerializedList: string;
const AList: IInterface;
const AClazz: TClass;
const AType: TMVCSerializationType = stDefault;
const AIgnoredAttributes: TMVCIgnoredList = nil
); overload;
procedure DeserializeDataSet(
const ASerializedDataSet: string;
const ADataSet: TDataSet;
const AIgnoredFields: TMVCIgnoredList = [];
const ANameCase: TMVCNameCase = ncAsIs
);
procedure DeserializeDataSetRecord(
const ASerializedDataSetRecord: string;
const ADataSet: TDataSet;
const AIgnoredFields: TMVCIgnoredList = [];
const ANameCase: TMVCNameCase = ncAsIs
);
function SerializeArrayOfRecord(
var ATValueContainingAnArray: TValue;
const AType: TMVCSerializationType = stDefault;
const AIgnoredAttributes: TMVCIgnoredList = nil;
const ASerializationAction: TMVCSerializationAction = nil): string;
end;
implementation
uses
System.NetEncoding,
MVCFramework,
MVCFramework.Logger,
MVCFramework.DataSet.Utils,
MVCFramework.Nullables, System.StrUtils;
{ TMVCMessagePackSerializer }
procedure TMVCMessagePackSerializer.AfterConstruction;
begin
inherited AfterConstruction;
end;
procedure TMVCMessagePackSerializer.DeserializeCollection(
const ASerializedList: string; const AList: IInterface; const AClazz: TClass;
const AType: TMVCSerializationType;
const AIgnoredAttributes: TMVCIgnoredList);
begin
RaiseNotImplemented;
end;
procedure TMVCMessagePackSerializer.DeserializeCollection(
const ASerializedList: string; const AList: TObject; const AClazz: TClass;
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList;
const ARootNode: string);
begin
RaiseNotImplemented;
end;
procedure TMVCMessagePackSerializer.DeserializeDataSet(
const ASerializedDataSet: string; const ADataSet: TDataSet;
const AIgnoredFields: TMVCIgnoredList; const ANameCase: TMVCNameCase);
begin
RaiseNotImplemented;
end;
procedure TMVCMessagePackSerializer.DeserializeDataSetRecord(
const ASerializedDataSetRecord: string; const ADataSet: TDataSet;
const AIgnoredFields: TMVCIgnoredList; const ANameCase: TMVCNameCase);
begin
RaiseNotImplemented;
end;
procedure TMVCMessagePackSerializer.DeserializeObject(const ASerializedObject: string;
const AObject: TObject; const AType: TMVCSerializationType;
const AIgnoredAttributes: TMVCIgnoredList; const ARootNode: string);
begin
RaiseNotImplemented;
end;
procedure TMVCMessagePackSerializer.DeserializeObject(const ASerializedObject: string;
const AObject: IInterface; const AType: TMVCSerializationType;
const AIgnoredAttributes: TMVCIgnoredList);
begin
RaiseNotImplemented;
end;
procedure TMVCMessagePackSerializer.RaiseNotImplemented;
begin
raise EMVCException.Create('Not Implemented');
end;
procedure TMVCMessagePackSerializer.RegisterTypeSerializer(const ATypeInfo: PTypeInfo;
AInstance: IMVCTypeSerializer);
begin
RaiseNotImplemented;
end;
function TMVCMessagePackSerializer.SerializeCollection(const AList: TObject;
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList;
const ASerializationAction: TMVCSerializationAction): string;
begin
RaiseNotImplemented;
end;
function TMVCMessagePackSerializer.SerializeArrayOfRecord(
var ATValueContainingAnArray: TValue; const AType: TMVCSerializationType;
const AIgnoredAttributes: TMVCIgnoredList;
const ASerializationAction: TMVCSerializationAction): string;
begin
RaiseNotImplemented;
end;
function TMVCMessagePackSerializer.SerializeCollection(const AList: IInterface;
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList;
const ASerializationAction: TMVCSerializationAction): string;
begin
RaiseNotImplemented;
end;
function TMVCMessagePackSerializer.SerializeDataSet(const ADataSet: TDataSet;
const AIgnoredFields: TMVCIgnoredList; const ANameCase: TMVCNameCase;
const ASerializationAction: TMVCDatasetSerializationAction): string;
begin
RaiseNotImplemented;
end;
function TMVCMessagePackSerializer.SerializeDataSetRecord(const ADataSet: TDataSet;
const AIgnoredFields: TMVCIgnoredList; const ANameCase: TMVCNameCase;
const ASerializationAction: TMVCDatasetSerializationAction): string;
begin
RaiseNotImplemented;
end;
function TMVCMessagePackSerializer.SerializeObject(const AObject: TObject;
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList;
const ASerializationAction: TMVCSerializationAction): string;
var
LJObj: TSimpleMsgPack;
lObjType: TRttiType;
lDict: IMVCLinks;
begin
Result := EmptyStr;
if not Assigned(AObject) then
Exit('null');
// if AObject is TSimpleMsgPack then
// Exit(TSimpleMsgPack(AObject).EncodeToBytes(True));
//
// if AObject is TDataSet then
// Exit(self.SerializeDataSet(TDataSet(AObject), AIgnoredAttributes));
//
// if AObject is System.JSON.TJsonValue then
// Exit(System.JSON.TJsonValue(AObject).ToJSON);
//
// lObjType := GetRttiContext.GetType(AObject.ClassType);
//
// if GetTypeSerializers.ContainsKey(lObjType.Handle) then
// begin
// GetTypeSerializers.Items[lObjType.Handle].SerializeRoot(AObject, TObject(LJObj), []);
// try
// Result := LJObj.ToJSON(True);
// finally
// LJObj.Free;
// end;
// Exit;
// end;
//
// LJObj := TJDOJsonObject.Create;
// try
// if Assigned(ASerializationAction) then
// begin
// lDict := TJDOLinks.Create;
// InternalObjectToJsonObject(AObject, LJObj, GetSerializationType(AObject, AType), AIgnoredAttributes,
// ASerializationAction, lDict, fStringDictionarySerializer);
// end
// else
// begin
// InternalObjectToJsonObject(AObject, LJObj, GetSerializationType(AObject, AType), AIgnoredAttributes, nil,
// nil, nil);
// end;
// Result := LJObj.ToJSON(True);
// finally
// LJObj.Free;
// end;
end;
function TMVCMessagePackSerializer.SerializeObject(const AObject: IInterface;
const AType: TMVCSerializationType;
const AIgnoredAttributes: TMVCIgnoredList;
const ASerializationAction: TMVCSerializationAction): string;
begin
RaiseNotImplemented;
end;
function TMVCMessagePackSerializer.SerializeRecord(const ARecord: Pointer;
const ARecordTypeInfo: PTypeInfo; const AType: TMVCSerializationType;
const AIgnoredAttributes: TMVCIgnoredList;
const ASerializationAction: TMVCSerializationAction): string;
begin
raise Exception.Create('Not implemented');
end;
end.