Added TEXT serializer - Improved content type handling in case of errors and no_route_found cases.

This commit is contained in:
Daniele Teti 2024-02-22 19:18:34 +01:00
parent f9076a4732
commit 02c0ae0f37
8 changed files with 525 additions and 101 deletions

View File

@ -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);

View File

@ -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

View 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.

View File

@ -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;

View File

@ -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;

View File

@ -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}

View File

@ -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>

View File

@ -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;