mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-15 07:45:54 +01:00
Added TEXT serializer - Improved content type handling in case of errors and no_route_found cases.
This commit is contained in:
parent
f9076a4732
commit
02c0ae0f37
@ -458,6 +458,7 @@ implementation
|
||||
|
||||
uses
|
||||
Data.FmtBcd,
|
||||
Data.SqlTimSt,
|
||||
MVCFramework.Nullables,
|
||||
System.Generics.Defaults;
|
||||
|
||||
@ -1195,6 +1196,11 @@ begin
|
||||
begin
|
||||
aRTTIField.SetValue(AObject, AField.AsDateTime);
|
||||
end;
|
||||
ftTimeStampOffset:
|
||||
begin
|
||||
aRTTIField.SetValue(AObject,
|
||||
TValue.From<TSQLTimeStampOffset>(AField.AsSQLTimeStampOffset));
|
||||
end;
|
||||
ftBoolean:
|
||||
begin
|
||||
aRTTIField.SetValue(AObject, AField.AsBoolean);
|
||||
|
@ -313,7 +313,8 @@ function TMVCHTMLSerializer.SerializeObject(const AObject: TObject;
|
||||
const ASerializationAction: TMVCSerializationAction): string;
|
||||
function EmitExceptionClass(const ClazzName, Message: string): string;
|
||||
begin
|
||||
Result := Result + '<h2>' + HTMLEntitiesEncode(ClazzName) + ': ' + Message + '</h2>';
|
||||
Result := Result + '<h2>' +
|
||||
IfThen(not ClazzName.IsEmpty, HTMLEntitiesEncode(ClazzName) + ': ') + Message + '</h2>';
|
||||
end;
|
||||
function EmitTitle(const HTTPStatusCode: Word): string;
|
||||
begin
|
||||
|
372
sources/MVCFramework.Serializer.Text.pas
Normal file
372
sources/MVCFramework.Serializer.Text.pas
Normal file
@ -0,0 +1,372 @@
|
||||
// ***************************************************************************
|
||||
//
|
||||
// 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.Text;
|
||||
|
||||
{$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,
|
||||
MVCFramework.Commons,
|
||||
MVCFramework.Serializer.Intf,
|
||||
MVCFramework.Serializer.Abstract,
|
||||
MVCFramework.DuckTyping,
|
||||
MVCFramework.Serializer.Commons;
|
||||
|
||||
type
|
||||
TMVCTextSerializer = 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;
|
||||
|
||||
{ TMVCTextSerializer }
|
||||
|
||||
procedure TMVCTextSerializer.AfterConstruction;
|
||||
begin
|
||||
inherited AfterConstruction;
|
||||
end;
|
||||
|
||||
procedure TMVCTextSerializer.DeserializeCollection(
|
||||
const ASerializedList: string; const AList: IInterface; const AClazz: TClass;
|
||||
const AType: TMVCSerializationType;
|
||||
const AIgnoredAttributes: TMVCIgnoredList);
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
procedure TMVCTextSerializer.DeserializeCollection(
|
||||
const ASerializedList: string; const AList: TObject; const AClazz: TClass;
|
||||
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList;
|
||||
const ARootNode: string);
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
procedure TMVCTextSerializer.DeserializeDataSet(
|
||||
const ASerializedDataSet: string; const ADataSet: TDataSet;
|
||||
const AIgnoredFields: TMVCIgnoredList; const ANameCase: TMVCNameCase);
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
procedure TMVCTextSerializer.DeserializeDataSetRecord(
|
||||
const ASerializedDataSetRecord: string; const ADataSet: TDataSet;
|
||||
const AIgnoredFields: TMVCIgnoredList; const ANameCase: TMVCNameCase);
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
procedure TMVCTextSerializer.DeserializeObject(const ASerializedObject: string;
|
||||
const AObject: TObject; const AType: TMVCSerializationType;
|
||||
const AIgnoredAttributes: TMVCIgnoredList; const ARootNode: string);
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
procedure TMVCTextSerializer.DeserializeObject(const ASerializedObject: string;
|
||||
const AObject: IInterface; const AType: TMVCSerializationType;
|
||||
const AIgnoredAttributes: TMVCIgnoredList);
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
procedure TMVCTextSerializer.RaiseNotImplemented;
|
||||
begin
|
||||
raise EMVCException.Create('Not Implemented');
|
||||
end;
|
||||
|
||||
procedure TMVCTextSerializer.RegisterTypeSerializer(const ATypeInfo: PTypeInfo;
|
||||
AInstance: IMVCTypeSerializer);
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
function TMVCTextSerializer.SerializeCollection(const AList: TObject;
|
||||
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList;
|
||||
const ASerializationAction: TMVCSerializationAction): string;
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
function TMVCTextSerializer.SerializeArrayOfRecord(
|
||||
var ATValueContainingAnArray: TValue; const AType: TMVCSerializationType;
|
||||
const AIgnoredAttributes: TMVCIgnoredList;
|
||||
const ASerializationAction: TMVCSerializationAction): string;
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
function TMVCTextSerializer.SerializeCollection(const AList: IInterface;
|
||||
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList;
|
||||
const ASerializationAction: TMVCSerializationAction): string;
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
function TMVCTextSerializer.SerializeDataSet(const ADataSet: TDataSet;
|
||||
const AIgnoredFields: TMVCIgnoredList; const ANameCase: TMVCNameCase;
|
||||
const ASerializationAction: TMVCDatasetSerializationAction): string;
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
function TMVCTextSerializer.SerializeDataSetRecord(const ADataSet: TDataSet;
|
||||
const AIgnoredFields: TMVCIgnoredList; const ANameCase: TMVCNameCase;
|
||||
const ASerializationAction: TMVCDatasetSerializationAction): string;
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
function TMVCTextSerializer.SerializeObject(const AObject: TObject;
|
||||
const AType: TMVCSerializationType; const AIgnoredAttributes: TMVCIgnoredList;
|
||||
const ASerializationAction: TMVCSerializationAction): string;
|
||||
function EmitExceptionClass(const ClazzName, Message: string): string;
|
||||
begin
|
||||
Result := Result + IfThen(not ClazzName.IsEmpty, ClazzName + ': ') + Message;
|
||||
end;
|
||||
function EmitTitle(const HTTPStatusCode: Word): string;
|
||||
begin
|
||||
Result := HTTPStatusCode.ToString + ': ' + HTTP_STATUS.ReasonStringFor(HTTPStatusCode);
|
||||
end;
|
||||
|
||||
function GetText(
|
||||
const HTTPStatusCode: Integer;
|
||||
const Message: String;
|
||||
const DetailedMessage: String;
|
||||
const ClazzName: String;
|
||||
const AppErrorCode: Integer;
|
||||
const ErrorItems: TArray<String>): String;
|
||||
var
|
||||
lErr: String;
|
||||
begin
|
||||
Result :=
|
||||
EmitTitle(HTTPStatusCode) +
|
||||
IfThen(not ClazzName.IsEmpty, sLineBreak + EmitExceptionClass(ClazzName, Message)) +
|
||||
IfThen(not DetailedMessage.IsEmpty, sLineBreak + DetailedMessage);
|
||||
if Assigned(FConfig) then
|
||||
begin
|
||||
if FConfig[TMVCConfigKey.ExposeServerSignature] = 'true' then
|
||||
begin
|
||||
Result := Result + sLineBreak + FConfig[TMVCConfigKey.ServerName];
|
||||
end;
|
||||
end;
|
||||
if AppErrorCode <> 0 then
|
||||
begin
|
||||
Result := sLineBreak + Result + 'Application Error Code: ' + AppErrorCode.ToString + sLineBreak;
|
||||
end;
|
||||
if Assigned(ErrorItems) and (Length(ErrorItems) > 0) then
|
||||
begin
|
||||
Result := sLineBreak + Result + 'Error Items: ';
|
||||
for lErr in ErrorItems do
|
||||
begin
|
||||
Result := sLineBreak + Result + '- ' + lErr;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
var
|
||||
lMVCException: EMVCException;
|
||||
lException: Exception;
|
||||
lErrResponse: TMVCErrorResponse;
|
||||
begin
|
||||
if AObject is Exception then
|
||||
begin
|
||||
if AObject is EMVCException then
|
||||
begin
|
||||
lMVCException := EMVCException(AObject);
|
||||
Result := GetText(
|
||||
lMVCException.HTTPStatusCode,
|
||||
lMVCException.Message,
|
||||
lMVCException.DetailedMessage,
|
||||
lMVCException.ClassName,
|
||||
lMVCException.ApplicationErrorCode,
|
||||
lMVCException.ErrorItems);
|
||||
end
|
||||
else
|
||||
begin
|
||||
lException := Exception(AObject);
|
||||
Result := EmitTitle(500) + sLineBreak +
|
||||
EmitExceptionClass(lException.ClassName, lException.Message) + sLineBreak;
|
||||
end;
|
||||
end else if AObject is TMVCErrorResponse then
|
||||
begin
|
||||
lErrResponse := TMVCErrorResponse(AObject);
|
||||
Result := GetText(
|
||||
lErrResponse.StatusCode,
|
||||
lErrResponse.Message,
|
||||
lErrResponse.DetailedMessage,
|
||||
lErrResponse.ClassName,
|
||||
lErrResponse.AppErrorCode,
|
||||
nil);
|
||||
end;
|
||||
|
||||
if Result.IsEmpty then
|
||||
begin
|
||||
RaiseNotImplemented
|
||||
end;
|
||||
end;
|
||||
|
||||
function TMVCTextSerializer.SerializeObject(const AObject: IInterface;
|
||||
const AType: TMVCSerializationType;
|
||||
const AIgnoredAttributes: TMVCIgnoredList;
|
||||
const ASerializationAction: TMVCSerializationAction): string;
|
||||
begin
|
||||
RaiseNotImplemented;
|
||||
end;
|
||||
|
||||
function TMVCTextSerializer.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.
|
@ -397,6 +397,9 @@ type
|
||||
|
||||
function Accept: string;
|
||||
function BestAccept: string;
|
||||
function AcceptHTML: boolean;
|
||||
function CanAcceptMediaType(const MediaType: String): boolean;
|
||||
|
||||
function ContentParam(const AName: string): string;
|
||||
function Cookie(const AName: string): string;
|
||||
function Body: string;
|
||||
@ -643,7 +646,7 @@ type
|
||||
procedure Redirect(const AUrl: string);
|
||||
procedure ResponseStatus(const AStatusCode: Integer; const AReasonString: string = '');
|
||||
procedure Render201Created(const Location: string = '');
|
||||
// Serializer access
|
||||
// Serializers access
|
||||
function Serializer: IMVCSerializer; overload;
|
||||
function Serializer(const AContentType: string; const ARaiseExcpIfNotExists: Boolean = True)
|
||||
: IMVCSerializer; overload;
|
||||
@ -1040,6 +1043,7 @@ type
|
||||
procedure OnWebContextDestroy(const WebContextDestroyEvent: TWebContextDestroyEvent);
|
||||
{ end - webcontext events}
|
||||
|
||||
function Serializer(const AContentType: string; const ARaiseExceptionIfNotExists: Boolean = True): IMVCSerializer;
|
||||
function AddSerializer(const AContentType: string; const ASerializer: IMVCSerializer)
|
||||
: TMVCEngine;
|
||||
function AddMiddleware(const AMiddleware: IMVCMiddleware): TMVCEngine;
|
||||
@ -1053,15 +1057,13 @@ type
|
||||
function SetViewEngine(const AViewEngineClass: TMVCViewEngineClass): TMVCEngine;
|
||||
function SetExceptionHandler(const AExceptionHandlerProc: TMVCExceptionHandlerProc): TMVCEngine;
|
||||
|
||||
procedure HTTP404(const AContext: TWebContext);
|
||||
procedure HTTP500(const AContext: TWebContext; const AReasonString: string = '');
|
||||
procedure SendRawHTTPStatus(const AContext: TWebContext; const HTTPStatusCode: Integer;
|
||||
const AReasonString: string; const AClassName: string = '');
|
||||
procedure SendHTTPStatus(const AContext: TWebContext; const HTTPStatusCode: Integer;
|
||||
const AReasonString: string = ''; const AClassName: string = '');
|
||||
|
||||
property ViewEngineClass: TMVCViewEngineClass read GetViewEngineClass;
|
||||
property WebModule: TWebModule read FWebModule;
|
||||
property Config: TMVCConfig read FConfig;
|
||||
property Serializers: TDictionary<string, IMVCSerializer> read FSerializers;
|
||||
//property Serializers: TDictionary<string, IMVCSerializer> read FSerializers;
|
||||
property Middlewares: TList<IMVCMiddleware> read FMiddlewares;
|
||||
property Controllers: TObjectList<TMVCControllerDelegate> read FControllers;
|
||||
property ApplicationSession: TWebApplicationSession read FApplicationSession
|
||||
@ -1254,8 +1256,9 @@ uses
|
||||
MVCFramework.JSONRPC,
|
||||
MVCFramework.Router,
|
||||
MVCFramework.Rtti.Utils,
|
||||
MVCFramework.Serializer.HTML, MVCFramework.Serializer.Abstract,
|
||||
MVCFramework.Utils;
|
||||
MVCFramework.Serializer.HTML,
|
||||
MVCFramework.Serializer.Abstract,
|
||||
MVCFramework.Utils, MVCFramework.Serializer.Text;
|
||||
|
||||
var
|
||||
gIsShuttingDown: Boolean = False;
|
||||
@ -1424,6 +1427,11 @@ begin
|
||||
Result := FWebRequest.Accept;
|
||||
end;
|
||||
|
||||
function TMVCWebRequest.AcceptHTML: boolean;
|
||||
begin
|
||||
Result := CanAcceptMediaType(TMVCMediaType.TEXT_HTML);
|
||||
end;
|
||||
|
||||
function TMVCWebRequest.BestAccept: string;
|
||||
begin
|
||||
if Accept.Contains(',') then
|
||||
@ -1556,6 +1564,11 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function TMVCWebRequest.CanAcceptMediaType(const MediaType: String): boolean;
|
||||
begin
|
||||
Result := Accept.Contains(MediaType);
|
||||
end;
|
||||
|
||||
function TMVCWebRequest.ClientIp: string;
|
||||
var
|
||||
lValue: string;
|
||||
@ -2788,7 +2801,7 @@ begin
|
||||
begin
|
||||
lContext.Response.StatusCode := http_status.NotFound;
|
||||
lContext.Response.ReasonString := 'Not Found';
|
||||
lContext.Response.SetContentStream(TStringStream.Create(), FConfigCache_DefaultContentType);
|
||||
SendHTTPStatus(lContext, HTTP_STATUS.NotFound);
|
||||
fOnRouterLog(lRouter, rlsRouteNotFound, lContext);
|
||||
end
|
||||
else
|
||||
@ -2823,7 +2836,7 @@ begin
|
||||
end
|
||||
else
|
||||
begin
|
||||
SendRawHTTPStatus(lContext, E.HTTPStatusCode, E.Message, E.Classname);
|
||||
SendHTTPStatus(lContext, E.HTTPStatusCode, E.Message, E.Classname);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@ -2840,7 +2853,7 @@ begin
|
||||
end
|
||||
else
|
||||
begin
|
||||
SendRawHTTPStatus(lContext, http_status.InternalServerError,
|
||||
SendHTTPStatus(lContext, http_status.InternalServerError,
|
||||
Format('[%s] %s', [EIO.Classname, EIO.Message]), EIO.Classname);
|
||||
end;
|
||||
end;
|
||||
@ -2858,7 +2871,7 @@ begin
|
||||
end
|
||||
else
|
||||
begin
|
||||
SendRawHTTPStatus(lContext, http_status.InternalServerError,
|
||||
SendHTTPStatus(lContext, http_status.InternalServerError,
|
||||
Format('[%s] %s', [Ex.Classname, Ex.Message]), Ex.Classname);
|
||||
end;
|
||||
end;
|
||||
@ -2881,7 +2894,7 @@ begin
|
||||
end
|
||||
else
|
||||
begin
|
||||
SendRawHTTPStatus(lContext, http_status.InternalServerError,
|
||||
SendHTTPStatus(lContext, http_status.InternalServerError,
|
||||
Format('[%s] %s', [Ex.Classname, Ex.Message]), Ex.Classname);
|
||||
end;
|
||||
end;
|
||||
@ -3340,76 +3353,59 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMVCEngine.HTTP404(const AContext: TWebContext);
|
||||
begin
|
||||
AContext.Response.SetStatusCode(http_status.NotFound);
|
||||
AContext.Response.SetContentType(BuildContentType(TMVCMediaType.TEXT_PLAIN,
|
||||
AContext.Config[TMVCConfigKey.DefaultContentCharset]));
|
||||
AContext.Response.SetReasonString('Not Found');
|
||||
AContext.Response.SetContent('Not Found' + sLineBreak + FConfigCache_ServerSignature);
|
||||
end;
|
||||
|
||||
procedure TMVCEngine.HTTP500(const AContext: TWebContext; const AReasonString: string);
|
||||
begin
|
||||
AContext.Response.SetStatusCode(http_status.InternalServerError);
|
||||
AContext.Response.SetContentType(BuildContentType(TMVCMediaType.TEXT_PLAIN,
|
||||
AContext.Config[TMVCConfigKey.DefaultContentCharset]));
|
||||
AContext.Response.SetReasonString('Internal server error');
|
||||
AContext.Response.SetContent('Internal server error' + sLineBreak + FConfigCache_ServerSignature +
|
||||
': ' + AReasonString);
|
||||
end;
|
||||
|
||||
procedure TMVCEngine.SendRawHTTPStatus(const AContext: TWebContext; const HTTPStatusCode: Integer;
|
||||
procedure TMVCEngine.SendHTTPStatus(const AContext: TWebContext; const HTTPStatusCode: Integer;
|
||||
const AReasonString: string; const AClassName: string);
|
||||
var
|
||||
lSer: IMVCSerializer; lError: TMVCErrorResponse;
|
||||
lIgnored: TMVCIgnoredList;
|
||||
lContentType, lItem: String;
|
||||
lPreferredAcceptContentType: TArray<string>;
|
||||
begin
|
||||
lPreferredAcceptContentType := [
|
||||
AContext.Request.BestAccept,
|
||||
FConfigCache_DefaultContentType,
|
||||
TMVCMediaType.TEXT_HTML,
|
||||
TMVCMediaType.TEXT_PLAIN];
|
||||
|
||||
lError := TMVCErrorResponse.Create;
|
||||
try
|
||||
lError.Classname := AClassName;
|
||||
lError.StatusCode := HTTPStatusCode;
|
||||
lError.Message := AReasonString;
|
||||
lError.Message := IfThen(not AReasonString.IsEmpty, AReasonString, HTTP_STATUS.ReasonStringFor(HTTPStatusCode));
|
||||
|
||||
if AContext.Request.ClientPreferHTML then
|
||||
lIgnored := ['ObjectDictionary'];
|
||||
if lError.fAppErrorCode = 0 then
|
||||
lIgnored := lIgnored + ['AppErrorCode'];
|
||||
if lError.Data = nil then
|
||||
lIgnored := lIgnored + ['Data'];
|
||||
if lError.Classname.IsEmpty then
|
||||
lIgnored := lIgnored + ['ClassName'];
|
||||
if lError.DetailedMessage.IsEmpty then
|
||||
lIgnored := lIgnored + ['DetailedMessage'];
|
||||
if lError.Items.Count = 0 then
|
||||
begin
|
||||
if not Serializers.TryGetValue(TMVCMediaType.TEXT_HTML, lSer) then
|
||||
begin
|
||||
raise EMVCConfigException.Create('Cannot find HTML serializer');
|
||||
end;
|
||||
AContext.Response.SetContent(lSer.SerializeObject(lError));
|
||||
AContext.Response.SetContentType(BuildContentType(TMVCMediaType.TEXT_HTML,
|
||||
AContext.Config[TMVCConfigKey.DefaultContentCharset]));
|
||||
end
|
||||
else if AContext.Request.ClientPrefer(AContext.Config[TMVCConfigKey.DefaultContentType]) and
|
||||
Serializers.TryGetValue(AContext.Config[TMVCConfigKey.DefaultContentType], lSer) then
|
||||
begin
|
||||
lIgnored := ['ObjectDictionary'];
|
||||
if lError.fAppErrorCode = 0 then
|
||||
lIgnored := lIgnored + ['AppErrorCode'];
|
||||
if lError.Data = nil then
|
||||
lIgnored := lIgnored + ['Data'];
|
||||
if lError.DetailedMessage.IsEmpty then
|
||||
lIgnored := lIgnored + ['DetailedMessage'];
|
||||
if lError.Items.Count = 0 then
|
||||
begin
|
||||
lIgnored := lIgnored + ['Items'];
|
||||
end;
|
||||
|
||||
AContext.Response.SetContent(lSer.SerializeObject(lError, stDefault, lIgnored));
|
||||
AContext.Response.SetContentType
|
||||
(BuildContentType(AContext.Config[TMVCConfigKey.DefaultContentType],
|
||||
AContext.Config[TMVCConfigKey.DefaultContentCharset]));
|
||||
end
|
||||
else
|
||||
begin
|
||||
AContext.Response.SetContentType(BuildContentType(TMVCMediaType.TEXT_PLAIN,
|
||||
AContext.Config[TMVCConfigKey.DefaultContentCharset]));
|
||||
AContext.Response.SetContent(FConfigCache_ServerSignature + sLineBreak + 'HTTP ' +
|
||||
HTTPStatusCode.ToString + ': ' + AReasonString);
|
||||
lIgnored := lIgnored + ['Items'];
|
||||
end;
|
||||
|
||||
for lItem in lPreferredAcceptContentType do
|
||||
begin
|
||||
lSer := Serializer(lItem, False);
|
||||
if lSer <> nil then
|
||||
begin
|
||||
lContentType := lItem;
|
||||
Break;
|
||||
end;
|
||||
end;
|
||||
if lSer = nil then
|
||||
begin
|
||||
raise EMVCConfigException.Create('Cannot find a proper serializer among ' + string.Join(',', lPreferredAcceptContentType));
|
||||
end;
|
||||
|
||||
AContext.Response.SetContentType(
|
||||
BuildContentType(lItem, AContext.Config[TMVCConfigKey.DefaultContentCharset]));
|
||||
AContext.Response.SetContent(lSer.SerializeObject(lError, stDefault, lIgnored));
|
||||
AContext.Response.SetStatusCode(HTTPStatusCode);
|
||||
AContext.Response.SetReasonString(AReasonString);
|
||||
AContext.Response.SetReasonString(lError.Message);
|
||||
finally
|
||||
lError.Free;
|
||||
end;
|
||||
@ -3515,6 +3511,13 @@ begin
|
||||
begin
|
||||
FSerializers.Add(lDefaultSerializerContentType, TMVCHTMLSerializer.Create(Config));
|
||||
end;
|
||||
|
||||
// this is used only for TMVCError (dt 2024-02-22)
|
||||
lDefaultSerializerContentType := BuildContentType(TMVCMediaType.TEXT_PLAIN, '');
|
||||
if not FSerializers.ContainsKey(lDefaultSerializerContentType) then
|
||||
begin
|
||||
FSerializers.Add(lDefaultSerializerContentType, TMVCTextSerializer.Create(Config));
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMVCEngine.ResponseErrorPage(const AException: Exception; const ARequest: TWebRequest;
|
||||
@ -3576,6 +3579,30 @@ begin
|
||||
Result := ASessionId;
|
||||
end;
|
||||
|
||||
function TMVCEngine.Serializer(const AContentType: string; const ARaiseExceptionIfNotExists: Boolean): IMVCSerializer;
|
||||
var
|
||||
lContentMediaType: string;
|
||||
lContentCharSet: string;
|
||||
begin
|
||||
SplitContentMediaTypeAndCharset(AContentType.ToLower, lContentMediaType, lContentCharSet);
|
||||
if FSerializers.ContainsKey(lContentMediaType) then
|
||||
begin
|
||||
Result := FSerializers.Items[lContentMediaType];
|
||||
end
|
||||
else
|
||||
begin
|
||||
if ARaiseExceptionIfNotExists then
|
||||
begin
|
||||
raise EMVCException.CreateFmt('The serializer for %s could not be found. [HINT] Register on TMVCEngine instance using "AddSerializer" method.',
|
||||
[lContentMediaType]);
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TMVCEngine.SetExceptionHandler(const AExceptionHandlerProc: TMVCExceptionHandlerProc)
|
||||
: TMVCEngine;
|
||||
begin
|
||||
@ -4163,26 +4190,8 @@ end;
|
||||
function TMVCRenderer.Serializer(
|
||||
const AContentType: string;
|
||||
const ARaiseExceptionIfNotExists: Boolean): IMVCSerializer;
|
||||
var lContentMediaType: string;
|
||||
lContentCharSet: string;
|
||||
begin
|
||||
SplitContentMediaTypeAndCharset(AContentType.ToLower, lContentMediaType, lContentCharSet);
|
||||
if Engine.Serializers.ContainsKey(lContentMediaType) then
|
||||
begin
|
||||
Result := Engine.Serializers.Items[lContentMediaType];
|
||||
end
|
||||
else
|
||||
begin
|
||||
if ARaiseExceptionIfNotExists then
|
||||
begin
|
||||
raise EMVCException.CreateFmt('The serializer for %s could not be found. [HINT] Register on TMVCEngine instance using "AddSerializer" method.',
|
||||
[lContentMediaType]);
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
end;
|
||||
Result := Engine.Serializer(AContentType, ARaiseExceptionIfNotExists);
|
||||
end;
|
||||
|
||||
function TMVCController.SessionAs<T>: T;
|
||||
@ -4528,13 +4537,11 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
if Serializer(GetContentType, False) = nil then
|
||||
begin
|
||||
if Serializer(FContext.Request.BestAccept, False) <> nil then
|
||||
GetContext.Response.ContentType := FContext.Request.BestAccept
|
||||
else
|
||||
GetContext.Response.ContentType := GetConfig[TMVCConfigKey.DefaultContentType];
|
||||
end;
|
||||
if Serializer(FContext.Request.BestAccept, False) <> nil then
|
||||
GetContext.Response.ContentType := FContext.Request.BestAccept
|
||||
else
|
||||
GetContext.Response.ContentType := Engine.FConfigCache_DefaultContentType;
|
||||
|
||||
Render(R, False, nil, R.GetIgnoredList);
|
||||
finally
|
||||
R.Free;
|
||||
|
@ -32,7 +32,7 @@ uses
|
||||
MVCFramework.RESTClient,
|
||||
MVCFramework.JSONRPC.Client,
|
||||
System.DateUtils,
|
||||
System.Hash, System.Rtti;
|
||||
System.Hash, System.Rtti, MVCFramework.Commons;
|
||||
|
||||
type
|
||||
|
||||
@ -237,6 +237,23 @@ type
|
||||
[Category('renders,exceptions')]
|
||||
procedure TestEMVCException4;
|
||||
|
||||
[Test]
|
||||
[Category('renders,exceptions')]
|
||||
[TestCase('404'+'invalid_accept', '404,/invalidurl,invalid_accept,' + TMVCMediaType.APPLICATION_JSON)]
|
||||
[TestCase('404'+TMVCMediaType.TEXT_HTML, '404,/invalidurl,' + TMVCMediaType.TEXT_HTML + ',' + TMVCMediaType.TEXT_HTML)]
|
||||
[TestCase('404'+TMVCMediaType.TEXT_PLAIN, '404,/invalidurl,' + TMVCMediaType.TEXT_PLAIN + ',' + TMVCMediaType.TEXT_PLAIN)]
|
||||
[TestCase('404'+TMVCMediaType.APPLICATION_JSON, '404,/invalidurl,' + TMVCMediaType.APPLICATION_JSON + ',' + TMVCMediaType.APPLICATION_JSON)]
|
||||
[TestCase('500'+'invalid_accept', '500,/exception/emvcexception1,invalid_accept,' + TMVCMediaType.APPLICATION_JSON)]
|
||||
[TestCase('500'+TMVCMediaType.TEXT_HTML, '500,/exception/emvcexception1,' + TMVCMediaType.TEXT_HTML + ',' + TMVCMediaType.TEXT_HTML)]
|
||||
[TestCase('500'+TMVCMediaType.TEXT_PLAIN, '500,/exception/emvcexception1,' + TMVCMediaType.TEXT_PLAIN + ',' + TMVCMediaType.TEXT_PLAIN)]
|
||||
[TestCase('500'+TMVCMediaType.APPLICATION_JSON, '500,/exception/emvcexception1,' + TMVCMediaType.APPLICATION_JSON + ',' + TMVCMediaType.APPLICATION_JSON)]
|
||||
|
||||
procedure TestResponseContentTypes(
|
||||
const ExpectedStatus: Integer;
|
||||
const URL: String;
|
||||
const RequestAccept: String;
|
||||
const ResponseContentType: String);
|
||||
|
||||
// test nullables
|
||||
[Test]
|
||||
procedure TestDeserializeNullablesWithValue;
|
||||
@ -449,7 +466,6 @@ uses
|
||||
MVCFramework.Serializer.Defaults,
|
||||
JsonDataObjects,
|
||||
MVCFramework.Serializer.JsonDataObjects,
|
||||
MVCFramework.Commons,
|
||||
System.SyncObjs,
|
||||
System.Generics.Collections,
|
||||
System.SysUtils,
|
||||
@ -1878,6 +1894,22 @@ begin
|
||||
Assert.areEqual(HTTP_STATUS.OK, lRes.StatusCode);
|
||||
end;
|
||||
|
||||
procedure TServerTest.TestResponseContentTypes(
|
||||
const ExpectedStatus: Integer;
|
||||
const URL: String;
|
||||
const RequestAccept: String;
|
||||
const ResponseContentType: String);
|
||||
var
|
||||
res: IMVCRESTResponse;
|
||||
begin
|
||||
res := RESTClient
|
||||
.ClearHeaders
|
||||
.Accept(RequestAccept)
|
||||
.Get(URL);
|
||||
Assert.AreEqual<Integer>(ExpectedStatus, res.StatusCode);
|
||||
Assert.StartsWith(ResponseContentType, res.ContentType);
|
||||
end;
|
||||
|
||||
procedure TServerTest.TestObjectDict;
|
||||
var
|
||||
lRes: IMVCRESTResponse;
|
||||
|
@ -28,7 +28,10 @@ uses
|
||||
EntitiesProcessors in '..\Several\EntitiesProcessors.pas',
|
||||
MVCFramework.JSONRPC.Client in '..\..\..\sources\MVCFramework.JSONRPC.Client.pas',
|
||||
MVCFramework.JSONRPC in '..\..\..\sources\MVCFramework.JSONRPC.pas',
|
||||
MVCFramework.Serializer.Commons;
|
||||
MVCFramework.Serializer.Commons,
|
||||
MVCFramework in '..\..\..\sources\MVCFramework.pas',
|
||||
MVCFramework.Serializer.Text in '..\..\..\sources\MVCFramework.Serializer.Text.pas',
|
||||
MVCFramework.Serializer.HTML in '..\..\..\sources\MVCFramework.Serializer.HTML.pas';
|
||||
|
||||
{$R *.res}
|
||||
|
||||
|
@ -133,6 +133,9 @@
|
||||
<DCCReference Include="..\Several\EntitiesProcessors.pas"/>
|
||||
<DCCReference Include="..\..\..\sources\MVCFramework.JSONRPC.Client.pas"/>
|
||||
<DCCReference Include="..\..\..\sources\MVCFramework.JSONRPC.pas"/>
|
||||
<DCCReference Include="..\..\..\sources\MVCFramework.pas"/>
|
||||
<DCCReference Include="..\..\..\sources\MVCFramework.Serializer.Text.pas"/>
|
||||
<DCCReference Include="..\..\..\sources\MVCFramework.Serializer.HTML.pas"/>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
|
@ -133,7 +133,7 @@ begin
|
||||
.AddMiddleware(TMVCCompressionMiddleware.Create);
|
||||
{$IFDEF MSWINDOWS}
|
||||
MVCEngine.SetViewEngine(TMVCMustacheViewEngine);
|
||||
RegisterOptionalCustomTypesSerializers(MVCEngine.Serializers[TMVCMediaType.APPLICATION_JSON]);
|
||||
RegisterOptionalCustomTypesSerializers(MVCEngine.Serializer(TMVCMediaType.APPLICATION_JSON));
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user