mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-15 15:55:54 +01:00
commit
9b59fd6e44
@ -40,36 +40,44 @@ resourcestring
|
||||
sDMVCDPR =
|
||||
'program %0:s;' + sLineBreak +
|
||||
sLineBreak +
|
||||
' {$APPTYPE CONSOLE}' + sLineBreak +
|
||||
'' + sLineBreak +
|
||||
'{$APPTYPE CONSOLE}' + sLineBreak +
|
||||
sLineBreak +
|
||||
'uses' + sLineBreak +
|
||||
' System.SysUtils,' + sLineBreak +
|
||||
' MVCFramework.Logger,' + sLineBreak +
|
||||
' MVCFramework.Commons,' + sLineBreak +
|
||||
' MVCFramework.REPLCommandsHandlerU,' + sLineBreak +
|
||||
' Web.ReqMulti, {If you have problem with this unit, see https://quality.embarcadero.com/browse/RSP-17216}' + sLineBreak +
|
||||
' Web.ReqMulti, //If you have problem with this unit, see https://quality.embarcadero.com/browse/RSP-17216' + sLineBreak +
|
||||
' Web.WebReq,' + sLineBreak +
|
||||
' Web.WebBroker,' + sLineBreak +
|
||||
' IdContext,' + sLineBreak +
|
||||
' IdHTTPWebBrokerBridge;' + sLineBreak +
|
||||
'' + sLineBreak +
|
||||
'{$R *.res}' + sLineBreak + sLineBreak +
|
||||
sLineBreak +
|
||||
'{$R *.res}' + sLineBreak +
|
||||
sLineBreak +
|
||||
'type' + sLineBreak +
|
||||
' TDMVCParseAuthentication = class' + sLineBreak +
|
||||
' public' + sLineBreak +
|
||||
' class procedure OnParseAuthentication(AContext: TIdContext; const AAuthType, AAuthData: String; var VUsername,' + sLineBreak +
|
||||
' VPassword: String; var VHandled: Boolean);' + sLineBreak +
|
||||
' end;' + sLineBreak +
|
||||
sLineBreak +
|
||||
'procedure RunServer(APort: Integer);' + sLineBreak +
|
||||
'var' + sLineBreak +
|
||||
' lServer: TIdHTTPWebBrokerBridge;' + sLineBreak +
|
||||
' lCustomHandler: TMVCCustomREPLCommandsHandler;' + sLineBreak +
|
||||
' lCmd: string;' + sLineBreak +
|
||||
' LServer: TIdHTTPWebBrokerBridge;' + sLineBreak +
|
||||
' LCustomHandler: TMVCCustomREPLCommandsHandler;' + sLineBreak +
|
||||
' LCmd: string;' + sLineBreak +
|
||||
'begin' + sLineBreak +
|
||||
' Writeln(''** DMVCFramework Server ** build '' + DMVCFRAMEWORK_VERSION);' + sLineBreak +
|
||||
' LCmd := ''start'';' + sLineBreak +
|
||||
' if ParamCount >= 1 then' + sLineBreak +
|
||||
' lCmd := ParamStr(1)' + sLineBreak +
|
||||
' else' + sLineBreak +
|
||||
' lCmd := ''start'';' + sLineBreak +
|
||||
'' + sLineBreak +
|
||||
' lCustomHandler := function(const Value: String; const Server: TIdHTTPWebBrokerBridge; out Handled: Boolean): THandleCommandResult' + sLineBreak +
|
||||
' LCmd := ParamStr(1);' + sLineBreak +
|
||||
sLineBreak +
|
||||
' LCustomHandler := function(const Value: String; const Server: TIdHTTPWebBrokerBridge; out Handled: Boolean): THandleCommandResult' + sLineBreak +
|
||||
' begin' + sLineBreak +
|
||||
' Handled := False;' + sLineBreak +
|
||||
' Result := THandleCommandResult.Unknown;' + sLineBreak +
|
||||
'' + sLineBreak +
|
||||
sLineBreak +
|
||||
' // Write here your custom command for the REPL using the following form...' + sLineBreak +
|
||||
' // ***' + sLineBreak +
|
||||
' // Handled := False;' + sLineBreak +
|
||||
@ -86,28 +94,29 @@ resourcestring
|
||||
' // Handled := True;' + sLineBreak +
|
||||
' // end;' + sLineBreak +
|
||||
' end;' + sLineBreak +
|
||||
'' + sLineBreak +
|
||||
sLineBreak +
|
||||
' LServer := TIdHTTPWebBrokerBridge.Create(nil);' + sLineBreak +
|
||||
' try' + sLineBreak +
|
||||
' LServer.OnParseAuthentication := TDMVCParseAuthentication.OnParseAuthentication;' + sLineBreak +
|
||||
' LServer.DefaultPort := APort;' + sLineBreak +
|
||||
'' + sLineBreak +
|
||||
sLineBreak +
|
||||
' { more info about MaxConnections' + sLineBreak +
|
||||
' http://www.indyproject.org/docsite/html/frames.html?frmname=topic&frmfile=TIdCustomTCPServer_MaxConnections.html }' + sLineBreak +
|
||||
' LServer.MaxConnections := 0;' + sLineBreak +
|
||||
'' + sLineBreak +
|
||||
sLineBreak +
|
||||
' { more info about ListenQueue' + sLineBreak +
|
||||
' http://www.indyproject.org/docsite/html/frames.html?frmname=topic&frmfile=TIdCustomTCPServer_ListenQueue.html }' + sLineBreak +
|
||||
' LServer.ListenQueue := 200;' + sLineBreak +
|
||||
'' + sLineBreak +
|
||||
sLineBreak +
|
||||
' WriteLn(''Write "quit" or "exit" to shutdown the server'');' + sLineBreak +
|
||||
' repeat' + sLineBreak +
|
||||
' if lCmd.IsEmpty then' + sLineBreak +
|
||||
' if LCmd.IsEmpty then' + sLineBreak +
|
||||
' begin' + sLineBreak +
|
||||
' Write(''-> '');' + sLineBreak +
|
||||
' ReadLn(lCmd)' + sLineBreak +
|
||||
' ReadLn(LCmd)' + sLineBreak +
|
||||
' end;' + sLineBreak +
|
||||
' try' + sLineBreak +
|
||||
' case HandleCommand(lCmd.ToLower, LServer, lCustomHandler) of' + sLineBreak +
|
||||
' case HandleCommand(LCmd.ToLower, LServer, LCustomHandler) of' + sLineBreak +
|
||||
' THandleCommandResult.Continue:' + sLineBreak +
|
||||
' begin' + sLineBreak +
|
||||
' Continue;' + sLineBreak +
|
||||
@ -118,18 +127,23 @@ resourcestring
|
||||
' end;' + sLineBreak +
|
||||
' THandleCommandResult.Unknown:' + sLineBreak +
|
||||
' begin' + sLineBreak +
|
||||
' REPLEmit(''Unknown command: '' + lCmd);' + sLineBreak +
|
||||
' REPLEmit(''Unknown command: '' + LCmd);' + sLineBreak +
|
||||
' end;' + sLineBreak +
|
||||
' end;' + sLineBreak +
|
||||
' finally' + sLineBreak +
|
||||
' lCmd := '''';' + sLineBreak +
|
||||
' LCmd := '''';' + sLineBreak +
|
||||
' end;' + sLineBreak +
|
||||
' until false;' + sLineBreak +
|
||||
' until False;' + sLineBreak +
|
||||
'' + sLineBreak +
|
||||
' finally' + sLineBreak +
|
||||
' LServer.Free;' + sLineBreak +
|
||||
' end;' + sLineBreak +
|
||||
|
||||
'end;' + sLineBreak +
|
||||
sLineBreak +
|
||||
'class procedure TDMVCParseAuthentication.OnParseAuthentication(AContext: TIdContext; const AAuthType, AAuthData: String;' + sLineBreak +
|
||||
' var VUsername, VPassword: String; var VHandled: Boolean);' + sLineBreak +
|
||||
'begin' + sLineBreak +
|
||||
' VHandled := SameText(LowerCase(AAuthType), ''bearer'');' + sLineBreak +
|
||||
'end;' + sLineBreak +
|
||||
sLineBreak +
|
||||
'begin' + sLineBreak +
|
||||
|
@ -324,9 +324,13 @@ function TJWTDictionaryObject.GetItemAsDateTime(const Index: string): TDateTime;
|
||||
var
|
||||
lIntValue: Int64;
|
||||
begin
|
||||
if not TryStrToInt64(Items[index], lIntValue) then
|
||||
raise Exception.Create('Item cannot be converted as Unix Epoch');
|
||||
Result := UnixToDateTime(lIntValue, False);
|
||||
Result := -693594;
|
||||
if Trim(Items[index]) <> EmptyStr then
|
||||
begin
|
||||
if not TryStrToInt64(Items[index], lIntValue) then
|
||||
raise Exception.Create('Item cannot be converted as Unix Epoch');
|
||||
Result := UnixToDateTime(lIntValue, False);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TJWTDictionaryObject.Keys: TArray<string>;
|
||||
|
@ -41,9 +41,9 @@ type
|
||||
TMVCJWTDefaults = class sealed
|
||||
public const
|
||||
/// <summary>
|
||||
/// Default authorization header name
|
||||
/// Default authorization header name (RFC 6750)
|
||||
/// </summary>
|
||||
AUTHORIZATION_HEADER = 'Authentication';
|
||||
AUTHORIZATION_HEADER = 'Authorization';
|
||||
/// <summary>
|
||||
/// Default username header name
|
||||
/// </summary>
|
||||
@ -70,26 +70,25 @@ type
|
||||
protected
|
||||
function NeedsToBeExtended(const JWTValue: TJWT): Boolean;
|
||||
procedure ExtendExpirationTime(const JWTValue: TJWT);
|
||||
procedure InternalRender(AJSONOb: TJDOJsonObject; AContentType: string; AContentEncoding: string;
|
||||
AContext: TWebContext; AInstanceOwner: Boolean = True);
|
||||
|
||||
procedure RenderError(const AHTTPStatusCode: UInt16; const AErrorMessage: string;
|
||||
const AContext: TWebContext; const AErrorClassName: string = ''; const AErrorNumber: Integer = 0);
|
||||
|
||||
procedure InternalRender(AJSONOb: TJDOJsonObject; AContentType: string; AContentEncoding: string; AContext: TWebContext;
|
||||
AInstanceOwner: Boolean = True);
|
||||
procedure RenderError(const AHTTPStatusCode: UInt16; const AErrorMessage: string; const AContext: TWebContext;
|
||||
const AErrorClassName: string = ''; const AErrorNumber: Integer = 0);
|
||||
procedure OnBeforeRouting(AContext: TWebContext; var AHandled: Boolean);
|
||||
|
||||
procedure OnBeforeControllerAction(AContext: TWebContext; const AControllerQualifiedClassName: string;
|
||||
const AActionName: string; var AHandled: Boolean);
|
||||
|
||||
procedure OnAfterControllerAction(AContext: TWebContext; const AActionName: string; const AHandled: Boolean);
|
||||
public
|
||||
/// <remarks>
|
||||
/// The AAuthorizationHeaderName, AUserNameHeaderName, and APasswordHeaderName parameters do not follow
|
||||
/// the IETF national convention - RFC 6750;
|
||||
/// </remarks>
|
||||
constructor Create(
|
||||
AAuthenticationHandler: IMVCAuthenticationHandler;
|
||||
AConfigClaims: TJWTClaimsSetup;
|
||||
ASecret: string = 'D3lph1MVCFram3w0rk';
|
||||
ALoginURLSegment: string = '/login';
|
||||
AClaimsToCheck: TJWTCheckableClaims = [TJWTCheckableClaim.ExpirationTime, TJWTCheckableClaim.NotBefore,
|
||||
TJWTCheckableClaim.IssuedAt];
|
||||
AClaimsToCheck: TJWTCheckableClaims = [];
|
||||
ALeewaySeconds: Cardinal = 300;
|
||||
AAuthorizationHeaderName: string = TMVCJWTDefaults.AUTHORIZATION_HEADER;
|
||||
AUserNameHeaderName: string = TMVCJWTDefaults.USERNAME_HEADER;
|
||||
@ -110,8 +109,7 @@ constructor TMVCJWTAuthenticationMiddleware.Create(AAuthenticationHandler: IMVCA
|
||||
AConfigClaims: TJWTClaimsSetup;
|
||||
ASecret: string = 'D3lph1MVCFram3w0rk';
|
||||
ALoginURLSegment: string = '/login';
|
||||
AClaimsToCheck: TJWTCheckableClaims = [TJWTCheckableClaim.ExpirationTime, TJWTCheckableClaim.NotBefore,
|
||||
TJWTCheckableClaim.IssuedAt];
|
||||
AClaimsToCheck: TJWTCheckableClaims = [];
|
||||
ALeewaySeconds: Cardinal = 300;
|
||||
AAuthorizationHeaderName: string = TMVCJWTDefaults.AUTHORIZATION_HEADER;
|
||||
AUserNameHeaderName: string = TMVCJWTDefaults.USERNAME_HEADER;
|
||||
@ -233,8 +231,7 @@ begin
|
||||
AContext.LoggedUser.LoggedSince := JWTValue.Claims.IssuedAt;
|
||||
AContext.LoggedUser.CustomData := JWTValue.CustomClaims.AsCustomData;
|
||||
|
||||
FAuthenticationHandler.OnAuthorization(AContext, AContext.LoggedUser.Roles, AControllerQualifiedClassName,
|
||||
AActionName, IsAuthorized);
|
||||
FAuthenticationHandler.OnAuthorization(AContext, AContext.LoggedUser.Roles, AControllerQualifiedClassName, AActionName, IsAuthorized);
|
||||
|
||||
if IsAuthorized then
|
||||
begin
|
||||
@ -261,92 +258,107 @@ end;
|
||||
|
||||
procedure TMVCJWTAuthenticationMiddleware.OnBeforeRouting(AContext: TWebContext; var AHandled: Boolean);
|
||||
var
|
||||
UserName: string;
|
||||
Password: string;
|
||||
RolesList: TList<string>;
|
||||
SessionData: TSessionData;
|
||||
IsValid: Boolean;
|
||||
JWTValue: TJWT;
|
||||
lCustomPair: TPair<string, string>;
|
||||
LObj: TJDOJsonObject;
|
||||
LUsername, LPassword, LBasicAuthenticationEncode: string;
|
||||
LBasicAuthenticationDecode: TStringList;
|
||||
LRolesList: TList<string>;
|
||||
LSessionData: TSessionData;
|
||||
LIsValid: Boolean;
|
||||
LJWTValue: TJWT;
|
||||
LCustomPair: TPair<string, string>;
|
||||
LJsonObject: TJDOJsonObject;
|
||||
begin
|
||||
if SameText(AContext.Request.PathInfo, FLoginURLSegment) and (AContext.Request.HTTPMethod = httpPOST) then
|
||||
if SameText(AContext.Request.PathInfo, FLoginURLSegment) then
|
||||
begin
|
||||
UserName := TNetEncoding.URL.Decode(AContext.Request.Headers[FUserNameHeaderName]);
|
||||
Password := TNetEncoding.URL.Decode(AContext.Request.Headers[FPasswordHeaderName]);
|
||||
if (UserName.IsEmpty) or (Password.IsEmpty) then
|
||||
LBasicAuthenticationEncode := AContext.Request.Headers[FAuthorizationHeaderName];
|
||||
if LBasicAuthenticationEncode.IsEmpty then
|
||||
begin
|
||||
RenderError(HTTP_STATUS.Unauthorized, 'Username and password Required', AContext);
|
||||
AHandled := True;
|
||||
Exit;
|
||||
LUsername := TNetEncoding.URL.Decode(AContext.Request.Headers[FUserNameHeaderName]);
|
||||
LPassword := TNetEncoding.URL.Decode(AContext.Request.Headers[FPasswordHeaderName]);
|
||||
if (LUsername.IsEmpty) or (LPassword.IsEmpty) then
|
||||
begin
|
||||
RenderError(HTTP_STATUS.Unauthorized, 'Username and password Required', AContext);
|
||||
AHandled := True;
|
||||
Exit;
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if not LBasicAuthenticationEncode.StartsWith('basic', True) then
|
||||
begin
|
||||
RenderError(HTTP_STATUS.Unauthorized, 'Invalid authorization type', AContext);
|
||||
AHandled := True;
|
||||
Exit;
|
||||
end;
|
||||
LBasicAuthenticationDecode := TStringList.Create;
|
||||
try
|
||||
LBasicAuthenticationDecode.Delimiter := ':';
|
||||
LBasicAuthenticationDecode.DelimitedText := TBase64Encoding.Base64.Decode(LBasicAuthenticationEncode.Replace('basic ', '', [rfIgnoreCase]));
|
||||
LUsername := LBasicAuthenticationDecode.Strings[0];
|
||||
LPassword := LBasicAuthenticationDecode.Strings[1];
|
||||
finally
|
||||
LBasicAuthenticationDecode.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
// check the authorization for the requested resource
|
||||
RolesList := TList<string>.Create;
|
||||
LRolesList := TList<string>.Create;
|
||||
try
|
||||
SessionData := TSessionData.Create;
|
||||
LSessionData := TSessionData.Create;
|
||||
try
|
||||
try
|
||||
FAuthenticationHandler.OnAuthentication(AContext, UserName, Password, RolesList, IsValid, SessionData);
|
||||
if IsValid then
|
||||
FAuthenticationHandler.OnAuthentication(AContext, LUsername, LPassword, LRolesList, LIsValid, LSessionData);
|
||||
if LIsValid then
|
||||
begin
|
||||
JWTValue := TJWT.Create(FSecret, FLeewaySeconds);
|
||||
LJWTValue := TJWT.Create(FSecret, FLeewaySeconds);
|
||||
try
|
||||
// let's user config claims and custom claims
|
||||
if not Assigned(FSetupJWTClaims) then
|
||||
raise EMVCJWTException.Create('SetupJWTClaims not set');
|
||||
|
||||
FSetupJWTClaims(JWTValue);
|
||||
FSetupJWTClaims(LJWTValue);
|
||||
|
||||
// these claims are mandatory and managed by the middleware
|
||||
if not JWTValue.CustomClaims['username'].IsEmpty then
|
||||
raise EMVCJWTException.Create
|
||||
('Custom claim "username" is reserved and cannot be modified in the JWT setup');
|
||||
if not LJWTValue.CustomClaims['username'].IsEmpty then
|
||||
raise EMVCJWTException.Create('Custom claim "username" is reserved and cannot be modified in the JWT setup');
|
||||
|
||||
if not JWTValue.CustomClaims['roles'].IsEmpty then
|
||||
raise EMVCJWTException.Create
|
||||
('Custom claim "roles" is reserved and cannot be modified in the JWT setup');
|
||||
if not LJWTValue.CustomClaims['roles'].IsEmpty then
|
||||
raise EMVCJWTException.Create('Custom claim "roles" is reserved and cannot be modified in the JWT setup');
|
||||
|
||||
JWTValue.CustomClaims['username'] := UserName;
|
||||
JWTValue.CustomClaims['roles'] := string.Join(',', RolesList.ToArray);
|
||||
LJWTValue.CustomClaims['username'] := LUsername;
|
||||
LJWTValue.CustomClaims['roles'] := string.Join(',', LRolesList.ToArray);
|
||||
|
||||
if JWTValue.LiveValidityWindowInSeconds > 0 then
|
||||
begin
|
||||
if NeedsToBeExtended(JWTValue) then
|
||||
begin
|
||||
ExtendExpirationTime(JWTValue);
|
||||
end;
|
||||
end;
|
||||
if LJWTValue.LiveValidityWindowInSeconds > 0 then
|
||||
if NeedsToBeExtended(LJWTValue) then
|
||||
ExtendExpirationTime(LJWTValue);
|
||||
|
||||
// setup the current logged user from the JWT
|
||||
AContext.LoggedUser.Roles.AddRange(RolesList);
|
||||
AContext.LoggedUser.UserName := JWTValue.CustomClaims['username'];
|
||||
AContext.LoggedUser.LoggedSince := JWTValue.Claims.IssuedAt;
|
||||
AContext.LoggedUser.Realm := JWTValue.Claims.Subject;
|
||||
AContext.LoggedUser.Roles.AddRange(LRolesList);
|
||||
AContext.LoggedUser.UserName := LJWTValue.CustomClaims['username'];
|
||||
AContext.LoggedUser.LoggedSince := LJWTValue.Claims.IssuedAt;
|
||||
AContext.LoggedUser.Realm := LJWTValue.Claims.Subject;
|
||||
|
||||
if SessionData.Count > 0 then
|
||||
if LSessionData.Count > 0 then
|
||||
begin
|
||||
AContext.LoggedUser.CustomData := TMVCCustomData.Create;
|
||||
for lCustomPair in SessionData do
|
||||
for LCustomPair in LSessionData do
|
||||
begin
|
||||
AContext.LoggedUser.CustomData.AddOrSetValue(lCustomPair.Key, lCustomPair.Value);
|
||||
if not JWTValue.CustomClaims.Items[lCustomPair.Key].IsEmpty then
|
||||
raise EMVCJWTException.CreateFmt('JWT Error: "%s" is a reserved key name', [lCustomPair.Key]);
|
||||
JWTValue.CustomClaims.Items[lCustomPair.Key] := lCustomPair.Value;
|
||||
AContext.LoggedUser.CustomData.AddOrSetValue(LCustomPair.Key, LCustomPair.Value);
|
||||
if not LJWTValue.CustomClaims.Items[LCustomPair.Key].IsEmpty then
|
||||
raise EMVCJWTException.CreateFmt('JWT Error: "%s" is a reserved key name', [LCustomPair.Key]);
|
||||
LJWTValue.CustomClaims.Items[LCustomPair.Key] := LCustomPair.Value;
|
||||
end;
|
||||
end;
|
||||
|
||||
LObj := TJDOJsonObject.Create;
|
||||
LJsonObject := TJDOJsonObject.Create;
|
||||
try
|
||||
LObj.S['token'] := JWTValue.GetToken;
|
||||
InternalRender(LObj, TMVCMediaType.APPLICATION_JSON, TMVCConstants.DEFAULT_CONTENT_CHARSET,
|
||||
AContext, False);
|
||||
LJsonObject.S['token'] := LJWTValue.GetToken;
|
||||
InternalRender(LJsonObject, TMVCMediaType.APPLICATION_JSON, TMVCConstants.DEFAULT_CONTENT_CHARSET, AContext, False);
|
||||
finally
|
||||
LObj.Free;
|
||||
LJsonObject.Free;
|
||||
end;
|
||||
AHandled := True;
|
||||
finally
|
||||
JWTValue.Free;
|
||||
LJWTValue.Free;
|
||||
end;
|
||||
end
|
||||
else
|
||||
@ -367,10 +379,10 @@ begin
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
SessionData.Free;
|
||||
LSessionData.Free;
|
||||
end;
|
||||
finally
|
||||
RolesList.Free;
|
||||
LRolesList.Free;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@ -378,37 +390,29 @@ end;
|
||||
procedure TMVCJWTAuthenticationMiddleware.RenderError(const AHTTPStatusCode: UInt16; const AErrorMessage: string;
|
||||
const AContext: TWebContext; const AErrorClassName: string; const AErrorNumber: Integer);
|
||||
var
|
||||
lJObj: TJDOJsonObject;
|
||||
lStatus: string;
|
||||
LJObj: TJDOJsonObject;
|
||||
LStatus: string;
|
||||
begin
|
||||
AContext.Response.StatusCode := AHTTPStatusCode;
|
||||
AContext.Response.ReasonString := AErrorMessage;
|
||||
|
||||
lStatus := 'error';
|
||||
LStatus := 'error';
|
||||
if (AHTTPStatusCode div 100) = 2 then
|
||||
lStatus := 'ok';
|
||||
LStatus := 'ok';
|
||||
|
||||
lJObj := TJDOJsonObject.Create;
|
||||
lJObj.S['status'] := lStatus;
|
||||
lJObj.I['statuscode'] := AHTTPStatusCode;
|
||||
lJObj.S['message'] := AErrorMessage;
|
||||
|
||||
if AErrorClassName = '' then
|
||||
begin
|
||||
lJObj.Values['classname'] := nil
|
||||
end
|
||||
else
|
||||
begin
|
||||
lJObj.S['classname'] := AErrorClassName;
|
||||
end;
|
||||
LJObj := TJDOJsonObject.Create;
|
||||
LJObj.S['status'] := LStatus;
|
||||
LJObj.I['statuscode'] := AHTTPStatusCode;
|
||||
LJObj.S['message'] := AErrorMessage;
|
||||
|
||||
LJObj.Values['classname'] := nil;
|
||||
if AErrorClassName <> '' then
|
||||
LJObj.S['classname'] := AErrorClassName;
|
||||
|
||||
if AErrorNumber <> 0 then
|
||||
begin
|
||||
lJObj.I['errornumber'] := AErrorNumber;
|
||||
end;
|
||||
LJObj.I['errornumber'] := AErrorNumber;
|
||||
|
||||
InternalRender(lJObj, TMVCConstants.DEFAULT_CONTENT_TYPE, TMVCConstants.DEFAULT_CONTENT_CHARSET, AContext);
|
||||
InternalRender(LJObj, TMVCConstants.DEFAULT_CONTENT_TYPE, TMVCConstants.DEFAULT_CONTENT_CHARSET, AContext);
|
||||
end;
|
||||
|
||||
end.
|
||||
|
Loading…
Reference in New Issue
Block a user